Skip to content
Snippets Groups Projects
Commit 5ffa06be authored by Peter Fogg's avatar Peter Fogg
Browse files

Responding to review comments.

parent 4805946a
No related merge requests found
Showing
with 165 additions and 41 deletions
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment