ziafazal/SOL-1044: Database-Driven Certificate Templates
* added certificate template model * added certificate template asset model * added django admin configs and migration * initial LMS template matching logic * improved LMS template matching * improved LMS template matching and rendering logic * address Django admin form/model load order incongruence * add missing unique constraint migration.
This commit is contained in:
@@ -4,6 +4,7 @@ Utility library for working with the edx-organizations app
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.utils import DatabaseError
|
||||
|
||||
|
||||
def add_organization(organization_data):
|
||||
@@ -43,7 +44,15 @@ def get_organizations():
|
||||
if not settings.FEATURES.get('ORGANIZATIONS_APP', False):
|
||||
return []
|
||||
from organizations import api as organizations_api
|
||||
return organizations_api.get_organizations()
|
||||
# Due to the way unit tests run for edx-platform, models are not yet available at the time
|
||||
# of Django admin form instantiation. This unfortunately results in an invocation of the following
|
||||
# workflow, because the test configuration is (correctly) configured to exercise the application
|
||||
# The good news is that this case does not manifest in the Real World, because migrations have
|
||||
# been run ahead of application instantiation and the flag set only when that is truly the case.
|
||||
try:
|
||||
return organizations_api.get_organizations()
|
||||
except DatabaseError:
|
||||
return []
|
||||
|
||||
|
||||
def get_organization_courses(organization_id):
|
||||
|
||||
@@ -2,12 +2,49 @@
|
||||
django admin pages for certificates models
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django import forms
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from util.organizations_helpers import get_organizations
|
||||
from certificates.models import (
|
||||
CertificateGenerationConfiguration, CertificateHtmlViewConfiguration, BadgeImageConfiguration
|
||||
CertificateGenerationConfiguration,
|
||||
CertificateHtmlViewConfiguration,
|
||||
BadgeImageConfiguration,
|
||||
CertificateTemplate,
|
||||
CertificateTemplateAsset,
|
||||
)
|
||||
|
||||
|
||||
class CertificateTemplateForm(forms.ModelForm):
|
||||
"""
|
||||
Django admin form for CertificateTemplate model
|
||||
"""
|
||||
organizations = get_organizations()
|
||||
org_choices = [(org["id"], org["name"]) for org in organizations]
|
||||
org_choices.insert(0, ('', 'None'))
|
||||
organization_id = forms.TypedChoiceField(choices=org_choices, required=False, coerce=int, empty_value=None)
|
||||
|
||||
class Meta(object):
|
||||
""" Meta definitions for CertificateTemplateForm """
|
||||
model = CertificateTemplate
|
||||
|
||||
|
||||
class CertificateTemplateAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Django admin customizations for CertificateTemplate model
|
||||
"""
|
||||
list_display = ('name', 'description', 'organization_id', 'course_key', 'mode', 'is_active')
|
||||
form = CertificateTemplateForm
|
||||
|
||||
|
||||
class CertificateTemplateAssetAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Django admin customizations for CertificateTemplateAsset model
|
||||
"""
|
||||
list_display = ('description', '__unicode__')
|
||||
|
||||
|
||||
admin.site.register(CertificateGenerationConfiguration)
|
||||
admin.site.register(CertificateHtmlViewConfiguration, ConfigurationModelAdmin)
|
||||
admin.site.register(BadgeImageConfiguration)
|
||||
admin.site.register(CertificateTemplate, CertificateTemplateAdmin)
|
||||
admin.site.register(CertificateTemplateAsset, CertificateTemplateAssetAdmin)
|
||||
|
||||
@@ -14,6 +14,7 @@ from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from util.organizations_helpers import get_course_organizations
|
||||
|
||||
from certificates.models import (
|
||||
CertificateStatuses,
|
||||
@@ -21,7 +22,8 @@ from certificates.models import (
|
||||
CertificateGenerationCourseSetting,
|
||||
CertificateGenerationConfiguration,
|
||||
ExampleCertificateSet,
|
||||
GeneratedCertificate
|
||||
GeneratedCertificate,
|
||||
CertificateTemplate,
|
||||
)
|
||||
from certificates.queue import XQueueCertInterface
|
||||
|
||||
@@ -373,6 +375,46 @@ def get_active_web_certificate(course, is_preview_mode=None):
|
||||
return None
|
||||
|
||||
|
||||
def get_certificate_template(course_key, mode):
|
||||
"""
|
||||
Retrieves the custom certificate template based on course_key and mode.
|
||||
"""
|
||||
org_id, template = None, None
|
||||
# fetch organization of the course
|
||||
course_organization = get_course_organizations(course_key)
|
||||
if course_organization:
|
||||
org_id = course_organization[0]['id']
|
||||
|
||||
if org_id and mode:
|
||||
template = CertificateTemplate.objects.filter(
|
||||
organization_id=org_id,
|
||||
course_key=course_key,
|
||||
mode=mode,
|
||||
is_active=True
|
||||
)
|
||||
# if don't template find by org and mode
|
||||
if not template and org_id and mode:
|
||||
template = CertificateTemplate.objects.filter(
|
||||
organization_id=org_id,
|
||||
mode=mode,
|
||||
is_active=True
|
||||
)
|
||||
# if don't template find by only org
|
||||
if not template and org_id:
|
||||
template = CertificateTemplate.objects.filter(
|
||||
organization_id=org_id,
|
||||
is_active=True
|
||||
)
|
||||
# if we still don't template find by only course mode
|
||||
if not template and mode:
|
||||
template = CertificateTemplate.objects.filter(
|
||||
mode=mode,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
return template[0].template if template else None
|
||||
|
||||
|
||||
def emit_certificate_event(event_name, user, course_id, course=None, event_data=None):
|
||||
"""
|
||||
Emits certificate event.
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Removing unique constraint on 'BadgeAssertion', fields ['course_id', 'user']
|
||||
db.delete_unique('certificates_badgeassertion', ['course_id', 'user_id'])
|
||||
|
||||
# Adding unique constraint on 'BadgeAssertion', fields ['course_id', 'user', 'mode']
|
||||
db.create_unique('certificates_badgeassertion', ['course_id', 'user_id', 'mode'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'BadgeAssertion', fields ['course_id', 'user', 'mode']
|
||||
db.delete_unique('certificates_badgeassertion', ['course_id', 'user_id', 'mode'])
|
||||
|
||||
# Adding unique constraint on 'BadgeAssertion', fields ['course_id', 'user']
|
||||
db.create_unique('certificates_badgeassertion', ['course_id', 'user_id'])
|
||||
|
||||
|
||||
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.badgeassertion': {
|
||||
'Meta': {'unique_together': "(('course_id', 'user', 'mode'),)", 'object_name': 'BadgeAssertion'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'default': 'None', 'max_length': '255', 'blank': 'True'}),
|
||||
'data': ('django.db.models.fields.TextField', [], {'default': "'{}'"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'certificates.badgeimageconfiguration': {
|
||||
'Meta': {'object_name': 'BadgeImageConfiguration'},
|
||||
'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '125'})
|
||||
},
|
||||
'certificates.certificategenerationconfiguration': {
|
||||
'Meta': {'ordering': "('-change_date',)", 'object_name': 'CertificateGenerationConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'certificates.certificategenerationcoursesetting': {
|
||||
'Meta': {'object_name': 'CertificateGenerationCourseSetting'},
|
||||
'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'})
|
||||
},
|
||||
'certificates.certificatehtmlviewconfiguration': {
|
||||
'Meta': {'ordering': "('-change_date',)", 'object_name': 'CertificateHtmlViewConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'configuration': ('django.db.models.fields.TextField', [], {}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'certificates.certificatewhitelist': {
|
||||
'Meta': {'object_name': 'CertificateWhitelist'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'default': 'None', '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.examplecertificate': {
|
||||
'Meta': {'object_name': 'ExampleCertificate'},
|
||||
'access_key': ('django.db.models.fields.CharField', [], {'default': "'25c5af67da3d47039aa8b00b3a929fa9'", 'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'download_url': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
|
||||
'error_reason': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
|
||||
'example_cert_set': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['certificates.ExampleCertificateSet']"}),
|
||||
'full_name': ('django.db.models.fields.CharField', [], {'default': "u'John Do\\xeb'", 'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'started'", 'max_length': '255'}),
|
||||
'template': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'uuid': ('django.db.models.fields.CharField', [], {'default': "'88190407a2f14c429a7b5336e3fb0189'", 'unique': 'True', 'max_length': '255', 'db_index': 'True'})
|
||||
},
|
||||
'certificates.examplecertificateset': {
|
||||
'Meta': {'object_name': 'ExampleCertificateSet'},
|
||||
'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'})
|
||||
},
|
||||
'certificates.generatedcertificate': {
|
||||
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'GeneratedCertificate'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'default': 'None', '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'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '32'}),
|
||||
'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']
|
||||
@@ -0,0 +1,197 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as 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 'CertificateTemplate'
|
||||
db.create_table('certificates_certificatetemplate', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)),
|
||||
('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)),
|
||||
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||
('description', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
|
||||
('template', self.gf('django.db.models.fields.TextField')()),
|
||||
('organization_id', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)),
|
||||
('course_key', self.gf('xmodule_django.models.CourseKeyField')(db_index=True, max_length=255, null=True, blank=True)),
|
||||
('mode', self.gf('django.db.models.fields.CharField')(default='honor', max_length=125, null=True, blank=True)),
|
||||
('is_active', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
))
|
||||
db.send_create_signal('certificates', ['CertificateTemplate'])
|
||||
|
||||
# Adding unique constraint on 'CertificateTemplate', fields ['organization_id', 'course_key', 'mode']
|
||||
db.create_unique('certificates_certificatetemplate', ['organization_id', 'course_key', 'mode'])
|
||||
|
||||
# Adding model 'CertificateTemplateAsset'
|
||||
db.create_table('certificates_certificatetemplateasset', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)),
|
||||
('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)),
|
||||
('description', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
|
||||
('asset', self.gf('django.db.models.fields.files.FileField')(max_length=255)),
|
||||
))
|
||||
db.send_create_signal('certificates', ['CertificateTemplateAsset'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'CertificateTemplate', fields ['organization_id', 'course_key', 'mode']
|
||||
db.delete_unique('certificates_certificatetemplate', ['organization_id', 'course_key', 'mode'])
|
||||
|
||||
# Deleting model 'CertificateTemplate'
|
||||
db.delete_table('certificates_certificatetemplate')
|
||||
|
||||
# Deleting model 'CertificateTemplateAsset'
|
||||
db.delete_table('certificates_certificatetemplateasset')
|
||||
|
||||
|
||||
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.badgeassertion': {
|
||||
'Meta': {'unique_together': "(('course_id', 'user', 'mode'),)", 'object_name': 'BadgeAssertion'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'default': 'None', 'max_length': '255', 'blank': 'True'}),
|
||||
'data': ('django.db.models.fields.TextField', [], {'default': "'{}'"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'certificates.badgeimageconfiguration': {
|
||||
'Meta': {'object_name': 'BadgeImageConfiguration'},
|
||||
'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '125'})
|
||||
},
|
||||
'certificates.certificategenerationconfiguration': {
|
||||
'Meta': {'ordering': "('-change_date',)", 'object_name': 'CertificateGenerationConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'certificates.certificategenerationcoursesetting': {
|
||||
'Meta': {'object_name': 'CertificateGenerationCourseSetting'},
|
||||
'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'})
|
||||
},
|
||||
'certificates.certificatehtmlviewconfiguration': {
|
||||
'Meta': {'ordering': "('-change_date',)", 'object_name': 'CertificateHtmlViewConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'configuration': ('django.db.models.fields.TextField', [], {}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'certificates.certificatetemplate': {
|
||||
'Meta': {'unique_together': "(('organization_id', 'course_key', 'mode'),)", 'object_name': 'CertificateTemplate'},
|
||||
'course_key': ('xmodule_django.models.CourseKeyField', [], {'db_index': 'True', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '125', 'null': 'True', 'blank': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'organization_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'template': ('django.db.models.fields.TextField', [], {})
|
||||
},
|
||||
'certificates.certificatetemplateasset': {
|
||||
'Meta': {'object_name': 'CertificateTemplateAsset'},
|
||||
'asset': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'})
|
||||
},
|
||||
'certificates.certificatewhitelist': {
|
||||
'Meta': {'object_name': 'CertificateWhitelist'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'default': 'None', '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.examplecertificate': {
|
||||
'Meta': {'object_name': 'ExampleCertificate'},
|
||||
'access_key': ('django.db.models.fields.CharField', [], {'default': "'f14d7721cd154a57a4fb52b9d4b4bc75'", 'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'download_url': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
|
||||
'error_reason': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
|
||||
'example_cert_set': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['certificates.ExampleCertificateSet']"}),
|
||||
'full_name': ('django.db.models.fields.CharField', [], {'default': "u'John Do\\xeb'", 'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'started'", 'max_length': '255'}),
|
||||
'template': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'uuid': ('django.db.models.fields.CharField', [], {'default': "'789810b9a54b4dd5bae3feec5b4e9fdb'", 'unique': 'True', 'max_length': '255', 'db_index': 'True'})
|
||||
},
|
||||
'certificates.examplecertificateset': {
|
||||
'Meta': {'object_name': 'ExampleCertificateSet'},
|
||||
'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'})
|
||||
},
|
||||
'certificates.generatedcertificate': {
|
||||
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'GeneratedCertificate'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'default': 'None', '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'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '32'}),
|
||||
'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']
|
||||
@@ -674,6 +674,88 @@ class BadgeImageConfiguration(models.Model):
|
||||
return cls.objects.get(default=True).icon
|
||||
|
||||
|
||||
class CertificateTemplate(TimeStampedModel):
|
||||
"""A set of custom web certificate templates.
|
||||
|
||||
Web certificate templates are Django web templates
|
||||
to replace PDF certificate.
|
||||
|
||||
A particular course may have several kinds of certificate templates
|
||||
(e.g. honor and verified).
|
||||
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
help_text=_(u'Name of template.'),
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_(u'Description and/or admin notes.'),
|
||||
)
|
||||
template = models.TextField(
|
||||
help_text=_(u'Django template HTML.'),
|
||||
)
|
||||
organization_id = models.IntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text=_(u'Organization of template.'),
|
||||
)
|
||||
course_key = CourseKeyField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
mode = models.CharField(
|
||||
max_length=125,
|
||||
choices=GeneratedCertificate.MODES,
|
||||
default=GeneratedCertificate.MODES.honor,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_(u'The course mode for this template.'),
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
help_text=_(u'On/Off switch.'),
|
||||
default=False,
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % (self.name, )
|
||||
|
||||
class Meta(object): # pylint: disable=missing-docstring
|
||||
get_latest_by = 'created'
|
||||
unique_together = (('organization_id', 'course_key', 'mode'),)
|
||||
|
||||
|
||||
class CertificateTemplateAsset(TimeStampedModel):
|
||||
"""A set of assets to be used in custom web certificate templates.
|
||||
|
||||
This model stores assets used in custom web certificate templates
|
||||
such as image, css files.
|
||||
|
||||
"""
|
||||
description = models.CharField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_(u'Description of the asset.'),
|
||||
)
|
||||
asset = models.FileField(
|
||||
max_length=255,
|
||||
upload_to='certificate_template_assets',
|
||||
help_text=_(u'Asset file. It could be an image or css file.'),
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % (self.asset.url, ) # pylint: disable=no-member
|
||||
|
||||
class Meta(object): # pylint: disable=missing-docstring
|
||||
get_latest_by = 'created'
|
||||
|
||||
|
||||
@receiver(post_save, sender=GeneratedCertificate)
|
||||
#pylint: disable=unused-argument
|
||||
def create_badge(sender, instance, **kwargs):
|
||||
|
||||
@@ -30,6 +30,7 @@ from certificates.models import (
|
||||
CertificateStatuses,
|
||||
CertificateHtmlViewConfiguration,
|
||||
CertificateSocialNetworks,
|
||||
CertificateTemplate,
|
||||
)
|
||||
|
||||
from certificates.tests.factories import (
|
||||
@@ -45,6 +46,11 @@ FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
|
||||
FEATURES_WITH_CERTS_DISABLED = settings.FEATURES.copy()
|
||||
FEATURES_WITH_CERTS_DISABLED['CERTIFICATES_HTML_VIEW'] = False
|
||||
|
||||
FEATURES_WITH_CUSTOM_CERTS_ENABLED = {
|
||||
"CUSTOM_CERTIFICATE_TEMPLATES_ENABLED": True
|
||||
}
|
||||
FEATURES_WITH_CUSTOM_CERTS_ENABLED.update(FEATURES_WITH_CERTS_ENABLED)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
@ddt.ddt
|
||||
@@ -427,6 +433,30 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
|
||||
self.course.save()
|
||||
self.store.update_item(self.course, self.user.id)
|
||||
|
||||
def _create_custom_template(self, org_id=None, mode=None, course_key=None):
|
||||
"""
|
||||
Creates a custom certificate template entry in DB.
|
||||
"""
|
||||
template_html = """
|
||||
<html>
|
||||
<body>
|
||||
lang: ${LANGUAGE_CODE}
|
||||
course name: ${accomplishment_copy_course_name}
|
||||
mode: ${course_mode}
|
||||
${accomplishment_copy_course_description}
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
template = CertificateTemplate(
|
||||
name='custom template',
|
||||
template=template_html,
|
||||
organization_id=org_id,
|
||||
course_key=course_key,
|
||||
mode=mode,
|
||||
is_active=True
|
||||
)
|
||||
template.save()
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_rendering_course_organization_data(self):
|
||||
"""
|
||||
@@ -724,6 +754,75 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
|
||||
response_json = json.loads(response.content)
|
||||
self.assertEqual(CertificateStatuses.generating, response_json['add_status'])
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
|
||||
@override_settings(LANGUAGE_CODE='fr')
|
||||
def test_certificate_custom_template_with_org_mode_course(self):
|
||||
"""
|
||||
Tests custom template search and rendering.
|
||||
"""
|
||||
self._add_course_certificates(count=1, signatory_count=2)
|
||||
self._create_custom_template(1, mode='honor', course_key=unicode(self.course.id))
|
||||
self._create_custom_template(2, mode='honor')
|
||||
test_url = get_certificate_url(
|
||||
user_id=self.user.id,
|
||||
course_id=unicode(self.course.id)
|
||||
)
|
||||
|
||||
with patch('certificates.api.get_course_organizations') as mock_get_orgs:
|
||||
mock_get_orgs.side_effect = [
|
||||
[{"id": 1, "name": "organization name"}],
|
||||
[{"id": 2, "name": "organization name 2"}],
|
||||
]
|
||||
response = self.client.get(test_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'lang: fr')
|
||||
self.assertContains(response, 'course name: {}'.format(self.course.display_name))
|
||||
# test with second organization template
|
||||
response = self.client.get(test_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'lang: fr')
|
||||
self.assertContains(response, 'course name: {}'.format(self.course.display_name))
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
|
||||
def test_certificate_custom_template_with_org(self):
|
||||
"""
|
||||
Tests custom template search if if have a single template for all courses of organization.
|
||||
"""
|
||||
self._add_course_certificates(count=1, signatory_count=2)
|
||||
self._create_custom_template(1)
|
||||
self._create_custom_template(1, mode='honor')
|
||||
test_url = get_certificate_url(
|
||||
user_id=self.user.id,
|
||||
course_id=unicode(self.course.id)
|
||||
)
|
||||
|
||||
with patch('certificates.api.get_course_organizations') as mock_get_orgs:
|
||||
mock_get_orgs.side_effect = [
|
||||
[{"id": 1, "name": "organization name"}],
|
||||
]
|
||||
response = self.client.get(test_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'course name: {}'.format(self.course.display_name))
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
|
||||
def test_certificate_custom_template_with_course_mode(self):
|
||||
"""
|
||||
Tests custom template search if if have a single template for a course mode.
|
||||
"""
|
||||
mode = 'honor'
|
||||
self._add_course_certificates(count=1, signatory_count=2)
|
||||
self._create_custom_template(mode=mode)
|
||||
test_url = get_certificate_url(
|
||||
user_id=self.user.id,
|
||||
course_id=unicode(self.course.id)
|
||||
)
|
||||
|
||||
with patch('certificates.api.get_course_organizations') as mock_get_orgs:
|
||||
mock_get_orgs.return_value = []
|
||||
response = self.client.get(test_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'mode: {}'.format(mode))
|
||||
|
||||
|
||||
class TrackShareRedirectTest(UrlResetMixin, ModuleStoreTestCase, EventTrackingTestCase):
|
||||
"""
|
||||
|
||||
@@ -7,22 +7,27 @@ import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpResponse
|
||||
from django.template import RequestContext
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from courseware.courses import course_image_url
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from edxmako.template import Template
|
||||
from eventtracking import tracker
|
||||
from microsite_configuration import microsite
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from microsite_configuration import microsite
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from eventtracking import tracker
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from student.models import LinkedInAddToProfileConfiguration
|
||||
from courseware.courses import course_image_url
|
||||
from util import organizations_helpers as organization_api
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from certificates.api import (
|
||||
get_active_web_certificate,
|
||||
get_certificate_url,
|
||||
emit_certificate_event,
|
||||
has_html_certificates_enabled
|
||||
has_html_certificates_enabled,
|
||||
get_certificate_template
|
||||
)
|
||||
from certificates.models import (
|
||||
GeneratedCertificate,
|
||||
@@ -31,7 +36,6 @@ from certificates.models import (
|
||||
BadgeAssertion
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -401,4 +405,11 @@ def render_html_view(request, user_id, course_id):
|
||||
context.update(course.cert_html_view_overrides)
|
||||
|
||||
# FINALLY, generate and send the output the client
|
||||
if settings.FEATURES.get('CUSTOM_CERTIFICATE_TEMPLATES_ENABLED', False):
|
||||
custom_template = get_certificate_template(course_key, user_certificate.mode)
|
||||
if custom_template:
|
||||
template = Template(custom_template)
|
||||
context = RequestContext(request, context)
|
||||
return HttpResponse(template.render(context))
|
||||
|
||||
return render_to_response("certificates/valid.html", context)
|
||||
|
||||
Reference in New Issue
Block a user