Djangoでスケジュール付き週間カレンダー

Python Django Bootstrap4

概要

Djangoでカレンダーを作るシリーズの1つです。週間カレンダーをベースに、スケジュール付きの週間カレンダーを作成していきます。スケジュールが表示されると、少し見た目がよくなります。

次のような感じです。 スケジュール付き週間カレンダー

mixins.py

WeekCalendarMixinをベースに、新しいMixinを定義します。

class WeekWithScheduleMixin(WeekCalendarMixin):
    """スケジュール付きの、週間カレンダーを提供するMixin"""

    def get_week_schedules(self, start, end, days):
        """それぞれの日とスケジュールを返す"""
        lookup = {
            # '例えば、date__range: (1日, 31日)'を動的に作る
            '{}__range'.format(self.date_field): (start, end)
        }
        # 例えば、Schedule.objects.filter(date__range=(1日, 31日)) になる
        queryset = self.model.objects.filter(**lookup)

        # {1日のdatetime: 1日のスケジュール全て, 2日のdatetime: 2日の全て...}のような辞書を作る
        day_schedules = {day: [] for day in days}
        for schedule in queryset:
            schedule_date = getattr(schedule, self.date_field)
            day_schedules[schedule_date].append(schedule)
        return day_schedules

    def get_week_calendar(self):
        calendar_context = super().get_week_calendar()
        calendar_context['week_day_schedules'] = self.get_week_schedules(
            calendar_context['week_first'],
            calendar_context['week_last'],
            calendar_context['week_days']
        )
        return calendar_context

基本的には週間カレンダーと同じで、get_week_calendar()がメインです。返すものも同様ですが、week_day_schedulesという{日:その日のスケジュール}な辞書も返すようになりました。例えばその週が1日から7日までならば、次のような辞書を返します。

{
    1日: [1日の全てのスケジュール], 
    2日: [2日の全てのスケジュール],
    3日: [2日の全てのスケジュール],
    4日: [2日の全てのスケジュール],
    5日: [2日の全てのスケジュール],
    6日: [2日の全てのスケジュール],
    7日: [2日の全てのスケジュール],
},

また、クラス属性modelにはスケジュールを表すモデルを、date_fieldにはそのモデルの日付を表すフィールド名を指定して使えるようにしています。

urls.py

週間カレンダーと殆ど同じですね。

    path('week_with_schedule/', views.WeekWithScheduleCalendar.as_view(), name='week_with_schedule'),
    path(
        'week_with_schedule/<int:year>/<int:month>/<int:day>/',
        views.WeekWithScheduleCalendar.as_view(),
        name='week_with_schedule'
    ),

views.py

WeekWithScheduleMixinを継承すれば、後は週間カレンダーと同様です。

from .models import Schedule
...
...
class WeekWithScheduleCalendar(mixins.WeekWithScheduleMixin, generic.TemplateView):
    """スケジュール付きの週間カレンダーを表示するビュー"""
    template_name = 'app/week_with_schedule.html'
    model = Schedule
    date_field = 'date'

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

week_with_schedule.html

スケジュール付き週間カレンダーのテンプレートです。

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

    <a href="{% url 'app:week_with_schedule' week_previous.year week_previous.month  week_previous.day %}">前週</a>
    {{ week_first | date:"Y年m月d日" }}〜{{ week_last | date:"Y年m月d日" }}
    <a href="{% url 'app:week_with_schedule' week_next.year week_next.month  week_next.day %}">次週</a>

    <table class="table table-bordered">
        <thead>
        <tr>
            {% for w in week_names %}
                <th>{{ w }}</th>
            {% endfor %}
        </tr>
        </thead>
        <tbody>
        <tr>
            {% for day in week_days %}
                {% if now == day %}
                    <td class="table-success">
                {% else %}
                    <td>
                {% endif %}
            {% if week_first.month != day.month %}
                {{ day | date:"m/d" }}
            {% else %}
                {{ day.day }}
            {% endif %}
            </td>
            {% endfor %}
        </tr>
        <tr>
            {% for schedules in week_day_schedules.values %}
                <td>
                    {% for s in schedules %}
                        {{ s.start_time }} - {{ s.end_time }}<br>
                        {{ s.summary }}<br>
                        {{ s.description | linebreaks }}
                    {% endfor %}
                </td>
            {% endfor %}
        </tr>
        </tbody>
    </table>
{% endblock %}

週間カレンダーと違うのが、以下の部分です。スケジュールを取り出しています。

        <tr>
            {% for schedules in week_day_schedules.values %}
                <td>
                    {% for s in schedules %}
                        {{ s.start_time }} - {{ s.end_time }}<br>
                        {{ s.summary }}<br>
                        {{ s.description | linebreaks }}
                    {% endfor %}
                </td>
            {% endfor %}
        </tr>

