nikkie-ftnextの日記

イベントレポートや読書メモを発信

イベントレポート | #imas_hack にてPythonでアイドルのシルエットをアスキーアートっぽくしました

はじめに

いつも心は虹色に! nikkieです。
忙しい年の瀬ではありますが、アイマスハッカソンに参加してきました。
作りたいプロダクトのアイデアを一通り試すことができ、他の方の取り組みからエンジニアという副業をアイマスでハックするという視点に気づくことができました。

イベントの概要

【増枠】アイマスハッカソン2018 - connpass

プロデューサーの皆様のあふれんばかり愛を表現する場として今回のハッカソンを企画しました。
皆様のアイドルへの愛を表現する場として、 また同僚のアイドルへの愛を感じる場として、是非お楽しみください!

2016年の初回から存在は知っていたアイマスハッカソン
バリバリ活動されている方の中に入ることに参加前は不安も感じていました。
プロデューサーとして愛はあれども副業メインすぎるためです。
参加してみたところ、不安は杞憂で、1日楽しめました。

プロデューサーとしてのnikkie

取り組んだこと

ニコニコ動画で見た、アイドルのシルエットのアスキーアートをプログラムから自動生成することに取り組みました。

▼こんなアスキーアートを作りたい f:id:nikkie-ftnext:20181223003240j:plain

シルエットの切り出しについては、前日に触っています:イベントレポート | 第94回 #pyhack にてDjango Girls Tutorial Extensionで手を動かし、Background Removal APIを触りました - nikkie-ftnextの日記
シルエットが切り出された前提で、アスキーアートのように画像を加工することに取り組みました。

  • 色付きの四角と白い四角を並べて表現することにする(まずMVPを作る)
  • 色付きの四角はアイドルのイメージカラーとする2
  • 四角に置き換える際は、以前取り組んだモザイクアートのプログラムが流用できそう
  • まずシルエットを2色に減色する。四角で置き換えるため格子状に区切り、どちらの色が多いかで、色付きの四角で置き換えるか、白い四角で置き換えるか判断する

成果物

f:id:nikkie-ftnext:20181224232020p:plain

ソースコードはこちら:
20181223 #imas_hack · GitHub

開発環境

  • Python 3.6.6
  • jupyter==1.0.0
  • matplotlib==3.0.2
  • numpy==1.15.4
  • Pillow==5.3.0
  • scikit-learn==0.20.2

notebookのポイント

画像の原色処理には以下を使います。

Pillowを使う理由は、キャラクターを抜き出した画像の仕様によります。
remove.bg で抜き出しているのですが、背景の部分の画素はA(alpha 透過度)が0に設定されているだけで、RGBの色情報を持ちます。
次のQiita記事を試したところ、背景込みで2色に原色した画像ができました:【機械学習】scikit learnでの画像減色プログラム - Qiita
(なお、KMeansでのクラスタリングでRGBの値のみを使うのは妥当と考えています)
元の画像のAの情報を保持する必要があるようなので、opencvから使い慣れたPillowにスイッチしました。
減色後に元の画像と同様のAの値を設定することで背景を消すことができました。

減色処理に使う画素の範囲についても2通り考えられます。
一つは背景込みで全部の画素を使う方法、もう一つはA=0となっている背景の画素を除く方法です。
現時点ではどちらを使うかは画像によるという結論です。
ソースコード箱崎星梨花のケースでは全部の画素を使ったところイマイチだったので、背景の画素を除きました)

numpyでの画像の扱いについて

  1. Pillowで画像を読み込み、RGBAのタプルからなるリストを作る(画像の縦と横の情報を消し、一列に並べた状態):getdata
  2. 1のリストの各要素からRGBの値のみ取得して新たなリストを作る(このRGBのデータをクラスタリングに使う。ここで背景の画素を除く場合もある)
  3. 2のRGBのリストを行列に変換(2で画素を除いていない場合、サイズ:(画像の縦 × 横, 3チャネルRGB))
  4. 3の行列をKMeansに渡してクラスタリング。2色を見つける
  5. 3の行列について各画素がどちらの色になるか判定する。このとき1で読み込んだ画素のAの情報を追加する(2で画素を除いていない場合、サイズ:(画像の縦 × 横, 4チャネルRGBA)という行列ができる)
  6. 画像を表す状態に行列のサイズを変換する:reshape
  7. 行列からRGBAの画像に変換する:fromarray

5ではnp.uint8という型を指定する必要がありました。(これを指定しないと画像が表示されません)
また5でnp.append()を使って一次元配列の末尾に要素を追加するのにハマりました。
参考:配列末尾へ要素を追加するNumPyのappendの使い方 - DeepAge

第三引数を指定しないと第一、第二引数で指定された配列のshapeとは関係なく一次元配列が生成されます。

2色に減色した画像を色付きの四角と白い四角で作成する処理は、過去に作ったモザイクアートのプログラムをベースにしています。

  • 画像を5×5の格子に分割(余りが出ないようにするためにサイズが5の倍数になるようにcropしておく)
  • 減色処理のうちどちらの色が多いか数える。ここで多い方の色で塗ると減色した2色からなるモザイク調になる
  • 塗り方の指定をアイドルのイメージカラーと白色に変更(ここは人の目で見て対応させています)

改良点はいくつかありますが、アイデアが実現できたのでハッカソンでの進捗には満足しています。

  • ノートブックを複製しているので、パッケージ化する
  • 減色する色を3−4色に増やし、この色はイメージカラーの四角、この色は白い四角というように人の目で判断する
  • 四角以外の図形を導入してみる

あにべんLT再演しました

副業メインでも愛を叫びたかったので、5分LTしました。
「もう時間がないんです」を差し込んでもらえたり、春香カラーのサイリウム降っていただけたり、発表してよかったです。

他の方の取り組みから

今回参加して気づいたのはエンジニアという副業をアイマスでハックする視点です。
具体的には、

  • 新しい技術を触るときにアイマスへのコントリビュートを兼ねた開発をする(例:Vue.jsを触ってアイマスの〇〇を作る)
  • 開発体験をアイマスでハックする(テストデータにアイドルの名前を使うことでノッて開発できる)

の2つで、「個人開発の中に取り入れたいな」と思っています。

