naritoブログ

このブログはPython/DjangoとBootstrap4で作成されました
Githubのソースコード

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

約78日前 2018年5月4日12:31
プログラミング関連
Bootstrap4 Django Python calendarモジュール

概要


Djangoで、カレンダーを作るシリーズの1つです。
スケジュール付きの、週間カレンダーを作成していきます。

以下のようなものが作れます。
スケジュール付きの週間カレンダー

スケジュールが表示されると、少し見た目がよくなりますね。

scalendar/views.py


WeekCalendarMixinをベースに、新しいMixinを定義します。
import calendar
from collections import deque
import datetime
from .models import Schedule
...
...
class WeekWithScheduleMixin(WeekCalendarMixin):
    """スケジュール付きの、週間カレンダーを提供するMixin"""
    model = Schedule
    date_field = 'date'
    order_field = 'start_time'

    def get_week_schedules(self, days):
        """それぞれの日のスケジュールを返す"""
        for day in days:
            lookup = {self.date_field: day}
            queryset = self.model.objects.filter(**lookup)
            if self.order_field:
                queryset = queryset.order_by(self.order_field)
            yield queryset

    def get_week_calendar(self):
        calendar_data = super().get_week_calendar()
        schedules = self.get_week_schedules(calendar_data['days'])
        calendar_data['schedule_list'] = schedules
        return calendar_data



WeekCalendarMixinの説明


get_week_calendarメソッドを上書きし、スケジュールを返すようにしています。
例えばその週が[1, 2, 3, 4, 5, 6, 7]となっていれば、schedule_listは[1日の全てのスケジュール, 2日の全てのスケジュール,....]となります。
scalendarで定義したScheduleモデルを使っていますが、それを変更することもできます。その際は、スケジュールの日付を表すフィールドをdate_field属性で指定してください。
また、並び替えをしたい場合は、order_fieldに指定します。


sampleapp/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'
    ),


sampleapp/views.py


WeekWithScheduleMixinを継承すれば、後は週間カレンダーと同様です。
class WeekWithScheduleCalendar(WeekWithScheduleMixin, generic.TemplateView):
    """スケジュール付きの週間カレンダーを表示するビュー"""
    template_name = 'sampleapp/week_with_schedule.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['week'] = self.get_week_calendar()
        return context



week_with_schedule.html


スケジュール付き週間カレンダーのテンプレートです。
{% extends 'sampleapp/base.html' %}
{% block content %}

<a href="{% url 'sampleapp: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 'sampleapp:week_with_schedule' week.next.year week.next.month  week.next.day %}">次週</a>
<table class="table table-bordered">
  <thead>
    <tr>
      {% for w in week.week_names %}
        <th>{{ w }}</th>
      {% endfor %}
    </tr>
  </thead>
  <tbody>
      <tr>
        {% for day in week.days %}
          {% if week.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 day_schedule_list in week.schedule_list %}
          <td>
            {% for s in day_schedule_list %}
                {{ s.start_time }} - {{ s.end_time }}<br>
                {{ s.summary }}<br>
                {{ s.description | linebreaks }}
            {% endfor %}
          </td>
        {% endfor %}
      </tr>
  </tbody>
</table>
{% endblock %}



週間カレンダーと違うのが、以下の部分です。スケジュールを取り出しています。
      <tr>
        {% for day_schedule_list in week.schedule_list %}
          <td>
            {% for s in day_schedule_list %}
                {{ s.start_time }} - {{ s.end_time }}<br>
                {{ s.summary }}<br>
                {{ s.description | linebreaks }}
            {% endfor %}
          </td>
        {% endfor %}
      </tr>


曜日と日付を一緒にする等のケース


現状では曜日と日付が別の行に表示されています。
もしかしたらスケジュール部分も一つの行に収めたいかもしれません。

views.py


