1日で出来る！DjangoでCRUDを作る

# はじめに

[Vagrant+Python3+Django環境でHelloWorld](https://qiita.com/192_60_33_2/items/73df25e2dd64217165ae)の続編です。

Hello Worldを出したら、とりあえずデータ登録(C)、参照(R)、更新(U)、削除(D)をしたかったので、todoアプリ的なものを作りました。

ただただ、一覧表示、登録、更新・削除、をするアプリです。
うまくいけば、環境構築含め1日あれば作れる規模感です

基本的には、[はじめての Django アプリ作成](https://docs.djangoproject.com/ja/2.0/intro/tutorial01/)にそって進めます（多少アレンジが入ります）

# アプリケーションをはじめよう

todoアプリケーションを作成する

※python3で実行しているので注意!!

```shell-session
[vagrant@localhost mysite]$ pwd
/home/vagrant/app/mysite
[vagrant@localhost mysite]$ python3 manage.py startapp todos
[vagrant@localhost mysite]$ ls -ltr todos/
total 20
-rw-r--r--. 1 vagrant vagrant  63 Jul 30 16:31 views.py
-rw-r--r--. 1 vagrant vagrant  60 Jul 30 16:31 tests.py
-rw-r--r--. 1 vagrant vagrant  57 Jul 30 16:31 models.py
drwxr-xr-x. 1 vagrant vagrant 102 Jul 30 16:31 migrations
-rw-r--r--. 1 vagrant vagrant   0 Jul 30 16:31 __init__.py
-rw-r--r--. 1 vagrant vagrant  85 Jul 30 16:31 apps.py
-rw-r--r--. 1 vagrant vagrant  63 Jul 30 16:31 admin.py
```

ちなみに、「プロジェクト(mysite)」と「アプリケーション(todos)」の違いはこんな感じ

> プロジェクトとアプリケーション
>
> プロジェクトとアプリケーションの違いとは何でしょうか？アプリケーションとは、実際に何らかの処理を行う Web アプリケーションを指します。例えばブログシステムや公開レコードのデータベース、単純な投票アプリといった具合です。プロジェクトとは、あるウェブサイト向けに設定とアプリケーションを集めたものです。一つのプロジェクトには複数のアプリケーションを入れられ ます。また、一つのアプリケーションは複数のプロジェクトで使えます。


アプリケーションができたところで、新しい画面をつくってみよう。

まずは`todos/views.py`を作成

```python:todos/views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world! new page!")
```

続いて、`todos/urls.py`を作成し、urlとviews.pyをマッピング

```python:todos/urls.py
from django.urls import path
from . import views

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

最後に、`mysite/urls.py`を修正して、プロジェクト(mysite)のurlマッピングに上記を追加する

```python:mysite/urls.py
from django.contrib import admin
from django.urls import include, path ## ここに「include」を追加

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

これで、動作確認する。

`python3 manage.py runserver 0:8000`を実行してから、`http://localhost:18000/todos/`を確認

寂しい画面で**Hello, world! new page!**って出たと思います。

# DataBaseを利用しよう

今回は楽をしてSQLiteを使います（デフォルト設定でSQLiteになっています）

え？ホント？って方は`mysite/setting.py`を確認。

以下みたいに`'ENGINE': 'django.db.backends.sqlite3',`ってなっているはず。

```python:mysite/setting.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
```



ちょっと試しにマイグレーションしてみる（デフォルトの設定が読み込まれる）

```shell-session
[vagrant@localhost mysite]$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK
```

`migrate`で`mysite/setting.py INSTALLED_APPS`の設定を読み込んで、プロジェクトで必要なデータベースを作ってくれます。

では、今回作りたいモデルを作成します

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

# Create your models here.

class Todo(models.Model):
    todo_id = models.CharField(unique=True, max_length=5)
    title = models.CharField(max_length=50)
    main_text = models.CharField(max_length=300)
    update_date = models.DateTimeField('date published')
```

※fieldのリファレンスは[Model field reference](https://docs.djangoproject.com/en/1.11/ref/models/fields/)を参考ください

またまた、`mysite/setting.py`を修正して、上記をプロジェクトに反映

```python:mysite/setting.py
INSTALLED_APPS = [
    'todos.apps.TodosConfig', ## ここを追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
```

テーブルをつくる（モデルの変更分を反映）

```shell-session
[vagrant@localhost mysite]$ python3 manage.py makemigrations todos
Migrations for 'todos':
  todos/migrations/0001_initial.py
    - Create model Todo
```

これでテーブルができた！！


# CRUDを作ろう

まずはAPIを利用して、いろいろDBを操作してみよう。

`python3 manage.py shell`でPython 対話シェルを起動。

```shell-session
[vagrant@localhost mysite]$ python3 manage.py shell
Python 3.7.0 (default, Jul 26 2018, 15:06:09) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from todos.models import Todo
>>> from django.utils import timezone
>>> 
>>> Todo.objects.all()
<QuerySet []>
>>> 
>>> t = Todo(todo_id=1, title="hoge", main_text="fuga", update_date=timezone.now())
>>> t.save()
>>> Todo.objects.all()
<QuerySet [<Todo: Todo object (1)>]>
>>> 
>>> t1 = Todo.objects.get(todo_id=1)
>>> t1.title = 'hogehoge'
>>> t1.save()
>>>   
>>> t2 = Todo.objects.get(todo_id=1)
>>> t2.title
'hogehoge'
>>> 
>>> t2.delete()
(1, {'todos.Todo': 1})
>>> 
>>> Todo.objects.all()
<QuerySet []>
>>> 
```

これで参照、登録、更新、削除が一式できた

1. `Todo.objects.all()`で全件取得、`Todo.objects.get(todo_id=1)`でtodo_id=1と一致するものを取得。
2. `Todo(todo_id=1, title="hoge", main_text="fuga", update_date=timezone.now()).save() `で登録。重複していたら更新になる。（上記例では、変数においてから`save()`している）
3. `Todo.objects.get(todo_id=1).delete() `で削除

これをアプリケーションへ反映

`todos/views.py`でCRUD用の画面を作成

```python:todos/views.py
from django.http import HttpResponse
from django.utils import timezone
from todos.models import Todo

def index(request):
    return HttpResponse("Hello, world! new page!")

def create(request):
    t = Todo(todo_id=1, title="hoge", main_text="fuga", update_date=timezone.now())
    t.save()
    return HttpResponse("insert!")

def read(request):
    t = Todo.objects.get(todo_id=1)
    return HttpResponse('todo_id:%s,  title:%s, main_text:%s.' % (t.todo_id, t.title, t.main_text))

def update(request):
    t = Todo.objects.get(todo_id=1)
    t.title = 'changed!'
    t.save()
    return HttpResponse('update!')

def delete(request):
    t = Todo.objects.get(todo_id=1)
    t.delete()
    return HttpResponse('delete!')
```

もちろん、`todos/urls.py`も修正

```python:todos/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('create', views.create, name='create'),
    path('read', views.read, name='read'),
    path('update', views.update, name='update'),
    path('delete', views.delete, name='delete'),
]
```

これでアプリケーションを起動（`python3 manage.py runserver 0:8000 `）

以下でそれぞれがちゃんと動いていることを確認

1. `http://localhost:18000/todos/create` → **insert!**と寂しく表示（裏でデータが登録されてる）
2. `http://localhost:18000/todos/read` → **todo_id:1, title:hoge, main_text:fuga.**と出力。さっき登録したデータが表示できた
3. `http://localhost:18000/todos/update` →  **update!**と寂しく表示（裏でデータが更新されてる）
4. `http://localhost:18000/todos/read` → **todo_id:1, title:changed!, main_text:fuga.**と出力。更新されていることを確認
5. `http://localhost:18000/todos/delete` → **delete!**と寂しく表示（裏でデータが削除されてる）
6. `http://localhost:18000/todos/read` → 対象のデータがなくてエラー
7. `http://localhost:18000/todos/create` → 次の内容のためにInsertしておく

# Templatesを利用しよう

さすがに雑すぎるので、このCRUDを(cssあてたら)ちゃんとした画面にする

```python:todos/views.py
from django.http import HttpResponse
from django.utils import timezone
from django.shortcuts import render
from todos.models import Todo

def index(request):
    todo_list = Todo.objects.all()
    context = {'todo_list': todo_list}
    return render(request, 'todos/index.html', context)
```

これで、全件データ取得して、そのデータを`todo_list`というキー値でテンプレートに渡す感じ

では、テンプレート側を作成する。

新規で`todos/templates/todos/index.html`を作る

```html:todos/templates/todos/index.html
<html>
<head></head>
<body>
  <a href="/todos/new/">登録</a>
  {% if todo_list %}
    <table>
      <tr>
        <td>id</td>
        <td>title</td>
        <td></td>
      </tr>
      {% for todo in todo_list %}
      <tr>
        <td><a href="/todos/{{ todo.todo_id }}/">{{ todo.todo_id }}</a></td>
        <td>{{ todo.title }}</td>
        <td><a href="/todos/delete/{{ todo.todo_id }}/">削除</a></td>
      </tr>
      {% endfor %}
    </table>
  {% else %}
      <p>No todos are available.</p>
  {% endif %}
  </body>
</html>
```

こんな感じ

1. `{% if todo_list %}`は`todo_list`が空じゃなければ中身の処理を実施
2. `{% for todo in todo_list %}`これでfor文を実行(1つ1つをtodoという変数においている)
3. `{{ todo.todo_id }}`で値の出力

テンプレートとの紐付けもできた！

# CRUDを完成させよう

今まで使ったもの＋αでCRUDを完成させる

まずは`todos/urls.py`、`todos/views.py`で一式画面紐付けさせる

```python:todos/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('new/', views.new, name='new'),
    path('add/', views.add, name='add'),
    path('<int:todo_id>/', views.detail, name='detail'),
    path('add/<int:todo_id>/', views.update, name='update'),
    path('delete/<int:todo_id>/', views.delete, name='delete'),
]
```

`<int:todo_id>`と書けば、パラメータを渡せる。`views.py`を見ればイメージつくはず。

続いて

```python:todos/views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import timezone
from django.urls import reverse
from django.shortcuts import render
from todos.models import Todo, TodoForm

def index(request):
    todo_list = Todo.objects.all()
    context = {'todo_list': todo_list}
    return render(request, 'todos/index.html', context)

def new(request):
    return render(request, 'todos/new.html')

def add(request):
    t1 = Todo()
    t1.todo_id = len(Todo.objects.order_by('-todo_id'))+1
    t1.update_date = timezone.now()
    t = TodoForm(request.POST, instance=t1)
    t.save()
    return HttpResponseRedirect(reverse('index'))

def detail(request, todo_id):
    todo = Todo.objects.get(todo_id=todo_id)
    context = {'todo': todo}
    return render(request, 'todos/new.html', context)

def update(request, todo_id):
    t1 = Todo.objects.get(todo_id=todo_id)
    t = TodoForm(request.POST, instance=t1)
    t.save()
    return HttpResponseRedirect(reverse('index'))

def delete(request, todo_id):
    t = Todo.objects.get(todo_id=todo_id)
    t.delete()
    return HttpResponseRedirect(reverse('index'))
```

新しい内容をつらつらと書いていくと、

1. `HttpResponseRedirect`  → 登録完了後にリダイレクトする（`reverse`を使って`urls.py`のnameと紐づけている）
2. `TodoForm` → `form`と`model`の結びつける。（使ってみたものの、使いこなせなかった。。。）
3. `def detail(request, todo_id):` → `urls.py`の`<int:todo_id>`と結びつける。urlをパラメータとして利用できる
4. `request.POST` → POSTデータを利用できる。`request.POST['key値']`とすればパラメータを取得できるが、その`key値`がないとErrorになるので注意



ということで、新しく出てきた`todos/models.py`と`todos/templates/todos/new.html`を実装して終了

```python:todos/models.py
from django.db import models
from django.forms import ModelForm

# Create your models here.

class Todo(models.Model):
    todo_id = models.CharField(primary_key=True, max_length=5)
    title = models.CharField(max_length=50)
    main_text = models.CharField(max_length=300)
    update_date = models.DateTimeField('date published')

class TodoForm(ModelForm):
    class Meta:
        model = Todo
        fields = ['todo_id', 'title', 'main_text', 'update_date']
        exclude = ['todo_id', 'update_date']

```

`exclude = ['todo_id', 'update_date']`を指定することで画面に表示されないデータを表現できる、チェックを除外できる
とあるが、よくわかっていない。。。

最後に`new.html`を作る

```html:todos/templates/todos/new.html
<html>
<head></head>
<body>
  <form action="/todos/add/{% if todo %}{{ todo.todo_id }}/{% endif %}" method="post">
    {% csrf_token %}
    title: <input type="text" name="title" value="{{ todo.title }}"/>
    main: <input type="text" name="main_text" value="{{ todo.main_text }}"/>
    <input type="hidden" name="todo_id" value="{{ todo.todo_id }}"/>
    <input type="submit" value="登録"/>
  </form>
</body>
</html>
```
これで一式できたので、ポチポチしてみてください！

# おわりに

最初にも書いた通り以下に沿って作っています。

なので、今これを見るとすんなり入ってくるのでは？と思います。

1. [はじめての Django アプリ作成、その 1](https://docs.djangoproject.com/ja/2.0/intro/tutorial01/#writing-your-first-django-app-part-1)
2. [はじめての Django アプリ作成、その 2](https://docs.djangoproject.com/ja/2.0/intro/tutorial02/)
3. [はじめての Django アプリ作成、その 3](https://docs.djangoproject.com/ja/2.0/intro/tutorial03/)
4. [はじめての Django アプリ作成、その 4](https://docs.djangoproject.com/ja/2.0/intro/tutorial04/)

※まだ、一部（`ModelForm`など）使いこなせていないので、この辺りは別途整理しようと思っています。



結構大変だったな、と思いつつも3〜5hほどでここまでできたので、これを参考にしてもらたら半日でできる（と信じてます）

まだまだ、勉強中なので、誤りとか、要望とかあればご指摘いただけると嬉しいです！