Pythonでのチームアプリ開発に参画するための学習履歴~Djangoチュートリアル1・2・3~

# はじめに

前回、DockerでDjango環境を作り、簡単に触ってみたので今度は前回の補足を兼ねつつ公式チュートリアルを通してDjangoを少し掘り下げていきます。
今回はチュートリアルの1~3から個人的に大切だなと感じたところをPUしています。

[Djangoチュートリアル](https://docs.djangoproject.com/ja/3.0/intro/tutorial01/)

# include()について。


`include()`はURLconf(ここではurl.py)への参照を表す関数です。
引数に`urls.py`があるパス、第2引数に参照する`url.py`の名前を設定する。
ここではpollsの`urls.py`を参照したいので、`polls.urls`を設定することになります。

`path()`の引数は`route、view、kwargs、name`の4つを設定できます。
`route`はそのアプリケーションのルートアドレスになります。
`view`は呼び出したい`view関数`を指定します。
`kwargs`は任意のキーワードを辞書として`view`に渡すことができます、省略可能です。
`name`は`URL`に名前付けがある場合、それを指定するとDjangoのどこからでも参照できるグローバルなURLになります。

以上2つの例を見てみます。

```

path('polls/', include('polls.urls'))


```

例えばこの記述だと`route`は`'polls/'`の部分になり、`view`はinclude`('polls.urls')`となります。
`include('polls.urls')`に`view`なんてあったっけ？ と思ったらもう一度`urls.py`を見てみましょう。冒頭にインポートされてるのがわかると思います。

ちなみに第2引数に入る`view`は今回のような`include()`の他に`as_views`の出力も取ることができます。
`as_views`はDjangoにおけるビューの条件を満たす関数を作る`classonlymethod`です。
詳しいことは [参考記事](https://qiita.com/tell-k/items/38c0612a44497b311d6b)やドキュメントを見るとして、噛み砕くとまずDjangoには`dispatch`メソッドというリクエストメソッドを判定してそのリクエストメソッドと同じ名前のメソッドを実行するメソッドがあります。
今回の場合は例えば前回の記事の

```python



class SampleView(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'app_folder/page01.html')

    def post(self, request, *args, **kwargs):
     	input_data = request.POST['input_data']
     	result = SampleDB.objects.filter(sample1=input_data)
     	result_sample1 = result[0].sample1
     	result_sample2 = result[0].sample2
     	context={'result_sample1':result_sample1, 'result_sample2':result_sample2}
     	return render(request, 'app_folder/page02.html', context=context,)

top_page = SampleView.as_view()


```

この部分においてですが、ここで`request.method == 'GET'`だった場合はSampleView.get()が実行されるということになります。
`as_view`は`cls`としてインスタンス化して(クラスメソッドのインスタンスになるときクラス自身は`self`ではなく`cls`で表す)、この`dispatch`メソッドを実行するような関数を生成し、それを`return`するというものになり、それはすなわち`view`に分類されるから引数として取ることができるということになります。

さて`name`とは何かというと

```

urlpatterns = [
	path('', views.index, name='index')

]


```

こういうやつですね。
こうすると`ルートインデックス/index`あるいは`ルートインデックス/`の形でURLを叩いたときに`views.index`が呼ばれるという処理にすることができます。
最後の方にもっとわかりやすい使い方が出てきますのでとりあえずここではパスに名前がつくということだけ覚えておきます。


# Djangoでのリレーション

```python


from django.db import models

# Create your models here.

class Question(models.Model):
	question_next = models.CharField(max_length=200)
	pub_date = models.DateTimeField('date published')

class Choice(models.Model):
	question = models.Foreignkey(Question, on_delete=models.CASCADE) # リレーション
	choice_text = models.CharField(max_length=200)
	votes = models.IntegerField(default=0)

```

`models.Foreignkey(リレーションさせるテーブルのクラス名, on_delete=models.CASCADE)`の形で設定します。
この場合は`Question:Choice = 1:多 `という関係になります。
わかりやすくいうと1つの質問に対して答えは複数あるという関係ですね。

`on_delete`以下は参照するオブジェクトが削除されたときにそれと紐付けられたオブジェクトも一緒に削除するのか、残しておくのかというのを設定しています。
`CASCADE`の場合は紐付けられたオブジェクトをすべて削除するということになります。
例えば

```
# polls_question

id		question_next

0		What`s_up?



# polls_choice


id		choice_text		votes		question_id
0		I`m_heading		 0				0
1		Stomachache		 1				1


```

とみたいなテーブルがあるとします。
ここで、`polls_question`テーブルの`id=0`のデータを削除します、するとそれを参照している`polls_choice`テーブルの`id=0`及び`id=1`のデータも削除されるということになります。
これが`CASCADE`を指定した場合のデータ削除処理になります。
他にもオプションはあるのですが、`CASCADE`を使うことが多いようです。
リレーション関係にあるデータは扱いが少し難しいので削除する際は依存関係にある双方のテーブルのデータは一方を消すときは全部消すのがベターという解釈を私はしています。
もちろん他方をnull扱いにしたり、他方だけ完全に削除するという指定をもできます。


# Djangoでのマイグレーション及びAPI操作(データベースとのやりとり)

さて、ここまで終わればあとは`migrate`でマイグレーションを行えばテーブルの完成ですが、`SQL文`を出力して`migrate`が何をしているか示してくれるコマンドがあるので試してみます。

```
python manage.py makemigrations polls

```
dockerの場合は適宜`docker exec`コマンドでコンテナの中に入り`manage.py`ファイルがあるディレクトリに移動して実行してください。
`makemigrations`コマンドにアプリケーションフォルダの名前を指定すると、そのアプリケーションのマイグレーションを保存することができます。
例えば今回このコマンドを実行すると

```
Migrations for 'polls':
  polls/migrations/0001_initial.py
    - Create model Question
    - Create model Choice


```

このように0001という名前がマイグレーションに与えられてファイルが保存されます。
ちなみに今回私は間違えてマイグレーションしてしまい、テーブルに登録するデータのプロパティを間違えてしまったので変更して再度`makemigrations`をしました。

その場合は以下のような`0002_auto_20200408_1848.py`というファイルが作られ再度`migrate`するとテーブルが更新されます。

```0002_auto_20200408_1848.py

# Generated by Django 3.0 on 2020-04-08 18:48

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('polls', '0001_initial'),
    ]

    operations = [
        migrations.RenameField(
            model_name='question',
            old_name='question_next',
            new_name='question_text',
        ),
    ]


```

次にこのマイグレーションを`migrate`するとどのような処理が行われるかを見てみます。

```
 py manage.py sqlmigrate polls 0001

```

このように`sqlmigrate`コマンドにアプリケーションフォルダとマイグレーションを指定すると


```
--
-- Create model Question
--
CREATE TABLE `polls_question` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `question_next` varchar(200) NOT NULL, `pub_date` datetime(6) NOT NULL);
--
-- Create model Choice
--
CREATE TABLE `polls_choice` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `choice_text` varchar(200) NOT NULL, `votes` integer NOT NULL, `question_id` integer NOT NULL);
ALTER TABLE `polls_choice` ADD CONSTRAINT `polls_choice_question_id_c5b4b260_fk_polls_question_id` FOREIGN KEY (`question_id`) REFERENCES `polls_question` (`id`);


```

このようにSQL文が表示され、`migrate`コマンドが何をやっているかわかるようになります。
どのようなSQL文でテーブルを作るのか一目瞭然ですね。
ここまで確認したら、`migrate`してテーブルを作りましょう。

ではチュートリアル通り`shell`コマンドを試してみて、データベースとやりとりをしてみます。

```
# モデルをインポートしてクラスを取得する
>>> from polls.models import Choice, Question  # Import the model classes we just wrote.

# Questionクラスのオブジェクトをすべて取得する。データが入っていないので結果は空で返ってくる。
>>> Question.objects.all()
OUT:<QuerySet []>

# datetimeモジュールを使うために、django.utilsからその中のtimezoneクラスをインポートする。
>>> from django.utils import timezone

# 変数にオブジェクトの形でデータを代入
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# データをセーブ
>>> q.save()

# データのプロパティ取得
>>> q.id
1

>>> q.question_text
"What's new?"

>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)


# プロパティの変更をこころみる
>>> q.question_text = "What's up?"
>>> q.save()

# 失敗
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

```

失敗を回避するために`models.py`を書き換えます。

```
import datetime
from django.db import models
from django.utils import timezone

# Create your models here.

class Question(models.Model):
	question_text = models.CharField(max_length=200)
	pub_date = models.DateTimeField('date published')

	# 追加1
	def __str__(self):
		return self.question_text

	# 追加2
	def was_published_recently(self):
		return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

class Choice(models.Model):
	question = models.ForeignKey(Question, on_delete=models.CASCADE)
	choice_text = models.CharField(max_length=200)
	votes = models.IntegerField(default=0)

	# 追加3
	def __str__(self):
		return self.choice_text

```

`__str__()`はオブジェクトメソッドです。
例えばここでは引数に`self`を設定して、クラスをインスタス化して各プロパティを人間可読にするという役割を担っています。
先程の`Question.objects.all()`でオブジェクトがうまく表示されなかったのはオブジェクトが機械可読のままであったからということになります。
つまり、オブジェクトはそのままでは私達の目には`このクラスのオブジェクトです`という見え方でしかないので、そこに名前をつけてやるということをやるわけです。

追加2はオリジナルメソッドになります。
returnのあとに数式なのでTrue or Falseを返すということになります。
`timezone.now()`が現在の日付、`datetime.timedelta(days=1)`が1日後ということになるので`今日 - 明日`は昨日、それ以上で`self.pub_date`は真になる。
つまり、Questionテーブルのデータが昨日以降に作成された場合に`True`を返すメソッドになります。
しかし、このままではいわゆる未来日付と呼ばれる値であった場合にも`True`が返ってきてしまうバグを抱えています。
Questionテーブルということは質問の内容を登録するということになりますが、質問をしたということは時間的には必ず過去または現在でなければなりません。
昨日質問した、今日質問したとはいいますが、明日質問したとは言いませんよね？
このバグについてはチュートリアルの4以降の項目で解消をするようなのでとりあえずこのままおいておきます。

さて、再度shellコマンドを実行します。

```

>>> from polls.models import Choice, Question

# __str__メソッドを追記したのでQuestionプロパティのquestion_textがオブジェクトの名前として呼び出される。

>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# filterメソッドでidでオブジェクトを絞り込めることを確認。

>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>

# 同じようにquestion_textがwhatで始まるオブジェクトを絞り込んでみる。
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>

# 今年追加されたオブジェクトを取得する

>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>



# id=2のデータは存在しないのでエラー
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.


# 主キーが1のオブジェクトを取得。通常主キーはid。なのでここではpk=1 == id=1となる
>>> Question.objects.get(pk=1)
<Question: What's up?>

# 追加2のメソッドの動作確認

>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True


# 主キーが1であるオブジェクトをインスタンス化。
>>> q = Question.objects.get(pk=1)

# 以上のオブジェクトに紐付くchoiceオブジェクトを呼び出す。要は質問に対して答えがいくつあるかということになる。Choiceテーブルにはデータがないので空で返ってくる。
>>> q.choice_set.all()
<QuerySet []>

# Question.objects.get(pk=1)に紐付く答えとして、choiceテーブルにデータを3つ追加する。
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# questionプロパティはChoiceテーブルの外部キー、つまりQuestionテーブルの主キー参照となる。
>>> c.question

# よって上述の通りc変数はq = Question.objects.get(pk=1)に紐付いているので下記の結果が返ってくる。
<Question: What's up?>

# q = Question.objects.get(pk=1)に紐ついているChoiceオブジェクトを呼び出して、オブジェクトの数をカウント
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

# Choiceオブジェクトのうち、参照しているQuestionテーブルの主キーに紐付く質問の作成日が今年であるオブジェクトを絞り込む。

>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# 特定のChoiceオブジェクトを削除する。
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()



```

# views.pyを通してURLconfの役割をもう少し見てみる。

```polls/views.py

from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from .models import Question

# Create your views here.
def index(request):
	latest_question_list = Question.objects.order_by('-pub_date')[:5]
	context = {
		'latest_question_list': latest_question_list,
	}
    return render(request, 'polls/index.html', context)

def detail(request, question_id):

	"""
	try:
		question = Question.objects.get(pk=question_id)
	except Question.DoesNotExist:
		raise Http404("Question does not exist")
	return render(request, 'polls/detaill.html', {'question': question})

	"""

	# 上記はDjangoのショートカットだとこうなる。get_object_or_404メソッド

	question = get_object_or_404(Question, pk=question_id)
	return render(request, 'polls/detail.html', {'question': question})

def results(request, question_id):
	response = "You`re looking at the results of question %s."
	return HttpResponse(response % question_id)

def vote(request, question_id):
	return HttpResponse("You`re voting on question %s." question_id)




```


```polls/urls.py


from django.urls import path
from . import views

app_name = 'polls'
urlpatterns = [
	path('', views.index, name='index'),
	path('<int:question_id>/', views.detail, name='detail'),
	path('<int:question_id>/results/', views.results, name='results'),
	path('<int:question_id>/vote/', vies.vote, name='vote'),

]


```

`path()`については先程言及しました。
例えばここで`/polls/34/`へのアクセスがあったとします。
そうすると上記のような設定だとどういう処理になるか覚えていますでしょうか？
正解は`urls.py`の`urlpatterns`を参照して、どのview関数にリクエストを飛ばすかという判断がなされ、そこで呼び出されたview関数を実行するということになります。

もう少し掘り下げてみます。
まず、`path()`の第1引数に指定された部分を見ます。この部分にはルートアドレスが入るというのは先程やりましたね。
例えば1番目の記述がわかりやすいです。この書き方だとここで指定されるURLパターンは`/polls/`ということになります。
つまり、第1引数の指定が空でも`polls/`は自動的に指定されているというのがわかりますね。(指定なしはエラーになります。)
これはDjangoはリクエストを受け取るとまず、`settings.py`の`ROOT_URLCONF`に指定されている`urls.py`ファイルをロードします。
これはシステム全体のURL設計をするファイルが指定されています。前回の例でいうと`project/project/urls.py`がそれにあたります。
ではそこにこんな記述があるとしましょう。

```python


urlpatterns = [
	# 管理サイトにアクセスするURL
    path('admin/', admin.site.urls),
    # 今回作成するアプリ「app」にアクセスするURL
    path('app/', include('app.urls')),
    # 何もURLを指定しない場合
    path('', views.index, name='index'),
    path('polls/', include('polls.urls')),
]


```

ここまで来ればもうからくりがわかったという人もいるでしょう。
最初の`path()`の部分は呪文だと思ってください。3番目の記述は先程と同様のものです。
注目するべきは2・4番目の記述です。
ここでこういう記述をすることで、各アプリの`urls.py`が参照できるようになり、さらにその`urlpatterns`という変数とリクエストをマッチングさせられるということになります。
また、ここで` path('polls/', include('polls.urls'))`と記述することで`polls/url.py`では`/polls/34/`といったようなリクエストがあった場合、すでに`polls`はマッチしてると判断され、それを除いた部分がどのpath()とマッチするかの判断がなされます。
そうなると`/polls/34/`は`polls/url.py`の1番目の`path()`とはマッチングしないことになります。


では2番目以降の記述だとどうなるかというと、`<int:question_id>`を見てみます。
これはURLパターンから`int`を探してそれを`question_id`という名前にするという意味になります。

>ドキュメントより
>>山括弧を使用すると、URLの一部が「キャプチャ」され、キーワード引数としてビュー関数に送信します。 文字列の :question_id> 部分は、一致するパターンを識別するために使用される名前を定義し、<int: 部分は、URLパスのこの部分に一致するパターンを決定するコンバータです。

こうなると`/polls/34/`というアクセスは`34/`という文字列が残ることになるので2番目の`path()`とマッチングするということになります。
そうなると後は指定されたview関数にリクエストが渡されます。
この場合は`polls/views.py`の`detail()`メソッドにリクエストがパスされます。
見てみると、引数に`request`及び`question_id`が指定されているので、結果として

```
detail(request=<HttpRequest object>, question_id=34)

```

といったように`detail()`に引数が渡されて、メソッドが実行されていくということになります。
実際にどのような処理がなされているかは後ほど確認します。


# templatesについて

```

   'DIRS': [os.path.join(BASE_DIR, 'templates')],
   'APP_DIRS': True,

```

`settings.py`のこの部分が肝で、まずDIRSでどこにテンプレートhtmlを置くかを指定します。
1つのアプリケーションのだけのテンプレートの指定なら`BASE_DIR`ではなくアプリケーションフォルダ名が入りますが、`BASE_DIR`としたあと`APP_DIRS`を`True`にすることで上で指定したフォルダのサブディレクトリをアプリーケーションフォルダの名前で検索してテンプレートを適用するような設定にすることができます。

つまり、今回ならアプリケーションフォルダ名は`polls`なので、そのテンプレートを保存するディレクトリは`templates/polls`ということになります。
`render()`メソッドは`第1引数にrequest、第2引数にテンプレート名、第3引数に辞書`を指定できます。
そうして、受け取った引数を元に`HttpResponse`オブジェクトを作成してそれを返すという役割があります。
こういう処理のことをレンダリングと呼ぶそうです。
レンダリングはある情報を形にを変えて表現するような処理というイメージで、今回の場合は`request`オブジェクト(`session`や`get,post`)・`context`(データベースのデータ)などの情報とテンプレートのhtmlファイルとをうまく組み合わせ、
ブラウザの画面として成形するような処理をしているみたいです。

また`render`クラスをインポートすることで、`loader`クラスや`HttpResponse`クラスをインポートしなくて済むようになります。
ただし、`HttpResponse`クラスは他の役割もあるので残しておいた方が無難みたいです。

次に例外です。

```

from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})


