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

/ PythonDjangoBootstrap4シリーズ・まとめ

16日前に更新

概要

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

動作環境

Python: 3.6以上
Django: 2.0以上

初期設定

アプリケーション名をappとして進めていきます。

settings.py

設定ファイルに、appアプリケーションを定義します。

INSTALLED_APPS = [
    'app.apps.AppConfig',  # カレンダーアプリ
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('app.urls')),
]

models.py

スケジュールを管理するモデルとして、次のようなモデルを予め定義しておきます。

import datetime
from django.db import models
from django.utils import timezone


class Schedule(models.Model):
    """スケジュール"""
    summary = models.CharField('概要', max_length=50)
    description = models.TextField('詳細な説明', blank=True)
    start_time = models.TimeField('開始時間', default=datetime.time(7, 0, 0))
    end_time = models.TimeField('終了時間', default=datetime.time(7, 0, 0))
    date = models.DateField('日付')
    created_at = models.DateTimeField('作成日', default=timezone.now)

    def __str__(self):
        return self.summary

mixins.py

mixins.pyというファイルをappディレクトリ内に作ります。ここに作るクラスは、カレンダー関連ビューを作るための部品です。views.pyに定義しても良いのですが、専用のmixins.pyのようなファイルを作っておくほうが管理しやすいですし、一般的です。

手始めに、次のようなMixinを定義しておきましょう。これは各Mixinの基底クラスになります。

import calendar
from collections import deque


class BaseCalendarMixin:
    """カレンダー関連Mixinの、基底クラス"""
    first_weekday = 0  # 0は月曜から、1は火曜から。6なら日曜日からになります。お望みなら、継承したビューで指定してください。
    week_names = ['月', '火', '水', '木', '金', '土', '日']  # これは、月曜日から書くことを想定します。['Mon', 'Tue'...

    def setup_calendar(self):
        """内部カレンダーの設定処理

        calendar.Calendarクラスの機能を利用するため、インスタンス化します。
        Calendarクラスのmonthdatescalendarメソッドを利用していますが、デフォルトが月曜日からで、
        火曜日から表示したい(first_weekday=1)、といったケースに対応するためのセットアップ処理です。

        """
        self._calendar = calendar.Calendar(self.first_weekday)

    def get_week_names(self):
        """first_weekday(最初に表示される曜日)にあわせて、week_namesをシフトする"""
        week_names = deque(self.week_names)
        week_names.rotate(-self.first_weekday)  # リスト内の要素を右に1つずつ移動...なんてときは、dequeを使うと中々面白いです
        return week_names

内部的に標準ライブラリのcalendarモジュールを利用してカレンダーを作成しています。

デフォルトでは月曜日からカレンダーが始まりますが、火曜日から表示したいというケースも当然あるので、first_weekdayというクラス属性を定義しています。0はデフォルトの月曜日スタート、1なら火曜日...のようになります。

また、['月', '火', '水'...]といった曜日のリストを柔軟に取得できるようにget_week_names()も定義しています。first_weekdayを1にしたならば'['火', '水'...]'といった曜日リストを返してくれますし、week_names属性を上書きすることで、['Mon', 'Tue']のように曜日リストの表記も変更できます。

残りのMixinは、後で作っていきます。

base.html

Bootstrap4のスターターテンプレートを使っていきます。

<!doctype html>
<html lang="ja">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"
          integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">

    <title>カレンダー</title>
</head>
<body>

<div class="container" mt-3>
    {% block content %}{% endblock %}
</div>


<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
        integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
        crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
        integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut"
        crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
        integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k"
        crossorigin="anonymous"></script>
{% block extrajs %}{% endblock %}
</body>
</html>

後で追加のJavaScriptを少し書くので、{% block extrajs %}{% endblock %}を定義しておきます。

月間カレンダー

まず、月間カレンダーを作っていきます。次月、前月といった移動も勿論できます。

Djangoで、月間カレンダー
月間カレンダー

週間カレンダー

次は週間カレンダーです。その週ごとに表示するカレンダーですね。こちらも、前週・次週へ移動できます。

Djangoで、週間カレンダー
週間カレンダー

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

上の週間カレンダーに、スケジュールの表示機能をつけたものを作っていきます。

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

全て1行に収めたり... 週間の1行版

縦の表示にしたものなども作っていきます。 週間カレンダー、縦ver

スケジュール付き月間カレンダー

今度はスケジュール付きの、月間カレンダーを作ります。

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

スケジュール付き週間カレンダー+月間カレンダー+スケジュール登録フォーム

スケジュール付き週間カレンダーに月間カレンダーを付け、更にgeneric.CreateViewも使い、スケジュール登録フォームもつけてみます。

Djangoで、週間・月間カレンダー
Googleカレンダー風

一括作成・更新機能付き月間カレンダー

フォームセットを使い、月間カレンダーを表示しつつ、各日のスケジュール登録・更新を一括で行えるようにしていきます。

Djangoで、一括作成・更新機能付き月間カレンダー
一括作成・更新機能付き月間カレンダー

完成品(Github)

Githubにソースコードがあります。

うごかしかた。

git clone https://github.com/naritotakizawa/django-simple-calendar
pip install django (pipenv install でも)
python manage.py migrate
python manage.py runserver

この記事の関連記事

コメント欄

記事にコメントする

名無し

このスケジュールモデルだと、

start_time = models.TimeField('開始時間', default=datetime.time(7, 0, 0))において

TypeError: descriptor 'time' requires a 'datetime.datetime' object but received a 'int'

というようなエラーが出てしまいます。 何かコードに誤りがあるのではないでしょうか。

コメントに返信する

なりと

moels.pyでのimportを、from datetime import datetime のように書いていませんか。

そのエラーが出るのは、例えば以下のようなコードです。

from datetime import datetime
datetime.time(1, 0, 0)

正しくは以下のコードです。

import datetime
datetime.time(1, 0, 0)