皆さんの取り組みは非常に興味深くて、

  • 曲同士の似た部分を判定
  • Dockerコンテナの名前をアイマスっぽくする

などなど、成果物が気になる取り組みがたくさんありました。
また、コードが書けることはコントリビュートに必須ではないことも分かりました。
Microsoft Flow」などノンコーディングなツールでカバーできるため、大事なのはコントリビュートしたい強い気持ちと言えそうです。

知った中で気になったものを挙げておきます。

感想

技術で真剣に遊ぶのはやっぱりいいですね。
もくもく会で少しずつでも積み上げてきたから、今回やりたいことが一通り実現できたのだと考えています。
全力で遊んで少しはできるようになった減色処理やnumpyでの画像の扱いが、エンジニアとしての幅を広げることにもなると考えています。

参加者は文脈を共有しているので、LTはめちゃくちゃ盛り上がりました。
LT中にアップルパイつまみ食いしちゃったり4、マイニング始めたりと、字面だけ見たらカオスすぎですが、楽しませていただきました。
LTを盛り上げられるのも、歴戦のプロデューサーだからこそだと思います。

会場スポンサーのSpeeeさん、オフィスの蔵書量がすごかったです。
図書館になっていて、同じ背表紙の色のオライリー本で各段が埋まっていて圧巻でした。
同じスペースでOSS Daysというイベントを定期的に開催されているそうで、またお邪魔してみたいです。

参加者、運営者、スポンサーの皆さま、1日どうもありがとうございました。
本当に楽しかったので、今後も参加していきたいです。


  1. いわしまんさんのブログ記事 【イベント】WEBエンジニア勉強会#10にお邪魔してきた話 - Rのつく財団入り口 より。ハッカソンに飛び込むのにこの言葉に背中を押していただきました。ありがとうございます。(次回お会いしたら、直接お礼をお伝えしよう)

  2. アイドルのイメージカラー - アイマスDB(情報まとめ)

  3. https://kotobank.jp/word/RDF-49

  4. 「アップルパイ・プリンセス」でアップルパイを作る - #つくりおき

イベントレポート | 第94回 #pyhack にてDjango Girls Tutorial Extensionで手を動かし、Background Removal APIを触りました

はじめに

いつも心は虹色に! nikkieです。
2018年のもくもく会納めに #pyhack に参加してきました。

勉強会の概要

(第94回)Python mini Hack-a-thon - connpass

スプリントのゆるい版みたいな感じで各自自分でやりたいことを持ってきて、勝手に開発を進めています。参加費は無料です。 初めての方も常連さんもぜひご参加ください。

今回は、常連さんから初参加の方まで、満員御礼といった感じでした。

