Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
16e63c9e
Commit
16e63c9e
authored
Feb 18, 2015
by
Matt Drayer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added Certficates Web/HTML View
parent
766663ee
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
974 additions
and
8 deletions
+974
-8
common/lib/xmodule/xmodule/course_module.py
+7
-0
common/test/acceptance/pages/studio/settings_advanced.py
+1
-0
lms/djangoapps/certificates/admin.py
+3
-1
lms/djangoapps/certificates/migrations/0019_auto__add_certificatehtmlviewconfiguration.py
+104
-0
lms/djangoapps/certificates/migrations/0020_certificatehtmlviewconfiguration_data.py
+170
-0
lms/djangoapps/certificates/models.py
+44
-0
lms/djangoapps/certificates/tests/factories.py
+46
-1
lms/djangoapps/certificates/tests/test_models.py
+78
-1
lms/djangoapps/certificates/tests/test_views.py
+104
-3
lms/djangoapps/certificates/views.py
+222
-2
lms/envs/bok_choy.env.json
+1
-0
lms/envs/common.py
+3
-0
lms/envs/devstack.py
+4
-0
lms/envs/test.py
+3
-0
lms/templates/certificates/invalid.html
+25
-0
lms/templates/certificates/valid.html
+154
-0
lms/urls.py
+5
-0
No files found.
common/lib/xmodule/xmodule/course_module.py
View file @
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
...
...
common/test/acceptance/pages/studio/settings_advanced.py
View file @
16e63c9e
...
@@ -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'
,
...
...
lms/djangoapps/certificates/admin.py
View file @
16e63c9e
...
@@ -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
)
lms/djangoapps/certificates/migrations/0019_auto__add_certificatehtmlviewconfiguration.py
0 → 100644
View file @
16e63c9e
# -*- 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'
]
lms/djangoapps/certificates/migrations/0020_certificatehtmlviewconfiguration_data.py
0 → 100644
View file @
16e63c9e
# -*- 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
lms/djangoapps/certificates/models.py
View file @
16e63c9e
...
@@ -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
lms/djangoapps/certificates/tests/factories.py
View file @
16e63c9e
...
@@ -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"
}
}"""
lms/djangoapps/certificates/tests/test_models.py
View file @
16e63c9e
"""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
(),
{})
lms/djangoapps/certificates/tests/test_views.py
View file @
16e63c9e
...
@@ -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
)
lms/djangoapps/certificates/views.py
View file @
16e63c9e
"""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""".'
)
.
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
)
lms/envs/bok_choy.env.json
View file @
16e63c9e
...
@@ -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
,
...
...
lms/envs/common.py
View file @
16e63c9e
...
@@ -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
...
...
lms/envs/devstack.py
View file @
16e63c9e
...
@@ -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
:
...
...
lms/envs/test.py
View file @
16e63c9e
...
@@ -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
lms/templates/certificates/invalid.html
0 → 100644
View file @
16e63c9e
<
%!
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>
lms/templates/certificates/valid.html
0 → 100644
View file @
16e63c9e
<
%!
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>
lms/urls.py
View file @
16e63c9e
...
@@ -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
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment