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
5b630a77
Commit
5b630a77
authored
Jun 19, 2015
by
Kyle McCormick
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MA-779 Make has_access work on CourseOverview objects
parent
375341b8
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
395 additions
and
105 deletions
+395
-105
common/djangoapps/util/milestones_helpers.py
+44
-27
lms/djangoapps/courseware/access.py
+141
-66
lms/djangoapps/courseware/tests/helpers.py
+49
-0
lms/djangoapps/courseware/tests/test_access.py
+93
-1
lms/djangoapps/courseware/tests/test_view_authentication.py
+4
-8
openedx/core/djangoapps/content/course_overviews/migrations/0002_add_days_early_for_beta.py
+58
-0
openedx/core/djangoapps/content/course_overviews/models.py
+6
-3
No files found.
common/djangoapps/util/milestones_helpers.py
View file @
5b630a77
...
...
@@ -9,6 +9,7 @@ from django.utils.translation import ugettext as _
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
xmodule.modulestore.django
import
modulestore
NAMESPACE_CHOICES
=
{
...
...
@@ -86,33 +87,46 @@ def set_prerequisite_courses(course_key, prerequisite_course_keys):
add_prerequisite_course
(
course_key
,
prerequisite_course_key
)
def
get_pre_requisite_courses_not_completed
(
user
,
enrolled_courses
):
def
get_pre_requisite_courses_not_completed
(
user
,
enrolled_courses
):
# pylint: disable=invalid-name
"""
It would make dict of prerequisite courses not completed by user among courses
user has enrolled in. It calls the fulfilment api of milestones app and
iterates over all fulfilment milestones not achieved to make dict of
prerequisite courses yet to be completed.
Makes a dict mapping courses to their unfulfilled milestones using the
fulfillment API of the milestones app.
Arguments:
user (User): the user for whom we are checking prerequisites.
enrolled_courses (CourseKey): a list of keys for the courses to be
checked. The given user must be enrolled in all of these courses.
Returns:
dict[CourseKey: dict[
'courses': list[dict['key': CourseKey, 'display': str]]
]]
If a course has no incomplete prerequisites, it will be excluded from the
dictionary.
"""
if
not
settings
.
FEATURES
.
get
(
'ENABLE_PREREQUISITE_COURSES'
,
False
):
return
{}
from
milestones
import
api
as
milestones_api
pre_requisite_courses
=
{}
if
settings
.
FEATURES
.
get
(
'ENABLE_PREREQUISITE_COURSES'
,
False
):
from
milestones
import
api
as
milestones_api
for
course_key
in
enrolled_courses
:
required_courses
=
[]
fulfilment_paths
=
milestones_api
.
get_course_milestones_fulfillment_paths
(
course_key
,
{
'id'
:
user
.
id
})
for
milestone_key
,
milestone_value
in
fulfilment_paths
.
items
():
# pylint: disable=unused-variable
for
key
,
value
in
milestone_value
.
items
():
if
key
==
'courses'
and
value
:
for
required_course
in
value
:
required_course_key
=
CourseKey
.
from_string
(
required_course
)
required_course_descriptor
=
modulestore
()
.
get_course
(
required_course_key
)
required_courses
.
append
({
'key'
:
required_course_key
,
'display'
:
get_course_display_name
(
required_course_descriptor
)
})
# if there are required courses add to dict
if
required_courses
:
pre_requisite_courses
[
course_key
]
=
{
'courses'
:
required_courses
}
for
course_key
in
enrolled_courses
:
required_courses
=
[]
fulfillment_paths
=
milestones_api
.
get_course_milestones_fulfillment_paths
(
course_key
,
{
'id'
:
user
.
id
})
for
__
,
milestone_value
in
fulfillment_paths
.
items
():
for
key
,
value
in
milestone_value
.
items
():
if
key
==
'courses'
and
value
:
for
required_course
in
value
:
required_course_key
=
CourseKey
.
from_string
(
required_course
)
required_course_overview
=
CourseOverview
.
get_from_id
(
required_course_key
)
required_courses
.
append
({
'key'
:
required_course_key
,
'display'
:
get_course_display_string
(
required_course_overview
)
})
# If there are required courses, add them to the result dict.
if
required_courses
:
pre_requisite_courses
[
course_key
]
=
{
'courses'
:
required_courses
}
return
pre_requisite_courses
...
...
@@ -129,15 +143,18 @@ def get_prerequisite_courses_display(course_descriptor):
required_course_descriptor
=
modulestore
()
.
get_course
(
course_key
)
prc
=
{
'key'
:
course_key
,
'display'
:
get_course_display_
name
(
required_course_descriptor
)
'display'
:
get_course_display_
string
(
required_course_descriptor
)
}
pre_requisite_courses
.
append
(
prc
)
return
pre_requisite_courses
def
get_course_display_
name
(
descriptor
):
def
get_course_display_
string
(
descriptor
):
"""
It would return display name from given course descriptor
Returns a string to display for a course or course overview.
Arguments:
descriptor (CourseDescriptor|CourseOverview): a course or course overview.
"""
return
' '
.
join
([
descriptor
.
display_org_with_default
,
...
...
lms/djangoapps/courseware/access.py
View file @
5b630a77
...
...
@@ -33,6 +33,7 @@ from xmodule.util.django import get_current_request_hostname
from
external_auth.models
import
ExternalAuthMap
from
courseware.masquerade
import
get_masquerade_role
,
is_masquerading_as_student
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
student
import
auth
from
student.models
import
CourseEnrollmentAllowed
from
student.roles
import
(
...
...
@@ -100,6 +101,9 @@ def has_access(user, action, obj, course_key=None):
if
isinstance
(
obj
,
CourseDescriptor
):
return
_has_access_course_desc
(
user
,
action
,
obj
)
if
isinstance
(
obj
,
CourseOverview
):
return
_has_access_course_overview
(
user
,
action
,
obj
)
if
isinstance
(
obj
,
ErrorDescriptor
):
return
_has_access_error_desc
(
user
,
action
,
obj
,
course_key
)
...
...
@@ -129,6 +133,87 @@ def has_access(user, action, obj, course_key=None):
# ================ Implementation helpers ================================
def
_can_access_descriptor_with_start_date
(
user
,
descriptor
,
course_key
):
# pylint: disable=invalid-name
"""
Checks if a user has access to a descriptor based on its start date.
If there is no start date specified, grant access.
Else, check if we're past the start date.
Note:
We do NOT check whether the user is staff or if the descriptor
is detached... it is assumed both of these are checked by the caller.
Arguments:
user (User): the user whose descriptor access we are checking.
descriptor (AType): the descriptor for which we are checking access.
where AType is any descriptor that has the attributes .location and
.days_early_for_beta
"""
start_dates_disabled
=
settings
.
FEATURES
[
'DISABLE_START_DATES'
]
if
start_dates_disabled
and
not
is_masquerading_as_student
(
user
,
course_key
):
return
True
else
:
now
=
datetime
.
now
(
UTC
())
effective_start
=
_adjust_start_date_for_beta_testers
(
user
,
descriptor
,
course_key
=
course_key
)
return
(
descriptor
.
start
is
None
or
now
>
effective_start
or
in_preview_mode
()
)
def
_can_view_courseware_with_prerequisites
(
user
,
course
):
# pylint: disable=invalid-name
"""
Checks if a user has access to a course based on its prerequisites.
If the user is staff or anonymous, immediately grant access.
Else, return whether or not the prerequisite courses have been passed.
Arguments:
user (User): the user whose course access we are checking.
course (AType): the course for which we are checking access.
where AType is CourseDescriptor, CourseOverview, or any other class that
represents a course and has the attributes .location and .id.
"""
return
(
not
settings
.
FEATURES
[
'ENABLE_PREREQUISITE_COURSES'
]
or
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
)
or
not
course
.
pre_requisite_courses
or
user
.
is_anonymous
()
or
not
get_pre_requisite_courses_not_completed
(
user
,
[
course
.
id
])
)
def
_can_load_course_on_mobile
(
user
,
course
):
"""
Checks if a user can view the given course on a mobile device.
This function only checks mobile-specific access restrictions. Other access
restrictions such as start date and the .visible_to_staff_only flag must
be checked by callers in *addition* to the return value of this function.
Arguments:
user (User): the user whose course access we are checking.
course (CourseDescriptor|CourseOverview): the course for which we are
checking access.
Returns:
bool: whether the course can be accessed on mobile.
"""
return
(
is_mobile_available_for_user
(
user
,
course
)
and
(
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
)
or
not
any_unfulfilled_milestones
(
course
.
id
,
user
.
id
)
)
)
def
_has_access_course_desc
(
user
,
action
,
course
):
"""
Check if user has access to a course descriptor.
...
...
@@ -154,23 +239,6 @@ def _has_access_course_desc(user, action, course):
# delegate to generic descriptor check to check start dates
return
_has_access_descriptor
(
user
,
'load'
,
course
,
course
.
id
)
def
can_load_mobile
():
"""
Can this user access this course from a mobile device?
"""
return
(
# check start date
can_load
()
and
# check mobile_available flag
is_mobile_available_for_user
(
user
,
course
)
and
(
# either is a staff user or
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
)
or
# check for unfulfilled milestones
not
any_unfulfilled_milestones
(
course
.
id
,
user
.
id
)
)
)
def
can_enroll
():
"""
First check if restriction of enrollment by login method is enabled, both
...
...
@@ -274,25 +342,11 @@ def _has_access_course_desc(user, action, course):
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
)
)
def
can_view_courseware_with_prerequisites
():
# pylint: disable=invalid-name
"""
Checks if prerequisite courses feature is enabled and course has prerequisites
and user is neither staff nor anonymous then it returns False if user has not
passed prerequisite courses otherwise return True.
"""
if
settings
.
FEATURES
[
'ENABLE_PREREQUISITE_COURSES'
]
\
and
not
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
)
\
and
course
.
pre_requisite_courses
\
and
not
user
.
is_anonymous
()
\
and
get_pre_requisite_courses_not_completed
(
user
,
[
course
.
id
]):
return
False
else
:
return
True
checkers
=
{
'load'
:
can_load
,
'view_courseware_with_prerequisites'
:
can_view_courseware_with_prerequisites
,
'load_mobile'
:
can_load_mobile
,
'view_courseware_with_prerequisites'
:
lambda
:
_can_view_courseware_with_prerequisites
(
user
,
course
),
'load_mobile'
:
lambda
:
can_load
()
and
_can_load_course_on_mobile
(
user
,
course
),
'enroll'
:
can_enroll
,
'see_exists'
:
see_exists
,
'staff'
:
lambda
:
_has_staff_access_to_descriptor
(
user
,
course
,
course
.
id
),
...
...
@@ -304,6 +358,51 @@ def _has_access_course_desc(user, action, course):
return
_dispatch
(
checkers
,
action
,
user
,
course
)
def
_can_load_course_overview
(
user
,
course_overview
):
"""
Check if a user can load a course overview.
Arguments:
user (User): the user whose course access we are checking.
course_overview (CourseOverview): a course overview.
Note:
The user doesn't have to be enrolled in the course in order to have load
load access.
"""
return
(
not
course_overview
.
visible_to_staff_only
and
_can_access_descriptor_with_start_date
(
user
,
course_overview
,
course_overview
.
id
)
)
or
_has_staff_access_to_descriptor
(
user
,
course_overview
,
course_overview
.
id
)
_COURSE_OVERVIEW_CHECKERS
=
{
'load'
:
_can_load_course_overview
,
'load_mobile'
:
lambda
user
,
course_overview
:
(
_can_load_course_overview
(
user
,
course_overview
)
and
_can_load_course_on_mobile
(
user
,
course_overview
)
),
'view_courseware_with_prerequisites'
:
_can_view_courseware_with_prerequisites
}
COURSE_OVERVIEW_SUPPORTED_ACTIONS
=
_COURSE_OVERVIEW_CHECKERS
.
keys
()
# pylint: disable=invalid-name
def
_has_access_course_overview
(
user
,
action
,
course_overview
):
"""
Check if user has access to a course overview.
Arguments:
user (User): the user whose course access we are checking.
action (str): the action the user is trying to perform.
See COURSE_OVERVIEW_SUPPORTED_ACTIONS for valid values.
course_overview (CourseOverview): overview of the course in question.
"""
if
action
in
_COURSE_OVERVIEW_CHECKERS
:
return
_COURSE_OVERVIEW_CHECKERS
[
action
](
user
,
course_overview
)
else
:
raise
ValueError
(
u"Unknown action for object type 'CourseOverview': '{}'"
.
format
(
action
))
def
_has_access_error_desc
(
user
,
action
,
descriptor
,
course_key
):
"""
Only staff should see error descriptors.
...
...
@@ -408,38 +507,14 @@ def _has_access_descriptor(user, action, descriptor, course_key=None):
students to see modules. If not, views should check the course, so we
don't have to hit the enrollments table on every module load.
"""
if
descriptor
.
visible_to_staff_only
and
not
_has_staff_access_to_descriptor
(
user
,
descriptor
,
course_key
):
return
False
# enforce group access
if
not
_has_group_access
(
descriptor
,
user
,
course_key
):
# if group_access check failed, deny access unless the requestor is staff,
# in which case immediately grant access.
return
_has_staff_access_to_descriptor
(
user
,
descriptor
,
course_key
)
# If start dates are off, can always load
if
settings
.
FEATURES
[
'DISABLE_START_DATES'
]
and
not
is_masquerading_as_student
(
user
,
course_key
):
debug
(
"Allow: DISABLE_START_DATES"
)
return
True
# Check start date
if
'detached'
not
in
descriptor
.
_class_tags
and
descriptor
.
start
is
not
None
:
now
=
datetime
.
now
(
UTC
())
effective_start
=
_adjust_start_date_for_beta_testers
(
user
,
descriptor
,
course_key
=
course_key
return
(
not
descriptor
.
visible_to_staff_only
and
_has_group_access
(
descriptor
,
user
,
course_key
)
and
(
'detached'
in
descriptor
.
_class_tags
# pylint: disable=protected-access
or
_can_access_descriptor_with_start_date
(
user
,
descriptor
,
course_key
)
)
if
in_preview_mode
()
or
now
>
effective_start
:
# after start date, everyone can see it
debug
(
"Allow: now > effective start date"
)
return
True
# otherwise, need staff access
return
_has_staff_access_to_descriptor
(
user
,
descriptor
,
course_key
)
# No start date, so can always load.
debug
(
"Allow: no start date"
)
return
True
)
or
_has_staff_access_to_descriptor
(
user
,
descriptor
,
course_key
)
checkers
=
{
'load'
:
can_load
,
...
...
@@ -700,4 +775,4 @@ def in_preview_mode():
Returns whether the user is in preview mode or not.
"""
hostname
=
get_current_request_hostname
()
return
hostname
and
settings
.
PREVIEW_DOMAIN
in
hostname
.
split
(
'.'
)
return
bool
(
hostname
and
settings
.
PREVIEW_DOMAIN
in
hostname
.
split
(
'.'
)
)
lms/djangoapps/courseware/tests/helpers.py
View file @
5b630a77
...
...
@@ -5,6 +5,8 @@ from django.core.urlresolvers import reverse
from
django.test
import
TestCase
from
django.test.client
import
RequestFactory
from
courseware.access
import
has_access
,
COURSE_OVERVIEW_SUPPORTED_ACTIONS
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
student.models
import
Registration
...
...
@@ -137,3 +139,50 @@ class LoginEnrollmentTestCase(TestCase):
'course_id'
:
course
.
id
.
to_deprecated_string
(),
}
self
.
assert_request_status_code
(
200
,
url
,
method
=
"POST"
,
data
=
request_data
)
class
CourseAccessTestMixin
(
TestCase
):
"""
Utility mixin for asserting access (or lack thereof) to courses.
If relevant, also checks access for courses' corresponding CourseOverviews.
"""
def
assertCanAccessCourse
(
self
,
user
,
action
,
course
):
"""
Assert that a user has access to the given action for a given course.
Test with both the given course and, if the action is supported, with
a CourseOverview of the given course.
Arguments:
user (User): a user.
action (str): type of access to test.
See access.py:COURSE_OVERVIEW_SUPPORTED_ACTIONS.
course (CourseDescriptor): a course.
"""
self
.
assertTrue
(
has_access
(
user
,
action
,
course
))
if
action
in
COURSE_OVERVIEW_SUPPORTED_ACTIONS
:
self
.
assertTrue
(
has_access
(
user
,
action
,
CourseOverview
.
get_from_id
(
course
.
id
)))
def
assertCannotAccessCourse
(
self
,
user
,
action
,
course
):
"""
Assert that a user lacks access to the given action the given course.
Test with both the given course and, if the action is supported, with
a CourseOverview of the given course.
Arguments:
user (User): a user.
action (str): type of access to test.
See access.py:COURSE_OVERVIEW_SUPPORTED_ACTIONS.
course (CourseDescriptor): a course.
Note:
It may seem redundant to have one method for testing access
and another method for testing lack thereof (why not just combine
them into one method with a boolean flag?), but it makes reading
stack traces of failed tests easier to understand at a glance.
"""
self
.
assertFalse
(
has_access
(
user
,
action
,
course
))
if
action
in
COURSE_OVERVIEW_SUPPORTED_ACTIONS
:
self
.
assertFalse
(
has_access
(
user
,
action
,
CourseOverview
.
get_from_id
(
course
.
id
)))
lms/djangoapps/courseware/tests/test_access.py
View file @
5b630a77
import
datetime
import
ddt
import
itertools
import
pytz
from
django.test
import
TestCase
...
...
@@ -9,8 +11,9 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
import
courseware.access
as
access
from
courseware.masquerade
import
CourseMasquerade
from
courseware.tests.factories
import
UserFactory
,
StaffFactory
,
InstructorFactory
from
courseware.tests.factories
import
UserFactory
,
StaffFactory
,
InstructorFactory
,
BetaTesterFactory
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
student.tests.factories
import
AnonymousUserFactory
,
CourseEnrollmentAllowedFactory
,
CourseEnrollmentFactory
from
xmodule.course_module
import
(
CATALOG_VISIBILITY_CATALOG_AND_ABOUT
,
CATALOG_VISIBILITY_ABOUT
,
...
...
@@ -18,6 +21,7 @@ from xmodule.course_module import (
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
util.milestones_helpers
import
fulfill_course_milestone
from
util.milestones_helpers
import
(
set_prerequisite_courses
,
...
...
@@ -424,3 +428,91 @@ class UserRoleTestCase(TestCase):
'student'
,
access
.
get_user_role
(
self
.
anonymous_user
,
self
.
course_key
)
)
@ddt.ddt
class
CourseOverviewAccessTestCase
(
ModuleStoreTestCase
):
"""
Tests confirming that has_access works equally on CourseDescriptors and
CourseOverviews.
"""
def
setUp
(
self
):
super
(
CourseOverviewAccessTestCase
,
self
)
.
setUp
()
today
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
last_week
=
today
-
datetime
.
timedelta
(
days
=
7
)
next_week
=
today
+
datetime
.
timedelta
(
days
=
7
)
self
.
course_default
=
CourseFactory
.
create
()
self
.
course_started
=
CourseFactory
.
create
(
start
=
last_week
)
self
.
course_not_started
=
CourseFactory
.
create
(
start
=
next_week
,
days_early_for_beta
=
10
)
self
.
course_staff_only
=
CourseFactory
.
create
(
visible_to_staff_only
=
True
)
self
.
course_mobile_available
=
CourseFactory
.
create
(
mobile_available
=
True
)
self
.
course_with_pre_requisite
=
CourseFactory
.
create
(
pre_requisite_courses
=
[
str
(
self
.
course_started
.
id
)]
)
self
.
course_with_pre_requisites
=
CourseFactory
.
create
(
pre_requisite_courses
=
[
str
(
self
.
course_started
.
id
),
str
(
self
.
course_not_started
.
id
)]
)
self
.
user_normal
=
UserFactory
.
create
()
self
.
user_beta_tester
=
BetaTesterFactory
.
create
(
course_key
=
self
.
course_not_started
.
id
)
self
.
user_completed_pre_requisite
=
UserFactory
.
create
()
# pylint: disable=invalid-name
fulfill_course_milestone
(
self
.
user_completed_pre_requisite
,
self
.
course_started
.
id
)
self
.
user_staff
=
UserFactory
.
create
(
is_staff
=
True
)
self
.
user_anonymous
=
AnonymousUserFactory
.
create
()
LOAD_TEST_DATA
=
list
(
itertools
.
product
(
[
'user_normal'
,
'user_beta_tester'
,
'user_staff'
],
[
'load'
],
[
'course_default'
,
'course_started'
,
'course_not_started'
,
'course_staff_only'
],
))
LOAD_MOBILE_TEST_DATA
=
list
(
itertools
.
product
(
[
'user_normal'
,
'user_staff'
],
[
'load_mobile'
],
[
'course_default'
,
'course_mobile_available'
],
))
PREREQUISITES_TEST_DATA
=
list
(
itertools
.
product
(
[
'user_normal'
,
'user_completed_pre_requisite'
,
'user_staff'
,
'user_anonymous'
],
[
'view_courseware_with_prerequisites'
],
[
'course_default'
,
'course_with_pre_requisite'
,
'course_with_pre_requisites'
],
))
@ddt.data
(
*
(
LOAD_TEST_DATA
+
LOAD_MOBILE_TEST_DATA
+
PREREQUISITES_TEST_DATA
))
@ddt.unpack
def
test_course_overview_access
(
self
,
user_attr_name
,
action
,
course_attr_name
):
"""
Check that a user's access to a course is equal to the user's access to
the corresponding course overview.
Instead of taking a user and course directly as arguments, we have to
take their attribute names, as ddt doesn't allow us to reference self.
Arguments:
user_attr_name (str): the name of the attribute on self that is the
User to test with.
action (str): action to test with.
See COURSE_OVERVIEW_SUPPORTED_ACTIONS for valid values.
course_attr_name (str): the name of the attribute on self that is
the CourseDescriptor to test with.
"""
user
=
getattr
(
self
,
user_attr_name
)
course
=
getattr
(
self
,
course_attr_name
)
course_overview
=
CourseOverview
.
get_from_id
(
course
.
id
)
self
.
assertEqual
(
access
.
has_access
(
user
,
action
,
course
,
course_key
=
course
.
id
),
access
.
has_access
(
user
,
action
,
course_overview
,
course_key
=
course
.
id
)
)
def
test_course_overivew_unsupported_action
(
self
):
"""
Check that calling has_access with an unsupported action raises a
ValueError.
"""
overview
=
CourseOverview
.
get_from_id
(
self
.
course_default
.
id
)
with
self
.
assertRaises
(
ValueError
):
access
.
has_access
(
self
.
user
,
'_non_existent_action'
,
overview
)
lms/djangoapps/courseware/tests/test_view_authentication.py
View file @
5b630a77
...
...
@@ -6,7 +6,7 @@ from mock import patch
from
nose.plugins.attrib
import
attr
from
courseware.access
import
has_access
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
from
courseware.tests.helpers
import
CourseAccessTestMixin
,
LoginEnrollmentTestCase
from
courseware.tests.factories
import
(
BetaTesterFactory
,
StaffFactory
,
...
...
@@ -389,7 +389,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
@attr
(
'shard_1'
)
class
TestBetatesterAccess
(
ModuleStoreTestCase
):
class
TestBetatesterAccess
(
ModuleStoreTestCase
,
CourseAccessTestMixin
):
"""
Tests for the beta tester feature
"""
...
...
@@ -411,12 +411,8 @@ class TestBetatesterAccess(ModuleStoreTestCase):
Check that beta-test access works for courses.
"""
self
.
assertFalse
(
self
.
course
.
has_started
())
# student user shouldn't see it
self
.
assertFalse
(
has_access
(
self
.
normal_student
,
'load'
,
self
.
course
))
# now the student should see it
self
.
assertTrue
(
has_access
(
self
.
beta_tester
,
'load'
,
self
.
course
))
self
.
assertCannotAccessCourse
(
self
.
normal_student
,
'load'
,
self
.
course
)
self
.
assertCanAccessCourse
(
self
.
beta_tester
,
'load'
,
self
.
course
)
@patch.dict
(
'courseware.access.settings.FEATURES'
,
{
'DISABLE_START_DATES'
:
False
})
def
test_content_beta_period
(
self
):
...
...
openedx/core/djangoapps/content/course_overviews/migrations/0002_add_days_early_for_beta.py
0 → 100644
View file @
5b630a77
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding field 'CourseOverview.days_early_for_beta'
# The default value for the days_early_for_beta column is null. However,
# for courses already in the table that have a non-null value for
# days_early_for_beta, this would be invalid. So, we must clear the
# table before adding the new column.
db
.
clear_table
(
'course_overviews_courseoverview'
)
db
.
add_column
(
'course_overviews_courseoverview'
,
'days_early_for_beta'
,
self
.
gf
(
'django.db.models.fields.FloatField'
)(
null
=
True
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting field 'CourseOverview.days_early_for_beta'
db
.
delete_column
(
'course_overviews_courseoverview'
,
'days_early_for_beta'
)
models
=
{
'course_overviews.courseoverview'
:
{
'Meta'
:
{
'object_name'
:
'CourseOverview'
},
'_location'
:
(
'xmodule_django.models.UsageKeyField'
,
[],
{
'max_length'
:
'255'
}),
'_pre_requisite_courses_json'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'advertised_start'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'cert_name_long'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'cert_name_short'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'certificates_display_behavior'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'certificates_show_before_end'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'course_image_url'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'days_early_for_beta'
:
(
'django.db.models.fields.FloatField'
,
[],
{
'null'
:
'True'
}),
'display_name'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'display_number_with_default'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'display_org_with_default'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'end'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'end_of_course_survey_url'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'facebook_url'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'has_any_active_web_certificate'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'primary_key'
:
'True'
,
'db_index'
:
'True'
}),
'lowest_passing_grade'
:
(
'django.db.models.fields.DecimalField'
,
[],
{
'max_digits'
:
'5'
,
'decimal_places'
:
'2'
}),
'mobile_available'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'social_sharing_url'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'start'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'visible_to_staff_only'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
})
}
}
complete_apps
=
[
'course_overviews'
]
\ No newline at end of file
openedx/core/djangoapps/content/course_overviews/models.py
View file @
5b630a77
...
...
@@ -5,11 +5,9 @@ Declaration of CourseOverview model
import
json
import
django.db.models
from
django.db.models.fields
import
BooleanField
,
DateTimeField
,
DecimalField
,
TextField
from
django.db.models.fields
import
BooleanField
,
DateTimeField
,
DecimalField
,
TextField
,
FloatField
from
django.utils.translation
import
ugettext
from
lms.djangoapps.certificates.api
import
get_active_web_certificate
from
lms.djangoapps.courseware.courses
import
course_image_url
from
util.date_utils
import
strftime_localized
from
xmodule
import
course_metadata_utils
from
xmodule.modulestore.django
import
modulestore
...
...
@@ -54,6 +52,7 @@ class CourseOverview(django.db.models.Model):
lowest_passing_grade
=
DecimalField
(
max_digits
=
5
,
decimal_places
=
2
)
# Access parameters
days_early_for_beta
=
FloatField
(
null
=
True
)
mobile_available
=
BooleanField
()
visible_to_staff_only
=
BooleanField
()
_pre_requisite_courses_json
=
TextField
()
# JSON representation of list of CourseKey strings
...
...
@@ -72,6 +71,9 @@ class CourseOverview(django.db.models.Model):
Returns:
CourseOverview: overview extracted from the given course
"""
from
lms.djangoapps.certificates.api
import
get_active_web_certificate
from
lms.djangoapps.courseware.courses
import
course_image_url
return
CourseOverview
(
id
=
course
.
id
,
_location
=
course
.
location
,
...
...
@@ -95,6 +97,7 @@ class CourseOverview(django.db.models.Model):
lowest_passing_grade
=
course
.
lowest_passing_grade
,
end_of_course_survey_url
=
course
.
end_of_course_survey_url
,
days_early_for_beta
=
course
.
days_early_for_beta
,
mobile_available
=
course
.
mobile_available
,
visible_to_staff_only
=
course
.
visible_to_staff_only
,
_pre_requisite_courses_json
=
json
.
dumps
(
course
.
pre_requisite_courses
)
...
...
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