Django を始めたい方へ

# はじめに

※随時更新予定です (※2024/09/16)

PythonでWebアプリケーションを実装していくことを考える、FlaskやFastAPIなどが選択肢としてありますがやはりDjangoの人気は高いと思っています。ただ、Djangoは他にフレームワークと比べると少し難易度は高い気がします。今回はそのDjangoをなるべくハードル低くと始められるように記事を書いてみました。初心者の勉強に役立ててもらえたらと思います。


# 手順

### Starter

[1]. プロジェクト用のディレクトリ作成

```
$ mkdir todo
```

[2]. 仮想環境の構築

```
$ python -m venv venv
$ . venv/bin/activate
```

[3]. プロジェクトの立ち上げ

```bash
(venv)$ pip install django
(venv)$ cd todo
(venv)$ django-admin startproject todoproject .
```

※ 「startproject」 : 必要なファイルをコピーして持ってくる

Djangoでは、Webアプリの各種の設定情報を統括・管理するために仕組みでプロジェクトという概念がある。そのプロジェクト配下にアプリを1つまたは複数作成して開発していく。

[4]. アプリケーション作成

```
(venv)$ python manage.py startapp todoapp
```


ここまでのディレクトリ構造は以下のようになります

```
.
├── manage.py
├── todoapp
│   ├── __init__.py  #初期化
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
└── todoproject
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-38.pyc
    │   └── settings.cpython-38.pyc
    ├── asgi.py
    ├── settings.py #プロジェクト全体の設定情報を保存
    ├── urls.py #ルーティング記載
    └── wsgi.py #WeebアプリとWebサーバーを繋ぐ役割
```

- `__init__.py`
    - モジュールのインポートができるようにするための処理
    - モジュール自体の処理関数

[5]. settings.pyの編集

```python
#プロジェクトのアプリを作成したことを伝える

IMSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    'todoapp', #アプリ追加
]


TEMPLATES = [
    {

    'DIRS' : [BASE_DIR / 'templates'] # 追加 django v.3
    },
]
```
 'DIRS' : [BASE_DIR, 'templates']　# 追加 django 2

- IMSTALLED_APPSの追加文は、apps.pyで定義されているConfigクラスの部分です

[6]. プロジェクト(todoproject)の「urls.py」でURLの繋込みを行う


```urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('todoapp.urls'))
]
```
- path(route, view, kwargs=None, name=None)
    - route : ルートディレクトリ
    - view : ビューまたはas_view()で返されるビュー
    - path関数を利用して「リクエストされたページに対して特定のビューを呼び出すためのURLパターンを生成する」 

- importに 「include」の追加
- include(module, namespace=None)
    - module : URLconfモジュールの名前空間を指定する

[7]. アプリケーション(todoapp)に新規で「urls.py」を作成しURLの繋ぎ込みをする

```urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('hello/', views.helloworldfunction),
]
```

[8]. アプリの 「view.py」を編集する

```view.py
from django.shortcuts import render

#追加
def helloworldfunction(request):
    return render(request, 'index.html')
```


[9]. index.htmlを作成

todo/templates/index.html
※「manage.py」と同じ階層にtemplatesディレクトリ作成イメージ


```index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Hello World</title>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>
```


[10]. migrate

Djangoのマイグレーション : 管理画面が使う上表をデータベースに自動的に作成・管理する機能

※データベースマイグレーションとは、DBのデータを保持したままDBのテーブルやカラム等の構成を変更することです。Djangoには、DDLを直接書かずとも構成を変更してくれるマイグレーション機能が備わっています。

```bash
# ($ python manage.py makemigrations)
(venv) $ python manage.py migrate
```

[11]. アプリの起動

Djangoに搭載されたサーバーを起動し、Webアプリを実行 (開発用サーバーの起動)
```
$ python manage.py runserver
```



- settings.pyの深堀

    - BASE_DIR
    - DEBUG
    - ALLOWED_HOSTS = []
    - INSTALLED_APPS = []
    - MIDDLEWARE = []
    - ROOT_URLCONF = []
    - WSGI_APPLICATION = []
    - DATABASES = []
    - AUTH_PASSWORD_VALIDATORS = []


