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
2291483c
Commit
2291483c
authored
Jan 18, 2013
by
Diana Huang
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into diana/open-ended-ui-updates
parents
f5009584
44e7d6b2
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
155 additions
and
78 deletions
+155
-78
common/djangoapps/student/views.py
+2
-5
common/lib/xmodule/xmodule/course_module.py
+50
-18
common/lib/xmodule/xmodule/tests/test_course_module.py
+52
-23
common/lib/xmodule/xmodule/tests/test_import.py
+0
-13
common/lib/xmodule/xmodule/tests/test_self_assessment.py
+25
-9
lms/djangoapps/courseware/access.py
+4
-0
lms/djangoapps/courseware/courses.py
+16
-0
lms/djangoapps/courseware/views.py
+5
-10
lms/djangoapps/instructor/views.py
+1
-0
No files found.
common/djangoapps/student/views.py
View file @
2291483c
...
...
@@ -42,7 +42,7 @@ from xmodule.modulestore.django import modulestore
#from datetime import date
from
collections
import
namedtuple
from
courseware.courses
import
get_courses
from
courseware.courses
import
get_courses
,
sort_by_announcement
from
courseware.access
import
has_access
from
statsd
import
statsd
...
...
@@ -78,10 +78,7 @@ def index(request, extra_context={}, user=None):
domain
=
request
.
META
.
get
(
'HTTP_HOST'
)
courses
=
get_courses
(
None
,
domain
=
domain
)
# Sort courses by how far are they from they start day
key
=
lambda
course
:
course
.
days_until_start
courses
=
sorted
(
courses
,
key
=
key
,
reverse
=
True
)
courses
=
sort_by_announcement
(
courses
)
# Get the 3 most recent news
top_news
=
_get_news
(
top
=
3
)
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
2291483c
import
logging
from
math
import
exp
,
erf
from
lxml
import
etree
from
path
import
path
# NOTE (THK): Only used for detecting presence of syllabus
import
requests
...
...
@@ -183,35 +184,66 @@ class CourseDescriptor(SequenceDescriptor):
@property
def
is_new
(
self
):
# The course is "new" if either if the metadata flag is_new is
# true or if the course has not started yet
"""
Returns if the course has been flagged as new in the metadata. If
there is no flag, return a heuristic value considering the
announcement and the start dates.
"""
flag
=
self
.
metadata
.
get
(
'is_new'
,
None
)
if
flag
is
None
:
return
self
.
days_until_start
>
1
# Use a heuristic if the course has not been flagged
announcement
,
start
,
now
=
self
.
_sorting_dates
()
if
announcement
and
(
now
-
announcement
)
.
days
<
30
:
# The course has been announced for less that month
return
True
elif
(
now
-
start
)
.
days
<
1
:
# The course has not started yet
return
True
else
:
return
False
elif
isinstance
(
flag
,
basestring
):
return
flag
.
lower
()
in
[
'true'
,
'yes'
,
'y'
]
else
:
return
bool
(
flag
)
@property
def
days_until_start
(
self
):
def
convert_to_datetime
(
timestamp
):
def
sorting_score
(
self
):
"""
Returns a number that can be used to sort the courses according
the how "new"" they are. The "newness"" score is computed using a
heuristic that takes into account the announcement and
(advertized) start dates of the course if available.
The lower the number the "newer" the course.
"""
# Make courses that have an announcement date shave a lower
# score than courses than don't, older courses should have a
# higher score.
announcement
,
start
,
now
=
self
.
_sorting_dates
()
scale
=
300.0
# about a year
if
announcement
:
days
=
(
now
-
announcement
)
.
days
score
=
-
exp
(
-
days
/
scale
)
else
:
days
=
(
now
-
start
)
.
days
score
=
exp
(
days
/
scale
)
return
score
def
_sorting_dates
(
self
):
# utility function to get datetime objects for dates used to
# compute the is_new flag and the sorting_score
def
to_datetime
(
timestamp
):
return
datetime
.
fromtimestamp
(
time
.
mktime
(
timestamp
))
start_date
=
convert_to_datetime
(
self
.
start
)
def
get_date
(
field
):
timetuple
=
self
.
_try_parse_time
(
field
)
return
to_datetime
(
timetuple
)
if
timetuple
else
None
# Try to use course advertised date if we can parse it
advertised_start
=
self
.
metadata
.
get
(
'advertised_start'
,
None
)
if
advertised_start
:
try
:
start_date
=
datetime
.
strptime
(
advertised_start
,
"
%
Y-
%
m-
%
dT
%
H:
%
M"
)
except
ValueError
:
pass
# Invalid date, keep using 'start''
now
=
convert_to_datetime
(
time
.
gmtime
())
days_until_start
=
(
start_date
-
now
)
.
days
return
days_until_start
announcement
=
get_date
(
'announcement'
)
start
=
get_date
(
'advertised_start'
)
or
to_datetime
(
self
.
start
)
now
=
to_datetime
(
time
.
gmtime
())
return
announcement
,
start
,
now
@lazyproperty
def
grading_context
(
self
):
...
...
common/lib/xmodule/xmodule/tests/test_course_module.py
View file @
2291483c
import
unittest
from
time
import
strptime
,
gmtime
from
time
import
strptime
from
fs.memoryfs
import
MemoryFS
from
mock
import
Mock
,
patch
...
...
@@ -39,52 +39,81 @@ class DummySystem(ImportSystem):
class
IsNewCourseTestCase
(
unittest
.
TestCase
):
"""Make sure the property is_new works on courses"""
@staticmethod
def
get_dummy_course
(
start
,
is_new
=
None
,
load_error_modules
=
Tru
e
):
def
get_dummy_course
(
start
,
announcement
=
None
,
is_new
=
Non
e
):
"""Get a dummy course"""
system
=
DummySystem
(
load_error_modules
)
is_new
=
''
if
is_new
is
None
else
'is_new="{0}"'
.
format
(
is_new
)
.
lower
()
system
=
DummySystem
(
load_error_modules
=
True
)
def
to_attrb
(
n
,
v
):
return
''
if
v
is
None
else
'{0}="{1}"'
.
format
(
n
,
v
)
.
lower
()
is_new
=
to_attrb
(
'is_new'
,
is_new
)
announcement
=
to_attrb
(
'announcement'
,
announcement
)
start_xml
=
'''
<course org="{org}" course="{course}"
graceperiod="1 day" url_name="test"
start="{start}"
{announcement}
{is_new}>
<chapter url="hi" url_name="ch" display_name="CH">
<html url_name="h" display_name="H">Two houses, ...</html>
</chapter>
</course>
'''
.
format
(
org
=
ORG
,
course
=
COURSE
,
start
=
start
,
is_new
=
is_new
)
'''
.
format
(
org
=
ORG
,
course
=
COURSE
,
start
=
start
,
is_new
=
is_new
,
announcement
=
announcement
)
return
system
.
process_xml
(
start_xml
)
@patch
(
'xmodule.course_module.time.gmtime'
)
def
test_non_started_yet
(
self
,
gmtime_mock
):
descriptor
=
self
.
get_dummy_course
(
start
=
'2013-01-05T12:00'
)
gmtime_mock
.
return_value
=
NOW
assert
(
descriptor
.
is_new
==
True
)
assert
(
descriptor
.
days_until_start
==
4
)
@patch
(
'xmodule.course_module.time.gmtime'
)
def
test_already_started
(
self
,
gmtime_mock
):
def
test_sorting_score
(
self
,
gmtime_mock
):
gmtime_mock
.
return_value
=
NOW
dates
=
[(
'2012-10-01T12:00'
,
'2012-09-01T12:00'
),
# 0
(
'2012-12-01T12:00'
,
'2012-11-01T12:00'
),
# 1
(
'2013-02-01T12:00'
,
'2012-12-01T12:00'
),
# 2
(
'2013-02-01T12:00'
,
'2012-11-10T12:00'
),
# 3
(
'2013-02-01T12:00'
,
None
),
# 4
(
'2013-03-01T12:00'
,
None
),
# 5
(
'2013-04-01T12:00'
,
None
),
# 6
(
'2012-11-01T12:00'
,
None
),
# 7
(
'2012-09-01T12:00'
,
None
),
# 8
(
'1990-01-01T12:00'
,
None
),
# 9
(
'2013-01-02T12:00'
,
None
),
# 10
(
'2013-01-10T12:00'
,
'2012-12-31T12:00'
),
# 11
(
'2013-01-10T12:00'
,
'2013-01-01T12:00'
),
# 12
]
data
=
[]
for
i
,
d
in
enumerate
(
dates
):
descriptor
=
self
.
get_dummy_course
(
start
=
d
[
0
],
announcement
=
d
[
1
])
score
=
descriptor
.
sorting_score
data
.
append
((
score
,
i
))
result
=
[
d
[
1
]
for
d
in
sorted
(
data
)]
assert
(
result
==
[
12
,
11
,
2
,
3
,
1
,
0
,
6
,
5
,
4
,
10
,
7
,
8
,
9
])
descriptor
=
self
.
get_dummy_course
(
start
=
'2012-12-02T12:00'
)
assert
(
descriptor
.
is_new
==
False
)
assert
(
descriptor
.
days_until_start
<
0
)
@patch
(
'xmodule.course_module.time.gmtime'
)
def
test_is_new
_set
(
self
,
gmtime_mock
):
def
test_is_new
(
self
,
gmtime_mock
):
gmtime_mock
.
return_value
=
NOW
descriptor
=
self
.
get_dummy_course
(
start
=
'2012-12-02T12:00'
,
is_new
=
True
)
assert
(
descriptor
.
is_new
==
True
)
assert
(
descriptor
.
days_until_start
<
0
)
assert
(
descriptor
.
is_new
is
True
)
descriptor
=
self
.
get_dummy_course
(
start
=
'2013-02-02T12:00'
,
is_new
=
False
)
assert
(
descriptor
.
is_new
==
False
)
assert
(
descriptor
.
days_until_start
>
0
)
assert
(
descriptor
.
is_new
is
False
)
descriptor
=
self
.
get_dummy_course
(
start
=
'2013-02-02T12:00'
,
is_new
=
True
)
assert
(
descriptor
.
is_new
==
True
)
assert
(
descriptor
.
days_until_start
>
0
)
assert
(
descriptor
.
is_new
is
True
)
descriptor
=
self
.
get_dummy_course
(
start
=
'2013-01-15T12:00'
)
assert
(
descriptor
.
is_new
is
True
)
descriptor
=
self
.
get_dummy_course
(
start
=
'2013-03-00T12:00'
)
assert
(
descriptor
.
is_new
is
True
)
descriptor
=
self
.
get_dummy_course
(
start
=
'2012-10-15T12:00'
)
assert
(
descriptor
.
is_new
is
False
)
descriptor
=
self
.
get_dummy_course
(
start
=
'2012-12-31T12:00'
)
assert
(
descriptor
.
is_new
is
True
)
common/lib/xmodule/xmodule/tests/test_import.py
View file @
2291483c
...
...
@@ -339,19 +339,6 @@ class ImportTestCase(unittest.TestCase):
self
.
assertRaises
(
etree
.
XMLSyntaxError
,
system
.
process_xml
,
bad_xml
)
def
test_selfassessment_import
(
self
):
'''
Check to see if definition_from_xml in self_assessment_module.py
works properly. Pulls data from the self_assessment directory in the test data directory.
'''
modulestore
=
XMLModuleStore
(
DATA_DIR
,
course_dirs
=
[
'self_assessment'
])
sa_id
=
"edX/sa_test/2012_Fall"
location
=
Location
([
"i4x"
,
"edX"
,
"sa_test"
,
"selfassessment"
,
"SampleQuestion"
])
sa_sample
=
modulestore
.
get_instance
(
sa_id
,
location
)
#10 attempts is hard coded into SampleQuestion, which is the url_name of a selfassessment xml tag
self
.
assertEqual
(
sa_sample
.
metadata
[
'attempts'
],
'10'
)
def
test_graphicslidertool_import
(
self
):
'''
...
...
common/lib/xmodule/xmodule/tests/test_self_assessment.py
View file @
2291483c
...
...
@@ -4,6 +4,7 @@ import unittest
from
xmodule.self_assessment_module
import
SelfAssessmentModule
from
xmodule.modulestore
import
Location
from
lxml
import
etree
from
.
import
test_system
...
...
@@ -26,22 +27,37 @@ class SelfAssessmentTest(unittest.TestCase):
state
=
json
.
dumps
({
'student_answers'
:
[
"Answer 1"
,
"answer 2"
,
"answer 3"
],
'scores'
:
[
0
,
1
],
'hints'
:
[
'o hai'
],
'state'
:
SelfAssessmentModule
.
ASSESSING
,
'state'
:
SelfAssessmentModule
.
INITIAL
,
'attempts'
:
2
})
rubric
=
'''<rubric><rubric>
<category>
<description>Response Quality</description>
<option>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option>
</category>
</rubric></rubric>'''
prompt
=
etree
.
XML
(
"<prompt>Text</prompt>"
)
static_data
=
{
'max_attempts'
:
10
,
'rubric'
:
etree
.
XML
(
rubric
),
'prompt'
:
prompt
,
'max_score'
:
1
}
module
=
SelfAssessmentModule
(
test_system
,
self
.
location
,
self
.
definition
,
self
.
descriptor
,
stat
e
,
{}
,
metadata
=
self
.
metadata
)
stat
ic_data
,
state
,
metadata
=
self
.
metadata
)
self
.
assertEqual
(
module
.
get_score
()[
'score'
],
0
)
self
.
assertTrue
(
'answer 3'
in
module
.
get_html
())
self
.
assertFalse
(
'answer 2'
in
module
.
get_html
())
module
.
save_a
ssessment
({
'assessment'
:
'0'
}
)
self
.
assertEqual
(
module
.
state
,
module
.
REQUEST_HINT
)
module
.
save_a
nswer
({
'student_answer'
:
"I am an answer"
},
test_system
)
self
.
assertEqual
(
module
.
state
,
module
.
ASSESSING
)
module
.
save_hint
({
'hint'
:
'hint for ans 3'
})
module
.
save_assessment
({
'assessment'
:
'0'
},
test_system
)
self
.
assertEqual
(
module
.
state
,
module
.
POST_ASSESSMENT
)
module
.
save_hint
({
'hint'
:
'this is a hint'
},
test_system
)
self
.
assertEqual
(
module
.
state
,
module
.
DONE
)
d
=
module
.
reset
({})
...
...
@@ -49,6 +65,6 @@ class SelfAssessmentTest(unittest.TestCase):
self
.
assertEqual
(
module
.
state
,
module
.
INITIAL
)
# if we now assess as right, skip the REQUEST_HINT state
module
.
save_answer
({
'student_answer'
:
'answer 4'
})
module
.
save_assessment
({
'assessment'
:
'1'
})
module
.
save_answer
({
'student_answer'
:
'answer 4'
}
,
test_system
)
module
.
save_assessment
({
'assessment'
:
'1'
}
,
test_system
)
self
.
assertEqual
(
module
.
state
,
module
.
DONE
)
lms/djangoapps/courseware/access.py
View file @
2291483c
...
...
@@ -338,6 +338,10 @@ def course_beta_test_group_name(location):
"""
return
'beta_testers_{0}'
.
format
(
Location
(
location
)
.
course
)
# nosetests thinks that anything with _test_ in the name is a test.
# Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html)
course_beta_test_group_name
.
__test__
=
False
def
_course_instructor_group_name
(
location
):
"""
...
...
lms/djangoapps/courseware/courses.py
View file @
2291483c
...
...
@@ -64,6 +64,7 @@ def course_image_url(course):
path
=
course
.
metadata
[
'data_dir'
]
+
"/images/course_image.jpg"
return
try_staticfiles_lookup
(
path
)
def
find_file
(
fs
,
dirs
,
filename
):
"""
Looks for a filename in a list of dirs on a filesystem, in the specified order.
...
...
@@ -80,6 +81,7 @@ def find_file(fs, dirs, filename):
return
filepath
raise
ResourceNotFoundError
(
"Could not find {0}"
.
format
(
filename
))
def
get_course_about_section
(
course
,
section_key
):
"""
This returns the snippet of html to be rendered on the course about page,
...
...
@@ -234,4 +236,18 @@ def get_courses(user, domain=None):
courses
=
[
c
for
c
in
courses
if
has_access
(
user
,
c
,
'see_exists'
)]
courses
=
sorted
(
courses
,
key
=
lambda
course
:
course
.
number
)
return
courses
def
sort_by_announcement
(
courses
):
"""
Sorts a list of courses by their announcement date. If the date is
not available, sort them by their start date.
"""
# Sort courses by how far are they from they start day
key
=
lambda
course
:
course
.
sorting_score
courses
=
sorted
(
courses
,
key
=
key
)
return
courses
lms/djangoapps/courseware/views.py
View file @
2291483c
...
...
@@ -17,7 +17,8 @@ from django.views.decorators.cache import cache_control
from
courseware
import
grades
from
courseware.access
import
has_access
from
courseware.courses
import
(
get_courses
,
get_course_with_access
,
get_courses_by_university
)
from
courseware.courses
import
(
get_courses
,
get_course_with_access
,
get_courses_by_university
,
sort_by_announcement
)
import
courseware.tabs
as
tabs
from
courseware.models
import
StudentModuleCache
from
module_render
import
toc_for_course
,
get_module
,
get_instance_module
...
...
@@ -67,11 +68,8 @@ def courses(request):
'''
Render "find courses" page. The course selection work is done in courseware.courses.
'''
courses
=
get_courses
(
request
.
user
,
domain
=
request
.
META
.
get
(
'HTTP_HOST'
))
# Sort courses by how far are they from they start day
key
=
lambda
course
:
course
.
days_until_start
courses
=
sorted
(
courses
,
key
=
key
,
reverse
=
True
)
courses
=
get_courses
(
request
.
user
,
request
.
META
.
get
(
'HTTP_HOST'
))
courses
=
sort_by_announcement
(
courses
)
return
render_to_response
(
"courseware/courses.html"
,
{
'courses'
:
courses
})
...
...
@@ -438,10 +436,7 @@ def university_profile(request, org_id):
# Only grab courses for this org...
courses
=
get_courses_by_university
(
request
.
user
,
domain
=
request
.
META
.
get
(
'HTTP_HOST'
))[
org_id
]
# Sort courses by how far are they from they start day
key
=
lambda
course
:
course
.
days_until_start
courses
=
sorted
(
courses
,
key
=
key
,
reverse
=
True
)
courses
=
sort_by_announcement
(
courses
)
context
=
dict
(
courses
=
courses
,
org_id
=
org_id
)
template_file
=
"university_profile/{0}.html"
.
format
(
org_id
)
.
lower
()
...
...
lms/djangoapps/instructor/views.py
View file @
2291483c
...
...
@@ -111,6 +111,7 @@ def instructor_dashboard(request, course_id):
except
Group
.
DoesNotExist
:
group
=
Group
(
name
=
grpname
)
# create the group
group
.
save
()
return
group
def
get_beta_group
(
course
):
"""
...
...
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