From: gfawcett Date: Sun, 8 Mar 2009 01:48:42 +0000 (+0000) Subject: external course-lookup; continuing work on new-course-site creation. X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=d80e239031d39fc65161db66fe638b0541d2ecc7;p=Syrup.git 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. git-svn-id: svn://svn.open-ils.org/ILS-Contrib/servres/trunk@142 6d9bc8c9-1ec2-4278-b937-99fde70a366f --- diff --git a/conifer/custom/course_codes.py b/conifer/custom/course_codes.py new file mode 100644 index 0000000..0759ea6 --- /dev/null +++ b/conifer/custom/course_codes.py @@ -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])) + diff --git a/conifer/static/add_new_course.js b/conifer/static/add_new_course.js new file mode 100644 index 0000000..f89db1d --- /dev/null +++ b/conifer/static/add_new_course.js @@ -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); diff --git a/conifer/syrup/models.py b/conifer/syrup/models.py index 0c27401..7266eee 100644 --- a/conifer/syrup/models.py +++ b/conifer/syrup/models.py @@ -5,7 +5,7 @@ from django.contrib.auth import get_backends 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 @@ class Course(m.Model): ('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 @@ class NewsItem(m.Model): 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 @@ class NewsItem(m.Model): return Markup(self.body) elif self.encoding == 'markdown': return Markup(do_markdown(self.body)) - diff --git a/conifer/syrup/urls.py b/conifer/syrup/urls.py index 9b9e4d2..fee1c3d 100644 --- a/conifer/syrup/urls.py +++ b/conifer/syrup/urls.py @@ -10,6 +10,7 @@ urlpatterns = patterns('conifer.syrup.views', (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_courses'), (r'^prefs/$', 'user_prefs'), diff --git a/conifer/syrup/views.py b/conifer/syrup/views.py index 6ae4380..57e6b17 100644 --- a/conifer/syrup/views.py +++ b/conifer/syrup/views.py @@ -12,6 +12,8 @@ from django.db.models import Q 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 @@ def browse_courses(request, browse_option=''): 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,11 +120,44 @@ def my_courses(request): 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) @@ -386,7 +422,7 @@ def search(request, in_course=None): 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 diff --git a/conifer/templates/add_new_course.xhtml b/conifer/templates/add_new_course.xhtml index ba74a6f..cdba3f4 100644 --- a/conifer/templates/add_new_course.xhtml +++ b/conifer/templates/add_new_course.xhtml @@ -7,17 +7,23 @@ title = _('Add a new course site') ${title} +