```

いわゆる`try-catch`のよく見る形です。
`django.http`モジュールから`Http404`クラスをインポートすることで404エラーを出すことができます。
`question`変数に`Question`オブジェクトを代入したのものが存在するか否かで例外を出すというコードです。
もう少しわかりやすくいうと、リクエストされたIDを持つデータが存在しない場合は例外を出すという処理になります。
これも`loader`、`HttpRespose`の役割を担った`render()`メソッドと同じく`django.shortcuts`モジュールからより簡潔に書けるメソッドをインポートできます。


```
from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

```

`get_object_or_404()`メソッドにモデル名とキーを指定することで自動的に`try-catch`の処理を行ってくれます。
キーは複数設定することができます、例えば`get_object_or_404(User, pk=1, name='tanaka')`のような指定の場合主キーが１、nameキーの値がtanakaのデータを取得するか404エラーを出すということになります。
ちなみに`get_list_or_404()`というメソッドもあります。
同じようにモデルから値を取得するメソッドですが、今度は指定したキーを元にfilter()メソッドで絞り込んでlistですべて取得するという処理をします。
例えば先程のように`get_list_or_404(User, pk=1, name='tanaka')`と指定すると主キーが１、`name`キーの値が`tanaka`のデータをすべて取得しリストにして返すか1つも存在しない場合は404エラーを出すということになります。


# テンプレートシステムについて

Laravelでもあったテンプレートシステムですが書き方や用法にあまり違いはないです。

```
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

