【 Docker＋Nginx＋Django＋RDS】WEBアプリができるまで⑨身長体重を記録する＠一括削除機能つき

# 前置き
独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
　①[Djangoのようこそページへたどり着くまで](https://qiita.com/quantity82/items/e8c2392bf982efb55f80)
　②[NginxでDjangoのようこそページへたどり着くまで](https://qiita.com/quantity82/items/88a6348135e25c661feb)
　③[カスタムユーザーを作ってadminにたどり着く](https://qiita.com/quantity82/items/b3cd6ded5bbb5f3726f3)
　④[ログインログアウトをしよう](https://qiita.com/quantity82/items/8e4b7ce2ef56e60ad392)
　⑤[ユーザー登録（サインイン）機能を作ろう](https://qiita.com/quantity82/items/bbfc7e72d4a65c71e24c)
　⑥[ユーザーごとのデータ登録できるようにする〜CRU編](https://qiita.com/quantity82/items/4d019a4ee475208a5a26)
　⑦[ユーザーごとのデータ登録できるようにする〜削除編](https://qiita.com/quantity82/items/c48fee71b5f8eb766dae)
　⑧[画像ファイルのアップロード](https://qiita.com/quantity82/items/9bcbd25902f6878ee325)
　⑨[身長体重を記録する＠一括削除機能つき](https://qiita.com/quantity82/items/c8e58742eeaa8c0fc954)　<--ここです
　⑩[成長曲線グラフを描いてみよう](https://qiita.com/quantity82/items/619f5ed2a6cd45f7f592)
　⑪[本番環境へデプロイ＋色々手直し](https://qiita.com/quantity82/items/6e91b7ee7a2d7e0fab32)

# Goal
身長と体重を記録できるようにする。（次の章でグラフ化するデータを登録）

# やることとポイントのまとめ
この章で実装するのは、基本的に⑧で作ったもの構成と同じ。
１つ違うのは、レコードの一括削除機能をつけているところ。

Template→Viewへ、削除対象レコードのIDを渡すところが前回と違う。

あとは、Djangoの基本に則って、、、
①新しいAppを作る→setting.pyとurls.pyにAppを作ったことを知らせる
②Modelを作ろう→Migrationする。admin.pyにmodelを追加する
③Templateを作ろう→urls.pyでURLの体系を指定しよう
④Viewを作ろう→Modelを扱いやすいようFormも要るね


# ①APP作成〜プロジェクト内の指定
```
docker-compose run web python ./manage.py startapp phys
```
```setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig',
    'shoes.apps.ShoesConfig',
    'phys.apps.PhysConfig', #追加
    'bootstrap4',
    'bootstrap_datepicker_plus',
    'django_cleanup.apps.CleanupConfig',
]
```
```mysite/urls.py
from django.contrib import admin
from django.urls import path, include
from . import settings
from django.contrib.staticfiles.urls import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include('users.urls')),
    path('',include('shoes.urls')),
    path('',include('phys.urls')), #追加
]

urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
# ②Model作成
```phys/models.py
from django.db import models
from django.conf import settings
from django.utils import timezone
from users.models import KidsProfile

class PhysData(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    kidsProfile = models.ForeignKey(KidsProfile, on_delete=models.CASCADE)
    weight = models.DecimalField(
                max_digits=6,
                decimal_places=3
             )
    height = models.DecimalField(
                max_digits=5,
                decimal_places=2
             )
    date   = models.DateField(default=timezone.now)

    def __str__(self):
        return self.kidsProfile.name + str(self.date)
```
```phys/admin.py
from django.contrib import admin
from .models import PhysData

admin.site.register(PhysData)
```
# ③テンプレート〜URL設計
phys_data_addとphys_data_editは省略。
前回の章で作ったものと、変わらないです。

```phys/phys_data_list.html
{% extends 'base.html' %}

{% block content %}
<div class="row">
    <div class="col-md-12 col-lg-2">
        <div class="list-group">
          {% for kidsProfile in kidsProfiles %}
            <a href="/phys/list/{{kidsProfile.id}}" class="list-group-item list-group-item-action">{{kidsProfile.name}}</a>
          {% endfor %}
        </div>
    </div>
    <div class="col-md-12 col-lg-10 overflow-auto">
        <form method="post" action="{% url 'phys:phys_data_delete' %}">
        <table class="table table-hover">
          <thead class="thead-dark">
            <tr>
              <th>名前</th>
              <th>日付</th>
              <th>体重(kg)</th>
              <th>身長(cm)</th>
              <th>修正</th>
              <th>削除</th>
            </tr>
          </thead>
  	  {% for data_post in data_posts %}
          <tr>
            <td class="text-center">{{data_post.kidsProfile.name }}</td class="text-center">
            <td class="text-center">{{data_post.date }}</td>
            <td class="text-center">{{data_post.weight }}</td>
            <td class="text-center">{{data_post.height }}</td>
            <td class="text-center"><a href="{% url 'phys:phys_data_edit' data_post.pk %}" class="btn btn-success btn-sm" role="button">編集</a></td>
            <td class="text-center"><input type="checkbox" name="delete_ids" value="{{ data_post.pk }}"></input></td>
          </tr>
          {% endfor %}
          <tr>
            <td>
                <a href="{% url 'phys:phys_data_add' %}" role="button" class="btn btn-primary mr-auto">追加</a>
            </td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td class="text-center">
              {% csrf_token %}
              <input type="submit" value="一括削除" class="btn btn-danger float-right">
            </td>
          </tr>
        </table>
        </form>
    </div>
{% endblock %}
```
```phys/urls.py
from django.urls import path
from . import views

app_name = 'phys'

urlpatterns = [
    path('phys/list/', views.phys_data_list, name='phys_data_list'),
    path('phys/list/<kidsProfileId>', views.phys_data_list, name='phys_data_list'),
    path('phys/data_add/', views.phys_data_add, name='phys_data_add'),
    path('phys/data_edit/<DataPostId>', views.phys_data_edit, name='phys_data_edit'),
    path('phys/data_delete/', views.phys_data_delete, name='phys_data_delete'),
]
```

# ④ViewとModel

```phys/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .models import PhysData
from users.models import KidsProfile
from .forms import PhysDataForm, PhysDataEditForm



#身長体重データを昇順で返却
@login_required
def phys_data_list(request, **kwargs):
    user_name = request.user
    if len(kwargs) > 0:
        kidsProfileId = kwargs["kidsProfileId"]
    else:
        kidsProfileId = KidsProfile.objects.filter(user=user_name).order_by('id')[0]

    kids_profiles = KidsProfile.objects.filter(user=user_name)

    data_posts = PhysData.objects.filter(user=user_name, kidsProfile=kidsProfileId).order_by('date')
    params = {
        'data_posts': data_posts,
        'kidsProfiles' : kids_profiles,
    }

    return render(request, 'phys/phys_data_list.html', params)

#身長体重データを新規登録する
@login_required
def phys_data_add(request):
    user_name = request.user
    if request.method == 'POST':
      form = PhysDataForm(request.POST, user = user_name)
      if form.is_valid():
        data = form.save(commit=False)
        data.user = user_name
        data.save()
        return redirect('phys:phys_data_list')
    else:
      form = PhysDataForm(user = user_name)
    return render(request, 'phys/phys_data_add.html', {'form': form})

#身長体重データを編集する
@login_required
def phys_data_edit(request, DataPostId):
    physData = PhysData.objects.get(pk=DataPostId)
    if request.method == 'POST':
      form = PhysDataEditForm(request.POST)
      if form.is_valid():
        data = form.save(commit=False)
        data.user = request.user
        data.kidsProfile = physData.kidsProfile
        data.id = physData.id
        data.save()
        return redirect('phys:phys_data_list')
    else:
      form = PhysDataEditForm(
        {
          'weight' : physData.weight ,
          'height' : physData.height ,
          'date' : physData.date
        },
        user = request.user
      )
    return render(request, 'phys/phys_data_edit.html', {'form': form})

#身長体重データを削除する
@login_required
def phys_data_delete(request):
    delete_ids = request.POST.getlist('delete_ids')
    PhysData.objects.filter(pk__in=delete_ids).delete()
    return redirect('phys:phys_data_list')
```
```phys/forms.py
from django import forms
from .models import PhysData
from users.models import KidsProfile
import bootstrap_datepicker_plus as datetimepicker



class PhysDataForm(forms.ModelForm):
    class Meta:
        model = PhysData
        fields = ('kidsProfile', 'weight', 'height', 'date',)
        widgets = {
            'date': datetimepicker.DatePickerInput(
                           format='%Y-%m-%d'),
        }

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user','')
        super(PhysDataForm, self).__init__(*args, **kwargs)
        self.fields['kidsProfile'] = forms.ModelChoiceField(queryset=KidsProfile.objects.filter(user=user))

class PhysDataEditForm(forms.ModelForm):
    class Meta:
        model = PhysData
        fields = ('weight', 'height', 'date',)
        widgets = {
            'date': datetimepicker.DatePickerInput(
              format='%Y-%m-%d',
              ),
        }

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user','')
        kidsProfileId = kwargs.pop('kidsProfileId','')
        super(PhysDataEditForm, self).__init__(*args, **kwargs)
```

# できあがり
こんな形になります。削除レコードはチェックボックス形式で纏めて指定＆削除可能。
<img width="1440" alt="スクリーンショット 2020-02-13 01.34.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547363/725d7444-c492-7556-1214-e23eeb46ca5e.png">
登録画面は、普通。編集画面も、ほぼ同様。（子供の名前を選ばない程度）
<img width="1440" alt="スクリーンショット 2020-02-13 01.35.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547363/4285140f-130e-42c1-20ca-a9563d6c107a.png">