[open-ils-commits] r157 - in servres/trunk/conifer: custom syrup templates (gfawcett)
svn at svn.open-ils.org
svn at svn.open-ils.org
Sun Mar 8 22:05:27 EDT 2009
Author: gfawcett
Date: 2009-03-08 22:05:26 -0400 (Sun, 08 Mar 2009)
New Revision: 157
Added:
servres/trunk/conifer/custom/course_sections.py
Modified:
servres/trunk/conifer/syrup/models.py
servres/trunk/conifer/syrup/views.py
servres/trunk/conifer/templates/edit_course_permissions.xhtml
Log:
preliminary support for course-sections (from an external data source).
See conifer/custom/course_sections.py for the course-section
interface. It's primarily used in the edit-course-permissions handler;
it needs more testing, but appears pretty decent so far.
Added: servres/trunk/conifer/custom/course_sections.py
===================================================================
--- servres/trunk/conifer/custom/course_sections.py (rev 0)
+++ servres/trunk/conifer/custom/course_sections.py 2009-03-09 02:05:26 UTC (rev 157)
@@ -0,0 +1,147 @@
+# Operations on course-section identifiers
+
+# A course section is an instance of a course offered in a term.
+
+# A section is specified by a 'section-id', a 3-tuple (course-code,
+# term, section-code), where section-code is usually a short
+# identifier (e.g., "1" representing "section 1 in this term"). Note
+# that multiple sections of the same course are possible in a given
+# term.
+
+# Within the reserves system, a course-site can be associated with
+# zero or more sections, granting access to students in those
+# sections. We need two representations of a section-id.
+
+# The section_tuple_delimiter must be a string which will never appear
+# in a course-code, term, or section-code in your database. It may be
+# a nonprintable character (e.g. NUL or CR). It is used to delimit
+# parts of the tuples in a course's database record.
+
+#------------------------------------------------------------
+# Notes on the interface
+#
+# 'sections_taught_by(username)' returns a set of sections for which
+# username is an instructor. It is acceptable if 'sections_taught_by'
+# only returns current and future sections: historical information is
+# not required by the reserves system.
+#
+# It is expected that the reserves system will be able to resolve any
+# usernames into user records. If there are students on a section-list
+# which do not resolve into user accounts, they will probably be
+# ignored and will not get access to their course sites. So if you're
+# updating your users and sections in a batch-run, you might want to
+# update your users first.
+#
+#------------------------------------------------------------
+# Implementations
+
+# The reserves system will work with a null-implementation of the
+# course-section interface, but tasks related to course-sections will
+# be unavailable.
+
+# ------------------------------------------------------------
+# The null implementation:
+#
+# sections_tuple_delimiter = None
+# sections_taught_by = None
+# students_in = None
+# instructors_in = None
+# sections_for_code_and_term = None
+
+# ------------------------------------------------------------
+#
+# The minimal non-null implementation. At the least you must provide
+# sections_tuple_delimiter and students_in. Lookups for instructors
+# may be skipped. Note that sections passed to students_in are
+# (term, course-code, section-code) tuples (string, string, string).
+#
+# sections_tuple_delimiter = '|'
+#
+# def students_in(*sections):
+# ...
+# return set_of_usernames
+#
+# instructors_in = None
+# sections_for_code_and_term = None
+
+# ------------------------------------------------------------
+# A complete implementation, with a static database.
+
+# sections_tuple_delimiter = '|'
+#
+# _db = [
+# ('fred', ('2009W', 'ENG203', '1'), 'jim joe jack ellen ed'),
+# ('fred', ('2009W', 'ENG327', '1'), 'ed paul bill'),
+# ('bill', ('2009S', 'BIO323', '1'), 'alan june jack'),
+# ('bill', ('2009S', 'BIO323', '2'), 'emmet'),
+# ]
+#
+# def sections_taught_by(username):
+# return set([s[1] for s in _db if s[0] == username])
+#
+# def students_in(*sections):
+# def inner():
+# for instr, sec, studs in _db:
+# if sec in sections:
+# for s in studs.split(' '):
+# yield s
+# return set(inner())
+#
+# def instructors_in(*sections):
+# def inner():
+# for instr, sec, studs in _db:
+# if sec in sections:
+# yield instr
+# return set(inner())
+#
+# def sections_for_code_and_term(code, term):
+# return [(t, c, s) for (instr, (t, c, s), ss) in _db \
+# if c == code and t == term]
+#
+
+
+# ------------------------------------------------------------
+# Provide your own implementation below.
+
+sections_tuple_delimiter = None
+sections_taught_by = None
+students_in = None
+instructors_in = None
+sections_for_code_and_term = None
+
+
+
+# ------------------------------------------------------------
+# a temporary implementation, while I write up the UI.
+
+sections_tuple_delimiter = '|'
+
+_db = [
+ ('fred', ('2009W', 'ENG203', '1'), 'jim joe jack ellen ed'),
+ ('fred', ('2009W', 'ENG327', '1'), 'ed paul bill'),
+ ('graham', ('2009S', 'ART108', '1'), 'alan june jack'),
+ ('graham', ('2009S', 'ART108', '2'), 'emmet'),
+ ('graham', ('2009S', 'ART108', '3'), 'freda hugo bill'),
+]
+
+def sections_taught_by(username):
+ return set([s[1] for s in _db if s[0] == username])
+
+def students_in(*sections):
+ def inner():
+ for instr, sec, studs in _db:
+ if sec in sections:
+ for s in studs.split(' '):
+ yield s
+ return set(inner())
+
+def instructors_in(*sections):
+ def inner():
+ for instr, sec, studs in _db:
+ if sec in sections:
+ yield instr
+ return set(inner())
+
+def sections_for_code_and_term(code, term):
+ return [(t, c, s) for (instr, (t, c, s), ss) in _db \
+ if c == code and t == term]
Modified: servres/trunk/conifer/syrup/models.py
===================================================================
--- servres/trunk/conifer/syrup/models.py 2009-03-09 02:05:21 UTC (rev 156)
+++ servres/trunk/conifer/syrup/models.py 2009-03-09 02:05:26 UTC (rev 157)
@@ -6,6 +6,7 @@
from genshi import Markup
from gettext import gettext as _ # fixme, is this the right function to import?
from conifer.custom import course_codes # fixme, not sure if conifer.custom is a good parent.
+from conifer.custom import course_sections # fixme, not sure if conifer.custom is a good parent.
import re
import random
@@ -251,6 +252,44 @@
self.passkey = key
break
+ def sections(self):
+ delim = course_sections.sections_tuple_delimiter
+ if not delim:
+ return []
+ else:
+ def inner():
+ parts = self.enrol_codes.split(delim)
+ while len(parts) > 2:
+ yield tuple(parts[:3])
+ del parts[:3]
+ return set(inner())
+
+ def add_sections(self, *sections):
+ current = self.sections()
+ sections = set(sections).union(current)
+ self.enrol_codes = _merge_sections(sections)
+
+ def drop_sections(self, *sections):
+ current = self.sections()
+ sections = current - set(sections)
+ self.enrol_codes = _merge_sections(sections)
+
+ def get_students(self):
+ return User.objects.filter(member__course__exact=self, member__role__exact='STUDT') \
+ .order_by('last_name', 'first_name')
+
+def _merge_sections(secs):
+ delim = course_sections.sections_tuple_delimiter
+ return delim.join(delim.join(sec) for sec in secs)
+
+def section_decode_safe(secstring):
+ if not secstring:
+ return None
+ return tuple(secstring.decode('base64').split(course_sections.sections_tuple_delimiter))
+
+def section_encode_safe(section):
+ return course_sections.sections_tuple_delimiter.join(section).encode('base64').strip()
+
class Member(m.Model):
course = m.ForeignKey(Course)
user = m.ForeignKey(User)
Modified: servres/trunk/conifer/syrup/views.py
===================================================================
--- servres/trunk/conifer/syrup/views.py 2009-03-09 02:05:21 UTC (rev 156)
+++ servres/trunk/conifer/syrup/views.py 2009-03-09 02:05:26 UTC (rev 157)
@@ -226,11 +226,15 @@
def edit_course_permissions(request, course_id):
course = get_object_or_404(models.Course, pk=course_id)
- choose_access = django.forms.Select(choices=[
+ choices = [
(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'))])
+ (u'LOGIN', _(u'All Reserves patrons'))]
+ if models.course_sections.sections_tuple_delimiter is None:
+ del choices[1] # no STUDT support.
+ choose_access = django.forms.Select(choices=choices)
+
if request.method != 'POST':
return g.render('edit_course_permissions.xhtml', **locals())
else:
@@ -288,10 +292,31 @@
# 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.course_sections.students_in(*course.sections())
+ for name in student_names:
+ user = models.maybe_initialize_user(name)
+ if user:
+ mbr = models.Member.objects.create(course=course, user=user,
+ role='STUDT', provided=True)
+ mbr.save()
+ else:
+ pass
course.save()
- if course.access == u'STUDT':
- raise NotImplementedError, 'No course sections yet! Coming soon.'
- return HttpResponseRedirect('.')
+ return HttpResponseRedirect('.#student_access')
@instructors_only
def delete_course(request, course_id):
Modified: servres/trunk/conifer/templates/edit_course_permissions.xhtml
===================================================================
--- servres/trunk/conifer/templates/edit_course_permissions.xhtml 2009-03-09 02:05:21 UTC (rev 156)
+++ servres/trunk/conifer/templates/edit_course_permissions.xhtml 2009-03-09 02:05:26 UTC (rev 157)
@@ -64,16 +64,49 @@
</div>
<div id="STUDT_panel" class="specific">
<h3>Course section numbers</h3>
- <table>
- <tr py:for="x in range(3)"><td><input type="text"/></td></tr>
+ <?python
+ current_sections = course.sections()
+ my_sections = [s for s in models.course_sections.sections_taught_by(user.username) if not s in current_sections]
+ ct_sections = [s for s in models.course_sections.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>--------</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>
More information about the open-ils-commits
mailing list