# Django ペアプロ ###### tags: `Django` `GTUGGirls` `DRF` `presentation` 2020/09/06(日) [第51回 GTUG Girls はじめてのDjango(ペアプロ) - connpass](https://gtuggirls.connpass.com/event/186437/) ## はじめに ### 想定読者 + こんな方を想定してつくりました + PC に Python がインストールされている人 + Pythonの文法はなんとなく知ってる人 + Django をインストールしたことないし、まったくしらない人 ### 最終的にできること + Django管理者ツールに触れる + DjangoでCRUDを作る + 画面の見た目をちょっと良くする ### 今回はできないこと + やりたかったけど時間的に今回はムリなこと + login user によって、画面切り替え + Django Rest Framework(DRF)でのAPI開発 ## 事前準備 + python 3.* 以上がインストールされていること + python 2 系が入っている人は、 [python をインストール後](https://www.python.org/downloads/) terminal で呼び出す時に `python3` で呼び出せばOKのときもある ## 作業ディレクトリ作成 ```python $ mkdir gtug_django $ cd gtug_django ``` ## 仮想環境を作成 ### 現在のpython を確認 ```bash $ which python /your/path/bin/python ``` 上記で、python2.* が返る場合は、`python3`で確認してみて下さい。 ```bash $ which python3 /your/path/bin/python ``` ### 仮想環境作成 ```bash # gtug_djangoディレクトリの中で $ python -m venv .venv ``` + `python -m venv <仮想環境名>`  + `.venv` は慣習的によく使われる仮想環境名 ### 確認 `.venv` ディレクトリ下に python の環境が作成されていることを確認。 ```bash $ ls -la .venv ``` ### 仮想環境に入る ```bash $ source .venv/bin/activate $ which python /gtug_test/.venv/bin/python ``` + このDjangoプロジェクトに関しては、全てこのpythonで実行する + ライブラリのインストールもこの環境に行う。 ## Djangoインストール ### パッケージ管理システム pip + pip は .venv を作った時に一緒にインストール済み。 + `which pip` で確認できる + pip を使ったライブラリのinstall 方法 1. `pip install <ライブラリ名>` 2. `pip install -r requirements.txt` ←今回はこっち ### インストール 1. `requirements.txt` 作成 ```bash $ touch requirements.txt $ vi requirements.txt ``` 2. installしたいライブラリを追記.現在 Django3系が最新ですが,まだサードパーティのライブラリが追いついていないなど色々アレなので,2系の最新を入れます `requirements.txt` ```python Django==2.* ``` 3. install ```bash $ pip install -r requirements.txt ``` 4. インストール確認 ```bash $ pip freeze ``` ## Djangoプロジェクト作成 ### 一般的なプロジェクト作成方法 ```bash $ django-admin startproject <プロジェクト名> ``` + <プロジェクト名> というディレクトリを作成して、その下にプロジェクトに必要なファイルを自動生成 + このコマンドの問題点(参照:[現場で使える Django の教科書《基礎編》](https://www.amazon.co.jp/dp/4802094744) P21 ) + このコマンドでプロジェクトを作ると、設定用のディレクトリがベースディレクトリ名(`<プロジェクト名>`)で作成されてわかりづらい + テンプレートや静的ファイルがアプリケーション毎にバラバラに配置される雛形になる ### 今回のプロジェクト作成方法 ```bash # gtug_django ディレクトリの中で $ django-admin startproject config . ``` + 第一引数に「設定用ディレクトリ名」、第二引数に 「.」 + `gtug_django` をプロジェクトディレクトリにして、設定ファイルは config ディレクトリ配下に集めるという方法 + これで、プロジェクト名は `gtug_django` 、設定ディレクトリは `config` になりわかりやすい。 + 昨今,日本のDjango界で勢力を伸ばしている [akiyoko](https://twitter.com/aki_yok)さんのオススメ方法 ### 今のプロジェクトTree ```bash $ tree . . ├── config │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py └── requirements.txt ``` ### manage.py プロジェクト管理コマンドユーティリティ * データベースやテーブルの作成変更 * 新しいアプリケーション作成 * サーバーのスタートなど ## きょうつくる blog ツールについて + CRUD機能 + [Django Girls Tutorial](https://tutorial.djangogirls.org/ja/) にインスパイアーされたブログサイト + 一部[あきよこさん本形式](https://www.amazon.co.jp/dp/4802094744)に変えて作成 + urls + http://127.0.0.1:8000/blog : 投稿した記事一覧 + http://127.0.0.1:8000/blog/create : 記事を書く画面 + http://127.0.0.1:8000/blog/1 : 1記事を表示 + http://127.0.0.1:8000/blog/delete/1 : 1記事を削除 + http://127.0.0.1:8000/blog/update/1 : 1記事を更新 + http://127.0.0.1:8000/admin : 管理画面 ## Djangoアプリケーションについてちょっと ### アプリについて + 一般的にアプリケーションというと,それだけで全ての機能を持つ完結したソフトウェア + Djangoでは、1プロジェクトの配下にある、1単体機能のこと + たとえば,ユーザーアカウントを管理する機能, + ユーザーの日記を管理する機能... + アプリケーションは、ディレクトリ毎に作成されるので、 + 他のプロジェクトにそのディレクトリを「コピペ」(に近い形)することで転用可 + 一度作れば他のプロジェクトにも使える、「ライブラリ」みたいな使い方も ### 作成方法 1. 新規アプリケーション作成 ```bash $ python manage.py startapp <アプリケーション名> ``` 2. `settings/config.py` の `INSTALLED_APPS` にアプリ追加 ### 最初に作るべき Custom User アプリケーション + まず第一に作ったほうが絶対幸せ + なぜ? + Djangoには超絶強力なツール「**管理ツール**」がある. + これを使うには superuser が必要 + superuser を作るために user アプリが必要 + Djangoは デフォルトの User アプリケーションがあるので,カスタムを作らなくても supuruser は作成可 + **HOWEVER,** + この デフォルト User アプリケーション が<font color=red>**諸悪の根源**:weary: </font>で,今後開発を進めていくうえで不幸しかもたらさない + オフィシャルなドキュメントにもこのように記載 :innocent: > プロジェクトの開始時にカスタムのユーザーモデルを使用する 新しくプロジェクトを始める場合は、デフォルトの User で十分である場合でも、カスタムユーザーモデルを作成することを強く推奨します。このモデルはデフォルトのユーザーモデルと同様に動作しますが、必要に応じて将来的にカスタマイズすることができます: https://docs.djangoproject.com/ja/2.2/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project + Custom User アプリケーション を作ってから Superuser をつくれば万事OK ### Custom User アプリケーション とりあえず、下記を行って下さい。細かい説明はあとで。 1. 新規アプリ `user` 作成 ```bash $ python manage.py startapp user ``` 1. `settings/config.py` ```python INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user.apps.UserConfig', # ←追記 ] # プロジェクトにデフォルトUserではなく,Custom User を使うように設定 AUTH_USER_MODEL = 'user.CustomUser' SITE_ID = 1 ``` 1. `user/models.py` ```python from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): pass ``` 3. `user/admins.py` ```python from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import CustomUser class CustomUserAdmin(UserAdmin): model = CustomUser admin.site.register(CustomUser, CustomUserAdmin) ``` 1. migration ```bash $ python manage.py makemigrations user $ python manage.py migrate ``` ## マイグレーションとDB + 初めて `makemigrations` をすると、プロジェクトディレクトリ直下に `db.sqlite3`が新規作成される + 【オススメ】[DB Browser for SQLite](https://sqlitebrowser.org/)を使ってサクッと確認 ![](https://i.imgur.com/iLp20bv.jpg) ## Superuser 作成 + ProjectのUseruserを作成 + 管理ツールへのログインが出来るなど,なんでも出来るユーザー ```bash $ python manage.py createsuperuser Username: admin # なんでもいい Email address: # エンターでパス出来る Password: # パスワードを記入しても表示されない.心配しなくて大丈夫 Password (again): # 同じもの⇑を書く Superuser created successfully. # これが表示されたらOK ``` + SQLite で superuser が作成されたか確認 ![](https://i.imgur.com/73ENrAQ.jpg) ## Run server サーバーを立ち上げる ```bash $ python manage.py runserver ``` + http://localhost:8000/ + ロケットは発射できた=はじめての runserver できた ## ホットリロード体験 + サーバーを立ち上げたまま + `config/settings.py` で表示環境を英語→日本語に変更 ```python #LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'ja' ``` + ファイル保存→ブラウザリロード. ## 管理ツール + Djangoの超絶便利機能 + http://127.0.0.1:8000/admin/ + さっき作った superuser でログイン + [新刊『現場で使える Django 管理サイトのつくり方』頒布のお知らせ - akiyoko blog](https://akiyoko.hatenablog.jp/entry/2020/08/12/092908) ### 管理ツールをつかってデータを追加 ![](https://i.imgur.com/EAVAs73.jpg) ## Djangoアプリケーション(本格的に) ブログアプリケーションを作成 1. `python manage.py startapp <アプリケーション名>` ```bash $ python manage.py startapp blog ``` 2. `settings/config.py` の `INSTALLED_APPS` にアプリ追加 ```python INSTALLED_APPS = [ : : 'user.apps.UserConfig', 'blog.apps.BlogConfig', # 追記 ] ``` ## Model ### データ管理はすべて models.py で行う + テーブル作成 + データの定義を作成 + データの定義を変更 + など... ### 特徴 + [django.db.models](https://docs.djangoproject.com/ja/2.2/topics/db/models/) に用意されている「フィールド」とよばれるオブジェクトを使いデータを定義 + データの型や、オプション(文字数や、Default値など)も設定可 + これを使って定義することでデータ一つ一つを python のオブジェクトとして扱う事ができる + ForeignKeyで、他のテーブルを参照 + [Model field reference Field types](https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-types) field type (よく使うものだけ pickup)| 型 ---|--- BooleanField|true/false CharField|string (短めの単語など) DateTimeField|date and time, EmailField|email(EmailValidatorが付き) FileField|file-upload ImageField|image upload IntegerField|integer TextField|large text URLField|URL(URLValidator付き) UUIDField|UUID (PythonのユニークID) + [Model field reference Field options](https://docs.djangoproject.com/en/2.2/ref/models/fields/#field-options) field type (よく使うものだけ pickup)| option ---|--- null|Trueなら、DBに Null 値を入れる blank|Trueなら、Validation時に Empty Value を受け付ける。 choices| 指定された選択肢のみ選択できる :pencil: よって、`null=False`, `blank=True`という2つの組み合わせは不可 ### 今回必要なデータ + タイトル + 記事 + 作成日時 + 書いた人 `blog/models.py` ```python from django.db import models from django.utils import timezone from user.models import CustomUser class Post(models.Model): # ブログタイトル title = models.CharField(max_length=50, blank=False, null=False) # 記事 text = models.TextField(blank=False, null=False) # 作成日時 created_at = models.DateTimeField(default=timezone.now) # 筆者 author = models.ForeignKey(CustomUser, on_delete=models.CASCADE) ``` + `on_delete=models.CASCADE`: 削除するオブジェクトに紐づいたオブジェクトを全て削除 ## migration ### migration とは? + `<アプリ>/models.py` の記載事項を実際のDBのに反映させることを **migration** という ### コマンド 以下2つ ```bash $ python manage.py makemigrations <アプリ名> # 変更があった場合その変更の準備をする $ python manage.py migrate # 変更を適用する ``` `blog/models.py` を更新したので migrate しましょう ```bash $ python manage.py makemigrations blog $ python manage.py migrate ``` ## admin + `<アプリ名>/admin.py` は、作成したモデルを管理画面に登録する時に使うファイル + 登録すると、管理ツールからサクッとデータを追加できます。 ### 登録方法 1. 管理画面 http://127.0.0.1:8000/admin/ 立ち上がっているか確認 2. blog/admin.py ```python from django.contrib import admin from .models import Post # 同じ階層の models.py から Post をインポート admin.site.register(Post) # 管理ツールにPostを登録 ``` 3. おもむろに,管理画面ブラウザをリロード ![](https://i.imgur.com/zsvGp8a.jpg) ## Dataをいれてみよう ### post を追加 Posts の `+ 追加`を押す ![](https://i.imgur.com/TEVrOs2.jpg) + `blog/models.py` で定義したfieldに従ってフォームを自動生成 + Author は、ForeinKeyでUserテーブルを結んだので参照できるようになっている + 作成日時は default=timezone.now に従って現在時刻が挿入される + **ノート: あなたの環境はサーバー時間より、9時間進んでいます。** と薄く書いてある。これは嫌なので日本時間に変更 ### ローカルタイムに変更 `config/setting.py` ```python TIME_ZONE = 'Asia/Tokyo' #'UTC' ``` 変更したら、テスト記事を数本書いて下さい。 ### postリストをタイトルに変更したい + 1ブログ記事は、`Post Object` という python で扱えるオブジェクト + Post のリスティングされているところで`Post Object (1), Post object (2)...` と表示されているのはそれ ![](https://i.imgur.com/TdQzRYY.jpg) + これではわかりにくいので、Postタイトルを表示するように変更する `blog/models.py` ```python class Post(models.Model): title = models.CharField(max_length=200) : : # 追記 def __str__(self): return self.title ``` :pencil: `models.py` に変更を加えたけど管理画面の表示だけの話なので migrate は必要なし. ## View ### ViewがDjangoの要め ![](https://i.imgur.com/bjTIj5b.png) 1. HttpRequest (HttpRequest オブジェクト)を受け取って 1. 必要な演算を行い 1. レスポンス情報を作成 ### view を挟んだ流れ 1. WSGIRequestクラス(django.core.handlers.wsgi)が HttpRequestを取得してリクエストオブジェクトを作成 2. リクエストオブジェクトを urls.py に書かれたマッピングに従ってView関数にわたす 4. View関数が、 4. リクエストオブジェクトに含まれた入力したデータを取得して型変換 5. データのValidation 6. Validation OK であれば、ビジネスロジック処理。レスポンスの為の変数を用意 7. 遷移先画面を決定 8. 遷移先画面用に変数を使って内容をレンダリング 9. レスポンスオブジェクトの内部にレンダリングされたHTMLコンテンツを埋めてURLに投げる ### 2つの書き方 1. 関数型 : `def` で作る 2. **クラスベース型** : `class` で作る ### 圧倒的にクラスベース型が主流 + たくさんの Buildinクラスが用意されている + 開発者は自分に必要なクラスを選んで,ところどころカスタマイズすれば安全にviewが作成できる + [Built-in class-based views API](https://docs.djangoproject.com/en/2.2/ref/class-based-views/) + [generic view ](https://docs.djangoproject.com/ja/2.2/ref/class-based-views/generic-display/#)をみて、自分が使いたいものを探してみてください ### 注意:クラスベース型Viewを使う時は「継承の順番」に気をつける ```python class ViewA(TemplateView, LoginRequiredMixin): .... ``` これでは動作せず ```python class ViewB(LoginRequiredMixin, TemplateView): .... ``` これなら動作するということがある ### View 作成のながれ 1. `アプリ/views.py` に View を書く 2. `config/urls.py` に ルーティングを書く ### 投稿した記事一覧 127.0.0.1:8000/blog へアクセスしたら,ブログ一覧を表示 + [List Views](https://docs.djangoproject.com/ja/3.1/ref/class-based-views/generic-display/#listview) + オブジェクト(blog 記事)のリストを表示 + `as_view()` して,class を 実行可能な関数に変換する 1. `アプリ/views.py` に View を書く `blog/views.py` ```python from django.views.generic.list import ListView from .models import Post class PostListView(ListView): model = Post # リスティングしたいモデルを指定 ``` 1. `config/urls.py` に ルーティングを書く ```python from blog.views import PostListView # 追記 urlpatterns = [ path('admin/', admin.site.urls), path('blog/', PostListView.as_view()), # 追記 ] ``` 1. 127.0.0.1:8000/blog/ へアクセス ### Exception がなんでも教えてくれる ![](https://i.imgur.com/81ozX00.jpg) <font color="red">Exception Value **`blog/post_list.html` が無いとお怒りになっている**</font> つまり,Djangoは, ```python class PostListView(ListView): model = Post # リスティングしたいモデルを指定 ``` このコードだけで + Post オブジェクトの全データを + `blog/post_list.html` というテンプレートレンダリングする という動作をします。 いま、リストすべきオブジェクトはわかった、だけど`blog/post_list.html`というテンプレートはどこなの?とお怒りになっていらっしゃる状態です。 ## template ### 置き場所を設定 `config/settings.py` ```python TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', #'DIRS': [], 'DIRS': [os.path.join(BASE_DIR, 'templates')], #追記 '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', ], }, }, ] ``` + `'APP_DIRS': True,` は、テンプレートを探す時に各アプリケーションディレクトリ直下の templatesディレクトリ を最も優先して探す + `DIRS` にディレクトリを指定しておくと、 View がそのディレクトリ配下のテンプレートを優先的に探す。 + 上記の例では、まず `<アプリ名>/templates`を探し、次に `gtug_django/templates`配下を探す ### template directory 作成 先程のエラーを再確認すると, ![](https://i.imgur.com/81ozX00.jpg) `blog/post_list.html` を探しにいっているので, 上記の設定をした場合は, `blog/templates/blog/post_list.html` を作成してあげることになる ```bash $ mkdir -p blog/templates/blog/ $ touch blog/templates/blog/post_list.html ``` ここで一旦、http://127.0.0.1:8000/blog/ をリロード→エラー画面は消えたはず ## Django Template Language (DTL) HTMLの中に Django の python でつくった変数をつかってデータを表示したり、条件分岐などのロジックを書いたりすることができるテンプレートエンジン 1. `blog/templates/blog/post_list.html` ```html {{ post_list }} ``` 1. reload すると.QuerySetリストが表示される ``` <QuerySet [<Post: xxxxx>, <Post: xxxxx>, ...]> ``` 1. いろいろためしてみましょう ```html {{ post_list }} <br> {{ post_list.0 }} <br> {{ post_list.0.text }} <!-- models.py で使った変数名でアクセス可 --> ``` 1. loop させることで、全記事を表示させることができます ```htmlmixed {% for post in post_list %} <div class="post"> <div class="date"> {{ post.created_at }} </div> <h2><a href="">{{ post.title }}</a></h2> <p>{{ post.text|linebreaksbr }}</p> </div> {% endfor %} ``` --- ### (ちょっとよこみち)post_list について post_list はどこに定義してあるの?ということが気になる方へ。(そういう態度 :smile: :+1: ) ListView の定義をたどっていきましょう。 pycharm や、 vscode など、のエディターを使っていると、定義元をたどる事ができるのでオススメです。たとえば、vscode であれば、確認したいコードの上にカーソルをあわせて右クリックすれば`定義へ移動(F12)`できます。 ![](https://i.imgur.com/nqEvs3R.jpg) この方法で `class ListView(MultipleObjectTemplateResponseMixin, BaseListView):` → `class BaseListView(MultipleObjectMixin, View):`→`class MultipleObjectMixin(ContextMixin):` をたどっていくと、ここに `def get_context_object_name(self, object_list):` があり、このようなメソッドが定義されてます、。 ```python def get_context_object_name(self, object_list): """Get the name of the item to be used in the context.""" if self.context_object_name: return self.context_object_name elif hasattr(object_list, 'model'): # ココ!これが post_list を作成 return '%s_list' % object_list.model._meta.model_name else: return None ``` よく見ると、 `self.context_object_name` がもしあればそれを使えと言っています。ということは、開発者が定義したListViewに `context_object_name`があればそれを使うと言っているのです。つまり ``` class PostListView(ListView): model = Post # リスティングしたいモデルを指定 context_object_name = "hogehoge" ``` と上書きすれば、`post_list` ではなく `hogehoge` をDTLで使えるようになります。 --- ## CRUDを実現するViewを書いていく ### View 作成のながれ 1. `アプリ/views.py` に View を書く 2. `config/urls.py` に ルーティングを書く 3. エラーに対応しながら解決していく ### 1記事詳細 127.0.0.1:8000/blog/1 へアクセスしたら,記事ID1だけ表示 + [DetailView](https://docs.djangoproject.com/ja/2.2/ref/class-based-views/generic-display/#detailview) + 1オブジェクト(blog 記事)を表示 1. `blog/views.py` ```python from django.views.generic.detail import DetailView # 追加 ... from .models import Post class PostDetailView(DetailView): model = Post ``` 1. `config/urls.py` ```python from blog.views import PostListView, PostDetailView # 追記 urlpatterns = [ path('admin/', admin.site.urls), path('blog/', PostListView.as_view()), path('blog/<int:pk>', PostDetailView.as_view()), # 追記 ] ``` + `<int:pk>` : int 型の何かが来ることを期待して,きたら pk(primary key) という変数にセットされる + 内部的に, `Post.objects.get(pk=pk)` が走ってオブジェクトid=1 が取得される 1. 127.0.0.1:8000/blog/1 へアクセス 2. ![](https://i.imgur.com/G1oulbX.jpg) 3. `blog/templates/blog/post_detail.html`作成 4. 127.0.0.1:8000/blog/1 へアクセス-> エラーなし確認 5. `post_detail.html` ``` <h2><a href="">{{ post.title }}</a></h2> <p>{{ post.text|linebreaksbr }}</p> ``` 4. 127.0.0.1:8000/blog/1 へアクセス ### 1記事更新 127.0.0.1:8000/blog/update/1 へアクセスしたら,記事ID1を更新 + [UpdateView](https://docs.djangoproject.com/ja/2.2/ref/class-based-views/generic-editing/#django.views.generic.edit.UpdateView) + 1オブジェクト(blog 記事)を更新 1. `blog/views.py` ```python from django.views.generic.edit import UpdateView # 追加 ... from .models import Post class PostUpdateView(UpdateView): model = Post ``` 1. `config/urls.py` ```python from blog.views import PostListView, PostDetailView, PostUpdateView# 追記 urlpatterns = [ ... path('blog/update/<int:pk>', PostUpdateView.as_view()),# 追記 ] ``` 1. 127.0.0.1:8000/blog/update/1 へアクセス 1. ![](https://i.imgur.com/O5xlsQt.jpg) + NEW! + `Using ModelFormMixin (base class of PostUpdateView) without the 'fields' attribute is prohibited.` + `fields`属性をおしえろ.と言っている. + つまり **UpdateView**ではどの `fields` を更新したいのかおしえてあげる必要がある 1. `blog/views.py` ```python class PostUpdateView(UpdateView): model = Post fields = "__all__" # とりあえず全部 ``` 1. 127.0.0.1:8000/blog/update/1 リロード 2. ![](https://i.imgur.com/XzBKHjH.jpg) 3. `blog/templates/blog/post_form.html`作成 4. 127.0.0.1:8000/blog/update/1 リロード -> エラーはでない 5. `post_form.html` 更新 ``` {{ form }} ``` + `{{ form }}` だけでとりあえず `field` にあわせて form をつくってくれるがダサい 1. ![](https://i.imgur.com/y3NL88Z.jpg) 5. `post_form.html` 更新 ``` {{ form.as_p }} ``` + `{{ form.as_p }}` で pタグを追加してくれる 1. ![](https://i.imgur.com/WAqTVda.jpg) 5. `post_form.html` 更新 ``` <form method="post">{% csrf_token %} {{ form.as_p }} <input type="submit" value="Update"> </form> ``` + `{% csrf_token %}`: CSRF の防止用タグ + input タグを追加 1. 127.0.0.1:8000/blog/update/1 リロードしてデータを更新してみる 2. ![](https://i.imgur.com/H3lbxIQ.jpg) + `No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model.` + 更新したあとの redirect 先をおしえて + `define a get_absolute_url method on the Model` に従う 1. `blog/models.py` + [reverse](https://docs.djangoproject.com/en/3.1/ref/urlresolvers/#django.urls.reverse): URL pattern name を探して,そこにアクセス ```python from django.urls import reverse #追記 ... class Post(models.Model): ... #追記 def get_absolute_url(self): return reverse("home") ``` 1. `config/urls.py` ```python urlpatterns = [ ... path('blog/', PostListView.as_view(), name="home"),# name追加 ... ] ``` `name=` オプションを使うと、 この url pattern に名前をつけることができ、参照することが出来るようになる。 1. 127.0.0.1:8000/blog/update/1 リロードしてデータを更新してみる→更新されてリストでも確認できる ### 記事一覧から詳細記事へのリンクを貼る `config/urls.py`の `urlpatterns` 内で設定した `name=` は、テンプレート内の DTL からも参照できます。これを使って 記事一覧から詳細記事ページにジャンプするリンクを設定します。 1. config/urls.py` ```python urlpatterns = [ ... path('blog/<int:pk>', PostDetailView.as_view(), name="detail"), # name 追加 ... ] ``` 1. `blog/templates/blog/post_list.html` ```htmlmixed {% for post in post_list %} <div class="post"> <div class="date"> {{ post.created_at }} </div> <h2><a href="{% URL 'detail' post.id %}">{{ post.title }}</a></h2> <p>{{ post.text|linebreaksbr }}</p> </div> {% endfor %} ``` + `"{% URL %}"` : これはURLリンクですよ、というタグ。後ろに可変長の引数をひきとり、`/` でつなぐ + つまりID 1 の記事の時、`href="{% URL 'detail' post.id %}"` は、`href="127.0.0.1:8000/blog/1"` に変換される ### 1記事削除 127.0.0.1:8000/blog/delete/1 へアクセスしたら,記事ID1削除 + [DeleteView](https://docs.djangoproject.com/ja/2.2/ref/class-based-views/generic-editing/#django.views.generic.edit.DeleteView) + 1オブジェクト(blog 記事)を削除 1. `blog/views.py` ```python from django.views.generic.edit import UpdateView, DeleteView, #追記 ... from .models import Post class PostDeleteView(DeleteView): model = Post ``` 1. `config/urls.py` ```python from blog.views import PostListView, PostDetailView, PostUpdateView, PostDeleteView # 追記 urlpatterns = [ ... path('blog/delete/<int:pk>', PostDeleteView.as_view()), # 追記 ] ``` 1. 127.0.0.1:8000/blog/delete/1 アクセス 2. ![](https://i.imgur.com/mHf7ssM.jpg) + `blog/post_confirm_delete.html` が無いわよ?と怒られたので作成 1. `blog/templates/blog/post_confirm_delete.html`作成 1. 127.0.0.1:8000/blog/delete/1 リロード => エラーはない.消えたのかadminで確かめる 2. 127.0.0.1:8000/admin/blog/post/ => まだある! 1. `post_confirm_delete.html`で deleteの submit をしなくてはいけないのでその html を書く ```html <form method="post">{% csrf_token %} <p>Are you sure you want to delete "{{ object }}"?</p> <input type="submit" value="Confirm"> </form> ``` 1. 127.0.0.1:8000/blog/delete/1 アクセス 2. ![](https://i.imgur.com/3azRIco.jpg) 1. confirm する 2. ![](https://i.imgur.com/yUy3qyK.jpg) + `No URL to redirect to. Provide a success_url.` + 消した後のredirectを教えてといっている. + DeleteView に `success_url`を教える.が, + :pencil: `success_url` を渡す時は `reverse_lazy()`関数を使わなくてはいけない 1. `blog/views.py` ```python from django.urls import reverse_lazy #追記 ... from .models import Post class PostDeleteView(DeleteView): model = Post success_url = reverse_lazy("home") ``` 1. 127.0.0.1:8000/blog/delete/1 リロードして confirm すると削除される ### 新規作成 127.0.0.1:8000/blog/create へアクセスしたら新規作成 + [CreateView](https://docs.djangoproject.com/ja/2.2/ref/class-based-views/generic-editing/#django.views.generic.edit.CreateView) + 1オブジェクト(blog 記事)を作成,バリデーションチェック,オブジェクト保存フォームを表示 1. `blog/views.py` ```python from django.views.generic.edit import UpdateView, DeleteView, CreateView # 追加 ... from .models import Post class PostCreateView(CreateView): model = Post ``` 1. `config/urls.py` ```python from blog.views import PostListView, PostDetailView, PostUpdateView, PostDeleteView, PostCreateView # 追記 urlpatterns = [ ... path('blog/create', PostCreateView.as_view()), # 追記 ] ``` 1. 127.0.0.1:8000/blog/create アクセス 2. ![](https://i.imgur.com/9zgQjB7.jpg) + PostUpdateView と同じエラー.fields を渡せばOK 1. `blog/views.py` ```python class PostCreateView(CreateView): model = Post fields = "__all__" ``` 1. 127.0.0.1:8000/blog/create リロード 2. ![](https://i.imgur.com/Xwk3ohg.jpg) + 今回はどうして, Template 無い!ってエラーが無いのか? + PostUpdateView の時に作った `post_form.html`が再利用されている. + ボタンが update なのがキニナルけどとりあえず今はこのままで 1. 新規に記事を作成して updateすると,記事が投稿されて,リスト表示される + 今回はどうして, redirect エラーがないの? + これも,PostUpdateView の時,モデルに追加した `get_absolute_url` メソッドを利用しているから + 試しに,`get_absolute_url`をコメントアウトして create してみると,ちゃんとエラーがでる ## テンプレート継承 ### base template を作成 ```bash $ touch blog/templates/blog/base.html ``` template は Django Girls さんからお借りしました。 https://tutorial.djangogirls.org/ja/template_extending/ `blog/templates/blog/base.html` ```html {% load static %} <html> <head> <title>GTUG Girls blog</title> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"> <link href='//fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext' rel='stylesheet' type='text/css'> <link rel="stylesheet" href="{% static 'css/blog.css' %}"> </head> <body> <div class="page-header"> <h1><a href="/">GTUG Girls Blog</a></h1> </div> <div class="content container"> <div class="row"> <div class="col-md-8"> {% block content %} {% endblock %} </div> </div> </div> </body> </html> ``` + `{% load static %}` static file を使うための DTL + ブロックの部分に継承先の html が挿入される ```html {% block content %} {% endblock %} ``` + css はあとで ### 継承して使う 例: `blog/templates/blog/post_list.html` ```htmlmixed {% extends 'blog/base.html' %} {% block content %} {% for post in post_list %} <div class="post"> <div class="date"> {{ post.created_at }} </div> <h2><a href="">{{ post.title }}</a></h2> <p>{{ post.text|linebreaksbr }}</p> </div> {% endfor %} {% endblock %} ``` + `{% extends 'blog/base.html' %}` 継承する html へのパス + ブロックの部分の中に、差し込みたい html を書く ```html {% block content %} : : : {% endblock %} ``` + 他の html template も同じように書き換える ## CSS ### 設置場所 `config/settings.py` ```python STATIC_URL = '/static/' ``` + project の static ディレクトリを全て探しに行く設定 + `blog/templates/blog/base.html` で css へのパスを以下のように指定しているので ```html <link rel="stylesheet" href="{% static 'css/blog.css' %}"> ``` + `static/css/blog.css`をプロジェクト内で探す。 + アプリディレクトリの下に置いておくほうが楽なので、 ```bash $ mkdir -p blog/static/css $ touch blog.css ``` + `blog.css` こちらも Django Girls さんのファイルをおかりしました。 ```css .page-header { background-color: #C25100; margin-top: 0; padding: 20px 20px 20px 40px; } .page-header h1, .page-header h1 a, .page-header h1 a:visited, .page-header h1 a:active { color: #ffffff; font-size: 36pt; text-decoration: none; } .content { margin-left: 40px; } h1, h2, h3, h4 { font-family: 'Lobster', cursive; } .date { color: #828282; } .save { float: right; } .post-form textarea, .post-form input { width: 100%; } .top-menu, .top-menu:hover, .top-menu:visited { color: #ffffff; float: right; font-size: 26pt; margin-right: 20px; } .post { margin-bottom: 70px; } .post h2 a, .post h2 a:visited { color: #000000; } ``` これでだいぶオサレになったと思います。 ![](https://i.imgur.com/DHJZMAq.jpg) ## まとめ 今回は、Djangoでかんたんなブログサイトを作りながらDjangoで開発していくときの基本動作を学びました。 ざっくりまとめると 1. Djangoのプロジェクトとアプリケーションの雛形を作る 1. データを扱うためのモデルを作る 1. リクエストに応じるViewを作る 1. テンプレートを整える という流れになります。 ### さらにDjangoしたいひとへ 今回はできませんでしたが、カスタムユーザーを拡張して、ソーシャルログイン(TwitterやGithubなど)をしたり、登録確認メール(URL)を作成したりできる django-allauth や API開発を行う、Django RestFrameworkなどにぜひトライしてください ### 勉強会リスト + Django Girls https://djangogirls-org.connpass.com/ + [DjangoCongress JP 2020](https://djangocongress.jp/) + モグモグDjango