[open-ils-commits] r142 - in servres/trunk/conifer: custom static syrup templates (gfawcett)

svn at svn.open-ils.org svn at svn.open-ils.org
Sat Mar 7 20:48:43 EST 2009


Author: gfawcett
Date: 2009-03-07 20:48:42 -0500 (Sat, 07 Mar 2009)
New Revision: 142

Added:
   servres/trunk/conifer/custom/course_codes.py
   servres/trunk/conifer/static/add_new_course.js
Modified:
   servres/trunk/conifer/syrup/models.py
   servres/trunk/conifer/syrup/urls.py
   servres/trunk/conifer/syrup/views.py
   servres/trunk/conifer/templates/add_new_course.xhtml
Log:
external course-lookup; continuing work on new-course-site creation.

custom/course_codes.py generalizes the validation of course-codes and
lookup (in an external system) of information related to course-codes.


Added: servres/trunk/conifer/custom/course_codes.py
===================================================================
--- servres/trunk/conifer/custom/course_codes.py	                        (rev 0)
+++ servres/trunk/conifer/custom/course_codes.py	2009-03-08 01:48:42 UTC (rev 142)
@@ -0,0 +1,132 @@
+# Validation and lookup of course codes.
+
+# This modules specifies an "course-code interface" and a null
+# implementation of that interface. If your local system has rules for
+# valid course codes, and a mechanism for looking up details of these
+# codes, you can implement the interface according to your local
+# rules.
+
+
+# ------------------------------------------------------------
+# Overview and definitions
+
+# A course code identifies a specific course offering. Course codes
+# map 1:N onto formal course titles: by looking up a code, we can
+# derive a formal title (in theory, though it may not be possible for
+# external reasons).
+
+# A course code is insufficient to specify a class list: we need a
+# course section for that. A section ties a course code and term to an
+# instructor(s) and a list of students.
+
+# Course codes may have cross-listings, i.e., other codes which refer
+# to the same course, but which appear under a different department
+# for various academic purposes. In our system, we make no attempt to
+# subordinate cross-listings to a "primary" course code.
+
+
+#------------------------------------------------------------
+# Notes on the interface
+#
+# The `course_code_is_valid` function will be used ONLY if
+# course_code_list() returns None (it is a null implementation). If a
+# course-list is available, the system will use a membership test for
+# course-code validity.
+#
+# `course_code_lookup_title` will be used ONLY if `course_code_list`
+# is implemented.
+#
+#
+# "types" of the interface members
+#
+# course_code_is_valid       (string) --> boolean.
+# course_code_example        : a string constant.
+# course_code_list           () --> list of strings
+# course_code_lookup_title   (string) --> string, or None.
+# course_code_cross_listings (string) --> list of strings
+#
+# For each member, you MUST provide either a valid implementation, or
+# set the member to None. See the null implementation below.
+
+#------------------------------------------------------------
+# Implementations
+
+# ------------------------------------------------------------ 
+# Here is a 'null implementation' of the course-code interface. No
+# validation is done, nor are lookups.
+#
+#    course_code_is_valid       = None  # anything is OK;
+#    course_code_example        = None  # no examples;
+#    course_code_lookup_title   = None  # no codes to list;
+#    course_code_cross_listings = None  # no cross lists.
+
+# ------------------------------------------------------------
+# This one specifies a valid course-code format using a regular
+# expression, and offers some example codes, but does not have a
+# lookup system.
+#
+#    import re
+#
+#    def course_code_is_valid(course_code):
+#        pattern = re.compile(r'^\d{2}-\d{3}$')
+#        return bool(pattern.match(course_code))
+#
+#    course_code_example        = '55-203; 99-105'
+#
+#    course_code_list           = None
+#    course_code_lookup_title   = None
+#    course_code_cross_listings = None
+
+
+
+# ------------------------------------------------------------
+# This is a complete implementation, based on a hard-coded list of
+# course codes and titles, and two cross-listed course codes.
+#
+#    _codes = [('ENG100', 'Introduction to English'),
+#              ('ART108', 'English: An Introduction'),
+#              ('FRE238', 'Modern French Literature'),
+#              ('WEB203', 'Advanced Web Design'),]
+#
+#    _crosslists = set(['ENG100', 'ART108'])
+#
+#    course_code_is_valid = None
+#    course_code_example = 'ENG100; FRE238'
+#
+#    def course_code_list():
+#        return [a for (a,b) in _codes]
+#
+#    def course_code_lookup_title(course_code):
+#        return dict(_codes).get(course_code)
+#
+#    def course_code_cross_listings(course_code):
+#        if course_code in _crosslists:
+#            return list(_crosslists - set([course_code]))
+
+
+# ------------------------------------------------------------
+# Provide your own implementation below.
+
+
+_codes = [('ENG100', 'Introduction to English'),
+          ('ART108', 'English: An Introduction'),
+          ('FRE238', 'Modern French Literature'),
+          ('WEB203', 'Advanced Web Design'),]
+
+_crosslists = set(['ENG100', 'ART108'])
+
+
+course_code_is_valid = None
+
+course_code_example = 'ENG100; FRE238'
+
+def course_code_list():
+    return [a for (a,b) in _codes]
+
+def course_code_lookup_title(course_code):
+    return dict(_codes).get(course_code)
+
+def course_code_cross_listings(course_code):
+    if course_code in _crosslists:
+        return list(_crosslists - set([course_code]))
+

