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
10aae0d4
Commit
10aae0d4
authored
Jul 30, 2015
by
Kyle McCormick
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MA-1063 Add versioning and timestamping to CourseOverview
parent
848e72c7
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
147 additions
and
22 deletions
+147
-22
openedx/core/djangoapps/content/course_overviews/migrations/0006_add_version_and_timestamp.py
+76
-0
openedx/core/djangoapps/content/course_overviews/models.py
+53
-22
openedx/core/djangoapps/content/course_overviews/tests.py
+18
-0
No files found.
openedx/core/djangoapps/content/course_overviews/migrations/0006_add_version_and_timestamp.py
0 → 100644
View file @
10aae0d4
# -*- 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 field 'CourseOverview.created'
db
.
add_column
(
'course_overviews_courseoverview'
,
'created'
,
self
.
gf
(
'model_utils.fields.AutoCreatedField'
)(
default
=
datetime
.
datetime
.
now
),
keep_default
=
False
)
# Adding field 'CourseOverview.modified'
db
.
add_column
(
'course_overviews_courseoverview'
,
'modified'
,
self
.
gf
(
'model_utils.fields.AutoLastModifiedField'
)(
default
=
datetime
.
datetime
.
now
),
keep_default
=
False
)
# Adding field 'CourseOverview.version'
db
.
add_column
(
'course_overviews_courseoverview'
,
'version'
,
self
.
gf
(
'django.db.models.fields.IntegerField'
)(
default
=
0
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting field 'CourseOverview.created'
db
.
delete_column
(
'course_overviews_courseoverview'
,
'created'
)
# Deleting field 'CourseOverview.modified'
db
.
delete_column
(
'course_overviews_courseoverview'
,
'modified'
)
# Deleting field 'CourseOverview.version'
db
.
delete_column
(
'course_overviews_courseoverview'
,
'version'
)
models
=
{
'course_overviews.courseoverview'
:
{
'Meta'
:
{
'object_name'
:
'CourseOverview'
},
'_location'
:
(
'xmodule_django.models.UsageKeyField'
,
[],
{
'max_length'
:
'255'
}),
'_pre_requisite_courses_json'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'advertised_start'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'cert_html_view_enabled'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'cert_name_long'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'cert_name_short'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'certificates_display_behavior'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'certificates_show_before_end'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'course_image_url'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'days_early_for_beta'
:
(
'django.db.models.fields.FloatField'
,
[],
{
'null'
:
'True'
}),
'display_name'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'display_number_with_default'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'display_org_with_default'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'end'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'end_of_course_survey_url'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'enrollment_domain'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'enrollment_end'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'enrollment_start'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'facebook_url'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'has_any_active_web_certificate'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'primary_key'
:
'True'
,
'db_index'
:
'True'
}),
'invitation_only'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'lowest_passing_grade'
:
(
'django.db.models.fields.DecimalField'
,
[],
{
'null'
:
'True'
,
'max_digits'
:
'5'
,
'decimal_places'
:
'2'
}),
'max_student_enrollments_allowed'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'null'
:
'True'
}),
'mobile_available'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'social_sharing_url'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'start'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'version'
:
(
'django.db.models.fields.IntegerField'
,
[],
{}),
'visible_to_staff_only'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
})
}
}
complete_apps
=
[
'course_overviews'
]
\ No newline at end of file
openedx/core/djangoapps/content/course_overviews/models.py
View file @
10aae0d4
...
@@ -4,9 +4,9 @@ Declaration of CourseOverview model
...
@@ -4,9 +4,9 @@ Declaration of CourseOverview model
import
json
import
json
import
django.db.models
from
django.db.models.fields
import
BooleanField
,
DateTimeField
,
DecimalField
,
TextField
,
FloatField
,
IntegerField
from
django.db.models.fields
import
BooleanField
,
DateTimeField
,
DecimalField
,
TextField
,
FloatField
,
IntegerField
from
django.utils.translation
import
ugettext
from
django.utils.translation
import
ugettext
from
model_utils.models
import
TimeStampedModel
from
util.date_utils
import
strftime_localized
from
util.date_utils
import
strftime_localized
from
xmodule
import
course_metadata_utils
from
xmodule
import
course_metadata_utils
...
@@ -16,7 +16,7 @@ from xmodule.modulestore.django import modulestore
...
@@ -16,7 +16,7 @@ from xmodule.modulestore.django import modulestore
from
xmodule_django.models
import
CourseKeyField
,
UsageKeyField
from
xmodule_django.models
import
CourseKeyField
,
UsageKeyField
class
CourseOverview
(
django
.
db
.
models
.
Model
):
class
CourseOverview
(
TimeStamped
Model
):
"""
"""
Model for storing and caching basic information about a course.
Model for storing and caching basic information about a course.
...
@@ -25,6 +25,12 @@ class CourseOverview(django.db.models.Model):
...
@@ -25,6 +25,12 @@ class CourseOverview(django.db.models.Model):
a course as part of a user dashboard or enrollment API.
a course as part of a user dashboard or enrollment API.
"""
"""
# IMPORTANT: Bump this whenever you modify this model and/or add a migration.
VERSION
=
1
# Cache entry versioning.
version
=
IntegerField
()
# Course identification
# Course identification
id
=
CourseKeyField
(
db_index
=
True
,
primary_key
=
True
,
max_length
=
255
)
# pylint: disable=invalid-name
id
=
CourseKeyField
(
db_index
=
True
,
primary_key
=
True
,
max_length
=
255
)
# pylint: disable=invalid-name
_location
=
UsageKeyField
(
max_length
=
255
)
_location
=
UsageKeyField
(
max_length
=
255
)
...
@@ -67,8 +73,8 @@ class CourseOverview(django.db.models.Model):
...
@@ -67,8 +73,8 @@ class CourseOverview(django.db.models.Model):
invitation_only
=
BooleanField
(
default
=
False
)
invitation_only
=
BooleanField
(
default
=
False
)
max_student_enrollments_allowed
=
IntegerField
(
null
=
True
)
max_student_enrollments_allowed
=
IntegerField
(
null
=
True
)
@
static
method
@
class
method
def
_create_from_course
(
course
):
def
_create_from_course
(
c
ls
,
c
ourse
):
"""
"""
Creates a CourseOverview object from a CourseDescriptor.
Creates a CourseOverview object from a CourseDescriptor.
...
@@ -94,7 +100,9 @@ class CourseOverview(django.db.models.Model):
...
@@ -94,7 +100,9 @@ class CourseOverview(django.db.models.Model):
except
ValueError
:
except
ValueError
:
lowest_passing_grade
=
None
lowest_passing_grade
=
None
return
CourseOverview
(
return
cls
(
version
=
cls
.
VERSION
,
id
=
course
.
id
,
id
=
course
.
id
,
_location
=
course
.
location
,
_location
=
course
.
location
,
display_name
=
course
.
display_name
,
display_name
=
course
.
display_name
,
...
@@ -130,22 +138,17 @@ class CourseOverview(django.db.models.Model):
...
@@ -130,22 +138,17 @@ class CourseOverview(django.db.models.Model):
max_student_enrollments_allowed
=
course
.
max_student_enrollments_allowed
,
max_student_enrollments_allowed
=
course
.
max_student_enrollments_allowed
,
)
)
@
static
method
@
class
method
def
get_from_id
(
course_id
):
def
_load_from_module_store
(
cls
,
course_id
):
"""
"""
Load a CourseOverview object for a given course ID.
Load a CourseDescriptor, create a new CourseOverview from it, cache the
overview, and return it.
First, we try to load the CourseOverview from the database. If it
doesn't exist, we load the entire course from the modulestore, create a
CourseOverview object from it, and then cache it in the database for
future use.
Arguments:
Arguments:
course_id (CourseKey): the ID of the course overview to be loaded.
course_id (CourseKey): the ID of the course overview to be loaded.
Returns:
Returns:
CourseOverview: overview of the requested course. If loading course
CourseOverview: overview of the requested course.
from the module store failed, returns None.
Raises:
Raises:
- CourseOverview.DoesNotExist if the course specified by course_id
- CourseOverview.DoesNotExist if the course specified by course_id
...
@@ -153,16 +156,13 @@ class CourseOverview(django.db.models.Model):
...
@@ -153,16 +156,13 @@ class CourseOverview(django.db.models.Model):
- IOError if some other error occurs while trying to load the
- IOError if some other error occurs while trying to load the
course from the module store.
course from the module store.
"""
"""
course_overview
=
None
try
:
course_overview
=
CourseOverview
.
objects
.
get
(
id
=
course_id
)
except
CourseOverview
.
DoesNotExist
:
store
=
modulestore
()
store
=
modulestore
()
with
store
.
bulk_operations
(
course_id
):
with
store
.
bulk_operations
(
course_id
):
course
=
store
.
get_course
(
course_id
)
course
=
store
.
get_course
(
course_id
)
if
isinstance
(
course
,
CourseDescriptor
):
if
isinstance
(
course
,
CourseDescriptor
):
course_overview
=
CourseOverview
.
_create_from_course
(
course
)
course_overview
=
cls
.
_create_from_course
(
course
)
course_overview
.
save
()
course_overview
.
save
()
return
course_overview
elif
course
is
not
None
:
elif
course
is
not
None
:
raise
IOError
(
raise
IOError
(
"Error while loading course {} from the module store: {}"
,
"Error while loading course {} from the module store: {}"
,
...
@@ -170,8 +170,39 @@ class CourseOverview(django.db.models.Model):
...
@@ -170,8 +170,39 @@ class CourseOverview(django.db.models.Model):
course
.
error_msg
if
isinstance
(
course
,
ErrorDescriptor
)
else
unicode
(
course
)
course
.
error_msg
if
isinstance
(
course
,
ErrorDescriptor
)
else
unicode
(
course
)
)
)
else
:
else
:
raise
CourseOverview
.
DoesNotExist
()
raise
cls
.
DoesNotExist
()
return
course_overview
@classmethod
def
get_from_id
(
cls
,
course_id
):
"""
Load a CourseOverview object for a given course ID.
First, we try to load the CourseOverview from the database. If it
doesn't exist, we load the entire course from the modulestore, create a
CourseOverview object from it, and then cache it in the database for
future use.
Arguments:
course_id (CourseKey): the ID of the course overview to be loaded.
Returns:
CourseOverview: overview of the requested course.
Raises:
- CourseOverview.DoesNotExist if the course specified by course_id
was not found.
- IOError if some other error occurs while trying to load the
course from the module store.
"""
try
:
course_overview
=
cls
.
objects
.
get
(
id
=
course_id
)
if
course_overview
.
version
!=
cls
.
VERSION
:
# Throw away old versions of CourseOverview, as they might contain stale data.
course_overview
.
delete
()
course_overview
=
None
except
cls
.
DoesNotExist
:
course_overview
=
None
return
course_overview
or
cls
.
_load_from_module_store
(
course_id
)
def
clean_id
(
self
,
padding_char
=
'='
):
def
clean_id
(
self
,
padding_char
=
'='
):
"""
"""
...
...
openedx/core/djangoapps/content/course_overviews/tests.py
View file @
10aae0d4
...
@@ -331,3 +331,21 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
...
@@ -331,3 +331,21 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
__
=
course
.
lowest_passing_grade
__
=
course
.
lowest_passing_grade
course_overview
=
CourseOverview
.
_create_from_course
(
course
)
# pylint: disable=protected-access
course_overview
=
CourseOverview
.
_create_from_course
(
course
)
# pylint: disable=protected-access
self
.
assertEqual
(
course_overview
.
lowest_passing_grade
,
None
)
self
.
assertEqual
(
course_overview
.
lowest_passing_grade
,
None
)
@ddt.data
((
ModuleStoreEnum
.
Type
.
mongo
,
1
,
1
),
(
ModuleStoreEnum
.
Type
.
split
,
3
,
4
))
@ddt.unpack
def
test_versioning
(
self
,
modulestore_type
,
min_mongo_calls
,
max_mongo_calls
):
"""
Test that CourseOverviews with old version numbers are thrown out.
"""
with
self
.
store
.
default_store
(
modulestore_type
):
course
=
CourseFactory
.
create
()
course_overview
=
CourseOverview
.
get_from_id
(
course
.
id
)
course_overview
.
version
=
CourseOverview
.
VERSION
-
1
course_overview
.
save
()
# Because the course overview now has an old version number, it should
# be thrown out after being loaded from the cache, which results in
# a call to get_course.
with
check_mongo_calls_range
(
max_finds
=
max_mongo_calls
,
min_finds
=
min_mongo_calls
):
_course_overview_2
=
CourseOverview
.
get_from_id
(
course
.
id
)
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