```

例えばこういう記述ですが、おそらく見ただけで気づいた方もいらっしゃると思いますが`{{}}`と波括弧2重だと変数の表現になります。
もっというと、変数の属性を表現しているということになり、ここには`Question`オブジェクトの`question_text`の部分が入りますよということが書かれているわけです。

処理としては

>ドキュメントより
>>テンプレートシステムは、変数の属性にアクセスするためにドット検索の構文を使用します。 {{ question.question_text }} を例にすると、はじめに Django は question オブジェクトに辞書検索を行います。それに失敗したら、今度は属性として検索を行い、このケースの場合は成功します。もし属性の検索に失敗すると、リストインデックスでの検索を行います。

ということになります。
今回のチュートリアルで作成していることでいうと`h1`タグの中には質問の内容が入るということがわかると思います。
よって質問が複数あり、その都度テンプレートを作っていては大変……ということでこういった書き方をして、質問の内容によって`h1`タグに入る内容が変わりますよということがわかればOKです。

次に`{}`という波括弧1つの場合。これは、メソッドの呼び出しを行っています。

今回の処理は以下の通りです。

>ドキュメントより
>>メソッドの呼び出しは {% for %} ループの中で行われています。 question.choice_set.all は、 Python コードの question.choice_set.all() と解釈されます。その結果、Choice オブジェクトからなるイテレーション可能オブジェクトを返し、 {% for %} タグで使えるようになります。


つまり、このメソッドで得られた`choice.choice_text`オブジェクトの数だけ`li`タグによるリストができるということになります。
今回の場合は、質問の内容に対しての答えの表現ということになります。
1つの質問に対して、答え方は複数あるのでこういう形で表現しているということがわかればOKです。

また例えば、`polls.urls`において先程`name`を指定したことを覚えていますでしょうか？

```
path('<int:question_id>/', views.detail, name='detail'),

