Python + Django でCRUD

Djangoでジェネリックビューを使ったCRUDです。

##1.環境

| カテゴリ       | バージョンなど       |
|:-----------------|------------------:|
| os               |windows 10 home 64bit | 
| python           |            3.6.5 |
| django             |              2.1.4|


##2.アプリケーションの作成

####(1)以下のコマンドを実行してアプリケーションを作成する。

```cmd.prompt
(venv) C:\data\python\myproject>python manage.py startapp employee
```

####(2)ファイル構成

完成後は以下のようなフォルダ/ファイル構成になります。

```
employee
│  admin.py
│  apps.py
│  models.py
│  tests.py
│  urls.py
│  views.py
│  __init__.py
├─migrations
│  │  0001_initial.py
・・・
├─templates
│  └─employee
│          create.html
│          delete.html
│          detail.html
│          index.html
│          update.html
```

##3.migrate

まずはmodelを永続化するために、modelを作成してmigrateしていきます。

###(1)models.pyを編集する。

```employee\models.py
from django.db import models
from django.urls import reverse

# Create your models here.
class Employee(models.Model):
    """従業員"""
    name = models.CharField('氏名', max_length=255)
    age = models.IntegerField('年齢', blank=True, default=0)
    birthday = models.DateField('誕生日', blank=True)
    hiredate = models.DateField('入社日', blank=True)
    retiredate = models.DateField('退社日', blank=True)
    createddate = models.DateTimeField('登録日時', auto_now_add=True)
    lastmodifieddate = models.DateTimeField('最終更新日時', auto_now=True)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        """
        更処理完了時の戻り先URL
        """
        return reverse('employee:index')
```

###(2)プロジェクトにアプリケーションを追加する。

- settings.py

INSTALLED_APPSに以下のように追加します。

```myproject\settings.py
INSTALLED_APPS = [
    'accounts.apps.AccountsConfig', 
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
・・・
    'bootstrap4', #追加
    'employee', #追加
]
```

