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
eaab2cf4
Commit
eaab2cf4
authored
May 19, 2017
by
Robert Raposa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add course overrides of waffle flags.
parent
867fac31
Hide whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
731 additions
and
130 deletions
+731
-130
cms/envs/common.py
+3
-0
common/djangoapps/student/views.py
+1
-1
lms/djangoapps/ccx/tests/test_field_override_performance.py
+27
-27
lms/djangoapps/courseware/tabs.py
+3
-5
lms/djangoapps/courseware/tests/test_date_summary.py
+6
-6
lms/djangoapps/courseware/tests/test_tabs.py
+4
-4
lms/djangoapps/courseware/tests/test_views.py
+5
-5
lms/djangoapps/courseware/views/views.py
+9
-8
lms/djangoapps/grades/config/waffle.py
+2
-2
lms/envs/common.py
+3
-0
lms/templates/dashboard/_dashboard_course_listing.html
+1
-1
lms/templates/shoppingcart/registration_code_receipt.html
+1
-1
lms/tests.py
+1
-1
openedx/core/djangoapps/content/block_structure/config/__init__.py
+2
-2
openedx/core/djangoapps/monitoring_utils/middleware.py
+2
-2
openedx/core/djangoapps/waffle_utils/__init__.py
+218
-28
openedx/core/djangoapps/waffle_utils/admin.py
+29
-0
openedx/core/djangoapps/waffle_utils/forms.py
+35
-0
openedx/core/djangoapps/waffle_utils/migrations/0001_initial.py
+34
-0
openedx/core/djangoapps/waffle_utils/migrations/__init__.py
+0
-0
openedx/core/djangoapps/waffle_utils/models.py
+61
-0
openedx/core/djangoapps/waffle_utils/tests/__init__.py
+0
-0
openedx/core/djangoapps/waffle_utils/tests/test_init.py
+52
-0
openedx/core/djangoapps/waffle_utils/tests/test_models.py
+51
-0
openedx/core/djangoapps/waffle_utils/tests/test_testutils.py
+57
-0
openedx/core/djangoapps/waffle_utils/testutils.py
+60
-0
openedx/core/lib/courses.py
+38
-0
openedx/features/course_experience/__init__.py
+17
-8
openedx/features/course_experience/templates/course_experience/course-home-fragment.html
+3
-4
openedx/features/course_experience/templates/course_experience/course-updates-fragment.html
+0
-8
openedx/features/course_experience/tests/views/test_course_home.py
+5
-16
openedx/features/course_experience/tests/views/test_course_updates.py
+1
-1
No files found.
cms/envs/common.py
View file @
eaab2cf4
...
@@ -1010,6 +1010,9 @@ INSTALLED_APPS = (
...
@@ -1010,6 +1010,9 @@ INSTALLED_APPS = (
# Customized celery tasks, including persisting failed tasks so they can
# Customized celery tasks, including persisting failed tasks so they can
# be retried
# be retried
'celery_utils'
,
'celery_utils'
,
# Waffle related utilities
'openedx.core.djangoapps.waffle_utils'
,
)
)
...
...
common/djangoapps/student/views.py
View file @
eaab2cf4
...
@@ -2267,7 +2267,7 @@ def auto_auth(request):
...
@@ -2267,7 +2267,7 @@ def auto_auth(request):
elif
course_id
:
elif
course_id
:
# Redirect to the course homepage (in LMS) or outline page (in Studio)
# Redirect to the course homepage (in LMS) or outline page (in Studio)
try
:
try
:
redirect_url
=
reverse
(
course_home_url_name
(
request
),
kwargs
=
{
'course_id'
:
course_id
})
redirect_url
=
reverse
(
course_home_url_name
(
course_key
),
kwargs
=
{
'course_id'
:
course_id
})
except
NoReverseMatch
:
except
NoReverseMatch
:
redirect_url
=
reverse
(
'course_handler'
,
kwargs
=
{
'course_key_string'
:
course_id
})
redirect_url
=
reverse
(
'course_handler'
,
kwargs
=
{
'course_key_string'
:
course_id
})
else
:
else
:
...
...
lms/djangoapps/ccx/tests/test_field_override_performance.py
View file @
eaab2cf4
...
@@ -231,18 +231,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
...
@@ -231,18 +231,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
# # of sql queries to default,
# # of sql queries to default,
# # of mongo queries,
# # of mongo queries,
# )
# )
(
'no_overrides'
,
1
,
True
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
1
,
True
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
5
,
1
),
(
'ccx'
,
1
,
True
,
False
):
(
2
4
,
1
),
(
'ccx'
,
1
,
True
,
False
):
(
2
5
,
1
),
(
'ccx'
,
2
,
True
,
False
):
(
2
4
,
1
),
(
'ccx'
,
2
,
True
,
False
):
(
2
5
,
1
),
(
'ccx'
,
3
,
True
,
False
):
(
2
4
,
1
),
(
'ccx'
,
3
,
True
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
5
,
1
),
(
'ccx'
,
1
,
False
,
False
):
(
2
4
,
1
),
(
'ccx'
,
1
,
False
,
False
):
(
2
5
,
1
),
(
'ccx'
,
2
,
False
,
False
):
(
2
4
,
1
),
(
'ccx'
,
2
,
False
,
False
):
(
2
5
,
1
),
(
'ccx'
,
3
,
False
,
False
):
(
2
4
,
1
),
(
'ccx'
,
3
,
False
,
False
):
(
2
5
,
1
),
}
}
...
@@ -254,19 +254,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
...
@@ -254,19 +254,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__
=
True
__test__
=
True
TEST_DATA
=
{
TEST_DATA
=
{
(
'no_overrides'
,
1
,
True
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
1
,
True
,
False
):
(
2
5
,
3
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
5
,
3
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
5
,
3
),
(
'ccx'
,
1
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
1
,
True
,
False
):
(
2
5
,
3
),
(
'ccx'
,
2
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
2
,
True
,
False
):
(
2
5
,
3
),
(
'ccx'
,
3
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
3
,
True
,
False
):
(
2
5
,
3
),
(
'ccx'
,
1
,
True
,
True
):
(
2
5
,
3
),
(
'ccx'
,
1
,
True
,
True
):
(
2
6
,
3
),
(
'ccx'
,
2
,
True
,
True
):
(
2
5
,
3
),
(
'ccx'
,
2
,
True
,
True
):
(
2
6
,
3
),
(
'ccx'
,
3
,
True
,
True
):
(
2
5
,
3
),
(
'ccx'
,
3
,
True
,
True
):
(
2
6
,
3
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
5
,
3
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
5
,
3
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
5
,
3
),
(
'ccx'
,
1
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
1
,
False
,
False
):
(
2
5
,
3
),
(
'ccx'
,
2
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
2
,
False
,
False
):
(
2
5
,
3
),
(
'ccx'
,
3
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
3
,
False
,
False
):
(
2
5
,
3
),
}
}
lms/djangoapps/courseware/tabs.py
View file @
eaab2cf4
...
@@ -2,15 +2,14 @@
...
@@ -2,15 +2,14 @@
This module is essentially a broker to xmodule/tabs.py -- it was originally introduced to
This module is essentially a broker to xmodule/tabs.py -- it was originally introduced to
perform some LMS-specific tab display gymnastics for the Entrance Exams feature
perform some LMS-specific tab display gymnastics for the Entrance Exams feature
"""
"""
import
waffle
from
django.conf
import
settings
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
,
ugettext_noop
from
django.utils.translation
import
ugettext
as
_
,
ugettext_noop
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
courseware.entrance_exams
import
user_can_skip_entrance_exam
from
courseware.entrance_exams
import
user_can_skip_entrance_exam
from
openedx.core.lib.course_tabs
import
CourseTabPluginManager
from
openedx.core.lib.course_tabs
import
CourseTabPluginManager
from
openedx.features.course_experience
import
default_course_url_name
,
UNIFIED_COURSE_EXPERIENCE_FLAG
from
openedx.features.course_experience
import
default_course_url_name
from
openedx.features.course_experience
import
UNIFIED_COURSE_TAB_FLAG
from
request_cache.middleware
import
RequestCache
from
request_cache.middleware
import
RequestCache
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
xmodule.tabs
import
CourseTab
,
CourseTabList
,
key_checker
,
link_reverse_func
from
xmodule.tabs
import
CourseTab
,
CourseTabList
,
key_checker
,
link_reverse_func
...
@@ -66,8 +65,7 @@ class CourseInfoTab(CourseTab):
...
@@ -66,8 +65,7 @@ class CourseInfoTab(CourseTab):
"""
"""
The "Home" tab is not shown for the new unified course experience.
The "Home" tab is not shown for the new unified course experience.
"""
"""
request
=
RequestCache
.
get_current_request
()
return
not
UNIFIED_COURSE_TAB_FLAG
.
is_enabled
(
course
.
id
)
return
not
waffle
.
flag_is_active
(
request
,
UNIFIED_COURSE_EXPERIENCE_FLAG
)
class
SyllabusTab
(
EnrolledTab
):
class
SyllabusTab
(
EnrolledTab
):
...
...
lms/djangoapps/courseware/tests/test_date_summary.py
View file @
eaab2cf4
...
@@ -7,8 +7,6 @@ from django.core.urlresolvers import reverse
...
@@ -7,8 +7,6 @@ from django.core.urlresolvers import reverse
from
freezegun
import
freeze_time
from
freezegun
import
freeze_time
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
from
pytz
import
utc
from
pytz
import
utc
from
waffle.testutils
import
override_flag
from
openedx.features.course_experience
import
UNIFIED_COURSE_EXPERIENCE_FLAG
from
commerce.models
import
CommerceConfiguration
from
commerce.models
import
CommerceConfiguration
from
course_modes.tests.factories
import
CourseModeFactory
from
course_modes.tests.factories
import
CourseModeFactory
...
@@ -24,6 +22,8 @@ from courseware.date_summary import (
...
@@ -24,6 +22,8 @@ from courseware.date_summary import (
)
)
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.user_api.preferences.api
import
set_user_preference
from
openedx.core.djangoapps.user_api.preferences.api
import
set_user_preference
from
openedx.core.djangoapps.waffle_utils.testutils
import
override_waffle_flag
from
openedx.features.course_experience
import
UNIFIED_COURSE_TAB_FLAG
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
lms.djangoapps.verify_student.models
import
VerificationDeadline
from
lms.djangoapps.verify_student.models
import
VerificationDeadline
from
lms.djangoapps.verify_student.tests.factories
import
SoftwareSecurePhotoVerificationFactory
from
lms.djangoapps.verify_student.tests.factories
import
SoftwareSecurePhotoVerificationFactory
...
@@ -194,7 +194,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
...
@@ -194,7 +194,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'info'
,
'info'
,
'openedx.course_experience.course_home'
,
'openedx.course_experience.course_home'
,
)
)
@override_
flag
(
UNIFIED_COURSE_EXPERIENCE
_FLAG
,
active
=
True
)
@override_
waffle_flag
(
UNIFIED_COURSE_TAB
_FLAG
,
active
=
True
)
def
test_todays_date_no_timezone
(
self
,
url_name
):
def
test_todays_date_no_timezone
(
self
,
url_name
):
with
freeze_time
(
'2015-01-02'
):
with
freeze_time
(
'2015-01-02'
):
self
.
setup_course_and_user
()
self
.
setup_course_and_user
()
...
@@ -218,7 +218,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
...
@@ -218,7 +218,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'info'
,
'info'
,
'openedx.course_experience.course_home'
,
'openedx.course_experience.course_home'
,
)
)
@override_
flag
(
UNIFIED_COURSE_EXPERIENCE
_FLAG
,
active
=
True
)
@override_
waffle_flag
(
UNIFIED_COURSE_TAB
_FLAG
,
active
=
True
)
def
test_todays_date_timezone
(
self
,
url_name
):
def
test_todays_date_timezone
(
self
,
url_name
):
with
freeze_time
(
'2015-01-02'
):
with
freeze_time
(
'2015-01-02'
):
self
.
setup_course_and_user
()
self
.
setup_course_and_user
()
...
@@ -249,7 +249,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
...
@@ -249,7 +249,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'info'
,
'info'
,
'openedx.course_experience.course_home'
,
'openedx.course_experience.course_home'
,
)
)
@override_
flag
(
UNIFIED_COURSE_EXPERIENCE
_FLAG
,
active
=
True
)
@override_
waffle_flag
(
UNIFIED_COURSE_TAB
_FLAG
,
active
=
True
)
def
test_start_date_render
(
self
,
url_name
):
def
test_start_date_render
(
self
,
url_name
):
with
freeze_time
(
'2015-01-02'
):
with
freeze_time
(
'2015-01-02'
):
self
.
setup_course_and_user
()
self
.
setup_course_and_user
()
...
@@ -267,7 +267,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
...
@@ -267,7 +267,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'info'
,
'info'
,
'openedx.course_experience.course_home'
,
'openedx.course_experience.course_home'
,
)
)
@override_
flag
(
UNIFIED_COURSE_EXPERIENCE
_FLAG
,
active
=
True
)
@override_
waffle_flag
(
UNIFIED_COURSE_TAB
_FLAG
,
active
=
True
)
def
test_start_date_render_time_zone
(
self
,
url_name
):
def
test_start_date_render_time_zone
(
self
,
url_name
):
with
freeze_time
(
'2015-01-02'
):
with
freeze_time
(
'2015-01-02'
):
self
.
setup_course_and_user
()
self
.
setup_course_and_user
()
...
...
lms/djangoapps/courseware/tests/test_tabs.py
View file @
eaab2cf4
...
@@ -2,8 +2,6 @@
...
@@ -2,8 +2,6 @@
Test cases for tabs.
Test cases for tabs.
"""
"""
from
waffle.testutils
import
override_flag
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.http
import
Http404
from
django.http
import
Http404
from
mock
import
MagicMock
,
Mock
,
patch
from
mock
import
MagicMock
,
Mock
,
patch
...
@@ -18,7 +16,8 @@ from courseware.tests.helpers import LoginEnrollmentTestCase
...
@@ -18,7 +16,8 @@ from courseware.tests.helpers import LoginEnrollmentTestCase
from
courseware.tests.factories
import
InstructorFactory
,
StaffFactory
from
courseware.tests.factories
import
InstructorFactory
,
StaffFactory
from
courseware.views.views
import
get_static_tab_fragment
,
StaticCourseTabView
from
courseware.views.views
import
get_static_tab_fragment
,
StaticCourseTabView
from
openedx.core.djangolib.testing.utils
import
get_mock_request
from
openedx.core.djangolib.testing.utils
import
get_mock_request
from
openedx.features.course_experience
import
UNIFIED_COURSE_EXPERIENCE_FLAG
from
openedx.core.djangoapps.waffle_utils.testutils
import
override_waffle_flag
from
openedx.features.course_experience
import
UNIFIED_COURSE_TAB_FLAG
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
util.milestones_helpers
import
(
from
util.milestones_helpers
import
(
...
@@ -776,12 +775,13 @@ class CourseInfoTabTestCase(TabTestCase):
...
@@ -776,12 +775,13 @@ class CourseInfoTabTestCase(TabTestCase):
self
.
user
=
self
.
create_mock_user
()
self
.
user
=
self
.
create_mock_user
()
self
.
request
=
get_mock_request
(
self
.
user
)
self
.
request
=
get_mock_request
(
self
.
user
)
@override_waffle_flag
(
UNIFIED_COURSE_TAB_FLAG
,
active
=
False
)
def
test_default_tab
(
self
):
def
test_default_tab
(
self
):
# Verify that the course info tab is the first tab
# Verify that the course info tab is the first tab
tabs
=
get_course_tab_list
(
self
.
request
,
self
.
course
)
tabs
=
get_course_tab_list
(
self
.
request
,
self
.
course
)
self
.
assertEqual
(
tabs
[
0
]
.
type
,
'course_info'
)
self
.
assertEqual
(
tabs
[
0
]
.
type
,
'course_info'
)
@override_
flag
(
UNIFIED_COURSE_EXPERIENCE
_FLAG
,
active
=
True
)
@override_
waffle_flag
(
UNIFIED_COURSE_TAB
_FLAG
,
active
=
True
)
def
test_default_tab_for_new_course_experience
(
self
):
def
test_default_tab_for_new_course_experience
(
self
):
# Verify that the unified course experience hides the course info tab
# Verify that the unified course experience hides the course info tab
tabs
=
get_course_tab_list
(
self
.
request
,
self
.
course
)
tabs
=
get_course_tab_list
(
self
.
request
,
self
.
course
)
...
...
lms/djangoapps/courseware/tests/test_views.py
View file @
eaab2cf4
...
@@ -210,8 +210,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
...
@@ -210,8 +210,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS
=
20
NUM_PROBLEMS
=
20
@ddt.data
(
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
10
,
14
2
),
(
ModuleStoreEnum
.
Type
.
mongo
,
10
,
14
3
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
14
2
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
14
3
),
)
)
@ddt.unpack
@ddt.unpack
def
test_index_query_counts
(
self
,
store_type
,
expected_mongo_query_count
,
expected_mysql_query_count
):
def
test_index_query_counts
(
self
,
store_type
,
expected_mongo_query_count
,
expected_mysql_query_count
):
...
@@ -1420,12 +1420,12 @@ class ProgressPageTests(ProgressPageBaseTests):
...
@@ -1420,12 +1420,12 @@ class ProgressPageTests(ProgressPageBaseTests):
"""Test that query counts remain the same for self-paced and instructor-paced courses."""
"""Test that query counts remain the same for self-paced and instructor-paced courses."""
SelfPacedConfiguration
(
enabled
=
self_paced_enabled
)
.
save
()
SelfPacedConfiguration
(
enabled
=
self_paced_enabled
)
.
save
()
self
.
setup_course
(
self_paced
=
self_paced
)
self
.
setup_course
(
self_paced
=
self_paced
)
with
self
.
assertNumQueries
(
4
0
),
check_mongo_calls
(
1
):
with
self
.
assertNumQueries
(
4
1
),
check_mongo_calls
(
1
):
self
.
_get_progress_page
()
self
.
_get_progress_page
()
@ddt.data
(
@ddt.data
(
(
False
,
4
0
,
26
),
(
False
,
4
1
,
27
),
(
True
,
3
3
,
22
)
(
True
,
3
4
,
23
)
)
)
@ddt.unpack
@ddt.unpack
def
test_progress_queries
(
self
,
enable_waffle
,
initial
,
subsequent
):
def
test_progress_queries
(
self
,
enable_waffle
,
initial
,
subsequent
):
...
...
lms/djangoapps/courseware/views/views.py
View file @
eaab2cf4
...
@@ -86,9 +86,9 @@ from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender
...
@@ -86,9 +86,9 @@ from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.features.course_experience
import
(
from
openedx.features.course_experience
import
(
UNIFIED_COURSE_
EXPERIENCE
_FLAG
,
UNIFIED_COURSE_
TAB
_FLAG
,
UNIFIED_COURSE_VIEW_FLAG
,
UNIFIED_COURSE_VIEW_FLAG
,
course_home_url_name
course_home_url_name
,
)
)
from
openedx.features.course_experience.views.course_dates
import
CourseDatesFragmentView
from
openedx.features.course_experience.views.course_dates
import
CourseDatesFragmentView
from
openedx.features.enterprise_support.api
import
data_sharing_consent_required
from
openedx.features.enterprise_support.api
import
data_sharing_consent_required
...
@@ -263,11 +263,12 @@ def course_info(request, course_id):
...
@@ -263,11 +263,12 @@ def course_info(request, course_id):
return
url
return
url
return
None
return
None
course_key
=
CourseKey
.
from_string
(
course_id
)
# If the unified course experience is enabled, redirect to the "Course" tab
# If the unified course experience is enabled, redirect to the "Course" tab
if
waffle
.
flag_is_active
(
request
,
UNIFIED_COURSE_EXPERIENCE_FLAG
):
if
UNIFIED_COURSE_TAB_FLAG
.
is_enabled
(
course_key
):
return
redirect
(
reverse
(
course_home_url_name
(
request
),
args
=
[
course_id
]))
return
redirect
(
reverse
(
course_home_url_name
(
course_key
),
args
=
[
course_id
]))
course_key
=
CourseKey
.
from_string
(
course_id
)
with
modulestore
()
.
bulk_operations
(
course_key
):
with
modulestore
()
.
bulk_operations
(
course_key
):
course
=
get_course_by_id
(
course_key
,
depth
=
2
)
course
=
get_course_by_id
(
course_key
,
depth
=
2
)
access_response
=
has_access
(
request
.
user
,
'load'
,
course
,
course_key
)
access_response
=
has_access
(
request
.
user
,
'load'
,
course
,
course_key
)
...
@@ -669,7 +670,7 @@ def course_about(request, course_id):
...
@@ -669,7 +670,7 @@ def course_about(request, course_id):
modes
=
CourseMode
.
modes_for_course_dict
(
course_key
)
modes
=
CourseMode
.
modes_for_course_dict
(
course_key
)
if
configuration_helpers
.
get_value
(
'ENABLE_MKTG_SITE'
,
settings
.
FEATURES
.
get
(
'ENABLE_MKTG_SITE'
,
False
)):
if
configuration_helpers
.
get_value
(
'ENABLE_MKTG_SITE'
,
settings
.
FEATURES
.
get
(
'ENABLE_MKTG_SITE'
,
False
)):
return
redirect
(
reverse
(
course_home_url_name
(
request
),
args
=
[
unicode
(
course
.
id
)]))
return
redirect
(
reverse
(
course_home_url_name
(
course
.
id
),
args
=
[
unicode
(
course
.
id
)]))
registered
=
registered_for_course
(
course
,
request
.
user
)
registered
=
registered_for_course
(
course
,
request
.
user
)
...
@@ -677,7 +678,7 @@ def course_about(request, course_id):
...
@@ -677,7 +678,7 @@ def course_about(request, course_id):
studio_url
=
get_studio_url
(
course
,
'settings/details'
)
studio_url
=
get_studio_url
(
course
,
'settings/details'
)
if
has_access
(
request
.
user
,
'load'
,
course
):
if
has_access
(
request
.
user
,
'load'
,
course
):
course_target
=
reverse
(
course_home_url_name
(
request
),
args
=
[
course
.
id
.
to_deprecated_string
()])
course_target
=
reverse
(
course_home_url_name
(
course
.
id
),
args
=
[
course
.
id
.
to_deprecated_string
()])
else
:
else
:
course_target
=
reverse
(
'about_course'
,
args
=
[
course
.
id
.
to_deprecated_string
()])
course_target
=
reverse
(
'about_course'
,
args
=
[
course
.
id
.
to_deprecated_string
()])
...
@@ -1241,7 +1242,7 @@ def course_survey(request, course_id):
...
@@ -1241,7 +1242,7 @@ def course_survey(request, course_id):
course_key
=
CourseKey
.
from_string
(
course_id
)
course_key
=
CourseKey
.
from_string
(
course_id
)
course
=
get_course_with_access
(
request
.
user
,
'load'
,
course_key
)
course
=
get_course_with_access
(
request
.
user
,
'load'
,
course_key
)
redirect_url
=
reverse
(
course_home_url_name
(
request
),
args
=
[
course_id
])
redirect_url
=
reverse
(
course_home_url_name
(
course
.
id
),
args
=
[
course_id
])
# if there is no Survey associated with this course,
# if there is no Survey associated with this course,
# then redirect to the course instead
# then redirect to the course instead
...
...
lms/djangoapps/grades/config/waffle.py
View file @
eaab2cf4
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
This module contains various configuration settings via
This module contains various configuration settings via
waffle switches for the Grades app.
waffle switches for the Grades app.
"""
"""
from
openedx.core.django
lib.waffle_utils
import
WaffleSwitchPlus
from
openedx.core.django
apps.waffle_utils
import
WaffleSwitchNamespace
# Namespace
# Namespace
...
@@ -18,4 +18,4 @@ def waffle():
...
@@ -18,4 +18,4 @@ def waffle():
"""
"""
Returns the namespaced, cached, audited Waffle class for Grades.
Returns the namespaced, cached, audited Waffle class for Grades.
"""
"""
return
WaffleSwitch
Plus
(
namespac
e
=
WAFFLE_NAMESPACE
,
log_prefix
=
u'Grades: '
)
return
WaffleSwitch
Namespace
(
nam
e
=
WAFFLE_NAMESPACE
,
log_prefix
=
u'Grades: '
)
lms/envs/common.py
View file @
eaab2cf4
...
@@ -2211,6 +2211,9 @@ INSTALLED_APPS = (
...
@@ -2211,6 +2211,9 @@ INSTALLED_APPS = (
# Unusual migrations
# Unusual migrations
'database_fixups'
,
'database_fixups'
,
# Waffle related utilities
'openedx.core.djangoapps.waffle_utils'
,
# Features
# Features
'openedx.features.course_bookmarks'
,
'openedx.features.course_bookmarks'
,
'openedx.features.course_experience'
,
'openedx.features.course_experience'
,
...
...
lms/templates/dashboard/_dashboard_course_listing.html
View file @
eaab2cf4
...
@@ -56,7 +56,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
...
@@ -56,7 +56,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
% endif
% endif
<div
class=
"course-container"
>
<div
class=
"course-container"
>
<article
class=
"course${mode_class}"
>
<article
class=
"course${mode_class}"
>
<
%
course_target =
reverse(course_home_url_name(),
args=
[unicode(course_overview.id)])
%
>
<
%
course_target =
reverse(course_home_url_name(
course_overview.id
),
args=
[unicode(course_overview.id)])
%
>
<section
class=
"details"
aria-labelledby=
"details-heading-${course_overview.number}"
>
<section
class=
"details"
aria-labelledby=
"details-heading-${course_overview.number}"
>
<h2
class=
"hd hd-2 sr"
id=
"details-heading-${course_overview.number}"
>
${_('Course details')}
</h2>
<h2
class=
"hd hd-2 sr"
id=
"details-heading-${course_overview.number}"
>
${_('Course details')}
</h2>
<div
class=
"wrapper-course-image"
aria-hidden=
"true"
>
<div
class=
"wrapper-course-image"
aria-hidden=
"true"
>
...
...
lms/templates/shoppingcart/registration_code_receipt.html
View file @
eaab2cf4
...
@@ -75,7 +75,7 @@ from openedx.features.course_experience import course_home_url_name
...
@@ -75,7 +75,7 @@ from openedx.features.course_experience import course_home_url_name
</div>
</div>
% if not reg_code_already_redeemed:
% if not reg_code_already_redeemed:
%if redemption_success:
%if redemption_success:
<
%
course_url =
reverse(course_home_url_name(),
args=
[course.id.to_deprecated_string()])
%
>
<
%
course_url =
reverse(course_home_url_name(
course.id
),
args=
[course.id.to_deprecated_string()])
%
>
<a
href=
"${course_url}"
class=
"link-button course-link-bg-color"
>
${_("View Course")}
<span
class=
"icon fa fa-caret-right"
aria-hidden=
"true"
></span></a>
<a
href=
"${course_url}"
class=
"link-button course-link-bg-color"
>
${_("View Course")}
<span
class=
"icon fa fa-caret-right"
aria-hidden=
"true"
></span></a>
%elif not registered_for_course:
%elif not registered_for_course:
<form
method=
"post"
>
<form
method=
"post"
>
...
...
lms/tests.py
View file @
eaab2cf4
...
@@ -55,6 +55,6 @@ class HelpModalTests(ModuleStoreTestCase):
...
@@ -55,6 +55,6 @@ class HelpModalTests(ModuleStoreTestCase):
Simple test to make sure that you don't get a 500 error when the modal
Simple test to make sure that you don't get a 500 error when the modal
is enabled.
is enabled.
"""
"""
url
=
reverse
(
course_home_url_name
(),
args
=
[
self
.
course
.
id
.
to_deprecated_string
()])
url
=
reverse
(
course_home_url_name
(
self
.
course
.
id
),
args
=
[
self
.
course
.
id
.
to_deprecated_string
()])
resp
=
self
.
client
.
get
(
url
)
resp
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
openedx/core/djangoapps/content/block_structure/config/__init__.py
View file @
eaab2cf4
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
This module contains various configuration settings via
This module contains various configuration settings via
waffle switches for the Block Structure framework.
waffle switches for the Block Structure framework.
"""
"""
from
openedx.core.django
lib.waffle_utils
import
WaffleSwitchPlus
from
openedx.core.django
apps.waffle_utils
import
WaffleSwitchNamespace
from
request_cache.middleware
import
request_cached
from
request_cache.middleware
import
request_cached
from
.models
import
BlockStructureConfiguration
from
.models
import
BlockStructureConfiguration
...
@@ -22,7 +22,7 @@ def waffle():
...
@@ -22,7 +22,7 @@ def waffle():
"""
"""
Returns the namespaced and cached Waffle class for BlockStructures.
Returns the namespaced and cached Waffle class for BlockStructures.
"""
"""
return
WaffleSwitch
Plus
(
namespac
e
=
WAFFLE_NAMESPACE
,
log_prefix
=
u'BlockStructure: '
)
return
WaffleSwitch
Namespace
(
nam
e
=
WAFFLE_NAMESPACE
,
log_prefix
=
u'BlockStructure: '
)
@request_cached
@request_cached
...
...
openedx/core/djangoapps/monitoring_utils/middleware.py
View file @
eaab2cf4
...
@@ -19,7 +19,7 @@ except ImportError:
...
@@ -19,7 +19,7 @@ except ImportError:
import
psutil
import
psutil
import
request_cache
import
request_cache
from
openedx.core.django
lib.waffle_utils
import
WaffleSwitchPlus
from
openedx.core.django
apps.waffle_utils
import
WaffleSwitchNamespace
REQUEST_CACHE_KEY
=
'monitoring_custom_metrics'
REQUEST_CACHE_KEY
=
'monitoring_custom_metrics'
...
@@ -163,4 +163,4 @@ class MonitoringMemoryMiddleware(object):
...
@@ -163,4 +163,4 @@ class MonitoringMemoryMiddleware(object):
"""
"""
Returns whether this middleware is enabled.
Returns whether this middleware is enabled.
"""
"""
return
WaffleSwitch
Plus
(
namespac
e
=
WAFFLE_NAMESPACE
)
.
is_enabled
(
u'enable_memory_middleware'
)
return
WaffleSwitch
Namespace
(
nam
e
=
WAFFLE_NAMESPACE
)
.
is_enabled
(
u'enable_memory_middleware'
)
openedx/core/django
lib/waffle_utils
.py
→
openedx/core/django
apps/waffle_utils/__init__
.py
View file @
eaab2cf4
"""
"""
Utilities for waffle usage.
Utilities for waffle.
Includes namespacing, caching, and course overrides for waffle flags.
Usage:
For Waffle Flags, first set up the namespace, and then create flags using the
namespace. For example:
WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience')
HIDE_SEARCH_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'hide_search')
UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'unified_course_tab')
You can check these flags in code using the following:
HIDE_SEARCH_FLAG.is_enabled()
UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key)
To test these WaffleFlags, see testutils.py.
In the above examples, you will use Django Admin "waffle" section to configure
for a flag named: course_experience.unified_course_tab
You could also use the Django Admin "waffle_utils" section to configure a course
override for this same flag (e.g. course_experience.unified_course_tab).
For Waffle Switches, first set up the namespace, and then create the flag name.
For example:
WAFFLE_SWITCHES = WaffleSwitchNamespace(name=WAFFLE_NAMESPACE)
ESTIMATE_FIRST_ATTEMPTED = 'estimate_first_attempted'
You can then use the switch as follows:
WAFFLE_SWITCHES.is_enabled(waffle.ESTIMATE_FIRST_ATTEMPTED)
To test WaffleSwitchNamespace, use the provided context managers. For example:
with WAFFLE_SWITCHES.override(waffle.ESTIMATE_FIRST_ATTEMPTED, active=True):
...
"""
"""
import
logging
from
abc
import
ABCMeta
from
abc
import
ABCMeta
from
contextlib
import
contextmanager
from
contextlib
import
contextmanager
import
logging
from
waffle
import
switch_is_active
from
waffle.testutils
import
override_switch
as
waffle_override_switch
from
waffle.testutils
import
override_switch
as
waffle_override_switch
from
waffle
import
flag_is_active
,
switch_is_active
from
opaque_keys.edx.keys
import
CourseKey
from
request_cache
import
get_request
,
get_cache
as
get_request_cache
from
request_cache
import
get_cache
as
get_request_cache
from
.models
import
WaffleFlagCourseOverrideModel
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
class
Waffle
Plus
(
object
):
class
Waffle
Namespace
(
object
):
"""
"""
Waffle helper class that provides native support for
A base class for a request cached namespace for waffle flags/switches.
namespacing waffle settings and caching within a request.
An instance of this class represents a single namespace
(e.g. "course_experience"), and can be used to work with a set of
flags or switches that will all share this namespace.
"""
"""
__metaclass__
=
ABCMeta
__metaclass__
=
ABCMeta
def
__init__
(
self
,
namespace
,
log_prefix
=
None
):
def
__init__
(
self
,
name
,
log_prefix
=
None
):
self
.
namespace
=
namespace
"""
self
.
log_prefix
=
log_prefix
Initializes the waffle namespace instance.
Arguments:
name (String): Namespace string appended to start of all waffle
flags and switches (e.g. "grades")
log_prefix (String): Optional string to be appended to log messages
(e.g. "Grades: "). Defaults to ''.
"""
assert
name
,
"The name is required."
self
.
name
=
name
self
.
log_prefix
=
log_prefix
if
log_prefix
else
''
def
_namespaced_
setting_
name
(
self
,
setting_name
):
def
_namespaced_name
(
self
,
setting_name
):
"""
"""
Returns the namespaced name of the waffle switch/flag.
Returns the namespaced name of the waffle switch/flag.
For example, the namespaced name of a waffle switch/flag would be:
my_namespace.my_setting_name
Arguments:
setting_name (String): The name of the flag or switch.
"""
"""
assert
self
.
namespace
is
not
None
return
u'{}.{}'
.
format
(
self
.
name
,
setting_name
)
return
u'{}.{}'
.
format
(
self
.
namespace
,
setting_name
)
@staticmethod
@staticmethod
def
_get_request_cache
():
def
_get_request_cache
():
"""
"""
Returns
the request cache used by WafflePlus classe
s.
Returns
a request cache shared by all instances of this clas
s.
"""
"""
return
get_request_cache
(
'Waffle
Plus
'
)
return
get_request_cache
(
'Waffle
Namespace
'
)
class
WaffleSwitch
Plus
(
WafflePlus
):
class
WaffleSwitch
Namespace
(
WaffleNamespace
):
"""
"""
Waffle Switch helper class that provides native support for
Provides a single namespace for a set of waffle switches.
namespacing waffle switches and caching within a request.
All namespaced switch values are stored in a single request cache containing
all switches for all namespaces.
"""
"""
def
is_enabled
(
self
,
switch_name
):
def
is_enabled
(
self
,
switch_name
):
"""
"""
Returns and caches whether the given waffle switch is enabled.
Returns and caches whether the given waffle switch is enabled.
"""
"""
namespaced_switch_name
=
self
.
_namespaced_
setting_
name
(
switch_name
)
namespaced_switch_name
=
self
.
_namespaced_name
(
switch_name
)
value
=
self
.
_cached_switches
.
get
(
namespaced_switch_name
)
value
=
self
.
_cached_switches
.
get
(
namespaced_switch_name
)
if
value
is
None
:
if
value
is
None
:
value
=
switch_is_active
(
namespaced_switch_name
)
value
=
switch_is_active
(
namespaced_switch_name
)
...
@@ -76,7 +144,7 @@ class WaffleSwitchPlus(WafflePlus):
...
@@ -76,7 +144,7 @@ class WaffleSwitchPlus(WafflePlus):
this request (as this is not a context manager).
this request (as this is not a context manager).
Note: The value is overridden in the request cache, not in the model.
Note: The value is overridden in the request cache, not in the model.
"""
"""
namespaced_switch_name
=
self
.
_namespaced_
setting_
name
(
switch_name
)
namespaced_switch_name
=
self
.
_namespaced_name
(
switch_name
)
self
.
_cached_switches
[
namespaced_switch_name
]
=
active
self
.
_cached_switches
[
namespaced_switch_name
]
=
active
log
.
info
(
u"
%
sSwitch '
%
s' set to
%
s for request."
,
self
.
log_prefix
,
namespaced_switch_name
,
active
)
log
.
info
(
u"
%
sSwitch '
%
s' set to
%
s for request."
,
self
.
log_prefix
,
namespaced_switch_name
,
active
)
...
@@ -87,7 +155,7 @@ class WaffleSwitchPlus(WafflePlus):
...
@@ -87,7 +155,7 @@ class WaffleSwitchPlus(WafflePlus):
contextmanager.
contextmanager.
Note: The value is overridden in the model, not the request cache.
Note: The value is overridden in the model, not the request cache.
"""
"""
namespaced_switch_name
=
self
.
_namespaced_
setting_
name
(
switch_name
)
namespaced_switch_name
=
self
.
_namespaced_name
(
switch_name
)
with
waffle_override_switch
(
namespaced_switch_name
,
active
):
with
waffle_override_switch
(
namespaced_switch_name
,
active
):
log
.
info
(
u"
%
sSwitch '
%
s' set to
%
s in model."
,
self
.
log_prefix
,
namespaced_switch_name
,
active
)
log
.
info
(
u"
%
sSwitch '
%
s' set to
%
s in model."
,
self
.
log_prefix
,
namespaced_switch_name
,
active
)
yield
yield
...
@@ -95,14 +163,136 @@ class WaffleSwitchPlus(WafflePlus):
...
@@ -95,14 +163,136 @@ class WaffleSwitchPlus(WafflePlus):
@property
@property
def
_cached_switches
(
self
):
def
_cached_switches
(
self
):
"""
"""
Returns
cached active values of all switches in this namespac
e.
Returns
a dictionary of all namespaced switches in the request cach
e.
"""
"""
return
self
.
_all_cached_switches
.
setdefault
(
self
.
namespace
,
{})
return
self
.
_get_request_cache
()
.
setdefault
(
'switches'
,
{})
class
WaffleFlagNamespace
(
WaffleNamespace
):
"""
Provides a single namespace for a set of waffle flags.
All namespaced flag values are stored in a single request cache containing
all flags for all namespaces.
"""
__metaclass__
=
ABCMeta
@property
@property
def
_
all_cached_switche
s
(
self
):
def
_
cached_flag
s
(
self
):
"""
"""
Returns dictionary of all switches in the request cache,
Returns a dictionary of all namespaced flags in the request cache.
keyed by namespace.
"""
"""
return
self
.
_get_request_cache
()
.
setdefault
(
'switches'
,
{})
return
self
.
_get_request_cache
()
.
setdefault
(
'flags'
,
{})
def
is_flag_active
(
self
,
flag_name
,
check_before_waffle_callback
=
None
):
"""
Returns and caches whether the provided flag is active.
If the flag value is already cached in the request, it is returned.
If check_before_waffle_callback is supplied, it is called before
checking waffle.
If check_before_waffle_callback returns None, or if it is not supplied,
then waffle is used to check the flag.
Arguments:
flag_name (String): The name of the flag to check.
check_before_waffle_callback (function): (Optional) A function that
will be checked before continuing on to waffle. If
check_before_waffle_callback(namespaced_flag_name) returns True
or False, it is cached and returned. If it returns None, then
waffle is used.
"""
# validate arguments
namespaced_flag_name
=
self
.
_namespaced_name
(
flag_name
)
value
=
self
.
_cached_flags
.
get
(
namespaced_flag_name
)
if
value
is
None
:
if
check_before_waffle_callback
:
value
=
check_before_waffle_callback
(
namespaced_flag_name
)
if
value
is
None
:
value
=
flag_is_active
(
get_request
(),
namespaced_flag_name
)
self
.
_cached_flags
[
namespaced_flag_name
]
=
value
return
value
class
WaffleFlag
(
object
):
"""
Represents a single waffle flag, using a cached waffle namespace.
"""
def
__init__
(
self
,
waffle_namespace
,
flag_name
):
"""
Initializes the waffle flag instance.
Arguments:
waffle_namespace (WaffleFlagNamespace): Provides a cached namespace
for this flag.
flag_name (String): The name of the flag (without namespacing).
"""
self
.
waffle_namespace
=
waffle_namespace
self
.
flag_name
=
flag_name
def
is_enabled
(
self
):
"""
Returns whether or not the flag is enabled.
"""
return
self
.
waffle_namespace
.
is_flag_active
(
self
.
flag_name
)
class
CourseWaffleFlag
(
WaffleFlag
):
"""
Represents a single waffle flag that can be forced on/off for a course.
Uses a cached waffle namespace.
"""
def
_get_course_override_callback
(
self
,
course_id
):
"""
Returns a function to use as the check_before_waffle_callback.
Arguments:
course_id (CourseKey): The course to check for override before
checking waffle.
"""
def
course_override_callback
(
namespaced_flag_name
):
"""
Returns True/False if the flag was forced on or off for the provided
course. Returns None if the flag was not overridden.
Arguments:
namespaced_flag_name (String): A namespaced version of the flag
to check.
"""
force_override
=
WaffleFlagCourseOverrideModel
.
override_value
(
namespaced_flag_name
,
course_id
)
if
force_override
==
WaffleFlagCourseOverrideModel
.
ALL_CHOICES
.
on
:
return
True
if
force_override
==
WaffleFlagCourseOverrideModel
.
ALL_CHOICES
.
off
:
return
False
return
None
return
course_override_callback
def
is_enabled
(
self
,
course_id
=
None
):
"""
Returns whether or not the flag is enabled.
Arguments:
course_id (CourseKey): The course to check for override before
checking waffle.
"""
# validate arguments
assert
issubclass
(
type
(
course_id
),
CourseKey
),
"The course_id '{}' must be a CourseKey."
.
format
(
str
(
course_id
))
return
self
.
waffle_namespace
.
is_flag_active
(
self
.
flag_name
,
check_before_waffle_callback
=
self
.
_get_course_override_callback
(
course_id
)
)
openedx/core/djangoapps/waffle_utils/admin.py
0 → 100644
View file @
eaab2cf4
"""
Django admin page for waffle utils models
"""
from
django.contrib
import
admin
from
config_models.admin
import
ConfigurationModelAdmin
,
KeyedConfigurationModelAdmin
from
.forms
import
WaffleFlagCourseOverrideAdminForm
from
.models
import
WaffleFlagCourseOverrideModel
class
WaffleFlagCourseOverrideAdmin
(
KeyedConfigurationModelAdmin
):
"""
Admin for course override of waffle flags.
Includes search by course_id and waffle_flag.
"""
form
=
WaffleFlagCourseOverrideAdminForm
search_fields
=
[
'waffle_flag'
,
'course_id'
]
fieldsets
=
(
(
None
,
{
'fields'
:
(
'waffle_flag'
,
'course_id'
,
'override_choice'
,
'enabled'
),
'description'
:
'Enter a valid course id and an existing waffle flag. The waffle flag name is not validated.'
}),
)
admin
.
site
.
register
(
WaffleFlagCourseOverrideModel
,
WaffleFlagCourseOverrideAdmin
)
openedx/core/djangoapps/waffle_utils/forms.py
0 → 100644
View file @
eaab2cf4
"""
Defines a form for providing validation of subsection grade templates.
"""
from
django
import
forms
from
openedx.core.lib.courses
import
clean_course_id
from
.models
import
WaffleFlagCourseOverrideModel
class
WaffleFlagCourseOverrideAdminForm
(
forms
.
ModelForm
):
"""
Input form for course override of waffle flags, allowing us to verify data.
"""
class
Meta
(
object
):
model
=
WaffleFlagCourseOverrideModel
fields
=
'__all__'
def
clean_course_id
(
self
):
"""
Validate the course id
"""
return
clean_course_id
(
self
)
def
clean_waffle_flag
(
self
):
"""
Validate the waffle flag is an existing flag.
"""
cleaned_flag
=
self
.
cleaned_data
[
'waffle_flag'
]
if
not
cleaned_flag
:
msg
=
u'Waffle flag must be supplied.'
raise
forms
.
ValidationError
(
msg
)
return
cleaned_flag
.
strip
()
openedx/core/djangoapps/waffle_utils/migrations/0001_initial.py
0 → 100644
View file @
eaab2cf4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
import
django.db.models.deletion
from
django.conf
import
settings
from
django.db
import
migrations
,
models
import
openedx.core.djangoapps.xmodule_django.models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
migrations
.
swappable_dependency
(
settings
.
AUTH_USER_MODEL
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
'WaffleFlagCourseOverrideModel'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
serialize
=
False
,
auto_created
=
True
,
primary_key
=
True
)),
(
'change_date'
,
models
.
DateTimeField
(
auto_now_add
=
True
,
verbose_name
=
'Change date'
)),
(
'enabled'
,
models
.
BooleanField
(
default
=
False
,
verbose_name
=
'Enabled'
)),
(
'waffle_flag'
,
models
.
CharField
(
max_length
=
255
,
db_index
=
True
)),
(
'course_id'
,
openedx
.
core
.
djangoapps
.
xmodule_django
.
models
.
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)),
(
'override_choice'
,
models
.
CharField
(
default
=
b
'on'
,
max_length
=
3
,
choices
=
[(
b
'on'
,
'Force On'
),
(
b
'off'
,
'Force Off'
)])),
(
'changed_by'
,
models
.
ForeignKey
(
on_delete
=
django
.
db
.
models
.
deletion
.
PROTECT
,
editable
=
False
,
to
=
settings
.
AUTH_USER_MODEL
,
null
=
True
,
verbose_name
=
'Changed by'
)),
],
options
=
{
'verbose_name'
:
'Waffle flag course override'
,
'verbose_name_plural'
:
'Waffle flag course overrides'
,
},
),
]
openedx/core/djangoapps/waffle_utils/migrations/__init__.py
0 → 100644
View file @
eaab2cf4
openedx/core/djangoapps/waffle_utils/models.py
0 → 100644
View file @
eaab2cf4
"""
Models for configuring waffle utils.
"""
from
django.db.models
import
CharField
from
django.utils.translation
import
ugettext_lazy
as
_
from
model_utils
import
Choices
from
config_models.models
import
ConfigurationModel
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
from
request_cache.middleware
import
request_cached
class
WaffleFlagCourseOverrideModel
(
ConfigurationModel
):
"""
Used to force a waffle flag on or off for a course.
"""
OVERRIDE_CHOICES
=
Choices
((
'on'
,
_
(
'Force On'
)),
(
'off'
,
_
(
'Force Off'
)))
ALL_CHOICES
=
OVERRIDE_CHOICES
+
Choices
(
'unset'
)
KEY_FIELDS
=
(
'waffle_flag'
,
'course_id'
)
# The course that these features are attached to.
waffle_flag
=
CharField
(
max_length
=
255
,
db_index
=
True
)
course_id
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
override_choice
=
CharField
(
choices
=
OVERRIDE_CHOICES
,
default
=
OVERRIDE_CHOICES
.
on
,
max_length
=
3
)
@classmethod
@request_cached
def
override_value
(
cls
,
waffle_flag
,
course_id
):
"""
Returns whether the waffle flag was overridden (on or off) for the
course, or is unset.
Arguments:
waffle_flag (String): The name of the flag.
course_id (CourseKey): The course id for which the flag may have
been overridden.
If the current config is not set or disabled for this waffle flag and
course id, returns ALL_CHOICES.unset.
Otherwise, returns ALL_CHOICES.on or ALL_CHOICES.off as configured for
the override_choice.
"""
if
not
course_id
or
not
waffle_flag
:
return
cls
.
ALL_CHOICES
.
unset
effective
=
cls
.
objects
.
filter
(
waffle_flag
=
waffle_flag
,
course_id
=
course_id
)
.
order_by
(
'-change_date'
)
.
first
()
if
effective
and
effective
.
enabled
:
return
effective
.
override_choice
return
cls
.
ALL_CHOICES
.
unset
class
Meta
(
object
):
app_label
=
"waffle_utils"
verbose_name
=
'Waffle flag course override'
verbose_name_plural
=
'Waffle flag course overrides'
def
__unicode__
(
self
):
enabled_label
=
"Enabled"
if
self
.
enabled
else
"Not Enabled"
# pylint: disable=no-member
return
u"Course '{}': Persistent Grades {}"
.
format
(
self
.
course_id
.
to_deprecated_string
(),
enabled_label
)
openedx/core/djangoapps/waffle_utils/tests/__init__.py
0 → 100644
View file @
eaab2cf4
openedx/core/djangoapps/waffle_utils/tests/test_init.py
0 → 100644
View file @
eaab2cf4
"""
Tests for waffle utils features.
"""
import
ddt
from
django.test
import
TestCase
from
mock
import
patch
from
opaque_keys.edx.keys
import
CourseKey
from
waffle.testutils
import
override_flag
from
request_cache.middleware
import
RequestCache
from
..
import
CourseWaffleFlag
,
WaffleFlagNamespace
from
..models
import
WaffleFlagCourseOverrideModel
@ddt.ddt
class
TestCourseWaffleFlag
(
TestCase
):
"""
Tests the CourseWaffleFlag.
"""
NAMESPACE_NAME
=
"test_namespace"
FLAG_NAME
=
"test_flag"
NAMESPACED_FLAG_NAME
=
NAMESPACE_NAME
+
"."
+
FLAG_NAME
TEST_COURSE_KEY
=
CourseKey
.
from_string
(
"edX/DemoX/Demo_Course"
)
TEST_NAMESPACE
=
WaffleFlagNamespace
(
NAMESPACE_NAME
)
TEST_COURSE_FLAG
=
CourseWaffleFlag
(
TEST_NAMESPACE
,
FLAG_NAME
)
@ddt.data
(
{
'course_override'
:
WaffleFlagCourseOverrideModel
.
ALL_CHOICES
.
on
,
'waffle_enabled'
:
False
,
'result'
:
True
},
{
'course_override'
:
WaffleFlagCourseOverrideModel
.
ALL_CHOICES
.
off
,
'waffle_enabled'
:
True
,
'result'
:
False
},
{
'course_override'
:
WaffleFlagCourseOverrideModel
.
ALL_CHOICES
.
unset
,
'waffle_enabled'
:
True
,
'result'
:
True
},
{
'course_override'
:
WaffleFlagCourseOverrideModel
.
ALL_CHOICES
.
unset
,
'waffle_enabled'
:
False
,
'result'
:
False
},
)
def
test_course_waffle_flag
(
self
,
data
):
"""
Tests various combinations of a flag being set in waffle and overridden
for a course.
"""
RequestCache
.
clear_request_cache
()
with
patch
.
object
(
WaffleFlagCourseOverrideModel
,
'override_value'
,
return_value
=
data
[
'course_override'
]):
with
override_flag
(
self
.
NAMESPACED_FLAG_NAME
,
active
=
data
[
'waffle_enabled'
]):
# check twice to test that the result is properly cached
self
.
assertEqual
(
self
.
TEST_COURSE_FLAG
.
is_enabled
(
self
.
TEST_COURSE_KEY
),
data
[
'result'
])
self
.
assertEqual
(
self
.
TEST_COURSE_FLAG
.
is_enabled
(
self
.
TEST_COURSE_KEY
),
data
[
'result'
])
# result is cached, so override check should happen once
WaffleFlagCourseOverrideModel
.
override_value
.
assert_called_once_with
(
self
.
NAMESPACED_FLAG_NAME
,
self
.
TEST_COURSE_KEY
)
openedx/core/djangoapps/waffle_utils/tests/test_models.py
0 → 100644
View file @
eaab2cf4
"""
Tests for waffle utils models.
"""
from
ddt
import
data
,
ddt
,
unpack
from
django.test
import
TestCase
from
opaque_keys.edx.keys
import
CourseKey
from
request_cache.middleware
import
RequestCache
from
..models
import
WaffleFlagCourseOverrideModel
@ddt
class
WaffleFlagCourseOverrideTests
(
TestCase
):
"""
Tests for the waffle flag course override model.
"""
WAFFLE_TEST_NAME
=
"waffle_test_course_override"
TEST_COURSE_KEY
=
CourseKey
.
from_string
(
"edX/DemoX/Demo_Course"
)
OVERRIDE_CHOICES
=
WaffleFlagCourseOverrideModel
.
ALL_CHOICES
# Data format: ( is_enabled, override_choice, expected_result )
@data
((
True
,
OVERRIDE_CHOICES
.
on
,
OVERRIDE_CHOICES
.
on
),
(
True
,
OVERRIDE_CHOICES
.
off
,
OVERRIDE_CHOICES
.
off
),
(
False
,
OVERRIDE_CHOICES
.
on
,
OVERRIDE_CHOICES
.
unset
))
@unpack
def
test_setting_override
(
self
,
is_enabled
,
override_choice
,
expected_result
):
RequestCache
.
clear_request_cache
()
self
.
set_waffle_course_override
(
override_choice
,
is_enabled
)
override_value
=
WaffleFlagCourseOverrideModel
.
override_value
(
self
.
WAFFLE_TEST_NAME
,
self
.
TEST_COURSE_KEY
)
self
.
assertEqual
(
override_value
,
expected_result
)
def
test_setting_override_multiple_times
(
self
):
RequestCache
.
clear_request_cache
()
self
.
set_waffle_course_override
(
self
.
OVERRIDE_CHOICES
.
on
)
self
.
set_waffle_course_override
(
self
.
OVERRIDE_CHOICES
.
off
)
override_value
=
WaffleFlagCourseOverrideModel
.
override_value
(
self
.
WAFFLE_TEST_NAME
,
self
.
TEST_COURSE_KEY
)
self
.
assertEqual
(
override_value
,
self
.
OVERRIDE_CHOICES
.
off
)
def
set_waffle_course_override
(
self
,
override_choice
,
is_enabled
=
True
):
WaffleFlagCourseOverrideModel
.
objects
.
create
(
waffle_flag
=
self
.
WAFFLE_TEST_NAME
,
override_choice
=
override_choice
,
enabled
=
is_enabled
,
course_id
=
self
.
TEST_COURSE_KEY
)
openedx/core/djangoapps/waffle_utils/tests/test_testutils.py
0 → 100644
View file @
eaab2cf4
"""
Tests for waffle utils test utilities.
"""
from
django.test
import
TestCase
from
opaque_keys.edx.keys
import
CourseKey
from
request_cache.middleware
import
RequestCache
from
..
import
CourseWaffleFlag
,
WaffleFlagNamespace
from
..testutils
import
override_waffle_flag
class
OverrideWaffleFlagTests
(
TestCase
):
"""
Tests for the override_waffle_flag decorator.
"""
NAMESPACE_NAME
=
"test_namespace"
FLAG_NAME
=
"test_flag"
NAMESPACED_FLAG_NAME
=
NAMESPACE_NAME
+
"."
+
FLAG_NAME
TEST_COURSE_KEY
=
CourseKey
.
from_string
(
"edX/DemoX/Demo_Course"
)
TEST_NAMESPACE
=
WaffleFlagNamespace
(
NAMESPACE_NAME
)
TEST_COURSE_FLAG
=
CourseWaffleFlag
(
TEST_NAMESPACE
,
FLAG_NAME
)
def
setUp
(
self
):
super
(
OverrideWaffleFlagTests
,
self
)
.
setUp
()
RequestCache
.
clear_request_cache
()
@override_waffle_flag
(
TEST_COURSE_FLAG
,
True
)
def
check_is_enabled_with_decorator
(
self
):
# test flag while overridden with decorator
self
.
assertTrue
(
self
.
TEST_COURSE_FLAG
.
is_enabled
(
self
.
TEST_COURSE_KEY
))
def
test_override_waffle_flag_pre_cached
(
self
):
# checks and caches the is_enabled value
self
.
assertFalse
(
self
.
TEST_COURSE_FLAG
.
is_enabled
(
self
.
TEST_COURSE_KEY
))
flag_cache
=
self
.
TEST_COURSE_FLAG
.
waffle_namespace
.
_cached_flags
self
.
assertIn
(
self
.
NAMESPACED_FLAG_NAME
,
flag_cache
)
# test flag while overridden with decorator
self
.
check_is_enabled_with_decorator
()
# test cached flag is restored
self
.
assertIn
(
self
.
NAMESPACED_FLAG_NAME
,
flag_cache
)
self
.
assertEquals
(
self
.
TEST_COURSE_FLAG
.
is_enabled
(
self
.
TEST_COURSE_KEY
),
False
)
def
test_override_waffle_flag_not_pre_cached
(
self
):
# check that the flag is not yet cached
flag_cache
=
self
.
TEST_COURSE_FLAG
.
waffle_namespace
.
_cached_flags
self
.
assertNotIn
(
self
.
NAMESPACED_FLAG_NAME
,
flag_cache
)
# test flag while overridden with decorator
self
.
check_is_enabled_with_decorator
()
# test cache is removed when no longer using decorator/context manager
self
.
assertNotIn
(
self
.
NAMESPACED_FLAG_NAME
,
flag_cache
)
openedx/core/djangoapps/waffle_utils/testutils.py
0 → 100644
View file @
eaab2cf4
"""
Test utilities for waffle utilities.
"""
from
functools
import
wraps
from
waffle.testutils
import
override_flag
def
override_waffle_flag
(
flag
,
active
):
"""
To be used as a decorator for a test function to override a namespaced
waffle flag.
flag (WaffleFlag): The namespaced cached waffle flag.
active (Boolean): The value to which the flag will be set.
Example usage:
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
"""
def
real_decorator
(
function
):
"""
Actual decorator function.
"""
@wraps
(
function
)
def
wrapper
(
*
args
,
**
kwargs
):
"""
Provides the actual override functionality of the decorator.
Saves the previous cached value of the flag and restores it (if it
was set), after overriding it.
"""
waffle_namespace
=
flag
.
waffle_namespace
namespaced_flag_name
=
waffle_namespace
.
_namespaced_name
(
flag
.
flag_name
)
# save previous value and whether it existed in the cache
cached_value_existed
=
namespaced_flag_name
in
waffle_namespace
.
_cached_flags
if
cached_value_existed
:
previous_value
=
waffle_namespace
.
_cached_flags
[
namespaced_flag_name
]
# set new value
waffle_namespace
.
_cached_flags
[
namespaced_flag_name
]
=
active
with
override_flag
(
namespaced_flag_name
,
active
):
# call wrapped function
function
(
*
args
,
**
kwargs
)
# restore value
if
cached_value_existed
:
waffle_namespace
.
_cached_flags
[
namespaced_flag_name
]
=
previous_value
elif
namespaced_flag_name
in
waffle_namespace
.
_cached_flags
:
del
waffle_namespace
.
_cached_flags
[
namespaced_flag_name
]
return
wrapper
return
real_decorator
openedx/core/lib/courses.py
View file @
eaab2cf4
"""
"""
Common utility functions related to courses.
Common utility functions related to courses.
"""
"""
from
django
import
forms
from
django.conf
import
settings
from
django.conf
import
settings
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.locator
import
CourseKey
from
xmodule.assetstore.assetmgr
import
AssetManager
from
xmodule.assetstore.assetmgr
import
AssetManager
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.contentstore.django
import
contentstore
from
xmodule.contentstore.django
import
contentstore
from
xmodule.modulestore.django
import
modulestore
def
course_image_url
(
course
,
image_key
=
'course_image'
):
def
course_image_url
(
course
,
image_key
=
'course_image'
):
...
@@ -43,3 +47,37 @@ def create_course_image_thumbnail(course, dimensions):
...
@@ -43,3 +47,37 @@ def create_course_image_thumbnail(course, dimensions):
_content
,
thumb_loc
=
contentstore
()
.
generate_thumbnail
(
course_image
,
dimensions
=
dimensions
)
_content
,
thumb_loc
=
contentstore
()
.
generate_thumbnail
(
course_image
,
dimensions
=
dimensions
)
return
StaticContent
.
serialize_asset_key_with_slash
(
thumb_loc
)
return
StaticContent
.
serialize_asset_key_with_slash
(
thumb_loc
)
def
clean_course_id
(
model_form
,
is_required
=
True
):
"""
Cleans and validates a course_id for use with a Django ModelForm.
Arguments:
model_form (form.ModelForm): The form that has a course_id.
is_required (Boolean): Default True. When True, validates that the
course_id is not empty. In all cases, when course_id is supplied,
validates that it is a valid course.
Returns:
(CourseKey) The cleaned and validated course_id as a CourseKey.
NOTE: This should ultimately replace all copies of "def clean_course_id".
"""
cleaned_id
=
model_form
.
cleaned_data
[
"course_id"
]
if
not
cleaned_id
and
not
is_required
:
return
None
try
:
course_key
=
CourseKey
.
from_string
(
cleaned_id
)
except
InvalidKeyError
:
msg
=
u'Course id invalid. Entered course id was: "{0}."'
.
format
(
cleaned_id
)
raise
forms
.
ValidationError
(
msg
)
if
not
modulestore
()
.
has_course
(
course_key
):
msg
=
u'Course not found. Entered course id was: "{0}". '
.
format
(
course_key
.
to_deprecated_string
())
raise
forms
.
ValidationError
(
msg
)
return
course_key
openedx/features/course_experience/__init__.py
View file @
eaab2cf4
"""
"""
Unified course experience settings and helper methods.
Unified course experience settings and helper methods.
"""
"""
import
waffle
import
waffle
from
openedx.core.djangoapps.waffle_utils
import
CourseWaffleFlag
,
WaffleFlagNamespace
from
request_cache.middleware
import
RequestCache
from
request_cache.middleware
import
RequestCache
# Waffle flag to enable a single unified "Course" tab.
# Waffle flag to enable the full screen course content view along with a unified
UNIFIED_COURSE_EXPERIENCE_FLAG
=
'unified_course_experience'
# course home page.
# NOTE: This is the only legacy flag that does not use the namespace.
# Waffle flag to enable the full screen course content view
# along with a unified course home page.
UNIFIED_COURSE_VIEW_FLAG
=
'unified_course_view'
UNIFIED_COURSE_VIEW_FLAG
=
'unified_course_view'
# Namespace for course experience waffle flags.
WAFFLE_FLAG_NAMESPACE
=
WaffleFlagNamespace
(
name
=
'course_experience'
)
# Waffle flag to enable a single unified "Course" tab.
UNIFIED_COURSE_TAB_FLAG
=
CourseWaffleFlag
(
WAFFLE_FLAG_NAMESPACE
,
'unified_course_tab'
)
def
default_course_url_name
(
request
=
None
):
def
default_course_url_name
(
request
=
None
):
"""
"""
...
@@ -24,11 +28,16 @@ def default_course_url_name(request=None):
...
@@ -24,11 +28,16 @@ def default_course_url_name(request=None):
return
'courseware'
return
'courseware'
def
course_home_url_name
(
request
=
None
):
def
course_home_url_name
(
course_key
):
"""
"""
Returns the course home page's URL name for the current user.
Returns the course home page's URL name for the current user.
Arguments:
course_key (CourseKey): The course key for which the home url is being
requested.
"""
"""
if
waffle
.
flag_is_active
(
request
or
RequestCache
.
get_current_request
(),
UNIFIED_COURSE_EXPERIENCE_FLAG
):
if
UNIFIED_COURSE_TAB_FLAG
.
is_enabled
(
course_key
):
return
'openedx.course_experience.course_home'
return
'openedx.course_experience.course_home'
else
:
else
:
return
'info'
return
'info'
openedx/features/course_experience/templates/course_experience/course-home-fragment.html
View file @
eaab2cf4
...
@@ -5,7 +5,6 @@
...
@@ -5,7 +5,6 @@
<
%!
<
%!
import
json
import
json
import
waffle
from
django
.
conf
import
settings
from
django
.
conf
import
settings
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
utils
.
translation
import
ugettext
as
_
...
@@ -15,7 +14,7 @@ from django.core.urlresolvers import reverse
...
@@ -15,7 +14,7 @@ from django.core.urlresolvers import reverse
from
django_comment_client
.
permissions
import
has_permission
from
django_comment_client
.
permissions
import
has_permission
from
openedx
.
core
.
djangolib
.
js_utils
import
dump_js_escaped_json
,
js_escaped_string
from
openedx
.
core
.
djangolib
.
js_utils
import
dump_js_escaped_json
,
js_escaped_string
from
openedx
.
core
.
djangolib
.
markup
import
HTML
from
openedx
.
core
.
djangolib
.
markup
import
HTML
from
openedx
.
features
.
course_experience
import
UNIFIED_COURSE_
EXPERIENCE
_FLAG
from
openedx
.
features
.
course_experience
import
UNIFIED_COURSE_
TAB
_FLAG
%
>
%
>
<
%
block
name=
"content"
>
<
%
block
name=
"content"
>
...
@@ -58,7 +57,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
...
@@ -58,7 +57,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
<div
class=
"page-content"
>
<div
class=
"page-content"
>
<div
class=
"layout layout-1q3q"
>
<div
class=
"layout layout-1q3q"
>
<main
class=
"layout-col layout-col-b"
>
<main
class=
"layout-col layout-col-b"
>
% if welcome_message_fragment and
waffle.flag_is_active(request, UNIFIED_COURSE_EXPERIENCE_FLAG
):
% if welcome_message_fragment and
UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id
):
<div
class=
"section section-dates"
>
<div
class=
"section section-dates"
>
${HTML(welcome_message_fragment.body_html())}
${HTML(welcome_message_fragment.body_html())}
</div>
</div>
...
@@ -76,7 +75,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
...
@@ -76,7 +75,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
${_("Bookmarks")}
${_("Bookmarks")}
</a>
</a>
</li>
</li>
% if
waffle.flag_is_active(request, UNIFIED_COURSE_EXPERIENCE_FLAG
):
% if
UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id
):
<li>
<li>
<a
href=
"${reverse('openedx.course_experience.course_updates', args=[course.id])}"
>
<a
href=
"${reverse('openedx.course_experience.course_updates', args=[course.id])}"
>
<span
class=
"icon fa fa-newspaper-o"
aria-hidden=
"true"
></span>
<span
class=
"icon fa fa-newspaper-o"
aria-hidden=
"true"
></span>
...
...
openedx/features/course_experience/templates/course_experience/course-updates-fragment.html
View file @
eaab2cf4
...
@@ -4,17 +4,9 @@
...
@@ -4,17 +4,9 @@
<
%
namespace
name=
'static'
file=
'../static_content.html'
/>
<
%
namespace
name=
'static'
file=
'../static_content.html'
/>
<
%!
<
%!
import
json
from
django
.
conf
import
settings
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
template
.
defaultfilters
import
escapejs
from
django
.
core
.
urlresolvers
import
reverse
from
django_comment_client
.
permissions
import
has_permission
from
openedx
.
core
.
djangolib
.
js_utils
import
dump_js_escaped_json
,
js_escaped_string
from
openedx
.
core
.
djangolib
.
markup
import
HTML
from
openedx
.
core
.
djangolib
.
markup
import
HTML
from
openedx
.
features
.
course_experience
import
UNIFIED_COURSE_EXPERIENCE_FLAG
%
>
%
>
<
%
block
name=
"content"
>
<
%
block
name=
"content"
>
...
...
openedx/features/course_experience/tests/views/test_course_home.py
View file @
eaab2cf4
...
@@ -2,18 +2,16 @@
...
@@ -2,18 +2,16 @@
Tests for the course home page.
Tests for the course home page.
"""
"""
from
waffle.testutils
import
override_flag
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
openedx.core.djangoapps.waffle_utils.testutils
import
override_waffle_flag
from
openedx.features.course_experience
import
UNIFIED_COURSE_TAB_FLAG
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
,
check_mongo_calls
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
,
check_mongo_calls
from
openedx.features.course_experience
import
UNIFIED_COURSE_EXPERIENCE_FLAG
from
.test_course_updates
import
create_course_update
,
remove_course_updates
from
.test_course_updates
import
create_course_update
,
remove_course_updates
TEST_PASSWORD
=
'test'
TEST_PASSWORD
=
'test'
...
@@ -71,22 +69,13 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
...
@@ -71,22 +69,13 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
remove_course_updates
(
self
.
course
)
remove_course_updates
(
self
.
course
)
super
(
TestCourseHomePage
,
self
)
.
tearDown
()
super
(
TestCourseHomePage
,
self
)
.
tearDown
()
@override_flag
(
UNIFIED_COURSE_EXPERIENCE_FLAG
,
active
=
True
)
@override_waffle_flag
(
UNIFIED_COURSE_TAB_FLAG
,
active
=
True
)
def
test_unified_page
(
self
):
"""
Verify the rendering of the unified page.
"""
url
=
course_home_url
(
self
.
course
)
response
=
self
.
client
.
get
(
url
)
self
.
assertContains
(
response
,
'<h2 class="hd hd-3 page-title">Test Course</h2>'
)
@override_flag
(
UNIFIED_COURSE_EXPERIENCE_FLAG
,
active
=
True
)
def
test_welcome_message_when_unified
(
self
):
def
test_welcome_message_when_unified
(
self
):
url
=
course_home_url
(
self
.
course
)
url
=
course_home_url
(
self
.
course
)
response
=
self
.
client
.
get
(
url
)
response
=
self
.
client
.
get
(
url
)
self
.
assertContains
(
response
,
TEST_WELCOME_MESSAGE
,
status_code
=
200
)
self
.
assertContains
(
response
,
TEST_WELCOME_MESSAGE
,
status_code
=
200
)
@override_
flag
(
UNIFIED_COURSE_EXPERIENCE
_FLAG
,
active
=
False
)
@override_
waffle_flag
(
UNIFIED_COURSE_TAB
_FLAG
,
active
=
False
)
def
test_welcome_message_when_not_unified
(
self
):
def
test_welcome_message_when_not_unified
(
self
):
url
=
course_home_url
(
self
.
course
)
url
=
course_home_url
(
self
.
course
)
response
=
self
.
client
.
get
(
url
)
response
=
self
.
client
.
get
(
url
)
...
@@ -100,7 +89,7 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
...
@@ -100,7 +89,7 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
course_home_url
(
self
.
course
)
course_home_url
(
self
.
course
)
# Fetch the view and verify the query counts
# Fetch the view and verify the query counts
with
self
.
assertNumQueries
(
4
3
):
with
self
.
assertNumQueries
(
4
2
):
with
check_mongo_calls
(
5
):
with
check_mongo_calls
(
5
):
url
=
course_home_url
(
self
.
course
)
url
=
course_home_url
(
self
.
course
)
self
.
client
.
get
(
url
)
self
.
client
.
get
(
url
)
openedx/features/course_experience/tests/views/test_course_updates.py
View file @
eaab2cf4
...
@@ -124,7 +124,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
...
@@ -124,7 +124,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
course_updates_url
(
self
.
course
)
course_updates_url
(
self
.
course
)
# Fetch the view and verify that the query counts haven't changed
# Fetch the view and verify that the query counts haven't changed
with
self
.
assertNumQueries
(
3
2
):
with
self
.
assertNumQueries
(
3
3
):
with
check_mongo_calls
(
4
):
with
check_mongo_calls
(
4
):
url
=
course_updates_url
(
self
.
course
)
url
=
course_updates_url
(
self
.
course
)
self
.
client
.
get
(
url
)
self
.
client
.
get
(
url
)
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