Calendar整形
Calendarは以前にPHPやLaravel Inertia Vue3でも作成したが、DjangoのTemplateではどうやって整形するのか少し悩んだ。
1週間のBlockを作るには、
v-for="n in 7"
とかでLoopさせてDivタグで整形すれば済む話だけど、DjangoのTemplateではシンプルなFor Nextが使えない。
7回のLoopをさせるには、Views.pyで7個の要素を持つListを作成しContextでTemplateのHTMLに渡し、そのリスト内をLoopさせる。
(他にも方法はあるのかもしれないけど)
同様に1月分のBlockを作成するには、対象月の日数分の要素を持つListをViews.pyで作成し、それをTemplateに渡す。
また、その月の1日が何曜日かでカレンダーの開始位置が変わる。なのでその曜日までのブランクのBlockを作る必要がある。
(作りやすいという理由で月曜開始のスタイルにした。JRA方式🏇)
ブランクの個数のListもViews.pyで曜日を調べて作成しTemplateに渡す。
最終日のBlockであることを容易に確認するために、Listとは別にIntegerとして日数もTemplateに渡しておくと楽。
(Blockの罫線は基本的に左と下にのみ設定する。(重複すると濃くなったりするため)日曜日と最後のBlockは右も罫線を設定するから)
HTMLレベルだけど、カレンダーの各BLock内で縦位置が真ん中になったり高さの制御が難しかった。
align-items: flex-start; display: flex; を指定したdivで囲む必要がある。
カレンダーの各BLock内で改行されない。
white-space: normal; overflow-wrap: break-word; flex-wrap: wrap;を指定したdivで囲む必要がある。
というようなことで、意外と手間取った。 😮💨
レスポンシブなスマホ対応。一応 😞
Calendar登録
予定の登録先はシンプルにSQLiteを使用。
Modelにフィールド追加
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
verbose_name='Author'
)
登録はCreateViewを用いて簡単に行うが、投稿者はLoginUserとするので、auth_userとリレーションしたテーブルを対象にする。
そのため、登録時に、authorフィールドにLoginUserIDを登録出来る様に、form_validメソッドをオーバーライドする。
class TodoCreateView(LoginRequiredMixin,CreateView):
model = Todo
template_name = 'todo/todo_create.html'
form_class=TodoForm
success_url=reverse_lazy('todo:todolist')
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
List表示
外部テーブルにあるauthorの名前を表示するには、なんの苦労もなく{{d.author.username}}で良かった。
( dはViewから渡されたオブジェクト )
Calendar表示
登録した予定は予定日のカレンダーにタイトルを表示する。
やり方がわからずヘンテコながらなんとかできたメモ。
views.pyでカレンダーを表示する年月でデータを抽出し、辞書としてTemplateに渡す。
# 年月でフィルター(予定日順)
results = tmodel.objects.filter(
scddate__year=int(str_y),
scddate__month=int(str_m)
).order_by('scddate')
# 日付をキーとする辞書を作成する。
# 日付順なので、日付が変わるとキーを変える。svd
svd=0
# 日付をKeyとしてタイトルのリストをValueとする辞書
dicd={}
# IDをKeyタイトルをValueとする辞書
dicscd={}
# データのない年月は対象にしない(any() は要素が一つでも真なら True、すべての要素が偽であるときFalse)
if any(results):
svd=0
for result in results:
if svd != result.scddate.day:
if svd > 0 :
# 日付が変わって初回でなければリストを辞書に吐き出す
dicd[svd]=lstd
# 日付Keyの更新
svd=result.scddate.day
# 日付Keyが変わったのでリストの初期化
lstd=[]
# リストにタイトルを追加
lstd.append(str(result.id))
else:
# 日付keyが変わらない場合はそのままリストに追加
lstd.append(str(result.id))
#Loop毎にID、タイトルの辞書に追加
dicscd[result.id]=result.title
#Loop終了時にもリストを辞書に吐き出す
dicd[svd]=lstd
Templateで辞書を読むためにフィルターを実装する。
@register.filter(name='getdic')
def get_item(dictionary, key):
return dictionary.get(key,"")
@register.filter(name='getdic_int')
def get_item(dictionary, key):
return dictionary.get(int(key),"")
Templateで、カレンダーをレンダリングするときに日付をキーとしてタイトルIDのリストを取得。
タイトルIDのリストをLoopしてIDをキーに順次タイトルを取得し、IDを利用して予定詳細へリンクを貼る。
{% if dicd|getdic:d != '' %}
{% for x in dicd|getdic:d %}
<div><a href="{% url 'todo:tododetail' x %}">{{dicscd|getdic_int:x}}</a></div>
{% endfor %}
{% endif %}
Responsive
CSSで画面が狭い時の対応をするだけなんだけど、カレンダーはそれだと複雑になってしまう。
なので、PCでは表示するけどスマホでは表示しないBlockとその逆のBlockを用意して、その中でそれぞれ全く別に整形。
スマホ版はカレンダー形式ではなく、日付順にBlockが縦に並ぶようにするだけなので簡単。
しかしながら、個別に曜日を用意していないので曜日を表示できない。
やむなく、月の開始曜日の番号(月曜日=1)と日付を加えたものから1を引いたものを7で除した余りから曜日を取得するフィルターを用意した。
@register.filter(name='get_week')
def get_item4(value,arg):
r=(value + arg -1) % 7
match r:
case 1:
wname="月"
case 2:
wname="火"
case 3:
wname="水"
case 4:
wname="木"
case 5:
wname="金"
case 6:
wname="土"
case 7:
wname="日"
case _:
wname="日"
return wname
HTMLソース
DjangoのTemplateでは {%if %} や {%for %}などの箇所が改行されて空行だらけになってしまう。
共通枠のbase.htmlのBodyを {% spaceless %} {% endspaceless %}で囲むと全ページの改行が省略された。
… 省略されすぎて 何もわからなくなった。😑