日付とスケジュールを一緒にする

現状では日付とスケジュールが別の行に表示されています。同じ行に表示してみましょう。

week_day_schedulesが日: スケジュールな辞書なので、辞書のitems()でキーと値...つまり日付とスケジュールが一緒に取れます。

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

    <a href="{% url 'app:week_with_schedule' week_previous.year week_previous.month  week_previous.day %}">前週</a>
    {{ week_first | date:"Y年m月d日" }}〜{{ week_last | date:"Y年m月d日" }}
    <a href="{% url 'app:week_with_schedule' week_next.year week_next.month  week_next.day %}">次週</a>

    <table class="table table-bordered">
        <thead>
        <tr>
            {% for w in week_names %}
                <th>{{ w }}</th>
            {% endfor %}
        </tr>
        </thead>
        <tbody>
        <tr>
            {% for day, schedules in week_day_schedules.items %}
                <td>
                    <!-- 日付 -->
                    {% if week_first.month != day.month %}
                        {{ day | date:"m/d" }}
                    {% else %}
                        {{ day.day }}
                    {% endif %}
                    <br>

                    <!-- その日のスケジュール -->
                    {% for s in schedules %}
                        {{ s.start_time }} - {{ s.end_time }}<br>
                        {{ s.summary }}<br>
                        {{ s.description | linebreaks }}
                    {% endfor %}
                </td>
            {% endfor %}
        </tr>
        </tbody>
    </table>
{% endblock %}

このような見た目になります。

全て同じ行に表示する

曜日、日付、スケジュールを全て同じ行に表示したいかもしれません。

まず、views.pyを編集します。

class WeekWithScheduleCalendar(mixins.WeekWithScheduleMixin, generic.TemplateView):
    """スケジュール付きの週間カレンダーを表示するビュー"""
    template_name = 'app/week_with_schedule.html'
    model = Schedule
    date_field = 'date'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        calendar_context = self.get_week_calendar()
        context.update(calendar_context)
        context['week_row'] = zip(
            calendar_context['week_names'],
            calendar_context['week_days'],
            calendar_context['week_day_schedules'].values()
        )
        return context

組み込み関数zip()を使い、曜日、日付、スケジュールをループ毎に1つずつ取り出せるようにしています。

テンプレートでは、次のように書けます。

    <table class="table table-bordered">
        <tbody>
        <tr>
            {% for week_name, day, schedules in week_row %}
                <td>
                    <!-- 曜日 -->
                    {{ week_name }}
                    <br>

                    <!-- 日付 -->
                    {% if week_first.month != day.month %}
                        {{ day | date:"m/d" }}
                    {% else %}
                        {{ day.day }}
                    {% endif %}
                    <br>

                    <!-- その日のスケジュール -->
                    {% for s in schedules %}
                        {{ s.start_time }} - {{ s.end_time }}<br>
                        {{ s.summary }}<br>
                        {{ s.description | linebreaks }}
                    {% endfor %}
                </td>
            {% endfor %}
        </tr>
        </tbody>
    </table>

{% for week_name, day, schedules in week_row %}で、一括で取り出せていますね。

1行に収まった

カレンダーを縦にする

さきほどのを応用すれば、縦のカレンダーも簡単に作れます。

    <table class="table table-bordered">
        <tbody>
        {% for week_name, day, schedules in week_row %}
            <tr>
                <td>
                    {% if week_first.month != day.month %}
                        {{ day | date:"m/d" }}
                    {% else %}
                        {{ day.day }}
                    {% endif %}
                    ({{ week_name }})
                </td>

                <td>
                    {% for s in schedules %}
                        {{ s.start_time }} - {{ s.end_time }}<br>
                        {{ s.summary }}<br>
                        {{ s.description | linebreaks }}
                    {% endfor %}
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>

このような見た目になります。 縦にした

Relation Posts

Djangoでカレンダーを作るシリーズ

Djangoで、月間カレンダーや週間カレンダー、それぞれにスケジュール表示機能がついたもの、スケジュール登録フォームがついたもの等、様々なカレンダーを作成していきます。

Python Django Bootstrap4 シリーズ・まとめ

Comment

記事にコメントする

名無し

こんばんわ。拝見させていただきました。ありがとうございます。
質問でscalendarはどこに作ればよいですか?
新しく作っていいのか、既存のファイルに上書きするのか
回答お願いします

返信する

なりと

新しく作って大丈夫です。あくまで、Djangoアプリケーションです。

ただ、現状2つのDjangoアプリケーションがあってわかりにくいですし、パフォーマンス的に少し問題のある書き方もあるので、近日修正します。その際には、`app`アプリケーション1つで済むようにすると思います。

名無し

回答ありがとうございます。大変助かりました。

返信する