取り組んだこと

  • Django Girls Tutorial ExtensionのDjango2系での動作確認
  • 画像の背景を除いてくれるAPIを試す(Background Removal API

ExtensionのDjango2系での動作確認

Django Girls Tutorialの拡張版(翻訳進行中)のコメントの章Django 2.x系に書き換えたらどうなるかを検証しました。

この章に取り組むと、コメントの表示機能や入力機能、管理者が承認/削除する機能が追加されます。
Django 2.x系向けの変更点は、urls.pyをpath関数で書き換えるくらいでした。(url関数でも動くので必須ではありません)

■記事一覧画面(承認されたコメントの数が表示される)
f:id:nikkie-ftnext:20181223003117p:plain

■記事詳細画面(ログインユーザはコメントを管理できる。ログインしていないユーザもコメントが可能)
f:id:nikkie-ftnext:20181223003200p:plain

検証が終わったので、原文のコードについてPRを出しました。
今後は日本語翻訳のレビューを進めていきます。
検証時に気づいたことをブログにまとめているところです。(長くかかりそうなので次の作業へスイッチしました)

画像の背景を除いてくれるAPI

人物が写った画像(写真)の背景を消し、人物だけを取り出すAPIを最近知りました。

知ったときに思ったのは、「アニメ画像からアニメキャラクターを取り出せるのでは?」でした。

切り出したキャラクターを加工して、以下のアスキーアートをプログラムから作ろうとしています。
色付きの四角と枠線だけの四角から構成されるシルエット画像です。
(2018年のニコニコ動画でのアイドルマスターシリーズ一挙放送で見かけたアスキーアートです)
f:id:nikkie-ftnext:20181223003240j:plain

手元にモザイクアートのプログラム(Python製)があるので、これをベースに実現できると考えています。
背景を除くAPIで切り出したキャラクターだけの画像について、格子状に分割し、各領域を色で塗りつぶすのか否かを判定するというロジックで実装予定です。
(23日の某ハッカソンで試します)

このAPI(Webアプリ)で 遊んで どんな画像なら切り出せるかを検証していました。

  • キービジュアルのような大きめのアニメイラストは切り出せる(響け!ユーフォニアム、君の名は)
  • ARキャラクターは切り出せる(デレステや舞台めぐりのAR写真)
  • 私のツイッターアイコンのような顔のアップの画像からは切り出せない
    • 画像の領域を広げてみたが、今のところ成功していない

API Beta版のテストユーザを募集していたので、申込んでみました。(想定していない使い方だと思いますが、通ることを祈ってます)

他の方の取り組みから

自分のメモとしてツイートを一元化。

感想

本編から懇親会と非常に楽しい時間でした。
#pyhack を機に、ほぼ毎週のようにPythonもくもく会に参加し始めて1年が経ち、振り返ると感慨深いものがありました。
次回は冬山合宿、せっかくのまとまった開発期間なので、取り組むことを決めて参加しようと思います。
皆さま、1日ありがとうございました!

Django Girls Tutorial修了者が投票アプリチュートリアルでDjangoの理解を深めました(その3, 4編)

この記事は、Django Advent Calendar 2018 5日目の記事です。

Django公式チュートリアル(投票アプリ作成)その3・その4に取り組んで得た気づきをアウトプットします。

はじめに

いつも心は虹色に! nikkieです。
Django公式ドキュメントのチュートリアル(投票アプリ)で手を動かした後のアウトプットが眠っていたので、アドベントカレンダーを機に消化することにしました。
その3(ビューとテンプレート)とその4(汎用ビューに書き直し)について扱います。
(この記事は、その5以降を進めるための足掛かりにする目的もあります。)

nikkieについて

アドベントカレンダーのリンクから来られた方向けに簡単に自己紹介します。

  • Pythonは始めて1年過ぎたくらい(執筆時点)
  • 趣味の開発や業務でWeb周りを触ってきた(Flask, Django
  • 2018年5月〜Django Girls Tutorialの翻訳・レビュー、2018年10月〜コーチ

チュートリアルの概要

はじめての Django アプリ作成、その 1 | Django documentation | Django から始まる7回構成のチュートリアルです。
作るアプリの名前をとって「投票アプリチュートリアル」と呼ぶことにします。
なお、私のDjangoレベルは「Django Girls Tutorial修了」です。
Django Girls Tutorialを終えてから投票アプリチュートリアルに取り組んだことで、Djangoへの理解が深まったと感じています。
また、このアウトプットでDjango Girls Tutorialと投票アプリチュートリアルを少しでも繋ぐことができれば嬉しいです。

その1・2についてのアウトプットはこちらです:
前回同様に今回も長くなってしまったので、拾い読みも推奨します。

動作環境

その3:ビューとテンプレート

はじめての Django アプリ作成、その 3 | Django documentation | Django

その3ではURLconf、テンプレート、HttpResponse(とショートカット)について、理解が深まりました。

URLconf

URLパターンは、URLを単に一般化したものです。
(中略)URLconf はURLパターンをビューにマッピングします。

プロジェクト(mysite)のsettings.pyには

ROOT_URLCONF = 'mysite.urls'

と指定されています。
これにより、mysite/urls.pyがロードされるのだと気づきました。

テンプレート

ビューにページのデザインがハードコード1されている問題を解決するために、テンプレートが導入されました。2

プロジェクト(mysite)のsettings.pyには

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True, # ここがポイント
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

と指定されています。
この指定により、INSTALLED_APPSのそれぞれのtemplatesディレクトリがテンプレートの検索対象になるそうです。

polls/templates/index.htmlではなく、polls/templates/polls/index.htmlと配置する理由は、テンプレートに名前空間を与えるためと知り、なるほどと思いました。

HttpResponse

Django Girls Tutorialでは意識してこなかったのですが、ビューはHttpResponseが肝と認識しました。

Django にとって必要なのは HttpResponse か、あるいは例外です。

先のハードコードの例で、テンプレートなしでもHttpResponseを返せばいいことが分かります。
テンプレートを返す場合は、以下の2つが必要です。

  1. loader.get_template()でテンプレートを取得する
  2. テンプレートに渡す変数を辞書で定義する(コンテキストと呼ばれる)

テンプレートの返却はHttpResponse(template.render(context, request))のようになります。

これはrender()を使って簡潔に書くことができます。
Django Girls Tutorialで見かけたrender()3が裏でやっていることはHttpResponseを返すことなのですね。

▼render()を使った質問一覧画面:
f:id:nikkie-ftnext:20181203234416p:plain:w400

モデルの取得についても複数手順からなるので、ショートカットget_object_or_404()が用意されています。
裏でやっていることは、

  1. getでオブジェクトを取得してみる
  2. オブジェクトが存在しない場合、Http404エラーを上げる
  3. オブジェクトが存在する場合、取得したオブジェクトをテンプレートに渡して返却(render()関数)

です。

▼質問詳細画面:
f:id:nikkie-ftnext:20181203234552p:plain:w400
▼質問詳細画面で404エラーが返るケース:
f:id:nikkie-ftnext:20181203234606p:plain

(再度)テンプレート

question.question_textのようなドットでつなげた構文にも検索順序があることが分かりました。

  1. 辞書のキーとして検索
  2. 属性として検索
  3. リストインデックスとして検索

今度はリンクがハードコードされています。
これは{% url %}を使って解決します。
このタグで使われるのは、path()のname引数です。4
f:id:nikkie-ftnext:20181204213336p:plain:w400

index.html{% url 'detail' question.id %}を使うことで、polls/urls.pypath('specifics/<int:question_id>/', views.detail, name='detail'),と変更しても、テンプレートは変更不要となりました5
f:id:nikkie-ftnext:20181204213248p:plain:w400

(再度)URLconf

テンプレートに名前空間がありましたが、URLconfにも名前空間が登場します。
それがpolls/urls.pyapp_nameです。 (app_name = 'polls'
名前空間の指定後は、index.htmlのテンプレートタグは{% url 'polls:detail' question.id %}となります。

その3についての疑問点

Djangoが採用している「カップリング」という概念は何を意味するのか?

Django の最も大きな目標の一つは、ルーズカップリングの維持にあります。

その4:汎用ビューに書き直し

はじめての Django アプリ作成、その 4 | Django documentation | Django

その4ではフォームを扱った後に、その3以降で追加した部分を汎用ビューで書き直します。
コードの内容が非常に簡単になって、「処理を全然書いてないけど、いいの?」と衝撃を受けました。

フォーム

残っている投票機能を実装します。
フォームを使った処理のポイント(Djangoに限らない)を学びました。

  • サーバのデータを更新するフォームには、 method="post"を使う
  • 自サイト内をURLに指定したフォームは、クロスサイトリクエストフォージェリ対策をする(Djangoでは{% csrf_token %}
  • POSTされたデータの扱いが成功した後は常に HttpResponseRedirect を返す(戻るボタン押下で再度データが送られることを防ぐため)

polls/views.pyのコードについて

  • try except else 参考:https://docs.python.jp/3/tutorial/errors.html#handling-exceptions

    else 節を設ける場合、全ての except 節よりも後ろに置かなければなりません。 else 節は try 節で全く例外が送出されなかったときに実行されるコードを書くのに役立ちます。

  • selected_choiceにget_object_or_404を使わないのは、エラー時にHttp404ではなくquestionを使ってrenderをしているからと考えた

  • URLをreverseで構築している
  • vote{{ choice.votes|pluralize }}でchoice.votesの値に応じて複数形のsの出し分けをしている

ここまでで投票と結果表示が実装されました。
▼投票画面:
f:id:nikkie-ftnext:20181204213558p:plain:w400
▼結果表示画面:
f:id:nikkie-ftnext:20181204213622p:plain:w400
▼選択肢を選んでいないときのエラー表示:
f:id:nikkie-ftnext:20181204213651p:plain:w400

汎用ビュー

一通り実装したコードを汎用ビューで書き直しました。6

汎用ビューとは、よくあるパターンを抽象化して、 Python コードすら書かずにアプリケーションを書き上げられる状態にしたものです。

手順

  1. URLconfのアップデート(汎用ビューを使う上での命名規則を適用)
  2. 古いビューの削除(index, detail, results)
  3. 汎用ビューで実装(ListViewとDetailView)

polls/views.pyの変更が劇的すぎます!

polls/views.py(Before)

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse

from .models import Choice
from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

def vote(request, question_id):
    # 変更がないため省略

polls/views.py(After)

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse
from django.views import generic # 追加

from .models import Choice
from .models import Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'


class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'


def vote(request, question_id):
    # 変更がないため省略

汎用ビューにはmodeltemplate_nameを指定しているだけです。
質問一覧画面については、コンテキスト変数の指定や、質問の取得方法の指定もしています。

終わりに

今回はここまでです。
次回、その5(テスト)からのアウトプットでお会いしましょう。
最後までお読みいただき、ありがとうございました。


  1. PyConJP 2018 の懇親会でSwall0wTech さんから聞いたハードウェアの話を思い出しました。ハードコードしたソフトウェアはハードウェアだそうです(ソースはClean Architectureと記憶)

  2. この部分、リスト内包表記をさらっと使って、output = ', '.join([q.question_text for q in latest_question_list])となっています。このあたりDjango Girls Tutorialとのギャップを感じるところです。

  3. render()Djangoビュー · Django Girls Tutorial に登場します。

  4. 次の箇所の理解が深まりました:https://docs.djangoproject.com/ja/2.1/intro/tutorial01/#path-argument-name

  5. リンク先が<a href="/polls/specifics/1/">What's up?</a>のように自動で生成されています

  6. 汎用ビューもショートカットと呼ばれるようです。render()と同じ呼ばれ方ですね。ショートカットは便利ですが、「使わずに書くとどう動くか」を知っていると、より使いこなせるんじゃないかと思います。

イベントレポート&LT報告 | #pyladiestokyo 4周年パーティ(2018/10開催)

この記事は、PyLadies Advent Calendar 2018 4日目の記事です。

PyLadiesとのあれこれを共有しましょうー
PyLadies参加したことないよっていう方も性別問わずウェルカムです(´∇`)ノ

ということで、2018年10月に参加したPyLadies Tokyo - 4周年記念パーティについて、眠っていたメモをもとにレポートします。

はじめに

いつも心は虹色に! nikkieです。
10月のPyLadies Tokyo 4周年記念パーティについてレポートします。
レポートは2つの内容で構成されます。

  • 印象的だったLTの紹介
  • 自分のLTについて

nikkieについて

アドベントカレンダーのリンクから来られた方向けに簡単に自己紹介します。

  • stapy 4代目LT王子(男性です)
  • Pythonは始めて1年過ぎたくらい(執筆時点)
  • PyLadiesとはうっすらとしたつながり(2018/10~ Django Girls Workshopのコーチ)

イベントの概要

PyLadies Tokyo - 4周年記念パーティ - connpass

この10月でPyLadies Tokyoは最初のMeetup開催から丸4年を迎えます。
設立4周年をみんなでお祝いするために、持ち寄り形式 & LT形式で4周年記念パーティーを開催します!
(略)
本イベントは男性も参加できます!

お祝い&アウトプットの機会ということで、LTに手を挙げました。

当日の様子は、以下のリンクからツイートで確認できます。(2018/10/08の #pyladiestokyo ツイートを検索)
https://twitter.com/search?f=tweets&vertical=default&q=%23pyladiestokyo%20since%3A2018-10-08%20until%3A2018-10-09&src=typd&lang=ja

印象的だったLT

私の主観でLTを紹介していきます。

Data Classesをつかってみよう(どりらんさん)

https://nbviewer.jupyter.org/format/slides/github/drillan/PyLadiesTokyo04LT/blob/master/slides.ipynb#/

データクラスを詳しく解説されています。(毎回情報が詰まっていて、素晴らしいと思っています)
スライドを元に手を動かせば、データクラスは一通り理解できそうです。

Pythonと数学と多面体とペーパークラフト(はむかずさん)

衝撃を受けたのが、数学の理論を取り入れたハサミ!

PDF生成ライブラリは初めて知りました。
PDF生成 (ReportLab) | Python-izm

はむかずさんの関連エントリ
ペーパークラフト準正多面体 – はむかず!

Seleniumクローラーを作っていて詰まったこと(あおいさん)

スクレイピングツールSeleniumやSplashの話を聞けて勉強になりました。
(BeautifulSoupだとJSで描画する要素が取得できなくて、Seleniumを知ったところでした)
Splash参考:[Python]ヘッドレスブラウザSplashと共にスクレイピングをしたメモ - Qiita

LTリンク

LT: 翻訳は人のためならず

Django Girls Tutorialは、2018年夏にDjango2.0.x系に対応しました!
翻訳に参加することは他の人のためだけでなく自分のためになることを学んだので、学んだこと3点を共有しました。

  1. Djangoの使い方
    • 何度も手を動かしたことで機能追加する流れが見えてきました。
    • テンプレート(既存の変更) →URL→モデル→ビュー→テンプレート(新規追加)
  2. Django Girls Tutorialに精通
  3. コミュニティの力
    • Django2.0.x系対応を成し遂げたのは、一人ひとりが少しずつ力を出し合った結果と考えています

LTの補足

交流タイムに聞かれた内容を元に補足します。

Django Girls Tutorialには、Django1系に対応した日本語版があります。
2018年5月のDjango Congressにて2.0.x版Tutorialの日本語翻訳プロジェクトが立ち上がりました。

翻訳では、Django1系対応の日本語版チュートリアルをベースにしています。(新規に一から翻訳する必要はなかったわけです)
はじめてプログラミングをする方向けに書かれているので、「英語→原文に沿った日本語→わかりやすい日本語」と翻訳+言い回しの推敲を行いました。

翻訳に参加しましたが、私はDjangoの上級者というわけではなく、Django Girls Tutorialを1回やった初学者です。
また、ドキュメントなどで英語を読むのには慣れていますが、聞く・話すはあまり得意ではありません。
それでも翻訳はできる(しかも自分の勉強にもなる!)ので、「やってみたいな」という方は一歩踏み出していただけたらと思います。

感想

LTと交流タイムが交互にあり、美味しい軽食をつまみながら交流という、非常に楽しい時間でした。
(例年パーティの持ち寄りには皆さん気合が入るらしいです)
N周年パーティが気になるという男性の皆さん、来年はぜひ会場でお会いしましょう 笑
また、「Django Girls Workshopに参加された方と5周年パーティーで会えたら素晴らしいな」と思います。
4周年パーティの運営者、登壇者、参加者の皆さま、どうもありがとうございました。

なお、PyLadiesパーティは技術書典5と重なっていました。
朝から技術書典に並び、パーティに移動、パーティ後は有志で技術書典巡りと満喫した1日でした。
(kaizumakiさん、kumappp27さん、技術書典までありがとうございました!)

おわりに

4周年パーティでPyLadiesのイベントに初めて参加しましたが、「実はもっと前のテストコードの回からつながりがあったんだな」とレポートを書いてみて気づきました。
今後もイベント資料の公開を楽しみにしています^ ^

今後は各地を巡るPyLadies Caravanが企画されており、「Django Girlsとタイアップできたら素敵だな」と思っています。
初回は福岡です:PyLadies Caravan in 福岡 - connpass

PyLadies Advent Calendar 2018 5日目は空いているようです(投稿時点)ので、アウトプットのネタがある方はぜひどうぞ!

最後までお読みくださり、ありがとうございました。

Django Girls Tutorial ExtensionsをDjango2系で動かす(Part 2/3) セキュリティ編

はじめに

いつも心は虹色に! nikkieです。
Django Girls Tutorial ExtensionsのDjango2系動作確認の第2弾です。
第1弾はこちら:Django Girls Tutorial ExtensionsをDjango2系で動かす(Part 1/3) #モグモグDjango - nikkie-ftnextの日記
先日の #モグモグDjango で確認したことをブログにまとめました。

背景

Django Girls Tutorial Extensionsでは、Django Girls Tutorialで作ったブログアプリに機能追加していきます。
ExtensionsのコードはDjango 1系で書かれており、Django Girls Tutorialの翻訳メンバーの間では「Django 2系に書き換えたいね」という話が出ていました。

Django 2系に書き換える対象は、3ページあります。

今回はセキュリティのチュートリアルを進める中で気づいたことを書きます。

ブログアプリで何が問題か

Django Girls Tutorialの中で、ログインしているユーザだけに投稿作成ボタンや編集ボタンが見えるようにしました。
参照:Djangoフォーム · Django Girls Tutorial の「セキュリティ」

これは新しい投稿の作成を完全に保護するものではありませんが、それは良い第一歩です。 私たちは拡張レッスンでより多くのセキュリティをカバーします。

確かに画面上にボタンが見えなくなったのですが、URLを推測すると記事が作れてしまうという問題があります。1
ログインしていないユーザでhttp://127.0.0.1:8000/post/new/にアクセスすると、記事の作成画面が表示されます。
f:id:nikkie-ftnext:20181110200323p:plain

セキュリティ対応

ログインが求められるように修正

URLを推測されても記事が作成できないようにする対応はそれほど大変ではありませんでした。
ビューの関数に@login_requiredを設定すると、ログインが求められるように変更できます。

blog/views.py

from django.contrib.auth.decorators import login_required # 追加
from django.shortcuts import redirect
# 省略

@login_required # 追加
def post_new(request):
    # 省略
    
@login_required # 追加
def post_edit(request, pk):
    # 省略

@login_required # 追加
def post_draft_list(request):
    # 省略

@login_required # 追加
def post_publish(request, pk):
    # 省略

@login_required # 追加
def post_remove(request, pk):
    # 省略

この時点ではログイン画面のテンプレートが用意されていないため、ログインしていないユーザで、http://127.0.0.1:8000/post/new/(記事作成のURL)にアクセスするとエラーとなります。
f:id:nikkie-ftnext:20181110200403p:plain
ですが、誰でも記事が作れる状態ではひとまず修正されました。

ログイン画面を用意する

ログイン画面用のURLの設定は、プロジェクト側で(mysite/urls.pyで)行います。

# 省略
from django.contrib.auth import views # 追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/login/', views.login, name='login'), # 追加
    path('', include('blog.urls')),
]

ログイン画面のテンプレートを追加しましょう。 blog/templatesの下にregistrationというフォルダを作り、login.htmlを置きます。
ログイン機能はDjangoに用意されたものを使うために、命名規則を守る必要があると理解しました。

blog/templates/registration/login.html

{% extends "blog/base.html" %}

{% block content %}
  {% if form.errors %}
    <p>Your username and password didn't match. Please try again.</p>
  {% endif %}

  <form method="POST" action="{% url 'login' %}">
    {% csrf_token %}
    <table>
      <tr>
        <td>{{ form.username.label_tag }}</td>
        <td>{{ form.username }}</td>
      </tr>
      <tr>
        <td>{{ form.password.label_tag }}</td>
        <td>{{ form.password }}</td>
      </tr>
    </table>

    <input type="submit" value="login">
    <input type="hidden" name="next" value="{{ next }}">
  </form>
{% endblock %}

テンプレートで使われているform.username.label_tagは、HTMLのlabel要素を作る関数だそうです。
https://docs.djangoproject.com/en/2.0/topics/forms/#rendering-fields-manually

ログインしていないユーザで、http://127.0.0.1:8000/post/new/(記事作成のURL)にアクセスすると、以下のようなログイン画面が表示されます!
(ただし、画像はログイン画面に直接アクセスしたときのものです。)
f:id:nikkie-ftnext:20181110200336p:plain

ログイン画面周りでは、あと少しだけコードを修正する必要があります。
ログイン画面のURL(http://127.0.0.1:8000/accounts/login/)を直接叩いてログインした場合、ログインしたあとにページが見つからずにエラーとなります。
f:id:nikkie-ftnext:20181124162627p:plain

この対応はmysite/settings.pyで行います。
末尾に以下の1行を追加しました。

LOGIN_REDIRECT_URL = '/'

この設定により、ログイン画面のURLを直接叩いた場合、ログインに成功後、記事一覧(http://127.0.0.1:8000/)が表示されます。

ログイン・ログアウトのためのリンクを追加

ログイン機能を使うためのリンクを追加します。

blog/templates/blog/base.html

    {% if user.is_authenticated %}
      <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
      <a href="{% url 'post_draft_list' %}" class="top-menu"><span class="glyphicon glyphicon-edit"></span></a>
    {% else %} <!-- 追加 -->
      <a href="{% url 'login' %}" class="top-menu"><span class="glyphicon glyphicon-lock"></span></a> <!-- 追加 -->
    {% endif %}

ログインしていない場合、ヘッダーにログイン画面に遷移するリンク(鍵のアイコン)が表示されます。 f:id:nikkie-ftnext:20181110200452p:plain

ログアウトの機能も実装します。

mysite/urls.py

# 省略

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/login/', views.login, name='login'),
    path('accounts/logout/', views.logout, name='logout', kwargs={'next_page': '/'}), # 追加
    path('', include('blog.urls')),
]

blog/templates/blog/base.html

    {% if user.is_authenticated %}
      <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
      <a href="{% url 'post_draft_list' %}" class="top-menu"><span class="glyphicon glyphicon-edit"></span></a>
      <p class="top-menu">Hello {{ user.username }} <small>(<a href="{% url 'logout' %}">Log out</a>)</small></p> <!-- 追加 -->
    {% else %}
      <a href="{% url 'login' %}" class="top-menu"><span class="glyphicon glyphicon-lock"></span></a>
    {% endif %}

ログインユーザの情報とログアウトのリンクがヘッダーに表示されるようになりました。
f:id:nikkie-ftnext:20181110195444p:plain

書き換えなくてもDjango2.0.9で動きました

ここまででDjango2系向けに書き換えたのは、urlpatternsのurl関数です(path関数にしました)。 Django2系でもurl関数は使えるので、Extensionsのコードのままでも動きました。
参考:Django 1.11 と 2.0 の違い (「現場で使える 基礎 Django」本の補講その1) - akiyoko blog

django.conf.urls.url() を使った書き方は、Django 2.0 でもそのまま使えるように互換性が保たれていますし、(後略)

mysite/urls.py

from django.conf.urls import url # 追加
from django.contrib import admin
from django.urls import path, include

from django.contrib.auth import views # 追加


urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^accounts/login/$', views.login, name='login'), # 追加
    url(r'^accounts/logout/$', views.logout, name='logout', kwargs={'next_page': '/'}), # 追加
    path('', include('blog.urls')),
]

mysite/urls.pyをDjango2系向けにアップデート

この記事を書く中で記述の食い違いに気づきました。

  • 原文(Django 1系):django.contrib.auth.views.login
  • セキュリティのバートで参照されているドキュメント2の書き方:django.contrib.auth.login

結論を言うと、mysite/urls.pyを以下のように書き換えた方がよいことがわかりました。

# 省略
urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/login/', views.LoginView.as_view(), name='login'), # 変更
    path('accounts/logout/', views.LogoutView.as_view(next_page='/'), name='logout'), # 変更
    path('', include('blog.urls')),
]

これはDjango 2系で動く書き方です。
LoginView()を使う書き換えをしないと、Django 2.1.xで動かないと考えています。3
django.contrib.auth.views.loginを使った書き方は、Django 2.0.xまでで動く書き方で、2.1.xでは動かないという認識です)

LogoutView.as_view()では、引数にログアウト後に表示されるページ(記事一覧)のURLを渡しています。4
mysite/settings.pyにLOGOUT_REDIRECT_URL = '/'を指定することでも同じ動きが実現できましたが、Django Adminからログアウトした場合も記事一覧に遷移するという副作用がありました。

なお、django.contrib.auth.loginを使う書き方はビュー向けであるようで、importしてからpath('accounts/login/', login, name='login')のように使うとエラーとなりました。(TypeError: login() missing 1 required positional argument: 'user'

終わりに

Extensionsでログイン機能を触ったことで、Djangoでアプリケーションが作れそうな気がしてきました。

そして、残るはコメント機能。
Django2系の動作確認最終回でまたお会いしましょう。
最後までお読みいただき、ありがとうございました。

脚注


  1. PythonAnywhereで公開されているDjango Girls Tutorialのブログアプリは第三者が記事を作れる状態なので、なるべくExtensionsのセキュリティのパートまで進めていただくのを個人的には推奨します。

  2. ドキュメントのURLのうち、Djangoのバージョンだけ2.0に変えて参照しました:https://docs.djangoproject.com/en/2.0/topics/auth/default/#how-to-log-a-user-out

  3. django.contrib.auth.views.loginのソースを見たところ、'The login() view is superseded by the class-based LoginView().'RemovedInDjango21Warning)と記載されていました。Django 2.1.xではLoginView()を使う書き方に置き換わるようです。→リリースノートを確認したら削除されていました:https://docs.djangoproject.com/ja/2.1/releases/2.1/#features-removed-in-2-1

  4. コード例:Django 1.9 から 2.1 に上げる時の備忘録 - Qiita

「The macOS installation couldn't be completed」にOS再インストールで対応しました

はじめに

いつも心は虹色に! nikkieです。
先日MacBook Proに問題が発生し、なんとか解決したので、備忘録がてら残します。

発生した問題

Macを再起動したら「The macOS installation couldn't be completed」というエラーメッセージが表示されました。

このエラーメッセージから抜け出せずに弱りました。。

  • Shutdown:シャットダウン後、再度起動すると同様のエラーメッセージが表示される
  • Restart:再起動が完了すると同様のエラーメッセージが表示される

MacBook Proについて

  • macOS 10.13.x(High Sierra1
  • FileVaultを有効にしている

解決策

いったんMacをシャットダウンした後、リカバリーモード(Cmd+R押し)で起動して、Macintosh HDにOSを再インストールしました。

注意:この方法はデータが消えないとは言い切れないので、リスキーです。自己責任で実施してください。

試したこと:セーフモード起動

OS再インストールの前にセーフモード(Shift押し)起動を試しました。
先ほどのエラーメッセージは表示されませんでしたが、プログレスバーが100%に達した後、画面が遷移しない事象が発生。。
やむなく(リスクを取って)電源ボタン長押しで強制終了しています。

Appleサポート曰く

Appleサポートの推奨する対応は、外付けハードディスクを用意することだそうです。

  1. 外付けハードディスクにOS再インストール
  2. 外付けハードディスクのOSで起動し、Macintosh HDのバックアップを取得する(必要なファイルのコピーをとる)
  3. Macintosh HDにOS再インストール

Macintosh HDにOSを再インストールすると、理論上はOSの部分だけが置き換わるはずですが、ユーザのデータに影響がないとは言い切れないため、外付けハードディスクの用意を推奨しているそうです。

外付けハードディスクについて

教訓:APFSでフォーマットできるものを選びましょう。

Mac向け・フォーマット不要」と謳った外付けハードディスクを用意したところ、APFS2でのフォーマットに失敗しました。
どうやらHigh SIerra以降のAPFSに対応していなかったようです。
「もういいや。まっさらでも仕事できないよりマシ」と割り切り、リスクを取って再インストールする決断ができました。

OS再インストールの副作用

Xcodeコマンドラインツールが消えていたので入れました。
xcode-select --install
現時点ではそのくらいで、ほとんどのデータは消えていないようです。

終わりに

OS再インストールで、データが消えずに解決して本当によかったです。
PCはいつ動かなくなるかわからないと痛感したので、クラウド上にデータを置くことを習慣化します。(日々の忙しさで後回しにしてしまいがちだったので要反省)
PCにグレートリセットがかかっても、問題ないと言えるようにしないといけませんね。

今回Appleサポートの方には非常に素晴らしい対応をしていただきました。
本当にありがとうございました。

OS再インストールにて「復旧サーバに接続できない」と表示される場合の対応(12/2 追記)

  1. Wi-Fi接続の確認
  2. PCの時刻の確認(リカバリーモードでもターミナルが起動できる。dateコマンドで確認する。再設定もdateコマンドを使う)

参考:macOSの再インストールで「復旧サーバに接続できませんでした。」とエラーが出たときの対処方法 - creativi.tea

参考情報


  1. おそらく10.13.6です(参考情報(1)によるとこの事象は10.13.4〜6で発生するらしいです)

  2. 詳細には立ち入りませんが、High Sierra以降のファイルフォーマットです

Django Girls Tutorial ExtensionsをDjango2系で動かす(Part 1/3) #モグモグDjango

はじめに

いつも心は虹色に! nikkieです。
2018/11/10に参加した #モグモグDjango で、Django Girls Tutorial Extensions がDjango2系で動作するように確認しました。
ExtensionsをDjango 2系で動かす中で気づいたことをアウトプットします。

勉強会の概要

akiyokoさんの『現場で使えるDjangoの教科書』頒布をきっかけに始まった、Djangoもくもく会です。
第3回 モグモグDjango - connpass

Djangoもくもく会 モグモグDjango を開催します. Django初心者の方から,上級者の方までどなたでもご参加下さい(^^)

この会は,しーーっんとしているもくもく会ではなく,質問や分からないところがあったら,その場で聞くことができる環境にしたい!と思っています.

取り組んだこと

Django Gitls Tutorial(Djangoでブログアプリを作るチュートリアル)にはExtensionsという拡張版があります。
Introduction · Django Girls Tutorial: Extensions
9月のPyConJP Sprintにて有志で翻訳したのですが、ExtensionsはコードがDjango 1系ということが発覚していました。1
GitHub - DjangoGirlsJapan/tutorial-extensions: Additional tasks for tutorial (翻訳はWIP、また未レビューです)
Extensions翻訳を完了させるために、コードをDjango 2系に置き換えたいとメンバー間で考えており、今回のもくもく会で着手しました。

達成したこと

Django 2系に書き換える対象は、3ページあります。

このうち、機能追加とセキュリティについてDjango2系で動作確認できました

以下では、機能追加のパートを進める中で気づいたことを書いていきます。(セキュリティについては記事を分けます)

ブログアプリに機能追加

このパートで追加する機能は、大きく分けて3つです。

  • ドラフト(下書き)の一覧機能
  • ドラフトを公開する機能
  • 記事を削除する機能

前提

以下の環境で動かしています。

なお、機能追加のチュートリアルは、9月から11月までの間にコードがDjango2系に更新されていました。(確認しやすかったです。ありがとうございます!)

Djangoの機能追加手順

これまで手を動かしてきた経験を踏まえると、機能追加するには以下の順がよさそうです。2

  1. 新規ページへ遷移するリンクを追加
  2. 新規ページのURL設定
  3. 新規ページのビュー作成
  4. 新規ページのテンプレート作成

Djangoは機能追加する際に触る箇所が多いので、「機能追加手順のチェックリストを持つと、使いやすく感じられるかな」と考えています。

ドラフトの一覧機能

まず、ドラフト一覧画面に遷移するアイコンをヘッダーに追加します。
(記事が作成できるのはログインしているユーザのみとしていたので、ドラフトが見えるのもログインしているユーザに限定しています)

blog/templates/blog/base.html

<!-- 省略 -->
  <div class="page-header">
    {% if user.is_authenticated %}
      <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
      <a href="{% url 'post_draft_list' %}" class="top-menu"><span class="glyphicon glyphicon-edit"></span></a> <!-- 追加 -->
    {% endif %}
    <h1><a href="/">Django Girls Blog</a></h1>
  </div>
<!-- 省略 -->

次に、ドラフト一覧画面のURLを追加します。

blog/urls.py

urlpatterns = [
    # 省略
    path('drafts/', views.post_draft_list, name='post_draft_list'), # 追加
]

ビューの編集では2つのことを行います。

1つ目は、記事を作成・編集したときにpublished_dateを設定しないようにすること。
これにより、作成した記事が即時公開されなくなり、ドラフト扱いとすることができます。

  • 新規作成した場合、published_dateは空(ドラフト状態)
  • ドラフトの記事を編集した場合、published_dateは空のまま(ドラフトのまま)
  • 既に公開した記事を編集した場合、published_dateの値は変わらない(公開されたまま)

2つ目は、ドラフトの記事の一覧を表示するための関数を追加すること。
ドラフトの記事はpublished_datenullです。

blog/views.py

def post_new(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            # post.published_date = timezone.now() # 削除
            post.save()
            return redirect('post_detail', pk=post.pk)
    else:
        form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})

def post_edit(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostForm(request.POST, instance=post)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            # post.published_date = timezone.now() # 削除
            post.save()
            return redirect('post_detail', pk=post.pk)
    else:
        form = PostForm(instance=post)
    return render(request, 'blog/post_edit.html', {'form': form})

# 以下を追加
def post_draft_list(request):
    posts = Post.objects.filter(published_date__isnull=True).order_by('created_date')
    return render(request, 'blog/post_draft_list.html', {'posts': posts})

最後に、ドラフト一覧のテンプレートを作成します。

blog/templates/blog/post_draft_list.html

{% extends 'blog/base.html' %}

{% block content %}
  {% for post in posts %}
    <div class="post">
      <p class="date">created: {{ post.created_date|date:'d-m-Y' }}</p>
      <h1><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h1>
      <p>{{ post.text|truncatechars:200 }}</p>
    </div>
  {% endfor %}
{% endblock %}

このテンプレートには2つのフィルタが登場します。

  • date
    • 指定したフォーマットで日付を表示するフィルタ
    • d-m-Y:日(2桁)-月(2桁)-年(4桁)
  • truncatechar
    • 文字列の後ろを切り捨てて、指定した長さ(ここでは200文字)とするフィルタ
    • 切り捨てた文字列は...と3文字で表されるので、post.textの先頭から197文字が取り出される
    • ドキュメント記載の例 {{ "Joel is a slug"|truncatechars:9 }}Joel i...Joel iの6文字+...の3文字で合計9文字)

ここまでで、ドラフトボタンからドラフト一覧画面に遷移できます。
f:id:nikkie-ftnext:20181110195137p:plain

ドラフトを公開する機能

まず、記事の詳細画面にドラフトを公開するボタンを作ります。
(公開されていない記事=ドラフトの場合のみ、公開ボタンが見えるようにしています3

blog/templates/blog/post_detail.html

    {% if post.published_date %}
      <div class="date">
        {{ post.published_date }}
      </div>
    {% else %} <!-- 追加 -->
      <a class="btn btn-default" href="{% url 'post_publish' pk=post.pk %}">Publish</a> <!-- 追加 -->
    {% endif %}

次に、ドラフト公開に使うURLを追加します。
Extensionsの原文のまま('post/<pk>/publish/')でもビューのget_object_or_404は動きましたが、チュートリアル本編のURLの設定に合わせて'post/<int:pk>/publish/'としています。4

blog/urls.py

urlpatterns = [
    # 省略
    path('drafts/', views.post_draft_list, name='post_draft_list'),
    path('post/<int:pk>/publish/', views.post_publish, name='post_publish'),  # 追加
]

最後にビューにpost_publish関数を追加します。

blog/views.py

def post_publish(request, pk):
    post = get_object_or_404(Post, pk=pk)
    post.publish()
    return redirect('post_detail', pk=pk)

追加した関数で使われているpublishメソッドですが、これは Djangoモデル · Django Girls Tutorial で定義したものです。
Extensionsにて、伏線がようやく回収されました!

記事を公開した後は記事詳細にリダイレクトするので、新規で作成するテンプレートはありません。

以上で、ドラフト公開ボタンが付きました!
f:id:nikkie-ftnext:20181110195249p:plain

記事を削除する機能

記事の削除機能の追加は、ドラフト公開機能と同様の流れです。

記事の削除ボタンが見えるのは、ログインしているユーザのみです。

blog/templates/blog/post_detail.html

    {% if user.is_authenticated %}
      <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
      <a class="btn btn-default" href="{% url 'post_remove' pk=post.pk %}"><span class="glyphicon glyphicon-remove"></span></a> <!-- 追加 -->
    {% endif %}

削除機能の場合も、URLの設定はチュートリアル本編の記載に合わせています。

blog/urls.py

urlpatterns = [
    # 省略
    path('drafts/', views.post_draft_list, name='post_draft_list'),
    path('post/<int:pk>/publish/', views.post_publish, name='post_publish'),
    path('post/<int:pk>/remove/', views.post_remove, name='post_remove'),  # 追加
]

blog/views.py

def post_remove(request, pk):
    post = get_object_or_404(Post, pk=pk)
    post.delete()
    return redirect('post_list')

deleteDjangoモデルが持っているメソッドだそうです。

こうして、記事が削除できるようになりました!
f:id:nikkie-ftnext:20181110195316p:plain

感想

Django Girls Tutorialの懇切丁寧な解説から方向転換し、Extensionsは「気になるところは自分で調べてね」という感じです。(独り立ちを意識しているのかもしれません)
ファイルの変更箇所を明確にする意図でこの記事は詳しく書いていますが、実際に手を入れる箇所はそんなに多くはありませんでした。
下書き機能もついて、ブログアプリの機能が充実してきて嬉しい限りです^ ^

教えていただいたもの

今回アウトプットが遅くなってしまいましたが、モグモグDjango参加者、運営者の皆さま、どうもありがとうございました!
Extensionsはperfectじゃなくてもいいので、なる早でdoneに持っていく所存です。

脚注


  1. path関数ではなくurl関数が使われていたため、発覚しました。

  2. 参照 週末ログ | Django Girls Tutorial翻訳レビュー 100%到達! - nikkie-ftnextの日記 なお、機能追加にモデルが必要な場合は作成が必要です。(ビューの前に作ることになると思います)

  3. ブログを書いていて気づいたのですが、Extensionsのとおりに進めると、ログインしていないユーザでも公開できてしまうみたいですね。セキュリティのパートを終えると、ログインしているユーザだけが公開できるようになりました

  4. 原文のまま('post/<pk>/publish/')だとstrのpk(例:'1')でget_object_or_404が動きます。'post/<int:pk>/publish/'とすると、intのpk(例:1)がget_object_or_404に渡ります。