[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