Added: servres/trunk/conifer/static/add_new_course.js
===================================================================
--- servres/trunk/conifer/static/add_new_course.js	                        (rev 0)
+++ servres/trunk/conifer/static/add_new_course.js	2009-03-08 01:48:42 UTC (rev 142)
@@ -0,0 +1,16 @@
+function do_init() {
+    if ($('#id_code')[0].tagName == 'SELECT') {
+	// code is a SELECT, so we add a callback to lookup titles.
+	$('#id_code').change(function() {
+	    $('#id_title')[0].disabled=true;
+	    $.getJSON('ajax_title', {course_code: $(this).val()},
+		      function(resp) {
+			  $('#id_title').val(resp.title)
+			  $('#id_title')[0].disabled=false;
+
+		      });
+	});
+    }
+}
+
+$(do_init);

Modified: servres/trunk/conifer/syrup/models.py
===================================================================
--- servres/trunk/conifer/syrup/models.py	2009-03-06 17:25:22 UTC (rev 141)
+++ servres/trunk/conifer/syrup/models.py	2009-03-08 01:48:42 UTC (rev 142)
@@ -5,7 +5,7 @@
 from datetime import datetime
 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.
 import re
 
 def highlight(text, phrase,
@@ -159,11 +159,13 @@
                                     ('CLOSE', _('Accessible only to course owners'))],
                          default='CLOSE')
 
-    # For sites that use a passphrase as an invitation.
-    passkey = m.CharField(db_index=True, unique=True, blank=True, null=True)
+    # For sites that use a passphrase as an invitation (STUDT access).
+    passkey = m.CharField(db_index=True, unique=True, blank=True, null=True,
+                          max_length=255)
 
-    # For sites that have registration-lists from an external system.
-    enrol_codes  = m.CharField(_('Registrar keys for class lists, pipe-separated'),
+    # For sites that have registration-lists from an external system
+    # (STUDT access).
+    enrol_codes  = m.CharField(_('Registrar keys for class lists'),
                                max_length=4098, 
                                blank=True, null=True)
     def __unicode__(self):
@@ -414,7 +416,7 @@
                            choices = (('plain', _('plain text')),
                                       ('html', _('HTML')),
                                       ('markdown', _('Markdown'))),
-                           default = 'html')
+                           default = 'plain')
 
     def __unicode__(self):
         return u'%s (%s)' % (self.subject, self.published)
@@ -426,4 +428,3 @@
             return Markup(self.body)
         elif self.encoding == 'markdown':
             return Markup(do_markdown(self.body))
-            

Modified: servres/trunk/conifer/syrup/urls.py
===================================================================
--- servres/trunk/conifer/syrup/urls.py	2009-03-06 17:25:22 UTC (rev 141)
+++ servres/trunk/conifer/syrup/urls.py	2009-03-08 01:48:42 UTC (rev 142)
@@ -10,6 +10,7 @@
     (r'^$', 'welcome'),                       
     (r'^course/$', 'my_courses'),
     (r'^course/new/$', 'add_new_course'),
+    (r'^course/new/ajax_title$', 'add_new_course_ajax_title'),
     (r'^browse/$', 'browse_courses'),
     (r'^browse/(?P<browse_option>.*)/$', 'browse_courses'),
     (r'^prefs/$', 'user_prefs'),

Modified: servres/trunk/conifer/syrup/views.py
===================================================================
--- servres/trunk/conifer/syrup/views.py	2009-03-06 17:25:22 UTC (rev 141)
+++ servres/trunk/conifer/syrup/views.py	2009-03-08 01:48:42 UTC (rev 142)
@@ -12,7 +12,9 @@
 from datetime import datetime
 from generics import *
 from gettext import gettext as _ # fixme, is this the right function to import?