```
こういうのですね。
このように指定しておくと、テンプレートにおいてurlタグを使うことでURLパスを書く必要がなくなります。
例えば

```

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

```

こういう記述です。
普通なら`a`タグの`href`属性には`URL`やパスを指定しなければなりません。
しかし、前もって`detail`は`/polls/'<int:question_id>/'`というパスの名前ですよということを定義してあります。
ということで、以上のように`detail`と書いてあとは`question_id`という名前の`int型`をおけば(ここではquestion.id)パスになるという寸法です。
余談ですが例えばこのパスを`polls/specifics/12/`のような形にしたいなら`polls/urls.py`を編集しましょう。

# 名前空間

最後に名前空間です。
各アプリケーションフォルダの`urls.py`に予め名前空間を定義することで1つのプロジェクト内で複数のアプリケーションフォルダを管理する際にどのアプリケーションの`URLconf`を使えばいいか自動的に判断してくれます。

```polls/urls.py

# 以下を追加
app_name = 'polls'

```

このように指定します。
そしてテンプレート側でURLパスの名前指定の部分で

```
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

```

このように名前空間付きで指定してやります。
そうすると`pollsアプリのdetailビューのパス`がここに入りますという表現になるということになります。


# 参考
[レンダリングってそもそも何？【初心者向けに分かりやすく解説】](https://codor.co.jp/django/about-rendering)
[Djangoのクラスベースビューのas_viewて何なの？](https://qiita.com/tell-k/items/38c0612a44497b311d6b)