# 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