def ical(request, year, semester_type, slug, ical_type=None): resources = [_(u'lectures'), _(u'exams')] if ical_type and ical_type not in resources: raise http.Http404 elif ical_type: resources = [ical_type] # TODO(adamcik): Add a last modified header and return 304s? title = urlresolvers.reverse('schedule', args=[year, semester_type, slug]) hostname = (settings.TIMETABLE_HOSTNAME or request.META.get('HTTP_HOST', socket.getfqdn())) semester = Semester(year=year, type=semester_type) cal = vobject.iCalendar() cal.add('method').value = 'PUBLISH' # IE/Outlook needs this # TODO(adamcik): use same logic as in common.templatetags.title if slug.lower().endswith('s'): description = _( u"%(slug)s' %(semester)s %(year)s schedule for %(resources)s") else: description = _( u"%(slug)s's %(semester)s %(year)s schedule for %(resources)s") cal.add('X-WR-CALNAME').value = title.strip('/') cal.add('X-WR-CALDESC').value = description % { 'slug': slug, 'semester': semester.get_type_display(), 'year': semester.year, 'resources': ', '.join(resources), } if _('lectures') in resources: lectures = Lecture.objects.get_lectures(year, semester.type, slug) add_lectutures(lectures, semester.year, cal, hostname) if _('exams') in resources: exams = Exam.objects.get_exams(year, semester.type, slug) add_exams(exams, cal, hostname) icalstream = cal.serialize() filename = '%s.ics' % '-'.join([str(semester.year), semester.type, slug] + resources) response = http.HttpResponse(icalstream, content_type='text/calendar') response['Content-Type'] = 'text/calendar; charset=utf-8' response['Filename'] = filename # IE needs this response['Content-Disposition'] = 'attachment; filename=%s' % filename response['X-Robots-Tag'] = 'noindex, nofollow' # TODO(adamcik): add expires header that reduces load on old semesters return response
def select_groups(request, year, semester_type, slug): '''Form handler for selecting groups to use in schedule''' courses = Course.objects.get_courses(year, semester_type, slug) course_groups = Course.get_groups(year, semester_type, [c.id for c in courses]) if request.method == 'POST': with transaction.atomic(): for c in courses: try: groups = course_groups[c.id] except KeyError: # Skip courses without groups continue group_form = forms.GroupForm(groups, request.POST, prefix=c.id) if group_form.is_valid(): subscription = Subscription.objects.get_subscriptions( year, semester_type, slug).get(course=c) subscription.groups = group_form.cleaned_data['groups'] return shortcuts.redirect('schedule-advanced', year, Semester.localize(semester_type), slug) color_map = utils.ColorMap(hex=True) subscription_groups = Subscription.get_groups(year, semester_type, slug) all_subscripted_groups = set() for groups in subscription_groups.values(): for group in groups: all_subscripted_groups.add(group) for c in courses: color_map[c.id] subscription_id = c.subscription_set.get(student__slug=slug).pk try: groups = course_groups[c.id] except KeyError: # Skip courses without groups continue initial_groups = subscription_groups.get(subscription_id, all_subscripted_groups) c.group_form = forms.GroupForm(groups, prefix=c.id, initial={'groups': initial_groups}) return shortcuts.render( request, 'select_groups.html', { 'semester': Semester(year=year, type=semester_type), 'slug': slug, 'courses': courses, 'color_map': color_map, })
def ical(request, year, semester_type, slug, ical_type=None): resources = [_(u'lectures'), _(u'exams')] if ical_type and ical_type not in resources: raise http.Http404 elif ical_type: resources = [ical_type] title = urlresolvers.reverse('schedule', args=[year, semester_type, slug]) hostname = (settings.TIMETABLE_HOSTNAME or request.META.get('HTTP_HOST', socket.getfqdn())) semester = Semester(year=year, type=semester_type) cal = vobject.iCalendar() cal.add('method').value = 'PUBLISH' # IE/Outlook needs this # TODO(adamcik): use same logic as in common.templatetags.title if slug.lower().endswith('s'): description = _(u"%(slug)s' %(semester)s %(year)s schedule for %(resources)s") else: description = _(u"%(slug)s's %(semester)s %(year)s schedule for %(resources)s") cal.add('X-WR-CALNAME').value = title.strip('/') cal.add('X-WR-CALDESC').value = description % { 'slug': slug, 'semester': semester.get_type_display(), 'year': semester.year, 'resources': ', '.join(resources), } if _('lectures') in resources: lectures = Lecture.objects.get_lectures(year, semester.type, slug) add_lectutures(lectures, semester.year, cal, hostname) if _('exams') in resources: exams = Exam.objects.get_exams(year, semester.type, slug) add_exams(exams, cal, hostname) icalstream = cal.serialize() filename = '%s.ics' % '-'.join([str(semester.year), semester.type, slug] + resources) response = http.HttpResponse(icalstream, content_type='text/calendar') response['Content-Type'] = 'text/calendar; charset=utf-8' response['Filename'] = filename # IE needs this response['Content-Disposition'] = 'attachment; filename=%s' % filename response['X-Robots-Tag'] = 'noindex, nofollow' # TODO(adamcik): add expires header that reduces load on old semesters return response
def ical(request, year, semester_type, slug, ical_type=None): resources = [_(u"lectures"), _(u"exams")] if ical_type and ical_type not in resources: raise http.Http404 elif ical_type: resources = [ical_type] title = urlresolvers.reverse("schedule", args=[year, semester_type, slug]) hostname = settings.TIMETABLE_HOSTNAME or request.META.get("HTTP_HOST", socket.getfqdn()) semester = Semester(year=year, type=semester_type) cal = vobject.iCalendar() cal.add("method").value = "PUBLISH" # IE/Outlook needs this # TODO(adamcik): use same logic as in common.templatetags.title if slug.lower().endswith("s"): description = _(u"%(slug)s' %(semester)s %(year)s schedule for %(resources)s") else: description = _(u"%(slug)s's %(semester)s %(year)s schedule for %(resources)s") cal.add("X-WR-CALNAME").value = title.strip("/") cal.add("X-WR-CALDESC").value = description % { "slug": slug, "semester": semester.get_type_display(), "year": semester.year, "resources": ", ".join(resources), } if _("lectures") in resources: lectures = Lecture.objects.get_lectures(year, semester.type, slug) add_lectutures(lectures, semester.year, cal, hostname) if _("exams") in resources: exams = Exam.objects.get_exams(year, semester.type, slug) add_exams(exams, cal, hostname) icalstream = cal.serialize() filename = "%s.ics" % "-".join([str(semester.year), semester.type, slug] + resources) response = http.HttpResponse(icalstream, content_type="text/calendar") response["Content-Type"] = "text/calendar; charset=utf-8" response["Filename"] = filename # IE needs this response["Content-Disposition"] = "attachment; filename=%s" % filename response["X-Robots-Tag"] = "noindex, nofollow" # TODO(adamcik): add expires header that reduces load on old semesters return response
def list_courses(request, year, semester_type, slug): '''Display a list of courses''' if request.method == 'POST': return select_course(request, year, semester_type, slug, add=True) courses = Course.objects.get_courses_with_exams(year, semester_type) return shortcuts.render( request, 'course_list.html', { 'semester': Semester(year=year, type=semester_type), 'course_list': courses, })
def select_groups(request, year, semester_type, slug): '''Form handler for selecting groups to use in schedule''' courses = Course.objects.get_courses(year, semester_type, slug) course_groups = Course.get_groups(year, semester_type, [c.id for c in courses]) if request.method == 'POST': with transaction.atomic(): for c in courses: try: groups = course_groups[c.id] except KeyError: # Skip courses without groups continue group_form = forms.GroupForm(groups, request.POST, prefix=c.id) if group_form.is_valid(): subscription = Subscription.objects.get_subscriptions(year, semester_type, slug).get(course=c) subscription.groups = group_form.cleaned_data['groups'] return shortcuts.redirect( 'schedule-advanced', year, Semester.localize(semester_type), slug) color_map = utils.ColorMap(hex=True) subscription_groups = Subscription.get_groups(year, semester_type, slug) all_subscripted_groups = set() for groups in subscription_groups.values(): for group in groups: all_subscripted_groups.add(group) for c in courses: color_map[c.id] subscription_id = c.subscription_set.get(student__slug=slug).pk try: groups = course_groups[c.id] except KeyError: # Skip courses without groups continue initial_groups = subscription_groups.get(subscription_id, all_subscripted_groups) c.group_form = forms.GroupForm(groups, prefix=c.id, initial={'groups': initial_groups}) return shortcuts.render(request, 'select_groups.html', { 'semester': Semester(year=year, type=semester_type), 'slug': slug, 'courses': courses, 'color_map': color_map, })
def schedule_current(request, year, semester_type, slug): semester = Semester(year=year, type=semester_type) current_week = get_current_week() weeks = Week.objects.filter( lecture__course__subscription__student__slug=slug, lecture__course__semester__year__exact=semester.year, lecture__course__semester__type=semester.type) weeks = weeks.distinct().values_list('number', flat=True) if current_week in weeks and semester.year == today().year: return shortcuts.redirect( 'schedule-week', semester.year, semester.slug, slug, current_week) return shortcuts.redirect('schedule', semester.year, semester.slug, slug)
def select_lectures(request, year, semester_type, slug): '''Handle selection of lectures to hide''' if request.method == 'POST': excludes = request.POST.getlist('exclude') subscriptions = Subscription.objects.get_subscriptions(year, semester_type, slug) for subscription in subscriptions: if excludes: subscription.exclude = subscription.course.lecture_set.filter(id__in=excludes) else: subscription.exclude.clear() return shortcuts.redirect( 'schedule-advanced', year, Semester.localize(semester_type), slug)
def select_lectures(request, year, semester_type, slug): """Handle selection of lectures to hide""" if request.method == "POST": with transaction.atomic(): excludes = request.POST.getlist("exclude") subscriptions = Subscription.objects.get_subscriptions(year, semester_type, slug) for subscription in subscriptions: if excludes: subscription.exclude = subscription.course.lecture_set.filter(id__in=excludes) else: subscription.exclude.clear() return shortcuts.redirect("schedule-advanced", year, Semester.localize(semester_type), slug)
def select_lectures(request, year, semester_type, slug): '''Handle selection of lectures to hide''' if request.method == 'POST': with transaction.atomic(): excludes = request.POST.getlist('exclude') subscriptions = Subscription.objects.get_subscriptions(year, semester_type, slug) for subscription in subscriptions: if excludes: subscription.exclude = subscription.course.lecture_set.filter(id__in=excludes) else: subscription.exclude.clear() return shortcuts.redirect( 'schedule-advanced', year, Semester.localize(semester_type), slug)
def select_course(request, year, semester_type, slug, add=False): '''Handle selecting of courses from course list, change of names and removeall of courses''' # FIXME split ut three sub functions into seperate functions? try: semester = Semester.objects.get(year=year, type=semester_type) except Semester.DoesNotExist: return shortcuts.redirect( 'schedule', year, Semester.localize(semester_type), slug) if request.method == 'POST': if 'submit_add' in request.POST or add: lookup = [] for l in request.POST.getlist('course_add'): lookup.extend(l.replace(',', '').split()) subscriptions = set(Subscription.objects.get_subscriptions(year, semester_type, slug).values_list('course__code', flat=True)) errors = [] to_many_subscriptions = False student, created = Student.objects.get_or_create(slug=slug) for l in lookup: try: if len(subscriptions) > settings.TIMETABLE_MAX_COURSES: to_many_subscriptions = True break course = Course.objects.get( code__iexact=l.strip(), semester__year__exact=year, semester__type__exact=semester_type, ) Subscription.objects.get_or_create( student=student, course=course, ) subscriptions.add(course.code) except Course.DoesNotExist: errors.append(l) if errors or to_many_subscriptions: return shortcuts.render(request, 'error.html', { 'courses': errors, 'max': settings.TIMETABLE_MAX_COURSES, 'slug': slug, 'year': year, 'type': semester_type, 'to_many_subscriptions': to_many_subscriptions, }) return shortcuts.redirect( 'change-groups', year, Semester.localize(semester_type), slug) elif 'submit_remove' in request.POST: with transaction.atomic(): courses = [] for c in request.POST.getlist('course_remove'): if c.strip(): courses.append(c.strip()) Subscription.objects.get_subscriptions(year, semester_type, slug). \ filter(course__id__in=courses).delete() if Subscription.objects.filter(student__slug=slug).count() == 0: Student.objects.filter(slug=slug).delete() elif 'submit_name' in request.POST: subscriptions = Subscription.objects.get_subscriptions(year, semester_type, slug) for u in subscriptions: form = forms.CourseAliasForm(request.POST, prefix=u.course_id) if form.is_valid(): alias = form.cleaned_data['alias'].strip() if alias.upper() == u.course.code.upper() or alias == "": # Leave as blank if we match the current course name alias = "" u.alias = alias u.save() return shortcuts.redirect( 'schedule-advanced', year, Semester.localize(semester_type), slug)
def pdf(request, year, semester_type, slug, size=None, week=None): if size is not None and size not in ['A4', 'A5', 'A6', 'A7']: raise http.Http404 semester = Semester(year=year, type=semester_type) color_map = ColorMap(hex=True) margin = 0.5*units.cm width, height = pagesizes.landscape(pagesizes.A5) width -= 2*margin height -= 2*margin time_width = 0.06 * width day_width = (width-time_width) / 5 filename = '%s-%s-%s' % (year, semester.type, slug) if week: filename += '-%s' % week response = http.HttpResponse(mimetype='application/pdf') response['Content-Disposition'] = 'attachment; filename=%s.pdf' % filename lectures = Lecture.objects.get_lectures(year, semester.type, slug, week) rooms = Lecture.get_related(Room, lectures) courses = Course.objects.get_courses(year, semester.type, slug) for course in courses: color_map[course.id] timetable = Timetable(lectures) if lectures: timetable.place_lectures() timetable.do_expansion() timetable.insert_times() if week: timetable.set_week(semester.year, int(week)) paragraph_style = default_styles['Normal'] paragraph_style.fontName = 'Helvetica-Bold' paragraph_style.fontSize = 10 paragraph_style.leading = 12 table_style = _tablestyle() data = [[platypus.Paragraph(render_title(semester, slug, week), paragraph_style)]] data[-1].extend([''] * sum(timetable.span)) table_style.add('SPAN', (0,0), (-1, 0)) # Add days data.append(['']) for span, date, name in timetable.header(): if date: data[-1].append(dateformat.format(date, 'l - j M.')) else: data[-1].append(unicode(name)) if span > 1: extra = span - 1 table_style.add('SPAN', (len(data[-1])-1, 2), (len(data[-1])-1+extra, 2)) data[-1].extend([''] * extra) # Convert to "simple" datastruct for row in timetable.table: data_row = [] for cells in row: for cell in cells: time = cell.get('time', '') lecture = cell.get('lecture', '') if lecture: if lecture.type and lecture.type.optional: paragraph_style.fontName = 'Helvetica' code = lecture.alias or lecture.course.code content = [platypus.Paragraph(html.escape(code), paragraph_style)] paragraph_style.leading = 8 if lecture.type: content += [platypus.Paragraph(u'<font size=6>%s</font>' % lecture.type.name.replace('/', ' / '), paragraph_style)] content += [platypus.Paragraph(u'<font size=6>%s</font>' % u', '.join(rooms.get(lecture.id, [])), paragraph_style)] paragraph_style.leading = 12 paragraph_style.fontName = 'Helvetica-Bold' elif time: content = time.replace(' - ', '\n') else: content = '' if cell.get('remove', False): data_row.append('') else: data_row.append(content) data.append(data_row) # Calculate widths and line that splits days col_widths = [time_width] for w in timetable.span: x = len(col_widths) table_style.add('LINEBEFORE', (x, 2), (x, -1), 1, outer_border) col_widths.extend([float(day_width)/w] * w) # Set row heights row_heights = [16, 12] row_heights += [(height-(8*2)) / (len(data)-2)] * (len(data)-2) # Create spans, setup backgrounds and put content in KeepInFrame for lecture in timetable.lectures: offset = 0 for o in timetable.span[:lecture['j']]: offset += o x1 = offset + lecture['k'] + 1 y1 = lecture['i']+2 x2 = x1 + lecture['width'] - 1 y2 = y1 + lecture['height'] - 1 table_style.add('SPAN', (x1, y1), (x2, y2)) table_style.add('BACKGROUND', (x1, y1), (x2, y2), colors.HexColor(color_map[lecture['l'].course_id])) content = data[y1][x1] data[y1][x1] = platypus.KeepInFrame(col_widths[x1]*lecture['width'], row_heights[2]*lecture['height'], content, mode='shrink') page = canvas.Canvas(response, pagesizes.A4) page.translate(margin, pagesizes.A4[1]-margin) if 'A4' == size: page.translate(0.5*margin, 2.5*margin-pagesizes.A4[1]) page.scale(1.414, 1.414) page.rotate(90) elif 'A6' == size: page.scale(0.707, 0.707) elif 'A7' == size: page.scale(0.5, 0.5) table = tables.Table(data, colWidths=col_widths, rowHeights=row_heights, style=table_style) table.wrapOn(page, width, height) table.drawOn(page, 0, -height) note = request.META.get('HTTP_HOST', '').split(':')[0] page.setFont('Helvetica', 10) page.setFillColor(colors.HexColor('#666666')) page.drawString(width - page.stringWidth(note) - 2, -height+2, note) page.showPage() page.save() response['X-Robots-Tag'] = 'noindex, nofollow' return response
def setUp(self): self.set_now_to(2009, 1, 1) self.semester = Semester(year=2009, type='spring') self.default_args = [self.semester.year, self.semester.type, 'adamcik']
def select_course(request, year, semester_type, slug, add=False): """Handle selecting of courses from course list, change of names and removeall of courses""" # FIXME split ut three sub functions into seperate functions? try: semester = Semester.objects.get(year=year, type=semester_type) except Semester.DoesNotExist: return shortcuts.redirect("schedule", year, Semester.localize(semester_type), slug) if request.method == "POST": if "submit_add" in request.POST or add: lookup = [] for l in request.POST.getlist("course_add"): lookup.extend(l.replace(",", "").split()) subscriptions = set( Subscription.objects.get_subscriptions(year, semester_type, slug).values_list("course__code", flat=True) ) if not lookup: localized_semester = Semester.localize(semester_type) return shortcuts.redirect("schedule-advanced", year, localized_semester, slug) errors = [] to_many_subscriptions = False student, created = Student.objects.get_or_create(slug=slug) for l in lookup: try: if len(subscriptions) > settings.TIMETABLE_MAX_COURSES: to_many_subscriptions = True break course = Course.objects.get( code__iexact=l.strip(), semester__year__exact=year, semester__type__exact=semester_type ) Subscription.objects.get_or_create(student=student, course=course) subscriptions.add(course.code) except Course.DoesNotExist: errors.append(l) if errors or to_many_subscriptions: return shortcuts.render( request, "error.html", { "courses": errors, "max": settings.TIMETABLE_MAX_COURSES, "slug": slug, "year": year, "type": semester_type, "to_many_subscriptions": to_many_subscriptions, }, ) return shortcuts.redirect("change-groups", year, Semester.localize(semester_type), slug) elif "submit_remove" in request.POST: with transaction.atomic(): courses = [] for c in request.POST.getlist("course_remove"): if c.strip(): courses.append(c.strip()) Subscription.objects.get_subscriptions(year, semester_type, slug).filter( course__id__in=courses ).delete() if Subscription.objects.filter(student__slug=slug).count() == 0: Student.objects.filter(slug=slug).delete() elif "submit_name" in request.POST: subscriptions = Subscription.objects.get_subscriptions(year, semester_type, slug) for u in subscriptions: form = forms.CourseAliasForm(request.POST, prefix=u.course_id) if form.is_valid(): alias = form.cleaned_data["alias"].strip() if alias.upper() == u.course.code.upper() or alias == "": # Leave as blank if we match the current course name alias = "" u.alias = alias u.save() return shortcuts.redirect("schedule-advanced", year, Semester.localize(semester_type), slug)