Commit 5ffa06be by Peter Fogg

Responding to review comments.

parent 4805946a
......@@ -30,6 +30,7 @@ from contentstore.views.component import ADVANCED_COMPONENT_POLICY_KEY
import ddt
from xmodule.modulestore import ModuleStoreEnum
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from util.milestones_helpers import seed_milestone_relationship_types
......@@ -87,6 +88,7 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertEqual(jsondetails['string'], 'string')
def test_update_and_fetch(self):
SelfPacedConfiguration(enabled=True).save()
jsondetails = CourseDetails.fetch(self.course.id)
jsondetails.syllabus = "<a href='foo'>bar</a>"
# encode - decode to convert date fields and other data which changes form
......@@ -135,11 +137,6 @@ class CourseDetailsTestCase(CourseTestCase):
jsondetails.self_paced
)
@override_settings(FEATURES=dict(settings.FEATURES, ENABLE_SELF_PACED_COURSES=False))
def test_enable_self_paced(self):
details = CourseDetails.fetch(self.course.id)
self.assertNotIn('self_paced', details.__dict__)
@override_settings(MKTG_URLS={'ROOT': 'dummy-root'})
def test_marketing_site_fetch(self):
settings_details_url = get_url(self.course.id)
......@@ -325,6 +322,7 @@ class CourseDetailsViewTest(CourseTestCase):
return Date().to_json(datetime_obj)
def test_update_and_fetch(self):
SelfPacedConfiguration(enabled=True).save()
details = CourseDetails.fetch(self.course.id)
# resp s/b json from here on
......
......@@ -28,6 +28,7 @@ from openedx.core.lib.course_tabs import CourseTabPluginManager
from openedx.core.djangoapps.credit.api import is_credit_course, get_credit_requirements
from openedx.core.djangoapps.credit.tasks import update_credit_course_requirements
from openedx.core.djangoapps.content.course_structures.api.v0 import api, errors
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from xmodule.modulestore import EdxJSONEncoder
from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError
from opaque_keys import InvalidKeyError
......@@ -913,6 +914,9 @@ def settings_handler(request, course_key_string):
about_page_editable = not marketing_site_enabled
enrollment_end_editable = GlobalStaff().has_user(request.user) or not marketing_site_enabled
short_description_editable = settings.FEATURES.get('EDITABLE_SHORT_DESCRIPTION', True)
self_paced_enabled = SelfPacedConfiguration.current().enabled
settings_context = {
'context_course': course_module,
'course_locator': course_key,
......@@ -929,7 +933,8 @@ def settings_handler(request, course_key_string):
'show_min_grade_warning': False,
'enrollment_end_editable': enrollment_end_editable,
'is_prerequisite_courses_enabled': is_prerequisite_courses_enabled(),
'is_entrance_exams_enabled': is_entrance_exams_enabled()
'is_entrance_exams_enabled': is_entrance_exams_enabled(),
'self_paced_enabled': self_paced_enabled,
}
if is_prerequisite_courses_enabled():
courses, in_process_course_actions = get_courses_accessible_to_user(request)
......
......@@ -10,6 +10,7 @@ from opaque_keys.edx.locations import Location
from xmodule.modulestore.exceptions import ItemNotFoundError
from contentstore.utils import course_image_url, has_active_web_certificate
from models.settings import course_grading
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from xmodule.fields import Date
from xmodule.modulestore.django import modulestore
......@@ -54,8 +55,7 @@ class CourseDetails(object):
'50'
) # minimum passing score for entrance exam content module/tree,
self.has_cert_config = None # course has active certificate configuration
if settings.FEATURES.get('ENABLE_SELF_PACED_COURSES'):
self.self_paced = None
self.self_paced = None
@classmethod
def _fetch_about_attribute(cls, course_key, attribute):
......@@ -88,8 +88,7 @@ class CourseDetails(object):
# 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)
if settings.FEATURES.get('ENABLE_SELF_PACED_COURSES'):
course_details.self_paced = descriptor.self_paced
course_details.self_paced = descriptor.self_paced
for attribute in ABOUT_ATTRIBUTES:
value = cls._fetch_about_attribute(course_key, attribute)
......@@ -192,7 +191,7 @@ class CourseDetails(object):
descriptor.language = jsondict['language']
dirty = True
if (settings.FEATURES.get('ENABLE_SELF_PACED_COURSES')
if (SelfPacedConfiguration.current().enabled
and 'self_paced' in jsondict
and jsondict['self_paced'] != descriptor.self_paced):
descriptor.self_paced = jsondict['self_paced']
......
......@@ -106,9 +106,6 @@ FEATURES['ENTRANCE_EXAMS'] = True
FEATURES['ENABLE_PROCTORED_EXAMS'] = True
# Enable self-paced courses
FEATURES['ENABLE_SELF_PACED_COURSES'] = True
# Point the URL used to test YouTube availability to our stub YouTube server
YOUTUBE_PORT = 9080
YOUTUBE['API'] = "http://127.0.0.1:{0}/get_youtube_api/".format(YOUTUBE_PORT)
......
......@@ -182,9 +182,6 @@ FEATURES = {
# Timed or Proctored Exams
'ENABLE_PROCTORED_EXAMS': False,
# Enable self-paced courses.
'ENABLE_SELF_PACED_COURSES': False,
}
ENABLE_JASMINE = False
......@@ -796,6 +793,9 @@ INSTALLED_APPS = (
# programs support
'openedx.core.djangoapps.programs',
# Self-paced course configuration
'openedx.core.djangoapps.self_paced',
)
......
......@@ -279,6 +279,3 @@ FEATURES['ENABLE_TEAMS'] = True
# Dummy secret key for dev/test
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
# Enable self-paced courses
FEATURES['ENABLE_SELF_PACED_COURSES'] = True
......@@ -412,7 +412,7 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
</section>
% endif
% if settings.FEATURES.get("ENABLE_SELF_PACED_COURSES", False):
% if self_paced_enabled:
<hr class="divide" />
......
......@@ -14,6 +14,7 @@ from ...pages.studio.utils import add_discussion, drag, verify_ordering
from ...pages.lms.courseware import CoursewarePage
from ...pages.lms.course_nav import CourseNavPage
from ...pages.lms.staff_view import StaffPage
from ...fixtures.config import ConfigModelFixture
from ...fixtures.course import XBlockFixtureDesc
from base_studio_test import StudioCourseTest
......@@ -1766,6 +1767,7 @@ class SelfPacedOutlineTest(CourseOutlineTest):
),
)
self.course_fixture.add_course_details({'self_paced': True})
ConfigModelFixture('/config/self_paced', {'enabled': True}).install()
def test_release_dates_not_shown(self):
"""
......
......@@ -4,6 +4,7 @@ Acceptance tests for Studio's Settings Details pages
from unittest import skip
from .base_studio_test import StudioCourseTest
from ...fixtures.config import ConfigModelFixture
from ...fixtures.course import CourseFixture
from ...pages.studio.settings import SettingsPage
from ...pages.studio.overview import CourseOutlinePage
......@@ -202,6 +203,9 @@ class SettingsMilestonesTest(StudioSettingsDetailsTest):
class CoursePacingTest(StudioSettingsDetailsTest):
"""Tests for setting a course to self-paced."""
def populate_course_fixture(self, __):
ConfigModelFixture('/config/self_paced', {'enabled': True}).install()
def test_default_instructor_led(self):
"""
Test that the 'instructor led' button is checked by default.
......
......@@ -4,6 +4,7 @@ dates for each block in the course.
"""
from .field_overrides import FieldOverrideProvider
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
class SelfPacedDateOverrideProvider(FieldOverrideProvider):
......@@ -20,4 +21,4 @@ class SelfPacedDateOverrideProvider(FieldOverrideProvider):
@classmethod
def enabled_for(cls, course):
"""This provider is enabled for self-paced courses only."""
return course.self_paced
return SelfPacedConfiguration.current().enabled and course.self_paced
......@@ -7,12 +7,13 @@ from urllib import urlencode
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from util.date_utils import strftime_localized
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_CLOSED_MODULESTORE
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
from student.models import CourseEnrollment
from .helpers import LoginEnrollmentTestCase
......@@ -114,3 +115,34 @@ class CourseInfoTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertNotIn(self.xml_data, resp.content)
@attr('shard_1')
@override_settings(FEATURES=dict(settings.FEATURES, EMBARGO=False))
class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
"""
Tests for the info page of self-paced courses.
"""
def setUp(self):
super(SelfPacedCourseInfoTestCase, self).setUp()
self.instructor_led_course = CourseFactory.create(self_paced=False)
self.self_paced_course = CourseFactory.create(self_paced=True)
self.setup_user()
def fetch_course_info_with_queries(self, course, sql_queries, mongo_queries):
"""
Fetch the given course's info page, asserting the number of SQL
and Mongo queries.
"""
url = reverse('info', args=[unicode(course.id)])
with self.assertNumQueries(sql_queries):
with check_mongo_calls(mongo_queries):
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
def test_num_queries_instructor_led(self):
self.fetch_course_info_with_queries(self.instructor_led_course, 14, 4)
def test_num_queries_self_paced(self):
self.fetch_course_info_with_queries(self.self_paced_course, 14, 4)
......@@ -9,6 +9,7 @@ from django.test.utils import override_settings
from student.tests.factories import UserFactory
from lms.djangoapps.ccx.tests.test_overrides import inject_field_overrides
from lms.djangoapps.courseware.field_overrides import OverrideFieldData
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......@@ -22,10 +23,9 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
"""
def setUp(self):
SelfPacedConfiguration(enabled=True).save()
super(SelfPacedDateOverrideTest, self).setUp()
self.due_date = datetime(2015, 5, 26, 8, 30, 00).replace(tzinfo=tzutc())
self.instructor_led_course, self.il_section = self.setup_course("Instructor Led Course", False)
self.self_paced_course, self.sp_section = self.setup_course("Self-Paced Course", True)
def tearDown(self):
super(SelfPacedDateOverrideTest, self).tearDown()
......@@ -43,7 +43,14 @@ class SelfPacedDateOverrideTest(ModuleStoreTestCase):
return (course, section)
def test_instructor_led(self):
self.assertEqual(self.due_date, self.il_section.due)
__, il_section = self.setup_course("Instructor Led Course", False)
self.assertEqual(self.due_date, il_section.due)
def test_self_paced(self):
self.assertIsNone(self.sp_section.due)
__, sp_section = self.setup_course("Self-Paced Course", True)
self.assertIsNone(sp_section.due)
def test_self_paced_disabled(self):
SelfPacedConfiguration(enabled=False).save()
__, sp_section = self.setup_course("Self-Paced Course", True)
self.assertEqual(self.due_date, sp_section.due)
......@@ -677,10 +677,9 @@ if FEATURES.get('INDIVIDUAL_DUE_DATES'):
)
##### Self-Paced Course Due Dates #####
if FEATURES.get('ENABLE_SELF_PACED_COURSES'):
FIELD_OVERRIDE_PROVIDERS += (
'courseware.self_paced_overrides.SelfPacedDateOverrideProvider',
)
FIELD_OVERRIDE_PROVIDERS += (
'courseware.self_paced_overrides.SelfPacedDateOverrideProvider',
)
# PROFILE IMAGE CONFIG
PROFILE_IMAGE_BACKEND = ENV_TOKENS.get('PROFILE_IMAGE_BACKEND', PROFILE_IMAGE_BACKEND)
......
......@@ -131,9 +131,6 @@ FEATURES['LICENSING'] = True
# Use the auto_auth workflow for creating users and logging them in
FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] = True
# Enable self-paced courses
FEATURES['ENABLE_SELF_PACED_COURSES'] = True
########################### Entrance Exams #################################
FEATURES['MILESTONES_APP'] = True
FEATURES['ENTRANCE_EXAMS'] = True
......
......@@ -408,9 +408,6 @@ FEATURES = {
# Enable LTI Provider feature.
'ENABLE_LTI_PROVIDER': False,
# Enable self-paced courses.
'ENABLE_SELF_PACED_COURSES': False,
}
# Ignore static asset files on import which match this pattern
......@@ -1970,6 +1967,9 @@ INSTALLED_APPS = (
# programs support
'openedx.core.djangoapps.programs',
# Self-paced course configuration
'openedx.core.djangoapps.self_paced',
)
######################### CSRF #########################################
......
......@@ -532,6 +532,3 @@ AUTHENTICATION_BACKENDS += ('lti_provider.users.LtiBackend',)
# ORGANIZATIONS
FEATURES['ORGANIZATIONS_APP'] = True
# Enable self-paced courses
FEATURES['ENABLE_SELF_PACED_COURSES'] = True
......@@ -11,6 +11,9 @@ import django.contrib.auth.views
from microsite_configuration import microsite
import auth_exchange.views
from config_models.views import ConfigurationModelCurrentAPIView
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
# Uncomment the next two lines to enable the admin:
if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
admin.autodiscover()
......@@ -739,6 +742,10 @@ if settings.FEATURES.get("ENABLE_LTI_PROVIDER"):
url(r'^lti_provider/', include('lti_provider.urls')),
)
urlpatterns += (
url(r'config/self_paced', ConfigurationModelCurrentAPIView.as_view(model=SelfPacedConfiguration)),
)
urlpatterns = patterns(*urlpatterns)
if settings.DEBUG:
......
"""
Admin site bindings for self-paced courses.
"""
from django.contrib import admin
from config_models.admin import ConfigurationModelAdmin
from .models import SelfPacedConfiguration
admin.site.register(SelfPacedConfiguration, ConfigurationModelAdmin)
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as 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 'SelfPacedConfiguration'
db.create_table('self_paced_selfpacedconfiguration', (
('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)),
))
db.send_create_signal('self_paced', ['SelfPacedConfiguration'])
def backwards(self, orm):
# Deleting model 'SelfPacedConfiguration'
db.delete_table('self_paced_selfpacedconfiguration')
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'})
},
'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'})
},
'self_paced.selfpacedconfiguration': {
'Meta': {'ordering': "('-change_date',)", 'object_name': 'SelfPacedConfiguration'},
'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'})
}
}
complete_apps = ['self_paced']
\ No newline at end of file
"""
Configuration for self-paced courses.
"""
from config_models.models import ConfigurationModel
class SelfPacedConfiguration(ConfigurationModel):
"""
Configuration for self-paced courses.
"""
pass
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