### ビュー

- Djangoではビューを作成するときのために、様々なタイプのスーパークラスを用意
- テンプレートをレンダリングするだけなら、「TemplateViewクラス」または「RedirectViewクラス」が最適
    - TemplateView : テンプレートをレンダリングする時に使う
        - django.views.generic.base.TemplateView
    - RedirectView : 任意のURLにリダイレクトするときに使う
        - django.views.generic.base.View


- ビュー作成方法
    - クラスベースビュー (実装が容易だから基本こっち)
    - 関数ベースビュー (細かい制御をしたいとき)


クラスベースの書き方

```views.py
from django.shortcuts import render
from django.views.generic.base import TemplateView

class IndexView(TemplateView):
    
    
    
    template_name = 'index.html'
```
※template_nameに指定するのはファイル名のみ。templatesフォルダは自動で検出

流れとしては
プロジェクトのURLconf
↓
アプリのURLconf
↓
IndexViewによるindex.htmlのレンダリング


関数ベースビュー

- アプリのURLconf

```python
from django.urls import path 
from . import views

app_name = 'blogapp'

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

- アプリのビュー

```python
from django.shortcuts import render

def index_view(request):
    
    return render(request,'index.html')
```

- 関数ベースビューでは「HTTPRequestオブジェクトを受け取るパラメーターを設定する」


### 静的ファイル

- Djangoでは静的ファイルは「static」というフォルダにおく
- 例) CSS, JS, 画像


Djangoが静的ファイルを参照する仕組み

テンプレートタグでstaticロード
```
{% load static %}
```
Django独自のテンプレートタグ
- load
- url
- for
- if


### モデル(models.py)の作成

- Djangoには、SQLite3というデータベースが搭載されている
- settings.pyにDBの設定は記載

```
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}
```

[1]. models.pyに記述する

```.py
from django.db import models

# Create your models here.

class TestModel(models.Model):
    age = models.IntegerField()
    name = models.CharField(max_length=10)

    def __str__(self):
        return self.name
```

[2]. マイグレーションファイルの作成


2STEPによってテーブル作成する
- モデルの定義に基づいてSQLを発行するためのマイグレーションファイル(Pythonモジュール)を自動生成
- 生成したマイグレーションファイルのコードを実行してDBにテーブルを作成


マイグレーションファイル生成(フィールド定義に従ってSQLを発行し、DBテーブルに反映させるためのコード)
```
$ python manage.py makemigrations [アプリ名]
```

[3]. マイグレーションファイルをDBに反映

- 生成されたマイグレーションを実行し、DBの更新(テーブルの作成)を行う
```
$ python mamage.py migrate
```


[4]. ユーザー登録

- Django管理サイトはDBの操作を行う重要なものなので、事前に登録されたsuperuserのみがログイン使う仕組み
```
$ python manage.py createsuperuser
```
*ユーザー登録していないとadminに入れない

[5]. admin.pyの編集

- Django管理サイトでは、あらかじめ登録されているモデルに対してのみ、データの追加や削除、編集ができる

```.py
from django.contrib import admin
from .models import TestModel
# Register your models here.
admin.site.register(TestModel)
```
*この追加をしないと管理者画面で作成したモデルが確認できない

[6]. DBにデータ登録

```
$ python manage.py runserver
```

- `http://127.0.0.1:8000/admin/`にアクセスしてDBにデータ追加可能
- 「＋追加」からデータ追加

別のアプローチ !!!(shellを起動して登録)

```
$ python manage.py shell
```

```shell
>> from todoapp.models import TestModel
>> c = TestModel(age=23, name='taro')
>> c.save()

>> TestModel.objects.create(age=33,name='jiro')
>> TestModel.objects.all()

>> TestModel.objects.filter(name='taro')

>> TestModel.objects.order_by('name')
>> TestModel.objects.order_by('-name')
>> TestModel.objects.filter(id=2).update(name='jun')
```

