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
d6ced072
Commit
d6ced072
authored
Dec 03, 2015
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor CourseDetails
parent
d170c92f
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
429 additions
and
345 deletions
+429
-345
cms/djangoapps/contentstore/tests/test_course_settings.py
+165
-250
cms/djangoapps/contentstore/tests/test_courseware_index.py
+4
-21
cms/djangoapps/contentstore/utils.py
+0
-21
cms/djangoapps/contentstore/views/course.py
+2
-1
cms/djangoapps/models/settings/encoder.py
+28
-0
cms/templates/settings_graders.html
+1
-1
common/lib/xmodule/xmodule/modulestore/tests/factories.py
+2
-1
openedx/core/djangoapps/models/__init__.py
+0
-0
openedx/core/djangoapps/models/course_details.py
+100
-50
openedx/core/djangoapps/models/tests/test_course_details.py
+127
-0
No files found.
cms/djangoapps/contentstore/tests/test_course_settings.py
View file @
d6ced072
...
@@ -2,63 +2,44 @@
...
@@ -2,63 +2,44 @@
Tests for Studio Course Settings.
Tests for Studio Course Settings.
"""
"""
import
datetime
import
datetime
import
ddt
import
json
import
json
import
copy
import
copy
import
mock
import
mock
from
mock
import
patch
from
mock
import
patch
import
unittest
import
unittest
from
django.conf
import
settings
from
django.utils.timezone
import
UTC
from
django.utils.timezone
import
UTC
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
from
django.conf
import
settings
from
models.settings.course_details
import
(
CourseDetails
,
CourseSettingsEncoder
)
from
models.settings.course_grading
import
CourseGradingModel
from
contentstore.utils
import
reverse_course_url
,
reverse_usage_url
from
contentstore.utils
import
reverse_course_url
,
reverse_usage_url
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
contentstore.views.component
import
ADVANCED_COMPONENT_POLICY_KEY
from
models.settings.course_grading
import
CourseGradingModel
from
models.settings.course_metadata
import
CourseMetadata
from
models.settings.encoder
import
CourseSettingsEncoder
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.models.course_details
import
CourseDetails
from
student.roles
import
CourseInstructorRole
from
student.roles
import
CourseInstructorRole
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
models.settings.course_metadata
import
CourseMetadata
from
xmodule.fields
import
Date
from
xmodule.fields
import
Date
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.tabs
import
InvalidTabsException
from
xmodule.tabs
import
InvalidTabsException
from
util.milestones_helpers
import
seed_milestone_relationship_types
from
.utils
import
CourseTestCase
from
.utils
import
CourseTestCase
from
xmodule.modulestore.django
import
modulestore
from
contentstore.views.component
import
ADVANCED_COMPONENT_POLICY_KEY
import
ddt
from
xmodule.modulestore
import
ModuleStoreEnum
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
util.milestones_helpers
import
seed_milestone_relationship_types
def
get_url
(
course_id
,
handler_name
=
'settings_handler'
):
def
get_url
(
course_id
,
handler_name
=
'settings_handler'
):
return
reverse_course_url
(
handler_name
,
course_id
)
return
reverse_course_url
(
handler_name
,
course_id
)
class
Course
DetailsTestCase
(
CourseTestCase
):
class
Course
SettingsEncoderTest
(
CourseTestCase
):
"""
"""
Tests
the first course settings page (course dates, overview, etc.)
.
Tests
for CourseSettingsEncoder
.
"""
"""
def
test_virgin_fetch
(
self
):
details
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
self
.
assertEqual
(
details
.
org
,
self
.
course
.
location
.
org
,
"Org not copied into"
)
self
.
assertEqual
(
details
.
course_id
,
self
.
course
.
location
.
course
,
"Course_id not copied into"
)
self
.
assertEqual
(
details
.
run
,
self
.
course
.
location
.
name
,
"Course name not copied into"
)
self
.
assertEqual
(
details
.
course_image_name
,
self
.
course
.
course_image
)
self
.
assertIsNotNone
(
details
.
start_date
.
tzinfo
)
self
.
assertIsNone
(
details
.
end_date
,
"end date somehow initialized "
+
str
(
details
.
end_date
))
self
.
assertIsNone
(
details
.
enrollment_start
,
"enrollment_start date somehow initialized "
+
str
(
details
.
enrollment_start
))
self
.
assertIsNone
(
details
.
enrollment_end
,
"enrollment_end date somehow initialized "
+
str
(
details
.
enrollment_end
))
self
.
assertIsNone
(
details
.
syllabus
,
"syllabus somehow initialized"
+
str
(
details
.
syllabus
))
self
.
assertIsNone
(
details
.
intro_video
,
"intro_video somehow initialized"
+
str
(
details
.
intro_video
))
self
.
assertIsNone
(
details
.
effort
,
"effort somehow initialized"
+
str
(
details
.
effort
))
self
.
assertIsNone
(
details
.
language
,
"language somehow initialized"
+
str
(
details
.
language
))
self
.
assertIsNone
(
details
.
has_cert_config
)
self
.
assertFalse
(
details
.
self_paced
)
def
test_encoder
(
self
):
def
test_encoder
(
self
):
details
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
details
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
jsondetails
=
json
.
dumps
(
details
,
cls
=
CourseSettingsEncoder
)
jsondetails
=
json
.
dumps
(
details
,
cls
=
CourseSettingsEncoder
)
...
@@ -87,59 +68,162 @@ class CourseDetailsTestCase(CourseTestCase):
...
@@ -87,59 +68,162 @@ class CourseDetailsTestCase(CourseTestCase):
self
.
assertEquals
(
1
,
jsondetails
[
'number'
])
self
.
assertEquals
(
1
,
jsondetails
[
'number'
])
self
.
assertEqual
(
jsondetails
[
'string'
],
'string'
)
self
.
assertEqual
(
jsondetails
[
'string'
],
'string'
)
@ddt.ddt
class
CourseDetailsViewTest
(
CourseTestCase
):
"""
Tests for modifying content on the first course settings page (course dates, overview, etc.).
"""
def
setUp
(
self
):
super
(
CourseDetailsViewTest
,
self
)
.
setUp
()
def
alter_field
(
self
,
url
,
details
,
field
,
val
):
"""
Change the one field to the given value and then invoke the update post to see if it worked.
"""
setattr
(
details
,
field
,
val
)
# Need to partially serialize payload b/c the mock doesn't handle it correctly
payload
=
copy
.
copy
(
details
.
__dict__
)
payload
[
'start_date'
]
=
CourseDetailsViewTest
.
convert_datetime_to_iso
(
details
.
start_date
)
payload
[
'end_date'
]
=
CourseDetailsViewTest
.
convert_datetime_to_iso
(
details
.
end_date
)
payload
[
'enrollment_start'
]
=
CourseDetailsViewTest
.
convert_datetime_to_iso
(
details
.
enrollment_start
)
payload
[
'enrollment_end'
]
=
CourseDetailsViewTest
.
convert_datetime_to_iso
(
details
.
enrollment_end
)
resp
=
self
.
client
.
ajax_post
(
url
,
payload
)
self
.
compare_details_with_encoding
(
json
.
loads
(
resp
.
content
),
details
.
__dict__
,
field
+
str
(
val
))
@staticmethod
def
convert_datetime_to_iso
(
datetime_obj
):
"""
Use the xblock serializer to convert the datetime
"""
return
Date
()
.
to_json
(
datetime_obj
)
def
test_update_and_fetch
(
self
):
def
test_update_and_fetch
(
self
):
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
jsondetails
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
details
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
jsondetails
.
syllabus
=
"<a href='foo'>bar</a>"
# encode - decode to convert date fields and other data which changes form
# resp s/b json from here on
self
.
assertEqual
(
url
=
get_url
(
self
.
course
.
id
)
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
syllabus
,
resp
=
self
.
client
.
get_json
(
url
)
jsondetails
.
syllabus
,
"After set syllabus"
self
.
compare_details_with_encoding
(
json
.
loads
(
resp
.
content
),
details
.
__dict__
,
"virgin get"
)
)
jsondetails
.
short_description
=
"Short Description"
utc
=
UTC
()
self
.
assertEqual
(
self
.
alter_field
(
url
,
details
,
'start_date'
,
datetime
.
datetime
(
2012
,
11
,
12
,
1
,
30
,
tzinfo
=
utc
))
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
short_description
,
self
.
alter_field
(
url
,
details
,
'start_date'
,
datetime
.
datetime
(
2012
,
11
,
1
,
13
,
30
,
tzinfo
=
utc
))
jsondetails
.
short_description
,
"After set short_description"
self
.
alter_field
(
url
,
details
,
'end_date'
,
datetime
.
datetime
(
2013
,
2
,
12
,
1
,
30
,
tzinfo
=
utc
))
)
self
.
alter_field
(
url
,
details
,
'enrollment_start'
,
datetime
.
datetime
(
2012
,
10
,
12
,
1
,
30
,
tzinfo
=
utc
))
jsondetails
.
overview
=
"Overview"
self
.
assertEqual
(
self
.
alter_field
(
url
,
details
,
'enrollment_end'
,
datetime
.
datetime
(
2012
,
11
,
15
,
1
,
30
,
tzinfo
=
utc
))
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
overview
,
self
.
alter_field
(
url
,
details
,
'short_description'
,
"Short Description"
)
jsondetails
.
overview
,
"After set overview"
self
.
alter_field
(
url
,
details
,
'overview'
,
"Overview"
)
)
self
.
alter_field
(
url
,
details
,
'intro_video'
,
"intro_video"
)
jsondetails
.
intro_video
=
"intro_video"
self
.
alter_field
(
url
,
details
,
'effort'
,
"effort"
)
self
.
assertEqual
(
self
.
alter_field
(
url
,
details
,
'course_image_name'
,
"course_image_name"
)
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
intro_video
,
self
.
alter_field
(
url
,
details
,
'language'
,
"en"
)
jsondetails
.
intro_video
,
"After set intro_video"
self
.
alter_field
(
url
,
details
,
'self_paced'
,
"true"
)
)
jsondetails
.
effort
=
"effort"
def
compare_details_with_encoding
(
self
,
encoded
,
details
,
context
):
self
.
assertEqual
(
"""
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
effort
,
compare all of the fields of the before and after dicts
jsondetails
.
effort
,
"After set effort"
"""
)
self
.
compare_date_fields
(
details
,
encoded
,
context
,
'start_date'
)
jsondetails
.
self_paced
=
True
self
.
compare_date_fields
(
details
,
encoded
,
context
,
'end_date'
)
self
.
assertEqual
(
self
.
compare_date_fields
(
details
,
encoded
,
context
,
'enrollment_start'
)
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
self_paced
,
self
.
compare_date_fields
(
details
,
encoded
,
context
,
'enrollment_end'
)
jsondetails
.
self_paced
)
jsondetails
.
start_date
=
datetime
.
datetime
(
2010
,
10
,
1
,
0
,
tzinfo
=
UTC
())
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
start_date
,
jsondetails
.
start_date
)
jsondetails
.
end_date
=
datetime
.
datetime
(
2011
,
10
,
1
,
0
,
tzinfo
=
UTC
())
self
.
assertEqual
(
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
end_date
,
details
[
'short_description'
],
encoded
[
'short_description'
],
context
+
" short_description not =="
jsondetails
.
end_date
)
)
jsondetails
.
course_image_name
=
"an_image.jpg"
self
.
assertEqual
(
details
[
'overview'
],
encoded
[
'overview'
],
context
+
" overviews not =="
)
self
.
assertEqual
(
self
.
assertEqual
(
details
[
'intro_video'
],
encoded
.
get
(
'intro_video'
,
None
),
context
+
" intro_video not =="
)
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
course_image_name
,
self
.
assertEqual
(
details
[
'effort'
],
encoded
[
'effort'
],
context
+
" efforts not =="
)
jsondetails
.
course_image_name
self
.
assertEqual
(
details
[
'course_image_name'
],
encoded
[
'course_image_name'
],
context
+
" images not =="
)
self
.
assertEqual
(
details
[
'language'
],
encoded
[
'language'
],
context
+
" languages not =="
)
def
compare_date_fields
(
self
,
details
,
encoded
,
context
,
field
):
"""
Compare the given date fields between the before and after doing json deserialization
"""
if
details
[
field
]
is
not
None
:
date
=
Date
()
if
field
in
encoded
and
encoded
[
field
]
is
not
None
:
dt1
=
date
.
from_json
(
encoded
[
field
])
dt2
=
details
[
field
]
self
.
assertEqual
(
dt1
,
dt2
,
msg
=
"{} != {} at {}"
.
format
(
dt1
,
dt2
,
context
))
else
:
self
.
fail
(
field
+
" missing from encoded but in details at "
+
context
)
elif
field
in
encoded
and
encoded
[
field
]
is
not
None
:
self
.
fail
(
field
+
" included in encoding but missing from details at "
+
context
)
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENABLE_PREREQUISITE_COURSES'
:
True
,
'MILESTONES_APP'
:
True
})
def
test_pre_requisite_course_list_present
(
self
):
seed_milestone_relationship_types
()
settings_details_url
=
get_url
(
self
.
course
.
id
)
response
=
self
.
client
.
get_html
(
settings_details_url
)
self
.
assertContains
(
response
,
"Prerequisite Course"
)
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENABLE_PREREQUISITE_COURSES'
:
True
,
'MILESTONES_APP'
:
True
})
def
test_pre_requisite_course_update_and_fetch
(
self
):
seed_milestone_relationship_types
()
url
=
get_url
(
self
.
course
.
id
)
resp
=
self
.
client
.
get_json
(
url
)
course_detail_json
=
json
.
loads
(
resp
.
content
)
# assert pre_requisite_courses is initialized
self
.
assertEqual
([],
course_detail_json
[
'pre_requisite_courses'
])
# update pre requisite courses with a new course keys
pre_requisite_course
=
CourseFactory
.
create
(
org
=
'edX'
,
course
=
'900'
,
run
=
'test_run'
)
pre_requisite_course2
=
CourseFactory
.
create
(
org
=
'edX'
,
course
=
'902'
,
run
=
'test_run'
)
pre_requisite_course_keys
=
[
unicode
(
pre_requisite_course
.
id
),
unicode
(
pre_requisite_course2
.
id
)]
course_detail_json
[
'pre_requisite_courses'
]
=
pre_requisite_course_keys
self
.
client
.
ajax_post
(
url
,
course_detail_json
)
# fetch updated course to assert pre_requisite_courses has new values
resp
=
self
.
client
.
get_json
(
url
)
course_detail_json
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
pre_requisite_course_keys
,
course_detail_json
[
'pre_requisite_courses'
])
# remove pre requisite course
course_detail_json
[
'pre_requisite_courses'
]
=
[]
self
.
client
.
ajax_post
(
url
,
course_detail_json
)
resp
=
self
.
client
.
get_json
(
url
)
course_detail_json
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
([],
course_detail_json
[
'pre_requisite_courses'
])
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENABLE_PREREQUISITE_COURSES'
:
True
,
'MILESTONES_APP'
:
True
})
def
test_invalid_pre_requisite_course
(
self
):
seed_milestone_relationship_types
()
url
=
get_url
(
self
.
course
.
id
)
resp
=
self
.
client
.
get_json
(
url
)
course_detail_json
=
json
.
loads
(
resp
.
content
)
# update pre requisite courses one valid and one invalid key
pre_requisite_course
=
CourseFactory
.
create
(
org
=
'edX'
,
course
=
'900'
,
run
=
'test_run'
)
pre_requisite_course_keys
=
[
unicode
(
pre_requisite_course
.
id
),
'invalid_key'
]
course_detail_json
[
'pre_requisite_courses'
]
=
pre_requisite_course_keys
response
=
self
.
client
.
ajax_post
(
url
,
course_detail_json
)
self
.
assertEqual
(
400
,
response
.
status_code
)
@ddt.data
(
(
False
,
False
,
False
),
(
True
,
False
,
True
),
(
False
,
True
,
False
),
(
True
,
True
,
True
),
)
)
jsondetails
.
language
=
"hr"
def
test_visibility_of_entrance_exam_section
(
self
,
feature_flags
):
"""
Tests entrance exam section is available if ENTRANCE_EXAMS feature is enabled no matter any other
feature is enabled or disabled i.e ENABLE_MKTG_SITE.
"""
with
patch
.
dict
(
"django.conf.settings.FEATURES"
,
{
'ENTRANCE_EXAMS'
:
feature_flags
[
0
],
'ENABLE_MKTG_SITE'
:
feature_flags
[
1
]
}):
course_details_url
=
get_url
(
self
.
course
.
id
)
resp
=
self
.
client
.
get_html
(
course_details_url
)
self
.
assertEqual
(
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
language
,
feature_flags
[
2
]
,
jsondetails
.
language
'<h3 id="heading-entrance-exam">'
in
resp
.
content
)
)
@override_settings
(
MKTG_URLS
=
{
'ROOT'
:
'dummy-root'
})
@override_settings
(
MKTG_URLS
=
{
'ROOT'
:
'dummy-root'
})
...
@@ -296,175 +380,6 @@ class CourseDetailsTestCase(CourseTestCase):
...
@@ -296,175 +380,6 @@ class CourseDetailsTestCase(CourseTestCase):
self
.
assertContains
(
response
,
"Course Introduction Video"
)
self
.
assertContains
(
response
,
"Course Introduction Video"
)
self
.
assertContains
(
response
,
"Requirements"
)
self
.
assertContains
(
response
,
"Requirements"
)
def
test_toggle_pacing_during_course_run
(
self
):
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
self
.
course
.
start
=
datetime
.
datetime
.
now
()
modulestore
()
.
update_item
(
self
.
course
,
self
.
user
.
id
)
details
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
updated_details
=
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
dict
(
details
.
__dict__
,
self_paced
=
True
),
self
.
user
)
self
.
assertFalse
(
updated_details
.
self_paced
)
@ddt.ddt
class
CourseDetailsViewTest
(
CourseTestCase
):
"""
Tests for modifying content on the first course settings page (course dates, overview, etc.).
"""
def
setUp
(
self
):
super
(
CourseDetailsViewTest
,
self
)
.
setUp
()
def
alter_field
(
self
,
url
,
details
,
field
,
val
):
"""
Change the one field to the given value and then invoke the update post to see if it worked.
"""
setattr
(
details
,
field
,
val
)
# Need to partially serialize payload b/c the mock doesn't handle it correctly
payload
=
copy
.
copy
(
details
.
__dict__
)
payload
[
'start_date'
]
=
CourseDetailsViewTest
.
convert_datetime_to_iso
(
details
.
start_date
)
payload
[
'end_date'
]
=
CourseDetailsViewTest
.
convert_datetime_to_iso
(
details
.
end_date
)
payload
[
'enrollment_start'
]
=
CourseDetailsViewTest
.
convert_datetime_to_iso
(
details
.
enrollment_start
)
payload
[
'enrollment_end'
]
=
CourseDetailsViewTest
.
convert_datetime_to_iso
(
details
.
enrollment_end
)
resp
=
self
.
client
.
ajax_post
(
url
,
payload
)
self
.
compare_details_with_encoding
(
json
.
loads
(
resp
.
content
),
details
.
__dict__
,
field
+
str
(
val
))
@staticmethod
def
convert_datetime_to_iso
(
datetime_obj
):
"""
Use the xblock serializer to convert the datetime
"""
return
Date
()
.
to_json
(
datetime_obj
)
def
test_update_and_fetch
(
self
):
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
details
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
# resp s/b json from here on
url
=
get_url
(
self
.
course
.
id
)
resp
=
self
.
client
.
get_json
(
url
)
self
.
compare_details_with_encoding
(
json
.
loads
(
resp
.
content
),
details
.
__dict__
,
"virgin get"
)
utc
=
UTC
()
self
.
alter_field
(
url
,
details
,
'start_date'
,
datetime
.
datetime
(
2012
,
11
,
12
,
1
,
30
,
tzinfo
=
utc
))
self
.
alter_field
(
url
,
details
,
'start_date'
,
datetime
.
datetime
(
2012
,
11
,
1
,
13
,
30
,
tzinfo
=
utc
))
self
.
alter_field
(
url
,
details
,
'end_date'
,
datetime
.
datetime
(
2013
,
2
,
12
,
1
,
30
,
tzinfo
=
utc
))
self
.
alter_field
(
url
,
details
,
'enrollment_start'
,
datetime
.
datetime
(
2012
,
10
,
12
,
1
,
30
,
tzinfo
=
utc
))
self
.
alter_field
(
url
,
details
,
'enrollment_end'
,
datetime
.
datetime
(
2012
,
11
,
15
,
1
,
30
,
tzinfo
=
utc
))
self
.
alter_field
(
url
,
details
,
'short_description'
,
"Short Description"
)
self
.
alter_field
(
url
,
details
,
'overview'
,
"Overview"
)
self
.
alter_field
(
url
,
details
,
'intro_video'
,
"intro_video"
)
self
.
alter_field
(
url
,
details
,
'effort'
,
"effort"
)
self
.
alter_field
(
url
,
details
,
'course_image_name'
,
"course_image_name"
)
self
.
alter_field
(
url
,
details
,
'language'
,
"en"
)
self
.
alter_field
(
url
,
details
,
'self_paced'
,
"true"
)
def
compare_details_with_encoding
(
self
,
encoded
,
details
,
context
):
"""
compare all of the fields of the before and after dicts
"""
self
.
compare_date_fields
(
details
,
encoded
,
context
,
'start_date'
)
self
.
compare_date_fields
(
details
,
encoded
,
context
,
'end_date'
)
self
.
compare_date_fields
(
details
,
encoded
,
context
,
'enrollment_start'
)
self
.
compare_date_fields
(
details
,
encoded
,
context
,
'enrollment_end'
)
self
.
assertEqual
(
details
[
'short_description'
],
encoded
[
'short_description'
],
context
+
" short_description not =="
)
self
.
assertEqual
(
details
[
'overview'
],
encoded
[
'overview'
],
context
+
" overviews not =="
)
self
.
assertEqual
(
details
[
'intro_video'
],
encoded
.
get
(
'intro_video'
,
None
),
context
+
" intro_video not =="
)
self
.
assertEqual
(
details
[
'effort'
],
encoded
[
'effort'
],
context
+
" efforts not =="
)
self
.
assertEqual
(
details
[
'course_image_name'
],
encoded
[
'course_image_name'
],
context
+
" images not =="
)
self
.
assertEqual
(
details
[
'language'
],
encoded
[
'language'
],
context
+
" languages not =="
)
def
compare_date_fields
(
self
,
details
,
encoded
,
context
,
field
):
"""
Compare the given date fields between the before and after doing json deserialization
"""
if
details
[
field
]
is
not
None
:
date
=
Date
()
if
field
in
encoded
and
encoded
[
field
]
is
not
None
:
dt1
=
date
.
from_json
(
encoded
[
field
])
dt2
=
details
[
field
]
self
.
assertEqual
(
dt1
,
dt2
,
msg
=
"{} != {} at {}"
.
format
(
dt1
,
dt2
,
context
))
else
:
self
.
fail
(
field
+
" missing from encoded but in details at "
+
context
)
elif
field
in
encoded
and
encoded
[
field
]
is
not
None
:
self
.
fail
(
field
+
" included in encoding but missing from details at "
+
context
)
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENABLE_PREREQUISITE_COURSES'
:
True
,
'MILESTONES_APP'
:
True
})
def
test_pre_requisite_course_list_present
(
self
):
seed_milestone_relationship_types
()
settings_details_url
=
get_url
(
self
.
course
.
id
)
response
=
self
.
client
.
get_html
(
settings_details_url
)
self
.
assertContains
(
response
,
"Prerequisite Course"
)
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENABLE_PREREQUISITE_COURSES'
:
True
,
'MILESTONES_APP'
:
True
})
def
test_pre_requisite_course_update_and_fetch
(
self
):
seed_milestone_relationship_types
()
url
=
get_url
(
self
.
course
.
id
)
resp
=
self
.
client
.
get_json
(
url
)
course_detail_json
=
json
.
loads
(
resp
.
content
)
# assert pre_requisite_courses is initialized
self
.
assertEqual
([],
course_detail_json
[
'pre_requisite_courses'
])
# update pre requisite courses with a new course keys
pre_requisite_course
=
CourseFactory
.
create
(
org
=
'edX'
,
course
=
'900'
,
run
=
'test_run'
)
pre_requisite_course2
=
CourseFactory
.
create
(
org
=
'edX'
,
course
=
'902'
,
run
=
'test_run'
)
pre_requisite_course_keys
=
[
unicode
(
pre_requisite_course
.
id
),
unicode
(
pre_requisite_course2
.
id
)]
course_detail_json
[
'pre_requisite_courses'
]
=
pre_requisite_course_keys
self
.
client
.
ajax_post
(
url
,
course_detail_json
)
# fetch updated course to assert pre_requisite_courses has new values
resp
=
self
.
client
.
get_json
(
url
)
course_detail_json
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
(
pre_requisite_course_keys
,
course_detail_json
[
'pre_requisite_courses'
])
# remove pre requisite course
course_detail_json
[
'pre_requisite_courses'
]
=
[]
self
.
client
.
ajax_post
(
url
,
course_detail_json
)
resp
=
self
.
client
.
get_json
(
url
)
course_detail_json
=
json
.
loads
(
resp
.
content
)
self
.
assertEqual
([],
course_detail_json
[
'pre_requisite_courses'
])
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENABLE_PREREQUISITE_COURSES'
:
True
,
'MILESTONES_APP'
:
True
})
def
test_invalid_pre_requisite_course
(
self
):
seed_milestone_relationship_types
()
url
=
get_url
(
self
.
course
.
id
)
resp
=
self
.
client
.
get_json
(
url
)
course_detail_json
=
json
.
loads
(
resp
.
content
)
# update pre requisite courses one valid and one invalid key
pre_requisite_course
=
CourseFactory
.
create
(
org
=
'edX'
,
course
=
'900'
,
run
=
'test_run'
)
pre_requisite_course_keys
=
[
unicode
(
pre_requisite_course
.
id
),
'invalid_key'
]
course_detail_json
[
'pre_requisite_courses'
]
=
pre_requisite_course_keys
response
=
self
.
client
.
ajax_post
(
url
,
course_detail_json
)
self
.
assertEqual
(
400
,
response
.
status_code
)
@ddt.data
(
(
False
,
False
,
False
),
(
True
,
False
,
True
),
(
False
,
True
,
False
),
(
True
,
True
,
True
),
)
def
test_visibility_of_entrance_exam_section
(
self
,
feature_flags
):
"""
Tests entrance exam section is available if ENTRANCE_EXAMS feature is enabled no matter any other
feature is enabled or disabled i.e ENABLE_MKTG_SITE.
"""
with
patch
.
dict
(
"django.conf.settings.FEATURES"
,
{
'ENTRANCE_EXAMS'
:
feature_flags
[
0
],
'ENABLE_MKTG_SITE'
:
feature_flags
[
1
]
}):
course_details_url
=
get_url
(
self
.
course
.
id
)
resp
=
self
.
client
.
get_html
(
course_details_url
)
self
.
assertEqual
(
feature_flags
[
2
],
'<h3 id="heading-entrance-exam">'
in
resp
.
content
)
@ddt.ddt
@ddt.ddt
class
CourseGradingTest
(
CourseTestCase
):
class
CourseGradingTest
(
CourseTestCase
):
...
@@ -1068,7 +983,7 @@ class CourseMetadataEditingTest(CourseTestCase):
...
@@ -1068,7 +983,7 @@ class CourseMetadataEditingTest(CourseTestCase):
self
.
assertEqual
(
tab_list
,
course
.
tabs
)
self
.
assertEqual
(
tab_list
,
course
.
tabs
)
@override_settings
(
FEATURES
=
{
'CERTIFICATES_HTML_VIEW'
:
True
})
@override_settings
(
FEATURES
=
{
'CERTIFICATES_HTML_VIEW'
:
True
})
def
test_web_view_certifcate_configuration_settings
(
self
):
def
test_web_view_certif
i
cate_configuration_settings
(
self
):
"""
"""
Test that has_cert_config is updated based on cert_html_view_enabled setting.
Test that has_cert_config is updated based on cert_html_view_enabled setting.
"""
"""
...
...
cms/djangoapps/contentstore/tests/test_courseware_index.py
View file @
d6ced072
...
@@ -15,6 +15,7 @@ from unittest import skip
...
@@ -15,6 +15,7 @@ from unittest import skip
from
django.conf
import
settings
from
django.conf
import
settings
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
openedx.core.djangoapps.models.course_details
import
CourseDetails
from
xmodule.library_tools
import
normalize_key_for_search
from
xmodule.library_tools
import
normalize_key_for_search
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
SignalHandler
,
modulestore
from
xmodule.modulestore.django
import
SignalHandler
,
modulestore
...
@@ -192,26 +193,6 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
...
@@ -192,26 +193,6 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
with
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
):
with
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
):
store
.
update_item
(
item
,
ModuleStoreEnum
.
UserID
.
test
)
store
.
update_item
(
item
,
ModuleStoreEnum
.
UserID
.
test
)
def
update_about_item
(
self
,
store
,
about_key
,
data
):
"""
Update the about item with the new data blob. If data is None, then
delete the about item.
"""
temploc
=
self
.
course
.
id
.
make_usage_key
(
'about'
,
about_key
)
if
data
is
None
:
try
:
self
.
delete_item
(
store
,
temploc
)
# Ignore an attempt to delete an item that doesn't exist
except
ValueError
:
pass
else
:
try
:
about_item
=
store
.
get_item
(
temploc
)
except
ItemNotFoundError
:
about_item
=
store
.
create_xblock
(
self
.
course
.
runtime
,
self
.
course
.
id
,
'about'
,
about_key
)
about_item
.
data
=
data
store
.
update_item
(
about_item
,
ModuleStoreEnum
.
UserID
.
test
,
allow_not_found
=
True
)
@ddt.ddt
@ddt.ddt
class
TestCoursewareSearchIndexer
(
MixedWithOptionsTestCase
):
class
TestCoursewareSearchIndexer
(
MixedWithOptionsTestCase
):
...
@@ -487,7 +468,9 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
...
@@ -487,7 +468,9 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
def
_test_course_about_store_index
(
self
,
store
):
def
_test_course_about_store_index
(
self
,
store
):
""" Test that informational properties in the about store end up in the course_info index """
""" Test that informational properties in the about store end up in the course_info index """
short_description
=
"Not just anybody"
short_description
=
"Not just anybody"
self
.
update_about_item
(
store
,
"short_description"
,
short_description
)
CourseDetails
.
update_about_item
(
self
.
course
,
"short_description"
,
short_description
,
ModuleStoreEnum
.
UserID
.
test
,
store
)
self
.
reindex_course
(
store
)
self
.
reindex_course
(
store
)
response
=
self
.
searcher
.
search
(
response
=
self
.
searcher
.
search
(
doc_type
=
CourseAboutSearchIndexer
.
DISCOVERY_DOCUMENT_TYPE
,
doc_type
=
CourseAboutSearchIndexer
.
DISCOVERY_DOCUMENT_TYPE
,
...
...
cms/djangoapps/contentstore/utils.py
View file @
d6ced072
...
@@ -3,7 +3,6 @@ Common utility functions useful throughout the contentstore
...
@@ -3,7 +3,6 @@ Common utility functions useful throughout the contentstore
"""
"""
import
logging
import
logging
from
opaque_keys
import
InvalidKeyError
import
re
import
re
from
datetime
import
datetime
from
datetime
import
datetime
from
pytz
import
UTC
from
pytz
import
UTC
...
@@ -14,7 +13,6 @@ from django.utils.translation import ugettext as _
...
@@ -14,7 +13,6 @@ from django.utils.translation import ugettext as _
from
django_comment_common.models
import
assign_default_role
from
django_comment_common.models
import
assign_default_role
from
django_comment_common.utils
import
seed_permissions_roles
from
django_comment_common.utils
import
seed_permissions_roles
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
...
@@ -304,25 +302,6 @@ def reverse_usage_url(handler_name, usage_key, kwargs=None):
...
@@ -304,25 +302,6 @@ def reverse_usage_url(handler_name, usage_key, kwargs=None):
return
reverse_url
(
handler_name
,
'usage_key_string'
,
usage_key
,
kwargs
)
return
reverse_url
(
handler_name
,
'usage_key_string'
,
usage_key
,
kwargs
)
def
has_active_web_certificate
(
course
):
"""
Returns True if given course has active web certificate configuration.
If given course has no active web certificate configuration returns False.
Returns None If `CERTIFICATES_HTML_VIEW` is not enabled of course has not enabled
`cert_html_view_enabled` settings.
"""
cert_config
=
None
if
settings
.
FEATURES
.
get
(
'CERTIFICATES_HTML_VIEW'
,
False
)
and
course
.
cert_html_view_enabled
:
cert_config
=
False
certificates
=
getattr
(
course
,
'certificates'
,
{})
configurations
=
certificates
.
get
(
'certificates'
,
[])
for
config
in
configurations
:
if
config
.
get
(
'is_active'
):
cert_config
=
True
break
return
cert_config
def
get_user_partition_info
(
xblock
,
schemes
=
None
,
course
=
None
):
def
get_user_partition_info
(
xblock
,
schemes
=
None
,
course
=
None
):
"""
"""
Retrieve user partition information for an XBlock for display in editors.
Retrieve user partition information for an XBlock for display in editors.
...
...
cms/djangoapps/contentstore/views/course.py
View file @
d6ced072
...
@@ -58,12 +58,13 @@ from course_action_state.models import CourseRerunState, CourseRerunUIStateManag
...
@@ -58,12 +58,13 @@ from course_action_state.models import CourseRerunState, CourseRerunUIStateManag
from
course_creators.views
import
get_course_creator_status
,
add_user_with_status_unrequested
from
course_creators.views
import
get_course_creator_status
,
add_user_with_status_unrequested
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
microsite_configuration
import
microsite
from
microsite_configuration
import
microsite
from
models.settings.course_details
import
CourseDetails
,
CourseSettingsEncoder
from
models.settings.course_grading
import
CourseGradingModel
from
models.settings.course_grading
import
CourseGradingModel
from
models.settings.course_metadata
import
CourseMetadata
from
models.settings.course_metadata
import
CourseMetadata
from
models.settings.encoder
import
CourseSettingsEncoder
from
openedx.core.djangoapps.content.course_structures.api.v0
import
api
,
errors
from
openedx.core.djangoapps.content.course_structures.api.v0
import
api
,
errors
from
openedx.core.djangoapps.credit.api
import
is_credit_course
,
get_credit_requirements
from
openedx.core.djangoapps.credit.api
import
is_credit_course
,
get_credit_requirements
from
openedx.core.djangoapps.credit.tasks
import
update_credit_course_requirements
from
openedx.core.djangoapps.credit.tasks
import
update_credit_course_requirements
from
openedx.core.djangoapps.models.course_details
import
CourseDetails
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.utils
import
get_programs
from
openedx.core.djangoapps.programs.utils
import
get_programs
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
...
...
cms/djangoapps/models/settings/encoder.py
0 → 100644
View file @
d6ced072
"""
CourseSettingsEncoder
"""
import
datetime
import
json
from
json.encoder
import
JSONEncoder
from
opaque_keys.edx.locations
import
Location
from
openedx.core.djangoapps.models.course_details
import
CourseDetails
from
xmodule.fields
import
Date
from
.course_grading
import
CourseGradingModel
class
CourseSettingsEncoder
(
json
.
JSONEncoder
):
"""
Serialize CourseDetails, CourseGradingModel, datetime, and old
Locations
"""
def
default
(
self
,
obj
):
# pylint: disable=method-hidden
if
isinstance
(
obj
,
(
CourseDetails
,
CourseGradingModel
)):
return
obj
.
__dict__
elif
isinstance
(
obj
,
Location
):
return
obj
.
dict
()
elif
isinstance
(
obj
,
datetime
.
datetime
):
return
Date
()
.
to_json
(
obj
)
else
:
return
JSONEncoder
.
default
(
self
,
obj
)
cms/templates/settings_graders.html
View file @
d6ced072
...
@@ -8,8 +8,8 @@
...
@@ -8,8 +8,8 @@
import
json
import
json
from
contentstore
import
utils
from
contentstore
import
utils
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
utils
.
translation
import
ugettext
as
_
from
models
.
settings
.
encoder
import
CourseSettingsEncoder
from
openedx
.
core
.
lib
.
js_utils
import
escape_json_dumps
from
openedx
.
core
.
lib
.
js_utils
import
escape_json_dumps
from
models
.
settings
.
course_details
import
CourseSettingsEncoder
%
>
%
>
<
%
block
name=
"header_extras"
>
<
%
block
name=
"header_extras"
>
...
...
common/lib/xmodule/xmodule/modulestore/tests/factories.py
View file @
d6ced072
...
@@ -660,7 +660,8 @@ def check_mongo_calls(num_finds=0, num_sends=None):
...
@@ -660,7 +660,8 @@ def check_mongo_calls(num_finds=0, num_sends=None):
# This dict represents the attribute keys for a course's 'about' info.
# This dict represents the attribute keys for a course's 'about' info.
# Note: The 'video' attribute is intentionally excluded as it must be
# Note: The 'video' attribute is intentionally excluded as it must be
# handled separately; its value maps to an alternate key name.
# handled separately; its value maps to an alternate key name.
# Reference : cms/djangoapps/models/settings/course_details.py
# Reference : from openedx.core.djangoapps.models.course_details.py
ABOUT_ATTRIBUTES
=
{
ABOUT_ATTRIBUTES
=
{
'effort'
:
"Testing effort"
,
'effort'
:
"Testing effort"
,
...
...
openedx/core/djangoapps/models/__init__.py
0 → 100644
View file @
d6ced072
cms/djangoapps/models/setting
s/course_details.py
→
openedx/core/djangoapps/model
s/course_details.py
View file @
d6ced072
"""
CourseDetails
"""
import
re
import
re
import
logging
import
logging
import
datetime
import
json
from
json.encoder
import
JSONEncoder
from
django.conf
import
settings
from
django.conf
import
settings
from
opaque_keys.edx.locations
import
Location
from
xmodule.fields
import
Date
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
contentstore.utils
import
has_active_web_certificate
from
models.settings
import
course_grading
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.lib.courses
import
course_image_url
from
openedx.core.lib.courses
import
course_image_url
from
xmodule.fields
import
Date
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
# This list represents the attribute keys for a course's 'about' info.
# This list represents the attribute keys for a course's 'about' info.
# Note: The 'video' attribute is intentionally excluded as it must be
# Note: The 'video' attribute is intentionally excluded as it must be
# handled separately; its value maps to an alternate key name.
# handled separately; its value maps to an alternate key name.
...
@@ -30,8 +28,12 @@ ABOUT_ATTRIBUTES = [
...
@@ -30,8 +28,12 @@ ABOUT_ATTRIBUTES = [
class
CourseDetails
(
object
):
class
CourseDetails
(
object
):
"""
An interface for extracting course information from the modulestore.
"""
def
__init__
(
self
,
org
,
course_id
,
run
):
def
__init__
(
self
,
org
,
course_id
,
run
):
# still need these for now b/c the client's screen shows these 3 fields
# still need these for now b/c the client's screen shows these 3
# fields
self
.
org
=
org
self
.
org
=
org
self
.
course_id
=
course_id
self
.
course_id
=
course_id
self
.
run
=
run
self
.
run
=
run
...
@@ -44,7 +46,7 @@ class CourseDetails(object):
...
@@ -44,7 +46,7 @@ class CourseDetails(object):
self
.
short_description
=
""
self
.
short_description
=
""
self
.
overview
=
""
# html to render as the overview
self
.
overview
=
""
# html to render as the overview
self
.
intro_video
=
None
# a video pointer
self
.
intro_video
=
None
# a video pointer
self
.
effort
=
None
#
int
hours/week
self
.
effort
=
None
# hours/week
self
.
license
=
"all-rights-reserved"
# default course license is all rights reserved
self
.
license
=
"all-rights-reserved"
# default course license is all rights reserved
self
.
course_image_name
=
""
self
.
course_image_name
=
""
self
.
course_image_asset_path
=
""
# URL of the course image
self
.
course_image_asset_path
=
""
# URL of the course image
...
@@ -73,7 +75,8 @@ class CourseDetails(object):
...
@@ -73,7 +75,8 @@ class CourseDetails(object):
@classmethod
@classmethod
def
fetch
(
cls
,
course_key
):
def
fetch
(
cls
,
course_key
):
"""
"""
Fetch the course details for the given course from persistence and return a CourseDetails model.
Fetch the course details for the given course from persistence
and return a CourseDetails model.
"""
"""
descriptor
=
modulestore
()
.
get_course
(
course_key
)
descriptor
=
modulestore
()
.
get_course
(
course_key
)
course_details
=
cls
(
course_key
.
org
,
course_key
.
course
,
course_key
.
run
)
course_details
=
cls
(
course_key
.
org
,
course_key
.
course
,
course_key
.
run
)
...
@@ -86,33 +89,57 @@ class CourseDetails(object):
...
@@ -86,33 +89,57 @@ class CourseDetails(object):
course_details
.
course_image_name
=
descriptor
.
course_image
course_details
.
course_image_name
=
descriptor
.
course_image
course_details
.
course_image_asset_path
=
course_image_url
(
descriptor
)
course_details
.
course_image_asset_path
=
course_image_url
(
descriptor
)
course_details
.
language
=
descriptor
.
language
course_details
.
language
=
descriptor
.
language
course_details
.
self_paced
=
descriptor
.
self_paced
# Default course license is "All Rights Reserved"
# Default course license is "All Rights Reserved"
course_details
.
license
=
getattr
(
descriptor
,
"license"
,
"all-rights-reserved"
)
course_details
.
license
=
getattr
(
descriptor
,
"license"
,
"all-rights-reserved"
)
course_details
.
has_cert_config
=
has_active_web_certificate
(
descriptor
)
course_details
.
has_cert_config
=
has_active_web_certificate
(
descriptor
)
course_details
.
self_paced
=
descriptor
.
self_paced
course_details
.
intro_video
=
cls
.
fetch_youtube_video_id
(
course_key
)
for
attribute
in
ABOUT_ATTRIBUTES
:
for
attribute
in
ABOUT_ATTRIBUTES
:
value
=
cls
.
_fetch_about_attribute
(
course_key
,
attribute
)
value
=
cls
.
_fetch_about_attribute
(
course_key
,
attribute
)
if
value
is
not
None
:
if
value
is
not
None
:
setattr
(
course_details
,
attribute
,
value
)
setattr
(
course_details
,
attribute
,
value
)
return
course_details
@classmethod
def
fetch_youtube_video_id
(
cls
,
course_key
):
"""
Returns the course about video ID.
"""
raw_video
=
cls
.
_fetch_about_attribute
(
course_key
,
'video'
)
raw_video
=
cls
.
_fetch_about_attribute
(
course_key
,
'video'
)
if
raw_video
:
if
raw_video
:
course_details
.
intro_video
=
CourseDetai
ls
.
parse_video_tag
(
raw_video
)
return
c
ls
.
parse_video_tag
(
raw_video
)
return
course_details
@classmethod
def
fetch_video_url
(
cls
,
course_key
):
"""
Returns the course about video URL.
"""
video_id
=
cls
.
fetch_youtube_video_id
(
course_key
)
if
video_id
:
return
"http://www.youtube.com/watch?v={0}"
.
format
(
video_id
)
@classmethod
def
fetch_effort
(
cls
,
course_key
):
"""
Returns the hours per week of effort for the course.
"""
return
cls
.
_fetch_about_attribute
(
course_key
,
'effort'
)
@classmethod
@classmethod
def
update_about_item
(
cls
,
course
_key
,
about_key
,
data
,
course
,
user
):
def
update_about_item
(
cls
,
course
,
about_key
,
data
,
user_id
,
store
=
None
):
"""
"""
Update the about item with the new data blob. If data is None,
then
Update the about item with the new data blob. If data is None,
delete the about item.
then
delete the about item.
"""
"""
temploc
=
course
_key
.
make_usage_key
(
'about'
,
about_key
)
temploc
=
course
.
id
.
make_usage_key
(
'about'
,
about_key
)
store
=
modulestore
()
store
=
store
or
modulestore
()
if
data
is
None
:
if
data
is
None
:
try
:
try
:
store
.
delete_item
(
temploc
,
user
.
id
)
store
.
delete_item
(
temploc
,
user
_
id
)
# Ignore an attempt to delete an item that doesn't exist
# Ignore an attempt to delete an item that doesn't exist
except
ValueError
:
except
ValueError
:
pass
pass
...
@@ -122,10 +149,18 @@ class CourseDetails(object):
...
@@ -122,10 +149,18 @@ class CourseDetails(object):
except
ItemNotFoundError
:
except
ItemNotFoundError
:
about_item
=
store
.
create_xblock
(
course
.
runtime
,
course
.
id
,
'about'
,
about_key
)
about_item
=
store
.
create_xblock
(
course
.
runtime
,
course
.
id
,
'about'
,
about_key
)
about_item
.
data
=
data
about_item
.
data
=
data
store
.
update_item
(
about_item
,
user
.
id
,
allow_not_found
=
True
)
store
.
update_item
(
about_item
,
user
_
id
,
allow_not_found
=
True
)
@classmethod
@classmethod
def
update_from_json
(
cls
,
course_key
,
jsondict
,
user
):
def
update_about_video
(
cls
,
course
,
video_id
,
user_id
):
"""
Updates the Course's about video to the given video ID.
"""
recomposed_video_tag
=
CourseDetails
.
recompose_video_tag
(
video_id
)
cls
.
update_about_item
(
course
,
'video'
,
recomposed_video_tag
,
user_id
)
@classmethod
def
update_from_json
(
cls
,
course_key
,
jsondict
,
user
):
# pylint: disable=too-many-statements
"""
"""
Decode the json into CourseDetails and save any changed attrs to the db
Decode the json into CourseDetails and save any changed attrs to the db
"""
"""
...
@@ -134,10 +169,11 @@ class CourseDetails(object):
...
@@ -134,10 +169,11 @@ class CourseDetails(object):
dirty
=
False
dirty
=
False
# In the descriptor's setter, the date is converted to JSON using Date's to_json method.
# In the descriptor's setter, the date is converted to JSON
# Calling to_json on something that is already JSON doesn't work. Since reaching directly
# using Date's to_json method. Calling to_json on something that
# into the model is nasty, convert the JSON Date to a Python date, which is what the
# is already JSON doesn't work. Since reaching directly into the
# setter expects as input.
# model is nasty, convert the JSON Date to a Python date, which
# is what the setter expects as input.
date
=
Date
()
date
=
Date
()
if
'start_date'
in
jsondict
:
if
'start_date'
in
jsondict
:
...
@@ -202,25 +238,28 @@ class CourseDetails(object):
...
@@ -202,25 +238,28 @@ class CourseDetails(object):
if
dirty
:
if
dirty
:
module_store
.
update_item
(
descriptor
,
user
.
id
)
module_store
.
update_item
(
descriptor
,
user
.
id
)
# NOTE: below auto writes to the db w/o verifying that any of the fields actually changed
# NOTE: below auto writes to the db w/o verifying that any of
# to make faster, could compare against db or could have client send over a list of which fields changed.
# the fields actually changed to make faster, could compare
# against db or could have client send over a list of which
# fields changed.
for
attribute
in
ABOUT_ATTRIBUTES
:
for
attribute
in
ABOUT_ATTRIBUTES
:
if
attribute
in
jsondict
:
if
attribute
in
jsondict
:
cls
.
update_about_item
(
course_key
,
attribute
,
jsondict
[
attribute
],
descriptor
,
user
)
cls
.
update_about_item
(
descriptor
,
attribute
,
jsondict
[
attribute
],
user
.
id
)
recomposed_video_tag
=
CourseDetails
.
recompose_video_tag
(
jsondict
[
'intro_video'
])
cls
.
update_about_video
(
descriptor
,
jsondict
[
'intro_video'
],
user
.
id
)
cls
.
update_about_item
(
course_key
,
'video'
,
recomposed_video_tag
,
descriptor
,
user
)
# Could just return jsondict w/o doing any db reads, but I put
the reads in as a means to confirm
# Could just return jsondict w/o doing any db reads, but I put
# it persisted correctly
#
the reads in as a means to confirm
it persisted correctly
return
CourseDetails
.
fetch
(
course_key
)
return
CourseDetails
.
fetch
(
course_key
)
@staticmethod
@staticmethod
def
parse_video_tag
(
raw_video
):
def
parse_video_tag
(
raw_video
):
"""
"""
Because the client really only wants the author to specify the youtube key, that's all we send to and get from the client.
Because the client really only wants the author to specify the
The problem is that the db stores the html markup as well (which, of course, makes any sitewide changes to how we do videos
youtube key, that's all we send to and get from the client. The
next to impossible.)
problem is that the db stores the html markup as well (which, of
course, makes any site-wide changes to how we do videos next to
impossible.)
"""
"""
if
not
raw_video
:
if
not
raw_video
:
return
None
return
None
...
@@ -237,26 +276,37 @@ class CourseDetails(object):
...
@@ -237,26 +276,37 @@ class CourseDetails(object):
@staticmethod
@staticmethod
def
recompose_video_tag
(
video_key
):
def
recompose_video_tag
(
video_key
):
# TODO should this use a mako template? Of course, my hope is that this is a short-term workaround for the db not storing
"""
Returns HTML string to embed the video in an iFrame.
"""
# TODO should this use a mako template? Of course, my hope is
# that this is a short-term workaround for the db not storing
# the right thing
# the right thing
result
=
None
result
=
None
if
video_key
:
if
video_key
:
result
=
'<iframe title="YouTube Video" width="560" height="315" src="//www.youtube.com/embed/'
+
\
result
=
(
video_key
+
'?rel=0" frameborder="0" allowfullscreen=""></iframe>'
'<iframe title="YouTube Video" width="560" height="315" src="//www.youtube.com/embed/'
+
video_key
+
'?rel=0" frameborder="0" allowfullscreen=""></iframe>'
)
return
result
return
result
# TODO
move to a more general util?
# TODO
- we may no longer need this - check with Zia
class
CourseSettingsEncoder
(
json
.
JSONEncoder
):
def
has_active_web_certificate
(
course
):
"""
"""
Serialize CourseDetails, CourseGradingModel, datetime, and old Locations
Returns True if given course has active web certificate
configuration. If given course has no active web certificate
configuration returns False. Returns None If `CERTIFICATES_HTML_VIEW`
is not enabled or course has not enabled `cert_html_view_enabled`
settings.
"""
"""
def
default
(
self
,
obj
):
cert_config
=
None
if
isinstance
(
obj
,
(
CourseDetails
,
course_grading
.
CourseGradingModel
))
:
if
settings
.
FEATURES
.
get
(
'CERTIFICATES_HTML_VIEW'
,
False
)
and
course
.
cert_html_view_enabled
:
return
obj
.
__dict__
cert_config
=
False
elif
isinstance
(
obj
,
Location
):
certificates
=
getattr
(
course
,
'certificates'
,
{})
return
obj
.
dict
(
)
configurations
=
certificates
.
get
(
'certificates'
,
[]
)
elif
isinstance
(
obj
,
datetime
.
datetime
)
:
for
config
in
configurations
:
return
Date
()
.
to_json
(
obj
)
if
config
.
get
(
'is_active'
):
else
:
return
True
return
JSONEncoder
.
default
(
self
,
obj
)
return
cert_config
openedx/core/djangoapps/models/tests/test_course_details.py
0 → 100644
View file @
d6ced072
"""
Tests for CourseDetails
"""
import
datetime
from
django.utils.timezone
import
UTC
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.models.course_details
import
CourseDetails
class
CourseDetailsTestCase
(
ModuleStoreTestCase
):
"""
Tests the first course settings page (course dates, overview, etc.).
"""
def
setUp
(
self
):
super
(
CourseDetailsTestCase
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
def
test_virgin_fetch
(
self
):
details
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
self
.
assertEqual
(
details
.
org
,
self
.
course
.
location
.
org
,
"Org not copied into"
)
self
.
assertEqual
(
details
.
course_id
,
self
.
course
.
location
.
course
,
"Course_id not copied into"
)
self
.
assertEqual
(
details
.
run
,
self
.
course
.
location
.
name
,
"Course name not copied into"
)
self
.
assertEqual
(
details
.
course_image_name
,
self
.
course
.
course_image
)
self
.
assertIsNotNone
(
details
.
start_date
.
tzinfo
)
self
.
assertIsNone
(
details
.
end_date
,
"end date somehow initialized "
+
str
(
details
.
end_date
))
self
.
assertIsNone
(
details
.
enrollment_start
,
"enrollment_start date somehow initialized "
+
str
(
details
.
enrollment_start
)
)
self
.
assertIsNone
(
details
.
enrollment_end
,
"enrollment_end date somehow initialized "
+
str
(
details
.
enrollment_end
)
)
self
.
assertIsNone
(
details
.
syllabus
,
"syllabus somehow initialized"
+
str
(
details
.
syllabus
))
self
.
assertIsNone
(
details
.
intro_video
,
"intro_video somehow initialized"
+
str
(
details
.
intro_video
))
self
.
assertIsNone
(
details
.
effort
,
"effort somehow initialized"
+
str
(
details
.
effort
))
self
.
assertIsNone
(
details
.
language
,
"language somehow initialized"
+
str
(
details
.
language
))
self
.
assertIsNone
(
details
.
has_cert_config
)
self
.
assertFalse
(
details
.
self_paced
)
def
test_update_and_fetch
(
self
):
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
jsondetails
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
jsondetails
.
syllabus
=
"<a href='foo'>bar</a>"
# encode - decode to convert date fields and other data which changes form
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
self
.
course
.
id
):
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
syllabus
,
jsondetails
.
syllabus
,
"After set syllabus"
)
jsondetails
.
short_description
=
"Short Description"
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
short_description
,
jsondetails
.
short_description
,
"After set short_description"
)
jsondetails
.
overview
=
"Overview"
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
overview
,
jsondetails
.
overview
,
"After set overview"
)
jsondetails
.
intro_video
=
"intro_video"
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
intro_video
,
jsondetails
.
intro_video
,
"After set intro_video"
)
jsondetails
.
effort
=
"effort"
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
effort
,
jsondetails
.
effort
,
"After set effort"
)
jsondetails
.
self_paced
=
True
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
self_paced
,
jsondetails
.
self_paced
)
jsondetails
.
start_date
=
datetime
.
datetime
(
2010
,
10
,
1
,
0
,
tzinfo
=
UTC
())
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
start_date
,
jsondetails
.
start_date
)
jsondetails
.
end_date
=
datetime
.
datetime
(
2011
,
10
,
1
,
0
,
tzinfo
=
UTC
())
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
end_date
,
jsondetails
.
end_date
)
jsondetails
.
course_image_name
=
"an_image.jpg"
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
course_image_name
,
jsondetails
.
course_image_name
)
jsondetails
.
language
=
"hr"
self
.
assertEqual
(
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
jsondetails
.
__dict__
,
self
.
user
)
.
language
,
jsondetails
.
language
)
def
test_toggle_pacing_during_course_run
(
self
):
SelfPacedConfiguration
(
enabled
=
True
)
.
save
()
self
.
course
.
start
=
datetime
.
datetime
.
now
()
self
.
store
.
update_item
(
self
.
course
,
self
.
user
.
id
)
details
=
CourseDetails
.
fetch
(
self
.
course
.
id
)
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
self
.
course
.
id
):
updated_details
=
CourseDetails
.
update_from_json
(
self
.
course
.
id
,
dict
(
details
.
__dict__
,
self_paced
=
True
),
self
.
user
)
self
.
assertFalse
(
updated_details
.
self_paced
)
def
test_fetch_effort
(
self
):
effort_value
=
'test_hours_of_effort'
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
self
.
course
.
id
):
CourseDetails
.
update_about_item
(
self
.
course
,
'effort'
,
effort_value
,
self
.
user
.
id
)
self
.
assertEqual
(
CourseDetails
.
fetch_effort
(
self
.
course
.
id
),
effort_value
)
def
test_fetch_video
(
self
):
video_value
=
'test_video_id'
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
self
.
course
.
id
):
CourseDetails
.
update_about_video
(
self
.
course
,
video_value
,
self
.
user
.
id
)
self
.
assertEqual
(
CourseDetails
.
fetch_youtube_video_id
(
self
.
course
.
id
),
video_value
)
video_url
=
CourseDetails
.
fetch_video_url
(
self
.
course
.
id
)
self
.
assertRegexpMatches
(
video_url
,
r'http://.*{}'
.
format
(
video_value
))
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