From 5d5642673ce77c0bec0324fbd09e660b1797a550 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 28 Jan 2013 12:58:02 -0500 Subject: [PATCH 01/15] Add new field to disable certificate download --- common/djangoapps/student/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 44b947c045..ba1977da5b 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -90,6 +90,7 @@ class UserProfile(models.Model): ) mailing_address = models.TextField(blank=True, null=True) goals = models.TextField(blank=True, null=True) + allow_certificate = models.BooleanField(default=1) def get_meta(self): js_str = self.meta @@ -402,11 +403,11 @@ class TestCenterRegistration(models.Model): def exam_authorization_count(self): # TODO: figure out if this should really go in the database (with a default value). return 1 - + @property def needs_uploading(self): return self.uploaded_at is None or self.uploaded_at < self.user_updated_at - + @classmethod def create(cls, testcenter_user, exam, accommodation_request): registration = cls(testcenter_user = testcenter_user) @@ -532,7 +533,7 @@ def get_testcenter_registration(user, course_id, exam_series_code): # nosetests thinks that anything with _test_ in the name is a test. # Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html) get_testcenter_registration.__test__ = False - + def unique_id_for_user(user): """ Return a unique id for a user, suitable for inserting into From fba88db5fadf6ddc6729b419494efe8623eaf53b Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 28 Jan 2013 15:10:35 -0500 Subject: [PATCH 02/15] Adding django-admin command for certificate_restriction --- .../management/commands/cert_restriction.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 common/djangoapps/student/management/commands/cert_restriction.py diff --git a/common/djangoapps/student/management/commands/cert_restriction.py b/common/djangoapps/student/management/commands/cert_restriction.py new file mode 100644 index 0000000000..a5de880980 --- /dev/null +++ b/common/djangoapps/student/management/commands/cert_restriction.py @@ -0,0 +1,102 @@ +from django.core.management.base import BaseCommand, CommandError +import os +from optparse import make_option +from student.models import UserProfile +import csv + + +class Command(BaseCommand): + + help = """ + Sets or gets certificate restrictions for users + from embargoed countries. + + Import a list of students to restrict certificate download + by setting "allow_certificate" to True in userprofile: + + $ ... cert_restriction --import path/to/userlist.csv + + CSV should be comma delimited with double quoted entries. + + Export a list of students who have "allow_certificate" in + userprofile set to True + + $ ... cert_restriction --export path/to/export.csv + + """ + + option_list = BaseCommand.option_list + ( + make_option('-i', '--import', + metavar='IMPORT_FILE', + dest='import', + default=False, + help='csv file to import, comma delimitted file with ' + 'double-quoted entries'), + make_option('-o', '--output', + metavar='EXPORT_FILE', + dest='output', + default=False, + help='csv file to export'), + make_option('-e', '--enable', + metavar='STUDENT', + dest='enable', + default=False, + help="enable a single student's certificate"), + make_option('-d', '--disable', + metavar='STUDENT', + dest='disable', + default=False, + help="disable a single student's certificate") + ) + + def handle(self, *args, **options): + + if options['output']: + + if os.path.exists(options['output']): + raise CommandError("File {0} already exists".format( + options['output'])) + disabled_users = UserProfile.objects.filter( + allow_certificate=False) + + with open(options['output'], 'w') as csvfile: + csvwriter = csv.writer(csvfile, delimiter=',', quotechar='"', + quoting=csv.QUOTE_MINIMAL) + for user in disabled_users: + csvwriter.writerow([user.username]) + + elif options['input']: + + if not os.path.exists(options['input']): + raise CommandError("File {0} does not exist".format( + options['input'])) + + print "Importing students from {0}".format(options['input']) + + students = None + with open(options['input']) as csvfile: + student_list = csv.reader(csvfile, delimiter=',', + quotechar='"') + students = [student[0] for student in student_list] + if not students: + raise CommandError( + "Unable to read student data from {0}".format( + options['input'])) + UserProfile.objects.filter(username__in=students).update( + allow_certificate=False) + + elif options['enable']: + + print "Enabling {0} for certificate download".format( + options['enable']) + cert_allow = UserProfile.objects.get(user=options['enable']) + cert_allow.allow_certificate = True + cert_allow.save() + + elif options['disable']: + + print "Disabling {0} for certificate download".format( + options['disable']) + cert_allow = UserProfile.objects.get(user=options['disable']) + cert_allow.allow_certificate = False + cert_allow.save() From 7e033d0265851633080e52a961da2116575847f8 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 28 Jan 2013 19:28:24 -0500 Subject: [PATCH 03/15] django command for certificate restriction --- .../migrations/0024_add_allow_certificate.py | 172 ++++++++++++++++++ .../management/commands/cert_whitelist.py | 73 ++++++++ .../migrations/0014_adding_whitelist.py | 89 +++++++++ lms/djangoapps/certificates/models.py | 9 + lms/djangoapps/certificates/queue.py | 106 +++-------- 5 files changed, 372 insertions(+), 77 deletions(-) create mode 100644 common/djangoapps/student/migrations/0024_add_allow_certificate.py create mode 100644 lms/djangoapps/certificates/management/commands/cert_whitelist.py create mode 100644 lms/djangoapps/certificates/migrations/0014_adding_whitelist.py diff --git a/common/djangoapps/student/migrations/0024_add_allow_certificate.py b/common/djangoapps/student/migrations/0024_add_allow_certificate.py new file mode 100644 index 0000000000..fb3a97cd4b --- /dev/null +++ b/common/djangoapps/student/migrations/0024_add_allow_certificate.py @@ -0,0 +1,172 @@ +# -*- coding: 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): + # Adding field 'UserProfile.allow_certificate' + db.add_column('auth_userprofile', 'allow_certificate', + self.gf('django.db.models.fields.BooleanField')(default=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'UserProfile.allow_certificate' + db.delete_column('auth_userprofile', 'allow_certificate') + + + 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']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", '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'}) + }, + 'student.courseenrollment': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.courseenrollmentallowed': { + 'Meta': {'unique_together': "(('email', 'course_id'),)", 'object_name': 'CourseEnrollmentAllowed'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.testcenterregistration': { + 'Meta': {'object_name': 'TestCenterRegistration'}, + 'accommodation_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'accommodation_request': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'blank': 'True'}), + 'authorization_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}), + 'client_authorization_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), + 'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'eligibility_appointment_date_first': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), + 'eligibility_appointment_date_last': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), + 'exam_series_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'processed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'testcenter_user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['student.TestCenterUser']"}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'upload_error_message': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}), + 'upload_status': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}) + }, + 'student.testcenteruser': { + 'Meta': {'object_name': 'TestCenterUser'}, + 'address_1': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'address_2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'address_3': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'candidate_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'client_candidate_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), + 'company_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}), + 'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'extension': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8', 'blank': 'True'}), + 'fax': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}), + 'fax_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'middle_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '35'}), + 'phone_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}), + 'postal_code': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'blank': 'True'}), + 'processed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'salutation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'state': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}), + 'suffix': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'upload_error_message': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}), + 'upload_status': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}), + 'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'unique': 'True'}), + 'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] \ No newline at end of file diff --git a/lms/djangoapps/certificates/management/commands/cert_whitelist.py b/lms/djangoapps/certificates/management/commands/cert_whitelist.py new file mode 100644 index 0000000000..73f6338577 --- /dev/null +++ b/lms/djangoapps/certificates/management/commands/cert_whitelist.py @@ -0,0 +1,73 @@ +from django.core.management.base import BaseCommand, CommandError +import os +from optparse import make_option +from student.models import UserProfile +from certificates.models import CertificateWhitelist +from django.contrib.auth.models import User + +class Command(BaseCommand): + + help = """ + Sets or gets the certificate whitelist for a given + user/course + + Add a user to the whitelist for a course + + $ ... cert_whitelist --add joe -c "MITx/6.002x/2012_Fall" + + Remove a user from the whitelist for a course + + $ ... cert_whitelist --del joe -c "MITx/6.002x/2012_Fall" + + Print out who is whitelisted for a course + + $ ... cert_whitelist -c "MITx/6.002x/2012_Fall" + + """ + + option_list = BaseCommand.option_list + ( + make_option('-a', '--add', + metavar='USER', + dest='add', + default=False, + help='user to add to the certificate whitelist'), + + make_option('-d', '--del', + metavar='USER', + dest='del', + default=False, + help='user to remove from the certificate whitelist'), + + make_option('-c', '--course-id', + metavar='COURSE_ID', + dest='course_id', + default=False, + help="course id to query"), + ) + + def handle(self, *args, **options): + course_id = options['course_id'] + if not course_id: + raise CommandError("You must specify a course-id") + if options['add'] and options['del']: + raise CommandError("Either remove or add a user, not both") + + if options['add'] or options['del']: + user_str = options['add'] or options['del'] + if '@' in user_str: + user = User.objects.get(email=user_str) + else: + user = User.objects.get(username=user_str) + + cert_whitelist, created = CertificateWhitelist.objects.get_or_create( + user=user, course_id=course_id) + if options['add']: + cert_whitelist.whitelist = True + elif options['del']: + cert_whitelist.whitelist = False + cert_whitelist.save() + + whitelist = CertificateWhitelist.objects.all() + print "User whitelist for course {0}:\n{1}".format(course_id, + '\n'.join([ "{0} {1} {2}".format(u.user.username, u.user.email, u.whitelist) for u in whitelist])) + diff --git a/lms/djangoapps/certificates/migrations/0014_adding_whitelist.py b/lms/djangoapps/certificates/migrations/0014_adding_whitelist.py new file mode 100644 index 0000000000..1bd4d994cf --- /dev/null +++ b/lms/djangoapps/certificates/migrations/0014_adding_whitelist.py @@ -0,0 +1,89 @@ +# -*- coding: 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): + # Adding model 'CertificateWhitelist' + db.create_table('certificates_certificatewhitelist', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('course_id', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True)), + ('whitelist', self.gf('django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('certificates', ['CertificateWhitelist']) + + + def backwards(self, orm): + # Deleting model 'CertificateWhitelist' + db.delete_table('certificates_certificatewhitelist') + + + 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']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'certificates.certificatewhitelist': { + 'Meta': {'object_name': 'CertificateWhitelist'}, + 'course_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'whitelist': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'certificates.generatedcertificate': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'GeneratedCertificate'}, + 'course_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + 'created_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}), + 'distinction': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'download_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), + 'download_uuid': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), + 'error_reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '512', 'blank': 'True'}), + 'grade': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '5', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}), + 'modified_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'unavailable'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'verify_uuid': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", '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'}) + } + } + + complete_apps = ['certificates'] \ No newline at end of file diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index b9bd55b9af..c8e1dd047f 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -46,8 +46,13 @@ class CertificateStatuses(object): deleted = 'deleted' downloadable = 'downloadable' notpassing = 'notpassing' + restricted = 'restricted' error = 'error' +class CertificateWhitelist(models.Model): + user = models.ForeignKey(User) + course_id = models.CharField(max_length=255, blank=True, default='') + whitelist = models.BooleanField(default=0) class GeneratedCertificate(models.Model): user = models.ForeignKey(User) @@ -87,6 +92,10 @@ def certificate_status_for_student(student, course_id): deleted - The certificate has been deleted. downloadable - The certificate is available for download. notpassing - The student was graded but is not passing + restricted - The student is on the restricted embargo list and + should not be issued a certificate. This will + be set if allow_certificate is set to False in + the userprofile table If the status is "downloadable", the dictionary also contains "download_url". diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index b9316220fa..cdf84bba2f 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -1,6 +1,7 @@ from certificates.models import GeneratedCertificate from certificates.models import certificate_status_for_student from certificates.models import CertificateStatuses as status +from certificates.models import CertificateWhitelist from courseware import grades, courses from django.test.client import RequestFactory @@ -71,6 +72,8 @@ class XQueueCertInterface(object): settings.XQUEUE_INTERFACE['django_auth'], requests_auth, ) + self.whitelist = CertificateWhitelist.objects.all() + self.restricted = UserProfile.objects.filter(allow_certificate=False) def regen_cert(self, student, course_id): """ @@ -93,49 +96,7 @@ class XQueueCertInterface(object): """ - VALID_STATUSES = [status.error, status.downloadable] - - cert_status = certificate_status_for_student( - student, course_id)['status'] - - if cert_status in VALID_STATUSES: - # grade the student - course = courses.get_course_by_id(course_id) - grade = grades.grade(student, self.request, course) - - profile = UserProfile.objects.get(user=student) - try: - cert = GeneratedCertificate.objects.get( - user=student, course_id=course_id) - except GeneratedCertificate.DoesNotExist: - logger.critical("Attempting to regenerate a certificate" - "for a user that doesn't have one") - raise - - if grade['grade'] is not None: - - cert.status = status.regenerating - cert.name = profile.name - - contents = { - 'action': 'regen', - 'delete_verify_uuid': cert.verify_uuid, - 'delete_download_uuid': cert.download_uuid, - 'username': cert.user.username, - 'course_id': cert.course_id, - 'name': profile.name, - } - - key = cert.key - self._send_to_xqueue(contents, key) - cert.save() - - else: - cert.status = status.notpassing - cert.name = profile.name - cert.save() - - return cert_status + raise NotImplementedError def del_cert(self, student, course_id): @@ -152,34 +113,7 @@ class XQueueCertInterface(object): """ - VALID_STATUSES = [status.error, status.downloadable] - - cert_status = certificate_status_for_student( - student, course_id)['status'] - - if cert_status in VALID_STATUSES: - - try: - cert = GeneratedCertificate.objects.get( - user=student, course_id=course_id) - except GeneratedCertificate.DoesNotExist: - logger.warning("Attempting to delete a certificate" - "for a user that doesn't have one") - raise - - cert.status = status.deleting - - contents = { - 'action': 'delete', - 'delete_verify_uuid': cert.verify_uuid, - 'delete_download_uuid': cert.download_uuid, - 'username': cert.user.username, - } - - key = cert.key - self._send_to_xqueue(contents, key) - cert.save() - return cert_status + raise NotImplementedError def add_cert(self, student, course_id): """ @@ -189,14 +123,18 @@ class XQueueCertInterface(object): course_id - courseenrollment.course_id (string) Request a new certificate for a student. - Will change the certificate status to 'deleting'. + Will change the certificate status to 'generating'. Certificate must be in the 'unavailable', 'error', 'deleted' or 'generating' state. - If a student has a passing grade a request will made - for a new cert + If a student has a passing grade or is in the whitelist + table for the course a request will made for a new cert. + If a student has allow_certificate set to False in the + userprofile table the status will change to 'restricted' + + If a student does not have a passing grade the status will change to status.notpassing @@ -210,16 +148,30 @@ class XQueueCertInterface(object): cert_status = certificate_status_for_student( student, course_id)['status'] - + if cert_status in VALID_STATUSES: # grade the student course = courses.get_course_by_id(course_id) - grade = grades.grade(student, self.request, course) profile = UserProfile.objects.get(user=student) + + cert, created = GeneratedCertificate.objects.get_or_create( user=student, course_id=course_id) - if grade['grade'] is not None: + grade = grades.grade(student, self.request, course) + whitelist = self.whitelist.filter( + user=student, course_id=course_id, whitelist=True).exists() + + if whitelist or grade['grade'] is not None: + + # check to see whether the student is on the + # the embargoed country restricted list + if self.restricted.filter(user=student).exists(): + cert_status = status.restricted + cert.status = cert_status + cert.save() + return cert.status + cert_status = status.generating key = make_hashkey(random.random()) From 666828f71aaf571546e332bab70a2067315d5859 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 28 Jan 2013 19:28:47 -0500 Subject: [PATCH 04/15] Command for certificate whitelist --- .../management/commands/cert_whitelist.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lms/djangoapps/certificates/management/commands/cert_whitelist.py b/lms/djangoapps/certificates/management/commands/cert_whitelist.py index 73f6338577..94dfda74a6 100644 --- a/lms/djangoapps/certificates/management/commands/cert_whitelist.py +++ b/lms/djangoapps/certificates/management/commands/cert_whitelist.py @@ -1,10 +1,9 @@ from django.core.management.base import BaseCommand, CommandError -import os from optparse import make_option -from student.models import UserProfile from certificates.models import CertificateWhitelist from django.contrib.auth.models import User + class Command(BaseCommand): help = """ @@ -16,12 +15,12 @@ class Command(BaseCommand): $ ... cert_whitelist --add joe -c "MITx/6.002x/2012_Fall" Remove a user from the whitelist for a course - + $ ... cert_whitelist --del joe -c "MITx/6.002x/2012_Fall" Print out who is whitelisted for a course - $ ... cert_whitelist -c "MITx/6.002x/2012_Fall" + $ ... cert_whitelist -c "MITx/6.002x/2012_Fall" """ @@ -37,7 +36,7 @@ class Command(BaseCommand): dest='del', default=False, help='user to remove from the certificate whitelist'), - + make_option('-c', '--course-id', metavar='COURSE_ID', dest='course_id', @@ -50,7 +49,7 @@ class Command(BaseCommand): if not course_id: raise CommandError("You must specify a course-id") if options['add'] and options['del']: - raise CommandError("Either remove or add a user, not both") + raise CommandError("Either remove or add a user, not both") if options['add'] or options['del']: user_str = options['add'] or options['del'] @@ -59,8 +58,9 @@ class Command(BaseCommand): else: user = User.objects.get(username=user_str) - cert_whitelist, created = CertificateWhitelist.objects.get_or_create( - user=user, course_id=course_id) + cert_whitelist, created = \ + CertificateWhitelist.objects.get_or_create( + user=user, course_id=course_id) if options['add']: cert_whitelist.whitelist = True elif options['del']: @@ -68,6 +68,7 @@ class Command(BaseCommand): cert_whitelist.save() whitelist = CertificateWhitelist.objects.all() - print "User whitelist for course {0}:\n{1}".format(course_id, - '\n'.join([ "{0} {1} {2}".format(u.user.username, u.user.email, u.whitelist) for u in whitelist])) - + print "User whitelist for course {0}:\n{1}".format(course_id, + '\n'.join(["{0} {1} {2}".format( + u.user.username, u.user.email, u.whitelist) + for u in whitelist])) From e81394c2b0baee2e0b0a4c09d3ff7503c6010c4a Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 28 Jan 2013 19:29:13 -0500 Subject: [PATCH 05/15] Adding new model for certificate whitelist --- lms/djangoapps/certificates/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index c8e1dd047f..02e8ab0c5f 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -92,7 +92,7 @@ def certificate_status_for_student(student, course_id): deleted - The certificate has been deleted. downloadable - The certificate is available for download. notpassing - The student was graded but is not passing - restricted - The student is on the restricted embargo list and + restricted - The student is on the restricted embargo list and should not be issued a certificate. This will be set if allow_certificate is set to False in the userprofile table From e9722b259b117ab8fb4e79e9ad611a7469044b16 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 28 Jan 2013 19:29:53 -0500 Subject: [PATCH 06/15] Removing whitespace --- lms/djangoapps/certificates/queue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index cdf84bba2f..f6b5943564 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -134,7 +134,7 @@ class XQueueCertInterface(object): If a student has allow_certificate set to False in the userprofile table the status will change to 'restricted' - + If a student does not have a passing grade the status will change to status.notpassing @@ -148,7 +148,7 @@ class XQueueCertInterface(object): cert_status = certificate_status_for_student( student, course_id)['status'] - + if cert_status in VALID_STATUSES: # grade the student course = courses.get_course_by_id(course_id) From 0c43e69c00cee0a7d6a71dc39222056fe41baaa0 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 28 Jan 2013 19:30:40 -0500 Subject: [PATCH 07/15] command for certificate restriction (embargoed countries) --- .../management/commands/cert_restriction.py | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/common/djangoapps/student/management/commands/cert_restriction.py b/common/djangoapps/student/management/commands/cert_restriction.py index a5de880980..81405de156 100644 --- a/common/djangoapps/student/management/commands/cert_restriction.py +++ b/common/djangoapps/student/management/commands/cert_restriction.py @@ -9,15 +9,13 @@ class Command(BaseCommand): help = """ Sets or gets certificate restrictions for users - from embargoed countries. - - Import a list of students to restrict certificate download - by setting "allow_certificate" to True in userprofile: - - $ ... cert_restriction --import path/to/userlist.csv + from embargoed countries. (allow_certificate in + userprofile) CSV should be comma delimited with double quoted entries. + $ ... cert_restriction --import path/to/userlist.csv + Export a list of students who have "allow_certificate" in userprofile set to True @@ -50,7 +48,6 @@ class Command(BaseCommand): ) def handle(self, *args, **options): - if options['output']: if os.path.exists(options['output']): @@ -63,33 +60,34 @@ class Command(BaseCommand): csvwriter = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) for user in disabled_users: - csvwriter.writerow([user.username]) + csvwriter.writerow([user.user.username]) - elif options['input']: + elif options['import']: - if not os.path.exists(options['input']): + if not os.path.exists(options['import']): raise CommandError("File {0} does not exist".format( - options['input'])) + options['import'])) - print "Importing students from {0}".format(options['input']) + print "Importing students from {0}".format(options['import']) students = None - with open(options['input']) as csvfile: + with open(options['import']) as csvfile: student_list = csv.reader(csvfile, delimiter=',', quotechar='"') students = [student[0] for student in student_list] if not students: raise CommandError( "Unable to read student data from {0}".format( - options['input'])) - UserProfile.objects.filter(username__in=students).update( + options['import'])) + UserProfile.objects.filter(user__username__in=students).update( allow_certificate=False) elif options['enable']: print "Enabling {0} for certificate download".format( options['enable']) - cert_allow = UserProfile.objects.get(user=options['enable']) + cert_allow = UserProfile.objects.get( + user__username=options['enable']) cert_allow.allow_certificate = True cert_allow.save() @@ -97,6 +95,7 @@ class Command(BaseCommand): print "Disabling {0} for certificate download".format( options['disable']) - cert_allow = UserProfile.objects.get(user=options['disable']) + cert_allow = UserProfile.objects.get( + user__username=options['disable']) cert_allow.allow_certificate = False cert_allow.save() From 289301e0ea29962b6e25377c9302a491ff20acdb Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Mon, 28 Jan 2013 19:38:51 -0500 Subject: [PATCH 08/15] adding more to help string --- .../student/management/commands/cert_restriction.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/common/djangoapps/student/management/commands/cert_restriction.py b/common/djangoapps/student/management/commands/cert_restriction.py index 81405de156..c43ff05f3e 100644 --- a/common/djangoapps/student/management/commands/cert_restriction.py +++ b/common/djangoapps/student/management/commands/cert_restriction.py @@ -19,7 +19,15 @@ class Command(BaseCommand): Export a list of students who have "allow_certificate" in userprofile set to True - $ ... cert_restriction --export path/to/export.csv + $ ... cert_restriction --output path/to/export.csv + + Enable a single user so she is not on the restricted list + + $ ... cert_restriction -e user + + Disable a single user so she is on the restricted list + + $ ... cert_restriction -d user """ From dbc0f353c55e81a92eb8d6751f6b20b0818356ce Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 29 Jan 2013 10:22:58 -0500 Subject: [PATCH 09/15] cert_status temporary assignment removed --- lms/djangoapps/certificates/queue.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index f6b5943564..2150f1066c 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -167,8 +167,7 @@ class XQueueCertInterface(object): # check to see whether the student is on the # the embargoed country restricted list if self.restricted.filter(user=student).exists(): - cert_status = status.restricted - cert.status = cert_status + cert.status = status.restricted cert.save() return cert.status From 415ebec8797b2da6044254d8523851fca905982d Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 29 Jan 2013 10:24:29 -0500 Subject: [PATCH 10/15] renaming whitelist to is_whitelisted --- lms/djangoapps/certificates/queue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index 2150f1066c..d4e96f85be 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -159,10 +159,10 @@ class XQueueCertInterface(object): user=student, course_id=course_id) grade = grades.grade(student, self.request, course) - whitelist = self.whitelist.filter( + is_whitelisted = self.whitelist.filter( user=student, course_id=course_id, whitelist=True).exists() - if whitelist or grade['grade'] is not None: + if is_whitelisted or grade['grade'] is not None: # check to see whether the student is on the # the embargoed country restricted list From 4b0999939b5226fbc74660f6444be5be6c10f643 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 29 Jan 2013 10:26:21 -0500 Subject: [PATCH 11/15] Adding docstring to CertificateWhitelist model --- lms/djangoapps/certificates/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 02e8ab0c5f..334200d348 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -50,6 +50,13 @@ class CertificateStatuses(object): error = 'error' class CertificateWhitelist(models.Model): + """ + Tracks students who are whitelisted, all users + in this table will always qualify for a certificate + regardless of their grade unless they are on the + embargoed country restriction list + (allow_certificate set to False in userprofile). + """ user = models.ForeignKey(User) course_id = models.CharField(max_length=255, blank=True, default='') whitelist = models.BooleanField(default=0) From 0f39e04697295c950a5a6d92ac2fa5562ae99da8 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 29 Jan 2013 10:27:53 -0500 Subject: [PATCH 12/15] Adding certificate restriction lookup If certificate status is 'restricted' the grade and survey link will be shown, the download link will not --- common/djangoapps/student/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 61b49e6022..b583599e97 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -135,7 +135,7 @@ def cert_info(user, course): Get the certificate info needed to render the dashboard section for the given student and course. Returns a dictionary with keys: - 'status': one of 'generating', 'ready', 'notpassing', 'processing' + 'status': one of 'generating', 'ready', 'notpassing', 'processing', 'restricted' 'show_download_url': bool 'download_url': url, only present if show_download_url is True 'show_disabled_download_button': bool -- true if state is 'generating' @@ -168,6 +168,7 @@ def _cert_info(user, course, cert_status): CertificateStatuses.regenerating: 'generating', CertificateStatuses.downloadable: 'ready', CertificateStatuses.notpassing: 'notpassing', + CertificateStatuses.restricted: 'restricted', } status = template_state.get(cert_status['status'], default_status) @@ -176,7 +177,7 @@ def _cert_info(user, course, cert_status): 'show_download_url': status == 'ready', 'show_disabled_download_button': status == 'generating',} - if (status in ('generating', 'ready', 'notpassing') and + if (status in ('generating', 'ready', 'notpassing', 'restricted') and course.end_of_course_survey_url is not None): d.update({ 'show_survey_button': True, @@ -192,7 +193,7 @@ def _cert_info(user, course, cert_status): else: d['download_url'] = cert_status['download_url'] - if status in ('generating', 'ready', 'notpassing'): + if status in ('generating', 'ready', 'notpassing', 'restricted'): if 'grade' not in cert_status: # Note: as of 11/20/2012, we know there are students in this state-- cs169.1x, # who need to be regraded (we weren't tracking 'notpassing' at first). From 3a6d45e716eb5d17ab625f7d73f9caa6fcb08f49 Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 29 Jan 2013 11:13:30 -0500 Subject: [PATCH 13/15] Grade a student even if they are on the restricted list --- lms/djangoapps/certificates/queue.py | 32 +++++++++++++--------------- lms/templates/dashboard.html | 2 +- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index d4e96f85be..d926035efd 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -164,31 +164,29 @@ class XQueueCertInterface(object): if is_whitelisted or grade['grade'] is not None: - # check to see whether the student is on the - # the embargoed country restricted list - if self.restricted.filter(user=student).exists(): - cert.status = status.restricted - cert.save() - return cert.status - - cert_status = status.generating key = make_hashkey(random.random()) - cert.status = cert_status cert.grade = grade['percent'] cert.user = student cert.course_id = course_id cert.key = key cert.name = profile.name - contents = { - 'action': 'create', - 'username': student.username, - 'course_id': course_id, - 'name': profile.name, - } - - self._send_to_xqueue(contents, key) + # check to see whether the student is on the + # the embargoed country restricted list + # otherwise, put a new certificate request + # on the queue + if self.restricted.filter(user=student).exists(): + cert.status = status.restricted + else: + contents = { + 'action': 'create', + 'username': student.username, + 'course_id': course_id, + 'name': profile.name, + } + cert.status = status.generating + self._send_to_xqueue(contents, key) cert.save() else: cert_status = status.notpassing diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 8ec58a6a28..845880c69f 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -273,7 +273,7 @@ % if cert_status['status'] == 'processing':

