Djangoでカレンダー その3


先日、CSSを使ってカレンダー(1日分)を作りましたが、それをDjangoのカレンダーアプリに
組み込んでみました。

python manage.py runserver

で開発用のサーバを起動し、ブラウザでカレンダーのURLを入力すると、
以下のような感じで表示されました。


ビュー関数はこんな感じです。

def daily(request, year, month, day):
    '''
    1日単位のカレンダーを表示するビュー。

    year:

    month:

    day:

    '''
    # 日付のデータを検索
    taskList = Task.objects.filter(task_date__year=year,
                                   task_date__month=month,
                                   task_date__day=day)
    taskList = taskList.order_by('time_start')

    # ユーザ名(ニックネーム)をキーとした辞書にタスクを振り分ける
    userDicts = {}
    for task in taskList:
        userTasks = userDicts.get(task.user_setting.nickname)
        if userTasks == None:
            userTasks = []
            userDicts[task.user_setting.nickname] = userTasks
        userTasks.append(task)

    d = {'year': year,
         'month': month,
         'day': day,
         'task_list': userDicts}

    return render_to_response('daily.html', d)


テンプレートはこんな感じで書いてあります。

{% extends "base.html" %}
{% load calendar_templates %}

{% block content %}
<div id="container">
  <div id="header1">{{year}} / {{month}} / {{day}}</div>

  {# 00:00 〜 23:00の時刻を表示 #}
  <div id="timeframe">	
	<div class="timelabel">Time</div>
	<div class="timeline">00:00</div>
	<div class="timeline">01:00</div>
	<div class="timeline">02:00</div>
	<div class="timeline">03:00</div>
	<div class="timeline">04:00</div>
	<div class="timeline">05:00</div>
	<div class="timeline">06:00</div>
	<div class="timeline">07:00</div>
	<div class="timeline">08:00</div>
	<div class="timeline">09:00</div>
	<div class="timeline">10:00</div>
	<div class="timeline">11:00</div>
	<div class="timeline">12:00</div>
	<div class="timeline">13:00</div>
	<div class="timeline">14:00</div>
	<div class="timeline">15:00</div>
	<div class="timeline">16:00</div>
	<div class="timeline">17:00</div>
	<div class="timeline">18:00</div>
	<div class="timeline">19:00</div>
	<div class="timeline">20:00</div>
	<div class="timeline">21:00</div>
	<div class="timeline">22:00</div>
	<div class="timeline">23:00</div>
  </div>

  {# ユーザごとのタスクリストを取得して処理 #}
  {% for user, tasks in task_list.items %}
    {# ユーザ数をもとに、スタイルシートの列幅を指定 #}
    <div class="userframe width_user_x{{task_list.keys|length}}">

    {# ユーザ名(ニックネーム)を表示 #}
    <div class="userlabel">{{user}}</div>

    {# 変数task_rowsに時系列のタスク情報を取得する #}
    {% daily_task_rows tasks as task_rows %}

    {# タスク情報の内容をもとに、時刻ごとのタスクを表示する #}
    {% for hours, tsk in task_rows %}
      {% if tsk %}
        {# 時刻にタスクが割り当てられている場合 #}
        <div class="taskfield timeunit{{hours|cut:"."}} active">
          {{tsk}}({{tsk.time_start|time:"H:i"}} 〜 {{tsk.time_end|time:"H:i"}})
        </div>
      {% else %}
        {# 時刻の場合にタスクが割り当てられていない場合 #}
        <div class="taskfield timeunit{{hours|cut:"."}} normal"></div>
      {% endif %}
    {% endfor %}
  </div>
  {% endfor %}

  <div class="clear"><hr/></div>
</div>
{% endblock content %}


上記のこの部分、

    {# ユーザ数をもとに、スタイルシートの列幅を指定 #}
    <div class="userframe width_user_x{{task_list.keys|length}}">

ユーザ数が増えると、1ユーザ分の幅を狭くする必要があると思い、
ユーザ数ごとの幅をCSSで定義しておいてそれを切り替えるようにしました。。


CSSには以下のように書いてあります。

.width_user_x1 { width:90%; }
.width_user_x2 { width:45%; }
.width_user_x3 { width:30%; }
.width_user_x4 { width:22.5%; }
.width_user_x5 { width:18%; }

本来はこんな書き方はしないのかも知れませんが、この方法しか思いつきませんでした。。(笑)


また、上記のこの部分、

    {# 変数task_rowsに時系列のタスク情報を取得する #}
    {% daily_task_rows tasks as task_rows %}

daily_task_rowsというカスタムタグを作って呼び出しています。


daily_task_rowsはどんなものかというと、こんな感じです。
(もっと簡潔にかければよかったんですが。。)

@register.tag(name='daily_task_rows')
def do_daily_task_rows(parser, token):
    try:
        tag, args = token.contents.split(None, 1)
    except ValueError:
        tag = token.contents.split()[0]
        msg = '%r tag requires exactly two arguments' % tag
        raise template.TemplateSyntaxError, msg

    match = re.search(r'(.*?) as (\w+)', args)
    if not match:
        msg = '%r tag had invalid arguments' % tag
        raise template.TemplateSyntaxError, msg

    tasks, varName = match.groups()
    return DailyTaskRowsNode(tasks, varName)

class DailyTaskRowsNode(template.Node):
    def __init__(self, tasks, varName):
        self.tasks = tasks
        self.varName = varName
    
    def render(self, context):
        '''
        00:00〜23:00の各時刻へのタスクの割り当て状態のリストを作成し、
        self.varNameで指定された変数へ設定する。

        割り当て状態のリストはタプルのリストとなっており、それぞれのタプルの情報
    は以下のようになっている。

        (タスクの継続時間, タスクのインスタンス)

     継続時間            :最小値は0.5(30分)〜。
     タスクのインスタンス:タスクが割り当てられていない場合にはNone
        '''
        rows = []
        
        tasks = resolve_variable(self.tasks, context)
        if not tasks:
            context[self.varName] = rows
            return ''

        # 最後のタスクまでの処理を行う
        prevEd = 0
        for t in tasks:
            # タスクの開始、終了時刻を取得(単位:秒)
            taskSt, taskEd = t.get_range_as_seconds()
            if taskSt < prevEd:
                continue

            # タスク開始時刻の端数(30秒未満)を切り捨てる
            odd = taskSt % 1800
            if odd:
                taskSt -= odd

            # タスク終了時刻の端数(30秒未満)を切り上げる
            odd = taskEd % 1800
            if odd:
                taskEd += 1800 - odd

            # 直前のタスクの終了時刻から、現在処理中のタスク開始時刻
            # までの時刻を求め、30分を1単位として、いくつぶんになるか
            # 計算。
            emptySec = taskSt - prevEd
            halfHours = emptySec / 1800

            # 直前のタスクの終了時刻から、現在処理中のタスク開始時刻
            # までの時刻を1時間単に変換する。
            hours = halfHours / 2

            # 1時間単位で空の行を設定する
            rows += [('1.0', None) for h in range(hours)]

            # 30分の端数が出た場合のケア
            if halfHours % 2:
                rows.append(('0.5', None))

            # タスクの継続時間(秒数)を求める
            duration = taskEd - taskSt

            # タスクの継続時間が30分を1として、いくつぶんになるか計算。
            # 30分未満の場合は切り上げする。
            halfHours = duration / 1800
            rows.append((float(halfHours/2.0), t))
            prevEd = taskEd

        # 最後のタスクが**:30で終わっていた場合、
        # 30分の行を補う。
        odd = prevEd % 3600
        if odd:
            rows.append(('0.5', None))
            prevEd += 3600 - odd

        # 最後のタスクから24:00までの空白の時間の処理
        hours = 24 - (prevEd / 3600)
        rows += [('1.0', None) for h in range(hours)]
        
        context[self.varName] = rows
        return ''

少し長くなりました。。それにソース中のコメント文章がぐちゃぐちゃ。。
次回は1ヶ月単位のカレンダーの予定です。