NARITO BLOG

Djangoで、月間カレンダー

Python, Django, Bootstrap4,

概要

Djangoで、カレンダーを作るシリーズの1つです。 月間カレンダーを作成していきます。

以下のようなものが作れます。 月間カレンダー

mixins.py

月間カレンダー用のMixinクラスを定義します。

class MonthCalendarMixin(BaseCalendarMixin):
    """月間カレンダーの機能を提供するMixin"""

    def get_previous_month(self, date):
        """前月を返す"""
        if date.month == 1:
            return date.replace(year=date.year-1, month=12, day=1)
        else:
            return date.replace(month=date.month-1, day=1)

    def get_next_month(self, date):
        """次月を返す"""
        if date.month == 12:
            return date.replace(year=date.year+1, month=1, day=1)
        else:
            return date.replace(month=date.month+1, day=1)

    def get_month_days(self, date):
        """その月の全ての日を返す"""
        return self._calendar.monthdatescalendar(date.year, date.month)

    def get_current_month(self):
        """現在の月を返す"""
        month = self.kwargs.get('month')
        year = self.kwargs.get('year')
        if month and year:
            month = datetime.date(year=int(year), month=int(month), day=1)
        else:
            month = datetime.date.today().replace(day=1)
        return month

    def get_month_calendar(self):
        """月間カレンダー情報の入った辞書を返す"""
        self.setup()
        current_month = self.get_current_month()
        calendar_data = {
            'now': datetime.date.today(),
            'month_days': self.get_month_days(current_month),
            'month_current': current_month,
            'month_previous': self.get_previous_month(current_month),
            'month_next': self.get_next_month(current_month),
            'week_names': self.get_week_names(),
        }
        return calendar_data

一番重要なメソッドはget_month_calendar()メソッドで、これが月間カレンダー構築に必要なものが詰まった辞書を返します。

辞書の各キーと返すものを説明していきます。

now

現在の日付で、datetime.date型のオブジェクトを返します。これを使うと、その日が今日なら色をつける...なんてことができます。

month_days

これは、その月の全ての日を返しています。以下のような二次元のリストになります。

[[30, 1, 2, 3, 4, 5, 6],
[7, 8, 9, 10, 11, 12, 13],
[14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27]
[28, 29, 30, 31, 1, 2, 3]]

さらに、全ての日はdatetime.date型です。月をまたいている日付がありますが、これらは正しく跨いだ月でのdatetime.date型になっています、安心です。

month_current

そのカレンダーが表示している月です(datetime.date型)

month_previous

currentの、前の月です(datetime.date型)

month_next

currentの、次の月です(datetime.date型)

week_names

['月', '火', '水'...]といった曜日のリストを返します。これはBaseCalendarMixinで提供されています。

urls.py

/と、/month/2018/5 のようなURLで、月間カレンダーが表示されます。 Mixin内でself.kwargs['year']やself.kwargs['month']というように年と月を取得するので、yearやmonthといった名前はとりあえず固定です。

from django.urls import path
from . import views

app_name = 'sampleapp'

urlpatterns = [
    path('', views.MonthCalendar.as_view(), name='month'),
    path('month/<int:year>/<int:month>/', views.MonthCalendar.as_view(), name='month'),
]

views.py

ビューではMixinを継承し、テンプレートへ渡すcontextとしてget_month_calendar()を含めれば、後はテンプレートに好きに書くことができます。

from django.views import generic
from . import mixins


class MonthCalendar(mixins.MonthCalendarMixin, generic.TemplateView):
    """月間カレンダーを表示するビュー"""
    template_name = 'app/month.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        calendar_context = self.get_month_calendar()
        context.update(calendar_context)
        return context

month.html

次のような感じで、月間カレンダーを構築していきます。

{% extends 'app/base.html' %}

{% block content %}
    <a href="{% url 'app:month' month_previous.year month_previous.month %}">前月</a>
    {{ month_current | date:"Y年m月" }}
    <a href="{% url 'app:month' month_next.year month_next.month %}">次月</a>

    <table class="table">
        <thead>
        <tr>
            {% for w in week_names %}
                <th>{{ w }}</th>
            {% endfor %}
        </tr>
        </thead>
        <tbody>
        {% for week in month_days %}
            <tr>
                {% for day in week %}
                    {% if now == day %}
                        <td class="table-success">
                            {% else %}
                        <td>
                    {% endif %}

                {% if month_current.month != day.month %}
                    {{ day | date:"m/d" }}
                {% else %}
                    {{ day.day }}
                {% endif %}

                </td>
                {% endfor %}
            </tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}

Djangoのdateフィルタを使って、2018年06月 のように表示させています。{{ month_current.year }}年{{ month_current.month}}月と書くよりも単純です。

{{ month_current | date:"Y年m月" }}

前月、次月は、それぞれprevious、nextがありましたので、それが使えます。

<a href="{% url 'app:month' month_previous.year month_previous.month %}">前月</a>
...
<a href="{% url 'app:month' month_next.year month_next.month %}">次月</a>

月の全ての日付は、2次元なリストなので、forで2回回す必要があります。 {% if now == day %} は、その日が今日なら、という意味です。今日ならtrタグに色をつけます。 {% if month_current.month != day.month %}は、月を跨いだ日付なら分かりやすくするために6/1 のように表示させています。

        {% for week in month_days %}
            <tr>
                {% for day in week %}
                    {% if now == day %}
                        <td class="table-success">
                            {% else %}
                        <td>
                    {% endif %}

                {% if month_current.month != day.month %}
                    {{ day | date:"m/d" }}
                {% else %}
                    {{ day.day }}
                {% endif %}

                </td>
                {% endfor %}
            </tr>
        {% endfor %}