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

2018-10-07 / PythonDjangoBootstrap4シリーズ・まとめ

概要

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

動作環境

Python: 3.6以上
Django: 2.0以上

ソースコードダウンロード

先にソースコードをダウンロードしたい人は、Githubにソースコードがあるのでクローンしてください。

うごかしかた。

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

初期設定

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

python manage.py startapp 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にも、appを読み込ませるようにします。この辺はDjangoアプリケーションを作成したら毎回やる操作ですね。

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

概要、説明、スケジュールの開始時間・終了時間・日付、そしてスケジュールの作成日となるフィールドがあります。

ベーステンプレートファイル作成

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 %}を定義しておきます。

ミックスインクラスの作成

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は、後で作っていきます。

月間カレンダー

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

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

週間カレンダー

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

この記事の関連記事

Djangoで、月間カレンダー

2018-10-07 / PythonDjangoBootstrap4

- Djangoでカレンダーを作るシリーズの1つです。月間カレンダーを作ります。こういった月間カレンダーを埋め込みたい場合はよくあるので、便利です。

Djangoで週間カレンダー

2018-10-07 / PythonDjangoBootstrap4

- Djangoでカレンダーを作るシリーズの1つです。週間カレンダーを作成していきます。こちらもよく使う機能で、便利です。

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

2018-10-07 / PythonDjangoBootstrap4

- Djangoで、カレンダーを作るシリーズの1つです。スケジュール付きの週間カレンダーを作成していきます。以前の週間カレンダーを拡張することで簡単に作成でき、スケジュール表示部分も自由にカスタマイズできます。

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

2018-10-07 / PythonDjangoBootstrap4

- Djangoで、カレンダーを作るシリーズの1つです。スケジュール付きの月間カレンダーを作成していきます。以前の月間カレンダーを拡張することで簡単に作成でき、スケジュール表示部分も自由にカスタマイズできます。

Djangoで週間・月間カレンダー

2018-10-07 / PythonDjangoBootstrap4

- Djangoでカレンダーを作るシリーズの1つです。Googleカレンダーのように、週間カレンダー、月間カレンダーが一緒に表示され、スケジュール登録もできる総合的なカレンダーを作成します。

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

2019-01-13 / PythonDjangoBootstrap4

- Djangoで、カレンダーを作るシリーズの1つです。 一括作成・更新機能付きの月間カレンダーを作成していきます。

コメント欄

記事にコメントする

名無し

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

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)
名無し

カレンダー作成の記事、大変参考にさせていただきました。 ありがとうございます。

現在、このブログを参考にスケジュール登録できるカレンダーを 作成しました。 そして、月間カレンダーのスケジュール内容をリンクにして クリックすると概要や日付の変更をできるようにしようと 考えているのですが、できません。

目的は、日付や概要の変更を簡単におこないたいということ なのですが、 良い方法やコードを記載していただけると助かります。

ご検討のほどよろしくお願い致します。

コメントに返信する

なりと

スケジュールを更新するためのビューやテンプレートファイルを作成することになります。

まず、スケジュール付き月間カレンダーのスケジュール内容の部分あたりを更新用のリンクにします。他のカレンダーでも、同じような感じでリンクを作成できます。

{% for schedule in schedules %}
    <p><a href="{% url 'app:schedule_change' schedule.pk %}">{{ schedule.summary }}</a></p>
{% endfor %}

urls.pyに、更新用処理のURL定義を追加します。

path('schedule/change/<int:pk>/', views.ScheduleChange.as_view(), name='schedule_change'),

views.pyに、更新用ビューを定義します。

class ScheduleChange(generic.UpdateView):
    model = Schedule
    form_class = BS4ScheduleForm
    success_url = '/'

最後に、更新用ページのテンプレートファイルを作成します。schedule_form.htmlとして作成します。

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

{% block content %}
    <form action="" method="POST">
        {{ form.as_p }}
        {% csrf_token %}
        <button type="submit" class="btn btn-primary">送信</button>
    </form>
{% endblock %}


{% block extrajs %}
    <link rel="stylesheet" type="text/css"
          href="https://cdnjs.cloudflare.com/ajax/libs/timedropper/1.0/timedropper.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/timedropper/1.0/timedropper.min.js"></script>
    <script>
        $(function () {
            // timedropper
            $("#id_start_time").timeDropper({
                format: "H:mm",
                setCurrentTime: false,
            });
            $("#id_end_time").timeDropper({
                format: "H:mm",
                setCurrentTime: false,
            });
        });
    </script>
{% endblock %}

日付も変更する必要がある場合は、BS4ScheduleFormと似たようなフォームで、日付も編集できるフォームを定義し、それを利用してください。次のようなフォームです。

class BS4ScheduleFormWithDate(forms.ModelForm):

    class Meta:
        model = Schedule
        fields = ('summary', 'description', 'start_time', 'end_time', 'date')
        widgets = {
            'summary': forms.TextInput(attrs={
                'class': 'form-control',
            }),
            'description': forms.Textarea(attrs={
                'class': 'form-control',
            }),
            'start_time': forms.TextInput(attrs={
                'class': 'form-control',
            }),
            'end_time': forms.TextInput(attrs={
                'class': 'form-control',
            }),
            'date': forms.TextInput(attrs={
                'class': 'form-control',
            }),
        }

    def clean_end_time(self):
        start_time = self.cleaned_data['start_time']
        end_time = self.cleaned_data['end_time']
        if end_time <= start_time:
            raise forms.ValidationError(
                '終了時間は、開始時間よりも後にしてください'
            )
        return end_time

名無し

ご丁寧な解説とコードの記載ありがとうございます。 おかげさまで完成させることができました。

なりと様のブログはよく参考にさせて頂いているので、 これからもどうぞよろしくお願い致します。