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
420b0920
Commit
420b0920
authored
Jul 23, 2013
by
Adam
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #467 from edx/fix/adam/file-upload
Fix/adam/file upload
parents
86ee2bca
2b404622
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
245 additions
and
166 deletions
+245
-166
common/djangoapps/heartbeat/views.py
+3
-1
common/djangoapps/student/models.py
+30
-28
common/djangoapps/student/views.py
+4
-4
common/lib/capa/capa/capa_problem.py
+47
-21
common/lib/capa/capa/correctmap.py
+19
-15
common/lib/capa/capa/responsetypes.py
+16
-11
common/lib/capa/capa/tests/test_responsetypes.py
+11
-6
common/lib/capa/capa/xqueue_interface.py
+10
-9
common/lib/xmodule/xmodule/capa_module.py
+6
-2
common/lib/xmodule/xmodule/fields.py
+1
-0
common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
+3
-2
common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
+7
-3
common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
+4
-2
i18n/tests/test_extract.py
+15
-10
i18n/tests/test_generate.py
+8
-3
lms/djangoapps/certificates/management/commands/ungenerated_certs.py
+4
-4
lms/djangoapps/courseware/module_render.py
+57
-45
No files found.
common/djangoapps/heartbeat/views.py
View file @
420b0920
import
json
from
datetime
import
datetime
from
pytz
import
UTC
from
django.http
import
HttpResponse
from
xmodule.modulestore.django
import
modulestore
from
dogapi
import
dog_stats_api
@dog_stats_api.timed
(
'edxapp.heartbeat'
)
def
heartbeat
(
request
):
"""
Simple view that a loadbalancer can check to verify that the app is up
"""
output
=
{
'date'
:
datetime
.
now
()
.
isoformat
(),
'date'
:
datetime
.
now
(
UTC
)
.
isoformat
(),
'courses'
:
[
course
.
location
.
url
()
for
course
in
modulestore
()
.
get_courses
()],
}
return
HttpResponse
(
json
.
dumps
(
output
,
indent
=
4
))
common/djangoapps/student/models.py
View file @
420b0920
...
...
@@ -69,30 +69,33 @@ class UserProfile(models.Model):
location
=
models
.
CharField
(
blank
=
True
,
max_length
=
255
,
db_index
=
True
)
# Optional demographic data we started capturing from Fall 2012
this_year
=
datetime
.
now
()
.
year
this_year
=
datetime
.
now
(
UTC
)
.
year
VALID_YEARS
=
range
(
this_year
,
this_year
-
120
,
-
1
)
year_of_birth
=
models
.
IntegerField
(
blank
=
True
,
null
=
True
,
db_index
=
True
)
GENDER_CHOICES
=
((
'm'
,
'Male'
),
(
'f'
,
'Female'
),
(
'o'
,
'Other'
))
gender
=
models
.
CharField
(
blank
=
True
,
null
=
True
,
max_length
=
6
,
db_index
=
True
,
choices
=
GENDER_CHOICES
)
gender
=
models
.
CharField
(
blank
=
True
,
null
=
True
,
max_length
=
6
,
db_index
=
True
,
choices
=
GENDER_CHOICES
)
# [03/21/2013] removed these, but leaving comment since there'll still be
# p_se and p_oth in the existing data in db.
# ('p_se', 'Doctorate in science or engineering'),
# ('p_oth', 'Doctorate in another field'),
LEVEL_OF_EDUCATION_CHOICES
=
((
'p'
,
'Doctorate'
),
(
'm'
,
"Master's or professional degree"
),
(
'b'
,
"Bachelor's degree"
),
(
'a'
,
"Associate's degree"
),
(
'hs'
,
"Secondary/high school"
),
(
'jhs'
,
"Junior secondary/junior high/middle school"
),
(
'el'
,
"Elementary/primary school"
),
(
'none'
,
"None"
),
(
'other'
,
"Other"
))
LEVEL_OF_EDUCATION_CHOICES
=
(
(
'p'
,
'Doctorate'
),
(
'm'
,
"Master's or professional degree"
),
(
'b'
,
"Bachelor's degree"
),
(
'a'
,
"Associate's degree"
),
(
'hs'
,
"Secondary/high school"
),
(
'jhs'
,
"Junior secondary/junior high/middle school"
),
(
'el'
,
"Elementary/primary school"
),
(
'none'
,
"None"
),
(
'other'
,
"Other"
)
)
level_of_education
=
models
.
CharField
(
blank
=
True
,
null
=
True
,
max_length
=
6
,
db_index
=
True
,
choices
=
LEVEL_OF_EDUCATION_CHOICES
)
blank
=
True
,
null
=
True
,
max_length
=
6
,
db_index
=
True
,
choices
=
LEVEL_OF_EDUCATION_CHOICES
)
mailing_address
=
models
.
TextField
(
blank
=
True
,
null
=
True
)
goals
=
models
.
TextField
(
blank
=
True
,
null
=
True
)
allow_certificate
=
models
.
BooleanField
(
default
=
1
)
...
...
@@ -307,18 +310,18 @@ class TestCenterUserForm(ModelForm):
ACCOMMODATION_REJECTED_CODE
=
'NONE'
ACCOMMODATION_CODES
=
(
(
ACCOMMODATION_REJECTED_CODE
,
'No Accommodation Granted'
),
(
'EQPMNT'
,
'Equipment'
),
(
'ET12ET'
,
'Extra Time - 1/2 Exam Time'
),
(
'ET30MN'
,
'Extra Time - 30 Minutes'
),
(
'ETDBTM'
,
'Extra Time - Double Time'
),
(
'SEPRMM'
,
'Separate Room'
),
(
'SRREAD'
,
'Separate Room and Reader'
),
(
'SRRERC'
,
'Separate Room and Reader/Recorder'
),
(
'SRRECR'
,
'Separate Room and Recorder'
),
(
'SRSEAN'
,
'Separate Room and Service Animal'
),
(
'SRSGNR'
,
'Separate Room and Sign Language Interpreter'
),
)
(
ACCOMMODATION_REJECTED_CODE
,
'No Accommodation Granted'
),
(
'EQPMNT'
,
'Equipment'
),
(
'ET12ET'
,
'Extra Time - 1/2 Exam Time'
),
(
'ET30MN'
,
'Extra Time - 30 Minutes'
),
(
'ETDBTM'
,
'Extra Time - Double Time'
),
(
'SEPRMM'
,
'Separate Room'
),
(
'SRREAD'
,
'Separate Room and Reader'
),
(
'SRRERC'
,
'Separate Room and Reader/Recorder'
),
(
'SRRECR'
,
'Separate Room and Recorder'
),
(
'SRSEAN'
,
'Separate Room and Service Animal'
),
(
'SRSGNR'
,
'Separate Room and Sign Language Interpreter'
),
)
ACCOMMODATION_CODE_DICT
=
{
code
:
name
for
(
code
,
name
)
in
ACCOMMODATION_CODES
}
...
...
@@ -572,7 +575,6 @@ class TestCenterRegistrationForm(ModelForm):
return
code
def
get_testcenter_registration
(
user
,
course_id
,
exam_series_code
):
try
:
tcu
=
TestCenterUser
.
objects
.
get
(
user
=
user
)
...
...
common/djangoapps/student/views.py
View file @
420b0920
...
...
@@ -111,9 +111,9 @@ def get_date_for_press(publish_date):
# strip off extra months, and just use the first:
date
=
re
.
sub
(
multimonth_pattern
,
", "
,
publish_date
)
if
re
.
search
(
day_pattern
,
date
):
date
=
datetime
.
datetime
.
strptime
(
date
,
"
%
B
%
d,
%
Y"
)
date
=
datetime
.
datetime
.
strptime
(
date
,
"
%
B
%
d,
%
Y"
)
.
replace
(
tzinfo
=
UTC
)
else
:
date
=
datetime
.
datetime
.
strptime
(
date
,
"
%
B,
%
Y"
)
date
=
datetime
.
datetime
.
strptime
(
date
,
"
%
B,
%
Y"
)
.
replace
(
tzinfo
=
UTC
)
return
date
...
...
@@ -1100,7 +1100,7 @@ def confirm_email_change(request, key):
meta
=
up
.
get_meta
()
if
'old_emails'
not
in
meta
:
meta
[
'old_emails'
]
=
[]
meta
[
'old_emails'
]
.
append
([
user
.
email
,
datetime
.
datetime
.
now
()
.
isoformat
()])
meta
[
'old_emails'
]
.
append
([
user
.
email
,
datetime
.
datetime
.
now
(
UTC
)
.
isoformat
()])
up
.
set_meta
(
meta
)
up
.
save
()
# Send it to the old email...
...
...
@@ -1198,7 +1198,7 @@ def accept_name_change_by_id(id):
meta
=
up
.
get_meta
()
if
'old_names'
not
in
meta
:
meta
[
'old_names'
]
=
[]
meta
[
'old_names'
]
.
append
([
up
.
name
,
pnc
.
rationale
,
datetime
.
datetime
.
now
()
.
isoformat
()])
meta
[
'old_names'
]
.
append
([
up
.
name
,
pnc
.
rationale
,
datetime
.
datetime
.
now
(
UTC
)
.
isoformat
()])
up
.
set_meta
(
meta
)
up
.
name
=
pnc
.
new_name
...
...
common/lib/capa/capa/capa_problem.py
View file @
420b0920
...
...
@@ -32,6 +32,8 @@ import capa.xqueue_interface as xqueue_interface
import
capa.responsetypes
as
responsetypes
from
capa.safe_exec
import
safe_exec
from
pytz
import
UTC
# dict of tagname, Response Class -- this should come from auto-registering
response_tag_dict
=
dict
([(
x
.
response_tag
,
x
)
for
x
in
responsetypes
.
__all__
])
...
...
@@ -42,13 +44,22 @@ solution_tags = ['solution']
response_properties
=
[
"codeparam"
,
"responseparam"
,
"answer"
,
"openendedparam"
]
# special problem tags which should be turned into innocuous HTML
html_transforms
=
{
'problem'
:
{
'tag'
:
'div'
},
'text'
:
{
'tag'
:
'span'
},
'math'
:
{
'tag'
:
'span'
},
}
html_transforms
=
{
'problem'
:
{
'tag'
:
'div'
},
'text'
:
{
'tag'
:
'span'
},
'math'
:
{
'tag'
:
'span'
},
}
# These should be removed from HTML output, including all subelements
html_problem_semantics
=
[
"codeparam"
,
"responseparam"
,
"answer"
,
"script"
,
"hintgroup"
,
"openendedparam"
,
"openendedrubric"
]
html_problem_semantics
=
[
"codeparam"
,
"responseparam"
,
"answer"
,
"script"
,
"hintgroup"
,
"openendedparam"
,
"openendedrubric"
]
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -242,11 +253,15 @@ class LoncapaProblem(object):
return
None
# Get a list of timestamps of all queueing requests, then convert it to a DateTime object
queuetime_strs
=
[
self
.
correct_map
.
get_queuetime_str
(
answer_id
)
for
answer_id
in
self
.
correct_map
if
self
.
correct_map
.
is_queued
(
answer_id
)]
queuetimes
=
[
datetime
.
strptime
(
qt_str
,
xqueue_interface
.
dateformat
)
for
qt_str
in
queuetime_strs
]
queuetime_strs
=
[
self
.
correct_map
.
get_queuetime_str
(
answer_id
)
for
answer_id
in
self
.
correct_map
if
self
.
correct_map
.
is_queued
(
answer_id
)
]
queuetimes
=
[
datetime
.
strptime
(
qt_str
,
xqueue_interface
.
dateformat
)
.
replace
(
tzinfo
=
UTC
)
for
qt_str
in
queuetime_strs
]
return
max
(
queuetimes
)
...
...
@@ -404,10 +419,16 @@ class LoncapaProblem(object):
# open using ModuleSystem OSFS filestore
ifp
=
self
.
system
.
filestore
.
open
(
filename
)
except
Exception
as
err
:
log
.
warning
(
'Error
%
s in problem xml include:
%
s'
%
(
err
,
etree
.
tostring
(
inc
,
pretty_print
=
True
)))
log
.
warning
(
'Cannot find file
%
s in
%
s'
%
(
filename
,
self
.
system
.
filestore
))
log
.
warning
(
'Error
%
s in problem xml include:
%
s'
%
(
err
,
etree
.
tostring
(
inc
,
pretty_print
=
True
)
)
)
log
.
warning
(
'Cannot find file
%
s in
%
s'
%
(
filename
,
self
.
system
.
filestore
)
)
# if debugging, don't fail - just log error
# TODO (vshnayder): need real error handling, display to users
if
not
self
.
system
.
get
(
'DEBUG'
):
...
...
@@ -418,8 +439,11 @@ class LoncapaProblem(object):
# read in and convert to XML
incxml
=
etree
.
XML
(
ifp
.
read
())
except
Exception
as
err
:
log
.
warning
(
'Error
%
s in problem xml include:
%
s'
%
(
err
,
etree
.
tostring
(
inc
,
pretty_print
=
True
)))
log
.
warning
(
'Error
%
s in problem xml include:
%
s'
%
(
err
,
etree
.
tostring
(
inc
,
pretty_print
=
True
)
)
)
log
.
warning
(
'Cannot parse XML in
%
s'
%
(
filename
))
# if debugging, don't fail - just log error
# TODO (vshnayder): same as above
...
...
@@ -579,8 +603,9 @@ class LoncapaProblem(object):
# let each Response render itself
if
problemtree
in
self
.
responders
:
overall_msg
=
self
.
correct_map
.
get_overall_message
()
return
self
.
responders
[
problemtree
]
.
render_html
(
self
.
_extract_html
,
response_msg
=
overall_msg
)
return
self
.
responders
[
problemtree
]
.
render_html
(
self
.
_extract_html
,
response_msg
=
overall_msg
)
# let each custom renderer render itself:
if
problemtree
.
tag
in
customrender
.
registry
.
registered_tags
():
...
...
@@ -628,9 +653,10 @@ class LoncapaProblem(object):
answer_id
=
1
input_tags
=
inputtypes
.
registry
.
registered_tags
()
inputfields
=
tree
.
xpath
(
"|"
.
join
([
'//'
+
response
.
tag
+
'[@id=$id]//'
+
x
for
x
in
(
input_tags
+
solution_tags
)]),
id
=
response_id_str
)
inputfields
=
tree
.
xpath
(
"|"
.
join
([
'//'
+
response
.
tag
+
'[@id=$id]//'
+
x
for
x
in
(
input_tags
+
solution_tags
)]),
id
=
response_id_str
)
# assign one answer_id for each input type or solution type
for
entry
in
inputfields
:
...
...
common/lib/capa/capa/correctmap.py
View file @
420b0920
...
...
@@ -37,23 +37,27 @@ class CorrectMap(object):
return
self
.
cmap
.
__iter__
()
# See the documentation for 'set_dict' for the use of kwargs
def
set
(
self
,
answer_id
=
None
,
correctness
=
None
,
npoints
=
None
,
msg
=
''
,
hint
=
''
,
hintmode
=
None
,
queuestate
=
None
,
**
kwargs
):
def
set
(
self
,
answer_id
=
None
,
correctness
=
None
,
npoints
=
None
,
msg
=
''
,
hint
=
''
,
hintmode
=
None
,
queuestate
=
None
,
**
kwargs
):
if
answer_id
is
not
None
:
self
.
cmap
[
str
(
answer_id
)]
=
{
'correctness'
:
correctness
,
'npoints'
:
npoints
,
'msg'
:
msg
,
'hint'
:
hint
,
'hintmode'
:
hintmode
,
'queuestate'
:
queuestate
,
}
self
.
cmap
[
str
(
answer_id
)]
=
{
'correctness'
:
correctness
,
'npoints'
:
npoints
,
'msg'
:
msg
,
'hint'
:
hint
,
'hintmode'
:
hintmode
,
'queuestate'
:
queuestate
,
}
def
__repr__
(
self
):
return
repr
(
self
.
cmap
)
...
...
common/lib/capa/capa/responsetypes.py
View file @
420b0920
...
...
@@ -33,6 +33,7 @@ from shapely.geometry import Point, MultiPoint
from
calc
import
evaluator
,
UndefinedVariable
from
.
import
correctmap
from
datetime
import
datetime
from
pytz
import
UTC
from
.util
import
*
from
lxml
import
etree
from
lxml.html.soupparser
import
fromstring
as
fromstring_bs
# uses Beautiful Soup!!! FIXME?
...
...
@@ -1365,9 +1366,11 @@ class CodeResponse(LoncapaResponse):
# Note that submission can be a file
submission
=
student_answers
[
self
.
answer_id
]
except
Exception
as
err
:
log
.
error
(
'Error in CodeResponse
%
s: cannot get student answer for
%
s;'
' student_answers=
%
s'
%
(
err
,
self
.
answer_id
,
convert_files_to_filenames
(
student_answers
)))
log
.
error
(
'Error in CodeResponse
%
s: cannot get student answer for
%
s;'
' student_answers=
%
s'
%
(
err
,
self
.
answer_id
,
convert_files_to_filenames
(
student_answers
))
)
raise
Exception
(
err
)
# We do not support xqueue within Studio.
...
...
@@ -1381,19 +1384,20 @@ class CodeResponse(LoncapaResponse):
#------------------------------------------------------------
qinterface
=
self
.
system
.
xqueue
[
'interface'
]
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
qtime
=
datetime
.
strftime
(
datetime
.
now
(
UTC
),
xqueue_interface
.
dateformat
)
anonymous_student_id
=
self
.
system
.
anonymous_student_id
# Generate header
queuekey
=
xqueue_interface
.
make_hashkey
(
str
(
self
.
system
.
seed
)
+
qtime
+
anonymous_student_id
+
self
.
answer_id
)
queuekey
=
xqueue_interface
.
make_hashkey
(
str
(
self
.
system
.
seed
)
+
qtime
+
anonymous_student_id
+
self
.
answer_id
)
callback_url
=
self
.
system
.
xqueue
[
'construct_callback'
]()
xheader
=
xqueue_interface
.
make_xheader
(
lms_callback_url
=
callback_url
,
lms_key
=
queuekey
,
queue_name
=
self
.
queue_name
)
queue_name
=
self
.
queue_name
)
# Generate body
if
is_list_of_files
(
submission
):
...
...
@@ -1406,9 +1410,10 @@ class CodeResponse(LoncapaResponse):
# Metadata related to the student submission revealed to the external
# grader
student_info
=
{
'anonymous_student_id'
:
anonymous_student_id
,
'submission_time'
:
qtime
,
}
student_info
=
{
'anonymous_student_id'
:
anonymous_student_id
,
'submission_time'
:
qtime
,
}
contents
.
update
({
'student_info'
:
json
.
dumps
(
student_info
)})
# Submit request. When successful, 'msg' is the prior length of the
...
...
common/lib/capa/capa/tests/test_responsetypes.py
View file @
420b0920
...
...
@@ -18,6 +18,8 @@ from capa.correctmap import CorrectMap
from
capa.util
import
convert_files_to_filenames
from
capa.xqueue_interface
import
dateformat
from
pytz
import
UTC
class
ResponseTest
(
unittest
.
TestCase
):
""" Base class for tests of capa responses."""
...
...
@@ -333,8 +335,9 @@ class SymbolicResponseTest(ResponseTest):
correct_map
=
problem
.
grade_answers
(
input_dict
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_1'
),
expected_correctness
)
self
.
assertEqual
(
correct_map
.
get_correctness
(
'1_2_1'
),
expected_correctness
)
class
OptionResponseTest
(
ResponseTest
):
...
...
@@ -702,7 +705,7 @@ class CodeResponseTest(ResponseTest):
# Now we queue the LCP
cmap
=
CorrectMap
()
for
i
,
answer_id
in
enumerate
(
answer_ids
):
queuestate
=
CodeResponseTest
.
make_queuestate
(
i
,
datetime
.
now
())
queuestate
=
CodeResponseTest
.
make_queuestate
(
i
,
datetime
.
now
(
UTC
))
cmap
.
update
(
CorrectMap
(
answer_id
=
answer_ids
[
i
],
queuestate
=
queuestate
))
self
.
problem
.
correct_map
.
update
(
cmap
)
...
...
@@ -718,7 +721,7 @@ class CodeResponseTest(ResponseTest):
old_cmap
=
CorrectMap
()
for
i
,
answer_id
in
enumerate
(
answer_ids
):
queuekey
=
1000
+
i
queuestate
=
CodeResponseTest
.
make_queuestate
(
queuekey
,
datetime
.
now
())
queuestate
=
CodeResponseTest
.
make_queuestate
(
queuekey
,
datetime
.
now
(
UTC
))
old_cmap
.
update
(
CorrectMap
(
answer_id
=
answer_ids
[
i
],
queuestate
=
queuestate
))
# Message format common to external graders
...
...
@@ -778,13 +781,15 @@ class CodeResponseTest(ResponseTest):
cmap
=
CorrectMap
()
for
i
,
answer_id
in
enumerate
(
answer_ids
):
queuekey
=
1000
+
i
latest_timestamp
=
datetime
.
now
()
latest_timestamp
=
datetime
.
now
(
UTC
)
queuestate
=
CodeResponseTest
.
make_queuestate
(
queuekey
,
latest_timestamp
)
cmap
.
update
(
CorrectMap
(
answer_id
=
answer_id
,
queuestate
=
queuestate
))
self
.
problem
.
correct_map
.
update
(
cmap
)
# Queue state only tracks up to second
latest_timestamp
=
datetime
.
strptime
(
datetime
.
strftime
(
latest_timestamp
,
dateformat
),
dateformat
)
latest_timestamp
=
datetime
.
strptime
(
datetime
.
strftime
(
latest_timestamp
,
dateformat
),
dateformat
)
.
replace
(
tzinfo
=
UTC
)
self
.
assertEquals
(
self
.
problem
.
get_recentmost_queuetime
(),
latest_timestamp
)
...
...
common/lib/capa/capa/xqueue_interface.py
View file @
420b0920
...
...
@@ -30,9 +30,11 @@ def make_xheader(lms_callback_url, lms_key, queue_name):
'queue_name': designate a specific queue within xqueue server, e.g. 'MITx-6.00x' (string)
}
"""
return
json
.
dumps
({
'lms_callback_url'
:
lms_callback_url
,
'lms_key'
:
lms_key
,
'queue_name'
:
queue_name
})
return
json
.
dumps
({
'lms_callback_url'
:
lms_callback_url
,
'lms_key'
:
lms_key
,
'queue_name'
:
queue_name
})
def
parse_xreply
(
xreply
):
...
...
@@ -60,7 +62,7 @@ class XQueueInterface(object):
'''
def
__init__
(
self
,
url
,
django_auth
,
requests_auth
=
None
):
self
.
url
=
url
self
.
url
=
url
self
.
auth
=
django_auth
self
.
session
=
requests
.
session
(
auth
=
requests_auth
)
...
...
@@ -95,13 +97,13 @@ class XQueueInterface(object):
return
(
error
,
msg
)
def
_login
(
self
):
payload
=
{
'username'
:
self
.
auth
[
'username'
],
'password'
:
self
.
auth
[
'password'
]}
payload
=
{
'username'
:
self
.
auth
[
'username'
],
'password'
:
self
.
auth
[
'password'
]
}
return
self
.
_http_post
(
self
.
url
+
'/xqueue/login/'
,
payload
)
def
_send_to_queue
(
self
,
header
,
body
,
files_to_upload
):
payload
=
{
'xqueue_header'
:
header
,
'xqueue_body'
:
body
}
...
...
@@ -112,7 +114,6 @@ class XQueueInterface(object):
return
self
.
_http_post
(
self
.
url
+
'/xqueue/submit/'
,
payload
,
files
=
files
)
def
_http_post
(
self
,
url
,
data
,
files
=
None
):
try
:
r
=
self
.
session
.
post
(
url
,
data
=
data
,
files
=
files
)
...
...
common/lib/xmodule/xmodule/capa_module.py
View file @
420b0920
...
...
@@ -1126,8 +1126,12 @@ class CapaDescriptor(CapaFields, RawDescriptor):
mako_template
=
"widgets/problem-edit.html"
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/problem/edit.coffee'
)]}
js_module_name
=
"MarkdownEditingDescriptor"
css
=
{
'scss'
:
[
resource_string
(
__name__
,
'css/editor/edit.scss'
),
resource_string
(
__name__
,
'css/problem/edit.scss'
)]}
css
=
{
'scss'
:
[
resource_string
(
__name__
,
'css/editor/edit.scss'
),
resource_string
(
__name__
,
'css/problem/edit.scss'
)
]
}
# Capa modules have some additional metadata:
# TODO (vshnayder): do problems have any other metadata? Do they
...
...
common/lib/xmodule/xmodule/fields.py
View file @
420b0920
...
...
@@ -80,6 +80,7 @@ class Date(ModelType):
TIMEDELTA_REGEX
=
re
.
compile
(
r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$'
)
class
Timedelta
(
ModelType
):
def
from_json
(
self
,
time_str
):
"""
...
...
common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
View file @
420b0920
...
...
@@ -19,6 +19,7 @@ import openendedchild
from
numpy
import
median
from
datetime
import
datetime
from
pytz
import
UTC
from
.combined_open_ended_rubric
import
CombinedOpenEndedRubric
...
...
@@ -170,7 +171,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
if
xqueue
is
None
:
return
{
'success'
:
False
,
'msg'
:
"Couldn't submit feedback."
}
qinterface
=
xqueue
[
'interface'
]
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
qtime
=
datetime
.
strftime
(
datetime
.
now
(
UTC
),
xqueue_interface
.
dateformat
)
anonymous_student_id
=
system
.
anonymous_student_id
queuekey
=
xqueue_interface
.
make_hashkey
(
str
(
system
.
seed
)
+
qtime
+
anonymous_student_id
+
...
...
@@ -224,7 +225,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
if
xqueue
is
None
:
return
False
qinterface
=
xqueue
[
'interface'
]
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
qtime
=
datetime
.
strftime
(
datetime
.
now
(
UTC
),
xqueue_interface
.
dateformat
)
anonymous_student_id
=
system
.
anonymous_student_id
...
...
common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
View file @
420b0920
...
...
@@ -5,6 +5,7 @@ import re
import
open_ended_image_submission
from
xmodule.progress
import
Progress
import
capa.xqueue_interface
as
xqueue_interface
from
capa.util
import
*
from
.peer_grading_service
import
PeerGradingService
,
MockPeerGradingService
import
controller_query_service
...
...
@@ -334,12 +335,15 @@ class OpenEndedChild(object):
log
.
exception
(
"Could not create image and check it."
)
if
image_ok
:
image_key
=
image_data
.
name
+
datetime
.
now
()
.
strftime
(
"
%
Y
%
m
%
d
%
H
%
M
%
S"
)
image_key
=
image_data
.
name
+
datetime
.
now
(
UTC
)
.
strftime
(
xqueue_interface
.
dateformat
)
try
:
image_data
.
seek
(
0
)
success
,
s3_public_url
=
open_ended_image_submission
.
upload_to_s3
(
image_data
,
image_key
,
self
.
s3_interface
)
success
,
s3_public_url
=
open_ended_image_submission
.
upload_to_s3
(
image_data
,
image_key
,
self
.
s3_interface
)
except
:
log
.
exception
(
"Could not upload image to S3."
)
...
...
common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
View file @
420b0920
...
...
@@ -14,6 +14,7 @@ from xmodule.modulestore import Location
from
lxml
import
etree
import
capa.xqueue_interface
as
xqueue_interface
from
datetime
import
datetime
from
pytz
import
UTC
import
logging
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -212,7 +213,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'submission_id'
:
'1'
,
'grader_id'
:
'1'
,
'score'
:
3
}
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
qtime
=
datetime
.
strftime
(
datetime
.
now
(
UTC
),
xqueue_interface
.
dateformat
)
student_info
=
{
'anonymous_student_id'
:
self
.
test_system
.
anonymous_student_id
,
'submission_time'
:
qtime
}
contents
=
{
...
...
@@ -233,7 +234,7 @@ class OpenEndedModuleTest(unittest.TestCase):
def
test_send_to_grader
(
self
):
submission
=
"This is a student submission"
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
qtime
=
datetime
.
strftime
(
datetime
.
now
(
UTC
),
xqueue_interface
.
dateformat
)
student_info
=
{
'anonymous_student_id'
:
self
.
test_system
.
anonymous_student_id
,
'submission_time'
:
qtime
}
contents
=
self
.
openendedmodule
.
payload
.
copy
()
...
...
@@ -632,6 +633,7 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
module
.
handle_ajax
(
"reset"
,
{})
self
.
assertEqual
(
module
.
state
,
"initial"
)
class
OpenEndedModuleXmlAttemptTest
(
unittest
.
TestCase
,
DummyModulestore
):
"""
Test if student is able to reset the problem
...
...
i18n/tests/test_extract.py
View file @
420b0920
import
os
,
polib
import
os
import
polib
from
unittest
import
TestCase
from
nose.plugins.skip
import
SkipTest
from
datetime
import
datetime
,
timedelta
from
pytz
import
UTC
import
extract
from
config
import
CONFIGURATION
...
...
@@ -9,6 +11,7 @@ from config import CONFIGURATION
# Make sure setup runs only once
SETUP_HAS_RUN
=
False
class
TestExtract
(
TestCase
):
"""
Tests functionality of i18n/extract.py
...
...
@@ -19,20 +22,20 @@ class TestExtract(TestCase):
# Skip this test because it takes too long (>1 minute)
# TODO: figure out how to declare a "long-running" test suite
# and add this test to it.
raise
SkipTest
()
raise
SkipTest
()
global
SETUP_HAS_RUN
# Subtract 1 second to help comparisons with file-modify time succeed,
# since os.path.getmtime() is not millisecond-accurate
self
.
start_time
=
datetime
.
now
()
-
timedelta
(
seconds
=
1
)
self
.
start_time
=
datetime
.
now
(
UTC
)
-
timedelta
(
seconds
=
1
)
super
(
TestExtract
,
self
)
.
setUp
()
if
not
SETUP_HAS_RUN
:
# Run extraction script. Warning, this takes 1 minute or more
extract
.
main
()
SETUP_HAS_RUN
=
True
def
get_files
(
self
):
def
get_files
(
self
):
"""
This is a generator.
Returns the fully expanded filenames for all extracted files
...
...
@@ -65,19 +68,21 @@ class TestExtract(TestCase):
entry2
.
msgid
=
"This is not a keystring"
self
.
assertTrue
(
extract
.
is_key_string
(
entry1
.
msgid
))
self
.
assertFalse
(
extract
.
is_key_string
(
entry2
.
msgid
))
def
test_headers
(
self
):
"""Verify all headers have been modified"""
for
path
in
self
.
get_files
():
po
=
polib
.
pofile
(
path
)
header
=
po
.
header
self
.
assertEqual
(
header
.
find
(
'edX translation file'
),
0
,
msg
=
'Missing header in
%
s:
\n
"
%
s"'
%
\
(
os
.
path
.
basename
(
path
),
header
))
self
.
assertEqual
(
header
.
find
(
'edX translation file'
),
0
,
msg
=
'Missing header in
%
s:
\n
"
%
s"'
%
(
os
.
path
.
basename
(
path
),
header
)
)
def
test_metadata
(
self
):
"""Verify all metadata has been modified"""
for
path
in
self
.
get_files
():
for
path
in
self
.
get_files
():
po
=
polib
.
pofile
(
path
)
metadata
=
po
.
metadata
value
=
metadata
[
'Report-Msgid-Bugs-To'
]
...
...
i18n/tests/test_generate.py
View file @
420b0920
import
os
,
string
,
random
,
re
import
os
import
string
import
random
import
re
from
polib
import
pofile
from
unittest
import
TestCase
from
datetime
import
datetime
,
timedelta
from
pytz
import
UTC
import
generate
from
config
import
CONFIGURATION
class
TestGenerate
(
TestCase
):
"""
Tests functionality of i18n/generate.py
...
...
@@ -15,7 +20,7 @@ class TestGenerate(TestCase):
def
setUp
(
self
):
# Subtract 1 second to help comparisons with file-modify time succeed,
# since os.path.getmtime() is not millisecond-accurate
self
.
start_time
=
datetime
.
now
()
-
timedelta
(
seconds
=
1
)
self
.
start_time
=
datetime
.
now
(
UTC
)
-
timedelta
(
seconds
=
1
)
def
test_merge
(
self
):
"""
...
...
@@ -49,7 +54,7 @@ class TestGenerate(TestCase):
"""
This is invoked by test_main to ensure that it runs after
calling generate.main().
There should be exactly three merge comment headers
in our merged .po file. This counts them to be sure.
A merge comment looks like this:
...
...
lms/djangoapps/certificates/management/commands/ungenerated_certs.py
View file @
420b0920
...
...
@@ -8,6 +8,7 @@ from xmodule.course_module import CourseDescriptor
from
xmodule.modulestore.django
import
modulestore
from
certificates.models
import
CertificateStatuses
import
datetime
from
pytz
import
UTC
class
Command
(
BaseCommand
):
...
...
@@ -41,7 +42,6 @@ class Command(BaseCommand):
'whose entry in the certificate table matches STATUS. '
'STATUS can be generating, unavailable, deleted, error '
'or notpassing.'
),
)
def
handle
(
self
,
*
args
,
**
options
):
...
...
@@ -83,20 +83,20 @@ class Command(BaseCommand):
xq
=
XQueueCertInterface
()
total
=
enrolled_students
.
count
()
count
=
0
start
=
datetime
.
datetime
.
now
()
start
=
datetime
.
datetime
.
now
(
UTC
)
for
student
in
enrolled_students
:
count
+=
1
if
count
%
STATUS_INTERVAL
==
0
:
# Print a status update with an approximation of
# how much time is left based on how long the last
# interval took
diff
=
datetime
.
datetime
.
now
()
-
start
diff
=
datetime
.
datetime
.
now
(
UTC
)
-
start
timeleft
=
diff
*
(
total
-
count
)
/
STATUS_INTERVAL
hours
,
remainder
=
divmod
(
timeleft
.
seconds
,
3600
)
minutes
,
seconds
=
divmod
(
remainder
,
60
)
print
"{0}/{1} completed ~{2:02}:{3:02}m remaining"
.
format
(
count
,
total
,
hours
,
minutes
)
start
=
datetime
.
datetime
.
now
()
start
=
datetime
.
datetime
.
now
(
UTC
)
if
certificate_status_for_student
(
student
,
course_id
)[
'status'
]
in
valid_statuses
:
...
...
lms/djangoapps/courseware/module_render.py
View file @
420b0920
...
...
@@ -213,22 +213,28 @@ def get_module_for_descriptor_internal(user, descriptor, model_data_cache, cours
return
None
# Setup system context for module instance
ajax_url
=
reverse
(
'modx_dispatch'
,
kwargs
=
dict
(
course_id
=
course_id
,
location
=
descriptor
.
location
.
url
(),
dispatch
=
''
),
)
ajax_url
=
reverse
(
'modx_dispatch'
,
kwargs
=
dict
(
course_id
=
course_id
,
location
=
descriptor
.
location
.
url
(),
dispatch
=
''
),
)
# Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
ajax_url
=
ajax_url
.
rstrip
(
'/'
)
def
make_xqueue_callback
(
dispatch
=
'score_update'
):
# Fully qualified callback URL for external queueing system
relative_xqueue_callback_url
=
reverse
(
'xqueue_callback'
,
kwargs
=
dict
(
course_id
=
course_id
,
userid
=
str
(
user
.
id
),
mod_id
=
descriptor
.
location
.
url
(),
dispatch
=
dispatch
),
)
relative_xqueue_callback_url
=
reverse
(
'xqueue_callback'
,
kwargs
=
dict
(
course_id
=
course_id
,
userid
=
str
(
user
.
id
),
mod_id
=
descriptor
.
location
.
url
(),
dispatch
=
dispatch
),
)
return
xqueue_callback_url_prefix
+
relative_xqueue_callback_url
# Default queuename is course-specific and is derived from the course that
...
...
@@ -313,10 +319,12 @@ def get_module_for_descriptor_internal(user, descriptor, model_data_cache, cours
score_bucket
=
get_score_bucket
(
student_module
.
grade
,
student_module
.
max_grade
)
org
,
course_num
,
run
=
course_id
.
split
(
"/"
)
tags
=
[
"org:{0}"
.
format
(
org
),
"course:{0}"
.
format
(
course_num
),
"run:{0}"
.
format
(
run
),
"score_bucket:{0}"
.
format
(
score_bucket
)]
tags
=
[
"org:{0}"
.
format
(
org
),
"course:{0}"
.
format
(
course_num
),
"run:{0}"
.
format
(
run
),
"score_bucket:{0}"
.
format
(
score_bucket
)
]
if
grade_bucket_type
is
not
None
:
tags
.
append
(
'type:
%
s'
%
grade_bucket_type
)
...
...
@@ -326,38 +334,41 @@ def get_module_for_descriptor_internal(user, descriptor, model_data_cache, cours
# TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory
# that the xml was loaded from
system
=
ModuleSystem
(
track_function
=
track_function
,
render_template
=
render_to_string
,
ajax_url
=
ajax_url
,
xqueue
=
xqueue
,
# TODO (cpennington): Figure out how to share info between systems
filestore
=
descriptor
.
system
.
resources_fs
,
get_module
=
inner_get_module
,
user
=
user
,
# TODO (cpennington): This should be removed when all html from
# a module is coming through get_html and is therefore covered
# by the replace_static_urls code below
replace_urls
=
partial
(
static_replace
.
replace_static_urls
,
data_directory
=
getattr
(
descriptor
,
'data_dir'
,
None
),
course_namespace
=
descriptor
.
location
.
_replace
(
category
=
None
,
name
=
None
),
),
node_path
=
settings
.
NODE_PATH
,
xblock_model_data
=
xblock_model_data
,
publish
=
publish
,
anonymous_student_id
=
unique_id_for_user
(
user
),
course_id
=
course_id
,
open_ended_grading_interface
=
open_ended_grading_interface
,
s3_interface
=
s3_interface
,
cache
=
cache
,
can_execute_unsafe_code
=
(
lambda
:
can_execute_unsafe_code
(
course_id
)),
)
system
=
ModuleSystem
(
track_function
=
track_function
,
render_template
=
render_to_string
,
ajax_url
=
ajax_url
,
xqueue
=
xqueue
,
# TODO (cpennington): Figure out how to share info between systems
filestore
=
descriptor
.
system
.
resources_fs
,
get_module
=
inner_get_module
,
user
=
user
,
# TODO (cpennington): This should be removed when all html from
# a module is coming through get_html and is therefore covered
# by the replace_static_urls code below
replace_urls
=
partial
(
static_replace
.
replace_static_urls
,
data_directory
=
getattr
(
descriptor
,
'data_dir'
,
None
),
course_namespace
=
descriptor
.
location
.
_replace
(
category
=
None
,
name
=
None
),
),
node_path
=
settings
.
NODE_PATH
,
xblock_model_data
=
xblock_model_data
,
publish
=
publish
,
anonymous_student_id
=
unique_id_for_user
(
user
),
course_id
=
course_id
,
open_ended_grading_interface
=
open_ended_grading_interface
,
s3_interface
=
s3_interface
,
cache
=
cache
,
can_execute_unsafe_code
=
(
lambda
:
can_execute_unsafe_code
(
course_id
)),
)
# pass position specified in URL to module through ModuleSystem
system
.
set
(
'position'
,
position
)
system
.
set
(
'DEBUG'
,
settings
.
DEBUG
)
if
settings
.
MITX_FEATURES
.
get
(
'ENABLE_PSYCHOMETRICS'
):
system
.
set
(
'psychometrics_handler'
,
# set callback for updating PsychometricsData
make_psychometrics_data_update_handler
(
course_id
,
user
,
descriptor
.
location
.
url
()))
system
.
set
(
'psychometrics_handler'
,
# set callback for updating PsychometricsData
make_psychometrics_data_update_handler
(
course_id
,
user
,
descriptor
.
location
.
url
())
)
try
:
module
=
descriptor
.
xmodule
(
system
)
...
...
@@ -381,13 +392,14 @@ def get_module_for_descriptor_internal(user, descriptor, model_data_cache, cours
system
.
set
(
'user_is_staff'
,
has_access
(
user
,
descriptor
.
location
,
'staff'
,
course_id
))
_get_html
=
module
.
get_html
if
wrap_xmodule_display
==
True
:
if
wrap_xmodule_display
is
True
:
_get_html
=
wrap_xmodule
(
module
.
get_html
,
module
,
'xmodule_display.html'
)
module
.
get_html
=
replace_static_urls
(
_get_html
,
getattr
(
descriptor
,
'data_dir'
,
None
),
course_namespace
=
module
.
location
.
_replace
(
category
=
None
,
name
=
None
))
course_namespace
=
module
.
location
.
_replace
(
category
=
None
,
name
=
None
)
)
# Allow URLs of the form '/course/' refer to the root of multicourse directory
# hierarchy of this 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