Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
problem-builder
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
OpenEdx
problem-builder
Commits
125eeb30
Commit
125eeb30
authored
Mar 24, 2014
by
Alan Boudreault
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
first try of LightChild data persistence
parent
7ab8773d
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
216 additions
and
17 deletions
+216
-17
mentoring/answer.py
+2
-2
mentoring/html.py
+2
-1
mentoring/light_children.py
+117
-2
mentoring/migrations/0001_initial.py
+1
-2
mentoring/migrations/0004_auto__add_lightchild__add_unique_lightchild_student_id_course_id_name.py
+58
-0
mentoring/models.py
+18
-0
mentoring/mrq.py
+18
-10
No files found.
mentoring/answer.py
View file @
125eeb30
...
...
@@ -55,7 +55,7 @@ class AnswerBlock(LightChild):
@lazy
def
student_input
(
self
):
"""
Use lazy property instead of XBlock field, as __init__() doesn't support
Use lazy property instead of XBlock field, as __init__() doesn't support
overwriting field values
"""
# Only attempt to locate a model object for this block when the answer has a name
...
...
@@ -79,7 +79,7 @@ class AnswerBlock(LightChild):
html
=
render_template
(
'templates/html/answer_read_only.html'
,
{
'self'
:
self
,
})
fragment
=
Fragment
(
html
)
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
'public/css/answer.css'
))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
...
...
mentoring/html.py
View file @
125eeb30
...
...
@@ -56,7 +56,8 @@ class HTMLBlock(LightChild):
return
block
def
student_view
(
self
,
context
=
None
):
return
Fragment
(
self
.
content
)
# HACK, TO MODIFY
return
Fragment
(
self
.
content
.
get
())
def
mentoring_view
(
self
,
context
=
None
):
return
self
.
student_view
(
context
)
...
...
mentoring/light_children.py
View file @
125eeb30
...
...
@@ -24,6 +24,9 @@
# Imports ###########################################################
import
logging
import
json
from
lazy
import
lazy
from
cStringIO
import
StringIO
from
lxml
import
etree
...
...
@@ -34,6 +37,8 @@ from xblock.core import XBlock
from
xblock.fragment
import
Fragment
from
xblock.plugin
import
Plugin
from
.models
import
LightChild
as
LightChildModel
try
:
from
xmodule_modifiers
import
replace_jump_to_id_urls
except
:
...
...
@@ -194,6 +199,7 @@ class XBlockWithLightChildren(LightChildrenMixin, XBlock):
fragment
=
replace_jump_to_id_urls
(
course_id
,
jump_to_url
,
self
,
'student_view'
,
fragment
,
{})
return
fragment
class
LightChild
(
Plugin
,
LightChildrenMixin
):
"""
Base class for the light children
...
...
@@ -203,6 +209,31 @@ class LightChild(Plugin, LightChildrenMixin):
def
__init__
(
self
,
parent
):
self
.
parent
=
parent
self
.
xblock_container
=
parent
.
xblock_container
# This doesn't work, crash.... where should I trigger the lazy property to be loaded?
#self.load_student_data()
def
__setattr__
(
self
,
name
,
value
):
field
=
getattr
(
self
,
name
)
if
hasattr
(
self
,
name
)
else
None
# If the property is a LightChildField instance, use its setattr
if
isinstance
(
field
,
LightChildField
):
field
.
set
(
value
)
else
:
super
(
LightChild
,
self
)
.
__setattr__
(
name
,
value
)
@lazy
def
student_data
(
self
):
"""
Use lazy property instead of XBlock field, as __init__() doesn't support
overwriting field values
"""
if
not
self
.
name
:
return
''
student_data
=
self
.
get_lightchild_model_object
()
.
student_data
return
student_data
@property
def
runtime
(
self
):
...
...
@@ -220,20 +251,96 @@ class LightChild(Plugin, LightChildrenMixin):
xmodule_runtime
=
xmodule_runtime
()
return
xmodule_runtime
def
load_student_data
(
self
):
"""
Load the values from the student_data in the database.
"""
student_data
=
self
.
student_data
fields
=
self
.
get_fields
()
for
field
in
fields
:
if
field
in
student_data
:
setattr
(
self
,
field
,
student_data
[
field
])
@classmethod
def
get_fields
(
cls
):
"""
Returns a list of all LightChildField of the class. Used for saving student data.
"""
return
[]
def
save
(
self
):
pass
"""
Replicate data changes on the related Django model used for sharing of data accross XBlocks
"""
# Save all children
for
child
in
self
.
get_children_objects
():
child
.
save
()
self
.
student_data
=
{}
# Get All LightChild fields to save
for
field
in
self
.
get_fields
():
self
.
student_data
[
field
]
=
getattr
(
self
,
field
)
.
to_json
()
if
self
.
name
:
lightchild_data
=
self
.
get_lightchild_model_object
()
if
lightchild_data
.
student_data
!=
self
.
student_data
:
lightchild_data
.
student_data
=
json
.
dumps
(
self
.
student_data
)
lightchild_data
.
save
()
def
get_lightchild_model_object
(
self
,
name
=
None
):
"""
Fetches the LightChild model object for the lightchild named `name`
"""
if
not
name
:
name
=
self
.
name
if
not
name
:
raise
ValueError
,
'LightChild.name field need to be set to a non-null/empty value'
student_id
=
self
.
xmodule_runtime
.
anonymous_student_id
course_id
=
self
.
xmodule_runtime
.
course_id
lightchild_data
,
created
=
LightChildModel
.
objects
.
get_or_create
(
student_id
=
student_id
,
course_id
=
course_id
,
name
=
name
,
)
return
lightchild_data
class
LightChildField
(
object
):
"""
Fake field with no persistence - allows to keep XBlocks fields definitions on LightChild
"""
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
value
=
kwargs
.
get
(
'default'
,
''
)
def
__nonzero__
(
self
):
return
bool
(
self
.
value
)
def
set
(
self
,
value
):
self
.
value
=
value
def
get
(
self
):
return
self
.
value
def
to_json
(
self
):
"""
Returns the JSON representation of the LightChieldField.
"""
return
self
.
value
def
from_json
(
self
,
value
):
"""
Returns value as a native full featured python type from a JSON value.
"""
pass
class
String
(
LightChildField
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
...
...
@@ -259,11 +366,19 @@ class Integer(LightChildField):
def
__nonzero__
(
self
):
try
:
int
(
self
.
value
)
except
TypeError
,
ValueError
:
# not an integer
except
(
TypeError
,
ValueError
)
:
# not an integer
return
False
return
self
.
value
is
not
None
def
set
(
self
,
value
):
self
.
value
=
int
(
value
)
def
from_json
(
self
,
value
):
if
value
is
None
or
value
==
''
:
return
None
return
int
(
value
)
class
Boolean
(
LightChildField
):
pass
...
...
mentoring/migrations/0001_initial.py
View file @
125eeb30
...
...
@@ -43,4 +43,4 @@ class Migration(SchemaMigration):
}
}
complete_apps
=
[
'mentoring'
]
\ No newline at end of file
complete_apps
=
[
'mentoring'
]
mentoring/migrations/0004_auto__add_lightchild__add_unique_lightchild_student_id_course_id_name.py
0 → 100644
View file @
125eeb30
# -*- coding: utf-8 -*-
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding model 'LightChild'
db
.
create_table
(
'mentoring_lightchild'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
50
,
db_index
=
True
)),
(
'student_id'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
32
,
db_index
=
True
)),
(
'course_id'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
50
,
db_index
=
True
)),
(
'student_data'
,
self
.
gf
(
'django.db.models.fields.TextField'
)(
default
=
''
,
blank
=
True
)),
(
'created_on'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now_add
=
True
,
blank
=
True
)),
(
'modified_on'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now
=
True
,
blank
=
True
)),
))
db
.
send_create_signal
(
'mentoring'
,
[
'LightChild'
])
# Adding unique constraint on 'LightChild', fields ['student_id', 'course_id', 'name']
db
.
create_unique
(
'mentoring_lightchild'
,
[
'student_id'
,
'course_id'
,
'name'
])
def
backwards
(
self
,
orm
):
# Removing unique constraint on 'LightChild', fields ['student_id', 'course_id', 'name']
db
.
delete_unique
(
'mentoring_lightchild'
,
[
'student_id'
,
'course_id'
,
'name'
])
# Deleting model 'LightChild'
db
.
delete_table
(
'mentoring_lightchild'
)
models
=
{
'mentoring.answer'
:
{
'Meta'
:
{
'unique_together'
:
"(('student_id', 'course_id', 'name'),)"
,
'object_name'
:
'Answer'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'created_on'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_on'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'student_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'student_input'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'blank'
:
'True'
})
},
'mentoring.lightchild'
:
{
'Meta'
:
{
'unique_together'
:
"(('student_id', 'course_id', 'name'),)"
,
'object_name'
:
'LightChild'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'created_on'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'student_data'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_on'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'student_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'db_index'
:
'True'
})
}
}
complete_apps
=
[
'mentoring'
]
mentoring/models.py
View file @
125eeb30
...
...
@@ -49,3 +49,21 @@ class Answer(models.Model):
# Force validation of max_length
self
.
full_clean
()
super
(
Answer
,
self
)
.
save
(
*
args
,
**
kwargs
)
class
LightChild
(
models
.
Model
):
"""
Django model used to store LightChild student data that need to be shared and queried accross
XBlock instances (workaround). Since this is temporary, `data` are stored in json.
"""
class
Meta
:
app_label
=
'mentoring'
unique_together
=
((
'student_id'
,
'course_id'
,
'name'
),)
name
=
models
.
CharField
(
max_length
=
50
,
db_index
=
True
)
student_id
=
models
.
CharField
(
max_length
=
32
,
db_index
=
True
)
course_id
=
models
.
CharField
(
max_length
=
50
,
db_index
=
True
)
student_data
=
models
.
TextField
(
blank
=
True
,
default
=
''
)
created_on
=
models
.
DateTimeField
(
'created on'
,
auto_now_add
=
True
)
modified_on
=
models
.
DateTimeField
(
'modified on'
,
auto_now
=
True
)
mentoring/mrq.py
View file @
125eeb30
...
...
@@ -46,6 +46,12 @@ class MRQBlock(QuestionnaireAbstractBlock):
max_attempts
=
Integer
(
help
=
"Number of max attempts for this questions"
,
default
=
None
,
scope
=
Scope
.
content
)
num_attempts
=
Integer
(
help
=
"Number of attempts a user has answered for this questions"
,
scope
=
Scope
.
user_state
)
@classmethod
def
get_fields
(
cls
):
return
[
'num_attempts'
]
def
submit
(
self
,
submissions
):
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
...
...
@@ -66,7 +72,7 @@ class MRQBlock(QuestionnaireAbstractBlock):
completed
=
completed
and
choice_completed
results
.
append
({
'value'
:
choice
.
value
,
'value'
:
choice
.
value
.
get
()
,
'selected'
:
choice_selected
,
'completed'
:
choice_completed
,
'tips'
:
render_template
(
'templates/html/tip_choice_group.html'
,
{
...
...
@@ -80,12 +86,13 @@ class MRQBlock(QuestionnaireAbstractBlock):
# What's the proper way to get my value saved? it doesn't work without '.value'
# this is incorrect and the num_attempts is resetted if we restart the server.
self
.
num_attempts
.
value
=
int
(
self
.
num_attempts
)
+
1
if
self
.
max_attempts
else
0
num_attempts
=
self
.
num_attempts
.
get
()
+
1
if
self
.
max_attempts
else
0
setattr
(
self
,
'num_attempts'
,
num_attempts
)
max_attempts_reached
=
False
if
self
.
max_attempts
:
max_attempts
=
int
(
self
.
max_attempts
)
num_attempts
=
int
(
self
.
num_attempts
)
max_attempts
=
self
.
max_attempts
.
get
(
)
num_attempts
=
self
.
num_attempts
.
get
(
)
max_attempts_reached
=
num_attempts
>=
max_attempts
if
max_attempts_reached
and
(
not
completed
or
num_attempts
>
max_attempts
):
...
...
@@ -96,12 +103,13 @@ class MRQBlock(QuestionnaireAbstractBlock):
self
.
student_choices
=
submissions
result
=
{
'submissions'
:
submissions
,
'completed'
:
completed
,
'choices'
:
results
,
'message'
:
self
.
message
,
'max_attempts'
:
int
(
self
.
max_attempts
)
if
self
.
max_attempts
else
None
,
'num_attempts'
:
int
(
self
.
num_attempts
)
'submissions'
:
submissions
,
'completed'
:
completed
,
'choices'
:
results
,
'message'
:
self
.
message
.
get
()
,
'max_attempts'
:
self
.
max_attempts
.
get
(
)
if
self
.
max_attempts
else
None
,
'num_attempts'
:
self
.
num_attempts
.
get
(
)
}
log
.
debug
(
u'MRQ submissions result:
%
s'
,
result
)
return
result
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