[open-ils-commits] r927 - in servres/trunk/conifer: syrup syrup/migrations syrup/views templates (gfawcett)

svn at svn.open-ils.org svn at svn.open-ils.org
Fri Jul 16 13:38:27 EDT 2010


Author: gfawcett
Date: 2010-07-16 13:38:23 -0400 (Fri, 16 Jul 2010)
New Revision: 927

Added:
   servres/trunk/conifer/syrup/migrations/0003_auto__del_field_site_passkey.py
Modified:
   servres/trunk/conifer/syrup/models.py
   servres/trunk/conifer/syrup/views/sites.py
   servres/trunk/conifer/templates/edit_site_permissions.xhtml
Log:
Access-control model changes. Simplistic site-permisisons screen. Schema changes.

The site-permissions screen is unfinished; need ways to add
individuals and external groups. Right now it's mainly informational.

I've taken out the passkey (invitation-code) system for now; it could
easily be reimplemented in terms of a Group with a 'passkey:NNN'
external ID.

Added: servres/trunk/conifer/syrup/migrations/0003_auto__del_field_site_passkey.py
===================================================================
--- servres/trunk/conifer/syrup/migrations/0003_auto__del_field_site_passkey.py	                        (rev 0)
+++ servres/trunk/conifer/syrup/migrations/0003_auto__del_field_site_passkey.py	2010-07-16 17:38:23 UTC (rev 927)
@@ -0,0 +1,170 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Deleting field 'Site.passkey'
+        db.delete_column('syrup_site', 'passkey')
+
+
+    def backwards(self, orm):
+        
+        # Adding field 'Site.passkey'
+        db.add_column('syrup_site', 'passkey', self.gf('django.db.models.fields.CharField')(blank=True, max_length=256, null=True, db_index=True), keep_default=False)
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'syrup.config': {
+            'Meta': {'object_name': 'Config'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '8192'})
+        },
+        'syrup.course': {
+            'Meta': {'object_name': 'Course'},
+            'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'department': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Department']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'syrup.department': {
+            'Meta': {'object_name': 'Department'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'service_desk': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.ServiceDesk']"})
+        },
+        'syrup.group': {
+            'Meta': {'object_name': 'Group'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '2048', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Site']"})
+        },
+        'syrup.item': {
+            'Meta': {'object_name': 'Item'},
+            'author': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8192', 'null': 'True', 'blank': 'True'}),
+            'bib_id': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'fileobj': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'fileobj_mimetype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'item_type': ('django.db.models.fields.CharField', [], {'max_length': '7'}),
+            'itemtype': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1', 'null': 'True', 'blank': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'marcxml': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'parent_heading': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Item']", 'null': 'True', 'blank': 'True'}),
+            'published': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
+            'publisher': ('django.db.models.fields.CharField', [], {'max_length': '8192', 'null': 'True', 'blank': 'True'}),
+            'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Site']"}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '8192', 'db_index': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+        },
+        'syrup.membership': {
+            'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'Membership'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Group']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'role': ('django.db.models.fields.CharField', [], {'default': "'STUDT'", 'max_length': '6'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'syrup.servicedesk': {
+            'Meta': {'object_name': 'ServiceDesk'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'syrup.site': {
+            'Meta': {'unique_together': "(('course', 'term', 'owner'),)", 'object_name': 'Site'},
+            'access': ('django.db.models.fields.CharField', [], {'default': "'LOGIN'", 'max_length': '5'}),
+            'course': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Course']"}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'service_desk': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.ServiceDesk']"}),
+            'term': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['syrup.Term']"})
+        },
+        'syrup.term': {
+            'Meta': {'object_name': 'Term'},
+            'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'finish': ('django.db.models.fields.DateField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'start': ('django.db.models.fields.DateField', [], {})
+        },
+        'syrup.userprofile': {
+            'Meta': {'object_name': 'UserProfile'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ils_userid': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'last_email_notice': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}),
+            'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'wants_email_notices': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})
+        },
+        'syrup.z3950target': {
+            'Meta': {'object_name': 'Z3950Target'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'database': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'host': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'port': ('django.db.models.fields.IntegerField', [], {'default': '210'}),
+            'syntax': ('django.db.models.fields.CharField', [], {'default': "'USMARC'", 'max_length': '10'})
+        }
+    }
+
+    complete_apps = ['syrup']

Modified: servres/trunk/conifer/syrup/models.py
===================================================================
--- servres/trunk/conifer/syrup/models.py	2010-07-16 17:38:20 UTC (rev 926)
+++ servres/trunk/conifer/syrup/models.py	2010-07-16 17:38:23 UTC (rev 927)
@@ -48,7 +48,7 @@
             bool(callhook('can_create_sites', self))
 
     def get_list_name(self):
-        return '%s, %s (%s)' % (self.last_name, self.first_name, self.username)
+        return '%s, %s' % (self.last_name, self.first_name)
 
     @classmethod
     def active_instructors(cls):
@@ -164,19 +164,23 @@
                          choices = [
             ('ANON', _('World-accessible')),
             ('LOGIN', _('Accessible to all logged-in users')),
-            ('STUDT', _('Accessible to course students (by section)')),
-            ('INVIT', _('Accessible to course students (by invitation code)')),
+            ('MEMBR', _('Accessible to course-site members')),
             ('CLOSE', _('Accessible only to course-site owners'))])
 
-    # For sites that use a passkey as an invitation (INVIT access).
-    # Note: only set this value using 'generate_new_passkey'.
-    # TODO: for postgres, add UNIQUE constraint on 'passkey'.
-    passkey = m.CharField(db_index=True, blank=True, null=True, max_length=256)
-
     class Meta:
         unique_together = (('course', 'term', 'owner'))
         ordering = ['-term__start', 'course__code']
 
+    def save(self, *args, **kwargs):
+        # Ensure there is always an internal Group.
+        super(Site, self).save(*args, **kwargs)
+        internal, just_created = Group.objects.get_or_create(
+            site=self, external_id=None)
+        # ..and that the owner is an instructor in the site.
+        Membership.objects.get_or_create(group    = internal,
+                                         user     = self.owner,
+                                         defaults = {'role':'INSTR'})
+
     def __unicode__(self):
         return u'%s: %s (%s, %s)' % (
             self.course.code, self.course.name,
@@ -184,7 +188,8 @@
             self.term.name)
 
     def list_display(self):
-            return '%s [%s, %s]' % (self.course.name, self.course.code, self.term.name)
+            return '%s [%s, %s]' % (self.course.name, self.course.code,
+                                    self.term.name)
 
     def items(self):
         return self.item_set.all()
@@ -293,7 +298,6 @@
             or user.is_staff \
             or self.is_member(user)
 
-
     #--------------------------------------------------
 
     @classmethod

Modified: servres/trunk/conifer/syrup/views/sites.py
===================================================================
--- servres/trunk/conifer/syrup/views/sites.py	2010-07-16 17:38:20 UTC (rev 926)
+++ servres/trunk/conifer/syrup/views/sites.py	2010-07-16 17:38:23 UTC (rev 927)
@@ -7,7 +7,7 @@
 class NewSiteForm(ModelForm):
     class Meta:
         model = models.Site
-        exclude = ('passkey','access')
+        exclude = ('access',)
 
     def clean_code(self):
         v = (self.cleaned_data.get('code') or '').strip()
@@ -53,9 +53,6 @@
         else:
             form.save()
             site = form.instance
-            if site.access == u'INVIT' and not site.passkey:
-                site.generate_new_passkey()
-                site.save()
             assert site.id
 
             if is_add or (current_access_level != site.access):
@@ -70,31 +67,20 @@
     # choices: make the access-choice labels more personalized than
     # the ones in 'models'.
     choices = [
-        # note: I'm leaving ANON out for now, until we discuss it further.
-        (u'ANON', _(u'Anyone on the planet may access this site.')),
-        (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'))]
-    # TODO: fixme, campus module no longer exists.
-    if models.campus.sections_tuple_delimiter is None:
-        # no course-sections support? Then STUDT cannot be an option.
-        del choices[1]
-    choose_access = django.forms.Select(choices=choices)
+        (u'ANON',  _(u'Everyone: no login required.')),
+        (u'LOGIN', _(u'Members and non-members: login required.')),
+        (u'MEMBR', _(u'Members only.')),
+        (u'CLOSE', _(u'Instructors only: this site is closed.')),
+        ]
+
+    choose_access = django.forms.RadioSelect(choices=choices)
         
     if request.method != 'POST':
         return g.render('edit_site_permissions.xhtml', **locals())
     else:
         POST = request.POST
 
-        if 'action_change_code' in POST:
-            # update invitation code -------------------------------------
-            site.generate_new_passkey()
-            site.access = u'INVIT'
-            site.save()
-            return HttpResponseRedirect('.#student_access')
-
-        elif 'action_save_instructor' in POST:
+        if 'action_save_instructor' in POST:
             # update instructor details ----------------------------------
             iname = POST.get('new_instructor_name','').strip()
             irole = POST.get('new_instructor_role')
@@ -181,32 +167,8 @@
 
 @login_required                 # must be, to avoid/audit brute force attacks.
 def site_invitation(request):
-    if request.method != 'POST':
-        return g.render('site_invitation.xhtml', code='', error='',
-                        **locals())
-    else:
-        code = request.POST.get('code', '').strip()
-        # todo, a pluggable passkey implementation would normalize the code here.
-        if not code:
-            return HttpResponseRedirect('.')
-        try:
-            # note, we only allow the passkey if access='INVIT'.
-            crs = models.Site.objects.filter(access='INVIT').get(passkey=code)
-        except models.Site.DoesNotExist:
-            # todo, do we need a formal logging system? Or a table for
-            # invitation failures? They should be captured somehow, I
-            # think. Should we temporarily disable accounts after
-            # multiple failures?
-            error = _('The code you provided is not valid.')
-            return g.render('site_invitation.xhtml', **locals())
+    raise NotImplementedError
 
-        # the passkey is good; add the user if not already a member.
-        if not models.Membership.objects.filter(user=request.user, site=crs):
-            mbr = models.Membership.objects.create(user=request.user, site=crs, 
-                                               role='STUDT')
-            mbr.save()
-        return HttpResponseRedirect(crs.site_url())
-
 #-----------------------------------------------------------------------------
 # Site-instance handlers
 

Modified: servres/trunk/conifer/templates/edit_site_permissions.xhtml
===================================================================
--- servres/trunk/conifer/templates/edit_site_permissions.xhtml	2010-07-16 17:38:20 UTC (rev 926)
+++ servres/trunk/conifer/templates/edit_site_permissions.xhtml	2010-07-16 17:38:23 UTC (rev 927)
@@ -1,6 +1,8 @@
 <?python
 title = _('Edit site permissions')
 instructors = site.get_instructors()
+members = models.Membership.objects.select_related().filter(group__site=site).order_by('user__last_name', 'user__first_name')
+extgroups = site.group_set.filter(external_id__isnull=False)
 ?>
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:xi="http://www.w3.org/2001/XInclude"
@@ -10,72 +12,66 @@
 <head>
   <title>${title}</title>
   <script type="text/javascript" src="${ROOT}/static/edit_site_permissions.js"/>
+  <style type="text/css">
+    #access_level li { list-style-type: none; }
+  </style>
 </head>
 <body>
   ${site_banner(site)}
   <h1>${title}</h1>
   <p><a href="../">Edit site details</a> &bull; <a href="${site.site_url()}">Return to site page</a></p>
   <form action="." method="POST">
-    <h2>Instructor Access</h2>
-    <div py:if="defined('instructor_error')" class="errors">${instructor_error}</div>
-    <table class="pagetable">
+    <h2>Site Access Level</h2>
+    <p>Who has permission to view resources in this site?</p>
+    <div id="access_level" style="margin-left: 12;">${Markup(choose_access.render('access', site.access, {'id':'id_access'}, []))}</div>
+    <p><input type="submit" name="action_save_student" value="Save changes to student access"/></p>
+
+    <h2>Current Membership</h2>
+    <table class="pagetable" style="width: 100%;">
       <thead>
-	<tr><th>Person</th><th>Role</th><th>Remove?</th></tr>
+	<tr><th>Name</th><th>Role</th><th>User ID</th><th>Group</th></tr>
       </thead>
-      <tbody>
-	<select py:def="select_role(mbr)" 
-		py:replace="Markup(django.forms.Select(choices=
-			    [(u'INSTR',_('Instructor')), (u'PROXY', _('Proxy instructor'))])
-			    .render('instructor_role_%d' % mbr.id, mbr.role))"/>
-      <tr py:for="mbr in instructors">
-	<td>${mbr.user.get_full_name() or ''} <code>(${mbr.user.username})</code></td>
-	<td>${select_role(mbr)}</td>
-	<td><input type="checkbox" name="instructor_remove_${mbr.id}"/></td>
+      <tr py:for="member in members"
+	  style="${'' if member.user.is_active else 'text-decoration: line-through;'}">
+	<td>${member.user.get_list_name()}</td>
+	<td>${member.get_role_display()}</td>
+	<td>${member.user.username}</td>
+	<td>${member.group.external_id or '(internal)'}</td>
       </tr>
-      <tr style="vertical-align: bottom;">
-	<td><p>Username of the new instructor.</p><input type="text" name="new_instructor_name"/></td>
-	<td><select name="new_instructor_role">
-	  <option value="INSTR">Instructor</option>
-	  <option value="PROXY">Proxy instructor</option>
-	  </select>
-	</td>
-	<td/>
-      </tr>
-      </tbody>
     </table>
-    <p>	  <input type="submit" name="action_save_instructor" value="Save changes to instructors"/></p>
-    <div class="gap"/>
-    <h2 id="student_access">Student Access</h2>
-    <p>Who will have student-level access to this site?
-    <span style="margin-left: 12;"/>${Markup(choose_access.render('access', site.access, {'id':'id_access'}, []))}</p>
-    <div id="INVIT_panel" class="specific">
-      <h3>Site Invitation Code</h3>
-      <p style="font-size: larger;">Your Site Invitation Code is: <strong>${site.passkey}</strong>
-      <span style="margin-left: 16;">
-	  <input type="submit" name="action_change_code" value="Select a new code"/>
-      </span>
-      </p>
-      <p>This invitation code will enable your students to join this
-      site. Share it only with your students: anyone who has
-      the code can join your site.</p>
-      <p>You may change the code at any time. This will not block
-      students who have already joined, but will prevent new students
-      from joining with the old code.</p>
+    <div py:if="extgroups">
+      <h2>External Groups</h2>
+      <p py:for="eg in extgroups">${eg.external_id}</p>
     </div>
-    <div id="STUDT_panel" class="specific">
-      <h3>Course section numbers</h3>
-      <p>Not implemented yet.</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 site.</p>
-    <ol>
-      <li py:for="student in site.get_students()">
-	${student.get_full_name()} (${student.email})
-      </li>
-    </ol>
+    <!-- <h2>Instructor Access</h2> -->
+    <!-- <div py:if="defined('instructor_error')" class="errors">${instructor_error}</div> -->
+    <!-- <table class="pagetable"> -->
+    <!--   <thead> -->
+    <!-- 	<tr><th>Person</th><th>Role</th><th>Remove?</th></tr> -->
+    <!--   </thead> -->
+    <!--   <tbody> -->
+    <!-- 	<select py:def="select_role(mbr)"  -->
+    <!-- 		py:replace="Markup(django.forms.Select(choices= -->
+    <!-- 			    [(u'INSTR',_('Instructor')), (u'PROXY', _('Proxy instructor'))]) -->
+    <!-- 			    .render('instructor_role_%d' % mbr.id, mbr.role))"/> -->
+    <!--   <tr py:for="mbr in instructors"> -->
+    <!-- 	<td>${mbr.user.get_full_name() or ''} <code>(${mbr.user.username})</code></td> -->
+    <!-- 	<td>${select_role(mbr)}</td> -->
+    <!-- 	<td><input type="checkbox" name="instructor_remove_${mbr.id}"/></td> -->
+    <!--   </tr> -->
+    <!--   <tr style="vertical-align: bottom;"> -->
+    <!-- 	<td><p>Username of the new instructor.</p><input type="text" name="new_instructor_name"/></td> -->
+    <!-- 	<td><select name="new_instructor_role"> -->
+    <!-- 	  <option value="INSTR">Instructor</option> -->
+    <!-- 	  <option value="PROXY">Proxy instructor</option> -->
+    <!-- 	  </select> -->
+    <!-- 	</td> -->
+    <!-- 	<td/> -->
+    <!--   </tr> -->
+    <!--   </tbody> -->
+    <!-- </table> -->
+    <!-- <p>	  <input type="submit" name="action_save_instructor" value="Save changes to instructors"/></p> -->
   </form>
   <div class="gap"/>
 </body>



More information about the open-ils-commits mailing list