[bootstrap4](https://qiita.com/kaki_k/items/6e17597804437ef170ae)はこの記事を参考にしてください。

- urls.py

myprojectのurls.pyの「urlpatterns 」にも追加しておきます。

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

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

###(3)migrate

modelをmigrateします。

```cmd.prompt
(venv) C:\data\python\myproject>manage.py makemigrations employee
・・
(venv) C:\data\python\myproject>manage.py migrate employee
```

##4.crudの実装

準備ができましたのでいよいよ実装していきます。

###(1)urls.py

最初にルーティング(urls.py)です。CRUDは「create, read, update, delete」という意味で、それぞれのURLを設定しておきます。ただ、readは、一覧表示と詳細表示の２つがあるので、readとせず、それぞれindex, detailとしています。

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

app_name = 'employee'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'), 
    path('employee/create/', views.CreateView.as_view(), name='create'),
    path('employee/<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('employee/<int:pk>/update/', views.UpdateView.as_view(), name='update'),
    path('employee/<int:pk>/delete/', views.DeleteView.as_view(), name='delete'),
]
```

###(2)views.py

viewには、ジェネリックビュー（）を使います。

```employee\views.py
from django.urls import reverse_lazy
from django.views import generic
from .models import Employee
from pure_pagination.mixins import PaginationMixin

class IndexView(PaginationMixin, generic.ListView):
    model = Employee
    paginate_by = 5
    ordering = ['-createddate']
    template_name = 'employee/index.html'

class DetailView(generic.DetailView):
    model = Employee
    fields = '__all__'  # or ['colmunname', 'colmunname', 'colmunname']
    template_name = 'employee/detail.html'

class CreateView(generic.edit.CreateView):
    model = Employee
    fields = '__all__'  # or ['colmunname', 'colmunname', 'colmunname']
    template_name = 'employee/create.html'

class UpdateView(generic.edit.UpdateView):
    model = Employee
    fields = '__all__'  # or ['colmunname', 'colmunname', 'colmunname']
    template_name = 'employee/update.html'

class DeleteView(generic.edit.DeleteView):
    model = Employee
    success_url = reverse_lazy('employee:index')
    template_name = 'employee/delete.html'
```

###(3)html

最後はhtmlです。

```employee\templates\employee\create.html
{% extends 'commons/base.html' %}

{% load static %}

{% block links %}
<style media="screen">
</style>
{% endblock %}

{% block headertitle %}
新規作成
{% endblock %}

{% block content %}
  <form action="" method="post">
      {% csrf_token %}
      <table>{{form.as_table}}</table>
      <button type="submit" class="submit btn btn-info">登録</button>
  </form>
{% endblock %}
```

```employee\templates\employee\update.html
{% extends 'commons/base.html' %}

{% load static %}

{% block links %}
<style media="screen">
</style>
{% endblock %}

{% block headertitle %}
変更
{% endblock %}

{% block content %}
  <form action="" method="post">
      {% csrf_token %}
      <table>{{form.as_table}}</table>
      <button type="submit" class="submit btn btn-info">登録</button>
  </form>
{% endblock %}
```

```employee\templates\employee\delete.html
{% extends 'commons/base.html' %}

{% load static %}

{% block links %}
<style media="screen">
</style>
{% endblock %}

{% block headertitle %}
削除
{% endblock %}

{% block content %}
  <form action="" method="post">
    {% csrf_token %}
    <table>
        <tr><td>氏名     ：</td><td>{{ object.name             }}</td></tr>
        <tr><td>年齢     ：</td><td>{{ object.age              }}</td></tr>
        <tr><td>生年月日 ：</td><td>{{ object.birthday         }}</td></tr>
        <tr><td>入社日   ：</td><td>{{ object.hiredate         }}</td></tr>
        <tr><td>退社日   ：</td><td>{{ object.retiredate       }}</td></tr>
        <tr><td>登録日時 ：</td><td>{{ object.createddate      }}</td></tr>
        <tr><td>更新日時 ：</td><td>{{ object.lastmodifieddate }}</td></tr>
        <br/>
    </table>
    <br/>
    <br/>
    <button type="submit" class="submit delete btn btn-danger">削除</button>
  </form>
{% endblock %}
```

```employee\templates\employee\detail.html
{% extends 'commons/subwinbase.html' %}

{% load static %}

{% block links %}
<style media="screen">
  td{
    padding:5px;
  }
</style>
{% endblock %}

{% block headertitle %}
詳細表示
{% endblock %}

{% block content %}
  <div>
    <table>
        <tr><td>氏名     </td><td>{{ object.name             }}</td></tr>
        <tr><td>年齢     </td><td>{{ object.age              }}</td></tr>
        <tr><td>生年月日 </td><td>{{ object.birthday         }}</td></tr>
        <tr><td>入社日   </td><td>{{ object.hiredate         }}</td></tr>
        <tr><td>退社日   </td><td>{{ object.retiredate       }}</td></tr>
        <tr><td>登録日時 </td><td>{{ object.createddate      }}</td></tr>
        <tr><td>更新日時 </td><td>{{ object.lastmodifieddate }}</td></tr>
    </table>
    <br/>
  </div>
{% endblock %}
```

```employee\templates\employee\index.html
{% extends 'commons/subwinbase.html' %}

{% load static %}

{% block links %}
<link href="{% static 'css/pagination.css' %}" rel="stylesheet">
<style media="screen">
  .pagination > div {
    display: inline-block;
  }
</style>
{% endblock %}

{% block headertitle %}
一覧
{% endblock %}

{% block content %}

  <table class="table table-striped table-bordered　table-sm">
    <thead>
      <tr>
        <th>ID</th>
        <th>氏名</th>
        <th>年齢</th>
        <th>生年月日</th>
        <th>入社年月日</th>
        <th>退職年月日</th>
        <th><a href="{% url 'employee:create' %}" class="btn btn-primary btn-sm">追加</a></th>
      </tr>
    </thead>
    <tbody>
      {% for employee in object_list %}
      <tr>
        <td>{{ employee.id }}</td>
        <td>{{ employee.name }}</td>
        <td>{{ employee.age }}</td>
        <td>{{ employee.birthday }}</td>
        <td>{{ employee.hiredate }}</td>
        <td>{{ employee.retiredate }}</td>
        <td>
          <a href="{% url 'employee:update' employee.pk %}" class="btn btn-info btn-sm">修正</a>
          <a href="{% url 'employee:delete' employee.pk %}" class="btn btn-danger btn-sm">削除</a>
          <a href="{% url 'employee:detail' employee.pk %}" onclick="OpenSubWin(this);return false;" class="btn btn-success btn-sm">照会</a>
        </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>

  <br/>
  <!-- pagination -->
  {% include 'commons\pagination2.html' %}
{% endblock %}
```

base.html、pagination2.htmlはそれぞれ以下の記事を参照。
[Python+Django+psycopg2で内部結合クエリを試す](https://qiita.com/t-iguchi/items/827865481e82bb32ad04)
[DjangoでPaginationを実装する](https://qiita.com/t-iguchi/items/5f276f44d9e3bea76e1e)

以上で実装が完了
※本番ではformを作成して入力項目のバリデーションなども実装が必要でしょうが、この記事ではここまでとします。

##5.完成イメージ

あとはデータを入力して動かしてみましょう。

http://localhost:8000/employee/

![image.png](https://qiita-image-store.s3.amazonaws.com/0/246741/8238451b-d15f-6f44-5e2b-a218277d0263.png)

http://localhost:8000/employee/employee/create/

![image.png](https://qiita-image-store.s3.amazonaws.com/0/246741/3c5110e5-26e8-fffd-1a08-f02ae4bf7480.png)


わずかなコードでCRUDが実装できます。

以上です。

#追記 2018.12.14

detail.htmlで使用しているjavascript「OpenSubWin」はこちらを参照ください。

[ブラウザを閉じるときに、親画面が開いた子画面（サブウィンドウ）を閉じる](https://qiita.com/t-iguchi/items/77999c08bc26921d15ab)