LoginSignup
10
9

More than 3 years have passed since last update.

Django 初心者が簡単なアプリをつくる3

Last updated at Posted at 2020-05-20

はじめに

Django 初心者が簡単なアプリをつくる2の続き。前回はurls.py,views.py,templateを作成し、CRUDのR、つまりRead部分を作成した。今回はCreate部分をClass-based-viewで作成したい。

初心者が簡単なアプリをつくるシリーズ目次

環境

Ubuntu 20.04 LTS
Python 3.8.2
Django 3.02

前提

プロジェクト名はconfig、アプリ名はmyapp とする。つまり以下2つのコマンド実行済み

(myenv)$ django-admin startproject config .
(myenv)$ python manage.py startapp myapp

templatesディレクトリはmanage.pyと同じ階層に作成、setting.pyも修正済み。(「初心者が簡単なアプリをつくる1」を参照のこと)

0. 大まかな設計

本当は映画タイトルと監督名と視聴日と感想を1画面にで一気に入力できればと思ったのだが…。ForeignKeyで紐づけているとそう簡単にはいかないことが判明(こういうのって実際に作ってみてから不都合がわかるよね…)。「インラインフォームセット」なるもので実装はできるものの、初心者にはややハードルが高いので、監督名を入力、映画を入力、感想を入力、と別々に登録する方式を採用。イメージは下図。

08116AEF-A591-4655-A10C-14065C8E4BB8.jpeg

1. Create の実装

今までは127.0.0.1:8000/admin/の管理サイトから各データを入力していたが、webから入力するためのページをつくりたい。それがCreate部分。つくる順番は以下の通り。

  1. forms.py をつくる
  2. urls.py でアドレスを決定する
  3. views.py にどうやって監督名や映画の情報を入力するかを書く
  4. 入力するページのHTMLファイルをつくる

このformというのはmodelというデータベースに入力するための入り口。というよりmodelとHTMLのフォームをつなぐインターフェースと思われる。Djangoをいじり始めた頃、DjangoのFormとHTMLのフォームを一緒のモノと考えていたために何をしているのかさっぱり分からなかった。Formとフォームは別物です。なんでformなんかがあるんだろうとずっと疑問に思っていたが、洗濯した衣服(データ)が勝手にタンス(model)に入るわけがない。ちゃんと畳んで入れる人が必要で、それがform+viewなんだなと自己解決。

E635ADCB-33A0-4A9A-9BFA-C7593213C02E.jpeg

上図の真ん中部分のFormをform.pyに記述する。

form.py のコード

myapp/form.py
from django.forms import ModelForm
from myapp.models import Movie, Director, Log

class DirectorForm(ModelForm):
    class Meta:
        model = Director
        fields = ('name',)

class MovieForm(ModelForm):
    class Meta:
        model = Movie
        fields = ('title','watch_date', 'director')

class LogForm(ModelForm):
    class Meta:
        model = Log
        fields = ('movie','text')

Modelが3つあるのでFormも3つ記述。Formクラスにはforms.Formforms.ModelFormの2種類ありModelFormの方が楽。formは入力されたデータの入り口なので、そのままmodelと直結するから。model(データベース)は何を使うか、どのfield(項目)を使うか、をclass Meta以下に記述するだけ。次に入力ページのアドレスをurls.pyに記述する。

urls.py のコード

myapp/urls.py
from django.urls import path, include
from myapp import views

app_name = 'myapp'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('movie/<int:pk>/', views.MovieDetailView.as_view(), name='movie_detail'),
    path('register/director/', views.RegisterDirectorView.as_view(), name='registerdirector'), #これ追加
    path('register/movie/', views.RegisterMovieView.as_view(), name='registermovie'), #これ追加
    path('writing/log/', views.WritingLogView.as_view(), name='writinglog'), #これ追加
]

次にurls.pyで指定したように、Class-based-view (ここでは RegisterDirectorViewRegisterMovieViewWritingLogView のこと)を作成する。

views.py のコード

まずは監督名を登録するためのView。

myapp/views.py

class RegisterDirectorView(generic.CreateView):
    model = Director
    form_class = DirectorForm
    template_name = 'myapp/register.html'
    def get_success_url(self):
        return reverse('myapp:registermovie') 

次に映画の情報を登録するView。

myapp/views.py

