Merge pull request #7113 from edx/mattdrayer/certs-web-view
Added Certificates Web/HTML View
This commit is contained in:
@@ -670,6 +670,13 @@ class CourseFields(object):
|
||||
scope=Scope.settings,
|
||||
default=""
|
||||
)
|
||||
cert_html_view_overrides = Dict(
|
||||
# Translators: This field is the container for course-specific certifcate configuration values
|
||||
display_name=_("Certificate Web/HTML View Overrides"),
|
||||
# Translators: These overrides allow for an alternative configuration of the certificate web view
|
||||
help=_("Enter course-specific overrides for the Web/HTML template parameters here (JSON format)"),
|
||||
scope=Scope.settings,
|
||||
)
|
||||
|
||||
# An extra property is used rather than the wiki_slug/number because
|
||||
# there are courses that change the number for different runs. This allows
|
||||
|
||||
@@ -150,6 +150,7 @@ class AdvancedSettingsPage(CoursePage):
|
||||
'allow_anonymous',
|
||||
'allow_anonymous_to_peers',
|
||||
'allow_public_wiki_access',
|
||||
'cert_html_view_overrides',
|
||||
'cert_name_long',
|
||||
'cert_name_short',
|
||||
'certificates_display_behavior',
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
django admin pages for certificates models
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from certificates.models import CertificateGenerationConfiguration
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from certificates.models import CertificateGenerationConfiguration, CertificateHtmlViewConfiguration
|
||||
|
||||
|
||||
admin.site.register(CertificateGenerationConfiguration)
|
||||
admin.site.register(CertificateHtmlViewConfiguration, ConfigurationModelAdmin)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# -*- 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 'CertificateHtmlViewConfiguration'
|
||||
db.create_table('certificates_certificatehtmlviewconfiguration', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)),
|
||||
('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('configuration', self.gf('django.db.models.fields.TextField')()),
|
||||
))
|
||||
db.send_create_signal('certificates', ['CertificateHtmlViewConfiguration'])
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'CertificateHtmlViewConfiguration'
|
||||
db.delete_table('certificates_certificatehtmlviewconfiguration')
|
||||
|
||||
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.certificategenerationconfiguration': {
|
||||
'Meta': {'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.certificatehtmlviewconfiguration': {
|
||||
'Meta': {'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.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,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
"""
|
||||
Bootstraps the HTML view template with some default configuration parameters
|
||||
"""
|
||||
json_config = """{
|
||||
"default": {
|
||||
"accomplishment_class_append": "accomplishment--certificate--honorcode",
|
||||
"certificate_verify_url_prefix": "https://verify-test.edx.org/cert/",
|
||||
"certificate_verify_url_suffix": "/verify.html",
|
||||
"company_about_url": "http://www.edx.org/about-us",
|
||||
"company_courselist_url": "http://www.edx.org/course-list",
|
||||
"company_careers_url": "http://www.edx.org/jobs",
|
||||
"company_contact_url": "http://www.edx.org/contact-us",
|
||||
"platform_name": "edX",
|
||||
"company_privacy_url": "http://www.edx.org/edx-privacy-policy",
|
||||
"company_tos_url": "http://www.edx.org/edx-terms-service",
|
||||
"company_verified_certificate_url": "http://www.edx.org/verified-certificate",
|
||||
"document_script_src_modernizr": "https://verify-test.edx.org/v2/static/js/vendor/modernizr-2.6.2.min.js",
|
||||
"document_stylesheet_url_normalize": "https://verify-test.edx.org/v2/static/css/vendor/normalize.css",
|
||||
"document_stylesheet_url_fontawesome": "https://verify-test.edx.org/v2/static/css/vendor/font-awesome.css",
|
||||
"document_stylesheet_url_application": "https://verify-test.edx.org/v2/static/css/style-application.css",
|
||||
"logo_src": "https://verify-test.edx.org/v2/static/images/logo-edx.svg",
|
||||
"logo_url": "http://www.edx.org"
|
||||
},
|
||||
"honor": {
|
||||
"certificate_type": "Honor Code",
|
||||
"document_body_class_append": "is-honorcode"
|
||||
},
|
||||
"verified": {
|
||||
"certificate_type": "Verified",
|
||||
"document_body_class_append": "is-idverified"
|
||||
},
|
||||
"xseries": {
|
||||
"certificate_type": "XSeries",
|
||||
"document_body_class_append": "is-xseries",
|
||||
"document_script_src_modernizr": "https://verify-test.edx.org/xseries/static/js/vendor/modernizr-2.6.2.min.js",
|
||||
"document_stylesheet_url_normalize": "https://verify-test.edx.org/xseries/static/css/vendor/normalize.css",
|
||||
"document_stylesheet_url_fontawesome": "https://verify-test.edx.org/xseries/static/css/vendor/font-awesome.css",
|
||||
"document_stylesheet_url_application": "https://verify-test.edx.org/xseries/static/css/style-application.css",
|
||||
"logo_src": "https://verify-test.edx.org/xseries/static/images/logo-edx.svg"
|
||||
}
|
||||
}"""
|
||||
orm.CertificateHtmlViewConfiguration.objects.create(
|
||||
configuration=json_config,
|
||||
enabled=False,
|
||||
)
|
||||
|
||||
def backwards(self, orm):
|
||||
"Write your backwards methods here."
|
||||
|
||||
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.certificategenerationconfiguration': {
|
||||
'Meta': {'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': {'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': "'0836d966ec2047e18114969f89ee5270'", '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': "'558539cc3a114c48a1cc404a73d4cfcd'", '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']
|
||||
symmetrical = True
|
||||
@@ -46,9 +46,12 @@ Eligibility:
|
||||
unless he has allow_certificate set to False.
|
||||
"""
|
||||
from datetime import datetime
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models, transaction
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
@@ -503,3 +506,44 @@ class CertificateGenerationConfiguration(ConfigurationModel):
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CertificateHtmlViewConfiguration(ConfigurationModel):
|
||||
"""
|
||||
Static values for certificate HTML view context parameters.
|
||||
Default values will be applied across all certificate types (course modes)
|
||||
Matching 'mode' overrides will be used instead of defaults, where applicable
|
||||
Example configuration :
|
||||
{
|
||||
"default": {
|
||||
"url": "http://www.edx.org",
|
||||
"logo_src": "http://www.edx.org/static/images/logo.png",
|
||||
"logo_alt": "Valid Certificate"
|
||||
},
|
||||
"honor": {
|
||||
"logo_src": "http://www.edx.org/static/images/honor-logo.png",
|
||||
"logo_alt": "Honor Certificate"
|
||||
}
|
||||
}
|
||||
"""
|
||||
configuration = models.TextField(
|
||||
help_text="Certificate HTML View Parameters (JSON)"
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Ensures configuration field contains valid JSON.
|
||||
"""
|
||||
try:
|
||||
json.loads(self.configuration)
|
||||
except ValueError:
|
||||
raise ValidationError('Must be valid JSON string.')
|
||||
|
||||
@classmethod
|
||||
def get_config(cls):
|
||||
"""
|
||||
Retrieves the configuration field value from the database
|
||||
"""
|
||||
instance = cls.current()
|
||||
json_data = json.loads(instance.configuration) if instance.enabled else {}
|
||||
return json_data
|
||||
|
||||
@@ -2,7 +2,7 @@ from factory.django import DjangoModelFactory
|
||||
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
from certificates.models import GeneratedCertificate, CertificateStatuses
|
||||
from certificates.models import GeneratedCertificate, CertificateStatuses, CertificateHtmlViewConfiguration
|
||||
|
||||
|
||||
# Factories are self documenting
|
||||
@@ -15,3 +15,48 @@ class GeneratedCertificateFactory(DjangoModelFactory):
|
||||
status = CertificateStatuses.unavailable
|
||||
mode = GeneratedCertificate.MODES.honor
|
||||
name = ''
|
||||
|
||||
|
||||
class CertificateHtmlViewConfigurationFactory(DjangoModelFactory):
|
||||
|
||||
FACTORY_FOR = CertificateHtmlViewConfiguration
|
||||
|
||||
enabled = True
|
||||
configuration = """{
|
||||
"default": {
|
||||
"accomplishment_class_append": "accomplishment--certificate--honorcode",
|
||||
"certificate_verify_url_prefix": "https://verify-test.edx.org/cert/",
|
||||
"certificate_verify_url_suffix": "/verify.html",
|
||||
"company_about_url": "http://www.edx.org/about-us",
|
||||
"company_courselist_url": "http://www.edx.org/course-list",
|
||||
"company_careers_url": "http://www.edx.org/jobs",
|
||||
"company_contact_url": "http://www.edx.org/contact-us",
|
||||
"platform_name": "edX",
|
||||
"company_privacy_url": "http://www.edx.org/edx-privacy-policy",
|
||||
"company_tos_url": "http://www.edx.org/edx-terms-service",
|
||||
"company_verified_certificate_url": "http://www.edx.org/verified-certificate",
|
||||
"document_script_src_modernizr": "https://verify-test.edx.org/v2/static/js/vendor/modernizr-2.6.2.min.js",
|
||||
"document_stylesheet_url_normalize": "https://verify-test.edx.org/v2/static/css/vendor/normalize.css",
|
||||
"document_stylesheet_url_fontawesome": "https://verify-test.edx.org/v2/static/css/vendor/font-awesome.css",
|
||||
"document_stylesheet_url_application": "https://verify-test.edx.org/v2/static/css/style-application.css",
|
||||
"logo_src": "https://verify-test.edx.org/v2/static/images/logo-edx.svg",
|
||||
"logo_url": "http://www.edx.org"
|
||||
},
|
||||
"honor": {
|
||||
"certificate_type": "Honor Code",
|
||||
"document_body_class_append": "is-honorcode"
|
||||
},
|
||||
"verified": {
|
||||
"certificate_type": "Verified",
|
||||
"document_body_class_append": "is-idverified"
|
||||
},
|
||||
"xseries": {
|
||||
"certificate_type": "XSeries",
|
||||
"document_body_class_append": "is-xseries",
|
||||
"document_script_src_modernizr": "https://verify-test.edx.org/xseries/static/js/vendor/modernizr-2.6.2.min.js",
|
||||
"document_stylesheet_url_normalize": "https://verify-test.edx.org/xseries/static/css/vendor/normalize.css",
|
||||
"document_stylesheet_url_fontawesome": "https://verify-test.edx.org/xseries/static/css/vendor/font-awesome.css",
|
||||
"document_stylesheet_url_application": "https://verify-test.edx.org/xseries/static/css/style-application.css",
|
||||
"logo_src": "https://verify-test.edx.org/xseries/static/images/logo-edx.svg"
|
||||
}
|
||||
}"""
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
"""Tests for certificate Django models. """
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from certificates.models import (
|
||||
ExampleCertificate,
|
||||
ExampleCertificateSet
|
||||
ExampleCertificateSet,
|
||||
CertificateHtmlViewConfiguration
|
||||
)
|
||||
|
||||
FEATURES_INVALID_FILE_PATH = settings.FEATURES.copy()
|
||||
FEATURES_INVALID_FILE_PATH['CERTS_HTML_VIEW_CONFIG_PATH'] = 'invalid/path/to/config.json'
|
||||
|
||||
|
||||
class ExampleCertificateTest(TestCase):
|
||||
"""Tests for the ExampleCertificate model. """
|
||||
@@ -71,3 +78,73 @@ class ExampleCertificateTest(TestCase):
|
||||
other_course = CourseLocator(org='other', course='other', run='other')
|
||||
result = ExampleCertificateSet.latest_status(other_course)
|
||||
self.assertIs(result, None)
|
||||
|
||||
|
||||
class CertificateHtmlViewConfigurationTest(TestCase):
|
||||
"""
|
||||
Test the CertificateHtmlViewConfiguration model.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(CertificateHtmlViewConfigurationTest, self).setUp()
|
||||
self.configuration_string = """{
|
||||
"default": {
|
||||
"url": "http://www.edx.org",
|
||||
"logo_src": "http://www.edx.org/static/images/logo.png",
|
||||
"logo_alt": "Valid Certificate"
|
||||
},
|
||||
"honor": {
|
||||
"logo_src": "http://www.edx.org/static/images/honor-logo.png",
|
||||
"logo_alt": "Honor Certificate"
|
||||
}
|
||||
}"""
|
||||
self.config = CertificateHtmlViewConfiguration(configuration=self.configuration_string)
|
||||
|
||||
def test_create(self):
|
||||
"""
|
||||
Tests creation of configuration.
|
||||
"""
|
||||
self.config.save()
|
||||
self.assertEquals(self.config.configuration, self.configuration_string)
|
||||
|
||||
def test_clean_bad_json(self):
|
||||
"""
|
||||
Tests if bad JSON string was given.
|
||||
"""
|
||||
self.config = CertificateHtmlViewConfiguration(configuration='{"bad":"test"')
|
||||
self.assertRaises(ValidationError, self.config.clean)
|
||||
|
||||
def test_get(self):
|
||||
"""
|
||||
Tests get configuration from saved string.
|
||||
"""
|
||||
self.config.enabled = True
|
||||
self.config.save()
|
||||
expected_config = {
|
||||
"default": {
|
||||
"url": "http://www.edx.org",
|
||||
"logo_src": "http://www.edx.org/static/images/logo.png",
|
||||
"logo_alt": "Valid Certificate"
|
||||
},
|
||||
"honor": {
|
||||
"logo_src": "http://www.edx.org/static/images/honor-logo.png",
|
||||
"logo_alt": "Honor Certificate"
|
||||
}
|
||||
}
|
||||
self.assertEquals(self.config.get_config(), expected_config)
|
||||
|
||||
def test_get_not_enabled_returns_blank(self):
|
||||
"""
|
||||
Tests get configuration that is not enabled.
|
||||
"""
|
||||
self.config.enabled = False
|
||||
self.config.save()
|
||||
self.assertEquals(len(self.config.get_config()), 0)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_INVALID_FILE_PATH)
|
||||
def test_get_no_database_no_file(self):
|
||||
"""
|
||||
Tests get configuration that is not enabled.
|
||||
"""
|
||||
self.config.configuration = ''
|
||||
self.config.save()
|
||||
self.assertEquals(self.config.get_config(), {})
|
||||
|
||||
@@ -2,14 +2,28 @@
|
||||
|
||||
import json
|
||||
import ddt
|
||||
from uuid import uuid4
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
from certificates.models import ExampleCertificateSet, ExampleCertificate
|
||||
from certificates.models import ExampleCertificateSet, ExampleCertificate, GeneratedCertificate
|
||||
from certificates.tests.factories import CertificateHtmlViewConfigurationFactory
|
||||
|
||||
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
|
||||
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
|
||||
|
||||
FEATURES_WITH_CERTS_DISABLED = settings.FEATURES.copy()
|
||||
FEATURES_WITH_CERTS_DISABLED['CERTIFICATES_HTML_VIEW'] = False
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -151,3 +165,90 @@ class UpdateExampleCertificateViewTest(TestCase):
|
||||
content = json.loads(response.content)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(content['return_code'], 0)
|
||||
|
||||
|
||||
class CertificatesViewsTests(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the manual refund page
|
||||
"""
|
||||
def setUp(self):
|
||||
super(CertificatesViewsTests, self).setUp()
|
||||
self.client = Client()
|
||||
self.course = CourseFactory.create(
|
||||
org='testorg', number='run1', display_name='refundable course'
|
||||
)
|
||||
self.course_id = self.course.location.course_key
|
||||
self.user = UserFactory.create(
|
||||
email='joe_user@edx.org',
|
||||
username='joeuser',
|
||||
password='foo'
|
||||
)
|
||||
self.user.profile.name = "Joe User"
|
||||
self.user.profile.save()
|
||||
self.client.login(username=self.user.username, password='foo')
|
||||
|
||||
self.cert = GeneratedCertificate.objects.create(
|
||||
user=self.user,
|
||||
course_id=self.course_id,
|
||||
verify_uuid=uuid4(),
|
||||
download_uuid=uuid4(),
|
||||
grade="0.95",
|
||||
key='the_key',
|
||||
distinction=True,
|
||||
status='generated',
|
||||
mode='honor',
|
||||
name=self.user.profile.name,
|
||||
)
|
||||
CertificateHtmlViewConfigurationFactory.create()
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_render_html_view_valid_certificate(self):
|
||||
test_url = '/certificates/html?course={}'.format(unicode(self.course.id))
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn(str(self.cert.verify_uuid), response.content)
|
||||
|
||||
# Hit any "verified" mode-specific branches
|
||||
self.cert.mode = 'verified'
|
||||
self.cert.save()
|
||||
test_url = '/certificates/html?course={}'.format(unicode(self.course.id))
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn(str(self.cert.verify_uuid), response.content)
|
||||
|
||||
# Hit any 'xseries' mode-specific branches
|
||||
self.cert.mode = 'xseries'
|
||||
self.cert.save()
|
||||
test_url = '/certificates/html?course={}'.format(unicode(self.course.id))
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn(str(self.cert.verify_uuid), response.content)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_DISABLED)
|
||||
def test_render_html_view_invalid_feature_flag(self):
|
||||
test_url = '/certificates/html?course={}'.format(unicode(self.course.id))
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('invalid', response.content)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_render_html_view_missing_course_id(self):
|
||||
test_url = '/certificates/html'
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('invalid', response.content)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_render_html_view_invalid_course_id(self):
|
||||
test_url = '/certificates/html?course=az-23423-4vs'
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('invalid', response.content)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_render_html_view_invalid_course(self):
|
||||
test_url = '/certificates/html?course=missing/course/key'
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('invalid', response.content)
|
||||
|
||||
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
|
||||
def test_render_html_view_invalid_certificate(self):
|
||||
self.cert.delete()
|
||||
self.assertEqual(len(GeneratedCertificate.objects.all()), 0)
|
||||
test_url = '/certificates/html?course={}'.format(unicode(self.course.id))
|
||||
response = self.client.get(test_url)
|
||||
self.assertIn('invalid', response.content)
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
"""URL handlers related to certificate handling by LMS"""
|
||||
from datetime import datetime
|
||||
import dogstats_wrapper as dog_stats_api
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpResponse, Http404, HttpResponseForbidden
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
@@ -13,13 +17,17 @@ from certificates.models import (
|
||||
certificate_status_for_student,
|
||||
CertificateStatuses,
|
||||
GeneratedCertificate,
|
||||
ExampleCertificate
|
||||
ExampleCertificate,
|
||||
CertificateHtmlViewConfiguration
|
||||
)
|
||||
from certificates.queue import XQueueCertInterface
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from util.json_request import JsonResponse, JsonResponseBadRequest
|
||||
from util.bad_request_rate_limiter import BadRequestRateLimiter
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -224,3 +232,215 @@ def update_example_certificate(request):
|
||||
|
||||
# Let the XQueue know that we handled the response
|
||||
return JsonResponse({'return_code': 0})
|
||||
|
||||
|
||||
# pylint: disable=too-many-statements, bad-continuation
|
||||
@login_required
|
||||
def render_html_view(request):
|
||||
"""
|
||||
This view generates an HTML representation of the specified student's certificate
|
||||
If a certificate is not available, we display a "Sorry!" screen instead
|
||||
"""
|
||||
invalid_template_path = 'certificates/invalid.html'
|
||||
|
||||
# Feature Flag check
|
||||
if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
|
||||
return render_to_response(invalid_template_path)
|
||||
|
||||
context = {}
|
||||
course_id = request.GET.get('course', None)
|
||||
context['course'] = course_id
|
||||
if not course_id:
|
||||
return render_to_response(invalid_template_path, context)
|
||||
|
||||
# Course Lookup
|
||||
try:
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
except InvalidKeyError:
|
||||
return render_to_response(invalid_template_path, context)
|
||||
course = modulestore().get_course(course_key)
|
||||
if not course:
|
||||
return render_to_response(invalid_template_path, context)
|
||||
|
||||
# Certificate Lookup
|
||||
try:
|
||||
certificate = GeneratedCertificate.objects.get(
|
||||
user=request.user,
|
||||
course_id=course_key
|
||||
)
|
||||
except GeneratedCertificate.DoesNotExist:
|
||||
return render_to_response(invalid_template_path, context)
|
||||
|
||||
# Load static output values from configuration,
|
||||
configuration = CertificateHtmlViewConfiguration.get_config()
|
||||
context = configuration.get('default', {})
|
||||
# Override the defaults with any mode-specific static values
|
||||
context.update(configuration.get(certificate.mode, {}))
|
||||
# Override further with any course-specific static values
|
||||
context.update(course.cert_html_view_overrides)
|
||||
|
||||
# Populate dynamic output values using the course/certificate data loaded above
|
||||
user_fullname = request.user.profile.name
|
||||
platform_name = context.get('platform_name')
|
||||
context['accomplishment_copy_name'] = user_fullname
|
||||
context['accomplishment_copy_course_org'] = course.org
|
||||
context['accomplishment_copy_course_name'] = course.display_name
|
||||
context['certificate_id_number'] = certificate.verify_uuid
|
||||
context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format(
|
||||
prefix=context.get('certificate_verify_url_prefix'),
|
||||
uuid=certificate.verify_uuid,
|
||||
suffix=context.get('certificate_verify_url_suffix')
|
||||
)
|
||||
context['logo_alt'] = platform_name
|
||||
|
||||
accd_course_org_html = '<span class="detail--xuniversity">{partner_name}</span>'.format(partner_name=course.org)
|
||||
accd_platform_name_html = '<span class="detail--company">{platform_name}</span>'.format(platform_name=platform_name)
|
||||
# Translators: This line appears on the certificate after the name of a course, and provides more
|
||||
# information about the organizations providing the course material to platform users
|
||||
context['accomplishment_copy_course_description'] = _('a course of study offered by {partner_name}, '
|
||||
'through {platform_name}.').format(
|
||||
partner_name=accd_course_org_html,
|
||||
platform_name=accd_platform_name_html
|
||||
)
|
||||
|
||||
context['accomplishment_more_title'] = _("More Information About {user_name}'s Certificate:").format(
|
||||
user_name=user_fullname
|
||||
)
|
||||
|
||||
# Translators: This line appears on the page just before the generation date for the certificate
|
||||
context['certificate_date_issued_title'] = _("Issued On:")
|
||||
|
||||
# Translators: The format of the date includes the full name of the month
|
||||
context['certificate_date_issued'] = _('{month} {day}, {year}').format(
|
||||
month=certificate.modified_date.strftime("%B"),
|
||||
day=certificate.modified_date.day,
|
||||
year=certificate.modified_date.year
|
||||
)
|
||||
|
||||
# Translators: The Certificate ID Number is an alphanumeric value unique to each individual certificate
|
||||
context['certificate_id_number_title'] = _('Certificate ID Number')
|
||||
|
||||
context['certificate_info_title'] = _('About {platform_name} Certificates').format(
|
||||
platform_name=platform_name
|
||||
)
|
||||
|
||||
# Translators: This text describes the purpose (and therefore, value) of a course certificate
|
||||
# 'verifying your identity' refers to the process for establishing the authenticity of the student
|
||||
context['certificate_info_description'] = _("{platform_name} acknowledges achievements through certificates, which "
|
||||
"are awarded for various activities {platform_name} students complete "
|
||||
"under the <a href='{tos_url}'>{platform_name} Honor Code</a>. Some "
|
||||
"certificates require completing additional steps, such as "
|
||||
"<a href='{verified_cert_url}'> verifying your identity</a>.").format(
|
||||
platform_name=platform_name,
|
||||
tos_url=context.get('company_tos_url'),
|
||||
verified_cert_url=context.get('company_verified_certificate_url')
|
||||
)
|
||||
|
||||
# Translators: Certificate Types correspond to the different enrollment options available for a given course
|
||||
context['certificate_type_title'] = _('{certificate_type} Certfificate').format(
|
||||
certificate_type=context.get('certificate_type')
|
||||
)
|
||||
|
||||
context['certificate_verify_title'] = _("How {platform_name} Validates Student Certificates").format(
|
||||
platform_name=platform_name
|
||||
)
|
||||
|
||||
# Translators: This text describes the validation mechanism for a certificate file (known as GPG security)
|
||||
context['certificate_verify_description'] = _('Certificates issued by {platform_name} are signed by a gpg key so '
|
||||
'that they can be validated independently by anyone with the '
|
||||
'{platform_name} public key. For independent verification, '
|
||||
'{platform_name} uses what is called a '
|
||||
'"detached signature""".').format(platform_name=platform_name)
|
||||
|
||||
context['certificate_verify_urltext'] = _("Validate this certificate for yourself")
|
||||
|
||||
# Translators: This text describes (at a high level) the mission and charter the edX platform and organization
|
||||
context['company_about_description'] = _("{platform_name} offers interactive online classes and MOOCs from the "
|
||||
"world's best universities, including MIT, Harvard, Berkeley, University "
|
||||
"of Texas, and many others. {platform_name} is a non-profit online "
|
||||
"initiative created by founding partners Harvard and MIT.").format(
|
||||
platform_name=platform_name
|
||||
)
|
||||
|
||||
context['company_about_title'] = _("About {platform_name}").format(platform_name=platform_name)
|
||||
|
||||
context['company_about_urltext'] = _("Learn more about {platform_name}").format(platform_name=platform_name)
|
||||
|
||||
context['company_courselist_urltext'] = _("Learn with {platform_name}").format(platform_name=platform_name)
|
||||
|
||||
context['company_careers_urltext'] = _("Work at {platform_name}").format(platform_name=platform_name)
|
||||
|
||||
context['company_contact_urltext'] = _("Contact {platform_name}").format(platform_name=platform_name)
|
||||
|
||||
context['company_privacy_urltext'] = _("Privacy Policy")
|
||||
|
||||
context['company_tos_urltext'] = _("Terms of Service & Honor Code")
|
||||
|
||||
# Translators: This text appears near the top of the certficate and describes the guarantee provided by edX
|
||||
context['document_banner'] = _("{platform_name} acknowledges the following student accomplishment").format(
|
||||
platform_name=platform_name
|
||||
)
|
||||
|
||||
context['logo_subtitle'] = _("Certificate Validation")
|
||||
|
||||
if certificate.mode == 'honor':
|
||||
# Translators: This text describes the 'Honor' course certificate type.
|
||||
context['certificate_type_description'] = _("An {cert_type} Certificate signifies that an {platform_name} "
|
||||
"learner has agreed to abide by {platform_name}'s honor code and "
|
||||
"completed all of the required tasks for this course under its "
|
||||
"guidelines.").format(
|
||||
cert_type=context.get('certificate_type'),
|
||||
platform_name=platform_name
|
||||
)
|
||||
elif certificate.mode == 'verified':
|
||||
# Translators: This text describes the 'ID Verified' course certificate type, which is a higher level of
|
||||
# verification offered by edX. This type of verification is useful for professional education/certifications
|
||||
context['certificate_type_description'] = _("An {cert_type} Certificate signifies that an {platform_name} "
|
||||
"learner has agreed to abide by {platform_name}'s honor code and "
|
||||
"completed all of the required tasks for this course under its "
|
||||
"guidelines, as well as having their photo ID checked to verify "
|
||||
"their identity.").format(
|
||||
cert_type=context.get('certificate_type'),
|
||||
platform_name=platform_name
|
||||
)
|
||||
elif certificate.mode == 'xseries':
|
||||
# Translators: This text describes the 'XSeries' course certificate type. An XSeries is a collection of
|
||||
# courses related to each other in a meaningful way, such as a specific topic or theme, or even an organization
|
||||
context['certificate_type_description'] = _("An {cert_type} Certificate demonstrates a high level of "
|
||||
"achievement in a program of study, and includes verification of "
|
||||
"the student's identity.").format(
|
||||
cert_type=context.get('certificate_type')
|
||||
)
|
||||
|
||||
# Translators: This is the copyright line which appears at the bottom of the certificate page/screen
|
||||
context['copyright_text'] = _('© {year} {platform_name}. All rights reserved.').format(
|
||||
year=datetime.now().year,
|
||||
platform_name=platform_name
|
||||
)
|
||||
|
||||
# Translators: This text represents the verification of the certificate
|
||||
context['document_meta_description'] = _('This is a valid {platform_name} certificate for {user_name}, '
|
||||
'who participated in {partner_name} {course_number}').format(
|
||||
platform_name=platform_name,
|
||||
user_name=user_fullname,
|
||||
partner_name=course.org,
|
||||
course_number=course.number
|
||||
)
|
||||
|
||||
# Translators: This text is bound to the HTML 'title' element of the page and appears in the browser title bar
|
||||
context['document_title'] = _("Valid {partner_name} {course_number} Certificate | {platform_name}").format(
|
||||
partner_name=course.org,
|
||||
course_number=course.number,
|
||||
platform_name=platform_name
|
||||
)
|
||||
|
||||
# Translators: This text fragment appears after the student's name (displayed in a large font) on the certificate
|
||||
# screen. The text describes the accomplishment represented by the certificate information displayed to the user
|
||||
context['accomplishment_copy_description_full'] = _("successfully completed, received a passing grade, and was "
|
||||
"awarded a {platform_name} {certificate_type} "
|
||||
"Certificate of Completion in ").format(
|
||||
platform_name=platform_name,
|
||||
certificate_type=context.get("certificate_type")
|
||||
)
|
||||
|
||||
return render_to_response("certificates/valid.html", context)
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"FEATURES": {
|
||||
"AUTH_USE_OPENID_PROVIDER": true,
|
||||
"CERTIFICATES_ENABLED": true,
|
||||
"CERTIFICATES_HTML_VIEW": true,
|
||||
"MULTIPLE_ENROLLMENT_ROLES": true,
|
||||
"ENABLE_PAYMENT_FAKE": true,
|
||||
"ENABLE_VERIFIED_CERTIFICATES": true,
|
||||
|
||||
@@ -351,6 +351,9 @@ FEATURES = {
|
||||
|
||||
# enable beacons for video timing statistics
|
||||
'ENABLE_VIDEO_BEACON': False,
|
||||
|
||||
# Certificates Web/HTML Views
|
||||
'CERTIFICATES_HTML_VIEW': False,
|
||||
}
|
||||
|
||||
# Ignore static asset files on import which match this pattern
|
||||
|
||||
@@ -123,6 +123,10 @@ FEATURES['ENABLE_COURSEWARE_SEARCH'] = True
|
||||
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
|
||||
|
||||
|
||||
########################## Certificates Web/HTML View #######################
|
||||
FEATURES['CERTIFICATES_HTML_VIEW'] = True
|
||||
|
||||
|
||||
#####################################################################
|
||||
# See if the developer has any local overrides.
|
||||
try:
|
||||
|
||||
@@ -466,3 +466,6 @@ SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine"
|
||||
FACEBOOK_APP_SECRET = "Test"
|
||||
FACEBOOK_APP_ID = "Test"
|
||||
FACEBOOK_API_VERSION = "v2.2"
|
||||
|
||||
# Certificates Views
|
||||
FEATURES['CERTIFICATES_HTML_VIEW'] = True
|
||||
|
||||
25
lms/templates/certificates/invalid.html
Normal file
25
lms/templates/certificates/invalid.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
<link rel="stylesheet" href="/stylesheets/base.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>edX and MITX</h1>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<header class="invalid">
|
||||
<h1>${_("This is an invalid edX certificate number")}</h1>
|
||||
<p>${_("Please check the number to make sure that it is the exact same as on the certificate.")}</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<p>${_("This is an unknown certificate number and therefore is a potential forgery.")}</p>
|
||||
</section>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
154
lms/templates/certificates/valid.html
Normal file
154
lms/templates/certificates/valid.html
Normal file
@@ -0,0 +1,154 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! import mako.runtime %>
|
||||
<% mako.runtime.UNDEFINED = '' %>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]><html class="no-js lt-ie9 lt-ie8 lt-ie7"><![endif]-->
|
||||
<!--[if IE 7]><html class="no-js lt-ie10 lt-ie9 lt-ie8"><![endif]-->
|
||||
<!--[if IE 8]><html class="no-js lt-ie10 lt-ie9"><![endif]-->
|
||||
<!--[if IE 9]><html class="no-js lt-ie10"><![endif]-->
|
||||
<!--[if gt IE 9]><!--><html class="no-js"><!--<![endif]-->
|
||||
<head>
|
||||
<title>${document_title}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="${document_meta_description}">
|
||||
<link rel="stylesheet" href="${document_stylesheet_url_normalize}" />
|
||||
<link rel="stylesheet" href="${document_stylesheet_url_fontawesome}" />
|
||||
<link rel="stylesheet" href="${document_stylesheet_url_application}" />
|
||||
<script src="${document_script_src_modernizr}"></script>
|
||||
</head>
|
||||
|
||||
<body class="view--valid-certificate ${document_body_class_append}" data-view="valid-certificate">
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<p class="msg msg--browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
|
||||
<![endif]-->
|
||||
|
||||
<nav class="nav--skip sr">
|
||||
<h2>${_("Skip to This Page's Content")}</h2>
|
||||
<ol>
|
||||
<li class="nav__item"><a class="action" href="#validation-status">${_("Validation Status")}</a></li>
|
||||
<li class="nav__item"><a class="action" href="#validation-accomplishment">${_("Student Accomplishment")}</a></li>
|
||||
<li class="nav__item"><a class="action" href="#validation-info">${_("More Information")}</a></li>
|
||||
<li class="nav__item"><a class="action" href="#company-info">${_("About")} ${platform_name}</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="wrapper--view">
|
||||
<div class="wrapper--header">
|
||||
<header class="header--app" role="banner">
|
||||
<h1 class="title title--logo">
|
||||
<span class="logo">
|
||||
<a href="${logo_url}"><img class="img--logo" src="" alt="${logo_alt}" /></a>
|
||||
</span>
|
||||
<span class="title title--sub">${logo_subtitle}</span>
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
<hr class="divider /">
|
||||
<div class="wrapper--content">
|
||||
<section class="content content--main" role="main">
|
||||
<div class="status status--valid" id="validation-status">
|
||||
<h2 class="title title--lvl2">${document_banner}<span class="sr">:</span></h2>
|
||||
</div>
|
||||
<article class="accomplishment ${accomplishment_class_append}" id="validation-accomplishment">
|
||||
<div class="accomplishment__statement">
|
||||
<p class="copy">
|
||||
<span class="copy__name">${accomplishment_copy_name}</span>
|
||||
<span class="copy__context">${accomplishment_copy_description_full}</span>
|
||||
<span class="copy__course">
|
||||
<span class="copy__course__org">${accomplishment_copy_course_org}</span>
|
||||
<span class="copy__course__name">${accomplishment_copy_course_name}</span>
|
||||
</span>
|
||||
<span class="copy__context">${accomplishment_copy_course_description}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="accomplishment__details">
|
||||
<h3 class="title title--lvl2 sr">${accomplishment_more_title}</h3>
|
||||
<ul class="list list--metadata">
|
||||
<li class="item certificate--type">
|
||||
<span class="label">_(Certificate Type)</span>
|
||||
<span class="value">
|
||||
${certificate_type_title}
|
||||
<span class="explanation">${certificate_type_description} </span>
|
||||
</span>
|
||||
</li>
|
||||
<li class="item certificate--id">
|
||||
<span class="label">${certificate_id_number_title}</span>
|
||||
<span class="value">${certificate_id_number}</span>
|
||||
</li>
|
||||
<li class="item certificate--date">
|
||||
<span class="label">${certificate_date_issued_title}</span>
|
||||
<span class="value">${certificate_date_issued}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<hr class="divider /">
|
||||
<aside class="content content--supplemental" role="complimentary" id="validation-info">
|
||||
<div class="supplemental__about">
|
||||
<h2 class="title">${company_about_title}</h2>
|
||||
<div class="copy">
|
||||
<p>${company_about_description}</p>
|
||||
</div>
|
||||
<ul class="list list--actions">
|
||||
<li class="item item--action">
|
||||
<a class="action action--primary action--moreinfo" href="${company_about_url}">${company_about_urltext}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="supplemental__how">
|
||||
<h2 class="title">${certificate_verify_title}</h2>
|
||||
<div class="copy">
|
||||
<p>${certificate_verify_description}</p>
|
||||
</div>
|
||||
<ul class="list list--actions">
|
||||
<li class="item item--action">
|
||||
<a class="action action--moreinfo" href="${certificate_verify_url}">${certificate_verify_urltext}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="supplemental__certificates">
|
||||
<h2 class="title">${certificate_info_title}</h2>
|
||||
<div class="copy">
|
||||
<p>${certificate_info_description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
<hr class="divider /">
|
||||
<div class="wrapper--footer">
|
||||
<footer class="footer--app" role="contentinfo" id="company-info">
|
||||
<div class="copyright">
|
||||
<p>${copyright_text}</p>
|
||||
</div>
|
||||
<nav class="nav--footer">
|
||||
<ul class="list list--legal">
|
||||
<li class="nav__item">
|
||||
<a class="action" href="${company_tos_url}">${company_tos_urltext}</a>
|
||||
</li>
|
||||
<li class="nav__item">
|
||||
<a class="action" href="${company_privacy_url}">${company_privacy_urltext}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="list list--actions">
|
||||
<li class="nav__item">
|
||||
<a class="action" href="${company_courselist_url}">${company_courselist_urltext}</a>
|
||||
</li>
|
||||
<li class="nav__item">
|
||||
<a class="action" href="${company_careers_url}">${company_careers_urltext}</a>
|
||||
</li>
|
||||
<li class="nav__item">
|
||||
<a class="action" href="${company_contact_url}">${company_contact_urltext}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script>window.jQuery || document.write('<script src="/v2/static/jsjs/vendor/jquery-1.10.2.min.js"><\/script>')</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -598,6 +598,11 @@ if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
|
||||
url(r'^login_oauth_token/(?P<backend>[^/]+)/$', 'student.views.login_oauth_token'),
|
||||
)
|
||||
|
||||
# Certificates Web/HTML View
|
||||
if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
|
||||
urlpatterns += (
|
||||
url(r'^certificates/html', 'certificates.views.render_html_view', name='cert_html_view'),
|
||||
)
|
||||
|
||||
urlpatterns = patterns(*urlpatterns)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user