Commit a8f909fe by Matt Drayer

Merge pull request #8659 from edx/ziafazal/SOL-1006

ziafazal/SOL-1006: Disable course 'start date' for inactive web-enabled certificates
parents d3a5bc32 bcbd1464
......@@ -52,6 +52,7 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video))
self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort))
self.assertIsNone(details.language, "language somehow initialized" + str(details.language))
self.assertIsNone(details.has_cert_config)
def test_encoder(self):
details = CourseDetails.fetch(self.course.id)
......@@ -1008,6 +1009,41 @@ class CourseMetadataEditingTest(CourseTestCase):
tab_list.append(self.notes_tab)
self.assertEqual(tab_list, course.tabs)
@override_settings(FEATURES={'CERTIFICATES_HTML_VIEW': True})
def test_web_view_certifcate_configuration_settings(self):
"""
Test that has_cert_config is updated based on cert_html_view_enabled setting.
"""
test_model = CourseMetadata.update_from_json(
self.course,
{
"cert_html_view_enabled": {"value": "true"}
},
user=self.user
)
self.assertIn('cert_html_view_enabled', test_model)
url = get_url(self.course.id)
response = self.client.get_json(url)
course_detail_json = json.loads(response.content)
self.assertFalse(course_detail_json['has_cert_config'])
# Now add a certificate configuration
certificates = [
{
'id': 1,
'name': 'Certificate Config Name',
'course_title': 'Title override',
'org_logo_path': '/c4x/test/CSS101/asset/org_logo.png',
'signatories': [],
'is_active': True
}
]
self.course.certificates = {'certificates': certificates}
modulestore().update_item(self.course, self.user.id)
response = self.client.get_json(url)
course_detail_json = json.loads(response.content)
self.assertTrue(course_detail_json['has_cert_config'])
class CourseGraderUpdatesTest(CourseTestCase):
"""
......
......@@ -313,3 +313,22 @@ def reverse_usage_url(handler_name, usage_key, kwargs=None):
Creates the URL for handlers that use usage_keys as URL parameters.
"""
return reverse_url(handler_name, 'usage_key_string', usage_key, kwargs)
def has_active_web_certificate(course):
"""
Returns True if given course has active web certificate configuration.
If given course has no active web certificate configuration returns False.
Returns None If `CERTIFICATES_HTML_VIEW` is not enabled of course has not enabled
`cert_html_view_enabled` settings.
"""
cert_config = None
if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False) and course.cert_html_view_enabled:
cert_config = False
certificates = getattr(course, 'certificates', {})
configurations = certificates.get('certificates', [])
for config in configurations:
if config.get('is_active'):
cert_config = True
break
return cert_config
......@@ -8,7 +8,7 @@ from django.conf import settings
from opaque_keys.edx.locations import Location
from xmodule.modulestore.exceptions import ItemNotFoundError
from contentstore.utils import course_image_url
from contentstore.utils import course_image_url, has_active_web_certificate
from models.settings import course_grading
from xmodule.fields import Date
from xmodule.modulestore.django import modulestore
......@@ -52,7 +52,8 @@ class CourseDetails(object):
self.entrance_exam_minimum_score_pct = settings.FEATURES.get(
'ENTRANCE_EXAM_MIN_SCORE_PCT',
'50'
) # minimum passing score for entrance exam content module/tree
) # minimum passing score for entrance exam content module/tree,
self.has_cert_config = None # course has active certificate configuration
@classmethod
def _fetch_about_attribute(cls, course_key, attribute):
......@@ -84,6 +85,7 @@ class CourseDetails(object):
course_details.language = descriptor.language
# Default course license is "All Rights Reserved"
course_details.license = getattr(descriptor, "license", "all-rights-reserved")
course_details.has_cert_config = has_active_web_certificate(descriptor)
for attribute in ABOUT_ATTRIBUTES:
value = cls._fetch_about_attribute(course_key, attribute)
......
define(["backbone", "underscore", "gettext", "js/models/validation_helpers"],
function(Backbone, _, gettext, ValidationHelpers) {
define(["backbone", "underscore", "gettext", "js/models/validation_helpers", "js/utils/date_utils"],
function(Backbone, _, gettext, ValidationHelpers, DateUtils) {
var CourseDetails = Backbone.Model.extend({
defaults: {
......@@ -28,14 +28,21 @@ var CourseDetails = Backbone.Model.extend({
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {};
newattrs = DateUtils.convertDateStringsToObjects(
newattrs, ["start_date", "end_date", "enrollment_start", "enrollment_end"]
);
if (newattrs.start_date === null) {
errors.start_date = gettext("The course must have an assigned start date.");
}
if (this.hasChanged("start_date") && this.get("has_cert_config") === false){
errors.start_date = gettext("The course must have at least one active certificate configuration before it can be started.");
}
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = gettext("The course end date cannot be before the course start date.");
errors.end_date = gettext("The course end date must be later than the course start date.");
}
if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
errors.enrollment_start = gettext("The course start date cannot be before the enrollment start date.");
errors.enrollment_start = gettext("The course start date must be later than the enrollment start date.");
}
if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
errors.enrollment_end = gettext("The enrollment start date cannot be after the enrollment end date.");
......
......@@ -31,7 +31,8 @@ define([
entrance_exam_enabled : '',
entrance_exam_minimum_score_pct: '50',
license: null,
language: ''
language: '',
has_cert_config: false
},
mockSettingsPage = readFixtures('mock/mock-settings-page.underscore');
......@@ -71,6 +72,13 @@ define([
);
});
it('Changing course start date without active certificate configuration should result in error', function () {
this.view.$el.find('#course-start-date')
.val('10/06/2014')
.trigger('change');
expect(this.view.$el.find('span.message-error').text()).toContain("course must have at least one active certificate configuration");
});
it('Selecting a course in pre-requisite drop down should save it as part of course details', function () {
var pre_requisite_courses = ['test/CSS101/2012_T1'];
var requests = AjaxHelpers.requests(this),
......
......@@ -35,9 +35,29 @@ define(["jquery", "date", "jquery.ui", "jquery.timepicker"], function($, date) {
);
};
var parseDateFromString = function(stringDate){
if (stringDate && typeof stringDate === "string"){
return new Date(stringDate);
}
else {
return stringDate;
}
};
var convertDateStringsToObjects = function(obj, dateFields){
for (var i = 0; i < dateFields.length; i++){
if (obj[dateFields[i]]){
obj[dateFields[i]] = parseDateFromString(obj[dateFields[i]]);
}
}
return obj;
};
return {
getDate: getDate,
setDate: setDate,
renderDate: renderDate
renderDate: renderDate,
convertDateStringsToObjects: convertDateStringsToObjects,
parseDateFromString: parseDateFromString
};
});
......@@ -47,9 +47,14 @@ class CertificateDisplayTest(ModuleStoreTestCase):
@patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': True})
def test_display_download_certificate_button(self, enrollment_mode):
"""
Tests if CERTIFICATES_HTML_VIEW is True and there is no active certificate configuration available
Tests if CERTIFICATES_HTML_VIEW is True
and course has enabled web certificates via cert_html_view_enabled setting
and no active certificate configuration available
then any of the Download certificate button should not be visible.
"""
self.course.cert_html_view_enabled = True
self.course.save()
self.store.update_item(self.course, self.user.id)
self._create_certificate(enrollment_mode)
self._check_can_not_download_certificate()
......@@ -75,6 +80,7 @@ class CertificateDisplayTest(ModuleStoreTestCase):
}
]
self.course.certificates = {'certificates': certificates}
self.course.cert_html_view_enabled = True
self.course.save() # pylint: disable=no-member
self.store.update_item(self.course, self.user.id)
......
......@@ -59,7 +59,11 @@ from student.forms import AccountCreationForm, PasswordResetFormNoActive
from verify_student.models import SoftwareSecurePhotoVerification # pylint: disable=import-error
from certificates.models import CertificateStatuses, certificate_status_for_student
from certificates.api import get_certificate_url, get_active_web_certificate # pylint: disable=import-error
from certificates.api import ( # pylint: disable=import-error
get_certificate_url,
get_active_web_certificate,
has_html_certificates_enabled,
)
from dark_lang.models import DarkLangConfig
from xmodule.modulestore.django import modulestore
......@@ -304,7 +308,7 @@ def _cert_info(user, course, cert_status, course_mode):
if status == 'ready':
# showing the certificate web view button if certificate is ready state and feature flags are enabled.
if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
if has_html_certificates_enabled(course.id, course):
if get_active_web_certificate(course) is not None:
certificate_url = get_certificate_url(
user_id=user.id,
......
......@@ -712,6 +712,12 @@ class CourseFields(object):
scope=Scope.settings,
default=""
)
cert_html_view_enabled = Boolean(
display_name=_("Certificate Web/HTML View Enabled"),
help=_("If true, certificate Web/HTML views are enabled for the course."),
scope=Scope.settings,
default=False,
)
cert_html_view_overrides = Dict(
# Translators: This field is the container for course-specific certifcate configuration values
display_name=_("Certificate Web/HTML View Overrides"),
......
......@@ -201,4 +201,5 @@ class AdvancedSettingsPage(CoursePage):
'social_sharing_url',
'teams_configuration',
'video_bumper',
'cert_html_view_enabled',
]
......@@ -36,8 +36,11 @@ class CertificateWebViewTest(EventsTestMixin, UniqueCourseTest):
self.course_info["display_name"],
settings=course_settings
)
self.course_fixture.add_advanced_settings({
"cert_html_view_enabled": {"value": "true"}
})
self.course_fixture.install()
self.user_id = "99" # we have createad a user with this id in fixture
self.user_id = "99" # we have created a user with this id in fixture
self.cert_fixture = CertificateConfigFixture(self.course_id, test_certificate_config)
# Load certificate web view page for use by the tests
......
......@@ -10,6 +10,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from eventtracking import tracker
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore
......@@ -217,10 +218,14 @@ def has_html_certificates_enabled(course_key, course=None):
It determines if course has html certificates enabled
"""
html_certificates_enabled = False
if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
try:
if not isinstance(course_key, CourseKey):
course_key = CourseKey.from_string(course_key)
course = course if course else modulestore().get_course(course_key, depth=0)
if get_active_web_certificate(course) is not None:
if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False) and course.cert_html_view_enabled:
html_certificates_enabled = True
except: # pylint: disable=bare-except
pass
return html_certificates_enabled
......
......@@ -214,6 +214,7 @@ class GenerateUserCertificatesTest(EventTestMixin, ModuleStoreTestCase):
}
]
self.course.certificates = {'certificates': certificates}
self.course.cert_html_view_enabled = True
self.course.save()
self.store.update_item(self.course, self.user.id)
......
......@@ -267,6 +267,7 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
]
self.course.certificates = {'certificates': certificates}
self.course.cert_html_view_enabled = True
self.course.save()
self.store.update_item(self.course, self.user.id)
......@@ -422,6 +423,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
]
self.course.certificates = {'certificates': certificates}
self.course.cert_html_view_enabled = True
self.course.save()
self.store.update_item(self.course, self.user.id)
......@@ -483,6 +485,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
}
]
self.course.certificates = {'certificates': test_certificates}
self.course.cert_html_view_enabled = True
self.course.save()
self.store.update_item(self.course, self.user.id)
response = self.client.get(test_url)
......@@ -506,6 +509,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
}
]
self.course.certificates = {'certificates': test_certificates}
self.course.cert_html_view_enabled = True
self.course.save()
self.store.update_item(self.course, self.user.id)
response = self.client.get(test_url)
......@@ -646,6 +650,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
verify_uuid=self.cert.verify_uuid
)
test_url = '{}?evidence_visit=1'.format(cert_url)
self._add_course_certificates(count=1, signatory_count=2)
self.recreate_tracker()
assertion = BadgeAssertion(
user=self.user, course_id=self.course_id, mode='honor',
......
......@@ -21,7 +21,8 @@ from certificates.api import (
get_active_web_certificate,
get_certificate_url,
generate_user_certificates,
emit_certificate_event
emit_certificate_event,
has_html_certificates_enabled
)
from certificates.models import (
certificate_status_for_student,
......@@ -507,7 +508,7 @@ def render_html_view(request, user_id, course_id):
invalid_template_path = 'certificates/invalid.html'
# Kick the user back to the "Invalid" screen if the feature is disabled
if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
if not has_html_certificates_enabled(course_id):
return render_to_response(invalid_template_path, context)
# Load the core building blocks for the view context
......
......@@ -853,6 +853,7 @@ class ProgressPageTests(ModuleStoreTestCase):
]
self.course.certificates = {'certificates': certificates}
self.course.cert_html_view_enabled = True
self.course.save()
self.store.update_item(self.course, self.user.id)
......
......@@ -1081,7 +1081,7 @@ def _progress(request, course_key, student_id):
if show_generate_cert_btn:
context.update(certs_api.certificate_downloadable_status(student, course_key))
# showing the certificate web view button if feature flags are enabled.
if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
if certs_api.has_html_certificates_enabled(course_key, course):
if certs_api.get_active_web_certificate(course) is not None:
context.update({
'show_cert_web_view': True,
......
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