Datepickerとは?
Datepickerはカレンダー選択による日時入力インターフェースです。
日時入力フィールドにフォーカスを与えるとカレンダーがポップアップ表示され、カレンダーで年月日時間を選択することで指定の値を入力することが出来ます。
django-bootstrap-datepicker-plus
ここではDjango用Datepickerのライブラリである「django-bootstrap-datepicker-plus」をご紹介します。
条件
- Django 2.1.3
- Python 3.7.0
DjangoでのDatepickerの使い方
PyCharmでdjango-bootstrap-datepicker-plusを使用するチュートリアルの手順をご紹介します。
以下のサイトを参考にしています。
https://django-bootstrap-datepicker-plus.readthedocs.io/en/latest/Walkthrough.html
プロジェクトのチェックアウト
バージョン管理からのチェックアウト > Gitで以下のURLを指定しプロジェクトをクローンします。
https://github.com/monim67/django-polls

以下のように「django-polls」プロジェクトがクローンされます。

必要パッケージのインストール
- django-bootstrap4
- django-bootstrap-datepicker-plus
メニューのファイル > 設定 > プロジェクト:django-polls > プロジェクト・インタープリターを選択します。

右上「最新バージョン」横の+ボタンを押します。
使用可能なパッケージ画面が開くので検索入力欄に「django-bootstrap4」を入力します。

「django-bootstrap4」が選択された状態でパッケージのインストールボタンを押してインストールします。
同様に「django-bootstrap-datepicker-plus」もインストールします。
使用可能なパッケージ画面を閉じると、パッケージ一覧に「django-bootstrap4」「django-bootstrap-datepicker-plus」が追加されていることがわかります。

OKボタンを押して設定画面を閉じます。
INSTALLED_APPSへの追記
mysite/settings.pyのINSTALLED_APPSに以下の2行を追記します。
"bootstrap_datepicker_plus",
"bootstrap4",
"bootstrap_datepicker_plus",
"bootstrap4",
"bootstrap_datepicker_plus",

