【Django】forms.ModelFormでフォームの作り方

#概要
Djangoの`forms.ModelForm`で「ユーザーが投稿できるフォーム画面」を実装したいと思います。

[前回と同様に](https://qiita.com/frosty/items/52a6d41c721c77a18e81)、タイトルとメモを投稿するフォームになっています。（掲示板みたいな）

なお、環境は`python 3.7.3`、`django 2.2`になります。

#1. プロジェクト作成
1-1. まずはプロジェクトファイルを作りたいので、下記のコマンドでディレクトリを作成します。

```:terminal
$ mkdir sampleform
```

1-2. 次に`sampleform`というプロジェクトを作成します。最後の`.`は正しいので、そのとおり実行してください。（ディレクトリは`simpleform`であることを確認してください）

```:terminal
~sampleform$ django-admin startproject formproject .
```

1-3. コマンドを実行すると`sampleform`ディレクトリには、下記のようにファイルが生成されます。

```:sampleform/
sampleform
├── formproject
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
```

#2. アプリケーションを作成
2-1. 新しくアプリケーションを作成したいので、下記のコマンドを実行します。今回は、`formapp`というアプリケーションです。

```:terminal
$ python manage.py startapp formapp
```

実行したらディレクトリは下記のようになると思います。

ざっくり３つに分けると、`sampleform`ディレクトリ内に、①`formapp`というアプリケーション、②`formproject`というプロジェクト、③`manage.py`ファイルになります。

```:simpleform/
simpleform
├── formapp
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── formproject
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-37.pyc
│   │   └── settings.cpython-37.pyc
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
```

2-2. `formproject`の`settings.py`にある`INSTALLED_APPS`に、`formapp`を追加します。

```python:formproject/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    #追加
    'formapp',
]
```

2-3. `form`と投稿一覧の`list`を表示できるよう`TEMPLATES`にも設定をします。`DIRS`に`[BASE_DIR, 'templates'`を追加します。

```python:formproject/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        #追加
        'DIRS': [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',
            ],
        },
    },
]
```

2-4. モデルの定義を`models.py`に追加します。

```python:formapp/models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    memo = models.TextField()
```

2-5. 新しいモデルをデータベースに追加するので、`makemigrations`と`migrate`を実行します。

```:terminal
$ python manage.py makemigrations
$ python manage.py migrate
```

#3. Formを表示させよう
3-1. まずは、`formproject/urls.py`に下記のように追加してください。

```python:formproject/urls.py
from django.contrib import admin
#includeを追加
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    #追加
    path('', include('formapp.urls')),
]
```

3-2. `formapp`に`urls.py`ファイルを新規作成して、下記のコードを追加してください。これで、
①`https://サイト名.com/`とアクセスすれば、投稿一覧の`list`が表示され、
②`https://サイト名.com/form`とアクセスすれば、新規投稿の`form`が表示され、
③`https://サイト名.com/details/番号`とアクセスすれば、投稿の詳細である`detail`が表示されるようにします。

```python:formapp/urls.py
from django.urls import path
from .views import listfunc, formfunc, detailfunc

urlpatterns = [
    path('', listfunc, name='list'),
    path('form/', formfunc, name='form'),
    path('detail/<int:pk>', detailfunc, name='detail'),
]
```

3-3. 後ほど作成する`Templates`にアクセスできるよう、`views.py`に処理を追加してください。

```python:formapp/views.py
from django.shortcuts import render, redirect
from django.utils import timezone
from .models import Post
from .forms import PostForm

def formfunc(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.save()
            return redirect('list')
    else:
        form = PostForm()
    return render(request, 'form.html', {'form': form})

def listfunc(request):
    posts = Post.objects.all()
    return render(request, 'list.html', {'posts': posts})

def detailfunc(request, pk):
    post = Post.objects.get(pk=pk)
    return render(request, 'detail.html', {'post': post})
```

3-4. `form`に必要な`forms.py`ファイルを、`formapp`に新規作成して、下記のようにしてください。

```python:formapp/forms.py
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ('title', 'memo')
```


3-5. `Templates`フォルダーを`sampleform`に新規作成してください。その中に下記ファイルを作成し、それぞれコードを追加してください。
①`base.html`
②`detail.html`
③`form.html`
④`list.html`

```html:sampleform/templates/base.html
<!DOCTYPE html>
<html>
  <head>
    <title>Post App</title>
  </head>
  <body>
    <a href="/">Home</a> | <a href="{% url 'form' %}">新規投稿</a>
    {% block content %}
    {% endblock %}
  </body>
</html>
```

```html:sampleform/templates/detail.html
{% extends 'base.html' %}

{% block content %}
  <p>タイトル：{{ post.title }}</p>
  <p>メモ：{{ post.memo }}</p>
{% endblock content %}

```

```html:sampleform/templates/form.html
{% extends 'base.html' %}

{% block content %}
  <h1>Create New Post</h1>
  <form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">投稿する</button>
  </form>
{% endblock %}

```

```html:sampleform/templates/list.html
{% extends 'base.html' %}

{% block content %}
  <p>-------------------</p>
  {% for post in posts %}
    タイトル：<a href="{% url 'detail' post.pk %}">{{ post.title }}</a>
    <p>-------------------</p>
  {% endfor %}
{% endblock content %}

```

ディレクトリは下記のようになるはずです。

```:sampleform/
sampleform
├── formapp
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── formproject
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-37.pyc
│   │   └── settings.cpython-37.pyc
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── templates
    ├── base.html
    ├── detail.html
    ├── form.html
    └── list.html
```

#4. localhost起動
4-1. localhostを起動して、しっかりと表示されるか確認したいので、`runserver`を実行します。

```:terminal
$ python manage.py runserver
```

4-2. [http://localhost:8000/](http://localhost:8000/)をブラウザーで開くと、このようなフォームが表示されるはずです。（まだ何も投稿していないので、一覧に何もありません）
![sampleform1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403981/2e0e49ed-a2dd-26c8-1b13-2ec37d7b12b1.png)

①「新規投稿」をクリックして、試しに投稿してみましょう。
![sampleform3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403981/e7d60662-889f-5c19-367b-7f9b3c99213c.png)

②そうしたら一覧に、さきほどの投稿が表示されているはずです。

最後に「タイトル（画像ではtest）」をクリックしてみると...
![sampleform4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403981/a1666c26-fd19-bc09-39c0-52e0d1d62a76.png)

③...しっかり詳細が表示されています！
![sampleform5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403981/b4b6b971-ff3e-52c4-ede5-9af32595f542.png)

このように`一覧表示（list）`、`投稿画面（form）`、`詳細画面（detail）`が動作していれば、正しく実行できています。

以上