class RegisterMovieView(generic.CreateView):
    model = Movie
    form_class = MovieForm
    template_name = 'myapp/register.html'
    def get_success_url(self):
        return reverse('myapp:movie_detail', kwargs={'pk': self.object.pk }) 

最後に映画の感想を書くView。

myapp/views.py
class WritingLogView(generic.CreateView):
    model = Log
    form_class = LogForm
    template_name = 'myapp/register.html'
    def get_success_url(self):
        return reverse('myapp:movie_detail', kwargs={'pk': self.object.movie.pk }) 

以上3つのCreateViewに共通しているのは以下の4項目。

  1. 入力されたデータを格納するデータベースは何か(model = の部分)
  2. 入力するformは何を使うのか(form_class = の部分)
  3. 入力画面を表示するHTMLファイルは何か(template_name = の部分)
  4. データの入力も、データベースへの格納も上手くいったら、どこのページを表示するか(def get_success_url の部分)

この4項目を決めていればあとは自動的に色々やってくれるのがCreateView。

3つのviewの最後の行を解説

1. return reverse('myapp:registermovie')

RegisterDirectorView。reverse関数とは()に書かれたアドレスへ飛んでいけという意味。今回のmyapp:registermovieはurls.pyに記述したpath('register/movie/', views.RegisterMovieView.as_view(), name='registermovie'),の最後の部分に該当。つまり127.0.0.1:8000/myapp/register/movie/へ行け、ということ。

2. return reverse('myapp:movie_detail', kwargs={'pk': self.object.pk })

RegisterMovieView。kwargs={'pk': self.object.pk }はキーワード引数ってやつで辞書型。自分の理解では…入力されたデータはインスタンス化されてオブジェクト(インスタンス)というモノになる。それがself.object。そのモノには自動的にpk(プライマリーキー:勝手につけられるidナンバー)がついていて、そのpk(self.object.pk)はmovie_detailのpk(辞書のkey部分)に紐づけされる。それを使ってmovie_detailのページに行けと。つまりオブジェクト「ソナチネ:北野武:2020-05-01」のpkが1とすると、127.0.0.1:8000/myapp/movie/1/に飛んでいけという意味。

3. return reverse('myapp:movie_detail', kwargs={'pk': self.object.movie.pk })

WritingLogView。キーワード引数部分。先程はself.object.pkで今回はself.object.movie.pkになっている。なぜか。RegisterMovieViewのself.objectは映画のオブジェクトだったが、WritingLogViewのself.objectは感想のオブジェクトだから。感想に紐付けられている映画のpkを引っ張って来るためにself.object.movie.pkとなる。ちなみにこのmovie部分は、models.pyのclass Log(models.Model)で記述したmovieを意味している。

HTMLファイルをつくる

先程のコードに記述(template_name = 部分)したretister.htmlをつくる。templatesディレクトリ、その下のmyappディレクトリの中にregister.htmlファイルを作成し、以下のコードを記述する。

templates/myapp/register.html
<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">save</button>
</form>

これがHTMLのフォーム。{% csrf_token %}はセキュリティ上必要なおまじないと思って絶対に書く。次の{{ form.as_p }}でformの内容が自動的に作られて表示される。監督名入力、映画情報入力、感想入力の3つのフォームがひとつのコードで済むという。すごい。むっちゃ賢い。

register.htmlの表示結果

ブラウザに直接http://127.0.0.1:8000/myapp/register/director/を入力すると「監督名入力画面」へ。http://127.0.0.1:8000/myapp/register/movie/ならば「映画情報入力画面」へ。http://127.0.0.1:8000/myapp/writing/log/ならば「感想入力画面」へ飛ぶ。
「映画情報入力画面」の監督フィールドや「感想入力画面」のタイトルフィールドはドロップダウンの選択式になっている(ForeignKeyでひもづけされているため。)

監督名入力画面 映画情報入力画面 感想入力画面
Screenshot from 2020-05-20 20-09-30.png Screenshot from 2020-05-20 20-08-22.png Screenshot from 2020-05-20 20-39-27.png

以上でCreate部分が実装できた。

あとがき

これでCRUDの半分まで来たわけで、残るはUPDATEとDELETE。でも次回はちょっと寄り道をして今までClass-based-viewで書いてきたコードをFunction-viewで書き直し、両方比較しながらDjangoの仕組みを眺める予定。間違い等ありましたら、ご指摘ご指導よろしくお願いいたします。

10
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9