CreateViewの追加
polls/views.pyにCreateViewを追記します。
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from bootstrap_datepicker_plus import DateTimePickerInput
from .models import Choice, Question
class CreateView(generic.edit.CreateView):
fields = ['question_text', 'pub_date']
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput()
# FIle: polls/views.py
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from bootstrap_datepicker_plus import DateTimePickerInput
from .models import Choice, Question
class CreateView(generic.edit.CreateView):
model = Question
fields = ['question_text', 'pub_date']
def get_form(self):
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput()
return form
# 他のクラスは変更なし
# FIle: polls/views.py
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from bootstrap_datepicker_plus import DateTimePickerInput
from .models import Choice, Question
class CreateView(generic.edit.CreateView):
model = Question
fields = ['question_text', 'pub_date']
def get_form(self):
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput()
return form
# 他のクラスは変更なし
テンプレートファイルの追加
polls/templates/polls/question_form.htmlを追加します。
<!-- File: polls/templates/polls/question_form.html -->
{% bootstrap_javascript jquery='full' %}
<form method="post">{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Save">
<!-- File: polls/templates/polls/question_form.html -->
{% load bootstrap4 %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}
{{ form.media }}
<form method="post">{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Save">
</form>
<!-- File: polls/templates/polls/question_form.html -->
{% load bootstrap4 %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}
{{ form.media }}
<form method="post">{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Save">
</form>
モデルにget_absolute_urlを追加
polls/models.pyのQuestionモデルにget_absolute_urlメソッドを追加します。
from django.db import models
from django.urls import reverse
from django.utils import timezone
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
return self.question_text
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
def get_absolute_url(self):
return reverse('polls:detail', kwargs={'pk': self.pk})
# FIle: polls/models.py
import datetime
from django.db import models
from django.urls import reverse
from django.utils import timezone
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
def get_absolute_url(self):
return reverse('polls:detail', kwargs={'pk': self.pk})
# FIle: polls/models.py
import datetime
from django.db import models
from django.urls import reverse
from django.utils import timezone
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
def get_absolute_url(self):
return reverse('polls:detail', kwargs={'pk': self.pk})
URLパターンの追加
polls/urls.pyのURLパターンにcreateを追加します。
from django.urls import path
path('', views.IndexView.as_view(), name='index'),
path('create', views.CreateView.as_view(), name='create'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
# FIle: polls/urls.py
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('create', views.CreateView.as_view(), name='create'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
# FIle: polls/urls.py
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('create', views.CreateView.as_view(), name='create'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
実行
Ctrl + Alt + Rを押してPyCharmのmanage.pyコンソールを開きます。
「runserver」を入力してEnterを押します。

ブラウザで以下のURLを開きます。
http://localhost:8000/polls/create

「Date published」の入力欄または右側のカレンダーアイコンをクリックするとカレンダーがポップアップ表示します。

カレンダーを閉じると選択した日時が入力された状態になります。

以上で完了です。
日時のフォーマット指定
以下のように、パラメータでformatを指定することで日時の表示を変えることが出来ます。
class CreateView(generic.edit.CreateView):
fields = ['question_text', 'pub_date']
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput(format='%Y-%m-%d')
class CreateView(generic.edit.CreateView):
model = Question
fields = ['question_text', 'pub_date']
def get_form(self):
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput(format='%Y-%m-%d')
return form
class CreateView(generic.edit.CreateView):
model = Question
fields = ['question_text', 'pub_date']
def get_form(self):
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput(format='%Y-%m-%d')
return form

NGケース(解決方法あり)
原因は不明ですが、「format=’%Y/%m/%d’」とすると、日時のフォーマットチェックに必ず引っかかってしまうようです。

原因
Djangoの日時チェックで使用される「DATETIME_INPUT_FORMATS」に「’%Y/%m/%d’」が無いのが原因です。
DATETIME_INPUT_FORMATS = [
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M:%S.%f', # '2006-10-25 14:30:59.000200'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
'%m/%d/%Y %H:%M:%S.%f', # '10/25/2006 14:30:59.000200'
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
'%m/%d/%y %H:%M:%S.%f', # '10/25/06 14:30:59.000200'
'%m/%d/%y %H:%M', # '10/25/06 14:30'
DATETIME_INPUT_FORMATS = [
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M:%S.%f', # '2006-10-25 14:30:59.000200'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
'%m/%d/%Y %H:%M:%S.%f', # '10/25/2006 14:30:59.000200'
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
'%m/%d/%y %H:%M:%S.%f', # '10/25/06 14:30:59.000200'
'%m/%d/%y %H:%M', # '10/25/06 14:30'
'%m/%d/%y', # '10/25/06'
]
DATETIME_INPUT_FORMATS = [
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M:%S.%f', # '2006-10-25 14:30:59.000200'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
'%m/%d/%Y %H:%M:%S.%f', # '10/25/2006 14:30:59.000200'
'%m/%d/%Y %H:%M', # '10/25/2006 14:30'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
'%m/%d/%y %H:%M:%S.%f', # '10/25/06 14:30:59.000200'
'%m/%d/%y %H:%M', # '10/25/06 14:30'
'%m/%d/%y', # '10/25/06'
]
対応
settings.pyに以下を記述することで、日時チェックを通すことが出来ます。
USE_L10N = False # DATETIME_INPUT_FORMATSに変更を加えるため、Falseに設定
from django.conf.global_settings import DATETIME_INPUT_FORMATS
DATETIME_INPUT_FORMATS += ('%Y/%m/%d',)
USE_L10N = False # DATETIME_INPUT_FORMATSに変更を加えるため、Falseに設定
from django.conf.global_settings import DATETIME_INPUT_FORMATS
DATETIME_INPUT_FORMATS += ('%Y/%m/%d',)
USE_L10N = False # DATETIME_INPUT_FORMATSに変更を加えるため、Falseに設定
from django.conf.global_settings import DATETIME_INPUT_FORMATS
DATETIME_INPUT_FORMATS += ('%Y/%m/%d',)
settings.pyを以下のように記述しても、日時チェックが通るようです。
from django.conf.global_settings import DATETIME_INPUT_FORMATS
DATETIME_INPUT_FORMATS += ('%Y/%m/%d',)
LANGUAGE_CODE = 'ja-jp'
TIME_ZONE = 'Asia/Tokyo'
USE_I18N = True
USE_L10N = True
USE_TZ = False
from django.conf.global_settings import DATETIME_INPUT_FORMATS
DATETIME_INPUT_FORMATS += ('%Y/%m/%d',)
LANGUAGE_CODE = 'ja-jp'
TIME_ZONE = 'Asia/Tokyo'
USE_I18N = True
USE_L10N = True
USE_TZ = False
from django.conf.global_settings import DATETIME_INPUT_FORMATS
DATETIME_INPUT_FORMATS += ('%Y/%m/%d',)
カスタムフォームで使用する
カスタムフォームで「django-bootstrap-datepicker-plus」を使用する方法をご紹介します。
カスタムフォーム追加
polls/forms.pyを追加します。
from bootstrap_datepicker_plus import DatePickerInput
class ToDoForm(forms.Form):
widget=forms.TextInput(attrs={"class": "form-control"})
widget=DatePickerInput(format='%m/%d/%Y')
# File: forms.py
from bootstrap_datepicker_plus import DatePickerInput
from django import forms
class ToDoForm(forms.Form):
todo = forms.CharField(
widget=forms.TextInput(attrs={"class": "form-control"})
)
date = forms.DateField(
widget=DatePickerInput(format='%m/%d/%Y')
)
# File: forms.py
from bootstrap_datepicker_plus import DatePickerInput
from django import forms
class ToDoForm(forms.Form):
todo = forms.CharField(
widget=forms.TextInput(attrs={"class": "form-control"})
)
date = forms.DateField(
widget=DatePickerInput(format='%m/%d/%Y')
)
viewsの編集
polls/viewsのCreatViewにget_context_dataを追加してToDoFormをcontextに渡すようにします。
from .forms import ToDoForm
class CreateView(generic.edit.CreateView):
fields = ['question_text', 'pub_date']
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['todo_form'] = form
from .forms import ToDoForm
class CreateView(generic.edit.CreateView):
model = Question
fields = ['question_text', 'pub_date']
def get_form(self):
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput()
return form
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = ToDoForm()
context['todo_form'] = form
return context
from .forms import ToDoForm
class CreateView(generic.edit.CreateView):
model = Question
fields = ['question_text', 'pub_date']
def get_form(self):
form = super().get_form()
form.fields['pub_date'].widget = DateTimePickerInput()
return form
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = ToDoForm()
context['todo_form'] = form
return context
テンプレートの編集
polls/templates/polls/question_form.htmlに{{ todo_form }}を追記します。
<!-- File: polls/templates/polls/question_form.html -->
{% bootstrap_javascript jquery='full' %}
<form method="post">{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Save">
<!-- File: polls/templates/polls/question_form.html -->
{% load bootstrap4 %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}
{{ form.media }}
<form method="post">{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Save">
</form>
{{ todo_form }}
<!-- File: polls/templates/polls/question_form.html -->
{% load bootstrap4 %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}
{{ form.media }}
<form method="post">{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" value="Save">
</form>
{{ todo_form }}
サーバー実行
「runserver」を実行し、ブラウザで以下のURLを開きます。
http://localhost:8000/polls/create
追加したToDoフォームが追加されていることがわかります。

フォームの設定例
フォームでオプションを指定することにより様々な設定を行うことが出来ます。
以下は、開始日時と終了日時で日時にの大小に齟齬が出ないようにした一例です。
「.start_of(‘term’)および.end_of(‘term’)」でペアを指定しています。
また、初期表示や表示フォーマット、最小/最大日時の設定なども行っています。
class SampleForm(forms.Form):
start_date = forms.DateField(
widget=datetimepicker.DateTimePickerInput(
format='%Y/%m/%d %H:%M:%S',
'dayViewHeaderFormat': 'YYYY年 MMMM',
'allowInputToggle': True,
'minDate': '2018/10/22', # 最小日時(データ取得開始日)
'defaultDate': '2018/10/22', # 初期表示
end_date = forms.DateField(
initial=dt.now().strftime('%Y/%m/%d %H:%M:%S'), # 初期値
widget=datetimepicker.DateTimePickerInput(
format='%Y/%m/%d %H:%M:%S',
'dayViewHeaderFormat': 'YYYY年 MMMM',
'allowInputToggle': True,
'maxDate': (dt.now() + timedelta(days = 1)).strftime('%Y/%m/%d %H:%M:%S'), # 最大日時(翌日)
class SampleForm(forms.Form):
start_date = forms.DateField(
label='開始日時',
widget=datetimepicker.DateTimePickerInput(
format='%Y/%m/%d %H:%M:%S',
options={
'locale': 'ja',
'dayViewHeaderFormat': 'YYYY年 MMMM',
'ignoreReadonly': True,
'allowInputToggle': True,
'minDate': '2018/10/22', # 最小日時(データ取得開始日)
'defaultDate': '2018/10/22', # 初期表示
}
).start_of('term'),
)
end_date = forms.DateField(
label='終了日時',
initial=dt.now().strftime('%Y/%m/%d %H:%M:%S'), # 初期値
widget=datetimepicker.DateTimePickerInput(
format='%Y/%m/%d %H:%M:%S',
options={
'locale': 'ja',
'dayViewHeaderFormat': 'YYYY年 MMMM',
'ignoreReadonly': True,
'allowInputToggle': True,
'maxDate': (dt.now() + timedelta(days = 1)).strftime('%Y/%m/%d %H:%M:%S'), # 最大日時(翌日)
}
).end_of('term'),
)
class SampleForm(forms.Form):
start_date = forms.DateField(
label='開始日時',
widget=datetimepicker.DateTimePickerInput(
format='%Y/%m/%d %H:%M:%S',
options={
'locale': 'ja',
'dayViewHeaderFormat': 'YYYY年 MMMM',
'ignoreReadonly': True,
'allowInputToggle': True,
'minDate': '2018/10/22', # 最小日時(データ取得開始日)
'defaultDate': '2018/10/22', # 初期表示
}
).start_of('term'),
)
end_date = forms.DateField(
label='終了日時',
initial=dt.now().strftime('%Y/%m/%d %H:%M:%S'), # 初期値
widget=datetimepicker.DateTimePickerInput(
format='%Y/%m/%d %H:%M:%S',
options={
'locale': 'ja',
'dayViewHeaderFormat': 'YYYY年 MMMM',
'ignoreReadonly': True,
'allowInputToggle': True,
'maxDate': (dt.now() + timedelta(days = 1)).strftime('%Y/%m/%d %H:%M:%S'), # 最大日時(翌日)
}
).end_of('term'),
)
詳細は以下を参照してください。
http://eonasdan.github.io/bootstrap-datetimepicker/Options/
参考
django-bootstrap-datepicker-plus documentation
https://django-bootstrap-datepicker-plus.readthedocs.io/en/latest/index.html
“DjangoでDatepicker(カレンダーによる日時入力)を使用する方法” に対して1件のコメントがあります。