54
common/djangoapps/student/tests/test_certificates.py
Normal file
54
common/djangoapps/student/tests/test_certificates.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""Tests for display of certificates on the student dashboard. """
|
||||
|
||||
import unittest
|
||||
import ddt
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from certificates.tests.factories import GeneratedCertificateFactory # pylint: disable=import-error
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class CertificateDisplayTest(ModuleStoreTestCase):
|
||||
"""Tests display of certificates on the student dashboard. """
|
||||
|
||||
USERNAME = "test_user"
|
||||
PASSWORD = "password"
|
||||
DOWNLOAD_URL = "http://www.example.com/certificate.pdf"
|
||||
|
||||
def setUp(self):
|
||||
super(CertificateDisplayTest, self).setUp()
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
self.assertTrue(result, msg="Could not log in")
|
||||
|
||||
self.course = CourseFactory()
|
||||
self.course.certificates_display_behavior = "early_with_info"
|
||||
self.update_course(self.course, self.user.username)
|
||||
|
||||
@ddt.data('verified', 'professional')
|
||||
def test_display_verified_certificate(self, enrollment_mode):
|
||||
self._create_certificate(enrollment_mode)
|
||||
self._check_can_download_certificate()
|
||||
|
||||
def _create_certificate(self, enrollment_mode):
|
||||
"""Simulate that the user has a generated certificate. """
|
||||
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, mode=enrollment_mode)
|
||||
GeneratedCertificateFactory(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
mode=enrollment_mode,
|
||||
download_url=self.DOWNLOAD_URL,
|
||||
status="downloadable",
|
||||
grade=0.98,
|
||||
)
|
||||
|
||||
def _check_can_download_certificate(self):
|
||||
response = self.client.get(reverse('dashboard'))
|
||||
self.assertContains(response, u'Download Your ID Verified')
|
||||
self.assertContains(response, self.DOWNLOAD_URL)
|
||||
@@ -13,6 +13,7 @@ and then for each combination of modulestores, performing the sequence:
|
||||
"""
|
||||
from contextlib import contextmanager, nested
|
||||
import itertools
|
||||
import os
|
||||
from path import path
|
||||
import random
|
||||
from shutil import rmtree
|
||||
@@ -314,14 +315,15 @@ class MixedModulestoreBuilder(StoreBuilderBase):
|
||||
# Split stores all asset metadata in the structure collection.
|
||||
return store.db_connection.structures
|
||||
|
||||
|
||||
MIXED_MODULESTORE_BOTH_SETUP = MixedModulestoreBuilder([
|
||||
('draft', MongoModulestoreBuilder()),
|
||||
('split', VersioningModulestoreBuilder())
|
||||
])
|
||||
DRAFT_MODULESTORE_SETUP = MixedModulestoreBuilder([('draft', MongoModulestoreBuilder())])
|
||||
SPLIT_MODULESTORE_SETUP = MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())])
|
||||
MIXED_MODULESTORE_SETUPS = (
|
||||
MixedModulestoreBuilder([('draft', MongoModulestoreBuilder())]),
|
||||
MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())]),
|
||||
DRAFT_MODULESTORE_SETUP,
|
||||
SPLIT_MODULESTORE_SETUP,
|
||||
)
|
||||
MIXED_MS_SETUPS_SHORT = (
|
||||
'mixed_mongo',
|
||||
@@ -347,6 +349,8 @@ COURSE_DATA_NAMES = (
|
||||
'split_test_module_draft',
|
||||
)
|
||||
|
||||
EXPORTED_COURSE_DIR_NAME = 'exported_source_course'
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@attr('mongo')
|
||||
@@ -397,14 +401,14 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase):
|
||||
source_content,
|
||||
source_course_key,
|
||||
self.export_dir,
|
||||
'exported_source_course',
|
||||
EXPORTED_COURSE_DIR_NAME,
|
||||
)
|
||||
|
||||
import_course_from_xml(
|
||||
dest_store,
|
||||
'test_user',
|
||||
self.export_dir,
|
||||
source_dirs=['exported_source_course'],
|
||||
source_dirs=[EXPORTED_COURSE_DIR_NAME],
|
||||
static_content_store=dest_content,
|
||||
target_id=dest_course_key,
|
||||
raise_on_failure=True,
|
||||
@@ -448,3 +452,58 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase):
|
||||
dest_store,
|
||||
dest_course_key,
|
||||
)
|
||||
|
||||
def test_split_course_export_import(self):
|
||||
# Construct the contentstore for storing the first import
|
||||
with MongoContentstoreBuilder().build() as source_content:
|
||||
# Construct the modulestore for storing the first import (using the previously created contentstore)
|
||||
with SPLIT_MODULESTORE_SETUP.build(contentstore=source_content) as source_store:
|
||||
# Construct the contentstore for storing the second import
|
||||
with MongoContentstoreBuilder().build() as dest_content:
|
||||
# Construct the modulestore for storing the second import (using the second contentstore)
|
||||
with SPLIT_MODULESTORE_SETUP.build(contentstore=dest_content) as dest_store:
|
||||
source_course_key = source_store.make_course_key('a', 'source', '2015_Fall')
|
||||
dest_course_key = dest_store.make_course_key('a', 'dest', '2015_Fall')
|
||||
|
||||
import_course_from_xml(
|
||||
source_store,
|
||||
'test_user',
|
||||
TEST_DATA_DIR,
|
||||
source_dirs=['split_course_with_static_tabs'],
|
||||
static_content_store=source_content,
|
||||
target_id=source_course_key,
|
||||
raise_on_failure=True,
|
||||
create_if_not_present=True,
|
||||
)
|
||||
|
||||
export_course_to_xml(
|
||||
source_store,
|
||||
source_content,
|
||||
source_course_key,
|
||||
self.export_dir,
|
||||
EXPORTED_COURSE_DIR_NAME,
|
||||
)
|
||||
|
||||
source_course = source_store.get_course(source_course_key, depth=None, lazy=False)
|
||||
|
||||
self.assertEqual(source_course.url_name, 'course')
|
||||
|
||||
export_dir_path = path(self.export_dir)
|
||||
policy_dir = export_dir_path / 'exported_source_course' / 'policies' / source_course.url_name
|
||||
policy_path = policy_dir / 'policy.json'
|
||||
self.assertTrue(os.path.exists(policy_path))
|
||||
|
||||
import_course_from_xml(
|
||||
dest_store,
|
||||
'test_user',
|
||||
self.export_dir,
|
||||
source_dirs=[EXPORTED_COURSE_DIR_NAME],
|
||||
static_content_store=dest_content,
|
||||
target_id=dest_course_key,
|
||||
raise_on_failure=True,
|
||||
create_if_not_present=True,
|
||||
)
|
||||
|
||||
dest_course = dest_store.get_course(dest_course_key, depth=None, lazy=False)
|
||||
|
||||
self.assertEqual(dest_course.url_name, 'course')
|
||||
|
||||
@@ -264,8 +264,14 @@ class CourseExportManager(ExportManager):
|
||||
'about', 'about', '.html'
|
||||
)
|
||||
|
||||
course_policy_dir_name = courselike.location.run
|
||||
if courselike.url_name != courselike.location.run and courselike.url_name == 'course':
|
||||
# Use url_name for split mongo because course_run is not used when loading policies.
|
||||
course_policy_dir_name = courselike.url_name
|
||||
|
||||
course_run_policy_dir = policies_dir.makeopendir(course_policy_dir_name)
|
||||
|
||||
# export the grading policy
|
||||
course_run_policy_dir = policies_dir.makeopendir(courselike.location.run)
|
||||
with course_run_policy_dir.open('grading_policy.json', 'w') as grading_policy:
|
||||
grading_policy.write(dumps(courselike.grading_policy, cls=EdxJSONEncoder, sort_keys=True, indent=4))
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<course url_name='course' org='split_test' course='split_test'>
|
||||
|
||||
</course>
|
||||
@@ -0,0 +1,2 @@
|
||||
<course advanced_modules="["drag-and-drop-v2"]" display_name="Test Static Tab" due_date_display_format="" graceperiod="" no_grade="false" pdf_textbooks="[]" start=""2015-01-01T00:00:00+00:00"" use_latex_compiler="true">
|
||||
</course>
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{"GRADER": [{"short_label": "HW", "min_count": 12, "type": "Homework", "drop_count": 2, "weight": 0.15}, {"min_count": 12, "type": "Lab", "drop_count": 2, "weight": 0.15}, {"short_label": "Midterm", "min_count": 1, "type": "Midterm Exam", "drop_count": 0, "weight": 0.3}, {"short_label": "Final", "min_count": 1, "type": "Final Exam", "drop_count": 0, "weight": 0.4}], "GRADE_CUTOFFS": {"Pass": 0.5}}
|
||||
@@ -0,0 +1 @@
|
||||
{"course/3111": {"tabs": [{"type": "courseware", "name": "Courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "textbooks", "name": "Textbooks"}, {"type": "discussion", "name": "Discussion"}, {"type": "wiki", "name": "Wiki"}, {"type": "progress", "name": "Progress"}, {"name": "Test Page", "type": "static_tab", "url_slug": "test_page.html"}], "advanced_modules": ["split_test"], "display_name": "split export test", "user_partitions": [{"description": "Experiment 1", "version": 1, "id": 0, "groups": [{"version": 1, "id": 0, "name": "group 0"}, {"version": 1, "id": 1, "name": "group 1"}], "name": "Experiment 0,1"}, {"description": "Experiment 2", "version": 1, "id": 1, "groups": [{"version": 1, "id": 0, "name": "group A"}, {"version": 1, "id": 1, "name": "group B"}, {"version": 1, "id": 2, "name": "group C"}], "name": "Experiment A,B,C"}], "discussion_topics": {"General": {"id": "i4x-foo-1111-course-3111"}}}}
|
||||
@@ -0,0 +1 @@
|
||||
<p>Add the content you want students to see on this page.</p>
|
||||
@@ -44,6 +44,7 @@ class CommandsTestBase(ModuleStoreTestCase):
|
||||
|
||||
"""
|
||||
__test__ = False
|
||||
url_name = '2012_Fall'
|
||||
|
||||
def setUp(self):
|
||||
super(CommandsTestBase, self).setUp()
|
||||
@@ -201,7 +202,7 @@ class CommandsTestBase(ModuleStoreTestCase):
|
||||
|
||||
assert_in = self.assertIn
|
||||
assert_in('edX-simple-2012_Fall', names)
|
||||
assert_in('edX-simple-2012_Fall/policies/2012_Fall/policy.json', names)
|
||||
assert_in('edX-simple-2012_Fall/policies/{}/policy.json'.format(self.url_name), names)
|
||||
assert_in('edX-simple-2012_Fall/html/toylab.html', names)
|
||||
assert_in('edX-simple-2012_Fall/videosequence/A_simple_sequence.xml', names)
|
||||
assert_in('edX-simple-2012_Fall/sequential/Lecture_2.xml', names)
|
||||
@@ -232,3 +233,4 @@ class CommandSplitMongoTestCase(CommandsTestBase):
|
||||
"""
|
||||
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
|
||||
__test__ = True
|
||||
url_name = 'course'
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<%page args="cert_status, course, enrollment" />
|
||||
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from course_modes.models import CourseMode
|
||||
%>
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<%
|
||||
@@ -61,7 +64,7 @@ else:
|
||||
<a class="btn" href="${cert_status['download_url']}"
|
||||
title="${_('This link will open/download a PDF document')}">
|
||||
${_("Download Your {cert_name_short} (PDF)").format(cert_name_short=cert_name_short)}</a></li>
|
||||
% elif cert_status['show_download_url'] and enrollment.mode == 'verified':
|
||||
% elif cert_status['show_download_url'] and enrollment.mode in CourseMode.VERIFIED_MODES:
|
||||
<li class="action">
|
||||
<a class="btn" href="${cert_status['download_url']}"
|
||||
title="${_('This link will open/download a PDF document of your verified {cert_name_long}.').format(cert_name_long=cert_name_long)}">
|
||||
|
||||
Reference in New Issue
Block a user