Commit cb2c8bc8 by Matt Drayer

Merge pull request #7113 from edx/mattdrayer/certs-web-view

Added Certificates Web/HTML View
parents 766663ee 16e63c9e
...@@ -670,6 +670,13 @@ class CourseFields(object): ...@@ -670,6 +670,13 @@ class CourseFields(object):
scope=Scope.settings, scope=Scope.settings,
default="" 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 # An extra property is used rather than the wiki_slug/number because
# there are courses that change the number for different runs. This allows # there are courses that change the number for different runs. This allows
......
...@@ -150,6 +150,7 @@ class AdvancedSettingsPage(CoursePage): ...@@ -150,6 +150,7 @@ class AdvancedSettingsPage(CoursePage):
'allow_anonymous', 'allow_anonymous',
'allow_anonymous_to_peers', 'allow_anonymous_to_peers',
'allow_public_wiki_access', 'allow_public_wiki_access',
'cert_html_view_overrides',
'cert_name_long', 'cert_name_long',
'cert_name_short', 'cert_name_short',
'certificates_display_behavior', 'certificates_display_behavior',
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
django admin pages for certificates models django admin pages for certificates models
""" """
from django.contrib import admin 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(CertificateGenerationConfiguration)
admin.site.register(CertificateHtmlViewConfiguration, ConfigurationModelAdmin)
# -*- 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']
# -*- 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: ...@@ -46,9 +46,12 @@ Eligibility:
unless he has allow_certificate set to False. unless he has allow_certificate set to False.
""" """
from datetime import datetime from datetime import datetime
import json
import uuid import uuid
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models, transaction from django.db import models, transaction
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
...@@ -503,3 +506,44 @@ class CertificateGenerationConfiguration(ConfigurationModel): ...@@ -503,3 +506,44 @@ class CertificateGenerationConfiguration(ConfigurationModel):
""" """
pass 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 ...@@ -2,7 +2,7 @@ from factory.django import DjangoModelFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from certificates.models import GeneratedCertificate, CertificateStatuses from certificates.models import GeneratedCertificate, CertificateStatuses, CertificateHtmlViewConfiguration
# Factories are self documenting # Factories are self documenting
...@@ -15,3 +15,48 @@ class GeneratedCertificateFactory(DjangoModelFactory): ...@@ -15,3 +15,48 @@ class GeneratedCertificateFactory(DjangoModelFactory):
status = CertificateStatuses.unavailable status = CertificateStatuses.unavailable
mode = GeneratedCertificate.MODES.honor mode = GeneratedCertificate.MODES.honor
name = '' 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"
}
}"""
"""Tests for certificate Django models. """ """Tests for certificate Django models. """
from django.conf import settings
from django.core.exceptions import ValidationError
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings
from opaque_keys.edx.locator import CourseLocator from opaque_keys.edx.locator import CourseLocator
from certificates.models import ( from certificates.models import (
ExampleCertificate, 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): class ExampleCertificateTest(TestCase):
"""Tests for the ExampleCertificate model. """ """Tests for the ExampleCertificate model. """
...@@ -71,3 +78,73 @@ class ExampleCertificateTest(TestCase): ...@@ -71,3 +78,73 @@ class ExampleCertificateTest(TestCase):
other_course = CourseLocator(org='other', course='other', run='other') other_course = CourseLocator(org='other', course='other', run='other')
result = ExampleCertificateSet.latest_status(other_course) result = ExampleCertificateSet.latest_status(other_course)
self.assertIs(result, None) 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 @@ ...@@ -2,14 +2,28 @@
import json import json
import ddt import ddt
from uuid import uuid4
from django.test import TestCase from django.conf import settings
from django.core.urlresolvers import reverse
from django.core.cache import cache 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 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, GeneratedCertificate
from certificates.tests.factories import CertificateHtmlViewConfigurationFactory
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
from certificates.models import ExampleCertificateSet, ExampleCertificate FEATURES_WITH_CERTS_DISABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_DISABLED['CERTIFICATES_HTML_VIEW'] = False
@ddt.ddt @ddt.ddt
...@@ -151,3 +165,90 @@ class UpdateExampleCertificateViewTest(TestCase): ...@@ -151,3 +165,90 @@ class UpdateExampleCertificateViewTest(TestCase):
content = json.loads(response.content) content = json.loads(response.content)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(content['return_code'], 0) 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)
"""URL handlers related to certificate handling by LMS""" """URL handlers related to certificate handling by LMS"""
from datetime import datetime
import dogstats_wrapper as dog_stats_api import dogstats_wrapper as dog_stats_api
import json import json
import logging import logging
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.http import HttpResponse, Http404, HttpResponseForbidden 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.csrf import csrf_exempt
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
...@@ -13,13 +17,17 @@ from certificates.models import ( ...@@ -13,13 +17,17 @@ from certificates.models import (
certificate_status_for_student, certificate_status_for_student,
CertificateStatuses, CertificateStatuses,
GeneratedCertificate, GeneratedCertificate,
ExampleCertificate ExampleCertificate,
CertificateHtmlViewConfiguration
) )
from certificates.queue import XQueueCertInterface from certificates.queue import XQueueCertInterface
from edxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore 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.json_request import JsonResponse, JsonResponseBadRequest
from util.bad_request_rate_limiter import BadRequestRateLimiter from util.bad_request_rate_limiter import BadRequestRateLimiter
from opaque_keys.edx.locations import SlashSeparatedCourseKey
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -224,3 +232,215 @@ def update_example_certificate(request): ...@@ -224,3 +232,215 @@ def update_example_certificate(request):
# Let the XQueue know that we handled the response # Let the XQueue know that we handled the response
return JsonResponse({'return_code': 0}) 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"&quot;".').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 &amp; 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'] = _('&copy; {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 @@ ...@@ -65,6 +65,7 @@
"FEATURES": { "FEATURES": {
"AUTH_USE_OPENID_PROVIDER": true, "AUTH_USE_OPENID_PROVIDER": true,
"CERTIFICATES_ENABLED": true, "CERTIFICATES_ENABLED": true,
"CERTIFICATES_HTML_VIEW": true,
"MULTIPLE_ENROLLMENT_ROLES": true, "MULTIPLE_ENROLLMENT_ROLES": true,
"ENABLE_PAYMENT_FAKE": true, "ENABLE_PAYMENT_FAKE": true,
"ENABLE_VERIFIED_CERTIFICATES": true, "ENABLE_VERIFIED_CERTIFICATES": true,
......
...@@ -351,6 +351,9 @@ FEATURES = { ...@@ -351,6 +351,9 @@ FEATURES = {
# enable beacons for video timing statistics # enable beacons for video timing statistics
'ENABLE_VIDEO_BEACON': False, 'ENABLE_VIDEO_BEACON': False,
# Certificates Web/HTML Views
'CERTIFICATES_HTML_VIEW': False,
} }
# Ignore static asset files on import which match this pattern # Ignore static asset files on import which match this pattern
......
...@@ -123,6 +123,10 @@ FEATURES['ENABLE_COURSEWARE_SEARCH'] = True ...@@ -123,6 +123,10 @@ FEATURES['ENABLE_COURSEWARE_SEARCH'] = True
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine" SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
########################## Certificates Web/HTML View #######################
FEATURES['CERTIFICATES_HTML_VIEW'] = True
##################################################################### #####################################################################
# See if the developer has any local overrides. # See if the developer has any local overrides.
try: try:
......
...@@ -466,3 +466,6 @@ SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine" ...@@ -466,3 +466,6 @@ SEARCH_ENGINE = "search.tests.mock_search_engine.MockSearchEngine"
FACEBOOK_APP_SECRET = "Test" FACEBOOK_APP_SECRET = "Test"
FACEBOOK_APP_ID = "Test" FACEBOOK_APP_ID = "Test"
FACEBOOK_API_VERSION = "v2.2" FACEBOOK_API_VERSION = "v2.2"
# Certificates Views
FEATURES['CERTIFICATES_HTML_VIEW'] = True
<%! 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>
<%! 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'): ...@@ -598,6 +598,11 @@ if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
url(r'^login_oauth_token/(?P<backend>[^/]+)/$', 'student.views.login_oauth_token'), 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) urlpatterns = patterns(*urlpatterns)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment