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
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
210 additions
and
10 deletions
+210
-10
mentoring/answer.py
+0
-0
mentoring/html.py
+2
-1
mentoring/light_children.py
+117
-2
mentoring/migrations/0001_initial.py
+0
-0
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
+15
-7
No files found.
mentoring/answer.py
View file @
125eeb30
mentoring/html.py
View file @
125eeb30
...
@@ -56,7 +56,8 @@ class HTMLBlock(LightChild):
...
@@ -56,7 +56,8 @@ class HTMLBlock(LightChild):
return
block
return
block
def
student_view
(
self
,
context
=
None
):
def
student_view
(
self
,
context
=
None
):
return
Fragment
(
self
.
content
)
# HACK, TO MODIFY
return
Fragment
(
self
.
content
.
get
())
def
mentoring_view
(
self
,
context
=
None
):
def
mentoring_view
(
self
,
context
=
None
):
return
self
.
student_view
(
context
)
return
self
.
student_view
(
context
)
...
...
mentoring/light_children.py
View file @
125eeb30
...
@@ -24,6 +24,9 @@
...
@@ -24,6 +24,9 @@
# Imports ###########################################################
# Imports ###########################################################
import
logging
import
logging
import
json
from
lazy
import
lazy
from
cStringIO
import
StringIO
from
cStringIO
import
StringIO
from
lxml
import
etree
from
lxml
import
etree
...
@@ -34,6 +37,8 @@ from xblock.core import XBlock
...
@@ -34,6 +37,8 @@ from xblock.core import XBlock
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
from
xblock.plugin
import
Plugin
from
xblock.plugin
import
Plugin
from
.models
import
LightChild
as
LightChildModel
try
:
try
:
from
xmodule_modifiers
import
replace_jump_to_id_urls
from
xmodule_modifiers
import
replace_jump_to_id_urls
except
:
except
:
...
@@ -194,6 +199,7 @@ class XBlockWithLightChildren(LightChildrenMixin, XBlock):
...
@@ -194,6 +199,7 @@ class XBlockWithLightChildren(LightChildrenMixin, XBlock):
fragment
=
replace_jump_to_id_urls
(
course_id
,
jump_to_url
,
self
,
'student_view'
,
fragment
,
{})
fragment
=
replace_jump_to_id_urls
(
course_id
,
jump_to_url
,
self
,
'student_view'
,
fragment
,
{})
return
fragment
return
fragment
class
LightChild
(
Plugin
,
LightChildrenMixin
):
class
LightChild
(
Plugin
,
LightChildrenMixin
):
"""
"""
Base class for the light children
Base class for the light children
...
@@ -203,6 +209,31 @@ class LightChild(Plugin, LightChildrenMixin):
...
@@ -203,6 +209,31 @@ class LightChild(Plugin, LightChildrenMixin):
def
__init__
(
self
,
parent
):
def
__init__
(
self
,
parent
):
self
.
parent
=
parent
self
.
parent
=
parent
self
.
xblock_container
=
parent
.
xblock_container
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
@property
def
runtime
(
self
):
def
runtime
(
self
):
...
@@ -220,20 +251,96 @@ class LightChild(Plugin, LightChildrenMixin):
...
@@ -220,20 +251,96 @@ class LightChild(Plugin, LightChildrenMixin):
xmodule_runtime
=
xmodule_runtime
()
xmodule_runtime
=
xmodule_runtime
()
return
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
):
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
):
class
LightChildField
(
object
):
"""
"""
Fake field with no persistence - allows to keep XBlocks fields definitions on LightChild
Fake field with no persistence - allows to keep XBlocks fields definitions on LightChild
"""
"""
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
value
=
kwargs
.
get
(
'default'
,
''
)
self
.
value
=
kwargs
.
get
(
'default'
,
''
)
def
__nonzero__
(
self
):
def
__nonzero__
(
self
):
return
bool
(
self
.
value
)
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
):
class
String
(
LightChildField
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
...
@@ -259,11 +366,19 @@ class Integer(LightChildField):
...
@@ -259,11 +366,19 @@ class Integer(LightChildField):
def
__nonzero__
(
self
):
def
__nonzero__
(
self
):
try
:
try
:
int
(
self
.
value
)
int
(
self
.
value
)
except
TypeError
,
ValueError
:
# not an integer
except
(
TypeError
,
ValueError
)
:
# not an integer
return
False
return
False
return
self
.
value
is
not
None
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
):
class
Boolean
(
LightChildField
):
pass
pass
...
...
mentoring/migrations/0001_initial.py
View file @
125eeb30
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):
...
@@ -49,3 +49,21 @@ class Answer(models.Model):
# Force validation of max_length
# Force validation of max_length
self
.
full_clean
()
self
.
full_clean
()
super
(
Answer
,
self
)
.
save
(
*
args
,
**
kwargs
)
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):
...
@@ -46,6 +46,12 @@ class MRQBlock(QuestionnaireAbstractBlock):
max_attempts
=
Integer
(
help
=
"Number of max attempts for this questions"
,
default
=
None
,
scope
=
Scope
.
content
)
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
)
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
):
def
submit
(
self
,
submissions
):
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
...
@@ -66,7 +72,7 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -66,7 +72,7 @@ class MRQBlock(QuestionnaireAbstractBlock):
completed
=
completed
and
choice_completed
completed
=
completed
and
choice_completed
results
.
append
({
results
.
append
({
'value'
:
choice
.
value
,
'value'
:
choice
.
value
.
get
()
,
'selected'
:
choice_selected
,
'selected'
:
choice_selected
,
'completed'
:
choice_completed
,
'completed'
:
choice_completed
,
'tips'
:
render_template
(
'templates/html/tip_choice_group.html'
,
{
'tips'
:
render_template
(
'templates/html/tip_choice_group.html'
,
{
...
@@ -80,12 +86,13 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -80,12 +86,13 @@ class MRQBlock(QuestionnaireAbstractBlock):
# What's the proper way to get my value saved? it doesn't work without '.value'
# 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.
# 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
max_attempts_reached
=
False
if
self
.
max_attempts
:
if
self
.
max_attempts
:
max_attempts
=
int
(
self
.
max_attempts
)
max_attempts
=
self
.
max_attempts
.
get
(
)
num_attempts
=
int
(
self
.
num_attempts
)
num_attempts
=
self
.
num_attempts
.
get
(
)
max_attempts_reached
=
num_attempts
>=
max_attempts
max_attempts_reached
=
num_attempts
>=
max_attempts
if
max_attempts_reached
and
(
not
completed
or
num_attempts
>
max_attempts
):
if
max_attempts_reached
and
(
not
completed
or
num_attempts
>
max_attempts
):
...
@@ -99,9 +106,10 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -99,9 +106,10 @@ class MRQBlock(QuestionnaireAbstractBlock):
'submissions'
:
submissions
,
'submissions'
:
submissions
,
'completed'
:
completed
,
'completed'
:
completed
,
'choices'
:
results
,
'choices'
:
results
,
'message'
:
self
.
message
,
'message'
:
self
.
message
.
get
()
,
'max_attempts'
:
int
(
self
.
max_attempts
)
if
self
.
max_attempts
else
None
,
'max_attempts'
:
self
.
max_attempts
.
get
(
)
if
self
.
max_attempts
else
None
,
'num_attempts'
:
int
(
self
.
num_attempts
)
'num_attempts'
:
self
.
num_attempts
.
get
(
)
}
}
log
.
debug
(
u'MRQ submissions result:
%
s'
,
result
)
log
.
debug
(
u'MRQ submissions result:
%
s'
,
result
)
return
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