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
8824d032
Commit
8824d032
authored
Jul 02, 2015
by
Will Daly
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #8767 from edx/will/optimize-credit-reqs-update
Credit requirement optimizations
parents
bfe01605
06ddb2f3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
82 additions
and
58 deletions
+82
-58
openedx/core/djangoapps/credit/signals.py
+9
-2
openedx/core/djangoapps/credit/tasks.py
+65
-55
openedx/core/djangoapps/credit/tests/test_tasks.py
+8
-1
No files found.
openedx/core/djangoapps/credit/signals.py
View file @
8824d032
...
@@ -2,6 +2,8 @@
...
@@ -2,6 +2,8 @@
This file contains receivers of course publication signals.
This file contains receivers of course publication signals.
"""
"""
import
logging
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
django.utils
import
timezone
from
django.utils
import
timezone
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
...
@@ -10,6 +12,9 @@ from xmodule.modulestore.django import SignalHandler
...
@@ -10,6 +12,9 @@ from xmodule.modulestore.django import SignalHandler
from
openedx.core.djangoapps.signals.signals
import
GRADES_UPDATED
from
openedx.core.djangoapps.signals.signals
import
GRADES_UPDATED
log
=
logging
.
getLogger
(
__name__
)
@receiver
(
SignalHandler
.
course_published
)
@receiver
(
SignalHandler
.
course_published
)
def
listen_for_course_publish
(
sender
,
course_key
,
**
kwargs
):
# pylint: disable=unused-argument
def
listen_for_course_publish
(
sender
,
course_key
,
**
kwargs
):
# pylint: disable=unused-argument
"""Receive 'course_published' signal and kick off a celery task to update
"""Receive 'course_published' signal and kick off a celery task to update
...
@@ -18,9 +23,11 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
...
@@ -18,9 +23,11 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
# Import here, because signal is registered at startup, but items in tasks
# Import here, because signal is registered at startup, but items in tasks
# are not yet able to be loaded
# are not yet able to be loaded
from
.tasks
import
update_credit_course_requirement
s
from
openedx.core.djangoapps.credit
import
api
,
task
s
update_credit_course_requirements
.
delay
(
unicode
(
course_key
))
if
api
.
is_credit_course
(
course_key
):
tasks
.
update_credit_course_requirements
.
delay
(
unicode
(
course_key
))
log
.
info
(
u'Added task to update credit requirements for course "
%
s" to the task queue'
,
course_key
)
@receiver
(
GRADES_UPDATED
)
@receiver
(
GRADES_UPDATED
)
...
...
openedx/core/djangoapps/credit/tasks.py
View file @
8824d032
...
@@ -18,10 +18,20 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
...
@@ -18,10 +18,20 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
LOGGER
=
get_task_logger
(
__name__
)
LOGGER
=
get_task_logger
(
__name__
)
# XBlocks that can be added as credit requirements
CREDIT_REQUIREMENT_XBLOCKS
=
[
{
"category"
:
"edx-reverification-block"
,
"requirement-namespace"
:
"reverification"
}
]
# pylint: disable=not-callable
# pylint: disable=not-callable
@task
(
default_retry_delay
=
settings
.
CREDIT_TASK_DEFAULT_RETRY_DELAY
,
max_retries
=
settings
.
CREDIT_TASK_MAX_RETRIES
)
@task
(
default_retry_delay
=
settings
.
CREDIT_TASK_DEFAULT_RETRY_DELAY
,
max_retries
=
settings
.
CREDIT_TASK_MAX_RETRIES
)
def
update_credit_course_requirements
(
course_id
):
# pylint: disable=invalid-name
def
update_credit_course_requirements
(
course_id
):
# pylint: disable=invalid-name
"""Updates course requirements table for a course.
"""
Updates course requirements table for a course.
Args:
Args:
course_id(str): A string representation of course identifier
course_id(str): A string representation of course identifier
...
@@ -34,8 +44,7 @@ def update_credit_course_requirements(course_id): # pylint: disable=invalid-na
...
@@ -34,8 +44,7 @@ def update_credit_course_requirements(course_id): # pylint: disable=invalid-na
course_key
=
CourseKey
.
from_string
(
course_id
)
course_key
=
CourseKey
.
from_string
(
course_id
)
is_credit_course
=
CreditCourse
.
is_credit_course
(
course_key
)
is_credit_course
=
CreditCourse
.
is_credit_course
(
course_key
)
if
is_credit_course
:
if
is_credit_course
:
course
=
modulestore
()
.
get_course
(
course_key
)
requirements
=
_get_course_credit_requirements
(
course_key
)
requirements
=
_get_course_credit_requirements
(
course
)
set_credit_requirements
(
course_key
,
requirements
)
set_credit_requirements
(
course_key
,
requirements
)
except
(
InvalidKeyError
,
ItemNotFoundError
,
InvalidCreditRequirements
)
as
exc
:
except
(
InvalidKeyError
,
ItemNotFoundError
,
InvalidCreditRequirements
)
as
exc
:
LOGGER
.
error
(
'Error on adding the requirements for course
%
s -
%
s'
,
course_id
,
unicode
(
exc
))
LOGGER
.
error
(
'Error on adding the requirements for course
%
s -
%
s'
,
course_id
,
unicode
(
exc
))
...
@@ -44,41 +53,40 @@ def update_credit_course_requirements(course_id): # pylint: disable=invalid-na
...
@@ -44,41 +53,40 @@ def update_credit_course_requirements(course_id): # pylint: disable=invalid-na
LOGGER
.
info
(
'Requirements added for course
%
s'
,
course_id
)
LOGGER
.
info
(
'Requirements added for course
%
s'
,
course_id
)
def
_get_course_credit_requirements
(
course
):
def
_get_course_credit_requirements
(
course_key
):
"""Returns the list of credit requirements for the given course.
"""
Returns the list of credit requirements for the given course.
It returns the minimum_grade_credit and also the ICRV checkpoints
It returns the minimum_grade_credit and also the ICRV checkpoints
if any were added in the course
if any were added in the course
Args:
Args:
course
(Course): The course object
course
_key (CourseKey): Identifier for the course.
Returns:
Returns:
List of
minimum_grade_credit and ICRV requirements
List of
credit requirements (dictionaries)
"""
"""
credit_xblock_requirements
=
_get_credit_course_requirement_xblocks
(
course
)
credit_xblock_requirements
=
_get_credit_course_requirement_xblocks
(
course
_key
)
min_grade_requirement
=
_get_min_grade_requirement
(
course
)
min_grade_requirement
=
_get_min_grade_requirement
(
course
_key
)
credit_requirements
=
min_grade_requirement
+
credit_xblock_requirements
credit_requirements
=
min_grade_requirement
+
credit_xblock_requirements
return
credit_requirements
return
credit_requirements
def
_get_min_grade_requirement
(
course
):
def
_get_min_grade_requirement
(
course_key
):
"""Get list of 'minimum_grade_credit' requirement for the given course.
"""
Get list of 'minimum_grade_credit' requirement for the given course.
Args:
Args:
course(Course): The course object
course_key (CourseKey): Identifier for the course.
Raises:
AttributeError if the course has not 'minimum_grade_credit' attribute
Returns:
Returns:
The list of minimum_grade_credit requirements
The list of minimum_grade_credit requirements
"""
"""
requirement
=
[]
course
=
modulestore
()
.
get_course
(
course_key
,
depth
=
0
)
try
:
try
:
re
quirement
=
[
re
turn
[
{
{
"namespace"
:
"grade"
,
"namespace"
:
"grade"
,
"name"
:
"grade"
,
"name"
:
"grade"
,
...
@@ -90,41 +98,46 @@ def _get_min_grade_requirement(course):
...
@@ -90,41 +98,46 @@ def _get_min_grade_requirement(course):
]
]
except
AttributeError
:
except
AttributeError
:
LOGGER
.
error
(
"The course
%
s does not has minimum_grade_credit attribute"
,
unicode
(
course
.
id
))
LOGGER
.
error
(
"The course
%
s does not has minimum_grade_credit attribute"
,
unicode
(
course
.
id
))
return
requirement
else
:
return
[]
def
_get_credit_course_requirement_xblocks
(
course
):
# pylint: disable=invalid-name
def
_get_credit_course_requirement_xblocks
(
course
_key
):
# pylint: disable=invalid-name
"""Generate a course structure dictionary for the specified course.
"""Generate a course structure dictionary for the specified course.
Args:
Args:
course
(Course): The course object
course
_key (CourseKey): Identifier for the course.
Returns:
Returns:
The list of credit requirements xblocks dicts
The list of credit requirements xblocks dicts
"""
"""
blocks_stack
=
[
course
]
requirements
=
[]
requirements_blocks
=
[]
while
blocks_stack
:
# Retrieve all XBlocks from the course that we know to be credit requirements.
curr_block
=
blocks_stack
.
pop
()
# For performance reasons, we look these up by their "category" to avoid
children
=
curr_block
.
get_children
()
if
curr_block
.
has_children
else
[]
# loading and searching the entire course tree.
if
_is_credit_requirement
(
curr_block
):
for
desc
in
CREDIT_REQUIREMENT_XBLOCKS
:
block
=
{
requirements
.
extend
([
"namespace"
:
curr_block
.
get_credit_requirement_namespace
(),
{
"name"
:
curr_block
.
get_credit_requirement_name
(),
"namespace"
:
desc
[
"requirement-namespace"
],
"display_name"
:
curr_block
.
get_credit_requirement_display_name
(),
"name"
:
block
.
get_credit_requirement_name
(),
"criteria"
:
""
,
"display_name"
:
block
.
get_credit_requirement_display_name
(),
"criteria"
:
{},
}
}
requirements_blocks
.
append
(
block
)
for
block
in
modulestore
()
.
get_items
(
course_key
,
qualifiers
=
{
"category"
:
desc
[
"category"
]}
)
if
_is_credit_requirement
(
block
)
])
# Add the children of current block to the stack so that we can
return
requirements
# traverse them as well.
blocks_stack
.
extend
(
children
)
return
requirements_blocks
def
_is_credit_requirement
(
xblock
):
def
_is_credit_requirement
(
xblock
):
"""Check if the given XBlock is a credit requirement.
"""
Check if the given XBlock is a credit requirement.
Args:
Args:
xblock(XBlock): The given XBlock object
xblock(XBlock): The given XBlock object
...
@@ -133,21 +146,18 @@ def _is_credit_requirement(xblock):
...
@@ -133,21 +146,18 @@ def _is_credit_requirement(xblock):
True if XBlock is a credit requirement else False
True if XBlock is a credit requirement else False
"""
"""
is_credit_requirement
=
False
if
not
callable
(
getattr
(
xblock
,
"get_credit_requirement_namespace"
,
None
)):
LOGGER
.
error
(
if
callable
(
getattr
(
xblock
,
"is_course_credit_requirement"
,
None
)):
"XBlock
%
s is marked as a credit requirement but does not "
is_credit_requirement
=
xblock
.
is_course_credit_requirement
()
"implement get_credit_requirement_namespace()"
,
unicode
(
xblock
)
if
is_credit_requirement
:
)
if
not
callable
(
getattr
(
xblock
,
"get_credit_requirement_namespace"
,
None
)):
return
False
is_credit_requirement
=
False
LOGGER
.
error
(
if
not
callable
(
getattr
(
xblock
,
"get_credit_requirement_name"
,
None
)):
"XBlock
%
s is marked as a credit requirement but does not "
LOGGER
.
error
(
"implement get_credit_requirement_namespace()"
,
unicode
(
xblock
)
"XBlock
%
s is marked as a credit requirement but does not "
)
"implement get_credit_requirement_name()"
,
unicode
(
xblock
)
if
not
callable
(
getattr
(
xblock
,
"get_credit_requirement_name"
,
None
)):
)
is_credit_requirement
=
False
return
False
LOGGER
.
error
(
"XBlock
%
s is marked as a credit requirement but does not "
return
True
"implement get_credit_requirement_name()"
,
unicode
(
xblock
)
)
return
is_credit_requirement
openedx/core/djangoapps/credit/tests/test_tasks.py
View file @
8824d032
...
@@ -11,7 +11,7 @@ from openedx.core.djangoapps.credit.models import CreditCourse
...
@@ -11,7 +11,7 @@ from openedx.core.djangoapps.credit.models import CreditCourse
from
openedx.core.djangoapps.credit.signals
import
listen_for_course_publish
from
openedx.core.djangoapps.credit.signals
import
listen_for_course_publish
from
xmodule.modulestore.django
import
SignalHandler
from
xmodule.modulestore.django
import
SignalHandler
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
,
check_mongo_calls
class
TestTaskExecution
(
ModuleStoreTestCase
):
class
TestTaskExecution
(
ModuleStoreTestCase
):
...
@@ -85,6 +85,13 @@ class TestTaskExecution(ModuleStoreTestCase):
...
@@ -85,6 +85,13 @@ class TestTaskExecution(ModuleStoreTestCase):
requirements
=
get_credit_requirements
(
self
.
course
.
id
)
requirements
=
get_credit_requirements
(
self
.
course
.
id
)
self
.
assertEqual
(
len
(
requirements
),
2
)
self
.
assertEqual
(
len
(
requirements
),
2
)
def
test_query_counts
(
self
):
self
.
add_credit_course
(
self
.
course
.
id
)
self
.
add_icrv_xblock
()
with
check_mongo_calls
(
3
):
listen_for_course_publish
(
self
,
self
.
course
.
id
)
@mock.patch
(
@mock.patch
(
'openedx.core.djangoapps.credit.tasks.set_credit_requirements'
,
'openedx.core.djangoapps.credit.tasks.set_credit_requirements'
,
mock
.
Mock
(
mock
.
Mock
(
...
...
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