Djangoプロトタイプのすゝめ

# Djangoプロトタイプのすゝめ
自分がまだ扱ったことのないフレームワークや言語を習得する際、以下のような手順を踏むべきだと考えています。

1. テキストの解説を見ながらコードを模写する
2. (チョー)簡単なアプリを作ってみる
3. (ちゃんとした)アプリを作ってみる
4. 繰り返し使う
5.  気づいたら習得している

この記事では2.を中心的に取り上げます。

# さて、何から始めたらよいのか
テキストを模写しているときはテキストの通りに進めたら、いつの間にか１つの機能が完成している(？！)という状況でした。
ここで、何から始めればいいの？ってなると思います。
まずは最初のステップについて説明します。
## まずは何を作るか
ここでは、(チョー)簡単なアプリを作ります。
（習得していない状態でいきなり本格的なアプリを作ろうとすると詰みます）

(チョー)簡単なアプリとは...?
どのアプリでも(ほぼ)必ずあるものとは？
<details><summary>それは...</summary>
CRUD操作ができること
</details>
です！

これを持つアプリとして以下のようなものがあります。
- ブログ
- タスク管理アプリ
- ミニSNS

ということで、今回は**ブログ**を作ってみます
## 環境構築をする
今回はDockerを使って環境構築をしていきます。
プロジェクトフォルダを作成して、そのフォルダに移動しましょう。

プロジェクトフォルダ直下に以下のファイルを追加します。
```Dockerfile
FROM python:3.11
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
ADD . /code/
```
```requirements.txt
Django == 4.2.2
psycopg2-binary
django-environ
```
```docker-compose.yml
version: "3"

services:
  db:
    image: postgres
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
  django:
    build:
      context: .
      dockerfile: Dockerfile
    command: python3 manage.py runserver 0.0.0.0:8000
    container_name: django
    env_file:
      - .env
    volumes:
      - ./django_app:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

volumes:
  postgres_data:
```
```.env
POSTGRES_DB: "[データベース名]"
POSTGRES_USER: "[データベースのユーザ名（管理者）]"
POSTGRES_PASSWORD: "[データベースのパスワード]"
```
今後のアプリ開発ではGit管理をするので、それも意識して```.env```ファイルを作成します。
プロジェクトフォルダに移動して以下のコマンドを実行しましょう.

:::note info
コマンドを実行するときは、そのコマンドの意味を理解してから実行するようにしましょう。
:::
:::note warn
以下のコマンドはプロジェクト名もそのまま記述しています。
:::


```bash
docker-compose build
```
```bash
docker-compose django django-admin startproject django_app
```

## アプリのひな形を作る
以下のコマンドを実行してアプリのひな形を作成しましょう
```
docker-compose run django python manage.py startapp blog
```
# アプリを作ってみる
環境構築は上の解説を参考にしてください。
（(私のように)環境構築であまり詰まってほしくはない...）
Django以外のアプリ開発経験がある方は挑戦してみると色々学ぶことができます

さて、今回作成するアプリの概要を説明します。
コード自体は最後に載せるので、まずは挑戦してみてください

### 要件
#### 作るもの
- ブログ
（といっても、記事の編集と削除は省きます）
#### 手順
1. DBのことは考えず、views.pyからtemplateファイルに値を渡してみましょう
2. models.pyで、テーブルを定義しましょう（マイグレーションもお忘れなく）
3. DBから値を取得してtemplateファイルに値を渡しましょう

<details><summary>最終的なファイルたち</summary>