### ビューの作成方法

① Function-Based View (FBV) : リクエスストを引数として、戻り値としてhtmlレスポンスを返す
② Class-Based View (CBV): 親クラスを継承して、ビュークラスを作成してそれを使う

## ① FBV 

```.py
@login_required    
def listfunc(request):
    object_list = BoardModel.objects.all()
    return render(request,'list.html', {'object_list': object_list} )

def detailfunc(request, pk):
    object = get_object_or_404(BoardModel, pk=pk)
    return render(request, 'detail.html', {'object':object})
```

## ② CBV

#### TemplateView
- モデルと関連付けずにデータを扱わないhtmlファイルをつくるとき
- URL内でキャプチャされたパラメータを含むコンテキストとともに与えられたテンプレートをレンダリングする
- TemplateViewでの値の引き渡し(get_context_data)
- TemplateResponseMixin, ContextMixin, Viewの3つから継承
- `template_name` は必須

```views.py

from django.shortcuts import render
from django.views.generic import TemplateView

class MemberList(TemplateView):

    template_name = 'member_list.html' #表示するテンプレート

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['message'] = context
        return context
```
- この場合、html側で {{ message }}で受け取る
- contextは辞書型
- contextが自動で取得されるのは、class-based viewの時(as_view()が呼ばれたタイミングで)
- contextは、get_context_data() メソッドによって取得される

```urls.py
path('member/', views.MemberList.as_view(), name='member')
```


#### ListView

データを表示される際に使用

```views.py
from django.views.generic import ListView
from todoapp.models import TestModel
 
class ShowView(ListView):
    model = TestModel
    template_name = 'member_list.html'
```

※最低限modelのみ設定でOK
※ListViewを継承することによって色んな機能を使えるようになる

```urls.py
urlpatterns = [
    url('member/', ShowView.as_view(), name='member'),
]
```
※ `as_view()`というメソッドを持っている

```index.html
<ul>
  {% for item in object_list %}
  <li>{{ item }}</li>
  {% endfor %}
</ul>
```
※ ListViewではデフォルトでオブジェクトのリストをコンテキスト内のobject_listという変数に保存
※ ListViewはデフォルトで全件取得する。分割で一覧を取得する場合は、`paginate_by`を用いる


object_listという変数名を変えるには以下のように変更かける。
```
context_object_name = 'XXXX'
```

コンテキストを拡張
- ビューにある`get_context_data()`で拡張できる

```python
from django.views.generic import ListView
from .models import Person


class PersonList(ListView):
    model = Person

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['taro'] = Person.objects.filter(name='Taro').first()
        return context
```


※ returnで `context`を返却するのを忘れないように

クエリセットを指定

```python
from django.views.generic import ListView
from .models import Person


class PersonList(ListView):
    model = Person
    queryset = Person.objects.order_by('-name')
```

- setup()
- dispatch()
- http_method_not_allowed()
- get_template_names()
- get_queryset()
- get_context_object_name()
- get_context_data()
- get()
- render_to_response()



- queryset
    - リストで表示したいデータをいれる
    - 設定されていないときはすべてのレコードを取得


Contextについて
- XX
- get_context_data()
    - paginator, page_obj
    - object_list
    - orderby_records
    - view
- context_object_name : querysetの名前を指定。(デフォは「modelNAME_list」)

#### DetailView
- DBに入っている個別データを取得する時
- 個別データに紐づいているprimary key をurlpatternsで指定することによりデータを取り出す



```views.py
class DetailView(DetailView):
    model = TestModel 
    template_name = 'detail.html'
```

URLを作成する

```urls.py
urlpatterns = [
    path('detail/<int:pk>', DetailView.as_view(), name='detail')
]
```
※ `<int:pk>`が肝

```detail.html
{% extends 'base.html' %}

{% block header %}
{% endblock header %}

{% block content %}
<p>タイトル: {{ object.name }}</p>
<p>タイトル: {{ testmodel.name }}</p>
{% endblock content %}
```
※ {{ object.XXX }} か {{ modelで指定した変数の小文字.XXX }} で値の取得可能


