- Django Sprint #0 イントロダクション
- Django Sprint #1 環境構築
- Django Sprint #2 新規プロジェクトのスタート
- Django Sprint #3 ユーザーモデルのカスタマイズ
- Django Sprint #4 トップページの作成
- Django Sprint #5 汎用ビューとCRUD 前編
- Django Sprint #6 汎用ビューとCRUD 後編
目次
- 前編の復習
- ユーザーモデルに対する実装②:ユーザー情報の更新
- ユーザーモデルに対する実装③:ユーザーの詳細ページと一覧ページ
- ユーザーモデルに対する実装④:ユーザーの削除
前編の復習
前編に引き続き、後編ではユーザーモデルのCRUDを実装していきます。動的なページの作成フローは
- models.pyの書き換え → マイグレーション
- urls.pyを編集
- forms.pyを編集
- views.pyを編集
- HTMLファイルを編集
の手順でした。後編でもそれは変わりません。
ユーザーモデルに対する実装②:ユーザー情報の更新
この章ではCRUDのUを実装します。
「動的な」URL
ユーザーの更新ページはログインページなどと違って、ユーザーごとに異なります。だからと言って、ユーザーの数だけページを用意するのは愚かな話です。その割り振りはいろいろな方法が考えられますが、一番メジャーなのはURLにuserのidを埋め込み、それによってページを分けることです。cms/urls.pyを次のように書き換えましょう。
...urlpatterns=[...path('user/<int:pk>/update/',views.UserUpdate.as_view(),name='user_update'),]...<int:pk>のintはデータの型(整数)、pkはprimary key(データに固有の番号など)の略でこの場合、ユーザーidを示します。(各モデルにはデフォルトでIDが振られています。)
この場合、例えば/user/1/update/にアクセスした場合、ユーザーIDが1であるユーザーの更新ページにつながります。
モデルフォーム
これまでと同様にforms.pyを編集します。ここでモデルフォームforms.ModelFormを使います。使い方は簡単でMetaクラスにmodelとfieldsを追加するだけです。具体的には
fromdjangoimportforms...classUserUpdateForm(forms.ModelForm):classMeta:model=UserModelfields=('username','first_name','last_name','email')def__init__(self,*args,**kwargs):super().__init__(*args,**kwargs)forfieldinself.fields.values():field.widget.attrs['class']='input'...のような形です。この場合ユーザーネーム、姓名、メールアドレスを更新できるようになっています。
アクセス権限
更新ページにアクセスできるのは「ログインユーザー」かつ「更新ページのidを持つユーザー」でなければなりません。例えば、user/3/update/にuser.id == 4のユーザーがアクセスできたら大問題です。(このような場合403エラーを返さなければなりません。)そこで、このような認証機能を実装しなければなりません。この機能はよく使うのでmixins.pyという別のファイルにまとめておきましょう。cms直下にmixins.pyを作ります。
fromdjango.contrib.auth.mixinsimportUserPassesTestMixinclassOnlyYouMixin(UserPassesTestMixin):raise_exception=Truedeftest_func(self):user=self.request.userreturnuser.pk==self.kwargs['pk']oruser.is_superuserUserPassesTestMixinではtest_funcの出力がFalseのときに強制的に403エラーを吐かせることができます。(他にmixins.pyを使うときは、例えばグループのメンバーだけをアクセス許可したい時などです。)ここでは、管理者にも権限を与えています。
あとは、views.pyを書き換えましょう。先ほどのOnlyYouMixinを読み込むのを忘れないようにしましょう。
fromdjango.contrib.authimportget_user_model,loginfromdjango.contrib.auth.viewsimport(LoginView,LogoutView,)fromdjango.httpimportHttpResponseRedirectfromdjango.shortcutsimportresolve_urlfromdjango.urlsimportreverse_lazyfromdjango.views.generic.baseimportTemplateViewfromdjango.views.generic.editimport(CreateView,UpdateView,)from.mixinsimportOnlyYouMixinfrom.formsimport(LoginForm,UserCreateForm,UserUpdateForm,)...classUserUpdate(OnlyYouMixin,UpdateView):model=UserModelform_class=UserUpdateFormtemplate_name='cms/user_update.html'defget_success_url(self):returnresolve_url('cms:user_detail',pk=self.kwargs['pk'])テンプレート
あとは、テンプレートを書き換えるだけです。
{% extends 'cms/base.html' %}
{% block title %}{{ user.username }} | {% endblock %}
{% block content %}
<sectionclass="section"><divclass="container is-mobile"><h1class="title is-4">ユーザー情報の更新</h1><h2class="subtitle is-6">Update your information</h2><hr><formmethod="post"action="">
{% csrf_token %}
<divclass="field"><labelclass="label">ユーザーネーム</label><divclass="control has-icons-left">
{{ form.username }}
<spanclass="icon is-small is-left"><iclass="fas fa-user"></i></span></div></div><divclass="field"><labelclass="label">姓</label><divclass="control has-icons-left">
{{ form.last_name }}
<spanclass="icon is-small is-left"><iclass="fas fa-user"></i></span></div></div><divclass="field"><labelclass="label">名</label><divclass="control has-icons-left">
{{ form.first_name }}
<spanclass="icon is-small is-left"><iclass="fas fa-user"></i></span></div></div><divclass="field"><labelclass="label">メールアドレス</label><divclass="control has-icons-left">
{{ form.email }}
<spanclass="icon is-small is-left"><iclass="fas fa-envelope"></i></span></div></div><divclass="field"><pclass="control"><inputtype="submit"value="保存"class="button is-success"><inputtype="hidden"name="next"value="{{ next }}"></p></div></form></div></section>
{% endblock %}
一度ブラウザ上でアクセスして確認してみましょう。
ユーザーモデルに対する実装③:ユーザーの詳細ページと一覧ページ
次はCRUDのRを実装します。
ユーザーの詳細ページ
DetailViewを使います。urls.py、views.py、user_detail.html(user_detail.htmlは新規作成)を編集します。もしログインユーザーに限定したいなら、UserDetailビューにLoginRequiredMixinも合わせて継承させましょう。
...urlpatterns=[...path('user/<int:pk>/',views.UserDetail.as_view(),name='user_detail'),]......fromdjango.views.generic.detailimportDetailView...classUserDetail(DetailView):model=UserModeltemplate_name='cms/user_detail.html'defget_context_data(self,**kwargs):context=super().get_context_data(**kwargs)context['pk']=self.kwargs['pk']returncontext{% extends 'cms/base.html' %}
{% block title %}{{ user.last_name }} {{ user.first_name }} | {% endblock %}
{% block hero %}
<sectionclass="hero is-link is-bold"><divclass="hero-body"><divclass="container"><divclass="media"><divclass="media-left"><figureclass="image is-64x64 is-left"><imgclass="is-rounded"src="https://bulma.io/images/placeholders/128x128.png"></figure></div><divclass="media-content"><h1class="title">
{{ user.last_name }} {{ user.first_name }}
</h1><h2class="subtitle">
{{ user.username }}
</h2>
{% if user.pk == request.user.pk %}
<ahref="{% url 'cms:user_update' user.pk %}"><buttonclass="button is-small is-info is-inverted is-outlined"><spanclass="icon is-small is-left"><iclass="fas fa-user-edit"></i></span><strong>プロフィールを追加する</strong></button></a>
{% endif %}
</div></div></div></div></section>
{% endblock %}
{% block content %}
<sectionclass="section"><divclass="container"><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare magna eros, eu pellentesque tortor vestibulum ut. Maecenas non massa sem. Etiam finibus odio quis feugiat facilisis.</p></div></section>
{% endblock %}
これで自分の詳細ページでは更新ページへのリンクが表示されています。
ユーザー一覧
ListViewを使います。urls.py、views.py、user_list.html(user_list.htmlは新規作成)を編集します。
...urlpatterns=[...path('user/',views.UserList.as_view(),name='user_list'),]......fromdjango.views.generic.listimportListView...classUserList(ListView):model=UserModeltemplate_name='cms/user_list.html'{% extends 'cms/base.html' %}
{% block title %}ユーザー一覧 | {% endblock %}
{% block hero %}
<sectionclass="hero is-small is-link is-bold"><divclass="hero-body"><divclass="container has-text-centered"><h1class="title">ユーザー一覧</h1><h2class="subtitle">User List</h2></div></div></section>
{% endblock %}
{% block content %}
<sectionclass="section"><divclass="container"><divclass="tile is-ancestor"><divclass="tile is-12 is-vertical is-parent">
{% for object in object_list %}
<divclass="tile is-child box"><aclass="title is-4"href="{% url 'cms:user_detail' object.id %}">{{ object.username }}</a><p>Lorem ipsum dolor sit</p></div>
{% endfor %}
</div></div></div></section>
{% endblock %}
ユーザーモデルの書き換え
ユーザーモデルに「twitter」というフィールド(カラム)を追加してみましょう。まず、models.pyを編集して、ユーザーモデルにtwitterフィールドを追加します。
...date_joined=models.DateTimeField(_('date joined'),default=timezone.now)twitter=models.CharField(_('Twitter'),max_length=50,blank=True)...次にこれをDBに反映させるためにマイグレーションしなければなりません。マイグレーションは次の二つのコマンドでした。
$docker-compose run --rm web python manage.py makemigrations
Starting code_db_1 ... done
Migrations for 'cms':
cms/migrations/0002_user_twitter.py
- Add field twitter to user
$docker-compose run --rm web python manage.py migrate
Starting code_db_1 ... done
Operations to perform:
Apply all migrations: admin, auth, cms, contenttypes, sessions
Running migrations:
Applying cms.0002_user_twitter... OK
さあこれをユーザーの更新ページと詳細ページに反映させましょう。
...classUserUpdateForm(forms.ModelForm):classMeta:model=UserModelfields=('username','first_name','last_name','email','twitter')......
<divclass="field"><labelclass="label">メールアドレス</label><divclass="control has-icons-left">
{{ form.email }}
<spanclass="icon is-small is-left"><iclass="fas fa-envelope"></i></span></div></div><divclass="field"><labelclass="label">Twitter</label><divclass="control has-icons-left">
{{ form.twitter }}
<spanclass="icon is-small is-left"><iclass="fab fa-twitter"></i></span></div></div>
...
...
{% block content %}
<sectionclass="section"><divclass="container"><divclass="level"><divclass="level-left">
{% if user.twitter %}
<divclass="level-item"><divclass="tags has-addons"><spanclass="tag"><spanclass="icon is-small is-left"><iclass="fab fa-twitter"></i></span></span><spanclass="tag is-dark"><aclass="has-text-white"href="https://twitter.com/{{ user.twitter }}"target="_blank">@{{ user.twitter }}</a></span></div></div>
{% endif %}
</div></div><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare magna eros, eu pellentesque tortor vestibulum ut. Maecenas non massa sem. Etiam finibus odio quis feugiat facilisis.</p></div></section>
{% endblock %}
ユーザーモデルに対する実装④:ユーザーの削除
さあ、本チュートリアルもこれで最後です。最後にユーザーの削除を実装します。
...urlpatterns=[...path('user/<int:pk>/delete/',views.UserDelete.as_view(),name='user_delete'),]...fromdjango.views.generic.editimport(CreateView,UpdateView,DeleteView,)...classUserDelete(OnlyYouMixin,DeleteView):model=UserModeltemplate_name='cms/user_delete.html'success_url=reverse_lazy('cms:top')...
</form><hr><ahref="{% url 'cms:user_delete' user.pk %}"><buttonclass="button is-danger is-outlined">削除</button></a></div>
...
{% extends 'cms/base.html' %}
{% block title %}{{ user.username }} | {% endblock %}
{% block content %}
<sectionclass="section"><divclass="container is-mobile"><h1class="title is-4">ユーザー情報の削除</h1><h2class="subtitle is-6">Delete your information</h2><hr><p><strongclass="has-text-danger">本当にユーザー情報を削除してよろしいですか?</strong></p><br><formmethod="post"action="">
{% csrf_token %}
<divclass="field"><pclass="control"><inputtype="submit"value="確認して削除"class="button is-danger"><inputtype="hidden"name="next"value="{{ next }}"></p></div></form></div></section>
{% endblock %}
最後に管理者画面などをうまく使っていろいろ確認してみてください。
最後に
これで終了です。本当にお疲れ様でした!
しかしまだ基本的な部分しか完成していません。ここからが皆さんの色を見せるところです!素晴らしいサービスの完成を運営チーム一同心待ちにしています。
参照
- Django Sprint #0 イントロダクション
- Django Sprint #1 環境構築
- Django Sprint #2 新規プロジェクトのスタート
- Django Sprint #3 ユーザーモデルのカスタマイズ
- Django Sprint #4 トップページの作成
- Django Sprint #5 汎用ビューとCRUD 前編
- Django Sprint #6 汎用ビューとCRUD 後編
- Django Sprint Appendix Docker関連
- Django Sprint Appendix 各種実装まとめ
- Django Sprint Appendix モデルとデータベース
- Django+PostgreSQLのアプリケーションをAWSのElastic Beanstalkにデプロイする (UTokyo Project Sprint 用)
- Django+MySQLのアプリケーションをAWSのElastic Beanstalkにデプロイする (UTokyo Project Sprint 用)