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
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
513 additions
and
102 deletions
+513
-102
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
+0
-0
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 = (
# Customized celery tasks, including persisting failed tasks so they can
# be retried
'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):
elif
course_id
:
# Redirect to the course homepage (in LMS) or outline page (in Studio)
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
:
redirect_url
=
reverse
(
'course_handler'
,
kwargs
=
{
'course_key_string'
:
course_id
})
else
:
...
...
lms/djangoapps/ccx/tests/test_field_override_performance.py
View file @
eaab2cf4
...
...
@@ -231,18 +231,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
# # of sql queries to default,
# # of mongo queries,
# )
(
'no_overrides'
,
1
,
True
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
4
,
1
),
(
'ccx'
,
1
,
True
,
False
):
(
2
4
,
1
),
(
'ccx'
,
2
,
True
,
False
):
(
2
4
,
1
),
(
'ccx'
,
3
,
True
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
4
,
1
),
(
'ccx'
,
1
,
False
,
False
):
(
2
4
,
1
),
(
'ccx'
,
2
,
False
,
False
):
(
2
4
,
1
),
(
'ccx'
,
3
,
False
,
False
):
(
2
4
,
1
),
(
'no_overrides'
,
1
,
True
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
5
,
1
),
(
'ccx'
,
1
,
True
,
False
):
(
2
5
,
1
),
(
'ccx'
,
2
,
True
,
False
):
(
2
5
,
1
),
(
'ccx'
,
3
,
True
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
5
,
1
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
5
,
1
),
(
'ccx'
,
1
,
False
,
False
):
(
2
5
,
1
),
(
'ccx'
,
2
,
False
,
False
):
(
2
5
,
1
),
(
'ccx'
,
3
,
False
,
False
):
(
2
5
,
1
),
}
...
...
@@ -254,19 +254,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__
=
True
TEST_DATA
=
{
(
'no_overrides'
,
1
,
True
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
1
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
2
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
3
,
True
,
False
):
(
2
4
,
3
),
(
'ccx'
,
1
,
True
,
True
):
(
2
5
,
3
),
(
'ccx'
,
2
,
True
,
True
):
(
2
5
,
3
),
(
'ccx'
,
3
,
True
,
True
):
(
2
5
,
3
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
1
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
2
,
False
,
False
):
(
2
4
,
3
),
(
'ccx'
,
3
,
False
,
False
):
(
2
4
,
3
),
(
'no_overrides'
,
1
,
True
,
False
):
(
2
5
,
3
),
(
'no_overrides'
,
2
,
True
,
False
):
(
2
5
,
3
),
(
'no_overrides'
,
3
,
True
,
False
):
(
2
5
,
3
),
(
'ccx'
,
1
,
True
,
False
):
(
2
5
,
3
),
(
'ccx'
,
2
,
True
,
False
):
(
2
5
,
3
),
(
'ccx'
,
3
,
True
,
False
):
(
2
5
,
3
),
(
'ccx'
,
1
,
True
,
True
):
(
2
6
,
3
),
(
'ccx'
,
2
,
True
,
True
):
(
2
6
,
3
),
(
'ccx'
,
3
,
True
,
True
):
(
2
6
,
3
),
(
'no_overrides'
,
1
,
False
,
False
):
(
2
5
,
3
),
(
'no_overrides'
,
2
,
False
,
False
):
(
2
5
,
3
),
(
'no_overrides'
,
3
,
False
,
False
):
(
2
5
,
3
),
(
'ccx'
,
1
,
False
,
False
):
(
2
5
,
3
),
(
'ccx'
,
2
,
False
,
False
):
(
2
5
,
3
),
(
'ccx'
,
3
,
False
,
False
):
(
2
5
,
3
),
}
lms/djangoapps/courseware/tabs.py
View file @
eaab2cf4
...
...
@@ -2,15 +2,14 @@
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
"""
import
waffle
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
,
ugettext_noop
from
courseware.access
import
has_access
from
courseware.entrance_exams
import
user_can_skip_entrance_exam
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
student.models
import
CourseEnrollment
from
xmodule.tabs
import
CourseTab
,
CourseTabList
,
key_checker
,
link_reverse_func
...
...
@@ -66,8 +65,7 @@ class CourseInfoTab(CourseTab):
"""
The "Home" tab is not shown for the new unified course experience.
"""
request
=
RequestCache
.
get_current_request
()
return
not
waffle
.
flag_is_active
(
request
,
UNIFIED_COURSE_EXPERIENCE_FLAG
)
return
not
UNIFIED_COURSE_TAB_FLAG
.
is_enabled
(
course
.
id
)
class
SyllabusTab
(
EnrolledTab
):
...
...
lms/djangoapps/courseware/tests/test_date_summary.py
View file @
eaab2cf4
...
...
@@ -7,8 +7,6 @@ from django.core.urlresolvers import reverse
from
freezegun
import
freeze_time
from
nose.plugins.attrib
import
attr
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
course_modes.tests.factories
import
CourseModeFactory
...
...
@@ -24,6 +22,8 @@ from courseware.date_summary import (
)
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.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
lms.djangoapps.verify_student.models
import
VerificationDeadline
from
lms.djangoapps.verify_student.tests.factories
import
SoftwareSecurePhotoVerificationFactory
...
...
@@ -194,7 +194,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'info'
,
'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
):
with
freeze_time
(
'2015-01-02'
):
self
.
setup_course_and_user
()
...
...
@@ -218,7 +218,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'info'
,
'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
):
with
freeze_time
(
'2015-01-02'
):
self
.
setup_course_and_user
()
...
...
@@ -249,7 +249,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'info'
,
'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
):
with
freeze_time
(
'2015-01-02'
):
self
.
setup_course_and_user
()
...
...
@@ -267,7 +267,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
'info'
,
'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
):
with
freeze_time
(
'2015-01-02'
):
self
.
setup_course_and_user
()
...
...
lms/djangoapps/courseware/tests/test_tabs.py
View file @
eaab2cf4
...
...
@@ -2,8 +2,6 @@
Test cases for tabs.
"""
from
waffle.testutils
import
override_flag
from
django.core.urlresolvers
import
reverse
from
django.http
import
Http404
from
mock
import
MagicMock
,
Mock
,
patch
...
...
@@ -18,7 +16,8 @@ from courseware.tests.helpers import LoginEnrollmentTestCase
from
courseware.tests.factories
import
InstructorFactory
,
StaffFactory
from
courseware.views.views
import
get_static_tab_fragment
,
StaticCourseTabView
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.tests.factories
import
UserFactory
from
util.milestones_helpers
import
(
...
...
@@ -776,12 +775,13 @@ class CourseInfoTabTestCase(TabTestCase):
self
.
user
=
self
.
create_mock_user
()
self
.
request
=
get_mock_request
(
self
.
user
)
@override_waffle_flag
(
UNIFIED_COURSE_TAB_FLAG
,
active
=
False
)
def
test_default_tab
(
self
):
# Verify that the course info tab is the first tab
tabs
=
get_course_tab_list
(
self
.
request
,
self
.
course
)
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
):
# Verify that the unified course experience hides the course info tab
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):
NUM_PROBLEMS
=
20
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
10
,
14
2
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
14
2
),
(
ModuleStoreEnum
.
Type
.
mongo
,
10
,
14
3
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
14
3
),
)
@ddt.unpack
def
test_index_query_counts
(
self
,
store_type
,
expected_mongo_query_count
,
expected_mysql_query_count
):
...
...
@@ -1420,12 +1420,12 @@ class ProgressPageTests(ProgressPageBaseTests):
"""Test that query counts remain the same for self-paced and instructor-paced courses."""
SelfPacedConfiguration
(
enabled
=
self_paced_enabled
)
.
save
()
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
()
@ddt.data
(
(
False
,
4
0
,
26
),
(
True
,
3
3
,
22
)
(
False
,
4
1
,
27
),
(
True
,
3
4
,
23
)
)
@ddt.unpack
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
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.features.course_experience
import
(
UNIFIED_COURSE_
EXPERIENCE
_FLAG
,
UNIFIED_COURSE_
TAB
_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.enterprise_support.api
import
data_sharing_consent_required
...
...
@@ -263,11 +263,12 @@ def course_info(request, course_id):
return
url
return
None
course_key
=
CourseKey
.
from_string
(
course_id
)
# If the unified course experience is enabled, redirect to the "Course" tab
if
waffle
.
flag_is_active
(
request
,
UNIFIED_COURSE_EXPERIENCE_FLAG
):
return
redirect
(
reverse
(
course_home_url_name
(
request
),
args
=
[
course_id
]))
if
UNIFIED_COURSE_TAB_FLAG
.
is_enabled
(
course_key
):
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
):
course
=
get_course_by_id
(
course_key
,
depth
=
2
)
access_response
=
has_access
(
request
.
user
,
'load'
,
course
,
course_key
)
...
...
@@ -669,7 +670,7 @@ def course_about(request, course_id):
modes
=
CourseMode
.
modes_for_course_dict
(
course_key
)
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
)
...
...
@@ -677,7 +678,7 @@ def course_about(request, course_id):
studio_url
=
get_studio_url
(
course
,
'settings/details'
)
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
:
course_target
=
reverse
(
'about_course'
,
args
=
[
course
.
id
.
to_deprecated_string
()])
...
...
@@ -1241,7 +1242,7 @@ def course_survey(request, course_id):
course_key
=
CourseKey
.
from_string
(
course_id
)
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,
# then redirect to the course instead
...
...
lms/djangoapps/grades/config/waffle.py
View file @
eaab2cf4
...
...
@@ -2,7 +2,7 @@
This module contains various configuration settings via
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
...
...
@@ -18,4 +18,4 @@ def waffle():
"""
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 = (
# Unusual migrations
'database_fixups'
,
# Waffle related utilities
'openedx.core.djangoapps.waffle_utils'
,
# Features
'openedx.features.course_bookmarks'
,
'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_
% endif
<div
class=
"course-container"
>
<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}"
>
<h2
class=
"hd hd-2 sr"
id=
"details-heading-${course_overview.number}"
>
${_('Course details')}
</h2>
<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
</div>
% if not reg_code_already_redeemed:
%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>
%elif not registered_for_course:
<form
method=
"post"
>
...
...
lms/tests.py
View file @
eaab2cf4
...
...
@@ -55,6 +55,6 @@ class HelpModalTests(ModuleStoreTestCase):
Simple test to make sure that you don't get a 500 error when the modal
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
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
openedx/core/djangoapps/content/block_structure/config/__init__.py
View file @
eaab2cf4
...
...
@@ -2,7 +2,7 @@
This module contains various configuration settings via
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
.models
import
BlockStructureConfiguration
...
...
@@ -22,7 +22,7 @@ def waffle():
"""
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
...
...
openedx/core/djangoapps/monitoring_utils/middleware.py
View file @
eaab2cf4
...
...
@@ -19,7 +19,7 @@ except ImportError:
import
psutil
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'
...
...
@@ -163,4 +163,4 @@ class MonitoringMemoryMiddleware(object):
"""
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
This diff is collapsed.
Click to expand it.
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.
"""
from
django
import
forms
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.contentstore.content
import
StaticContent
from
xmodule.contentstore.django
import
contentstore
from
xmodule.modulestore.django
import
modulestore
def
course_image_url
(
course
,
image_key
=
'course_image'
):
...
...
@@ -43,3 +47,37 @@ def create_course_image_thumbnail(course, dimensions):
_content
,
thumb_loc
=
contentstore
()
.
generate_thumbnail
(
course_image
,
dimensions
=
dimensions
)
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.
"""
import
waffle
from
openedx.core.djangoapps.waffle_utils
import
CourseWaffleFlag
,
WaffleFlagNamespace
from
request_cache.middleware
import
RequestCache
# Waffle flag to enable a single unified "Course" tab.
UNIFIED_COURSE_EXPERIENCE_FLAG
=
'unified_course_experience'
# Waffle flag to enable the full screen course content view
# along with a unified course home page.
# Waffle flag to enable the full screen course content view along with a unified
# course home page.
# NOTE: This is the only legacy flag that does not use the namespace.
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
):
"""
...
...
@@ -24,11 +28,16 @@ def default_course_url_name(request=None):
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.
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'
else
:
return
'info'
openedx/features/course_experience/templates/course_experience/course-home-fragment.html
View file @
eaab2cf4
...
...
@@ -5,7 +5,6 @@
<
%!
import
json
import
waffle
from
django
.
conf
import
settings
from
django
.
utils
.
translation
import
ugettext
as
_
...
...
@@ -15,7 +14,7 @@ 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
.
features
.
course_experience
import
UNIFIED_COURSE_
EXPERIENCE
_FLAG
from
openedx
.
features
.
course_experience
import
UNIFIED_COURSE_
TAB
_FLAG
%
>
<
%
block
name=
"content"
>
...
...
@@ -58,7 +57,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
<div
class=
"page-content"
>
<div
class=
"layout layout-1q3q"
>
<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"
>
${HTML(welcome_message_fragment.body_html())}
</div>
...
...
@@ -76,7 +75,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
${_("Bookmarks")}
</a>
</li>
% if
waffle.flag_is_active(request, UNIFIED_COURSE_EXPERIENCE_FLAG
):
% if
UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id
):
<li>
<a
href=
"${reverse('openedx.course_experience.course_updates', args=[course.id])}"
>
<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 @@
<
%
namespace
name=
'static'
file=
'../static_content.html'
/>
<
%!
import
json
from
django
.
conf
import
settings
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
.
features
.
course_experience
import
UNIFIED_COURSE_EXPERIENCE_FLAG
%
>
<
%
block
name=
"content"
>
...
...
openedx/features/course_experience/tests/views/test_course_home.py
View file @
eaab2cf4
...
...
@@ -2,18 +2,16 @@
Tests for the course home page.
"""
from
waffle.testutils
import
override_flag
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.tests.factories
import
UserFactory
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
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
TEST_PASSWORD
=
'test'
...
...
@@ -71,22 +69,13 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
remove_course_updates
(
self
.
course
)
super
(
TestCourseHomePage
,
self
)
.
tearDown
()
@override_flag
(
UNIFIED_COURSE_EXPERIENCE_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
)
@override_waffle_flag
(
UNIFIED_COURSE_TAB_FLAG
,
active
=
True
)
def
test_welcome_message_when_unified
(
self
):
url
=
course_home_url
(
self
.
course
)
response
=
self
.
client
.
get
(
url
)
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
):
url
=
course_home_url
(
self
.
course
)
response
=
self
.
client
.
get
(
url
)
...
...
@@ -100,7 +89,7 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
course_home_url
(
self
.
course
)
# Fetch the view and verify the query counts
with
self
.
assertNumQueries
(
4
3
):
with
self
.
assertNumQueries
(
4
2
):
with
check_mongo_calls
(
5
):
url
=
course_home_url
(
self
.
course
)
self
.
client
.
get
(
url
)
openedx/features/course_experience/tests/views/test_course_updates.py
View file @
eaab2cf4
...
...
@@ -124,7 +124,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
course_updates_url
(
self
.
course
)
# 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
):
url
=
course_updates_url
(
self
.
course
)
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