Djangoで日付のカレンダー入力を実装する方法

<h1>目次</h1>

1. はじめに
2. 現状の確認
3. カレンダー入力を実装する
4. 実装するにあたって考えたこと

<h1>1. はじめに</h1>

DjangoでTodoリストアプリを作成した時に、日付の入力方式を「2022-09-28」などのように文字で入力するのは不便であったためカレンダー入力の実装を試みた。
初心者の自分にはやり方がわからず、苦戦したのでまとめてみました。

今回はTodoアプリのCreateViewとUpdateViewの日付入力でカレンダー入力を実装することを目標としています。

実装前
---

<img width="400" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090582/f44cfa4f-a82a-94d2-9c30-970f63765f44.png">

実装後
---
<img width="400" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090582/ae125110-b045-cf5a-e5fe-a8f0e2c5aad1.png">




<h1>2. 現状の確認</h1>

<h3>構成</h3>
私のプロジェクトではアプリ名をtodoとしています。


```
-templates
    |-create.html
- todo
    |- forms.py
    |- model.py
    |- views.py
    |- urls.py
```

models.pyではTodoModelを定義し、それをview.pyファイルにあるTodoCreateのmodelとして指定しています。

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

class TodoModel(models.Model):
    title = models.CharField(max_length=100)
    memo = models.TextField()
    priority = models.CharField(
        max_length = 50,
        choices = CHOICE
    )
    duedate = models.DateField()
    
    def __str__(self):
        return self.title
```

```python:view.py
from django.views.generic import CreateView

class TodoCreate(CreateView):
    template_name = 'create.html'
    model = TodoModel
    fields = ('title', 'memo', 'priority', 'duedate')
    success_url = reverse_lazy('list')

```

この段階ではTodoModelの日時を示すduedateはmodelsのDateFieldを適用しているだけで、数字の文字入力でしか日時を入力できません。
今回はこれにformsのDateInputのwidgetを使って、カレンダー入力を実装していきます。

<h1>3. カレンダー入力を実装する</h1>

<h2>form.pyファイルにTodoFormを定義</h2>

Djangoのmodelsではカレンダー入力を実装するwidgetが提供されていない（知識不足の可能性があるので断言はできないが、）ので、formsのDateInputをduedateに適用して、views.pyのTodoCreateビューにmodelとformの両方を適用していく。

```python:forms.py
from django import forms
from django.forms import ModelForm

from .models import TodoModel 　　　　#models.pyファイルのTodoModelをimportする。

class DateInput(forms.DateInput):
    input_type = 'date'


class TodoForm(ModelForm):

    class Meta:
        model = TodoModel
        fields = ['title', 'memo', 'priority', 'duedate']　　　　　#views.pyファイルのfieldsと同じ。
        widgets = {
            'duedate': DateInput(),
        }

```
<h2>views.pyのTodoCreateにTodoFormを適用する</h2>


これはforms.pyファイルにあるTodoCreateクラスのform_classにTodoFormを指定して完了です。


```python:views.py
from .forms import TodoForm  #forms.pyファイルからimportするのを忘れない。

class TodoCreate(CreateView):
    template_name = 'create.html'
    model = TodoModel
    form_class = TodoForm
    success_url = reverse_lazy('list')
```

注意点として、TodoCreateクラスに当初記載していたfields属性は、TodoFormクラス内のMetaクラスのfields属性と同じ様に扱われるために両方を同時に定義することはできないようです。

そのため、TodoCreate内でのfields属性は削除しておきましょう。
これを行わないと、ImproperlyConfigured: Specifying both ‘fields’ and ‘form_class’ is not permitted.というエラーが発生してしまいます。

<h1>4.　実装するにあたって考えたこと</h1>

以上でカレンダー入力の実装が完了しましたが、日時の入力方式にカレンダー入力を実装する基準について自分なりに考えてみました。

ユーザーファーストで考えた時に個人的には年月を遡らないといけない、「誕生日の入力フォーム」などではカレンダー入力よりもSelectDateWidgetでプルダウン選択を実装する方が使いやすいのではないかと考えています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1090582/1bde555e-b857-8db3-9ffb-68f0dc07e1bc.png)

一方で今回のTodoリストアプリのような直近の日時を設定したい時には、見やすさと使いやすさを含めてカレンダー入力の実装を検討するのもいいのではないでしょうか。


もっと簡単な実装方法などがありましたら、コメントにてご教示いただけると幸いです。






<h1>補足</h1>
urls.pyファイルはこのようになっている。


```python:urls.py

urlpatterns =[
                :
     path('create/', TodoCreate.as_view(), name = 'create'),
                :
]
```