Djangoの概要

Python

はじめに

PythonでWebアプリケーションを作成するにあたって最初にイメージするのがDjangoとFlaskです。

検索結果数

Googleの検索数を見てみるとDjangoの方がよく検索されているように見えますね。詳しい比較は別の機会に譲るとして、今回はDjangoとはどういうものかを簡単にまとめてみようと思います。

本文

Model-View-Template

WebアプリケーションにはModel-View-Controller(MVC)という概念があります。Mがデータ、Vが見た目、Cがデータと見た目をつなぐ(振る舞い)というのがざっくりとした役割です。

1つのファイル(クラス)の中で全部を作業することもできます。ただ、1つのファイルの場合、見た目を直したいだけなのにデータにや振る舞いに関する処理にも手が入ることになります。

それぞれの役割に沿ってファイル分割することで直す箇所だけに集中できることでテストする箇所が限定的になったり、複数人での開発ができるため効率が上がるといったメリットがあります。

Djangoにも似たような概念があります。Model-View-Template(MVT)で成り立っています。ただ、MVCを知っている人を混乱させるのが以下のように呼び名が同じでも役割が違うことにあります。

MVCMVT
ModelModel
ViewTemplate
ControllerView
MVCとMVTの違い

また、Webサイトにかかせない重要な要素がURLです。Djangoで作成すると、たとえばPHPで作成したときにできる.phpといった拡張子がないURLにできます。

Model

設計

通常データベースからデータを取り出すときにはSQLを使います。「SELECT xxx FROM yyy」のようなクエリを使ってデータを取得するのがイメージしやすいかと思います。

どのDBでも単純なSELECT文に違いはないと思いますが、複雑なことをしだすと採用するDBによって違いがでてきます。その違いを吸収してくれる考え方としてObject-Relational Mapper(O/R マッパー、O/Rマッピング)というのがWebアプリケーションフレームワークには備わっていることが多いです。DjangoにもO/Rマッパーが搭載されています。

投稿者と記事という2つのテーブルを例に実装を見ていきます。

from django.db import models

class Reporter(models.Model):
    full_name = models.CharField(max_length=70)

    def __str__(self):
        return self.full_name 

class Article(models.Model):
    pub_date = models.DateField()
    headline = models.CharField(max_length=200)
    content = models.TextField()
    reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)

    def __str__(self):
        return self.headline

Modelの中にRepoter(投稿者)とArticle(記事)という2つのクラスがあります。投稿者テーブルにはfull_nameという項目が最大長70文字で設定されています。記事テーブルにはpub_dateが日付 、headlineが最大長200文字、contentがテキスト 、reporterが投稿者テーブルと紐づけるキー項目で構成されています。

インストール

準備ができたら以下のコマンドを実行します。

python manage.py makemigrations
python manage.py migrate

makemigrationsでModelから未作成のテーブルを作るための準備をします。

migrateで実際にテーブルを作成します。

API自動生成

O/RマッパーはSQLを書かなくてもいい仕組みですが、その仕組みを作るのが大変だったりします。
Djangoは基本的な操作(CRUD:Create、Read、Update、Delete)は勿論、紐づけ先のものを取ってくる(JOIN)なども自動的に生成されます。

まずは投稿者テーブルについて操作してみます。

from news.models import Article, Reporter

# 投稿者テーブルから全件取得
print(Reporter.objects.all())
# まだレコードを作っていないので空の配列<QuerySet []>が返ってくる

# 投稿者のデータを1件作成
r = Reporter(full_name='シロクマ')
r.save()
print(r.id)            # save()で登録した後にr.idを呼ぶと「1」が返ってくる
print(r.full_name)     # テーブルの項目はオブジェクトの属性としてあらわされる

# 最初と同じように全件取得してみると
print(Reporter.objects.all())
# データを作成したので<QuerySet [<Reporter: シロクマ]>が返ってくる

# --- いろいろなデータ取得方法 ---
# ID指定
Reporter.objects.get(id=1)
# 項目の最初が一致
Reporter.objects.get(full_name__startswith='シロ')
# 項目に値が含まれる
Reporter.objects.get(full_name_contains='ロク')
# 存在しない値の場合は...
Reporter.objects.get(id=2)
# 以下のような例外が発生する
# Traceback (most recent call last): 
#    ... 
# DoesNotExist: Reporter matching query does not exist.

続いて記事テーブルを操作します。先ほどの続きなので、投稿者テーブルに1件データが登録された状態です。

