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
2d6f8a3f
Commit
2d6f8a3f
authored
Jul 14, 2015
by
Tim Krones
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement client-server communication for pagination.
parent
cd69698b
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
92 additions
and
24 deletions
+92
-24
problem_builder/instructor_tool.py
+26
-1
problem_builder/public/js/instructor_tool.js
+63
-20
problem_builder/tests/integration/test_instructor_tool.py
+3
-3
No files found.
problem_builder/instructor_tool.py
View file @
2d6f8a3f
...
@@ -23,14 +23,16 @@ Instructor Tool: An XBlock for instructors to export student answers from a cour
...
@@ -23,14 +23,16 @@ Instructor Tool: An XBlock for instructors to export student answers from a cour
All processing is done offline.
All processing is done offline.
"""
"""
import
json
import
json
from
django.core.paginator
import
Paginator
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.exceptions
import
JsonHandlerError
from
xblock.exceptions
import
JsonHandlerError
from
xblock.fields
import
Scope
,
String
,
Dict
from
xblock.fields
import
Scope
,
String
,
Dict
,
List
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.resources
import
ResourceLoader
loader
=
ResourceLoader
(
__name__
)
loader
=
ResourceLoader
(
__name__
)
PAGE_SIZE
=
15
# Make '_' a no-op so we can scrape strings
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
def
_
(
text
):
...
@@ -63,6 +65,12 @@ class InstructorToolBlock(XBlock):
...
@@ -63,6 +65,12 @@ class InstructorToolBlock(XBlock):
default
=
None
,
default
=
None
,
scope
=
Scope
.
user_state
,
scope
=
Scope
.
user_state
,
)
)
display_data
=
List
(
# The list of results associated with the most recent successful export.
# Stored separately to avoid the overhead of sending it to the client.
default
=
None
,
scope
=
Scope
.
user_state
,
)
has_author_view
=
True
has_author_view
=
True
@property
@property
...
@@ -90,11 +98,26 @@ class InstructorToolBlock(XBlock):
...
@@ -90,11 +98,26 @@ class InstructorToolBlock(XBlock):
self
.
active_export_task_id
=
''
self
.
active_export_task_id
=
''
if
task_result
.
successful
():
if
task_result
.
successful
():
if
isinstance
(
task_result
.
result
,
dict
)
and
not
task_result
.
result
.
get
(
'error'
):
if
isinstance
(
task_result
.
result
,
dict
)
and
not
task_result
.
result
.
get
(
'error'
):
self
.
display_data
=
task_result
.
result
[
'display_data'
]
del
task_result
.
result
[
'display_data'
]
self
.
last_export_result
=
task_result
.
result
self
.
last_export_result
=
task_result
.
result
else
:
else
:
self
.
last_export_result
=
{
'error'
:
u'Unexpected result: {}'
.
format
(
repr
(
task_result
.
result
))}
self
.
last_export_result
=
{
'error'
:
u'Unexpected result: {}'
.
format
(
repr
(
task_result
.
result
))}
self
.
display_data
=
None
else
:
else
:
self
.
last_export_result
=
{
'error'
:
unicode
(
task_result
.
result
)}
self
.
last_export_result
=
{
'error'
:
unicode
(
task_result
.
result
)}
self
.
display_data
=
None
@XBlock.json_handler
def
get_result_page
(
self
,
data
,
suffix
=
''
):
""" Return requested page of `last_export_result`. """
paginator
=
Paginator
(
self
.
display_data
,
PAGE_SIZE
)
page
=
data
.
get
(
'page'
,
None
)
return
{
'display_data'
:
paginator
.
page
(
page
)
.
object_list
,
'num_results'
:
len
(
self
.
display_data
),
'page_size'
:
PAGE_SIZE
}
def
student_view
(
self
,
context
=
None
):
def
student_view
(
self
,
context
=
None
):
""" Normal View """
""" Normal View """
...
@@ -144,6 +167,7 @@ class InstructorToolBlock(XBlock):
...
@@ -144,6 +167,7 @@ class InstructorToolBlock(XBlock):
self
.
last_export_result
=
{
self
.
last_export_result
=
{
'error'
:
message
,
'error'
:
message
,
}
}
self
.
display_data
=
None
raise
JsonHandlerError
(
code
,
message
)
raise
JsonHandlerError
(
code
,
message
)
@XBlock.json_handler
@XBlock.json_handler
...
@@ -157,6 +181,7 @@ class InstructorToolBlock(XBlock):
...
@@ -157,6 +181,7 @@ class InstructorToolBlock(XBlock):
def
_delete_export
(
self
):
def
_delete_export
(
self
):
self
.
last_export_result
=
None
self
.
last_export_result
=
None
self
.
display_data
=
None
self
.
active_export_task_id
=
''
self
.
active_export_task_id
=
''
@XBlock.json_handler
@XBlock.json_handler
...
...
problem_builder/public/js/instructor_tool.js
View file @
2d6f8a3f
...
@@ -18,12 +18,57 @@ function InstructorToolBlock(runtime, element) {
...
@@ -18,12 +18,57 @@ function InstructorToolBlock(runtime, element) {
model
:
Result
,
model
:
Result
,
getCurrentPage
:
function
(
returnObject
)
{
state
:
{
var
currentPage
=
this
.
state
.
currentPage
;
order
:
0
if
(
returnObject
)
{
},
return
this
.
getPage
(
currentPage
);
url
:
runtime
.
handlerUrl
(
element
,
'get_result_page'
),
parseState
:
function
(
response
)
{
return
{
totalRecords
:
response
.
num_results
,
pageSize
:
response
.
page_size
};
},
parseRecords
:
function
(
response
)
{
return
_
.
map
(
response
.
display_data
,
function
(
row
)
{
return
new
Result
(
null
,
{
values
:
row
});
});
},
fetchOptions
:
{
reset
:
true
,
type
:
'POST'
,
contentType
:
'application/json'
,
processData
:
false
,
beforeSend
:
function
(
jqXHR
,
options
)
{
options
.
data
=
JSON
.
stringify
(
options
.
data
);
}
}
return
currentPage
;
},
getFirstPage
:
function
()
{
Backbone
.
PageableCollection
.
prototype
.
getFirstPage
.
call
(
this
,
this
.
fetchOptions
);
},
getPreviousPage
:
function
()
{
Backbone
.
PageableCollection
.
prototype
.
getPreviousPage
.
call
(
this
,
this
.
fetchOptions
);
},
getNextPage
:
function
()
{
Backbone
.
PageableCollection
.
prototype
.
getNextPage
.
call
(
this
,
this
.
fetchOptions
);
},
getLastPage
:
function
()
{
Backbone
.
PageableCollection
.
prototype
.
getLastPage
.
call
(
this
,
this
.
fetchOptions
);
},
getCurrentPage
:
function
()
{
return
this
.
state
.
currentPage
;
},
},
getTotalPages
:
function
()
{
getTotalPages
:
function
()
{
...
@@ -34,17 +79,21 @@ function InstructorToolBlock(runtime, element) {
...
@@ -34,17 +79,21 @@ function InstructorToolBlock(runtime, element) {
var
ResultsView
=
Backbone
.
View
.
extend
({
var
ResultsView
=
Backbone
.
View
.
extend
({
initialize
:
function
()
{
this
.
listenTo
(
this
.
collection
,
'reset'
,
this
.
render
);
},
render
:
function
()
{
render
:
function
()
{
this
.
_insertRecords
(
this
.
collection
.
getCurrentPage
(
true
)
);
this
.
_insertRecords
();
this
.
_updateControls
();
this
.
_updateControls
();
this
.
$
(
'#total-pages'
).
text
(
this
.
collection
.
getTotalPages
()
||
0
);
this
.
$
(
'#total-pages'
).
text
(
this
.
collection
.
getTotalPages
()
||
0
);
return
this
;
return
this
;
},
},
_insertRecords
:
function
(
records
)
{
_insertRecords
:
function
()
{
var
tbody
=
this
.
$
(
'tbody'
);
var
tbody
=
this
.
$
(
'tbody'
);
tbody
.
empty
();
tbody
.
empty
();
records
.
each
(
function
(
result
,
index
)
{
this
.
collection
.
each
(
function
(
result
,
index
)
{
var
row
=
$
(
'<tr>'
);
var
row
=
$
(
'<tr>'
);
_
.
each
(
Result
.
properties
,
function
(
name
)
{
_
.
each
(
Result
.
properties
,
function
(
name
)
{
row
.
append
(
$
(
'<td>'
).
text
(
result
.
get
(
name
)));
row
.
append
(
$
(
'<td>'
).
text
(
result
.
get
(
name
)));
...
@@ -66,26 +115,26 @@ function InstructorToolBlock(runtime, element) {
...
@@ -66,26 +115,26 @@ function InstructorToolBlock(runtime, element) {
},
},
_firstPage
:
function
()
{
_firstPage
:
function
()
{
this
.
_insertRecords
(
this
.
collection
.
getFirstPage
()
);
this
.
collection
.
getFirstPage
(
);
this
.
_updateControls
();
this
.
_updateControls
();
},
},
_prevPage
:
function
()
{
_prevPage
:
function
()
{
if
(
this
.
collection
.
hasPreviousPage
())
{
if
(
this
.
collection
.
hasPreviousPage
())
{
this
.
_insertRecords
(
this
.
collection
.
getPreviousPage
()
);
this
.
collection
.
getPreviousPage
(
);
}
}
this
.
_updateControls
();
this
.
_updateControls
();
},
},
_nextPage
:
function
()
{
_nextPage
:
function
()
{
if
(
this
.
collection
.
hasNextPage
())
{
if
(
this
.
collection
.
hasNextPage
())
{
this
.
_insertRecords
(
this
.
collection
.
getNextPage
()
);
this
.
collection
.
getNextPage
(
);
}
}
this
.
_updateControls
();
this
.
_updateControls
();
},
},
_lastPage
:
function
()
{
_lastPage
:
function
()
{
this
.
_insertRecords
(
this
.
collection
.
getLastPage
()
);
this
.
collection
.
getLastPage
(
);
this
.
_updateControls
();
this
.
_updateControls
();
},
},
...
@@ -107,7 +156,7 @@ function InstructorToolBlock(runtime, element) {
...
@@ -107,7 +156,7 @@ function InstructorToolBlock(runtime, element) {
});
});
var
resultsView
=
new
ResultsView
({
var
resultsView
=
new
ResultsView
({
collection
:
new
Results
([]
,
{
mode
:
"client"
,
state
:
{
pageSize
:
15
}
}
),
collection
:
new
Results
([]),
el
:
$element
.
find
(
'#results'
)
el
:
$element
.
find
(
'#results'
)
});
});
...
@@ -207,13 +256,7 @@ function InstructorToolBlock(runtime, element) {
...
@@ -207,13 +256,7 @@ function InstructorToolBlock(runtime, element) {
)
)
));
));
// Display results
resultsView
.
collection
.
getFirstPage
();
var
results
=
_
.
map
(
status
.
last_export_result
.
display_data
,
function
(
row
)
{
return
new
Result
(
null
,
{
values
:
row
});
});
resultsView
.
collection
.
fullCollection
.
reset
(
results
);
resultsView
.
render
();
showResults
();
showResults
();
}
}
...
...
problem_builder/tests/integration/test_instructor_tool.py
View file @
2d6f8a3f
...
@@ -7,7 +7,7 @@ from mock import patch, Mock
...
@@ -7,7 +7,7 @@ from mock import patch, Mock
from
selenium.common.exceptions
import
NoSuchElementException
from
selenium.common.exceptions
import
NoSuchElementException
from
xblockutils.base_test
import
SeleniumXBlockTest
from
xblockutils.base_test
import
SeleniumXBlockTest
from
problem_builder.instructor_tool
import
InstructorToolBlock
from
problem_builder.instructor_tool
import
PAGE_SIZE
,
InstructorToolBlock
class
MockTasksModule
(
object
):
class
MockTasksModule
(
object
):
...
@@ -199,7 +199,7 @@ class InstructorToolTest(SeleniumXBlockTest):
...
@@ -199,7 +199,7 @@ class InstructorToolTest(SeleniumXBlockTest):
successful
=
True
,
display_data
=
[[
successful
=
True
,
display_data
=
[[
'Test section'
,
'Test subsection'
,
'Test unit'
,
'Test section'
,
'Test subsection'
,
'Test unit'
,
'Test type'
,
'Test question'
,
'Test answer'
,
'Test username'
'Test type'
,
'Test question'
,
'Test answer'
,
'Test username'
]
for
_
in
range
(
45
)]),
]
for
_
in
range
(
PAGE_SIZE
*
3
)]),
'instructor_task'
:
True
,
'instructor_task'
:
True
,
'instructor_task.models'
:
MockInstructorTaskModelsModule
(),
'instructor_task.models'
:
MockInstructorTaskModelsModule
(),
})
})
...
@@ -224,7 +224,7 @@ class InstructorToolTest(SeleniumXBlockTest):
...
@@ -224,7 +224,7 @@ class InstructorToolTest(SeleniumXBlockTest):
'Test type'
,
'Test question'
,
'Test answer'
,
'Test username'
'Test type'
,
'Test question'
,
'Test answer'
,
'Test username'
]:
]:
occurrences
=
re
.
findall
(
contents
,
result_block
.
text
)
occurrences
=
re
.
findall
(
contents
,
result_block
.
text
)
self
.
assertEqual
(
len
(
occurrences
),
15
)
self
.
assertEqual
(
len
(
occurrences
),
PAGE_SIZE
)
self
.
assertFalse
(
first_page_button
.
is_enabled
())
self
.
assertFalse
(
first_page_button
.
is_enabled
())
self
.
assertFalse
(
prev_page_button
.
is_enabled
())
self
.
assertFalse
(
prev_page_button
.
is_enabled
())
...
...
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