コメントのとおりで、一緒に取り出したいものをzip関数内に指定します。
class WeekWithScheduleCalendar(WeekWithScheduleMixin, generic.TemplateView):
    """スケジュール付きの週間カレンダーを表示するビュー"""
    template_name = 'sampleapp/week_with_schedule.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        week = self.get_week_calendar()
        context['week'] = week
        # {% for week_name, day, day_schedule_list in week_row %}
        # とテンプレートで取り出すための記述
        context['week_row'] = zip(
            week['week_names'],
            week['days'],
            week['schedule_list']
        )
        return context


week_with_schedule.html


曜日、日付、スケジュールが毎回のループで同時に取り出せています。
{% extends 'sampleapp/base.html' %}
{% block content %}

<a href="{% url 'sampleapp: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 'sampleapp:week_with_schedule' week.next.year week.next.month  week.next.day %}">次週</a>
<table class="table table-bordered">
  <tbody>
      <tr>
        {% for week_name, day, day_schedule_list 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 day_schedule_list %}
                {{ s.start_time }} - {{ s.end_time }}<br>
                {{ s.summary }}<br>
                {{ s.description | linebreaks }}
            {% endfor %}
          </td>
        {% endfor %}
      </tr>
  </tbody>
</table>
{% endblock %}


見た目


このようになります。
曜日、日付、スケジュールを1行に

カレンダーを縦にする


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

week_with_schedule.html


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

<a href="{% url 'sampleapp: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 'sampleapp:week_with_schedule' week.next.year week.next.month  week.next.day %}">次週</a>
<table class="table table-bordered">
  <tbody>
        {% for week_name, day, day_schedule_list 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 day_schedule_list %}
                {{ s.start_time }} - {{ s.end_time }}<br>
                {{ s.summary }}<br>
                {{ s.description | linebreaks }}
            {% endfor %}
            </td>
          </tr>
        {% endfor %}
  </tbody>
</table>
{% endblock %}


見た目


カレンダーを縦にした
生徒 約60日前 2018年5月23日1:56 返信する
いつも楽しく拝見しています。
ご多忙の中恐れ入りますが、質問させて頂きます。
この記事でご紹介頂いている週間カレンダーを参考にして、スケジュール管理表作成に挑戦しています。
自分で色々と試しているうち、この表を横書き(曜日、日付、内容がそれぞれ「行」内ではなく「列」内に収まっている)にできないかと思って奮闘してみたのですが、template内でforで回すと全て表示が崩れてしまいます。
この考えを実現するためには、どのような方法が適切でしょうか。
方向性だけでもご教示頂ければ嬉しく思います。
何卒宜しくお願い致します。
なりと 約60日前 2018年5月23日9:15
https://torina.top/detail/389/#i7
「曜日と日付を一緒にする等のケース」として追記しましたが、このような感じでどうですか。
生徒 約59日前 2018年5月23日16:23
narito先生有難うございます。
無事、実装できました。
今回ご指導頂いたコード内にある、zip()関数について初めて知りました。
もっと精進致します。
名無し 約33日前 2018年6月19日0:01 返信する
いつも楽しく拝見させていただいております。
現在カレンダーを自分のアプリケーションに実装しようとこちらを参考にさせていただきました。
ご多忙の中とても恐縮なのですがご質問させて頂きます
そこで、自分のアプリでは月カレンダーにスケジュールが表示されるようにしようと、WeekScheduleMixinをそのまま持ってきてweekを全てmonthに変えて実行してみたところ、"expected string or bytes-like object"というTypeErrorが出てしまいました。取りだされた値の型が機械側が期待していたものと違うということはわかったのですがなぜそうなるのか理解ができず...
もしよろしければアイディアなどご教授いただければと思いコメントさせて頂きました。
不束なお願いではありますが後学のためにも何卒よろしくお願いいたします。
なりと 約30日前 2018年6月21日15:38
https://torina.top/detail/478/
こちらにスケジュール付きの月間カレンダーを追加しました。