[open-ils-commits] r133 - in servres/trunk/conifer: . static syrup templates templates/admin templates/generic (gfawcett)
svn at svn.open-ils.org
svn at svn.open-ils.org
Sun Mar 1 23:09:20 EST 2009
Author: gfawcett
Date: 2009-03-01 23:09:18 -0500 (Sun, 01 Mar 2009)
New Revision: 133
Added:
servres/trunk/conifer/syrup/generics.py
servres/trunk/conifer/syrup/on_courses.txt
servres/trunk/conifer/templates/admin/
servres/trunk/conifer/templates/admin/index.xhtml
servres/trunk/conifer/templates/admin/term.xhtml
servres/trunk/conifer/templates/generic/
servres/trunk/conifer/templates/generic/delete.xhtml
servres/trunk/conifer/templates/generic/edit.xhtml
servres/trunk/conifer/templates/generic/index.xhtml
Modified:
servres/trunk/conifer/genshi_namespace.py
servres/trunk/conifer/static/main.css
servres/trunk/conifer/syrup/models.py
servres/trunk/conifer/syrup/urls.py
servres/trunk/conifer/syrup/views.py
servres/trunk/conifer/templates/tabbar.xhtml
Log:
Admin options for editing terms, deparments: generic indexes and forms.
The big change in this rev is introduction of the Django 'newforms'
system. Since we're using Genshi (not Django templating), newforms
seemed a bad fit. But I think I've got a fairly nice compromise
between Django and Genshi going on in my generics.py.
See the ModelForm subclasses in views.py: the Index attribute is
novel, and specific to the generics.py system.
There is still more to do here, notably figuring out how to handle
permissions and i18n.
Modified: servres/trunk/conifer/genshi_namespace.py
===================================================================
--- servres/trunk/conifer/genshi_namespace.py 2009-03-01 21:18:50 UTC (rev 132)
+++ servres/trunk/conifer/genshi_namespace.py 2009-03-02 04:09:18 UTC (rev 133)
@@ -9,3 +9,11 @@
# this probably ought to be a method on User, or another model class.
def instructor_url(instructor, suffix=''):
return '/syrup/instructor/%d/%s' % (instructor.id, suffix)
+
+
+def call_or_value(obj, dflt=None):
+ # This is used by the generics templates.
+ if callable(obj):
+ return obj() or dflt
+ else:
+ return obj or dflt
Modified: servres/trunk/conifer/static/main.css
===================================================================
--- servres/trunk/conifer/static/main.css 2009-03-01 21:18:50 UTC (rev 132)
+++ servres/trunk/conifer/static/main.css 2009-03-02 04:09:18 UTC (rev 133)
@@ -189,3 +189,6 @@
#coursebanner h1 { padding: 0; font-size: 110%; }
.breadcrumbs { margin: 8 8 8 0; }
+
+.errorlist { float: right; }
+.errorlist li { color: red; font-size: 90%; }
\ No newline at end of file
Added: servres/trunk/conifer/syrup/generics.py
===================================================================
--- servres/trunk/conifer/syrup/generics.py (rev 0)
+++ servres/trunk/conifer/syrup/generics.py 2009-03-02 04:09:18 UTC (rev 133)
@@ -0,0 +1,53 @@
+import conifer.genshi_support as g
+from django.http import HttpResponse, HttpResponseRedirect
+from django.http import HttpResponseForbidden
+from django.shortcuts import get_object_or_404
+from django.forms import ModelForm, ValidationError
+
+def generic_index(form):
+ assert hasattr(form, 'Index')
+ return g.render('generic/index.xhtml', form=form)
+
+def generic_edit(form, request, obj_id):
+ if obj_id == '0':
+ instance = None
+ else:
+ instance = get_object_or_404(form.Meta.model, pk=obj_id)
+ if request.method == 'GET':
+ form = form(instance=instance)
+ return g.render('generic/edit.xhtml', **locals())
+ else:
+ form = form(request.POST, instance=instance)
+ if not form.is_valid():
+ return g.render('generic/edit.xhtml', **locals())
+ else:
+ form.save()
+ return HttpResponseRedirect('../')
+
+def generic_delete(form, request, obj_id):
+ instance = get_object_or_404(models.Term, pk=obj_id)
+ if request.method != 'POST':
+ form = form(instance=instance)
+ return g.render('generic/delete.xhtml', **locals())
+ else:
+ instance.delete()
+ return HttpResponseRedirect('../')
+
+def generic_handler(form):
+ def handler(request, obj_id=None, action=None):
+ if obj_id is None and action is None:
+ return generic_index(form)
+ elif action is None:
+ return generic_edit(form, request, obj_id)
+ elif action == 'delete':
+ return generic_delete(form, request, obj_id)
+ return handler
+
+
+def strip_and_nonblank(fieldname):
+ def clean(self):
+ v = self.cleaned_data.get(fieldname) or ''
+ if not v.strip():
+ raise ValidationError('Cannot be blank.')
+ return v.strip()
+ return clean
Modified: servres/trunk/conifer/syrup/models.py
===================================================================
--- servres/trunk/conifer/syrup/models.py 2009-03-01 21:18:50 UTC (rev 132)
+++ servres/trunk/conifer/syrup/models.py 2009-03-02 04:09:18 UTC (rev 133)
@@ -127,7 +127,7 @@
# TERMS, COURSES, MEMBERSHIP
class Term(m.Model):
- code = m.CharField(max_length=16, blank=True, null=True)
+ code = m.CharField(max_length=16, blank=True, null=True, unique=True)
name = m.CharField(max_length=255)
start = m.DateField()
finish = m.DateField()
Added: servres/trunk/conifer/syrup/on_courses.txt
===================================================================
--- servres/trunk/conifer/syrup/on_courses.txt (rev 0)
+++ servres/trunk/conifer/syrup/on_courses.txt 2009-03-02 04:09:18 UTC (rev 133)
@@ -0,0 +1,161 @@
+# -*- mode: text; mode: auto-fill; -*-
+
+
+Fleshing out the course model
+--------------------------------------------------
+
+These are just some stream-of-thought notes; don't take them too
+seriously yet.
+
+
+
+We should capitalize on external data sources for course and
+registration information. At the same time, it must be possible to
+use Syrup without an external source, or in a mixed mode (where some
+courses are defined externally and others are ad-hoc).
+
+There will be local variations in the quality of course information.
+Possibly some can provide lists of known courses, but not registration
+information; others will know students enrolled but may not know
+instructors (or vice versa).
+
+So, in fact we have a number of granular external sources, and Syrup
+should operate with any combination of them.
+
+* list of terms (but allowing ad-hoc terms);
+
+* list of course codes and titles (not time- or term-related);
+
+* list of offerings (course-code offered in term);
+
+* list of sections (same course offered several times in one term,
+ and/or broken up into multiple subgroups. Some reserve courses may
+ aggregate some sections, excluding others). Sections are ultimately
+ the join-points between instructors and students, so we must handle
+ them well;
+
+* cross-listings (equivalent course-codes);
+
+* people (username/identifier, given, surname, email)
+
+* instructor and student relationships (fred teaches FRE233/2009W/01;
+ bill takes ESP125/2009W/03).
+
+
+Rolls-Royce Scenario
+--------------------------------------------------
+
+Shelley Smith wants to set up a reserves-site for the course she's
+teaching next term. She logs into reserves, clicks on My Courses, and
+clicks Add a New Course Site. The form asks her to pick a term (the
+current term is the default, all future terms are listed in a
+drop-down), and to pick a course. The system suggests the courses she
+is currently teaching; but this is for a future term, and she's not
+the instructor of record yet, so she picks the code and title from a
+drop-down list.
+
+She doesn't specify any sections, since she won't know that for a few
+months yet. The site remains unavailable to students until she does.
+
+Later, she clicks on Invite Students. It asks her to pick the
+course-sections she's teaching from a list. It knows her course is
+cross-listed; the cross-list sections also appear on the list.
+
+Once the sections are selected, and she presses Continue, the students
+are granted access.
+
+ Variation: staff-assistance
+ --------------------------------------------------
+
+ Shelley contacts Ed at the Reserves desk, and tells him she wants to
+ put some items on reserve. She's teaching MAC-100, Intro to Macrame,
+ section 2, in the upcoming term.
+
+ Ed clicks on Assist, and finds Shelley's in the people-list, and
+ selects her. He clicks on Shelley's Courses, then Add New Course,
+ and enters the term and picks MAC-100 from the list. He knows the
+ section so is able to add that right away. The system alerts him to
+ the cross-listed section, and he adds that too.
+
+ The site won't be ready for students until he (or Shelley) activates
+ the site later on.
+
+ Shelley gets an email letting her know the site is ready for
+ content.
+
+ Data sources
+ --------------------------------------------------
+
+ Shelley and Ed logged in against campus LDAP.
+
+ Shelley's current courses and
+
+
+
+Another take
+------------------------------------------------------------
+
+Shelley can set up the course well in advance, with a trivial working
+title, e.g. "Macrame". It's one of Shelley's courses, but no one else
+has access, and it's not associated with any term, etc.
+
+ Instructor: course-add
+ Library-Staff: course-add
+
+Later, when Shelley is ready to open her reserves to her students, she
+opens the site and hits Invite. What appears is a list of the course
+sections she is teaching:
+
+ MAC-100 section 02 -- Introduction to Macrame (58 members)
+ MAC-301 section 01 -- Macrame Advanced Studio (2 members)
+ LAB-203 section 01 -- Macrame in Labour Studies (8 members)
+
+She checkmarks the first and last section, and presses Invite.
+Automatically, her site is available to her students, but it also gets
+a proper title and course number: she is asked to pick either MAC-100
+or LAB-203 as the primary identifier for the site; the other becomes
+an alias.
+
+Later calls Ed -- she's talked with the instructor of Textiles 101,
+and they've agreed to share Shelley's reserves list with the Textiles
+class. She cannot add an arbitrary section to the course site. So Ed
+opens the site; clicks on Invite; clicks Arbitrary Section; and
+specifies the term, course code, and section of the Textiles class.
+
+ Instructor: course-section-invite
+ Library-Staff: course-section-invite-arbitrary
+
+A key point here is that the formal course numbers may not be
+selectable until late in the game; until just before the course, a
+working title may be all that you have. That's fine. Choosing specific
+course sections can be deferred until the registration info is
+available.
+
+
+What about the case where there is no registration data? How do you
+make a site available to an unknown audience?
+
+Have controls for "publishing" a site, that is, making it available to
+unknown users. A few options:
+
+ * Anyone at all can view the site, even anonymously
+
+ * Any logged-in user can view the site
+
+ * The instructor provides an access key out-of-band. The access key
+ auto-invites you into the site. This isn't so bad really.
+
+ * Any logged-in user can request access; the instructor has to grant
+ access. This way lies madness.
+
+Scrapping the madness-path, we're left with a reasonable set of
+options: Anonymous, Authenticated, AccessKey, Restricted (to members
+of the registered list), or NoAccess (for archived sites, etc.). None
+of this applies to instructors and their proxies, just to visitors.
+
+ Instructor: course-change-access
+ Library-Staff: course-change-access-extended
+
+ The extended permissions could let the librarian decide if certain
+ types of materials in the site should be inaccessible to Anonymous
+ users. Or maybe we should just scrap Anonymous access altogether.
Modified: servres/trunk/conifer/syrup/urls.py
===================================================================
--- servres/trunk/conifer/syrup/urls.py 2009-03-01 21:18:50 UTC (rev 132)
+++ servres/trunk/conifer/syrup/urls.py 2009-03-02 04:09:18 UTC (rev 133)
@@ -4,8 +4,8 @@
# want to cut down on the common boilerplate in the urlpatterns below.
ITEM_PREFIX = r'^course/(?P<course_id>\d+)/item/(?P<item_id>\d+)/'
+GENERIC_REGEX = r'((?P<obj_id>\d+)/)?(?P<action>.+)?$'
-
urlpatterns = patterns('conifer.syrup.views',
(r'^$', 'welcome'),
(r'^course/$', 'my_courses'),
@@ -24,4 +24,11 @@
(ITEM_PREFIX + r'meta$', 'item_metadata'),
(ITEM_PREFIX + r'edit/$', 'item_edit'),
(ITEM_PREFIX + r'add/$', 'item_add'), # for adding sub-things
+ (r'^admin/$', 'admin_index'),
+ (r'^admin/terms/' + GENERIC_REGEX, 'admin_terms'),
+ (r'^admin/depts/' + GENERIC_REGEX, 'admin_depts'),
+
+# (r'^admin/terms/(?P<term_id>\d+)/$', 'admin_term_edit'),
+# (r'^admin/terms/(?P<term_id>\d+)/delete$', 'admin_term_delete'),
+# (r'^admin/terms/$', 'admin_term'),
)
Modified: servres/trunk/conifer/syrup/views.py
===================================================================
--- servres/trunk/conifer/syrup/views.py 2009-03-01 21:18:50 UTC (rev 132)
+++ servres/trunk/conifer/syrup/views.py 2009-03-02 04:09:18 UTC (rev 133)
@@ -10,6 +10,7 @@
from django.contrib.auth.models import User
from django.db.models import Q
from datetime import datetime
+from generics import *
#------------------------------------------------------------
# Authentication
@@ -415,3 +416,49 @@
return g.render('search_results.xhtml', **locals())
+
+#------------------------------------------------------------
+# administrative options
+
+def admin_index(request):
+ return g.render('admin/index.xhtml')
+
+# fixme, no auth or permissions stuff yet.
+
+class TermForm(ModelForm):
+ class Meta:
+ model = models.Term
+
+ class Index:
+ title = 'Terms'
+ all = models.Term.objects.order_by('start', 'code').all
+ cols = ['code', 'name', 'start', 'finish']
+ links = [0,1]
+
+ clean_name = strip_and_nonblank('name')
+ clean_code = strip_and_nonblank('code')
+
+ def clean(self):
+ cd = self.cleaned_data
+ s, f = cd.get('start'), cd.get('finish')
+ if (s and f) and s >= f:
+ raise ValidationError, 'start must precede finish'
+ return cd
+
+admin_terms = generic_handler(TermForm)
+
+class DeptForm(ModelForm):
+ class Meta:
+ model = models.Department
+
+ class Index:
+ title = 'Departments'
+ all = models.Department.objects.order_by('abbreviation').all
+ cols = ['abbreviation', 'name']
+ links = [0,1]
+
+ clean_abbreviation = strip_and_nonblank('abbreviation')
+ clean_name = strip_and_nonblank('name')
+
+admin_depts = generic_handler(DeptForm)
+
Added: servres/trunk/conifer/templates/admin/index.xhtml
===================================================================
--- servres/trunk/conifer/templates/admin/index.xhtml (rev 0)
+++ servres/trunk/conifer/templates/admin/index.xhtml 2009-03-02 04:09:18 UTC (rev 133)
@@ -0,0 +1,18 @@
+<?python
+title = 'Administrative Options'
+?>
+<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>
+ <ul>
+ <li><a href="depts/">Departments</a></li>
+ <li><a href="terms/">Terms</a></li>
+ </ul>
+</body>
+</html>
Added: servres/trunk/conifer/templates/admin/term.xhtml
===================================================================
--- servres/trunk/conifer/templates/admin/term.xhtml (rev 0)
+++ servres/trunk/conifer/templates/admin/term.xhtml 2009-03-02 04:09:18 UTC (rev 133)
@@ -0,0 +1,18 @@
+<?python
+title = 'Add a new Term'
+?>
+<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>
+ <form action="." method="POST">
+ <table>${Markup(form.as_table())}</table>
+ <input type="submit" value="Save"/>
+ </form>
+</body>
+</html>
Added: servres/trunk/conifer/templates/generic/delete.xhtml
===================================================================
--- servres/trunk/conifer/templates/generic/delete.xhtml (rev 0)
+++ servres/trunk/conifer/templates/generic/delete.xhtml 2009-03-02 04:09:18 UTC (rev 133)
@@ -0,0 +1,24 @@
+<?python
+title = 'Delete %s?' % form.Meta.model.__name__
+?>
+<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>
+ <form action="delete" method="POST">
+ <table>
+ <tr>
+ <th>${form.Meta.model.__name__}</th>
+ <td>${instance}</td>
+ </tr>
+ </table>
+ <p><input type="submit" value="Delete"/></p>
+ <p><a href="../">Cancel</a></p>
+ </form>
+</body>
+</html>
Added: servres/trunk/conifer/templates/generic/edit.xhtml
===================================================================
--- servres/trunk/conifer/templates/generic/edit.xhtml (rev 0)
+++ servres/trunk/conifer/templates/generic/edit.xhtml 2009-03-02 04:09:18 UTC (rev 133)
@@ -0,0 +1,23 @@
+<?python
+if instance:
+ title = 'Modify %s' % form.Meta.model.__name__
+else:
+ title = 'Add %s' % form.Meta.model.__name__
+?>
+<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>
+ <form action="." method="POST">
+ <table>${Markup(form.as_table())}</table>
+ <p><input type="submit" value="Save changes"/></p>
+ <p><a href="../">Cancel changes</a></p>
+ <p><a href="delete">Delete this record</a></p>
+ </form>
+</body>
+</html>
Added: servres/trunk/conifer/templates/generic/index.xhtml
===================================================================
--- servres/trunk/conifer/templates/generic/index.xhtml (rev 0)
+++ servres/trunk/conifer/templates/generic/index.xhtml 2009-03-02 04:09:18 UTC (rev 133)
@@ -0,0 +1,28 @@
+<?python
+index = form.Index
+title = index.title
+?>
+<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>
+ <table class="pagetable">
+ <thead><tr><th py:for="c in form.Index.cols">${c}</th></tr></thead>
+ <tbody>
+ <tr py:for="r in form.Index.all()">
+ <td py:for="n, c in enumerate(form.Index.cols)">
+ <a py:strip="n not in form.Index.links"
+ href="${r.id}/">${call_or_value(getattr(r,c))}
+ </a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <p><a href="0/">Add ${form.Meta.model.__name__}</a></p>
+</body>
+</html>
Modified: servres/trunk/conifer/templates/tabbar.xhtml
===================================================================
--- servres/trunk/conifer/templates/tabbar.xhtml 2009-03-01 21:18:50 UTC (rev 132)
+++ servres/trunk/conifer/templates/tabbar.xhtml 2009-03-02 04:09:18 UTC (rev 133)
@@ -10,6 +10,7 @@
<li><a href="/syrup/">Home</a></li>
<li><a href="/syrup/browse/">Browse</a></li>
<li class="active"><a href="/syrup/course/">My Courses</a></li>
+ <li><a href="/syrup/admin/">Admin Options</a></li>
<!--
RD had a concept of "joining" which i am
suppressing for now, our staff is definitely
More information about the open-ils-commits
mailing list