+from django.utils import simplejson
 
+
 #------------------------------------------------------------
 # Authentication
 
@@ -102,7 +104,8 @@
             page_num=page_num,
             count=count)
     elif browse_option == 'courses':
-        paginator = Paginator(models.Course.objects.filter(active=True), count)
+        # fixme, course filter should not be (active=True) but based on user identity.
+        paginator = Paginator(models.Course.objects.all(), count)
 
         return g.render('courses.xhtml', paginator=paginator,
             page_num=page_num,
@@ -117,12 +120,45 @@
 class NewCourseForm(ModelForm):
     class Meta:
         model = models.Course
+    def clean_code(self):
+        v = (self.cleaned_data.get('code') or '').strip()
+        is_valid_func = models.course_codes.course_code_is_valid
+        if (not is_valid_func) or is_valid_func(v):
+            return v
+        else:
+            raise ValidationError, _('invalid course code')
 
+# hack the new-course form if we have course-code lookup
+COURSE_CODE_LIST = bool(models.course_codes.course_code_list)
+COURSE_CODE_LOOKUP_TITLE = bool(models.course_codes.course_code_lookup_title)
+
+if COURSE_CODE_LIST:
+    from django.forms import Select
+    course_list = models.course_codes.course_code_list()
+    choices = [(a,a) for a in course_list]
+    choices.sort()
+    empty_label = u'---------'
+    choices.insert(0, (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):
-    form = NewCourseForm(instance=models.Course())
-    return g.render('add_new_course.xhtml', **locals())
+    example = models.course_codes.course_code_example
+    if request.method != 'POST':
+        form = NewCourseForm(instance=models.Course())
+        return g.render('add_new_course.xhtml', **locals())
+    else:
+        form = NewCourseForm(request.POST, instance=models.Course())
+        if not form.is_valid():
+            return g.render('add_new_course.xhtml', **locals())
 
+def add_new_course_ajax_title(request):
+    course_code = request.GET['course_code']
+    title = models.course_codes.course_code_lookup_title(course_code)
+    return HttpResponse(simplejson.dumps({'title':title}))
+
 def course_detail(request, course_id):
     course = get_object_or_404(models.Course, pk=course_id)
     if course.moderated and request.user.is_anonymous():
@@ -386,7 +422,7 @@
             course_query = get_query(query_string, ['title', 'department__name'])
             print 'course_query'
             print course_query
-            course_results = models.Course.objects.filter(course_query).filter(active=True)
+            course_results = models.Course.objects.filter(course_query).all()
             # course_list = models.Course.objects.filter(course_query).filter(active=True).order_by('title')[0:5]
             course_list = course_results.order_by('title')[0:5]
             #there might be a better way of doing this, though instr and course tables should not be expensive to query

Modified: servres/trunk/conifer/templates/add_new_course.xhtml
===================================================================
--- servres/trunk/conifer/templates/add_new_course.xhtml	2009-03-06 17:25:22 UTC (rev 141)
+++ servres/trunk/conifer/templates/add_new_course.xhtml	2009-03-08 01:48:42 UTC (rev 142)
@@ -7,17 +7,23 @@
 <xi:include href="master.xhtml"/>
 <head>
   <title>${title}</title>
+  <script type="text/javascript" src="/static/add_new_course.js"/>
 </head>
 <body>
   <h1>${title}</h1>
   <form action="." method="POST">
+    <tr py:def="field_row(field)">
+      <th>${field.label}</th><td>${Markup(field)} !!!</td></tr>
   <fieldset>
     <legend>General description</legend>
-  <table class="formtable">
-    <tr><th>Course code</th><td><input type="text"/></td></tr>
-    <tr><th>Title</th><td><input type="text"/></td></tr>
+    <table class="formtable">
+    <tr><th>Course code</th><td>${Markup(form.code)}</td>
+    <td py:if="example">e.g. ${example}</td></tr>
+    <tr><th>Title</th><td>${Markup(form.title)} <span>${form.title.errors}</span></td></tr>
     <tr><th>Term</th><td>${Markup(form.term)}</td></tr>
-    <tr><th>Department</th><td>${Markup(form.department)}</td></tr>
+    <tr><th>Access</th><td>${Markup(form.access)}</td></tr>
+    ${field_row(form.department)}
+    <!-- <tr><th>Department</th><td>${Markup(form.department)} ${errorlist(form.department)}</td></tr> -->
   </table>
   </fieldset>
   <fieldset>



More information about the open-ils-commits mailing list