```index.html
<!DOCTYPE html>
<html>
  <head>
    <title>{{ page_title }}</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />
  </head>
  <body>
    <div class="container">
      <h1 class="my-4">{{ page_title }}</h1>
      <a href="{% url "create" %}" class="btn btn-success mb-4">投稿する</a>
      {% for post in posts %}
      <div class="list-group">
        <a
          href="{% url 'detail' post.id %}"
          class="list-group-item list-group-item-action"
        >
          <h5 class="mb-1">{{ post.title }}</h5>
          <small class="text-muted">{{ post.username }}</small>
          <p class="mb-1">{{ post.text }}</p>
        </a>
      </div>
      {% endfor %}
    </div>
  </body>
</html>
```
```create.html
<!-- blog/templates/blog/create.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>{{ page_title }}</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />
  </head>
  <body>
    <div class="container">
      <h1 class="my-4">Create a New Blog Post</h1>
      <form method="post" action="{% url "create" %}">
        {% csrf_token %}
        <div class="form-group">
          <label for="title">Title:</label>
          <input
            type="text"
            class="form-control"
            id="title"
            name="title"
            required
          />
        </div>
        <div class="form-group">
          <label for="username">Username:</label>
          <input
            type="text"
            class="form-control"
            id="username"
            name="username"
            required
          />
        </div>
        <div class="form-group">
          <label for="text">Content:</label>
          <textarea
            class="form-control"
            id="content"
            name="text"
            rows="5"
            required
          ></textarea>
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
      </form>
    </div>
  </body>
</html>
```
```detail.html
<!-- blog/templates/blog/show.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>{{ page_title }}</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />
  </head>
  <body>
    <div class="container">
      <h1 class="my-4">{{ post.title }}</h1>
      <p class="text-muted">{{ post.username }}</p>
      <p>{{ post.text }}</p>
      <a href="{% url 'index' %}" class="btn btn-primary">全件表示に戻る</a>
    </div>
  </body>
</html>
```
```views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse
from .forms import PostForm
from .models import Post

def index(request):
    posts = Post.objects.all()
    params = {
        'page_title': '記事一覧',
        'posts': posts,
    }
    return render(request, 'blog/index.html', params)

def detail(request, post_id):
    post = Post.objects.get(id=post_id)
    params = {
        'page_title': '記事詳細',
        'post': post,
    }
    return render(request, 'blog/detail.html', params)

def create(request):
    if (request.method == 'POST'):
        obj = Post()
        post = PostForm(request.POST, instance=obj)
        post.save()
        return redirect(to='/blog')
    params = {
        'page_title': '投稿する',
        'form': PostForm(),
    }
    return render(request, 'blog/create.html', params)
```
```forms.py
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['username', 'title', 'text']
        labels = {
            'title': '記事タイトル',
            'text': '記事本文',
        }
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control'}),
            'text': forms.Textarea(attrs={'class': 'form-control', 'rows': 5}),
        }
```
```models.py
from django.db import models

class Post(models.Model):
    username = models.CharField(max_length=255, default='default_name')
    title = models.CharField(max_length=100)
    text = models.TextField(max_length=300, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
```
```blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:post_id>', views.detail, name='detail'),
    path('create', views.create, name='create'),
    path('edit', views.edit, name='edit'),
]
```
```django_app/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
]
```
```settings.py
# 変更箇所のみ記載
import environ

env = environ.Env()
environ.Env.read_env()

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
]
POSTGRES_DB = env('POSTGRES_DB')
POSTGRES_USER = env('POSTGRES_USER')
POSTGRES_PASSWORD = env('POSTGRES_PASSWORD')
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': POSTGRES_DB,
        'PASSWORD': POSTGRES_PASSWORD,
        'USER': POSTGRES_USER,
        'HOST': 'db',
    }
}
```

</details>

# おわりに
無事、ブログアプリ完成したでしょうか？
環境や私の不備の関係でコードが思うように動かなかったということがあるかもしれません...。
コードが動かないときのtipsを記しておきます

- コンソールに表示されているエラー文を読む
（このコマンドを実行しろ！とかの文章が表示される場合があります）

- エラー文を検索してみる
Qiitaとかいろんなサイトで、同じところで詰まっている方がいるかもしれません。

- ChatGPTに相談してみる
ある程度は相談に乗ってくれます。ChatGPTの文章に含まれるキーワードを使って検索してみるといいかもしれません。