# 記事テーブル作成
from datetime import date
a = Article(pub_date=date.today(), headline='Djangoめっちゃ凄い', content='わかりみが深い', reporter=r)
a.save()

# 全件取得
Article.objects.all()
# <QuerySet [<Article: Djangoめっちゃ凄い>]> が返ってくる

# 簡単な記述で紐づけ先の情報が取得可能
r = a.reporter
print(r.full_name)
# シロクマ

# 逆引きにも対応している
r.article_set.all()
# <QuerySet [<Article: Djangoめっちゃ凄い>]> が返ってくる

# 投稿者と記事をJOINして、投稿者の名前で絞ることも可能
Article.objects.filter(repoter__full_name__startswith='シロ')

最後に投稿者の内容を変更してみます。

# 更新
r.full_name = 'グリズリー'
r.save()

# 削除
r.delete()

URL

設計

URLconfというPythonモジュールで作成します。このモジュールは、URLのパターンとPythonのコールバック関数をマッピングします。このような仕組みがあることで、URLconfはPythonのコードからURLを切り離すことにも役立ちます。仮にURLの変更があった場合でも、このモジュールのみ確認すればいいということですね。

from django.urls import path

from . import views

urlpatterns = [
    path('articles/<int:year>/', views.year_archive),
    path('articles/<int:year>/<int:month>', views.month_archive),
    path('articles/<int:year>/<int:month>/<int:pk>', views.article_detail),
]

ここではコールバック関数viewsとマッピングしています。たとえばhttps://www.example.com/articles/2005/05/39323というURLにアクセスするとDjangoはnews.views.article_detail(request, year=2005, month=5, pk=39323というように関数を呼び出します。urlpatternsで定義された内容を上から順番に探していき最初に一致した関数を返します。仮にどれとも一致しない場合は404のエラー画面を返します。

View

作成

URLの項で出てきたyear_archiveを例に実装を見てみます。

from django.shortcuts import render

from .models import Article

def year_archive(request, year):
    a_list = Article.objects.filter(pub_date__year=year)
    context = {'year': year, 'article_list': a_list}
    return render(request, 'news/year_archive.html', context)

year_archive関数の中を見ていきます。

1行目はModelの項でみたようにO/Rマッパーを使用しでデータを取得します。

2行目はcontext変数にTemplateで使用可能な変数とその値を格納します。

3行目はrender関数の結果を返します。render関数の第1引数でどのリクエストか、第2引数でどのテンプレートを使うか、第3引数でテンプレートで使用する変数群を指定します。

Template

設計

Viewで指定したnews/year_archive.htmlがロードされます。指定したTemplateには以下のように記載されています。

{% extends "base.html" %}

{% block title %}Articles for {{ year }}{% endblock %}

{% block content %}
<h1>Articles for {{ year }}</h1>

{% for article in article_list %}
    <p>{{ article.headline }}</p>
    <p>By {{ article.reporter.full_name }}</p>
    <p>Published {{ article.pub_date|date:"F j, Y" }}</p>
{% endfor %}
{% endblock %}

変数は二重の波括弧({{と}})で囲んで使用します。Viewで設定したyeararticle_listといった変数が使われていることがわかると思います。

{{ article.pub_date|date:"F j, Y" }}はdatetimeオブジェクトを指定のフォーマットで出力するための記述方法です。ただ、探してみたのですが、Fは何を表すのかわかりませんでした。。。jは0埋めした10進数で表記した年中の日にち(例:001、002、・・・、366)、Yは西暦(4桁)の10進数表記(例:0001、0002、・・・2021、・・・9999)

おわりに

簡単ながらDjangoの仕組みについて大枠を捉えることができました。調べてみるとDjangoを使ったリポジトリでYoutubeのCloneを目指したGitHubもあります。今の段階で具体的な実現方法はイメージできてないですが、できるのを目指して学習がんばりたいと思います!

参考

DJango公式

https://docs.djangoproject.com/ja/3.2/intro/overview/

Model

https://docs.djangoproject.com/ja/3.2/topics/db/models/

View

https://docs.djangoproject.com/ja/3.2/intro/tutorial03

Template

https://docs.djangoproject.com/ja/3.2/topics/templates/

datetime --- 基本的な日付型および時間型

https://docs.python.org/ja/3/library/datetime.html

geekswaroop/YouTube-Clone

https://github.com/geekswaroop/YouTube-Clone