#### CreateView
- データ作成(DBにレコード追加)
- <form></form>タグでフォームをつくることで送信ボタンを押すと簡単にデータ作成可能


```views.py
class CreateData(CreateView):
    template_name = 'create.html' #使用するテンプレート
    model = TestModel #使用するモデルクラス
    field= ('age', 'name') #モデルから引っ張ってくるカラム
    success_url=reverse_lazy('list') #成功したときの遷移先
```
※ `field`はユーザーが入力する項目。すべて入力させるなら `__all__`を設定
※ success_urlは投稿完了時に遷移するページを指定する
※ ここはフォームで入力をするイメージ

```.create.html
<form method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="create">
</form>
```
※ {{ form.as_p }}を記述することで自動で入力フォームが生成
※ `form.as_p` は各項目をpタグでくくる

## DeleteView

- 「CreateView」や「UpdateView」と似ている
- データベースのデータを削除することに特化したクラスベースビュー。データ削除時の確認画面を表示できる。



```python
from django.views.generic.edit import DeleteView

class NippoDeleteView(DeleteView):
    template_name = "nippo/nippo-delete.html"
    model = NippoModel
    success_url = reverse_lazy("list")
```
※ XX



```python
urlpatterns = [
    path("delete/<int:pk>/", TestDeleteView.as_view(), name="delete"),
]
```

```html
<form method="post">
    {% csrf_token %}
    <p>このテーブルを削除しますか？</p>
    <table class="table table-sm table-bordered table-responsive">
        <tbody>
            <tr>
                <th>ID</th>
                <td>{{ object.id }}</td>
            </tr>
            <tr>
                <th>テーブル名</th>
                <td>{{ object.name }}</td>
            </tr>
        </tbody>
    </table>
    <input class="btn btn-danger" type="submit" value="削除する">
</form>
```




## UpdateView
 
- データベースのデータを更新することに特化したクラスベースビュー。既存データの修正が可能。

```python
from django.views.generic.edit import UpdateView

class MemberUpdateView(UpdateView):
    model = Member
    fields = ('name', 'age')
    template_name_suffix = '_update_form'
    success_url = reverse_lazy('list')
```
※ model : データを登録する対象のモデル名
※ fields : データを登録するフィールドの一覧（"__all__"を指定するとすべてのフィールドが対象）
※ template_name_suffix : 使用するテンプレートHTMLファイルの末尾
※ success_url : ポスト成功時のリダイレクト先

https://en-junior.com/update-delete-view/


## FormView

- フォーム作成に特化したクラスベースビュー。クラス変数form_classに設定したフォームの表示や、POST時のバリデーションなどが実装済み。


```python
from django import forms


class MemberForm(forms.Form):
    name = forms.CharField(max_length=100, label = '名前')
    age = forms.IntegerField(label='年齢')

```


```python
from django.views.generic.edit import FormView
from .forms import MemberForm


class MemberFormView(FormView):
    # テンプレート名の設定
    template_name = 'app/form.html'
    # POST時の線先の設定
    success_url = '/static/app/top.html'
    # フォームの設定
    form_class = MemberForm
```
### ベーステンプレート

- XXX


### ページネーション機能


### API

必要なライブラリ

```bash
pip install django
pip install djangorestframework
pip install django-filter 
```

```settings.py
INSTALLED_APPS = (
    ...
    'appblog',
    'rest_framework',
)
```

※ `rest_framework`をINSTALLED_APPSに追加

REST APIを作成するには最低限以下の3つを定義する必要があります。
ここさえ抑えておけばOK.


- Serializer
- ViewSet
- URL pattern

Serializerは「Modelをどのようにシリアライズ(・デシリアライズ)するかを決めるためのもの」、ViewSetは「APIのクエリーをどう解釈するかを決めるためのもの」、そしてURL Patternは「DjangoにURLのパターンを教えるためのもの」です。これらをAPI化したいModelに対してそれぞれ定義していきます。


