# Your hooks go here.
# @hook
-# def can_create_reading_lists(user):
+# def can_create_sites(user):
# ...
+++ /dev/null
-function do_init() {
- if ($('#id_code').attr('tagName') == 'SELECT') {
- // code is a SELECT, so we add a callback to lookup titles.
- $('#id_code').change(function() {
- $('#id_title')[0].disabled=true;
- $.getJSON(ROOT + '/course/new/ajax_title', {course_code: $(this).val()},
- function(resp) {
- $('#id_title').val(resp.title)
- $('#id_title')[0].disabled=false;
-
- });
- });
- }
-}
-
-$(do_init);
+++ /dev/null
-function do_init() {
- show_specific_only();
- $('#id_access').change(show_specific_only);
-}
-
-function show_specific_only() {
- $('.specific').hide();
- var cur = $('#id_access').val();
- $('#' + cur + '_panel').fadeIn();
-}
-
-$(do_init);
\ No newline at end of file
--- /dev/null
+function do_init() {
+ if ($('#id_code').attr('tagName') == 'SELECT') {
+ // code is a SELECT, so we add a callback to lookup titles.
+ $('#id_code').change(function() {
+ $('#id_title')[0].disabled=true;
+ $.getJSON(ROOT + '/course/new/ajax_title', {course_code: $(this).val()},
+ function(resp) {
+ $('#id_title').val(resp.title)
+ $('#id_title')[0].disabled=false;
+
+ });
+ });
+ }
+}
+
+$(do_init);
--- /dev/null
+function do_init() {
+ show_specific_only();
+ $('#id_access').change(show_specific_only);
+}
+
+function show_specific_only() {
+ $('.specific').hide();
+ var cur = $('#id_access').val();
+ $('#' + cur + '_panel').fadeIn();
+}
+
+$(do_init);
\ No newline at end of file
+++ /dev/null
-from _common import *
-from django.utils.translation import ugettext as _
-from search import *
-
-#-----------------------------------------------------------------------------
-# Creating a new course
-
-class NewCourseForm(ModelForm):
- class Meta:
- model = models.Course
- exclude = ('passkey','access')
-
- def clean_code(self):
- v = (self.cleaned_data.get('code') or '').strip()
- is_valid_func = models.campus.course_code_is_valid
- if (not is_valid_func) or is_valid_func(v):
- return v
- else:
- raise ValidationError, _('invalid course code')
-
-# if we have course-code lookup, hack lookup support into the new-course form.
-
-COURSE_CODE_LIST = bool(models.campus.course_code_list)
-COURSE_CODE_LOOKUP_TITLE = bool(models.campus.course_code_lookup_title)
-
-if COURSE_CODE_LIST:
- from django.forms import Select
- course_list = models.campus.course_code_list()
- choices = [(a,a) for a in course_list]
- choices.sort()
- empty_label = u'---------'
- choices.insert(0, ('', empty_label))
- NewCourseForm.base_fields['code'].widget = Select(
- choices = choices)
- NewCourseForm.base_fields['code'].empty_label = empty_label
-
-#--------------------
-
-@login_required
-def add_new_course(request):
- if not request.user.can_create_reading_lists():
- return _access_denied(_('You are not allowed to create course sites.'))
- return _add_or_edit_course(request)
-
-@instructors_only
-def edit_course(request, course_id):
- instance = get_object_or_404(models.Course, pk=course_id)
- return _add_or_edit_course(request, instance=instance)
-
-def _add_or_edit_course(request, instance=None):
- is_add = (instance is None)
- if is_add:
- instance = models.Course()
- current_access_level = not is_add and instance.access or None
- example = models.campus.course_code_example
- if request.method != 'POST':
- form = NewCourseForm(instance=instance)
- return g.render('edit_course.xhtml', **locals())
- else:
- form = NewCourseForm(request.POST, instance=instance)
- if not form.is_valid():
- return g.render('edit_course.xhtml', **locals())
- else:
- form.save()
- course = form.instance
- if course.access == u'INVIT' and not course.passkey:
- course.generate_new_passkey()
- course.save()
- assert course.id
- user_in_course = models.Member.objects.filter(user=request.user,course=course)
- if not user_in_course: # for edits, might already be!
- mbr = course.member_set.create(user=request.user, role='INSTR')
- mbr.save()
-
- if is_add or (current_access_level != course.access):
- # we need to configure permissions.
- return HttpResponseRedirect(course.course_url('edit/permission/'))
- else:
- return HttpResponseRedirect('../') # back to main view.
-
-# no access-control needed to protect title lookup.
-def add_new_course_ajax_title(request):
- course_code = request.GET['course_code']
- title = models.campus.course_code_lookup_title(course_code)
- return HttpResponse(simplejson.dumps({'title':title}))
-
-@instructors_only
-def edit_course_permissions(request, course_id):
- course = get_object_or_404(models.Course, pk=course_id)
- # choices: make the access-choice labels more personalized than
- # the ones in 'models'.
- choices = [
- # note: I'm leaving ANON out for now, until we discuss it further.
- (u'ANON', _(u'Anyone on the planet may access this site.')),
- (u'CLOSE', _(u'No students: this site is closed.')),
- (u'STUDT', _(u'Students in my course -- I will provide section numbers')),
- (u'INVIT', _(u'Students in my course -- I will share an Invitation Code with them')),
- (u'LOGIN', _(u'All Reserves patrons'))]
- if models.campus.sections_tuple_delimiter is None:
- # no course-sections support? Then STUDT cannot be an option.
- del choices[1]
- choose_access = django.forms.Select(choices=choices)
-
- if request.method != 'POST':
- return g.render('edit_course_permissions.xhtml', **locals())
- else:
- POST = request.POST
-
- if 'action_change_code' in POST:
- # update invitation code -------------------------------------
- course.generate_new_passkey()
- course.access = u'INVIT'
- course.save()
- return HttpResponseRedirect('.#student_access')
-
- elif 'action_save_instructor' in POST:
- # update instructor details ----------------------------------
- iname = POST.get('new_instructor_name','').strip()
- irole = POST.get('new_instructor_role')
-
- def get_record_for(username):
- instr = models.maybe_initialize_user(iname)
- if instr:
- try:
- return models.Member.objects.get(user=instr, course=course)
- except models.Member.DoesNotExist:
- return models.Member.objects.create(user=instr, course=course)
-
- # add a new instructor
- if iname:
- instr = get_record_for(iname)
- if instr: # else? should have an error.
- instr.role = irole
- instr.save()
- else:
- instructor_error = 'No such user: %s' % iname
- return g.render('edit_course_permissions.xhtml', **locals())
-
-
- # removing and changing roles of instructors
- to_change_role = [(int(name.rsplit('_', 1)[-1]), POST[name]) \
- for name in POST if name.startswith('instructor_role_')]
- to_remove = [int(name.rsplit('_', 1)[-1]) \
- for name in POST if name.startswith('instructor_remove_')]
- for instr_id, newrole in to_change_role:
- if not instr_id in to_remove:
- instr = models.Member.objects.get(pk=instr_id, course=course)
- instr.role = newrole
- instr.save()
- for instr_id in to_remove:
- # todo, should warn if deleting yourself!
- instr = models.Member.objects.get(pk=instr_id, course=course)
- instr.delete()
- # todo, should have some error-reporting.
- return HttpResponseRedirect('.')
-
- elif 'action_save_student' in POST:
- # update student details ------------------------------------
- access = POST.get('access')
- course.access = access
- # drop all provided users. fixme, this could be optimized to do add/drops.
- models.Member.objects.filter(course=course, provided=True).delete()
- if course.access == u'STUDT':
- initial_sections = course.sections()
- # add the 'new section' if any
- new_sec = request.POST.get('add_section')
- new_sec = models.section_decode_safe(new_sec)
- if new_sec:
- course.add_sections(new_sec)
- # remove the sections to be dropped
- to_remove = [models.section_decode_safe(name.rsplit('_',1)[1]) \
- for name in POST \
- if name.startswith('remove_section_')]
- course.drop_sections(*to_remove)
- student_names = models.campus.students_in(*course.sections())
- for name in student_names:
- user = models.maybe_initialize_user(name)
- if user:
- if not models.Member.objects.filter(course=course, user=user):
- mbr = models.Member.objects.create(
- course=course, user=user,
- role='STUDT', provided=True)
- mbr.save()
- else:
- pass
- course.save()
- return HttpResponseRedirect('.#student_access')
-
-@instructors_only
-def delete_course(request, course_id):
- course = get_object_or_404(models.Course, pk=course_id)
- if request.POST.get('confirm_delete'):
- course.delete()
- return HttpResponseRedirect(reverse('my_courses'))
- else:
- return HttpResponseRedirect('../')
-
-#-----------------------------------------------------------------------------
-# Course Invitation Code handler
-
-@login_required # must be, to avoid/audit brute force attacks.
-def course_invitation(request):
- if request.method != 'POST':
- return g.render('course_invitation.xhtml', code='', error='',
- **locals())
- else:
- code = request.POST.get('code', '').strip()
- # todo, a pluggable passkey implementation would normalize the code here.
- if not code:
- return HttpResponseRedirect('.')
- try:
- # note, we only allow the passkey if access='INVIT'.
- crs = models.ReadingList.objects.filter(access='INVIT').get(passkey=code)
- except models.ReadingList.DoesNotExist:
- # todo, do we need a formal logging system? Or a table for
- # invitation failures? They should be captured somehow, I
- # think. Should we temporarily disable accounts after
- # multiple failures?
- log('WARN', 'Invitation failure, user %r gave code %r' % \
- (request.user.username, code))
- error = _('The code you provided is not valid.')
- return g.render('course_invitation.xhtml', **locals())
-
- # the passkey is good; add the user if not already a member.
- if not models.Member.objects.filter(user=request.user, course=crs):
- mbr = models.Member.objects.create(user=request.user, course=crs,
- role='STUDT')
- mbr.save()
- return HttpResponseRedirect(crs.course_url())
-
-#-----------------------------------------------------------------------------
-# ReadingList-instance handlers
-
-@members_only
-def course_detail(request, course_id):
- course = get_object_or_404(models.ReadingList, pk=course_id)
- return g.render('course_detail.xhtml', course=course)
-
-@members_only
-def course_search(request, course_id):
- course = get_object_or_404(models.ReadingList, pk=course_id)
- return search(request, in_course=course)
-
-@login_required
-def course_join(request, course_id):
- """Self-register into an open-registration course."""
- course = get_object_or_404(models.ReadingList, pk=course_id)
- if not course.is_joinable_by(request.user):
- # user should never see this.
- return simple_message(_('You cannot join this course.'),
- _('Sorry, but you cannot join this course at this time.'))
- elif request.method != 'POST':
- return g.render('course_join.xhtml', course=course)
- else:
- mbr = models.Member.objects.create(user=request.user, course=course, role='STUDT')
- mbr.save()
- return HttpResponseRedirect(course.course_url())
-
-
--- /dev/null
+from _common import *
+from django.utils.translation import ugettext as _
+from search import *
+
+#-----------------------------------------------------------------------------
+# Creating a new course
+
+class NewCourseForm(ModelForm):
+ class Meta:
+ model = models.Course
+ exclude = ('passkey','access')
+
+ def clean_code(self):
+ v = (self.cleaned_data.get('code') or '').strip()
+ is_valid_func = models.campus.course_code_is_valid
+ if (not is_valid_func) or is_valid_func(v):
+ return v
+ else:
+ raise ValidationError, _('invalid course code')
+
+# if we have course-code lookup, hack lookup support into the new-course form.
+
+COURSE_CODE_LIST = bool(models.campus.course_code_list)
+COURSE_CODE_LOOKUP_TITLE = bool(models.campus.course_code_lookup_title)
+
+if COURSE_CODE_LIST:
+ from django.forms import Select
+ course_list = models.campus.course_code_list()
+ choices = [(a,a) for a in course_list]
+ choices.sort()
+ empty_label = u'---------'
+ choices.insert(0, ('', empty_label))
+ NewCourseForm.base_fields['code'].widget = Select(
+ choices = choices)
+ NewCourseForm.base_fields['code'].empty_label = empty_label
+
+#--------------------
+
+@login_required
+def add_new_course(request):
+ if not request.user.can_create_reading_lists():
+ return _access_denied(_('You are not allowed to create course sites.'))
+ return _add_or_edit_course(request)
+
+@instructors_only
+def edit_course(request, course_id):
+ instance = get_object_or_404(models.Course, pk=course_id)
+ return _add_or_edit_course(request, instance=instance)
+
+def _add_or_edit_course(request, instance=None):
+ is_add = (instance is None)
+ if is_add:
+ instance = models.Course()
+ current_access_level = not is_add and instance.access or None
+ example = models.campus.course_code_example
+ if request.method != 'POST':
+ form = NewCourseForm(instance=instance)
+ return g.render('edit_course.xhtml', **locals())
+ else:
+ form = NewCourseForm(request.POST, instance=instance)
+ if not form.is_valid():
+ return g.render('edit_course.xhtml', **locals())
+ else:
+ form.save()
+ course = form.instance
+ if course.access == u'INVIT' and not course.passkey:
+ course.generate_new_passkey()
+ course.save()
+ assert course.id
+ user_in_course = models.Member.objects.filter(user=request.user,course=course)
+ if not user_in_course: # for edits, might already be!
+ mbr = course.member_set.create(user=request.user, role='INSTR')
+ mbr.save()
+
+ if is_add or (current_access_level != course.access):
+ # we need to configure permissions.
+ return HttpResponseRedirect(course.course_url('edit/permission/'))
+ else:
+ return HttpResponseRedirect('../') # back to main view.
+
+# no access-control needed to protect title lookup.
+def add_new_course_ajax_title(request):
+ course_code = request.GET['course_code']
+ title = models.campus.course_code_lookup_title(course_code)
+ return HttpResponse(simplejson.dumps({'title':title}))
+
+@instructors_only
+def edit_course_permissions(request, course_id):
+ course = get_object_or_404(models.Course, pk=course_id)
+ # choices: make the access-choice labels more personalized than
+ # the ones in 'models'.
+ choices = [
+ # note: I'm leaving ANON out for now, until we discuss it further.
+ (u'ANON', _(u'Anyone on the planet may access this site.')),
+ (u'CLOSE', _(u'No students: this site is closed.')),
+ (u'STUDT', _(u'Students in my course -- I will provide section numbers')),
+ (u'INVIT', _(u'Students in my course -- I will share an Invitation Code with them')),
+ (u'LOGIN', _(u'All Reserves patrons'))]
+ if models.campus.sections_tuple_delimiter is None:
+ # no course-sections support? Then STUDT cannot be an option.
+ del choices[1]
+ choose_access = django.forms.Select(choices=choices)
+
+ if request.method != 'POST':
+ return g.render('edit_course_permissions.xhtml', **locals())
+ else:
+ POST = request.POST
+
+ if 'action_change_code' in POST:
+ # update invitation code -------------------------------------
+ course.generate_new_passkey()
+ course.access = u'INVIT'
+ course.save()
+ return HttpResponseRedirect('.#student_access')
+
+ elif 'action_save_instructor' in POST:
+ # update instructor details ----------------------------------
+ iname = POST.get('new_instructor_name','').strip()
+ irole = POST.get('new_instructor_role')
+
+ def get_record_for(username):
+ instr = models.maybe_initialize_user(iname)
+ if instr:
+ try:
+ return models.Member.objects.get(user=instr, course=course)
+ except models.Member.DoesNotExist:
+ return models.Member.objects.create(user=instr, course=course)
+
+ # add a new instructor
+ if iname:
+ instr = get_record_for(iname)
+ if instr: # else? should have an error.
+ instr.role = irole
+ instr.save()
+ else:
+ instructor_error = 'No such user: %s' % iname
+ return g.render('edit_course_permissions.xhtml', **locals())
+
+
+ # removing and changing roles of instructors
+ to_change_role = [(int(name.rsplit('_', 1)[-1]), POST[name]) \
+ for name in POST if name.startswith('instructor_role_')]
+ to_remove = [int(name.rsplit('_', 1)[-1]) \
+ for name in POST if name.startswith('instructor_remove_')]
+ for instr_id, newrole in to_change_role:
+ if not instr_id in to_remove:
+ instr = models.Member.objects.get(pk=instr_id, course=course)
+ instr.role = newrole
+ instr.save()
+ for instr_id in to_remove:
+ # todo, should warn if deleting yourself!
+ instr = models.Member.objects.get(pk=instr_id, course=course)
+ instr.delete()
+ # todo, should have some error-reporting.
+ return HttpResponseRedirect('.')
+
+ elif 'action_save_student' in POST:
+ # update student details ------------------------------------
+ access = POST.get('access')
+ course.access = access
+ # drop all provided users. fixme, this could be optimized to do add/drops.
+ models.Member.objects.filter(course=course, provided=True).delete()
+ if course.access == u'STUDT':
+ initial_sections = course.sections()
+ # add the 'new section' if any
+ new_sec = request.POST.get('add_section')
+ new_sec = models.section_decode_safe(new_sec)
+ if new_sec:
+ course.add_sections(new_sec)
+ # remove the sections to be dropped
+ to_remove = [models.section_decode_safe(name.rsplit('_',1)[1]) \
+ for name in POST \
+ if name.startswith('remove_section_')]
+ course.drop_sections(*to_remove)
+ student_names = models.campus.students_in(*course.sections())
+ for name in student_names:
+ user = models.maybe_initialize_user(name)
+ if user:
+ if not models.Member.objects.filter(course=course, user=user):
+ mbr = models.Member.objects.create(
+ course=course, user=user,
+ role='STUDT', provided=True)
+ mbr.save()
+ else:
+ pass
+ course.save()
+ return HttpResponseRedirect('.#student_access')
+
+@instructors_only
+def delete_course(request, course_id):
+ course = get_object_or_404(models.Course, pk=course_id)
+ if request.POST.get('confirm_delete'):
+ course.delete()
+ return HttpResponseRedirect(reverse('my_courses'))
+ else:
+ return HttpResponseRedirect('../')
+
+#-----------------------------------------------------------------------------
+# Course Invitation Code handler
+
+@login_required # must be, to avoid/audit brute force attacks.
+def course_invitation(request):
+ if request.method != 'POST':
+ return g.render('course_invitation.xhtml', code='', error='',
+ **locals())
+ else:
+ code = request.POST.get('code', '').strip()
+ # todo, a pluggable passkey implementation would normalize the code here.
+ if not code:
+ return HttpResponseRedirect('.')
+ try:
+ # note, we only allow the passkey if access='INVIT'.
+ crs = models.ReadingList.objects.filter(access='INVIT').get(passkey=code)
+ except models.ReadingList.DoesNotExist:
+ # todo, do we need a formal logging system? Or a table for
+ # invitation failures? They should be captured somehow, I
+ # think. Should we temporarily disable accounts after
+ # multiple failures?
+ log('WARN', 'Invitation failure, user %r gave code %r' % \
+ (request.user.username, code))
+ error = _('The code you provided is not valid.')
+ return g.render('course_invitation.xhtml', **locals())
+
+ # the passkey is good; add the user if not already a member.
+ if not models.Member.objects.filter(user=request.user, course=crs):
+ mbr = models.Member.objects.create(user=request.user, course=crs,
+ role='STUDT')
+ mbr.save()
+ return HttpResponseRedirect(crs.course_url())
+
+#-----------------------------------------------------------------------------
+# ReadingList-instance handlers
+
+@members_only
+def course_detail(request, course_id):
+ course = get_object_or_404(models.ReadingList, pk=course_id)
+ return g.render('course_detail.xhtml', course=course)
+
+@members_only
+def course_search(request, course_id):
+ course = get_object_or_404(models.ReadingList, pk=course_id)
+ return search(request, in_course=course)
+
+@login_required
+def course_join(request, course_id):
+ """Self-register into an open-registration course."""
+ course = get_object_or_404(models.ReadingList, pk=course_id)
+ if not course.is_joinable_by(request.user):
+ # user should never see this.
+ return simple_message(_('You cannot join this course.'),
+ _('Sorry, but you cannot join this course at this time.'))
+ elif request.method != 'POST':
+ return g.render('course_join.xhtml', course=course)
+ else:
+ mbr = models.Member.objects.create(user=request.user, course=course, role='STUDT')
+ mbr.save()
+ return HttpResponseRedirect(course.course_url())
+
+
+++ /dev/null
-<?python
-searchtext = _('search this course...')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- py:strip="">
-
- <div py:def="course_search(course)" id="coursesearch" style="float: right;">
- <form method="get" action="${course.course_url('search')}"
- onsubmit="if(q.value.replace(/^\s*/, '').replace(/\s*$/, '') =='') return false;">
- <input id="q" name="q" maxlength="100" size="25" type="text"
- value="${searchtext}" onblur="if(this.value=='') this.value='${searchtext}';"
- onfocus="if(this.value=='${searchtext}') this.value='';"/>
- </form>
- </div>
-
- <div py:def="course_banner(course)" py:strip="True">
- <div id="coursebanner">
- ${course_search(course)}
- <div class="deptident">${course.department}</div>
- <h1><a href="${course.course_url()}"><span py:if="course.code">${course.code}: </span>${course.title}</a></h1>
- </div>
- </div>
-
- <!-- !show_tree: display a tree of items in a hierarchical style. -->
- <ul py:def="show_tree(tree, edit=False)"
- py:if="tree"
- class="itemtree">
- <li py:for="item, subs in tree" class="item_${item.item_type} an_item"
- id="item_${item.id}">
- <div class="mainline ${item.item_type=='HEADING' and 'headingmainline' or ''}">
- <a href="${item.item_url()}" class="mainlink">${item}</a>
- <span class="menublock" py:if="not (item.item_type=='HEADING' and not edit)">
-
- <!-- !I'm really sorry, this is ugly, but I want
- non-breaking spaces here, so that on long-named items,
- the links will stay togeter. A better way? -->
- <span py:if="item.needs_meta_link()"><a href="${item.item_url('meta')}">about</a> </span><span py:if="edit">• <a href="${item.item_url('edit/')}">edit</a></span><span py:if="edit"> • <a href="${item.item_url('relocate/')}">relocate</a></span>
- </span>
- </div>
- <!-- !to show a full tree, uncomment the following: -->
- ${show_tree(subs, edit)}
- </li>
- </ul>
-
- <ul py:def="heading_tree(tree, stop_at=None)" class="heading_tree">
- <li><input type="radio" name="heading" value="0" id="heading_0"/><label for="heading_0">Top Level</label></li>
- ${_heading_tree(tree, stop_at)}
- </ul>
-
- <ul py:def="_heading_tree(tree, stop_at=None)">
- <li py:for="item, subs in tree" py:if="item.item_type=='HEADING' and item != stop_at">
- <input type="radio" name="heading" value="${item.id}" id="heading_${item.id}"/><label for="heading_${item.id}">${item}</label>
- ${_heading_tree(subs, stop_at)}
- </li>
- </ul>
-
- <div py:def="nested_title(item)"
- py:if="item"
- py:with="hier=item.hierarchy()[:-1]; lastnum=len(hier)"
- class="nestedtitle">
- <div id="breadcrumbs">
- <span><a href="${item.course.course_url()}">Top</a></span> »
- <span py:for="n, x in enumerate(hier)"><a href="${x.item_url()}">${x.title}</a> » </span>
- <span class="final_item">${item.title}</span>
- </div>
- </div>
-
- <div py:def="add_subs(parent=None)" class="itemadd">
- <div>Add a new item:</div>
- <ul py:with="prefix = (not parent) and 'item/0/' or ''">
- <li><a href="${prefix}add/?item_type=HEADING">Subheading</a></li>
- <li><a href="${prefix}add/?item_type=URL">URL</a></li>
- <li><a href="${prefix}add/?item_type=ELEC">Electronic Document</a></li>
- <li><a href="${prefix}add/?item_type=PHYS">Physical Item or Catalogued Electronic Item</a></li>
- </ul>
- </div>
-
- <div py:def="item_metadata_formset_header()" py:strip="True">
- <script type="text/javascript">
- $(function() {$('#metadatatable').hide(); });
- function show_metadata() {
- $('#metadatatable').show();
- $('#show_metadata').fadeOut();
- }
- </script>
- </div>
-
- <div py:def="item_metadata_formset(show_more=True)">
- <p py:if="show_more"><a href="javascript:show_metadata();" id="show_metadata">Show more attributes</a></p>
- <div id="metadatatable">
- ${Markup(metadata_formset.management_form)}
- <table class="metadata_table">
- <thead>
- <tr><th>Attribute</th><th>Value</th><th/><th>Delete?</th></tr>
- </thead>
- <tbody>
- <tr py:for="form in metadata_formset.forms">
- <td py:if="cell" class="meta${n}" py:for="n, cell in enumerate(form)">${Markup(cell)}</td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
-
- <div py:def="item_resequence_panel()" py:strip="True">
- <div id="resequence_panel" class="little_action_panel">
- <a href="javascript:doResequence();">Resequence items</a>
- <div id="ropanelmessage" style="clear: right; display: none;"
- class="little_action_panel">Drag the items around. Then click Save Sequence, above.</div>
- </div>
- <div style="display: none;">
- <span id="i18n-save-order">Save Sequence</span>
- <span id="i18n-resequence-items">Resequence Items</span>
- <span id="i18n-new-order-saved">The new sequence has been saved.</span>
- </div>
- </div>
-
- <div py:def="offer_to_delete(item)" class="little_action_panel" py:if="item.id">
- <a href="../delete/">Delete this item</a>
- </div>
-
-</html>
-
--- /dev/null
+<?python
+searchtext = _('search this course...')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ py:strip="">
+
+ <div py:def="course_search(course)" id="coursesearch" style="float: right;">
+ <form method="get" action="${course.course_url('search')}"
+ onsubmit="if(q.value.replace(/^\s*/, '').replace(/\s*$/, '') =='') return false;">
+ <input id="q" name="q" maxlength="100" size="25" type="text"
+ value="${searchtext}" onblur="if(this.value=='') this.value='${searchtext}';"
+ onfocus="if(this.value=='${searchtext}') this.value='';"/>
+ </form>
+ </div>
+
+ <div py:def="course_banner(course)" py:strip="True">
+ <div id="coursebanner">
+ ${course_search(course)}
+ <div class="deptident">${course.department}</div>
+ <h1><a href="${course.course_url()}"><span py:if="course.code">${course.code}: </span>${course.title}</a></h1>
+ </div>
+ </div>
+
+ <!-- !show_tree: display a tree of items in a hierarchical style. -->
+ <ul py:def="show_tree(tree, edit=False)"
+ py:if="tree"
+ class="itemtree">
+ <li py:for="item, subs in tree" class="item_${item.item_type} an_item"
+ id="item_${item.id}">
+ <div class="mainline ${item.item_type=='HEADING' and 'headingmainline' or ''}">
+ <a href="${item.item_url()}" class="mainlink">${item}</a>
+ <span class="menublock" py:if="not (item.item_type=='HEADING' and not edit)">
+
+ <!-- !I'm really sorry, this is ugly, but I want
+ non-breaking spaces here, so that on long-named items,
+ the links will stay togeter. A better way? -->
+ <span py:if="item.needs_meta_link()"><a href="${item.item_url('meta')}">about</a> </span><span py:if="edit">• <a href="${item.item_url('edit/')}">edit</a></span><span py:if="edit"> • <a href="${item.item_url('relocate/')}">relocate</a></span>
+ </span>
+ </div>
+ <!-- !to show a full tree, uncomment the following: -->
+ ${show_tree(subs, edit)}
+ </li>
+ </ul>
+
+ <ul py:def="heading_tree(tree, stop_at=None)" class="heading_tree">
+ <li><input type="radio" name="heading" value="0" id="heading_0"/><label for="heading_0">Top Level</label></li>
+ ${_heading_tree(tree, stop_at)}
+ </ul>
+
+ <ul py:def="_heading_tree(tree, stop_at=None)">
+ <li py:for="item, subs in tree" py:if="item.item_type=='HEADING' and item != stop_at">
+ <input type="radio" name="heading" value="${item.id}" id="heading_${item.id}"/><label for="heading_${item.id}">${item}</label>
+ ${_heading_tree(subs, stop_at)}
+ </li>
+ </ul>
+
+ <div py:def="nested_title(item)"
+ py:if="item"
+ py:with="hier=item.hierarchy()[:-1]; lastnum=len(hier)"
+ class="nestedtitle">
+ <div id="breadcrumbs">
+ <span><a href="${item.course.course_url()}">Top</a></span> »
+ <span py:for="n, x in enumerate(hier)"><a href="${x.item_url()}">${x.title}</a> » </span>
+ <span class="final_item">${item.title}</span>
+ </div>
+ </div>
+
+ <div py:def="add_subs(parent=None)" class="itemadd">
+ <div>Add a new item:</div>
+ <ul py:with="prefix = (not parent) and 'item/0/' or ''">
+ <li><a href="${prefix}add/?item_type=HEADING">Subheading</a></li>
+ <li><a href="${prefix}add/?item_type=URL">URL</a></li>
+ <li><a href="${prefix}add/?item_type=ELEC">Electronic Document</a></li>
+ <li><a href="${prefix}add/?item_type=PHYS">Physical Item or Catalogued Electronic Item</a></li>
+ </ul>
+ </div>
+
+ <div py:def="item_metadata_formset_header()" py:strip="True">
+ <script type="text/javascript">
+ $(function() {$('#metadatatable').hide(); });
+ function show_metadata() {
+ $('#metadatatable').show();
+ $('#show_metadata').fadeOut();
+ }
+ </script>
+ </div>
+
+ <div py:def="item_metadata_formset(show_more=True)">
+ <p py:if="show_more"><a href="javascript:show_metadata();" id="show_metadata">Show more attributes</a></p>
+ <div id="metadatatable">
+ ${Markup(metadata_formset.management_form)}
+ <table class="metadata_table">
+ <thead>
+ <tr><th>Attribute</th><th>Value</th><th/><th>Delete?</th></tr>
+ </thead>
+ <tbody>
+ <tr py:for="form in metadata_formset.forms">
+ <td py:if="cell" class="meta${n}" py:for="n, cell in enumerate(form)">${Markup(cell)}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div py:def="item_resequence_panel()" py:strip="True">
+ <div id="resequence_panel" class="little_action_panel">
+ <a href="javascript:doResequence();">Resequence items</a>
+ <div id="ropanelmessage" style="clear: right; display: none;"
+ class="little_action_panel">Drag the items around. Then click Save Sequence, above.</div>
+ </div>
+ <div style="display: none;">
+ <span id="i18n-save-order">Save Sequence</span>
+ <span id="i18n-resequence-items">Resequence Items</span>
+ <span id="i18n-new-order-saved">The new sequence has been saved.</span>
+ </div>
+ </div>
+
+ <div py:def="offer_to_delete(item)" class="little_action_panel" py:if="item.id">
+ <a href="../delete/">Delete this item</a>
+ </div>
+
+</html>
+
+++ /dev/null
-<?python
-title = '%s: %s (%s)' % (course.code, course.title, course.term)
-item_tree = course.item_tree()
-is_editor = course.can_edit(request.user)
-is_joinable = course.is_joinable_by(request.user)
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
- <xi:include href="master.xhtml"/>
- <xi:include href="components/course.xhtml"/>
- <head>
- <title>${title}</title>
- <script type="text/javascript" src="${ROOT}/static/menublocks.js"/>
- </head>
- <body>
- ${course_banner(course)}
- <p py:if="not item_tree">
- There are no items associated with this course yet.
- </p>
- <div id="sidepanel">
- <div py:if="is_editor" id="edit_course" class="little_action_panel">
- <a href="${course.course_url()}edit/">Edit course details</a>
- </div>
- <div py:if="is_joinable">
- <a href="${course.course_url()}join/">Join this course</a>
- </div>
- <div id="showtree_panel" class="little_action_panel">
- <a href="javascript:doToggleItemTree();">Show/hide tree</a>
- </div>
- <div py:if="is_editor">${item_resequence_panel()}</div>
- </div>
- <div id="treepanel">
- ${show_tree(item_tree, edit=is_editor)}
- </div>
- <div py:if="is_editor">${add_subs()}</div>
-
-
- </body>
-</html>
+++ /dev/null
-<?python
-title = _('Join a course using an Invitation Code')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="master.xhtml"/>
-<head>
- <title>${title}</title>
- <script>
- $(function() {
- $('#code').focus();
- });
- </script>
-</head>
-<body>
- <h1>${title}</h1>
- <p>Your instructor may have provided you with an Invitation Code,
- which will give you access to your course's reserves. Enter the
- invitation code below to continue. Note that not all courses require
- an invitation code; contact your instructor or the library staff for
- more information.</p>
- <div class="errors" py:if="error">${error}</div>
- <form action="." method="POST">
- <table class="metadata_table">
- <tr><th>Invitation Code:</th><td><input type="text" id="code" name="code" size="30" value="${code}"/></td></tr>
- </table>
-<p><input type="submit" value="Continue"/> ${go_back_link()}</p>
- </form>
- <div class="gap"/>
-</body>
-</html>
+++ /dev/null
-<?python
-title = _('Join this course?')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
- <xi:include href="master.xhtml"/>
- <xi:include href="components/course.xhtml"/>
- <head>
- <title>${title}</title>
- <script type="text/javascript" src="${ROOT}/static/menublocks.js"/>
- </head>
- <body>
- ${course_banner(course)}
- <h2>${title}</h2>
-
- <p>If you choose to join this course, it will be added to your "My
- Courses" list.</p>
- <form action="." method="POST">
- <p><input type="submit" value="Yes, join this course"/> ${go_back_link()}</p>
- </form>
-
- </body>
-</html>
+++ /dev/null
-<?python
-title = defined('custom_title') and custom_title or _('Courses')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="master.xhtml"/>
-<xi:include href="paginate.xhtml"/>
-<head>
- <title>${title}</title>
- <script type="text/javascript">
- <!-- !This ought to be in paginate.xhtml, not here. how to do? -->
- $(function() { $('.pagetable').tablesorter(); });
- </script>
-</head>
-<body>
- <h1>${title}</h1>
- <tr py:def="pageheader()">
- <th>Name</th>
- </tr>
- <span py:def="pagerow(item)">
- <td><a href="${item.course_url()}">${item.title}, ${item.term}</a></td>
- </span>
- ${pagetable(paginator, count, pagerow, pageheader)}
-</body>
-</html>
+++ /dev/null
-<?python
-if instance.id:
- title = _('Edit course details')
-else:
- title = _('Create a new course site')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="master.xhtml"/>
-<xi:include href="components/course.xhtml"/>
-<head>
- <title>${title}</title>
- <script type="text/javascript" src="${ROOT}/static/edit_course.js"/>
-</head>
-<body>
- <div py:if="instance.id">${course_banner(instance)}</div>
- <h1>${title}</h1>
- <p py:if="instance.id"><a href="permission/">Edit course permissions</a> • <a href="${instance.course_url()}">Return to course page</a></p>
- <form action="." method="POST">
- <tr py:def="field_row(field, example=None)">
- <th>${field.label}</th>
- <td>
- <ul py:if="field.errors" class="errorlist">
- <li py:for="err in field.errors">${err}</li>
- </ul>
- ${Markup(field)}
- </td>
- <td class="example" py:if="example">e.g., ${example}</td>
- </tr>
- <h2>General description</h2>
- <table class="metadata_table">
- ${field_row(form.code, example)}
- ${field_row(form.title)}
- ${field_row(form.term)}
- ${field_row(form.department)}
- <!-- <tr><th>Department</th><td>${Markup(form.department)} ${errorlist(form.department)}</td></tr> -->
- </table>
- <p><input type="submit" value="Continue"/> ${go_back_link()}</p>
- </form>
- <div class="gap"/>
- <div py:if="instance.id">
- <h2>Delete this course</h2>
- <form action="delete/" method="POST">
- <p><input type="checkbox" name="confirm_delete" id="confirm_delete"/>
- <label for="confirm_delete">Yes, I want to delete this course site and all of its contents.</label>
- </p>
- <p><input type="submit" value="Delete this course"/> ${go_back_link()}</p>
- </form>
- </div>
-</body>
-</html>
+++ /dev/null
-<?python
-title = _('Edit course permissions')
-instructors = [m for m in models.Member.objects.filter(course=course) if m.role in ('PROXY', 'INSTR')]
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="master.xhtml"/>
- <xi:include href="components/course.xhtml"/>
-<head>
- <title>${title}</title>
- <script type="text/javascript" src="${ROOT}/static/edit_course_permissions.js"/>
-</head>
-<body>
- ${course_banner(course)}
- <h1>${title}</h1>
- <p><a href="../">Edit course details</a> • <a href="${course.course_url()}">Return to course page</a></p>
- <form action="." method="POST">
- <h2>Instructor Access</h2>
- <div py:if="defined('instructor_error')" class="errors">${instructor_error}</div>
- <table class="pagetable">
- <thead>
- <tr><th>Person</th><th>Role</th><th>Remove?</th></tr>
- </thead>
- <tbody>
- <select py:def="select_role(mbr)"
- py:replace="Markup(django.forms.Select(choices=
- [(u'INSTR',_('Instructor')), (u'PROXY', _('Proxy instructor'))])
- .render('instructor_role_%d' % mbr.id, mbr.role))"/>
- <tr py:for="mbr in instructors">
- <td>${mbr.user.get_full_name() or ''} <code>(${mbr.user.username})</code></td>
- <td>${select_role(mbr)}</td>
- <td><input type="checkbox" name="instructor_remove_${mbr.id}"/></td>
- </tr>
- <tr style="vertical-align: bottom;">
- <td><p>Username of the new instructor.</p><input type="text" name="new_instructor_name"/></td>
- <td><select name="new_instructor_role">
- <option value="INSTR">Instructor</option>
- <option value="PROXY">Proxy instructor</option>
- </select>
- </td>
- <td/>
- </tr>
- </tbody>
- </table>
- <p> <input type="submit" name="action_save_instructor" value="Save changes to instructors"/></p>
- <div class="gap"/>
- <h2 id="student_access">Student Access</h2>
- <p>Who will have student-level access to this site?
- <span style="margin-left: 12;"/>${Markup(choose_access.render('access', course.access, {'id':'id_access'}, []))}</p>
- <div id="INVIT_panel" class="specific">
- <h3>Course Invitation Code</h3>
- <p style="font-size: larger;">Your Course Invitation Code is: <strong>${course.passkey}</strong>
- <span style="margin-left: 16;">
- <input type="submit" name="action_change_code" value="Select a new code"/>
- </span>
- </p>
- <p>This invitation code will enable your students to join this
- course site. Share it only with your students: anyone who has
- the code can join your site.</p>
- <p>You may change the code at any time. This will not block
- students who have already joined, but will prevent new students
- from joining with the old code.</p>
- </div>
- <div id="STUDT_panel" class="specific">
- <h3>Course section numbers</h3>
- <?python
- current_sections = course.sections()
- my_sections = [s for s in models.campus.sections_taught_by(user.username) if not s in current_sections]
- ct_sections = [s for s in models.campus.sections_for_code_and_term(course.code, course.term.code) \
- if not s in current_sections]
- encode = lambda t,c,s: models.section_encode_safe((t,c,s))
- ?>
- <table class="pagetable">
- <thead>
- <tr>
- <th>Associated section</th><th>Remove?</th>
- </tr>
- </thead>
- <tbody>
- <tr py:for="(term, code, sec) in current_sections">
- <td>${code}, section ${sec}, in term ${term}</td>
- <td><input type="checkbox" name="remove_section_${encode(term,code,sec)}"/></td>
- </tr>
- </tbody>
- </table>
- <p>Add section:
- <select name="add_section">
- <option value="">--------</option>
- <optgroup py:for="label, sections in ((_('My Courses'), my_sections), (_('%s in term %s') % (course.code, course.term), ct_sections))"
- label="${label}" py:if="sections">
- <option py:for="(term, code, sec) in sections"
- value="${encode(term,code,sec)}">
- ${code}, section ${sec}, in term ${term}
- </option>
- </optgroup>
- </select>
- </p>
- </div>
- <p><input type="submit" name="action_save_student" value="Save changes to student access"/></p>
-
- <div class="gap"/>
- <h2>Class List</h2>
- <p>The following users have student-level access in this course site.</p>
- <ol>
- <li py:for="student in course.get_students()">
- ${student.get_full_name()} (${student.email})
- </li>
- </ol>
- </form>
- <div class="gap"/>
-</body>
-</html>
--- /dev/null
+<?python
+if instance.id:
+ title = _('Edit course details')
+else:
+ title = _('Create a new course site')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="master.xhtml"/>
+<xi:include href="components/course.xhtml"/>
+<head>
+ <title>${title}</title>
+ <script type="text/javascript" src="${ROOT}/static/edit_course.js"/>
+</head>
+<body>
+ <div py:if="instance.id">${course_banner(instance)}</div>
+ <h1>${title}</h1>
+ <p py:if="instance.id"><a href="permission/">Edit course permissions</a> • <a href="${instance.course_url()}">Return to course page</a></p>
+ <form action="." method="POST">
+ <tr py:def="field_row(field, example=None)">
+ <th>${field.label}</th>
+ <td>
+ <ul py:if="field.errors" class="errorlist">
+ <li py:for="err in field.errors">${err}</li>
+ </ul>
+ ${Markup(field)}
+ </td>
+ <td class="example" py:if="example">e.g., ${example}</td>
+ </tr>
+ <h2>General description</h2>
+ <table class="metadata_table">
+ ${field_row(form.code, example)}
+ ${field_row(form.title)}
+ ${field_row(form.term)}
+ ${field_row(form.department)}
+ <!-- <tr><th>Department</th><td>${Markup(form.department)} ${errorlist(form.department)}</td></tr> -->
+ </table>
+ <p><input type="submit" value="Continue"/> ${go_back_link()}</p>
+ </form>
+ <div class="gap"/>
+ <div py:if="instance.id">
+ <h2>Delete this course</h2>
+ <form action="delete/" method="POST">
+ <p><input type="checkbox" name="confirm_delete" id="confirm_delete"/>
+ <label for="confirm_delete">Yes, I want to delete this course site and all of its contents.</label>
+ </p>
+ <p><input type="submit" value="Delete this course"/> ${go_back_link()}</p>
+ </form>
+ </div>
+</body>
+</html>
--- /dev/null
+<?python
+title = _('Edit course permissions')
+instructors = [m for m in models.Member.objects.filter(course=course) if m.role in ('PROXY', 'INSTR')]
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="master.xhtml"/>
+ <xi:include href="components/course.xhtml"/>
+<head>
+ <title>${title}</title>
+ <script type="text/javascript" src="${ROOT}/static/edit_course_permissions.js"/>
+</head>
+<body>
+ ${course_banner(course)}
+ <h1>${title}</h1>
+ <p><a href="../">Edit course details</a> • <a href="${course.course_url()}">Return to course page</a></p>
+ <form action="." method="POST">
+ <h2>Instructor Access</h2>
+ <div py:if="defined('instructor_error')" class="errors">${instructor_error}</div>
+ <table class="pagetable">
+ <thead>
+ <tr><th>Person</th><th>Role</th><th>Remove?</th></tr>
+ </thead>
+ <tbody>
+ <select py:def="select_role(mbr)"
+ py:replace="Markup(django.forms.Select(choices=
+ [(u'INSTR',_('Instructor')), (u'PROXY', _('Proxy instructor'))])
+ .render('instructor_role_%d' % mbr.id, mbr.role))"/>
+ <tr py:for="mbr in instructors">
+ <td>${mbr.user.get_full_name() or ''} <code>(${mbr.user.username})</code></td>
+ <td>${select_role(mbr)}</td>
+ <td><input type="checkbox" name="instructor_remove_${mbr.id}"/></td>
+ </tr>
+ <tr style="vertical-align: bottom;">
+ <td><p>Username of the new instructor.</p><input type="text" name="new_instructor_name"/></td>
+ <td><select name="new_instructor_role">
+ <option value="INSTR">Instructor</option>
+ <option value="PROXY">Proxy instructor</option>
+ </select>
+ </td>
+ <td/>
+ </tr>
+ </tbody>
+ </table>
+ <p> <input type="submit" name="action_save_instructor" value="Save changes to instructors"/></p>
+ <div class="gap"/>
+ <h2 id="student_access">Student Access</h2>
+ <p>Who will have student-level access to this site?
+ <span style="margin-left: 12;"/>${Markup(choose_access.render('access', course.access, {'id':'id_access'}, []))}</p>
+ <div id="INVIT_panel" class="specific">
+ <h3>Course Invitation Code</h3>
+ <p style="font-size: larger;">Your Course Invitation Code is: <strong>${course.passkey}</strong>
+ <span style="margin-left: 16;">
+ <input type="submit" name="action_change_code" value="Select a new code"/>
+ </span>
+ </p>
+ <p>This invitation code will enable your students to join this
+ course site. Share it only with your students: anyone who has
+ the code can join your site.</p>
+ <p>You may change the code at any time. This will not block
+ students who have already joined, but will prevent new students
+ from joining with the old code.</p>
+ </div>
+ <div id="STUDT_panel" class="specific">
+ <h3>Course section numbers</h3>
+ <?python
+ current_sections = course.sections()
+ my_sections = [s for s in models.campus.sections_taught_by(user.username) if not s in current_sections]
+ ct_sections = [s for s in models.campus.sections_for_code_and_term(course.code, course.term.code) \
+ if not s in current_sections]
+ encode = lambda t,c,s: models.section_encode_safe((t,c,s))
+ ?>
+ <table class="pagetable">
+ <thead>
+ <tr>
+ <th>Associated section</th><th>Remove?</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr py:for="(term, code, sec) in current_sections">
+ <td>${code}, section ${sec}, in term ${term}</td>
+ <td><input type="checkbox" name="remove_section_${encode(term,code,sec)}"/></td>
+ </tr>
+ </tbody>
+ </table>
+ <p>Add section:
+ <select name="add_section">
+ <option value="">--------</option>
+ <optgroup py:for="label, sections in ((_('My Courses'), my_sections), (_('%s in term %s') % (course.code, course.term), ct_sections))"
+ label="${label}" py:if="sections">
+ <option py:for="(term, code, sec) in sections"
+ value="${encode(term,code,sec)}">
+ ${code}, section ${sec}, in term ${term}
+ </option>
+ </optgroup>
+ </select>
+ </p>
+ </div>
+ <p><input type="submit" name="action_save_student" value="Save changes to student access"/></p>
+
+ <div class="gap"/>
+ <h2>Class List</h2>
+ <p>The following users have student-level access in this course site.</p>
+ <ol>
+ <li py:for="student in course.get_students()">
+ ${student.get_full_name()} (${student.email})
+ </li>
+ </ol>
+ </form>
+ <div class="gap"/>
+</body>
+</html>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<feed xmlns="http://www.w3.org/2005/Atom"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
- <title>Reserves (${feed_type}) for ${course.list_display()}</title>
- <link href="${root}${course.course_url()}"
- rel="alternate"/>
- <link href="${root}${course.course_url('feeds/' + feed_type)}"
- rel="self"/>
- <id>${root}${course.course_url('feeds/') + feed_type}</id>
- <updated>${lastmod.strftime("%Y-%m-%dT%H:%M:%SZ")}</updated>
- <entry py:for="item in items">
- <title>${render_title(item)}</title>
- <link href="${root}${item.item_url(force_local_url=True)}"
- rel="alternate"></link>
- <updated>${item.last_modified.strftime("%Y-%m-%dT%H:%M:%SZ")}</updated>
- <id>${root}${item.item_url('')}</id>
- <summary type="html">${item.get_item_type_display()}.
- <div py:if="item.item_type=='HEADING'" py:strip="True">
- Contains ${len(models.Item.objects.filter(parent_heading=item))} items.
- </div>
- </summary>
- </entry>
-</feed>
+++ /dev/null
-<?python
-title = _('Available Feeds')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
- <xi:include href="../master.xhtml"/>
- <xi:include href="../components/course.xhtml"/>
- <head>
- <title>${title}</title>
- <script type="text/javascript" src="${ROOT}/static/menublocks.js"/>
- </head>
- <body>
- ${course_banner(course)}
- <h2>Available Feeds</h2>
- <ul>
- <li><a href="recent-changes">Recent changes</a></li>
- <li><a href="top-level">Top level items in this site</a></li>
- <li><a href="tree">Tree-walk of all items in this site</a></li>
- </ul>
- </body>
-</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <title>Reserves (${feed_type}) for ${course.list_display()}</title>
+ <link href="${root}${course.course_url()}"
+ rel="alternate"/>
+ <link href="${root}${course.course_url('feeds/' + feed_type)}"
+ rel="self"/>
+ <id>${root}${course.course_url('feeds/') + feed_type}</id>
+ <updated>${lastmod.strftime("%Y-%m-%dT%H:%M:%SZ")}</updated>
+ <entry py:for="item in items">
+ <title>${render_title(item)}</title>
+ <link href="${root}${item.item_url(force_local_url=True)}"
+ rel="alternate"></link>
+ <updated>${item.last_modified.strftime("%Y-%m-%dT%H:%M:%SZ")}</updated>
+ <id>${root}${item.item_url('')}</id>
+ <summary type="html">${item.get_item_type_display()}.
+ <div py:if="item.item_type=='HEADING'" py:strip="True">
+ Contains ${len(models.Item.objects.filter(parent_heading=item))} items.
+ </div>
+ </summary>
+ </entry>
+</feed>
--- /dev/null
+<?python
+title = _('Available Feeds')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="../master.xhtml"/>
+ <xi:include href="../components/course.xhtml"/>
+ <head>
+ <title>${title}</title>
+ <script type="text/javascript" src="${ROOT}/static/menublocks.js"/>
+ </head>
+ <body>
+ ${course_banner(course)}
+ <h2>Available Feeds</h2>
+ <ul>
+ <li><a href="recent-changes">Recent changes</a></li>
+ <li><a href="top-level">Top level items in this site</a></li>
+ <li><a href="tree">Tree-walk of all items in this site</a></li>
+ </ul>
+ </body>
+</html>
+++ /dev/null
-<?python
-title = _('My Reserves')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="master.xhtml"/>
-<head>
- <title>${title}</title>
-</head>
-<body>
- <h1>${title}</h1>
- <?python
- if user.is_authenticated():
- my_courses = user.reading_lists()
- else:
- my_courses = []
- ?>
- <p py:if="not my_courses">You are not part of any course reserves at this time.</p>
- <p py:for="course in my_courses" style="font-size: large;">
- <a href="${course.id}/">${course.list_display()}</a>
- </p>
- <div class="gap"/>
- <div class="itemadd">
- <p py:if="user.can_create_reading_lists()"><a href="new/">Create a new course reserves list</a></p>
- <p><a href="invitation/">Join a reserves list using an Invitation Code</a></p>
- </div>
-</body>
-</html>
--- /dev/null
+<?python
+title = _('My Reserves')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="master.xhtml"/>
+<head>
+ <title>${title}</title>
+</head>
+<body>
+ <h1>${title}</h1>
+ <?python
+ if user.is_authenticated():
+ my_courses = user.reading_lists()
+ else:
+ my_courses = []
+ ?>
+ <p py:if="not my_courses">You are not part of any course reserves at this time.</p>
+ <p py:for="course in my_courses" style="font-size: large;">
+ <a href="${course.id}/">${course.list_display()}</a>
+ </p>
+ <div class="gap"/>
+ <div class="itemadd">
+ <p py:if="user.can_create_reading_lists()"><a href="new/">Create a new course reserves list</a></p>
+ <p><a href="invitation/">Join a reserves list using an Invitation Code</a></p>
+ </div>
+</body>
+</html>
+++ /dev/null
-<?python
-title = _('Open Courses')
-?>
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
-<xi:include href="master.xhtml"/>
-<xi:include href="paginate.xhtml"/>
-<head>
- <title>${title}</title>
- <script type="text/javascript">
- <!-- !This ought to be in paginate.xhtml, not here. how to do? -->
- $(function() { $('.pagetable').tablesorter(); });
- </script>
-</head>
-<body>
- <h1>${title}</h1>
- <tr py:def="pageheader()">
- <th>Term</th><th>Code</th><th>Title</th>
- </tr>
- <span py:def="pagerow(item)">
- <td>${item.term}</td>
- <td><a href="../course/${item.id}/">${item.code}</a></td>
- <td><a href="../course/${item.id}/">${item.title}</a></td>
- </span>
- <!--
- ${pagetable(paginator, start, count, pagerow, pageheader)}
- -->
- ${pagetable(paginator, count, pagerow, pageheader)}
-</body>
-</html>
--- /dev/null
+<?python
+title = _('Open Courses')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="master.xhtml"/>
+<xi:include href="paginate.xhtml"/>
+<head>
+ <title>${title}</title>
+ <script type="text/javascript">
+ <!-- !This ought to be in paginate.xhtml, not here. how to do? -->
+ $(function() { $('.pagetable').tablesorter(); });
+ </script>
+</head>
+<body>
+ <h1>${title}</h1>
+ <tr py:def="pageheader()">
+ <th>Term</th><th>Code</th><th>Title</th>
+ </tr>
+ <span py:def="pagerow(item)">
+ <td>${item.term}</td>
+ <td><a href="../course/${item.id}/">${item.code}</a></td>
+ <td><a href="../course/${item.id}/">${item.title}</a></td>
+ </span>
+ <!--
+ ${pagetable(paginator, start, count, pagerow, pageheader)}
+ -->
+ ${pagetable(paginator, count, pagerow, pageheader)}
+</body>
+</html>
--- /dev/null
+<?python
+title = '%s: %s (%s)' % (course.code, course.title, course.term)
+item_tree = course.item_tree()
+is_editor = course.can_edit(request.user)
+is_joinable = course.is_joinable_by(request.user)
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="master.xhtml"/>
+ <xi:include href="components/course.xhtml"/>
+ <head>
+ <title>${title}</title>
+ <script type="text/javascript" src="${ROOT}/static/menublocks.js"/>
+ </head>
+ <body>
+ ${course_banner(course)}
+ <p py:if="not item_tree">
+ There are no items associated with this course yet.
+ </p>
+ <div id="sidepanel">
+ <div py:if="is_editor" id="edit_course" class="little_action_panel">
+ <a href="${course.course_url()}edit/">Edit course details</a>
+ </div>
+ <div py:if="is_joinable">
+ <a href="${course.course_url()}join/">Join this course</a>
+ </div>
+ <div id="showtree_panel" class="little_action_panel">
+ <a href="javascript:doToggleItemTree();">Show/hide tree</a>
+ </div>
+ <div py:if="is_editor">${item_resequence_panel()}</div>
+ </div>
+ <div id="treepanel">
+ ${show_tree(item_tree, edit=is_editor)}
+ </div>
+ <div py:if="is_editor">${add_subs()}</div>
+
+
+ </body>
+</html>
--- /dev/null
+<?python
+title = _('Join a course using an Invitation Code')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="master.xhtml"/>
+<head>
+ <title>${title}</title>
+ <script>
+ $(function() {
+ $('#code').focus();
+ });
+ </script>
+</head>
+<body>
+ <h1>${title}</h1>
+ <p>Your instructor may have provided you with an Invitation Code,
+ which will give you access to your course's reserves. Enter the
+ invitation code below to continue. Note that not all courses require
+ an invitation code; contact your instructor or the library staff for
+ more information.</p>
+ <div class="errors" py:if="error">${error}</div>
+ <form action="." method="POST">
+ <table class="metadata_table">
+ <tr><th>Invitation Code:</th><td><input type="text" id="code" name="code" size="30" value="${code}"/></td></tr>
+ </table>
+<p><input type="submit" value="Continue"/> ${go_back_link()}</p>
+ </form>
+ <div class="gap"/>
+</body>
+</html>
--- /dev/null
+<?python
+title = _('Join this course?')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="master.xhtml"/>
+ <xi:include href="components/course.xhtml"/>
+ <head>
+ <title>${title}</title>
+ <script type="text/javascript" src="${ROOT}/static/menublocks.js"/>
+ </head>
+ <body>
+ ${course_banner(course)}
+ <h2>${title}</h2>
+
+ <p>If you choose to join this course, it will be added to your "My
+ Courses" list.</p>
+ <form action="." method="POST">
+ <p><input type="submit" value="Yes, join this course"/> ${go_back_link()}</p>
+ </form>
+
+ </body>
+</html>
--- /dev/null
+<?python
+title = defined('custom_title') and custom_title or _('Courses')
+?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+<xi:include href="master.xhtml"/>
+<xi:include href="paginate.xhtml"/>
+<head>
+ <title>${title}</title>
+ <script type="text/javascript">
+ <!-- !This ought to be in paginate.xhtml, not here. how to do? -->
+ $(function() { $('.pagetable').tablesorter(); });
+ </script>
+</head>
+<body>
+ <h1>${title}</h1>
+ <tr py:def="pageheader()">
+ <th>Name</th>
+ </tr>
+ <span py:def="pagerow(item)">
+ <td><a href="${item.course_url()}">${item.title}, ${item.term}</a></td>
+ </span>
+ ${pagetable(paginator, count, pagerow, pageheader)}
+</body>
+</html>