--- /dev/null
+# 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']
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()
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):
# 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')
@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())
-
- # 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())
+ raise NotImplementedError
#-----------------------------------------------------------------------------
# Site-instance handlers
<?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"
<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> • <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>
- <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 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>
- </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>