#### Serializer

- シリアライズ (モデルオブジェクトをJSONに変換)やデシリアライズ (JSONをモデルオブジェクトに変換) という工程
- Serializerはデータベースの保存などの機能がとても豊富



```serializer.py
# coding: utf-8

from rest_framework import serializers

from .models import User, Entry


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('name', 'mail')


class EntrySerializer(serializers.ModelSerializer):
    class Meta:
        model = Entry
        fields = ('title', 'body', 'created_at', 'status', 'author')
```

※ シリアライザクラスの条件は、ModelSerializer ,listSerializer ,Serializer のいずれかを継承する必要があります。

- ModelSerializer： Modelクラスで定義しているフィールドをJSON文字列と変換するシリアライザとして利用する。
- listSerializer： 複数のModelリソースを利用する。
- Serializer： Modelを利用せず自分で定義した形でJSON文字列と変換するシリアライザとして利用する。


#### View

- DjangoRESTframeworkの場合は、JSON形式 のリクエストを受け取り、JSON形式のレスポンスを返す役割
- DjangoRESTframeworkでViewクラスを作る時は、以下のいずれかを継承
    - `views.APIView` : 
    - `generics.APIView` : 
    - `viewsets.ModelViewSet` : ひとつのクラスで（エンドポイントの切り替えて）CRUDの動き全てに対応可 




```view.py
# coding: utf-8

import django_filters
from rest_framework import viewsets, filters

from .models import User, Entry
from .serializer import UserSerializer, EntrySerializer


class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class EntryViewSet(viewsets.ModelViewSet):
    queryset = Entry.objects.all()
    serializer_class = EntrySerializer
```

※ `viewsets.ModelViewSet`
※  `queryset`と`serializer_class`を設定する
※ queryset : 
※ serializer_class : 利用するシリアライズ

#### URL Pattern

```[Project]/urls.py
# coding: utf-8

from django.conf.urls import url, include
from django.contrib import admin

from blog.urls import router as blog_router

urlpatterns = [
    url('admin/', admin.site.urls),
    # blog.urlsをincludeする
    url('api/', include(blog_router.urls)),
]
```

```[app]/urls.py
# coding: utf-8

from rest_framework import routers
from .views import UserViewSet, EntryViewSet


router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'entries', EntryViewSet)
```
※ ModelViewSetを継承するViewクラスの場合、ルーティングも簡素に記述する事が可能
※ DRFではAPIへのルーティングを行うためのRouterクラスを実装する必要があります。
urls.py に Router クラスをインスタンス化し、ViewSets オブジェクトをルーティングします。

https://qiita.com/yutoun/items/8ebb1864d012665507ce



関数ベース

```python
from rest_framework.response import Response
from rest_framework.decorators import api_view

@api_view(['GET', 'POST'])
def hello_world(request):
    if request.method == 'POST':
        return Response({"message": "Got some data!", "data": request.data})
    return Response({"message": "Hello, world!"})
```
※ 通常の Django は HttpResponse というクラスで返していたと思いますが、DRFでは Response というクラスで返却します。

※ クラス型のビューにはパーミッションやパーサなど複数の設定項目があるのですが、 関数型のビューには同じようにデコレータを使って設定することになります。

https://shotanukumizu-1000.hatenablog.com/entry/20211211

これがシンプルでいいかも

https://qiita.com/popo62520908/items/3184e0a8bc48ef8fff93




# Docker

https://inglow.jp/techblog/docker-django/


https://chigusa-web.com/blog/django-vscode-docker/

# Docker ( Django + MySQL + Nginx )

https://zenn.dev/dsonoda/articles/dbe14ca8af617ed85b1f





# AWS Fargate

https://django.baby/django-fargate-allowed_hosts/amp/django-fargate-allowed_hosts/django-fargate-allowed_hosts/


# 参考資料

- [Django4 Webアプリ開発　実装ハンドブック]()

https://tomato-develop.com/djangorestframework-serializer-view-basic-knowledge/

https://office54.net/python/django/web-api-rest