tag付け機能の実装例
WebアプリフレームワークDjangoの書籍1を勉強中です。 書籍の1章で説明されているコードSnippetの共有アプリをベースに機能を追加して勉強を継続しています。
今回、Snippetにtagを付ける機能を追加したので、その内容をメモしています。
機能追加前
tag付け機能は、Snippetの編集時に設定可能とし、topページにtagを表示するようにしました。
機能追加後
Snippetの編集時に、チェックボックスをonにしたtagを付与する機能を追加し、topページにtagを表示させた結果です。
ソースコード
Djangoはtag機能のためにdjango-taggitというライブラリが利用できます。 変更が必要なファイルは、settings.py, model.py, form.py, view.py, HTML templateと少し多いですが、個々のファイルの変更はそれほど多くありません。
django-taggitの使用準備
django-taggitがインストールされていない場合、以下でインストール可能です。
$ pip install django-taggit
Djangoプロジェクト中の、settings.pyに以下のように'taggit'を追加します。
INSTALLED_APPS = [
...
'taggit',
...
]
migrateすれば、taggitが使用可能となります。
$ python3 manage.py migrate
model.py
追加後
以下、更新箇所は僅かなので、追加後のソースコードのみ記載します。 TaggableManager のコンストラクタの引数のblankのデフォルトはFalseです。 Snippetに一つもtagがない状況を許容するため、Trueとしています2。 TaggableManager のリレーションは何になるか気になったのでソースコードを見ると、 クラス変数を変更しない限りはManyToManyが使用されるようでした。
from django.conf import settings from django.db import models from taggit.managers import TaggableManager # 追加classSnippet(models.Model): title = models.CharField("タイトル", max_length=128) code = models.TextField("コード", blank=True) description = models.TextField("説明", blank=True) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name="投稿者", on_delete=models.CASCADE) created_at = models.DateTimeField("投稿日", auto_now_add=True) updated_at = models.DateTimeField("更新日", auto_now=True) tags = TaggableManager(blank=True) # 追加def__str__(self): return self.title
管理画面からのtag追加
model更新後に、以下でDBを更新すれば、adminの管理画面からtagが追加・編集できるようになります。
$ python3 manage.py makemigrations $ python3 manage.py migrate
tagの追加画面から、例えばPythonというtagを追加します。 なお、slugはURLの生成に利用される項目で3、この画面ではnameを記入すると自動でslugも記入されます。 ただし、日本語や記号はslugには使われないため、自分で記入する必要があります(次の画面のC++タグのslugは自分でcppとしています)。
form.py
追加後
tagの入力フォームをチェックボックスにするため、 ModelMultipleChoiceFieldのwidgetの設定を用いています。 Formクラスのクラス変数fieldsに"tags"を追加すれば、入力フォームにtagsの項目が追加できます。
from django import forms from snippets.models import Snippet, Comment from taggit.models import Tag # 追加classSnippetForm(forms.ModelForm): # 追加 tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all(), widget=forms.CheckboxSelectMultiple()) classMeta: model = Snippet fields = ("title", "code", "description", "tags") # "tags"追加
views.py
追加後
form.save(commit=False)とした場合、save_m2m()を呼び出す必要があります4。 これが無いと、tagフォームの入力結果が保存されません。 ManyToManyのリレーションは、親オブジェクトをまず保存する必要がありますが、comit=Falseの時はこの操作が行われないためです5。
classSnippetNewView(LoginRequiredMixin, View): form_class = SnippetForm template_name = "snippets/snippet_new.html" login_url = "/snippets/accounts/login/"defget(self, request, *args, **kargs): form = self.form_class() return render(request, self.template_name, {"form": form}) defpost(self, request, *args, **kargs): form = self.form_class(request.POST) if form.is_valid(): snippet = form.save(commit=False) snippet.created_by = request.user snippet.save() # 追加。commit=False時にはこれが無いとtagが保存されない。 form.save_m2m() return redirect(snippet_detail, snippet_id=snippet.pk) return render(request, self.template_name, {"form": form})
HTML template
追加後
tag用の列をtableに追加しています。 forloop.lastを用いて、forループの最後だけ","を表示しないようにしています(もっとスマートなやり方がありそう)。 tagがない場合は"-"を表示させています。
<tableclass="table"><thead><tr><th>投稿者</th><th>投稿日</th><th>タイトル</th><th>タグ</th></tr></thead><tbody> {% for snippet in snippets %} <tr><th>{{ snippet.created_by.username }}</th><th>{{ snippet.created_at }}</th><th><ahref="{% url 'snippet_detail' snippet.id %}">{{ snippet.title }}</a></th><!-- 付与されたtagを全て表示 --><th> {% if snippet.tags.names %} {% for tag in snippet.tags.names %} {% if forloop.last %} {{ tag }} {% else %} {{ tag }}, {% endif %} {% endfor %} {% else %} - {% endif %} </th></tr> {% endfor %} </tbody></table>
書籍
まず実際にWebアプリを作り、その後詳細な機能を解説する形式で、Djangoを学ぶことができます。