Final course details are being wrapped up at this time. Your final standing will be available shortly.

- % elif cert_status['status'] in ('generating', 'ready', 'notpassing'): + % elif cert_status['status'] in ('generating', 'ready', 'notpassing', 'restricted'):

Your final grade: ${"{0:.0f}%".format(float(cert_status['grade'])*100)}. % if cert_status['status'] == 'notpassing': From a9ee71fffc96296903c6b7eb6d0d3896b56d833a Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 29 Jan 2013 11:26:55 -0500 Subject: [PATCH 14/15] Adding section on eligibility --- lms/djangoapps/certificates/models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 334200d348..0e68e3cfe7 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -35,6 +35,19 @@ State diagram: v v v [downloadable] [downloadable] [deleted] + +Eligibility: + + Students are eligible for a certificate if they pass the course + with the following exceptions: + + If the student has allow_certificate set to False in the student profile + he will never be issued a certificate. + + If the user and course is present in the certificate whitelist table + then the student will be issued a certificate regardless of his grade, + unless he has allow_certificate set to False. + """ From 3f740976bc12eb325cd3c3d05a70f3e9cb9d3a3d Mon Sep 17 00:00:00 2001 From: John Jarvis Date: Tue, 29 Jan 2013 13:24:17 -0500 Subject: [PATCH 15/15] Adding dashboard text for certificate restriction --- lms/templates/dashboard.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 845880c69f..43b69ea44b 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -279,6 +279,10 @@ % if cert_status['status'] == 'notpassing': Grade required for a certificate: ${"{0:.0f}%".format(float(course.lowest_passing_grade)*100)}. + % elif cert_status['status'] == 'restricted': +

+ Your certificate is being held while we seek confirmation that the issuance of your certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting info@edx.org. +

% endif

% endif