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
40e9e20a
Commit
40e9e20a
authored
May 09, 2017
by
sanfordstudent
Committed by
GitHub
May 09, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #15078 from edx/sstudent/revert_grade_reports
Sstudent/revert grade reports
parents
5673cee9
5388d5d1
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
89 additions
and
389 deletions
+89
-389
common/djangoapps/course_modes/models.py
+0
-13
common/djangoapps/course_modes/tests/test_models.py
+1
-4
common/djangoapps/request_cache/__init__.py
+0
-10
common/djangoapps/request_cache/middleware.py
+18
-39
common/djangoapps/student/models.py
+3
-29
common/djangoapps/student/roles.py
+3
-29
lms/djangoapps/certificates/models.py
+17
-20
lms/djangoapps/certificates/tests/tests.py
+5
-4
lms/djangoapps/grades/models.py
+1
-29
lms/djangoapps/grades/new/course_grade_factory.py
+3
-6
lms/djangoapps/grades/tests/test_new.py
+3
-3
lms/djangoapps/grades/tests/test_tasks.py
+2
-2
lms/djangoapps/instructor_task/tasks_helper/grades.py
+0
-0
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
+2
-43
lms/djangoapps/verify_student/models.py
+6
-14
lms/envs/devstack.py
+0
-12
openedx/core/djangoapps/course_groups/cohorts.py
+17
-55
openedx/core/djangoapps/credit/models.py
+0
-11
openedx/core/djangoapps/credit/tests/test_api.py
+2
-2
openedx/core/djangoapps/user_api/course_tag/api.py
+0
-43
openedx/core/djangoapps/user_api/partition_schemes.py
+1
-1
openedx/core/djangoapps/user_api/tests/test_partition_schemes.py
+0
-5
openedx/core/djangoapps/verified_track_content/models.py
+4
-14
requirements/edx/base.txt
+1
-1
No files found.
common/djangoapps/course_modes/models.py
View file @
40e9e20a
...
...
@@ -9,11 +9,8 @@ from config_models.models import ConfigurationModel
from
django.core.exceptions
import
ValidationError
from
django.db
import
models
from
django.db.models
import
Q
from
django.dispatch
import
receiver
from
django.utils.translation
import
ugettext_lazy
as
_
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
from
request_cache.middleware
import
ns_request_cached
,
RequestCache
Mode
=
namedtuple
(
'Mode'
,
[
...
...
@@ -144,8 +141,6 @@ class CourseMode(models.Model):
DEFAULT_SHOPPINGCART_MODE_SLUG
=
HONOR
DEFAULT_SHOPPINGCART_MODE
=
Mode
(
HONOR
,
_
(
'Honor'
),
0
,
''
,
'usd'
,
None
,
None
,
None
,
None
)
CACHE_NAMESPACE
=
u"course_modes.CourseMode.cache."
class
Meta
(
object
):
unique_together
=
(
'course_id'
,
'mode_slug'
,
'currency'
)
...
...
@@ -270,7 +265,6 @@ class CourseMode(models.Model):
return
[
mode
.
to_tuple
()
for
mode
in
found_course_modes
]
@classmethod
@ns_request_cached
(
CACHE_NAMESPACE
)
def
modes_for_course
(
cls
,
course_id
,
include_expired
=
False
,
only_selectable
=
True
):
"""
Returns a list of the non-expired modes for a given course id
...
...
@@ -672,13 +666,6 @@ class CourseMode(models.Model):
)
@receiver
(
models
.
signals
.
post_save
,
sender
=
CourseMode
)
@receiver
(
models
.
signals
.
post_delete
,
sender
=
CourseMode
)
def
invalidate_course_mode_cache
(
sender
,
**
kwargs
):
# pylint: disable=unused-argument
"""Invalidate the cache of course modes. """
RequestCache
.
clear_request_cache
(
name
=
CourseMode
.
CACHE_NAMESPACE
)
class
CourseModesArchive
(
models
.
Model
):
"""
Store the past values of course_mode that a course had in the past. We decided on having
...
...
common/djangoapps/course_modes/tests/test_models.py
View file @
40e9e20a
...
...
@@ -16,7 +16,7 @@ from opaque_keys.edx.locator import CourseLocator
import
pytz
from
course_modes.helpers
import
enrollment_mode_display
from
course_modes.models
import
CourseMode
,
Mode
,
invalidate_course_mode_cache
from
course_modes.models
import
CourseMode
,
Mode
from
course_modes.tests.factories
import
CourseModeFactory
...
...
@@ -31,9 +31,6 @@ class CourseModeModelTest(TestCase):
self
.
course_key
=
SlashSeparatedCourseKey
(
'Test'
,
'TestCourse'
,
'TestCourseRun'
)
CourseMode
.
objects
.
all
()
.
delete
()
def
tearDown
(
self
):
invalidate_course_mode_cache
(
sender
=
None
)
def
create_mode
(
self
,
mode_slug
,
...
...
common/djangoapps/request_cache/__init__.py
View file @
40e9e20a
...
...
@@ -41,16 +41,6 @@ def get_cache(name):
return
middleware
.
RequestCache
.
get_request_cache
(
name
)
def
clear_cache
(
name
):
"""
Clears the request cache named ``name``.
Arguments:
name (str): The name of the request cache to clear
"""
return
middleware
.
RequestCache
.
clear_request_cache
(
name
)
def
get_request
():
"""
Return the current request.
...
...
common/djangoapps/request_cache/middleware.py
View file @
40e9e20a
...
...
@@ -39,14 +39,11 @@ class RequestCache(object):
return
crum
.
get_current_request
()
@classmethod
def
clear_request_cache
(
cls
,
name
=
None
):
def
clear_request_cache
(
cls
):
"""
Empty the request cache.
"""
if
name
is
None
:
REQUEST_CACHE
.
data
=
{}
elif
REQUEST_CACHE
.
data
.
get
(
name
):
REQUEST_CACHE
.
data
[
name
]
=
{}
REQUEST_CACHE
.
data
=
{}
def
process_request
(
self
,
request
):
self
.
clear_request_cache
()
...
...
@@ -85,43 +82,25 @@ def request_cached(f):
cache the value it returns, and return that cached value for subsequent calls with the
same args/kwargs within a single request
"""
return
ns_request_cached
()(
f
)
def
wrapper
(
*
args
,
**
kwargs
):
"""
Wrapper function to decorate with.
"""
# Check to see if we have a result in cache. If not, invoke our wrapped
# function. Cache and return the result to the caller.
rcache
=
RequestCache
.
get_request_cache
()
cache_key
=
func_call_cache_key
(
f
,
*
args
,
**
kwargs
)
def
ns_request_cached
(
namespace
=
None
):
"""
Same as request_cached above, except an optional namespace can be passed in to compartmentalize the cache.
if
cache_key
in
rcache
.
data
:
return
rcache
.
data
.
get
(
cache_key
)
else
:
result
=
f
(
*
args
,
**
kwargs
)
rcache
.
data
[
cache_key
]
=
result
Arguments:
namespace (string): An optional namespace to use for the cache. Useful if the caller wants to manage
their own sub-cache by, for example, calling RequestCache.clear_request_cache for their own namespace.
"""
def
outer_wrapper
(
f
):
"""
Outer wrapper that decorates the given function
return
result
Arguments:
f (func): the function to wrap
"""
def
inner_wrapper
(
*
args
,
**
kwargs
):
"""
Wrapper function to decorate with.
"""
# Check to see if we have a result in cache. If not, invoke our wrapped
# function. Cache and return the result to the caller.
rcache
=
RequestCache
.
get_request_cache
(
namespace
)
rcache
=
rcache
.
data
if
namespace
is
None
else
rcache
cache_key
=
func_call_cache_key
(
f
,
*
args
,
**
kwargs
)
if
cache_key
in
rcache
:
return
rcache
.
get
(
cache_key
)
else
:
result
=
f
(
*
args
,
**
kwargs
)
rcache
[
cache_key
]
=
result
return
result
return
inner_wrapper
return
outer_wrapper
wrapper
.
request_cached_contained_func
=
f
return
wrapper
def
func_call_cache_key
(
func
,
*
args
,
**
kwargs
):
...
...
common/djangoapps/student/models.py
View file @
40e9e20a
...
...
@@ -998,9 +998,7 @@ class CourseEnrollment(models.Model):
history
=
HistoricalRecords
()
# cache key format e.g enrollment.<username>.<course_key>.mode = 'honor'
COURSE_ENROLLMENT_CACHE_KEY
=
u"enrollment.{}.{}.mode"
# TODO Can this be removed? It doesn't seem to be used.
MODE_CACHE_NAMESPACE
=
u'CourseEnrollment.mode_and_active'
COURSE_ENROLLMENT_CACHE_KEY
=
u"enrollment.{}.{}.mode"
class
Meta
(
object
):
unique_together
=
((
'user'
,
'course_id'
),)
...
...
@@ -1700,27 +1698,11 @@ class CourseEnrollment(models.Model):
return
enrollment_state
@classmethod
def
bulk_fetch_enrollment_states
(
cls
,
users
,
course_key
):
"""
Bulk pre-fetches the enrollment states for the given users
for the given course.
"""
# before populating the cache with another bulk set of data,
# remove previously cached entries to keep memory usage low.
request_cache
.
clear_cache
(
cls
.
MODE_CACHE_NAMESPACE
)
records
=
cls
.
objects
.
filter
(
user__in
=
users
,
course_id
=
course_key
)
.
select_related
(
'user__id'
)
cache
=
cls
.
_get_mode_active_request_cache
()
for
record
in
records
:
enrollment_state
=
CourseEnrollmentState
(
record
.
mode
,
record
.
is_active
)
cls
.
_update_enrollment
(
cache
,
record
.
user
.
id
,
course_key
,
enrollment_state
)
@classmethod
def
_get_mode_active_request_cache
(
cls
):
"""
Returns the request-specific cache for CourseEnrollment
"""
return
request_cache
.
get_cache
(
cls
.
MODE_CACHE_NAMESPACE
)
return
request_cache
.
get_cache
(
'CourseEnrollment.mode_and_active'
)
@classmethod
def
_get_enrollment_in_request_cache
(
cls
,
user
,
course_key
):
...
...
@@ -1736,15 +1718,7 @@ class CourseEnrollment(models.Model):
Updates the cached value for the user's enrollment in the
request cache.
"""
cls
.
_update_enrollment
(
cls
.
_get_mode_active_request_cache
(),
user
.
id
,
course_key
,
enrollment_state
)
@classmethod
def
_update_enrollment
(
cls
,
cache
,
user_id
,
course_key
,
enrollment_state
):
"""
Updates the cached value for the user's enrollment in the
given cache.
"""
cache
[(
user_id
,
course_key
)]
=
enrollment_state
cls
.
_get_mode_active_request_cache
()[(
user
.
id
,
course_key
)]
=
enrollment_state
@receiver
(
models
.
signals
.
post_save
,
sender
=
CourseEnrollment
)
...
...
common/djangoapps/student/roles.py
View file @
40e9e20a
...
...
@@ -4,12 +4,10 @@ adding users, removing users, and listing members
"""
from
abc
import
ABCMeta
,
abstractmethod
from
collections
import
defaultdict
from
django.contrib.auth.models
import
User
import
logging
from
request_cache
import
get_cache
from
student.models
import
CourseAccessRole
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
...
...
@@ -36,38 +34,14 @@ def register_access_role(cls):
return
cls
class
BulkRoleCache
(
object
):
CACHE_NAMESPACE
=
u"student.roles.BulkRoleCache"
CACHE_KEY
=
u'roles_by_user'
@classmethod
def
prefetch
(
cls
,
users
):
roles_by_user
=
defaultdict
(
set
)
get_cache
(
cls
.
CACHE_NAMESPACE
)[
cls
.
CACHE_KEY
]
=
roles_by_user
for
role
in
CourseAccessRole
.
objects
.
filter
(
user__in
=
users
)
.
select_related
(
'user__id'
):
roles_by_user
[
role
.
user
.
id
]
.
add
(
role
)
users_without_roles
=
filter
(
lambda
u
:
u
.
id
not
in
roles_by_user
,
users
)
for
user
in
users_without_roles
:
roles_by_user
[
user
.
id
]
=
set
()
@classmethod
def
get_user_roles
(
cls
,
user
):
return
get_cache
(
cls
.
CACHE_NAMESPACE
)[
cls
.
CACHE_KEY
][
user
.
id
]
class
RoleCache
(
object
):
"""
A cache of the CourseAccessRoles held by a particular user
"""
def
__init__
(
self
,
user
):
try
:
self
.
_roles
=
BulkRoleCache
.
get_user_roles
(
user
)
except
KeyError
:
self
.
_roles
=
set
(
CourseAccessRole
.
objects
.
filter
(
user
=
user
)
.
all
()
)
self
.
_roles
=
set
(
CourseAccessRole
.
objects
.
filter
(
user
=
user
)
.
all
()
)
def
has_role
(
self
,
role
,
course_id
,
org
):
"""
...
...
lms/djangoapps/certificates/models.py
View file @
40e9e20a
...
...
@@ -493,18 +493,6 @@ def handle_course_cert_awarded(sender, user, course_key, **kwargs): # pylint: d
def
certificate_status_for_student
(
student
,
course_id
):
"""
This returns a dictionary with a key for status, and other information.
See certificate_status for more information.
"""
try
:
generated_certificate
=
GeneratedCertificate
.
objects
.
get
(
user
=
student
,
course_id
=
course_id
)
except
GeneratedCertificate
.
DoesNotExist
:
generated_certificate
=
None
return
certificate_status
(
generated_certificate
)
def
certificate_status
(
generated_certificate
):
'''
This returns a dictionary with a key for status, and other information.
The status is one of the following:
...
...
@@ -539,7 +527,9 @@ def certificate_status(generated_certificate):
# the course_modes app is loaded, resulting in a Django deprecation warning.
from
course_modes.models
import
CourseMode
if
generated_certificate
:
try
:
generated_certificate
=
GeneratedCertificate
.
objects
.
get
(
# pylint: disable=no-member
user
=
student
,
course_id
=
course_id
)
cert_status
=
{
'status'
:
generated_certificate
.
status
,
'mode'
:
generated_certificate
.
mode
,
...
...
@@ -549,7 +539,7 @@ def certificate_status(generated_certificate):
cert_status
[
'grade'
]
=
generated_certificate
.
grade
if
generated_certificate
.
mode
==
'audit'
:
course_mode_slugs
=
[
mode
.
slug
for
mode
in
CourseMode
.
modes_for_course
(
generated_certificate
.
course_id
)]
course_mode_slugs
=
[
mode
.
slug
for
mode
in
CourseMode
.
modes_for_course
(
course_id
)]
# Short term fix to make sure old audit users with certs still see their certs
# only do this if there if no honor mode
if
'honor'
not
in
course_mode_slugs
:
...
...
@@ -560,24 +550,31 @@ def certificate_status(generated_certificate):
cert_status
[
'download_url'
]
=
generated_certificate
.
download_url
return
cert_status
else
:
return
{
'status'
:
CertificateStatuses
.
unavailable
,
'mode'
:
GeneratedCertificate
.
MODES
.
honor
,
'uuid'
:
None
}
except
GeneratedCertificate
.
DoesNotExist
:
pass
return
{
'status'
:
CertificateStatuses
.
unavailable
,
'mode'
:
GeneratedCertificate
.
MODES
.
honor
,
'uuid'
:
None
}
def
certificate_info_for_user
(
user
,
grade
,
user_is_whitelisted
,
user_certificat
e
):
def
certificate_info_for_user
(
user
,
course_id
,
grade
,
user_is_whitelisted
=
Non
e
):
"""
Returns the certificate info for a user for grade report.
"""
if
user_is_whitelisted
is
None
:
user_is_whitelisted
=
CertificateWhitelist
.
objects
.
filter
(
user
=
user
,
course_id
=
course_id
,
whitelist
=
True
)
.
exists
()
certificate_is_delivered
=
'N'
certificate_type
=
'N/A'
eligible_for_certificate
=
'Y'
if
(
user_is_whitelisted
or
grade
is
not
None
)
and
user
.
profile
.
allow_certificate
\
else
'N'
status
=
certificate_status
(
user_certificate
)
certificate_generated
=
status
[
'status'
]
==
CertificateStatuses
.
downloadable
certificate_status
=
certificate_status_for_student
(
user
,
course_id
)
certificate_generated
=
certificate_
status
[
'status'
]
==
CertificateStatuses
.
downloadable
if
certificate_generated
:
certificate_is_delivered
=
'Y'
certificate_type
=
status
[
'mode'
]
certificate_type
=
certificate_
status
[
'mode'
]
return
[
eligible_for_certificate
,
certificate_is_delivered
,
certificate_type
]
...
...
lms/djangoapps/certificates/tests/tests.py
View file @
40e9e20a
...
...
@@ -54,11 +54,11 @@ class CertificatesModelTest(ModuleStoreTestCase, MilestonesTestCaseMixin):
Verify that certificate_info_for_user works.
"""
student
=
UserFactory
()
_
=
CourseFactory
.
create
(
org
=
'edx'
,
number
=
'verified'
,
display_name
=
'Verified Course'
)
course
=
CourseFactory
.
create
(
org
=
'edx'
,
number
=
'verified'
,
display_name
=
'Verified Course'
)
student
.
profile
.
allow_certificate
=
allow_certificate
student
.
profile
.
save
()
certificate_info
=
certificate_info_for_user
(
student
,
grade
,
whitelisted
,
user_certificate
=
None
)
certificate_info
=
certificate_info_for_user
(
student
,
course
.
id
,
grade
,
whitelisted
)
self
.
assertEqual
(
certificate_info
,
output
)
@unpack
...
...
@@ -81,13 +81,14 @@ class CertificatesModelTest(ModuleStoreTestCase, MilestonesTestCaseMixin):
student
.
profile
.
allow_certificate
=
allow_certificate
student
.
profile
.
save
()
certificate
=
GeneratedCertificateFactory
.
create
(
GeneratedCertificateFactory
.
create
(
user
=
student
,
course_id
=
course
.
id
,
status
=
CertificateStatuses
.
downloadable
,
mode
=
'honor'
)
certificate_info
=
certificate_info_for_user
(
student
,
grade
,
whitelisted
,
certificate
)
certificate_info
=
certificate_info_for_user
(
student
,
course
.
id
,
grade
,
whitelisted
)
self
.
assertEqual
(
certificate_info
,
output
)
def
test_course_ids_with_certs_for_user
(
self
):
...
...
lms/djangoapps/grades/models.py
View file @
40e9e20a
...
...
@@ -25,7 +25,6 @@ from track.event_transaction_utils import get_event_transaction_id, get_event_tr
from
coursewarehistoryextended.fields
import
UnsignedBigIntAutoField
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
,
UsageKeyField
from
request_cache
import
get_cache
from
.config
import
waffle
...
...
@@ -523,8 +522,6 @@ class PersistentCourseGrade(DeleteGradesMixin, TimeStampedModel):
# Information related to course completion
passed_timestamp
=
models
.
DateTimeField
(
u'Date learner earned a passing grade'
,
blank
=
True
,
null
=
True
)
CACHE_NAMESPACE
=
u"grades.models.PersistentCourseGrade"
def
__unicode__
(
self
):
"""
Returns a string representation of this model.
...
...
@@ -539,21 +536,6 @@ class PersistentCourseGrade(DeleteGradesMixin, TimeStampedModel):
])
@classmethod
def
_cache_key
(
cls
,
course_id
):
return
u"grades_cache.{}"
.
format
(
course_id
)
@classmethod
def
prefetch
(
cls
,
course_id
,
users
):
"""
Prefetches grades for the given users for the given course.
"""
get_cache
(
cls
.
CACHE_NAMESPACE
)[
cls
.
_cache_key
(
course_id
)]
=
{
grade
.
user_id
:
grade
for
grade
in
cls
.
objects
.
filter
(
user_id__in
=
[
user
.
id
for
user
in
users
],
course_id
=
course_id
)
}
@classmethod
def
read
(
cls
,
user_id
,
course_id
):
"""
Reads a grade from database
...
...
@@ -564,17 +546,7 @@ class PersistentCourseGrade(DeleteGradesMixin, TimeStampedModel):
Raises PersistentCourseGrade.DoesNotExist if applicable
"""
try
:
prefetched_grades
=
get_cache
(
cls
.
CACHE_NAMESPACE
)[
cls
.
_cache_key
(
course_id
)]
try
:
return
prefetched_grades
[
user_id
]
except
KeyError
:
# user's grade is not in the prefetched list, so
# assume they have no grade
raise
cls
.
DoesNotExist
except
KeyError
:
# grades were not prefetched for the course, so fetch it
return
cls
.
objects
.
get
(
user_id
=
user_id
,
course_id
=
course_id
)
return
cls
.
objects
.
get
(
user_id
=
user_id
,
course_id
=
course_id
)
@classmethod
def
update_or_create
(
cls
,
user_id
,
course_id
,
**
kwargs
):
...
...
lms/djangoapps/grades/new/course_grade_factory.py
View file @
40e9e20a
...
...
@@ -81,6 +81,7 @@ class CourseGradeFactory(object):
users
,
course
=
None
,
collected_block_structure
=
None
,
course_structure
=
None
,
course_key
=
None
,
force_update
=
False
,
):
...
...
@@ -98,9 +99,7 @@ class CourseGradeFactory(object):
# compute the grade for all students.
# 2. Optimization: the collected course_structure is not
# retrieved from the data store multiple times.
course_data
=
CourseData
(
user
=
None
,
course
=
course
,
collected_block_structure
=
collected_block_structure
,
course_key
=
course_key
,
)
course_data
=
CourseData
(
None
,
course
,
collected_block_structure
,
course_structure
,
course_key
)
for
user
in
users
:
with
dog_stats_api
.
timer
(
'lms.grades.CourseGradeFactory.iter'
,
...
...
@@ -108,9 +107,7 @@ class CourseGradeFactory(object):
):
try
:
method
=
CourseGradeFactory
()
.
update
if
force_update
else
CourseGradeFactory
()
.
create
course_grade
=
method
(
user
,
course_data
.
course
,
course_data
.
collected_structure
,
course_key
=
course_key
,
)
course_grade
=
method
(
user
,
course
,
course_data
.
collected_structure
,
course_structure
,
course_key
)
yield
self
.
GradeResult
(
user
,
course_grade
,
None
)
except
Exception
as
exc
:
# pylint: disable=broad-except
...
...
lms/djangoapps/grades/tests/test_new.py
View file @
40e9e20a
...
...
@@ -178,10 +178,10 @@ class TestCourseGradeFactory(GradeTestBase):
self
.
assertEqual
(
course_grade
.
letter_grade
,
u'Pass'
if
expected_pass
else
None
)
self
.
assertEqual
(
course_grade
.
percent
,
0.5
)
with
self
.
assertNumQueries
(
1
1
),
mock_get_score
(
1
,
2
):
with
self
.
assertNumQueries
(
1
2
),
mock_get_score
(
1
,
2
):
_assert_create
(
expected_pass
=
True
)
with
self
.
assertNumQueries
(
1
3
),
mock_get_score
(
1
,
2
):
with
self
.
assertNumQueries
(
1
5
),
mock_get_score
(
1
,
2
):
grade_factory
.
update
(
self
.
request
.
user
,
self
.
course
)
with
self
.
assertNumQueries
(
1
):
...
...
@@ -189,7 +189,7 @@ class TestCourseGradeFactory(GradeTestBase):
self
.
_update_grading_policy
(
passing
=
0.9
)
with
self
.
assertNumQueries
(
6
):
with
self
.
assertNumQueries
(
8
):
_assert_create
(
expected_pass
=
False
)
@ddt.data
(
True
,
False
)
...
...
lms/djangoapps/grades/tests/test_tasks.py
View file @
40e9e20a
...
...
@@ -409,8 +409,8 @@ class ComputeGradesForCourseTest(HasCourseWithProblemsMixin, ModuleStoreTestCase
@ddt.data
(
*
xrange
(
1
,
12
,
3
))
def
test_database_calls
(
self
,
batch_size
):
per_user_queries
=
1
5
*
min
(
batch_size
,
6
)
# No more than 6 due to offset
with
self
.
assertNumQueries
(
6
+
per_user_queries
):
per_user_queries
=
1
7
*
min
(
batch_size
,
6
)
# No more than 6 due to offset
with
self
.
assertNumQueries
(
5
+
per_user_queries
):
with
check_mongo_calls
(
1
):
compute_grades_for_course_v2
.
delay
(
course_key
=
six
.
text_type
(
self
.
course
.
id
),
...
...
lms/djangoapps/instructor_task/tasks_helper/grades.py
View file @
40e9e20a
This diff is collapsed.
Click to expand it.
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
View file @
40e9e20a
...
...
@@ -34,11 +34,9 @@ from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMe
from
lms.djangoapps.verify_student.tests.factories
import
SoftwareSecurePhotoVerificationFactory
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroupPartitionGroup
,
CohortMembership
from
openedx.core.djangoapps.course_groups.tests.helpers
import
CohortFactory
from
openedx.core.djangoapps.credit.tests.factories
import
CreditCourseFactory
import
openedx.core.djangoapps.user_api.course_tag.api
as
course_tag_api
from
openedx.core.djangoapps.user_api.partition_schemes
import
RandomUserPartitionScheme
from
openedx.core.djangoapps.util.testing
import
ContentGroupTestCase
,
TestConditionalContent
from
request_cache.middleware
import
RequestCache
from
shoppingcart.models
import
(
Order
,
PaidCourseRegistration
,
CourseRegistrationCode
,
Invoice
,
CourseRegistrationCodeInvoiceItem
,
InvoiceTransaction
,
Coupon
...
...
@@ -46,9 +44,8 @@ from shoppingcart.models import (
from
student.models
import
CourseEnrollment
,
CourseEnrollmentAllowed
,
ManualEnrollmentAudit
,
ALLOWEDTOENROLL_TO_ENROLLED
from
student.tests.factories
import
CourseEnrollmentFactory
,
CourseModeFactory
,
UserFactory
from
survey.models
import
SurveyForm
,
SurveyAnswer
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
,
check_mongo_calls
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
..models
import
ReportStore
...
...
@@ -324,44 +321,6 @@ class TestInstructorGradeReport(InstructorGradeReportTestCase):
result
=
CourseGradeReport
.
generate
(
None
,
None
,
self
.
course
.
id
,
None
,
'graded'
)
self
.
assertDictContainsSubset
({
'attempted'
:
1
,
'succeeded'
:
1
,
'failed'
:
0
},
result
)
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
4
),
(
ModuleStoreEnum
.
Type
.
split
,
3
),
)
@ddt.unpack
def
test_query_counts
(
self
,
store_type
,
mongo_count
):
with
self
.
store
.
default_store
(
store_type
):
experiment_group_a
=
Group
(
2
,
u'Expériment Group A'
)
experiment_group_b
=
Group
(
3
,
u'Expériment Group B'
)
experiment_partition
=
UserPartition
(
1
,
u'Content Expériment Configuration'
,
u'Group Configuration for Content Expériments'
,
[
experiment_group_a
,
experiment_group_b
],
scheme_id
=
'random'
)
course
=
CourseFactory
.
create
(
cohort_config
=
{
'cohorted'
:
True
,
'auto_cohort'
:
True
,
'auto_cohort_groups'
:
[
'cohort 1'
,
'cohort 2'
]},
user_partitions
=
[
experiment_partition
],
teams_configuration
=
{
'max_size'
:
2
,
'topics'
:
[{
'topic-id'
:
'topic'
,
'name'
:
'Topic'
,
'description'
:
'A Topic'
}]
},
)
_
=
CreditCourseFactory
(
course_key
=
course
.
id
)
num_users
=
5
for
_
in
range
(
num_users
):
user
=
UserFactory
.
create
()
CourseEnrollment
.
enroll
(
user
,
course
.
id
,
mode
=
'verified'
)
SoftwareSecurePhotoVerificationFactory
.
create
(
user
=
user
,
status
=
'approved'
)
RequestCache
.
clear_request_cache
()
with
patch
(
'lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'
):
with
check_mongo_calls
(
mongo_count
):
with
self
.
assertNumQueries
(
41
):
CourseGradeReport
.
generate
(
None
,
None
,
course
.
id
,
None
,
'graded'
)
class
TestTeamGradeReport
(
InstructorGradeReportTestCase
):
""" Test that teams appear correctly in the grade report when it is enabled for the course. """
...
...
@@ -1824,7 +1783,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
'failed'
:
3
,
'skipped'
:
2
}
with
self
.
assertNumQueries
(
1
71
):
with
self
.
assertNumQueries
(
1
86
):
self
.
assertCertificatesGenerated
(
task_input
,
expected_results
)
expected_results
=
{
...
...
lms/djangoapps/verify_student/models.py
View file @
40e9e20a
...
...
@@ -210,19 +210,12 @@ class PhotoVerification(StatusModel):
This will check for the user's *initial* verification.
"""
return
cls
.
verified_query
(
earliest_allowed_date
)
.
filter
(
user
=
user
)
.
exists
()
@classmethod
def
verified_query
(
cls
,
earliest_allowed_date
=
None
):
"""
Return a query set for all records with 'approved' state
that are still valid according to the earliest_allowed_date
value or policy settings.
"""
return
cls
.
objects
.
filter
(
user
=
user
,
status
=
"approved"
,
created_at__gte
=
(
earliest_allowed_date
or
cls
.
_earliest_allowed_date
()),
)
created_at__gte
=
(
earliest_allowed_date
or
cls
.
_earliest_allowed_date
())
)
.
exists
()
@classmethod
def
verification_valid_or_pending
(
cls
,
user
,
earliest_allowed_date
=
None
,
queryset
=
None
):
...
...
@@ -958,15 +951,14 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
return
response
@classmethod
def
verification_status_for_user
(
cls
,
user
,
course_id
,
user_enrollment_mode
,
user_is_verified
=
None
):
def
verification_status_for_user
(
cls
,
user
,
course_id
,
user_enrollment_mode
):
"""
Returns the verification status for use in grade report.
"""
if
user_enrollment_mode
not
in
CourseMode
.
VERIFIED_MODES
:
return
'N/A'
if
user_is_verified
is
None
:
user_is_verified
=
cls
.
user_is_verified
(
user
)
user_is_verified
=
cls
.
user_is_verified
(
user
)
if
not
user_is_verified
:
return
'Not ID Verified'
...
...
lms/envs/devstack.py
View file @
40e9e20a
...
...
@@ -261,18 +261,6 @@ JWT_AUTH.update({
'JWT_AUDIENCE'
:
'lms-key'
,
})
# TODO: TNL-6546: Remove this waffle and flag code.
from
django.db.utils
import
ProgrammingError
from
waffle.models
import
Flag
try
:
flag
,
created
=
Flag
.
objects
.
get_or_create
(
name
=
'unified_course_view'
)
flag
.
everyone
=
True
flag
.
save
()
WAFFLE_OVERRIDE
=
True
except
ProgrammingError
:
# during initial reset_db, the table for the flag doesn't yet exist.
pass
#####################################################################
# See if the developer has any local overrides.
if
os
.
path
.
isfile
(
join
(
dirname
(
abspath
(
__file__
)),
'private.py'
)):
...
...
openedx/core/djangoapps/course_groups/cohorts.py
View file @
40e9e20a
...
...
@@ -14,8 +14,7 @@ from django.utils.translation import ugettext as _
from
courseware
import
courses
from
eventtracking
import
tracker
import
request_cache
from
request_cache.middleware
import
request_cached
from
request_cache.middleware
import
RequestCache
,
request_cached
from
student.models
import
get_user_by_username_or_email
from
.models
import
(
...
...
@@ -147,45 +146,8 @@ def get_cohorted_commentables(course_key):
return
ans
COHORT_CACHE_NAMESPACE
=
u"cohorts.get_cohort"
def
_cohort_cache_key
(
user_id
,
course_key
):
"""
Returns the cache key for the given user_id and course_key.
"""
return
u"{}.{}"
.
format
(
user_id
,
course_key
)
def
bulk_cache_cohorts
(
course_key
,
users
):
"""
Pre-fetches and caches the cohort assignments for the
given users, for later fast retrieval by get_cohort.
"""
# before populating the cache with another bulk set of data,
# remove previously cached entries to keep memory usage low.
request_cache
.
clear_cache
(
COHORT_CACHE_NAMESPACE
)
cache
=
request_cache
.
get_cache
(
COHORT_CACHE_NAMESPACE
)
if
is_course_cohorted
(
course_key
):
cohorts_by_user
=
{
membership
.
user
:
membership
for
membership
in
CohortMembership
.
objects
.
filter
(
user__in
=
users
,
course_id
=
course_key
)
.
select_related
(
'user__id'
)
}
for
user
,
membership
in
cohorts_by_user
.
iteritems
():
cache
[
_cohort_cache_key
(
user
.
id
,
course_key
)]
=
membership
.
course_user_group
uncohorted_users
=
filter
(
lambda
u
:
u
not
in
cohorts_by_user
,
users
)
else
:
uncohorted_users
=
users
for
user
in
uncohorted_users
:
cache
[
_cohort_cache_key
(
user
.
id
,
course_key
)]
=
None
def
get_cohort
(
user
,
course_key
,
assign
=
True
,
use_cached
=
False
):
"""
Returns the user's cohort for the specified course.
"""Returns the user's cohort for the specified course.
The cohort for the user is cached for the duration of a request. Pass
use_cached=True to use the cached value instead of fetching from the
...
...
@@ -204,19 +166,19 @@ def get_cohort(user, course_key, assign=True, use_cached=False):
Raises:
ValueError if the CourseKey doesn't exist.
"""
cache
=
request_cache
.
get_cache
(
COHORT_CACHE_NAMESPACE
)
cache_key
=
_cohort_cache_key
(
user
.
id
,
course_key
)
request_cache
=
RequestCache
.
get_request_cache
(
)
cache_key
=
u"cohorts.get_cohort.{}.{}"
.
format
(
user
.
id
,
course_key
)
if
use_cached
and
cache_key
in
cache
:
return
cache
[
cache_key
]
if
use_cached
and
cache_key
in
request_cache
.
data
:
return
request_cache
.
data
[
cache_key
]
cache
.
pop
(
cache_key
,
None
)
request_cache
.
data
.
pop
(
cache_key
,
None
)
# First check whether the course is cohorted (users shouldn't be in a cohort
# in non-cohorted courses, but settings can change after course starts)
course_cohort_settings
=
get_course_cohort_settings
(
course_key
)
if
not
course_cohort_settings
.
is_cohorted
:
return
cache
.
setdefault
(
cache_key
,
None
)
return
request_cache
.
data
.
setdefault
(
cache_key
,
None
)
# If course is cohorted, check if the user already has a cohort.
try
:
...
...
@@ -224,7 +186,7 @@ def get_cohort(user, course_key, assign=True, use_cached=False):
course_id
=
course_key
,
user_id
=
user
.
id
,
)
return
cache
.
setdefault
(
cache_key
,
membership
.
course_user_group
)
return
request_cache
.
data
.
setdefault
(
cache_key
,
membership
.
course_user_group
)
except
CohortMembership
.
DoesNotExist
:
# Didn't find the group. If we do not want to assign, return here.
if
not
assign
:
...
...
@@ -239,7 +201,7 @@ def get_cohort(user, course_key, assign=True, use_cached=False):
user
=
user
,
course_user_group
=
get_random_cohort
(
course_key
)
)
return
cache
.
setdefault
(
cache_key
,
membership
.
course_user_group
)
return
request_cache
.
data
.
setdefault
(
cache_key
,
membership
.
course_user_group
)
except
IntegrityError
as
integrity_error
:
# An IntegrityError is raised when multiple workers attempt to
# create the same row in one of the cohort model entries:
...
...
@@ -457,21 +419,21 @@ def get_group_info_for_cohort(cohort, use_cached=False):
use_cached=True to use the cached value instead of fetching from the
database.
"""
cache
=
request_cache
.
get_cache
(
u"cohorts.get_group_info_for_cohort"
)
cache_key
=
u
nicode
(
cohort
.
id
)
request_cache
=
RequestCache
.
get_request_cache
(
)
cache_key
=
u
"cohorts.get_group_info_for_cohort.{}"
.
format
(
cohort
.
id
)
if
use_cached
and
cache_key
in
cache
:
return
cache
[
cache_key
]
if
use_cached
and
cache_key
in
request_cache
.
data
:
return
request_cache
.
data
[
cache_key
]
cache
.
pop
(
cache_key
,
None
)
request_cache
.
data
.
pop
(
cache_key
,
None
)
try
:
partition_group
=
CourseUserGroupPartitionGroup
.
objects
.
get
(
course_user_group
=
cohort
)
return
cache
.
setdefault
(
cache_key
,
(
partition_group
.
group_id
,
partition_group
.
partition_id
))
return
request_cache
.
data
.
setdefault
(
cache_key
,
(
partition_group
.
group_id
,
partition_group
.
partition_id
))
except
CourseUserGroupPartitionGroup
.
DoesNotExist
:
pass
return
cache
.
setdefault
(
cache_key
,
(
None
,
None
))
return
request_cache
.
data
.
setdefault
(
cache_key
,
(
None
,
None
))
def
set_assignment_type
(
user_group
,
assignment_type
):
...
...
openedx/core/djangoapps/credit/models.py
View file @
40e9e20a
...
...
@@ -22,7 +22,6 @@ from model_utils.models import TimeStampedModel
import
pytz
from
simple_history.models
import
HistoricalRecords
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
from
request_cache.middleware
import
ns_request_cached
,
RequestCache
CREDIT_PROVIDER_ID_REGEX
=
r"[a-z,A-Z,0-9,\-]+"
...
...
@@ -291,8 +290,6 @@ class CreditRequirement(TimeStampedModel):
criteria
=
JSONField
()
active
=
models
.
BooleanField
(
default
=
True
)
CACHE_NAMESPACE
=
u"credit.CreditRequirement.cache."
class
Meta
(
object
):
unique_together
=
(
'namespace'
,
'name'
,
'course'
)
ordering
=
[
"order"
]
...
...
@@ -334,7 +331,6 @@ class CreditRequirement(TimeStampedModel):
return
credit_requirement
,
created
@classmethod
@ns_request_cached
(
CACHE_NAMESPACE
)
def
get_course_requirements
(
cls
,
course_key
,
namespace
=
None
,
name
=
None
):
"""
Get credit requirements of a given course.
...
...
@@ -396,13 +392,6 @@ class CreditRequirement(TimeStampedModel):
return
None
@receiver
(
models
.
signals
.
post_save
,
sender
=
CreditRequirement
)
@receiver
(
models
.
signals
.
post_delete
,
sender
=
CreditRequirement
)
def
invalidate_credit_requirement_cache
(
sender
,
**
kwargs
):
# pylint: disable=unused-argument
"""Invalidate the cache of credit requirements. """
RequestCache
.
clear_request_cache
(
name
=
CreditRequirement
.
CACHE_NAMESPACE
)
class
CreditRequirementStatus
(
TimeStampedModel
):
"""
This model represents the status of each requirement.
...
...
openedx/core/djangoapps/credit/tests/test_api.py
View file @
40e9e20a
...
...
@@ -664,7 +664,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
self
.
assertFalse
(
api
.
is_user_eligible_for_credit
(
user
.
username
,
self
.
course_key
))
# Satisfy the other requirement
with
self
.
assertNumQueries
(
2
4
):
with
self
.
assertNumQueries
(
2
5
):
api
.
set_credit_requirement_status
(
user
,
self
.
course_key
,
...
...
@@ -718,7 +718,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
# Delete the eligibility entries and satisfy the user's eligibility
# requirement again to trigger eligibility notification
CreditEligibility
.
objects
.
all
()
.
delete
()
with
self
.
assertNumQueries
(
1
6
):
with
self
.
assertNumQueries
(
1
7
):
api
.
set_credit_requirement_status
(
user
,
self
.
course_key
,
...
...
openedx/core/djangoapps/user_api/course_tag/api.py
View file @
40e9e20a
...
...
@@ -7,8 +7,6 @@ Stores global metadata using the UserPreference model, and per-course metadata u
UserCourseTag model.
"""
from
collections
import
defaultdict
from
request_cache
import
get_cache
from
..models
import
UserCourseTag
# Scopes
...
...
@@ -17,42 +15,6 @@ from ..models import UserCourseTag
COURSE_SCOPE
=
'course'
class
BulkCourseTags
(
object
):
CACHE_NAMESPACE
=
u'user_api.course_tag.api'
@classmethod
def
prefetch
(
cls
,
course_id
,
users
):
"""
Prefetches the value of the course tags for the specified users
for the specified course_id.
Args:
users: iterator of User objects
course_id: course identifier (CourseKey)
Returns:
course_tags: a dict of dicts,
where the primary key is the user's id
and the secondary key is the course tag's key
"""
course_tags
=
defaultdict
(
dict
)
for
tag
in
UserCourseTag
.
objects
.
filter
(
user__in
=
users
,
course_id
=
course_id
)
.
select_related
(
'user__id'
):
course_tags
[
tag
.
user
.
id
][
tag
.
key
]
=
tag
.
value
get_cache
(
cls
.
CACHE_NAMESPACE
)[
cls
.
_cache_key
(
course_id
)]
=
course_tags
@classmethod
def
get_course_tag
(
cls
,
user_id
,
course_id
,
key
):
return
get_cache
(
cls
.
CACHE_NAMESPACE
)[
cls
.
_cache_key
(
course_id
)][
user_id
][
key
]
@classmethod
def
is_prefetched
(
cls
,
course_id
):
return
cls
.
_cache_key
(
course_id
)
in
get_cache
(
cls
.
CACHE_NAMESPACE
)
@classmethod
def
_cache_key
(
cls
,
course_id
):
return
u'course_tag.{}'
.
format
(
course_id
)
def
get_course_tag
(
user
,
course_id
,
key
):
"""
Gets the value of the user's course tag for the specified key in the specified
...
...
@@ -66,11 +28,6 @@ def get_course_tag(user, course_id, key):
Returns:
string value, or None if there is no value saved
"""
if
BulkCourseTags
.
is_prefetched
(
course_id
):
try
:
return
BulkCourseTags
.
get_course_tag
(
user
.
id
,
course_id
,
key
)
except
KeyError
:
return
None
try
:
record
=
UserCourseTag
.
objects
.
get
(
user
=
user
,
...
...
openedx/core/djangoapps/user_api/partition_schemes.py
View file @
40e9e20a
...
...
@@ -70,7 +70,7 @@ class RandomUserPartitionScheme(object):
exc_info
=
True
)
if
group
is
None
and
assign
and
not
course_tag_api
.
BulkCourseTags
.
is_prefetched
(
course_key
)
:
if
group
is
None
and
assign
:
if
not
user_partition
.
groups
:
raise
UserPartitionError
(
'Cannot assign user to an empty user partition'
)
...
...
openedx/core/djangoapps/user_api/tests/test_partition_schemes.py
View file @
40e9e20a
...
...
@@ -26,11 +26,6 @@ class MemoryCourseTagAPI(object):
"""Gets the value of ``key``"""
self
.
_tags
[
course_id
][
key
]
=
value
class
BulkCourseTags
(
object
):
@classmethod
def
is_prefetched
(
self
,
course_id
):
return
False
class
TestRandomUserPartitionScheme
(
PartitionTestCase
):
"""
...
...
openedx/core/djangoapps/verified_track_content/models.py
View file @
40e9e20a
...
...
@@ -5,17 +5,17 @@ from django.db import models
from
django.utils.translation
import
ugettext_lazy
from
django.dispatch
import
receiver
from
django.db.models.signals
import
post_save
,
pre_save
import
logging
from
lms.djangoapps.courseware.courses
import
get_course_by_id
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
from
student.models
import
CourseEnrollment
from
lms.djangoapps.courseware.courses
import
get_course_by_id
from
openedx.core.djangoapps.verified_track_content.tasks
import
sync_cohort_with_mode
from
openedx.core.djangoapps.course_groups.cohorts
import
(
get_course_cohorts
,
CourseCohort
,
is_course_cohorted
,
get_random_cohort
)
from
request_cache.middleware
import
ns_request_cached
,
RequestCache
from
student.models
import
CourseEnrollment
import
logging
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -97,8 +97,6 @@ class VerifiedTrackCohortedCourse(models.Model):
enabled
=
models
.
BooleanField
()
CACHE_NAMESPACE
=
u"verified_track_content.VerifiedTrackCohortedCourse.cache."
def
__unicode__
(
self
):
return
u"Course: {}, enabled: {}"
.
format
(
unicode
(
self
.
course_key
),
self
.
enabled
)
...
...
@@ -121,7 +119,6 @@ class VerifiedTrackCohortedCourse(models.Model):
return
None
@classmethod
@ns_request_cached
(
CACHE_NAMESPACE
)
def
is_verified_track_cohort_enabled
(
cls
,
course_key
):
"""
Checks whether or not verified track cohort is enabled for the given course.
...
...
@@ -137,10 +134,3 @@ class VerifiedTrackCohortedCourse(models.Model):
return
cls
.
objects
.
get
(
course_key
=
course_key
)
.
enabled
except
cls
.
DoesNotExist
:
return
False
@receiver
(
models
.
signals
.
post_save
,
sender
=
VerifiedTrackCohortedCourse
)
@receiver
(
models
.
signals
.
post_delete
,
sender
=
VerifiedTrackCohortedCourse
)
def
invalidate_verified_track_cache
(
sender
,
**
kwargs
):
# pylint: disable=unused-argument
"""Invalidate the cache of VerifiedTrackCohortedCourse. """
RequestCache
.
clear_request_cache
(
name
=
VerifiedTrackCohortedCourse
.
CACHE_NAMESPACE
)
requirements/edx/base.txt
View file @
40e9e20a
...
...
@@ -52,7 +52,7 @@ edx-lint==0.4.3
astroid==1.3.8
edx-django-oauth2-provider==1.1.4
edx-django-sites-extensions==2.1.1
edx-enterprise==0.33.1
1
edx-enterprise==0.33.1
3
edx-oauth2-provider==1.2.0
edx-opaque-keys==0.4.0
edx-organizations==0.4.4
...
...
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