Djangoでシーケンス使ってみた

# はじめに

やったことをまとめると以下3つ

- [1日で出来る！DjangoでCRUDを作る](https://qiita.com/192_60_33_2/items/eb663f188e0de880871b)の続き（ソースはここのページのものが基準になってます）
- シーケンスをちゃんと使うようにした
  - 連番の取得を、登録されている最大値+1からの脱却
- `FormModel`を少し使えるようにした
  - `views.py`と`templates`の連携を`Todo`を利用していたが、`TodoForm`を使うように修正

# シーケンスを使ってみた

Djangoで提供してくれているようなのでそちらを使ってみた

```shell-session
[vagrant@localhost mysite]$ sudo pip3 install django-sequences
Collecting django-sequences
  Downloading https://files.pythonhosted.org/packages/7a/2d/0c21146f8283ee56b3d920859e55be2550b2c8fb9fb8b6aa9ac2930690ea/django_sequences-2.2-py2.py3-none-any.whl
Installing collected packages: django-sequences
Successfully installed django-sequences-2.2
[vagrant@localhost mysite]$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sequences, sessions, todos
Running migrations:
  Applying sequences.0001_initial... OK
[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 sequences import get_next_value
>>> get_next_value('todo_seq')                   
1
>>> get_next_value('todo_seq')
2
>>> get_next_value('todo_seq_next')
1
>>> get_next_value('todo_seq')
3
```

これでよしなに`sequence`で発番ができたので、シーケンスを使うようにする

```python:todos/views.py
def add(request):
    t1 = Todo()
    t1 = get_next_value('todo_seq')
    t = TodoForm(request.POST, instance=t1)
    t.save()
    return HttpResponseRedirect(reverse('index'))
```

ついでに、`save()`のたびに日付は自動更新されるようにする

```python:todos/models.py
update_date = models.DateTimeField('date published', auto_now=True)
```
- `auto_now=True`をつけるだけ
    - `auto_now_add=True`なら登録時のみ日付が自動でささる
        - `insert_date`と`update_date`で分けているなら使うべき

※ソースは一部抜粋

# FormModelを(ちょっと)使ってみた

基本的に`TodoForm`を使って連携するように修正　※ただし、`todo_id`だけは別途連携してる・・・
ついでに、`add:登録`と`update:更新`を共通化する。

```python:todos/views.py
from django.http import HttpResponse, HttpResponseRedirect
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):
    context = {'form': TodoForm(), 'todo_id': ""}
    return render(request, 'todos/new.html', context)

def add(request):
    todo = Todo()
    todo.todo_id = request.POST['todo_id']
    todo_form = TodoForm(request.POST, instance=todo)

    if todo_form.is_valid():
        todo_form.save()
        return HttpResponseRedirect(reverse('index'))
    else:
        return render(request, 'todos/new.html', {'form': todo_form, 'todo_id': todo_id})

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

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

- パラメータ`context`を`form: TodoForm()`になるように修正
  - `todo_id`は別で連携する
- `todo_form.is_valid`で登録可能かチェックを追加
  - ダメなら入力画面へ戻す

登録、更新をまとめたので`url.py`から`update`を削除する

```python:todos/urls.py
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('delete/<int:todo_id>/', views.delete, name='delete'),
]
```

もちろん`template`も修正する

```html:todos/templates/todos/new.html
<html>
<head></head>
<body>
  <form action="/todos/add/" method="post">
    {% csrf_token %}
    {% for field in form.visible_fields %}
    {{ field.label }}: <input type="text" name="{{ field.name }}" value="{{ field.value }}"/>
    {{ field.errors }}
    <br>
    {% endfor %}
    <input type="hidden" name="todo_id" value="{{ todo_id }}"/>
    <input type="submit" value="登録"/>
  </form>
</body>
</html>
```

- `form.visible_fields`は`hidden`以外のfieldのことをさす（今回は`hidden`ないけど）
  - `hidden`は、`form.hidden_fields`を利用する
  - `{{ form }}`でinputタグを書かずにformを作ってくれる
    - `form.as_table`ならテーブルで、`form.as_ul`ならリスト形式、`form.as_p`なら箇条書きで出力
       - 個人的(最終的)には、cssとかも当てたいのでhtmlタグは自分で書きたいと思う

おまけ
シーケンス取得を`Model`側で実施してみた
`save()`を継承して、`todo_id`のチェックを行い、空だったらシーケンスから採番する

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

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

    def save(self, *args, **kwargs):
        if self.todo_id == "":
            self.todo_id = get_next_value('todo_seq')
        super().save(*args, **kwargs)

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

- `Todo`に`save()`を実装
  - `todo_id == ""`ならシーケンスから採番する
    ※やってみたけど微妙だな。って思いました。`views.py`でハンドリングする方がいいと思います
    ※役に立つかもしれないので、残しままにします

# おわりに

まだまだ勉強中
「自分のこうしたい！」が全く反映できずにモヤモヤ中

でも、やりたいことは別なので次に進もうと思います。