diff options
-rw-r--r-- | django_skaschool/urls.py | 2 | ||||
-rw-r--r-- | page/templates/page/program.html | 20 | ||||
-rw-r--r-- | page/urls.py | 4 | ||||
-rw-r--r-- | schedule/migrations/0001_initial.py | 76 | ||||
-rw-r--r-- | schedule/migrations/__init__.py | 0 | ||||
-rw-r--r-- | schedule/models.py | 4 | ||||
-rw-r--r-- | schedule/static/css/schedule.css | 41 | ||||
-rw-r--r-- | schedule/static/js/schedule.js | 58 | ||||
-rw-r--r-- | schedule/templates/schedule/schedule.html | 93 | ||||
-rw-r--r-- | schedule/urls.py | 18 | ||||
-rw-r--r-- | schedule/views.py | 51 | ||||
-rw-r--r-- | templates/navbar.html | 2 | ||||
-rw-r--r-- | tools/templatetags/dict_utils.py | 19 |
13 files changed, 362 insertions, 26 deletions
diff --git a/django_skaschool/urls.py b/django_skaschool/urls.py index 60a298a..7b9ac2f 100644 --- a/django_skaschool/urls.py +++ b/django_skaschool/urls.py @@ -20,6 +20,8 @@ urlpatterns = patterns('', url(r'^$', IndexView.as_view(), name='index'), # app 'page' url(r'^page/', include('page.urls')), + # app 'schedule' + url(r'^schedule/', include('schedule.urls')), # app 'notice' url(r'^notice/', include('notice.urls')), # app 'archive' diff --git a/page/templates/page/program.html b/page/templates/page/program.html deleted file mode 100644 index c0b68d7..0000000 --- a/page/templates/page/program.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'base.html' %} -{% load staticfiles %} -{% load url from future %} -{% load bootstrap3 %} - -{# program template #} - -{% block pagetitle %}日程{% endblock %} - -{% block content %} - <div class="container"> - <h2>2014 SKA 暑期学校日程安排</h2> - <br> - <div class="alert alert-info"> - 日程安排尚未确定,请等待后续通知和更新…… - </div> - </div> -{% endblock %} - -{# vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: #} diff --git a/page/urls.py b/page/urls.py index 79ae696..0f72034 100644 --- a/page/urls.py +++ b/page/urls.py @@ -14,10 +14,6 @@ urlpatterns = patterns('', url(r'^introduction/$', TemplateView.as_view(template_name='page/introduction.html'), name='introduction'), - # program page - url(r'^program/$', - TemplateView.as_view(template_name='page/program.html'), - name='program'), # committee page url(r'^committee/$', TemplateView.as_view(template_name='page/committee.html'), diff --git a/schedule/migrations/0001_initial.py b/schedule/migrations/0001_initial.py new file mode 100644 index 0000000..3e14bd9 --- /dev/null +++ b/schedule/migrations/0001_initial.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Event' + db.create_table(u'schedule_event', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('person', self.gf('django.db.models.fields.CharField')(max_length=50)), + ('date', self.gf('django.db.models.fields.DateField')()), + ('time_start', self.gf('django.db.models.fields.TimeField')()), + ('time_end', self.gf('django.db.models.fields.TimeField')()), + ('contents', self.gf('django.db.models.fields.TextField')(blank=True)), + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, auto_now_add=True, blank=True)), + )) + db.send_create_signal(u'schedule', ['Event']) + + # Adding model 'EventAttachment' + db.create_table(u'schedule_eventattachment', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + ('attachment', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), + ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), + ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, auto_now_add=True, blank=True)), + )) + db.send_create_signal(u'schedule', ['EventAttachment']) + + + def backwards(self, orm): + # Deleting model 'Event' + db.delete_table(u'schedule_event') + + # Deleting model 'EventAttachment' + db.delete_table(u'schedule_eventattachment') + + + models = { + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'schedule.event': { + 'Meta': {'ordering': "['date', 'time_start']", 'object_name': 'Event'}, + 'contents': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'date': ('django.db.models.fields.DateField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'person': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'time_end': ('django.db.models.fields.TimeField', [], {}), + 'time_start': ('django.db.models.fields.TimeField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}) + }, + u'schedule.eventattachment': { + 'Meta': {'object_name': 'EventAttachment'}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['schedule']
\ No newline at end of file diff --git a/schedule/migrations/__init__.py b/schedule/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/schedule/migrations/__init__.py diff --git a/schedule/models.py b/schedule/models.py index 37ed3f4..aee7018 100644 --- a/schedule/models.py +++ b/schedule/models.py @@ -24,6 +24,8 @@ class Event(models.Model): time_end = models.TimeField(_("End time")) contents = models.TextField(_("Contents"), blank=True, help_text=_("Markdown syntax supported")) + updated_at = models.DateTimeField(_("Updated time"), + auto_now=True, auto_now_add=True) attachments = generic.GenericRelation('EventAttachment') class Meta: @@ -49,6 +51,8 @@ class EventAttachment(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey("content_type", "object_id") + updated_at = models.DateTimeField(_("Updated time"), + auto_now=True, auto_now_add=True) class Meta: verbose_name = _('Event attachment') diff --git a/schedule/static/css/schedule.css b/schedule/static/css/schedule.css new file mode 100644 index 0000000..82cdccd --- /dev/null +++ b/schedule/static/css/schedule.css @@ -0,0 +1,41 @@ +/* + * schedule.css + * used with 'schedule.html' page + * + * 2014/06/24 + */ + +/* bootstrap panel */ +.panel .panel-heading { + font-size: 180%; + font-weight: bold; +} + +/* event-contents */ +.event-contents p { + margin: 0px 0px 5px; +} +.event-contents .panel-heading { + font-size: 100%; + font-weight: normal; + padding: 5px 5px; + cursor: pointer; +} +.event-contents .panel-body { + padding: 5px 5px; +} + +/* event-attachments */ +.event-attachments p { + margin: 0px 0px 5px; +} +.event-attachments .panel-heading { + font-size: 100%; + font-weight: normal; + padding: 5px 5px; + cursor: pointer; +} +.event-attachments .panel-body { + padding: 5px 5px; +} + diff --git a/schedule/static/js/schedule.js b/schedule/static/js/schedule.js new file mode 100644 index 0000000..3714b19 --- /dev/null +++ b/schedule/static/js/schedule.js @@ -0,0 +1,58 @@ +/* + * schedule.js + * used with 'schedule.html' page + * + * 2014/06/24 + */ + +/* animated slidedown and slideup div */ +$(document).ready(function() { + /* hide() all .event-contents and .event-attchments body */ + $(".event-contents .panel-body").hide().removeClass('shown').addClass('hidden'); + $(".event-attachments .panel-body").hide().removeClass('shown').addClass('hidden'); + + /* click .event-contents heading to toggle its body contents */ + $(".event-contents .panel-heading").on("click", function(e) { + e.stopPropagation(); + ec_body = $(this).siblings(".panel-body"); + if ($(ec_body).hasClass('hidden')) { + $(ec_body).show().removeClass('hidden').addClass('shown'); + // update heading icon and text + span_icon = $(this).children("span.glyphicon"); + $(span_icon).removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-up'); + span_action = $(this).children("span.action"); + $(span_action).html('隐藏'); + } + else { + $(ec_body).hide().removeClass('shown').addClass('hidden'); + // update heading icon and text + span_icon = $(this).children("span.glyphicon"); + $(span_icon).removeClass('glyphicon-chevron-up').addClass('glyphicon-chevron-down'); + span_action = $(this).children("span.action"); + $(span_action).html('显示'); + } + }); + + /* click .event-attachments heading to toggle its body attachments */ + $(".event-attachments .panel-heading").on("click", function(e) { + e.stopPropagation(); + ec_body = $(this).siblings(".panel-body"); + if ($(ec_body).hasClass('hidden')) { + $(ec_body).show().removeClass('hidden').addClass('shown'); + // update heading icon and text + span_icon = $(this).children("span.glyphicon"); + $(span_icon).removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-up'); + span_action = $(this).children("span.action"); + $(span_action).html('隐藏'); + } + else { + $(ec_body).hide().removeClass('shown').addClass('hidden'); + // update heading icon and text + span_icon = $(this).children("span.glyphicon"); + $(span_icon).removeClass('glyphicon-chevron-up').addClass('glyphicon-chevron-down'); + span_action = $(this).children("span.action"); + $(span_action).html('显示'); + } + }); +}); + diff --git a/schedule/templates/schedule/schedule.html b/schedule/templates/schedule/schedule.html new file mode 100644 index 0000000..ee9b9c4 --- /dev/null +++ b/schedule/templates/schedule/schedule.html @@ -0,0 +1,93 @@ +{% extends 'base.html' %} +{% load staticfiles %} +{% load url from future %} +{% load bootstrap3 %} +{% load dict_utils %} +{% load my_markdown %} + +{# program template #} + +{% block pagetitle %}日程{% endblock %} + +{% block css_extra %} + <link href="{% static 'css/schedule.css' %}" rel="stylesheet"> +{% endblock %} + +{% block content %} + <div class="container"> + <h2>2014 SKA 暑期学校日程安排</h2> + <br> + <div class="alert alert-info"> + <strong>会场地址</strong>:学术活动中心(上海交通大学闵行校区) + </div> + {% if event_empty %} + <div class="alert alert-warning"> + 日程安排尚未确定,请等待我们的后续通知…… + </div> + {% endif %} + + {% for day in day_all %} + <div class="panel panel-info"> + <div class="panel-heading"> + {{ day }} + </div> + <div class="panel-body"> + {% with event_day=event_dict|dictkey:day %} + {% for event in event_day %} + <div class="row event"> + <div class="col-md-2 event-time"> + <p><strong>{{ event.time_start|time:'H:i' }} – {{ event.time_end|time:'H:i' }}</strong></p> + </div> <!-- event-time --> + <div class="col-md-10 event-body"> + <p> + <strong>{{ event.title }}</strong><br> + <em>{{ event.person }}</em> + </p> + {% if event.contents %} + <div class="panel panel-default event-contents"> + <div class="panel-heading"> + <span class="glyphicon glyphicon-chevron-down"></span> + <span class="action">显示</span>详情 + </div> + <div class="panel-body"> + {{ event.contents|my_markdown }} + </div> + </div> + {% endif %} + {% if event.attachments.all %} + <div class="panel panel-default event-attachments"> + <div class="panel-heading"> + <span class="glyphicon glyphicon-chevron-down"></span> + <span class="action">显示</span>附件 + </div> + <div class="panel-body"> + {% for attachment in event.attachments.all %} + <p><a href="{{ attachment.attachment.url }}">{{ attachment.title }}</a> – {{ attachment.description }}</p> + {% endfor %} {# attachment #} + </div> <!-- panel-body --> + </div> <!-- event-attachments --> + {% endif %} {# attachments #} + </div> <!-- event-body --> + </div> <!-- event --> + {% endfor %} {# event #} + {% endwith %} {# event_day #} + </div> <!-- panel-body --> + </div> <!-- panel --> + {% endfor %} {# day #} + + <br> + <p>注:<br> + 1. 我们会根据实际情况及时更新日程安排。<br> + 2. 上课讲义及其他相关资料会及时上传和更新。 + </p> + <br> + <p>Last updated: {{ updated_at }}</p> + <br> + </div> +{% endblock %} + +{% block js_extra %} + <script src="{% static 'js/schedule.js' %}" type="text/javascript"></script> +{% endblock %} + +{# vim: set ts=8 sw=2 tw=0 fenc=utf-8 ft=htmldjango.html: #} diff --git a/schedule/urls.py b/schedule/urls.py new file mode 100644 index 0000000..b7bd185 --- /dev/null +++ b/schedule/urls.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# +# urls.py for app 'page' +# + +from django.conf.urls import patterns, include, url +from django.views.generic.base import TemplateView + +from schedule.views import ScheduleView + + +urlpatterns = patterns('', + # program page + url(r'^all/$', + ScheduleView.as_view(), + name='schedule_all'), +) + diff --git a/schedule/views.py b/schedule/views.py index 91ea44a..ad4bbc2 100644 --- a/schedule/views.py +++ b/schedule/views.py @@ -1,3 +1,52 @@ +# -*- coding: utf-8 -*- + from django.shortcuts import render +from django.views.generic.base import TemplateView + +from schedule.models import Event, EventAttachment + +import re +import datetime + + +class ScheduleView(TemplateView): + """ + class-based view to show events with their attachments + generate the schedule page + """ + template_name = 'schedule/schedule.html' + + def get_context_data(self, **kwargs): + context = super(type(self), self).get_context_data(**kwargs) + event_all_qs = Event.objects.all().order_by('date') + if event_all_qs: + event_empty = False + # get all the dates + day_all = ['%s'%e.date.isoformat() for e in event_all_qs] + # deduplicate while preserver order + tmplist = [] + for e in day_all: + if e not in tmplist: + tmplist.append(e) + day_all[:] = tmplist + # make a dict to group events by date + event_dict = {} + for d in day_all: + day = datetime.date(*map(int, re.split('[^\d]', d))) + event_day = event_all_qs.filter(date=day).order_by('time_start') + event_dict[d] = event_day + # get time of last update + event_last_updated = event_all_qs.order_by('-updated_at')[0] + updated_at = event_last_updated.updated_at.isoformat() + else: + event_empty = True + event_dict = {} + day_all = [] + updated_at = 'N/A' + # + context['event_empty'] = event_empty + context['event_dict'] = event_dict + context['day_all'] = day_all + context['updated_at'] = updated_at + return context -# Create your views here. diff --git a/templates/navbar.html b/templates/navbar.html index fe0fffc..9546f71 100644 --- a/templates/navbar.html +++ b/templates/navbar.html @@ -18,7 +18,7 @@ <ul class="nav navbar-nav"> <li><a href="{% url 'introduction' %}">介绍</a></li> <li><a href="{% url 'list_notice' %}">通知</a></li> - <li><a href="{% url 'program' %}">日程</a></li> + <li><a href="{% url 'schedule_all' %}">日程</a></li> <li><a href="{% url 'committee' %}">组委会</a></li> <li><a href="{% url 'sponsor' %}">赞助方</a></li> <li><a href="{% url 'archive_all' %}">资料下载</a></li> diff --git a/tools/templatetags/dict_utils.py b/tools/templatetags/dict_utils.py new file mode 100644 index 0000000..5a81c79 --- /dev/null +++ b/tools/templatetags/dict_utils.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# +# utilities to deal with dictionary in template +# + +from django import template + + +register = template.Library() + +@register.filter +def dictkey(d, key): + try: + value = d[key] + except KeyError: + from django.conf import settings + value = settings.TEMPLATE_STRING_IF_INVALID + return value + |