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
86683400
Commit
86683400
authored
Mar 23, 2016
by
Zia Fazal
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #652 from edx-solutions/ziafazal/YONK-280
ziafazal/YONK-280: add course aggregate meta date model
parents
7e98f454
6d778118
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
384 additions
and
46 deletions
+384
-46
cms/djangoapps/contentstore/tests/test_import.py
+1
-1
cms/djangoapps/contentstore/tests/test_libraries.py
+1
-1
cms/djangoapps/contentstore/views/tests/test_group_configurations.py
+3
-1
cms/envs/common.py
+1
-0
lms/djangoapps/api_manager/courses/tests.py
+1
-2
lms/djangoapps/api_manager/courses/views.py
+10
-4
lms/djangoapps/api_manager/courseware_access.py
+0
-18
lms/djangoapps/api_manager/models.py
+0
-2
lms/djangoapps/api_manager/receivers.py
+1
-2
lms/envs/common.py
+1
-0
openedx/core/djangoapps/content/__init__.py
+1
-0
openedx/core/djangoapps/content/course_metadata/__init__.py
+0
-0
openedx/core/djangoapps/content/course_metadata/migrations/0001_initial.py
+39
-0
openedx/core/djangoapps/content/course_metadata/migrations/__init__.py
+0
-0
openedx/core/djangoapps/content/course_metadata/models.py
+44
-0
openedx/core/djangoapps/content/course_metadata/signals.py
+17
-0
openedx/core/djangoapps/content/course_metadata/tasks.py
+34
-0
openedx/core/djangoapps/content/course_metadata/test_utils.py
+115
-0
openedx/core/djangoapps/content/course_metadata/tests.py
+73
-0
openedx/core/djangoapps/content/course_metadata/utils.py
+22
-0
openedx/core/djangoapps/content/course_structures/tests.py
+0
-12
openedx/core/djangoapps/util/testing.py
+20
-3
No files found.
cms/djangoapps/contentstore/tests/test_import.py
View file @
86683400
...
@@ -11,7 +11,7 @@ from django.conf import settings
...
@@ -11,7 +11,7 @@ from django.conf import settings
import
ddt
import
ddt
import
copy
import
copy
from
openedx.core.djangoapps.
content.course_structures.tests
import
SignalDisconnectTestMixin
from
openedx.core.djangoapps.
util.testing
import
SignalDisconnectTestMixin
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
...
...
cms/djangoapps/contentstore/tests/test_libraries.py
View file @
86683400
...
@@ -21,7 +21,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...
@@ -21,7 +21,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
mock
import
Mock
from
mock
import
Mock
from
opaque_keys.edx.locator
import
CourseKey
,
LibraryLocator
from
opaque_keys.edx.locator
import
CourseKey
,
LibraryLocator
from
openedx.core.djangoapps.
content.course_structures.tests
import
SignalDisconnectTestMixin
from
openedx.core.djangoapps.
util.testing
import
SignalDisconnectTestMixin
class
LibraryTestCase
(
ModuleStoreTestCase
):
class
LibraryTestCase
(
ModuleStoreTestCase
):
...
...
cms/djangoapps/contentstore/views/tests/test_group_configurations.py
View file @
86683400
...
@@ -14,6 +14,7 @@ from xmodule.modulestore.tests.factories import ItemFactory
...
@@ -14,6 +14,7 @@ from xmodule.modulestore.tests.factories import ItemFactory
from
xmodule.validation
import
StudioValidation
,
StudioValidationMessage
from
xmodule.validation
import
StudioValidation
,
StudioValidationMessage
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
openedx.core.djangoapps.util.testing
import
SignalDisconnectTestMixin
GROUP_CONFIGURATION_JSON
=
{
GROUP_CONFIGURATION_JSON
=
{
u'name'
:
u'Test name'
,
u'name'
:
u'Test name'
,
...
@@ -204,7 +205,8 @@ class GroupConfigurationsBaseTestCase(object):
...
@@ -204,7 +205,8 @@ class GroupConfigurationsBaseTestCase(object):
# pylint: disable=no-member
# pylint: disable=no-member
class
GroupConfigurationsListHandlerTestCase
(
CourseTestCase
,
GroupConfigurationsBaseTestCase
,
HelperMethods
):
class
GroupConfigurationsListHandlerTestCase
(
SignalDisconnectTestMixin
,
CourseTestCase
,
GroupConfigurationsBaseTestCase
,
HelperMethods
):
"""
"""
Test cases for group_configurations_list_handler.
Test cases for group_configurations_list_handler.
"""
"""
...
...
cms/envs/common.py
View file @
86683400
...
@@ -769,6 +769,7 @@ INSTALLED_APPS = (
...
@@ -769,6 +769,7 @@ INSTALLED_APPS = (
'openedx.core.djangoapps.content.course_overviews'
,
'openedx.core.djangoapps.content.course_overviews'
,
'openedx.core.djangoapps.content.course_structures'
,
'openedx.core.djangoapps.content.course_structures'
,
'openedx.core.djangoapps.content.course_metadata'
,
# Credit courses
# Credit courses
'openedx.core.djangoapps.credit'
,
'openedx.core.djangoapps.credit'
,
...
...
lms/djangoapps/api_manager/courses/tests.py
View file @
86683400
...
@@ -25,12 +25,10 @@ from capa.tests.response_xml_factory import StringResponseXMLFactory
...
@@ -25,12 +25,10 @@ from capa.tests.response_xml_factory import StringResponseXMLFactory
from
courseware
import
module_render
from
courseware
import
module_render
from
courseware.tests.factories
import
StudentModuleFactory
from
courseware.tests.factories
import
StudentModuleFactory
from
courseware.model_data
import
FieldDataCache
from
courseware.model_data
import
FieldDataCache
from
courseware.models
import
StudentModule
from
django_comment_common.models
import
Role
,
FORUM_ROLE_MODERATOR
from
django_comment_common.models
import
Role
,
FORUM_ROLE_MODERATOR
from
gradebook.models
import
StudentGradebook
from
gradebook.models
import
StudentGradebook
from
instructor.access
import
allow_access
from
instructor.access
import
allow_access
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
,
mixed_store_config
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
,
mixed_store_config
...
@@ -40,6 +38,7 @@ from api_manager.courseware_access import get_course_key
...
@@ -40,6 +38,7 @@ from api_manager.courseware_access import get_course_key
from
.content
import
TEST_COURSE_OVERVIEW_CONTENT
,
TEST_COURSE_UPDATES_CONTENT
,
TEST_COURSE_UPDATES_CONTENT_LEGACY
from
.content
import
TEST_COURSE_OVERVIEW_CONTENT
,
TEST_COURSE_UPDATES_CONTENT
,
TEST_COURSE_UPDATES_CONTENT_LEGACY
from
.content
import
TEST_STATIC_TAB1_CONTENT
,
TEST_STATIC_TAB2_CONTENT
from
.content
import
TEST_STATIC_TAB1_CONTENT
,
TEST_STATIC_TAB2_CONTENT
MODULESTORE_CONFIG
=
mixed_store_config
(
settings
.
COMMON_TEST_DATA_ROOT
,
{},
include_xml
=
False
)
MODULESTORE_CONFIG
=
mixed_store_config
(
settings
.
COMMON_TEST_DATA_ROOT
,
{},
include_xml
=
False
)
TEST_API_KEY
=
str
(
uuid
.
uuid4
())
TEST_API_KEY
=
str
(
uuid
.
uuid4
())
USER_COUNT
=
6
USER_COUNT
=
6
...
...
lms/djangoapps/api_manager/courses/views.py
View file @
86683400
...
@@ -37,10 +37,15 @@ from student.models import CourseEnrollment, CourseEnrollmentAllowed
...
@@ -37,10 +37,15 @@ from student.models import CourseEnrollment, CourseEnrollmentAllowed
from
student.roles
import
CourseRole
,
CourseAccessRole
,
CourseInstructorRole
,
CourseStaffRole
,
CourseObserverRole
,
CourseAssistantRole
,
UserBasedRole
,
get_aggregate_exclusion_user_ids
from
student.roles
import
CourseRole
,
CourseAccessRole
,
CourseInstructorRole
,
CourseStaffRole
,
CourseObserverRole
,
CourseAssistantRole
,
UserBasedRole
,
get_aggregate_exclusion_user_ids
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.search
import
path_to_location
from
xmodule.modulestore.search
import
path_to_location
from
openedx.core.djangoapps.content.course_metadata.models
import
CourseAggregatedMetaData
from
api_manager.courseware_access
import
get_course
,
get_course_child
,
get_course_
leaf_nodes
,
get_course_
key
,
\
from
api_manager.courseware_access
import
get_course
,
get_course_child
,
get_course_key
,
\
course_exists
,
get_modulestore
,
get_course_descriptor
course_exists
,
get_modulestore
,
get_course_descriptor
from
api_manager.models
import
CourseGroupRelationship
,
CourseContentGroupRelationship
,
GroupProfile
from
api_manager.models
import
(
CourseGroupRelationship
,
CourseContentGroupRelationship
,
GroupProfile
,
)
from
progress.models
import
CourseModuleCompletion
from
progress.models
import
CourseModuleCompletion
from
api_manager.permissions
import
SecureAPIView
,
SecureListAPIView
from
api_manager.permissions
import
SecureAPIView
,
SecureListAPIView
from
api_manager.users.serializers
import
UserSerializer
,
UserCountByCitySerializer
from
api_manager.users.serializers
import
UserSerializer
,
UserCountByCitySerializer
...
@@ -1736,7 +1741,8 @@ class CoursesMetricsCompletionsLeadersList(SecureAPIView):
...
@@ -1736,7 +1741,8 @@ class CoursesMetricsCompletionsLeadersList(SecureAPIView):
if
not
course_exists
(
request
,
request
.
user
,
course_id
):
if
not
course_exists
(
request
,
request
.
user
,
course_id
):
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
course_key
=
get_course_key
(
course_id
)
course_key
=
get_course_key
(
course_id
)
total_possible_completions
=
float
(
len
(
get_course_leaf_nodes
(
course_key
)))
course_metadata
=
CourseAggregatedMetaData
.
get_from_id
(
course_key
)
total_possible_completions
=
float
(
course_metadata
.
total_assessments
)
exclude_users
=
get_aggregate_exclusion_user_ids
(
course_key
)
exclude_users
=
get_aggregate_exclusion_user_ids
(
course_key
)
orgs_filter
=
self
.
request
.
QUERY_PARAMS
.
get
(
'organizations'
,
None
)
orgs_filter
=
self
.
request
.
QUERY_PARAMS
.
get
(
'organizations'
,
None
)
if
orgs_filter
:
if
orgs_filter
:
...
@@ -1758,7 +1764,7 @@ class CoursesMetricsCompletionsLeadersList(SecureAPIView):
...
@@ -1758,7 +1764,7 @@ class CoursesMetricsCompletionsLeadersList(SecureAPIView):
if
orgs_filter
:
if
orgs_filter
:
total_users_qs
=
total_users_qs
.
filter
(
organizations__in
=
orgs_filter
)
total_users_qs
=
total_users_qs
.
filter
(
organizations__in
=
orgs_filter
)
total_users
=
total_users_qs
.
count
()
total_users
=
total_users_qs
.
count
()
if
total_users
and
total_actual_completions
:
if
total_users
and
total_actual_completions
and
total_possible_completions
:
course_avg
=
total_actual_completions
/
float
(
total_users
)
course_avg
=
total_actual_completions
/
float
(
total_users
)
course_avg
=
min
(
100
*
(
course_avg
/
total_possible_completions
),
100
)
# avg in percentage
course_avg
=
min
(
100
*
(
course_avg
/
total_possible_completions
),
100
)
# avg in percentage
data
[
'course_avg'
]
=
course_avg
data
[
'course_avg'
]
=
course_avg
...
...
lms/djangoapps/api_manager/courseware_access.py
View file @
86683400
""" Centralized access to LMS courseware app """
""" Centralized access to LMS courseware app """
from
django.contrib.auth.models
import
AnonymousUser
from
django.contrib.auth.models
import
AnonymousUser
from
django.utils
import
timezone
from
django.conf
import
settings
from
courseware
import
courses
,
module_render
from
courseware
import
courses
,
module_render
from
courseware.model_data
import
FieldDataCache
from
courseware.model_data
import
FieldDataCache
...
@@ -64,22 +62,6 @@ def get_course_total_score(course_summary):
...
@@ -64,22 +62,6 @@ def get_course_total_score(course_summary):
return
score
return
score
def
get_course_leaf_nodes
(
course_key
):
"""
Get count of the leaf nodes with ability to exclude some categories
"""
nodes
=
[]
detached_categories
=
getattr
(
settings
,
'PROGRESS_DETACHED_CATEGORIES'
,
[])
store
=
get_modulestore
()
verticals
=
store
.
get_items
(
course_key
,
qualifiers
=
{
'category'
:
'vertical'
})
orphans
=
store
.
get_orphans
(
course_key
)
for
vertical
in
verticals
:
if
hasattr
(
vertical
,
'children'
)
and
vertical
.
location
not
in
orphans
:
nodes
.
extend
([
unit
for
unit
in
vertical
.
children
if
getattr
(
unit
,
'category'
)
not
in
detached_categories
])
return
nodes
def
get_course_key
(
course_id
,
slashseparated
=
False
):
def
get_course_key
(
course_id
,
slashseparated
=
False
):
try
:
try
:
course_key
=
CourseKey
.
from_string
(
course_id
)
course_key
=
CourseKey
.
from_string
(
course_id
)
...
...
lms/djangoapps/api_manager/models.py
View file @
86683400
...
@@ -3,8 +3,6 @@
...
@@ -3,8 +3,6 @@
""" Database ORM models managed by this Django app """
""" Database ORM models managed by this Django app """
from
django.contrib.auth.models
import
Group
,
User
from
django.contrib.auth.models
import
Group
,
User
from
django.db
import
models
from
django.db
import
models
from
django.db.models
import
Q
from
django.conf
import
settings
from
model_utils.models
import
TimeStampedModel
from
model_utils.models
import
TimeStampedModel
from
.utils
import
is_int
from
.utils
import
is_int
...
...
lms/djangoapps/api_manager/receivers.py
View file @
86683400
"""
"""
Signal handlers supporting various
gradebook
use cases
Signal handlers supporting various
course metadata
use cases
"""
"""
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
util.signals
import
course_deleted
from
util.signals
import
course_deleted
from
.models
import
CourseGroupRelationship
,
CourseContentGroupRelationship
from
.models
import
CourseGroupRelationship
,
CourseContentGroupRelationship
...
...
lms/envs/common.py
View file @
86683400
...
@@ -1961,6 +1961,7 @@ INSTALLED_APPS = (
...
@@ -1961,6 +1961,7 @@ INSTALLED_APPS = (
'openedx.core.djangoapps.content.course_overviews'
,
'openedx.core.djangoapps.content.course_overviews'
,
'openedx.core.djangoapps.content.course_structures'
,
'openedx.core.djangoapps.content.course_structures'
,
'openedx.core.djangoapps.content.course_metadata'
,
'course_structure_api'
,
'course_structure_api'
,
# Mailchimp Syncing
# Mailchimp Syncing
...
...
openedx/core/djangoapps/content/__init__.py
View file @
86683400
...
@@ -2,3 +2,4 @@
...
@@ -2,3 +2,4 @@
Setup the signals on startup.
Setup the signals on startup.
"""
"""
import
openedx.core.djangoapps.content.course_structures.signals
import
openedx.core.djangoapps.content.course_structures.signals
import
openedx.core.djangoapps.content.course_metadata.signals
openedx/core/djangoapps/content/course_metadata/__init__.py
0 → 100644
View file @
86683400
openedx/core/djangoapps/content/course_metadata/migrations/0001_initial.py
0 → 100644
View file @
86683400
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
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 'CourseAggregatedMetaData'
db
.
create_table
(
'course_metadata_courseaggregatedmetadata'
,
(
(
'created'
,
self
.
gf
(
'model_utils.fields.AutoCreatedField'
)(
default
=
datetime
.
datetime
.
now
)),
(
'modified'
,
self
.
gf
(
'model_utils.fields.AutoLastModifiedField'
)(
default
=
datetime
.
datetime
.
now
)),
(
'id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
,
primary_key
=
True
,
db_index
=
True
)),
(
'total_modules'
,
self
.
gf
(
'django.db.models.fields.IntegerField'
)(
default
=
0
)),
(
'total_assessments'
,
self
.
gf
(
'django.db.models.fields.IntegerField'
)(
default
=
0
)),
))
db
.
send_create_signal
(
'course_metadata'
,
[
'CourseAggregatedMetaData'
])
def
backwards
(
self
,
orm
):
# Deleting model 'CourseAggregatedMetaData'
db
.
delete_table
(
'course_metadata_courseaggregatedmetadata'
)
models
=
{
'course_metadata.courseaggregatedmetadata'
:
{
'Meta'
:
{
'object_name'
:
'CourseAggregatedMetaData'
},
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'primary_key'
:
'True'
,
'db_index'
:
'True'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'total_assessments'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
}),
'total_modules'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
})
}
}
complete_apps
=
[
'course_metadata'
]
\ No newline at end of file
openedx/core/djangoapps/content/course_metadata/migrations/__init__.py
0 → 100644
View file @
86683400
openedx/core/djangoapps/content/course_metadata/models.py
0 → 100644
View file @
86683400
"""
Models for course_metadata app
"""
from
django.db
import
models
from
model_utils.models
import
TimeStampedModel
from
xmodule_django.models
import
CourseKeyField
from
openedx.core.djangoapps.content.course_metadata.utils
import
get_course_leaf_nodes
class
CourseAggregatedMetaData
(
TimeStampedModel
):
"""
Model for storing and caching aggregated metadata about a course.
This model contains aggregated metadata about a course such as
total modules, total assessments.
"""
id
=
CourseKeyField
(
db_index
=
True
,
primary_key
=
True
,
max_length
=
255
)
# pylint: disable=invalid-name
total_modules
=
models
.
IntegerField
(
default
=
0
)
total_assessments
=
models
.
IntegerField
(
default
=
0
)
@staticmethod
def
get_from_id
(
course_id
):
"""
Load a CourseAggregatedMetaData object for a given course ID.
First, we try to load the CourseAggregatedMetaData from the database. If it
doesn't exist, we create CourseAggregatedMetaData in the database for
future use.
Arguments:
course_id (CourseKey): the ID of the course aggregated data to be loaded
Returns:
CourseAggregatedMetaData: aggregated data of the requested course
"""
try
:
course_metadata
=
CourseAggregatedMetaData
.
objects
.
get
(
id
=
course_id
)
except
CourseAggregatedMetaData
.
DoesNotExist
:
course_metadata
=
CourseAggregatedMetaData
(
id
=
course_id
)
course_metadata
.
total_assessments
=
len
(
get_course_leaf_nodes
(
course_id
))
course_metadata
.
save
()
return
course_metadata
openedx/core/djangoapps/content/course_metadata/signals.py
0 → 100644
View file @
86683400
"""
This module has definition of receivers for signals
"""
from
django.dispatch.dispatcher
import
receiver
from
xmodule.modulestore.django
import
SignalHandler
from
openedx.core.djangoapps.content.course_metadata.tasks
import
update_course_aggregate_metadata
@receiver
(
SignalHandler
.
course_published
)
def
listen_for_course_publish
(
sender
,
course_key
,
**
kwargs
):
# pylint: disable=unused-argument
"""
Receives signal and kicks off celery task to update course aggregate metadata
"""
# Note: The countdown=0 kwarg is set to to ensure the method below does not attempt to access the course
# before the signal emitter has finished all operations. This is also necessary to ensure all tests pass.
update_course_aggregate_metadata
.
apply_async
([
unicode
(
course_key
)],
countdown
=
0
)
openedx/core/djangoapps/content/course_metadata/tasks.py
0 → 100644
View file @
86683400
"""
This module has implementation of celery tasks to compute course aggregate metadata
"""
import
logging
from
celery.task
import
task
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.content.course_metadata.models
import
CourseAggregatedMetaData
from
openedx.core.djangoapps.content.course_metadata.utils
import
get_course_leaf_nodes
log
=
logging
.
getLogger
(
'edx.celery.task'
)
@task
(
name
=
u'lms.djangoapps.api_manager.tasks.update_course_metadata'
)
def
update_course_aggregate_metadata
(
course_key
):
# pylint: disable=invalid-name
"""
Regenerates and updates the course aggregate metadata (in the database) for the specified course.
"""
if
not
isinstance
(
course_key
,
basestring
):
raise
ValueError
(
'course_key must be a string. {} is not acceptable.'
.
format
(
type
(
course_key
)))
course_key
=
CourseKey
.
from_string
(
course_key
)
try
:
course_leaf_nodes
=
get_course_leaf_nodes
(
course_key
)
except
Exception
as
ex
:
log
.
exception
(
'An error occurred while retrieving course assessments:
%
s'
,
ex
.
message
)
raise
course_metadata
,
__
=
CourseAggregatedMetaData
.
objects
.
get_or_create
(
id
=
course_key
)
course_metadata
.
total_assessments
=
len
(
course_leaf_nodes
)
course_metadata
.
save
()
openedx/core/djangoapps/content/course_metadata/test_utils.py
0 → 100644
View file @
86683400
"""
This module has tests for utils.py
"""
# pylint: disable=no-member
import
ddt
from
django.test.utils
import
override_settings
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
openedx.core.djangoapps.content.course_metadata.utils
import
get_course_leaf_nodes
@ddt.ddt
class
UtilsTests
(
ModuleStoreTestCase
):
""" Test suite to test operation in utils"""
def
setUp
(
self
):
super
(
UtilsTests
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
self
.
test_data
=
'<html>Test data</html>'
self
.
chapter
=
ItemFactory
.
create
(
category
=
"chapter"
,
parent_location
=
self
.
course
.
location
,
data
=
self
.
test_data
,
display_name
=
"Overview"
,
)
self
.
sub_section
=
ItemFactory
.
create
(
parent_location
=
self
.
chapter
.
location
,
category
=
"sequential"
,
display_name
=
u"test subsection"
,
)
self
.
sub_section2
=
ItemFactory
.
create
(
parent_location
=
self
.
chapter
.
location
,
category
=
"sequential"
,
display_name
=
u"test subsection 2"
,
)
self
.
vertical
=
ItemFactory
.
create
(
parent_location
=
self
.
sub_section
.
location
,
category
=
"vertical"
,
metadata
=
{
'graded'
:
True
,
'format'
:
'Homework'
},
display_name
=
u"test vertical"
,
)
self
.
vertical2
=
ItemFactory
.
create
(
parent_location
=
self
.
sub_section2
.
location
,
category
=
"vertical"
,
metadata
=
{
'graded'
:
True
,
'format'
:
'FinalExam'
},
display_name
=
u"test vertical 2"
,
)
self
.
content_child1
=
ItemFactory
.
create
(
category
=
"html"
,
parent_location
=
self
.
vertical
.
location
,
data
=
self
.
test_data
,
display_name
=
"Html component"
)
self
.
content_child2
=
ItemFactory
.
create
(
category
=
"video"
,
parent_location
=
self
.
vertical
.
location
,
data
=
self
.
test_data
,
display_name
=
"Video component"
)
self
.
content_child3
=
ItemFactory
.
create
(
category
=
"group-project"
,
parent_location
=
self
.
vertical
.
location
,
data
=
self
.
test_data
,
display_name
=
"group project component"
)
self
.
content_child4
=
ItemFactory
.
create
(
category
=
"html"
,
parent_location
=
self
.
vertical2
.
location
,
data
=
self
.
test_data
,
display_name
=
"Html component 2"
)
self
.
user
=
UserFactory
()
@override_settings
(
PROGRESS_DETACHED_CATEGORIES
=
[])
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_get_course_leaf_nodes
(
self
,
module_store_type
):
"""
Tests get_course_leaf_nodes works as expected
"""
with
modulestore
()
.
default_store
(
module_store_type
):
nodes
=
get_course_leaf_nodes
(
self
.
course
.
id
)
self
.
assertEqual
(
len
(
nodes
),
4
)
@override_settings
(
PROGRESS_DETACHED_CATEGORIES
=
[
"group-project"
])
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
)
def
test_get_course_leaf_nodes_with_detached_categories
(
self
,
module_store_type
):
"""
Tests get_course_leaf_nodes with detached categories
"""
with
modulestore
()
.
default_store
(
module_store_type
):
nodes
=
get_course_leaf_nodes
(
self
.
course
.
id
)
# group-project project node should not be counted
self
.
assertEqual
(
len
(
nodes
),
3
)
@override_settings
(
PROGRESS_DETACHED_CATEGORIES
=
[])
def
test_get_course_leaf_nodes_with_orphan_nodes
(
self
):
"""
Tests get_course_leaf_nodes if some nodes are orphan
"""
with
modulestore
()
.
default_store
(
ModuleStoreEnum
.
Type
.
mongo
):
with
modulestore
()
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
):
# delete sub_section2 to make vertical2 orphan
store
=
modulestore
()
store
.
delete_item
(
self
.
sub_section2
.
location
,
self
.
user
.
id
)
nodes
=
get_course_leaf_nodes
(
self
.
course
.
id
)
self
.
assertEqual
(
len
(
nodes
),
3
)
openedx/core/djangoapps/content/course_metadata/tests.py
0 → 100644
View file @
86683400
"""
Tests for course_metadata app
"""
from
mock_django
import
mock_signal_receiver
from
xmodule.modulestore.django
import
SignalHandler
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
openedx.core.djangoapps.content.course_metadata.signals
import
listen_for_course_publish
from
openedx.core.djangoapps.content.course_metadata.models
import
CourseAggregatedMetaData
class
CoursesMetaDataTests
(
ModuleStoreTestCase
):
""" Test suite for Course Meta Data """
def
setUp
(
self
):
super
(
CoursesMetaDataTests
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
self
.
test_data
=
'<html>Test data</html>'
self
.
chapter
=
ItemFactory
.
create
(
category
=
"chapter"
,
parent_location
=
self
.
course
.
location
,
data
=
self
.
test_data
,
display_name
=
"Overview"
,
)
self
.
sub_section
=
ItemFactory
.
create
(
parent_location
=
self
.
chapter
.
location
,
category
=
"sequential"
,
display_name
=
u"test subsection"
,
)
self
.
unit
=
ItemFactory
.
create
(
parent_location
=
self
.
sub_section
.
location
,
category
=
"vertical"
,
metadata
=
{
'graded'
:
True
,
'format'
:
'Homework'
},
display_name
=
u"test unit"
,
)
self
.
content_child1
=
ItemFactory
.
create
(
category
=
"html"
,
parent_location
=
self
.
unit
.
location
,
data
=
self
.
test_data
,
display_name
=
"Html component"
)
def
test_course_aggregate_metadata_update_on_course_published
(
self
):
"""
Test course aggregate metadata update receiver is called on course_published signal
and CourseAggregatedMetaData is updated
"""
with
mock_signal_receiver
(
SignalHandler
.
course_published
,
wraps
=
listen_for_course_publish
)
as
receiver
:
self
.
assertEqual
(
receiver
.
call_count
,
0
)
# adding new video unit to course should fire the signal
ItemFactory
.
create
(
category
=
"video"
,
parent_location
=
self
.
unit
.
location
,
data
=
self
.
test_data
,
display_name
=
"Video to test aggregates"
)
self
.
assertEqual
(
receiver
.
call_count
,
1
)
total_assessments
=
CourseAggregatedMetaData
.
objects
.
get
(
id
=
self
.
course
.
id
)
.
total_assessments
self
.
assertEqual
(
total_assessments
,
2
)
def
test_get_course_aggregate_metadata_by_course_key
(
self
):
"""
Test course aggregate metadata should compute and return metadata
when called by get_from_id
"""
course_metadata
=
CourseAggregatedMetaData
.
get_from_id
(
self
.
course
.
id
)
self
.
assertEqual
(
course_metadata
.
total_assessments
,
1
)
openedx/core/djangoapps/content/course_metadata/utils.py
0 → 100644
View file @
86683400
"""
Utility methods for course metadata app
"""
from
django.conf
import
settings
from
xmodule.modulestore.django
import
modulestore
def
get_course_leaf_nodes
(
course_key
):
"""
Get count of the leaf nodes with ability to exclude some categories
"""
nodes
=
[]
detached_categories
=
getattr
(
settings
,
'PROGRESS_DETACHED_CATEGORIES'
,
[])
store
=
modulestore
()
verticals
=
store
.
get_items
(
course_key
,
qualifiers
=
{
'category'
:
'vertical'
})
orphans
=
store
.
get_orphans
(
course_key
)
for
vertical
in
verticals
:
if
hasattr
(
vertical
,
'children'
)
and
vertical
.
location
not
in
orphans
:
nodes
.
extend
([
unit
for
unit
in
vertical
.
children
if
getattr
(
unit
,
'category'
)
not
in
detached_categories
])
return
nodes
openedx/core/djangoapps/content/course_structures/tests.py
View file @
86683400
import
json
import
json
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
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.content.course_structures.signals
import
listen_for_course_publish
from
openedx.core.djangoapps.content.course_structures.tasks
import
_generate_course_structure
,
update_course_structure
from
openedx.core.djangoapps.content.course_structures.tasks
import
_generate_course_structure
,
update_course_structure
class
SignalDisconnectTestMixin
(
object
):
"""
Mixin for tests to disable calls to signals.listen_for_course_publish when the course_published signal is fired.
"""
def
setUp
(
self
):
super
(
SignalDisconnectTestMixin
,
self
)
.
setUp
()
SignalHandler
.
course_published
.
disconnect
(
listen_for_course_publish
)
class
CourseStructureTaskTests
(
ModuleStoreTestCase
):
class
CourseStructureTaskTests
(
ModuleStoreTestCase
):
def
setUp
(
self
,
**
kwargs
):
def
setUp
(
self
,
**
kwargs
):
super
(
CourseStructureTaskTests
,
self
)
.
setUp
()
super
(
CourseStructureTaskTests
,
self
)
.
setUp
()
...
...
openedx/core/djangoapps/util/testing.py
View file @
86683400
...
@@ -3,14 +3,20 @@
...
@@ -3,14 +3,20 @@
from
datetime
import
datetime
from
datetime
import
datetime
from
pytz
import
UTC
from
pytz
import
UTC
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroupPartitionGroup
from
xmodule.modulestore.django
import
SignalHandler
from
openedx.core.djangoapps.course_groups.tests.helpers
import
CohortFactory
from
openedx.core.djangoapps.user_api.tests.factories
import
UserCourseTagFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.partitions.partitions
import
UserPartition
,
Group
from
xmodule.partitions.partitions
import
UserPartition
,
Group
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
openedx.core.djangoapps.course_groups.models
import
CourseUserGroupPartitionGroup
from
openedx.core.djangoapps.course_groups.tests.helpers
import
CohortFactory
from
openedx.core.djangoapps.content.course_structures.signals
import
listen_for_course_publish
from
openedx.core.djangoapps.content.course_metadata.signals
import
(
listen_for_course_publish
as
course_publish_listener
)
from
openedx.core.djangoapps.user_api.tests.factories
import
UserCourseTagFactory
class
ContentGroupTestCase
(
ModuleStoreTestCase
):
class
ContentGroupTestCase
(
ModuleStoreTestCase
):
"""
"""
...
@@ -207,3 +213,14 @@ class TestConditionalContent(ModuleStoreTestCase):
...
@@ -207,3 +213,14 @@ class TestConditionalContent(ModuleStoreTestCase):
display_name
=
'Group B problem container'
,
display_name
=
'Group B problem container'
,
location
=
vertical_b_url
location
=
vertical_b_url
)
)
class
SignalDisconnectTestMixin
(
object
):
"""
Mixin for tests to disable calls to signals.listen_for_course_publish when the course_published signal is fired.
"""
def
setUp
(
self
):
super
(
SignalDisconnectTestMixin
,
self
)
.
setUp
()
SignalHandler
.
course_published
.
disconnect
(
listen_for_course_publish
)
SignalHandler
.
course_published
.
disconnect
(
course_publish_listener
)
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