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
538bec92
Commit
538bec92
authored
Jul 27, 2014
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
LMS-11137 Course Action State Django models.
parent
919e5303
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
619 additions
and
0 deletions
+619
-0
cms/envs/common.py
+3
-0
common/djangoapps/course_action_state/__init__.py
+0
-0
common/djangoapps/course_action_state/managers.py
+150
-0
common/djangoapps/course_action_state/migrations/0001_initial.py
+93
-0
common/djangoapps/course_action_state/migrations/__init__.py
+0
-0
common/djangoapps/course_action_state/models.py
+114
-0
common/djangoapps/course_action_state/tests/test_managers.py
+159
-0
common/djangoapps/course_action_state/tests/test_rerun_manager.py
+97
-0
lms/envs/common.py
+3
-0
No files found.
cms/envs/common.py
View file @
538bec92
...
...
@@ -547,6 +547,9 @@ INSTALLED_APPS = (
# Monitoring signals
'monitoring'
,
# Course action state
'course_action_state'
)
...
...
common/djangoapps/course_action_state/__init__.py
0 → 100644
View file @
538bec92
common/djangoapps/course_action_state/managers.py
0 → 100644
View file @
538bec92
"""
Model Managers for Course Actions
"""
from
django.db
import
models
,
transaction
class
CourseActionStateManager
(
models
.
Manager
):
"""
An abstract Model Manager class for Course Action State models.
This abstract class expects child classes to define the ACTION (string) field.
"""
class
Meta
:
"""Abstract manager class, with subclasses defining the ACTION (string) field."""
abstract
=
True
def
find_all
(
self
,
exclude_args
=
None
,
**
kwargs
):
"""
Finds and returns all entries for this action and the given field names-and-values in kwargs.
The exclude_args dict allows excluding entries with the field names-and-values in exclude_args.
"""
return
self
.
filter
(
action
=
self
.
ACTION
,
**
kwargs
)
.
exclude
(
**
(
exclude_args
or
{}))
# pylint: disable=no-member
def
find_first
(
self
,
exclude_args
=
None
,
**
kwargs
):
"""
Returns the first entry for the this action and the given fields in kwargs, if found.
The exclude_args dict allows excluding entries with the field names-and-values in exclude_args.
Raises ItemNotFoundError if more than 1 entry is found.
There may or may not be greater than one entry, depending on the usage pattern for this Action.
"""
objects
=
self
.
find_all
(
exclude_args
=
exclude_args
,
**
kwargs
)
if
len
(
objects
)
==
0
:
raise
CourseActionStateItemNotFoundError
(
"No entry found for action {action} with filter {filter}, excluding {exclude}"
.
format
(
action
=
self
.
ACTION
,
# pylint: disable=no-member
filter
=
kwargs
,
exclude
=
exclude_args
,
))
else
:
return
objects
[
0
]
def
delete
(
self
,
entry_id
):
"""
Deletes the entry with given id.
"""
self
.
filter
(
id
=
entry_id
)
.
delete
()
class
CourseActionUIStateManager
(
CourseActionStateManager
):
"""
A Model Manager subclass of the CourseActionStateManager class that is aware of UI-related fields related
to state management, including "should_display" and "message".
"""
# add transaction protection to revert changes by get_or_create if an exception is raised before the final save.
@transaction.commit_on_success
def
update_state
(
self
,
course_key
,
new_state
,
should_display
=
True
,
message
=
""
,
user
=
None
,
allow_not_found
=
False
,
**
kwargs
):
"""
Updates the state of the given course for this Action with the given data.
If allow_not_found is True, automatically creates an entry if it doesn't exist.
Raises CourseActionStateException if allow_not_found is False and an entry for the given course
for this Action doesn't exist.
"""
state_object
,
created
=
self
.
get_or_create
(
course_key
=
course_key
,
action
=
self
.
ACTION
)
# pylint: disable=no-member
if
created
:
if
allow_not_found
:
state_object
.
created_user
=
user
else
:
raise
CourseActionStateItemNotFoundError
(
"Cannot update non-existent entry for course_key {course_key} and action {action}"
.
format
(
action
=
self
.
ACTION
,
# pylint: disable=no-member
course_key
=
course_key
,
))
# some state changes may not be user-initiated so override the user field only when provided
if
user
:
state_object
.
updated_user
=
user
state_object
.
state
=
new_state
state_object
.
should_display
=
should_display
state_object
.
message
=
message
# update any additional fields in kwargs
if
kwargs
:
for
key
,
value
in
kwargs
.
iteritems
():
setattr
(
state_object
,
key
,
value
)
state_object
.
save
()
return
state_object
def
update_should_display
(
self
,
entry_id
,
user
,
should_display
):
"""
Updates the should_display field with the given value for the entry for the given id.
"""
self
.
update
(
id
=
entry_id
,
updated_user
=
user
,
should_display
=
should_display
)
class
CourseRerunUIStateManager
(
CourseActionUIStateManager
):
"""
A concrete model Manager for the Reruns Action.
"""
ACTION
=
"rerun"
class
State
(
object
):
"""
An Enum class for maintaining the list of possible states for Reruns.
"""
IN_PROGRESS
=
"in_progress"
FAILED
=
"failed"
SUCCEEDED
=
"succeeded"
def
initiated
(
self
,
source_course_key
,
destination_course_key
,
user
):
"""
To be called when a new rerun is initiated for the given course by the given user.
"""
self
.
update_state
(
course_key
=
destination_course_key
,
new_state
=
self
.
State
.
IN_PROGRESS
,
user
=
user
,
allow_not_found
=
True
,
source_course_key
=
source_course_key
,
)
def
succeeded
(
self
,
course_key
):
"""
To be called when an existing rerun for the given course has successfully completed.
"""
self
.
update_state
(
course_key
=
course_key
,
new_state
=
self
.
State
.
SUCCEEDED
,
)
def
failed
(
self
,
course_key
,
exception
):
"""
To be called when an existing rerun for the given course has failed with the given exception.
"""
self
.
update_state
(
course_key
=
course_key
,
new_state
=
self
.
State
.
FAILED
,
message
=
exception
.
message
,
)
class
CourseActionStateItemNotFoundError
(
Exception
):
"""An exception class for errors specific to Course Action states."""
pass
common/djangoapps/course_action_state/migrations/0001_initial.py
0 → 100644
View file @
538bec92
# -*- coding: utf-8 -*-
import
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 'CourseRerunState'
db
.
create_table
(
'course_action_state_coursererunstate'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'created_time'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now_add
=
True
,
blank
=
True
)),
(
'updated_time'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now
=
True
,
blank
=
True
)),
(
'created_user'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'created_by_user+'
,
null
=
True
,
on_delete
=
models
.
SET_NULL
,
to
=
orm
[
'auth.User'
])),
(
'updated_user'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'updated_by_user+'
,
null
=
True
,
on_delete
=
models
.
SET_NULL
,
to
=
orm
[
'auth.User'
])),
(
'course_key'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
,
db_index
=
True
)),
(
'action'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
100
,
db_index
=
True
)),
(
'state'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
50
)),
(
'should_display'
,
self
.
gf
(
'django.db.models.fields.BooleanField'
)(
default
=
False
)),
(
'message'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
1000
)),
(
'source_course_key'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
,
db_index
=
True
)),
))
db
.
send_create_signal
(
'course_action_state'
,
[
'CourseRerunState'
])
# Adding unique constraint on 'CourseRerunState', fields ['course_key', 'action']
db
.
create_unique
(
'course_action_state_coursererunstate'
,
[
'course_key'
,
'action'
])
def
backwards
(
self
,
orm
):
# Removing unique constraint on 'CourseRerunState', fields ['course_key', 'action']
db
.
delete_unique
(
'course_action_state_coursererunstate'
,
[
'course_key'
,
'action'
])
# Deleting model 'CourseRerunState'
db
.
delete_table
(
'course_action_state_coursererunstate'
)
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'course_action_state.coursererunstate'
:
{
'Meta'
:
{
'unique_together'
:
"(('course_key', 'action'),)"
,
'object_name'
:
'CourseRerunState'
},
'action'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'db_index'
:
'True'
}),
'course_key'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created_time'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'created_user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'created_by_user+'"
,
'null'
:
'True'
,
'on_delete'
:
'models.SET_NULL'
,
'to'
:
"orm['auth.User']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'message'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'1000'
}),
'should_display'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'source_course_key'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'state'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
}),
'updated_time'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'updated_user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'updated_by_user+'"
,
'null'
:
'True'
,
'on_delete'
:
'models.SET_NULL'
,
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'course_action_state'
]
\ No newline at end of file
common/djangoapps/course_action_state/migrations/__init__.py
0 → 100644
View file @
538bec92
common/djangoapps/course_action_state/models.py
0 → 100644
View file @
538bec92
"""
Models for course action state
If you make changes to this model, be sure to create an appropriate migration
file and check it in at the same time as your model changes. To do that,
1. Go to the edx-platform dir
2. ./manage.py cms schemamigration course_action_state --auto description_of_your_change
3. It adds the migration file to edx-platform/common/djangoapps/course_action_state/migrations/
"""
from
django.contrib.auth.models
import
User
from
django.db
import
models
from
xmodule_django.models
import
CourseKeyField
from
course_action_state.managers
import
CourseActionStateManager
,
CourseRerunUIStateManager
class
CourseActionState
(
models
.
Model
):
"""
A django model for maintaining state data for course actions that take a long time.
For example: course copying (reruns), import, export, and validation.
"""
class
Meta
:
"""
For performance reasons, we disable "concrete inheritance", by making the Model base class abstract.
With the "abstract base class" inheritance model, tables are only created for derived models, not for
the parent classes. This way, we don't have extra overhead of extra tables and joins that would
otherwise happen with the multi-table inheritance model.
"""
abstract
=
True
# FIELDS
# Created is the time this action was initiated
created_time
=
models
.
DateTimeField
(
auto_now_add
=
True
)
# Updated is the last time this entry was modified
updated_time
=
models
.
DateTimeField
(
auto_now
=
True
)
# User who initiated the course action
created_user
=
models
.
ForeignKey
(
User
,
# allow NULL values in case the action is not initiated by a user (e.g., a background thread)
null
=
True
,
# set on_delete to SET_NULL to prevent this model from being deleted in the event the user is deleted
on_delete
=
models
.
SET_NULL
,
# add a '+' at the end to prevent a backward relation from the User model
related_name
=
'created_by_user+'
)
# User who last updated the course action
updated_user
=
models
.
ForeignKey
(
User
,
# allow NULL values in case the action is not updated by a user (e.g., a background thread)
null
=
True
,
# set on_delete to SET_NULL to prevent this model from being deleted in the event the user is deleted
on_delete
=
models
.
SET_NULL
,
# add a '+' at the end to prevent a backward relation from the User model
related_name
=
'updated_by_user+'
)
# Course that is being acted upon
course_key
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
# Action that is being taken on the course
action
=
models
.
CharField
(
max_length
=
100
,
db_index
=
True
)
# Current state of the action.
state
=
models
.
CharField
(
max_length
=
50
)
# MANAGERS
objects
=
CourseActionStateManager
()
class
CourseActionUIState
(
CourseActionState
):
"""
An abstract django model that is a sub-class of CourseActionState with additional fields related to UI.
"""
class
Meta
:
"""
See comment in CourseActionState on disabling "concrete inheritance".
"""
abstract
=
True
# FIELDS
# Whether or not the status should be displayed to users
should_display
=
models
.
BooleanField
()
# Message related to the status
message
=
models
.
CharField
(
max_length
=
1000
)
# Rerun courses also need these fields. All rerun course actions will have a row here as well.
class
CourseRerunState
(
CourseActionUIState
):
"""
A concrete django model for maintaining state specifically for the Action Course Reruns.
"""
class
Meta
:
"""
Set the (destination) course_key field to be unique for the rerun action
Although multiple reruns can be in progress simultaneously for a particular source course_key,
only a single rerun action can be in progress for the destination course_key.
"""
unique_together
=
(
"course_key"
,
"action"
)
# FIELDS
# Original course that is being rerun
source_course_key
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
# MANAGERS
# Override the abstract class' manager with a Rerun-specific manager that inherits from the base class' manager.
objects
=
CourseRerunUIStateManager
()
common/djangoapps/course_action_state/tests/test_managers.py
0 → 100644
View file @
538bec92
# pylint: disable=invalid-name, attribute-defined-outside-init
"""
Tests for basic common operations related to Course Action State managers
"""
from
ddt
import
ddt
,
data
from
django.test
import
TestCase
from
collections
import
namedtuple
from
opaque_keys.edx.locations
import
CourseLocator
from
course_action_state.models
import
CourseRerunState
from
course_action_state.managers
import
CourseActionStateItemNotFoundError
# Sequence of Action models to be tested with ddt.
COURSE_ACTION_STATES
=
(
CourseRerunState
,
)
class
TestCourseActionStateManagerBase
(
TestCase
):
"""
Base class for testing Course Action State Managers.
"""
def
setUp
(
self
):
self
.
course_key
=
CourseLocator
(
"test_org"
,
"test_course_num"
,
"test_run"
)
@ddt
class
TestCourseActionStateManager
(
TestCourseActionStateManagerBase
):
"""
Test class for testing the CourseActionStateManager.
"""
@data
(
*
COURSE_ACTION_STATES
)
def
test_update_state_allow_not_found_is_false
(
self
,
action_class
):
with
self
.
assertRaises
(
CourseActionStateItemNotFoundError
):
action_class
.
objects
.
update_state
(
self
.
course_key
,
"fake_state"
,
allow_not_found
=
False
)
@data
(
*
COURSE_ACTION_STATES
)
def
test_update_state_allow_not_found
(
self
,
action_class
):
action_class
.
objects
.
update_state
(
self
.
course_key
,
"initial_state"
,
allow_not_found
=
True
)
self
.
assertIsNotNone
(
action_class
.
objects
.
find_first
(
course_key
=
self
.
course_key
)
)
@data
(
*
COURSE_ACTION_STATES
)
def
test_delete
(
self
,
action_class
):
obj
=
action_class
.
objects
.
update_state
(
self
.
course_key
,
"initial_state"
,
allow_not_found
=
True
)
action_class
.
objects
.
delete
(
obj
.
id
)
with
self
.
assertRaises
(
CourseActionStateItemNotFoundError
):
action_class
.
objects
.
find_first
(
course_key
=
self
.
course_key
)
@ddt
class
TestCourseActionUIStateManager
(
TestCourseActionStateManagerBase
):
"""
Test class for testing the CourseActionUIStateManager.
"""
def
init_course_action_states
(
self
,
action_class
):
"""
Creates course action state entries with different states for the given action model class.
Creates both displayable (should_display=True) and non-displayable (should_display=False) entries.
"""
def
create_course_states
(
starting_course_num
,
ending_course_num
,
state
,
should_display
=
True
):
"""
Creates a list of course state tuples by creating unique course locators with course-numbers
from starting_course_num to ending_course_num.
"""
CourseState
=
namedtuple
(
'CourseState'
,
'course_key, state, should_display'
)
return
[
CourseState
(
CourseLocator
(
"org"
,
"course"
,
"run"
+
str
(
num
)),
state
,
should_display
)
for
num
in
range
(
starting_course_num
,
ending_course_num
)
]
NUM_COURSES_WITH_STATE1
=
3
NUM_COURSES_WITH_STATE2
=
3
NUM_COURSES_WITH_STATE3
=
3
NUM_COURSES_NON_DISPLAYABLE
=
3
# courses with state1 and should_display=True
self
.
courses_with_state1
=
create_course_states
(
0
,
NUM_COURSES_WITH_STATE1
,
'state1'
)
# courses with state2 and should_display=True
self
.
courses_with_state2
=
create_course_states
(
NUM_COURSES_WITH_STATE1
,
NUM_COURSES_WITH_STATE1
+
NUM_COURSES_WITH_STATE2
,
'state2'
)
# courses with state3 and should_display=True
self
.
courses_with_state3
=
create_course_states
(
NUM_COURSES_WITH_STATE1
+
NUM_COURSES_WITH_STATE2
,
NUM_COURSES_WITH_STATE1
+
NUM_COURSES_WITH_STATE2
+
NUM_COURSES_WITH_STATE3
,
'state3'
)
# all courses with should_display=True
self
.
course_actions_displayable_states
=
(
self
.
courses_with_state1
+
self
.
courses_with_state2
+
self
.
courses_with_state3
)
# courses with state3 and should_display=False
self
.
courses_with_state3_non_displayable
=
create_course_states
(
NUM_COURSES_WITH_STATE1
+
NUM_COURSES_WITH_STATE2
+
NUM_COURSES_WITH_STATE3
,
NUM_COURSES_WITH_STATE1
+
NUM_COURSES_WITH_STATE2
+
NUM_COURSES_WITH_STATE3
+
NUM_COURSES_NON_DISPLAYABLE
,
'state3'
,
should_display
=
False
,
)
# create course action states for all courses
for
CourseState
in
(
self
.
course_actions_displayable_states
+
self
.
courses_with_state3_non_displayable
):
action_class
.
objects
.
update_state
(
CourseState
.
course_key
,
CourseState
.
state
,
should_display
=
CourseState
.
should_display
,
allow_not_found
=
True
)
def
assertCourseActionStatesEqual
(
self
,
expected
,
found
):
"""Asserts that the set of course keys in the expected state equal those that are found"""
self
.
assertSetEqual
(
set
(
course_action_state
.
course_key
for
course_action_state
in
expected
),
set
(
course_action_state
.
course_key
for
course_action_state
in
found
))
@data
(
*
COURSE_ACTION_STATES
)
def
test_find_all_for_display
(
self
,
action_class
):
self
.
init_course_action_states
(
action_class
)
self
.
assertCourseActionStatesEqual
(
self
.
course_actions_displayable_states
,
action_class
.
objects
.
find_all
(
should_display
=
True
),
)
@data
(
*
COURSE_ACTION_STATES
)
def
test_find_all_for_display_filter_exclude
(
self
,
action_class
):
self
.
init_course_action_states
(
action_class
)
for
course_action_state
,
filter_state
,
exclude_state
in
(
(
self
.
courses_with_state1
,
'state1'
,
None
),
# filter for state1
(
self
.
courses_with_state2
,
'state2'
,
None
),
# filter for state2
(
self
.
courses_with_state2
+
self
.
courses_with_state3
,
None
,
'state1'
),
# exclude state1
(
self
.
courses_with_state1
+
self
.
courses_with_state3
,
None
,
'state2'
),
# exclude state2
(
self
.
courses_with_state1
,
'state1'
,
'state2'
),
# filter for state1, exclude state2
([],
'state1'
,
'state1'
),
# filter for state1, exclude state1
):
self
.
assertCourseActionStatesEqual
(
course_action_state
,
action_class
.
objects
.
find_all
(
exclude_args
=
({
'state'
:
exclude_state
}
if
exclude_state
else
None
),
should_display
=
True
,
**
({
'state'
:
filter_state
}
if
filter_state
else
{})
)
)
def
test_kwargs_in_update_state
(
self
):
destination_course_key
=
CourseLocator
(
"org"
,
"course"
,
"run"
)
source_course_key
=
CourseLocator
(
"source_org"
,
"source_course"
,
"source_run"
)
CourseRerunState
.
objects
.
update_state
(
course_key
=
destination_course_key
,
new_state
=
'state1'
,
allow_not_found
=
True
,
source_course_key
=
source_course_key
,
)
found_action_state
=
CourseRerunState
.
objects
.
find_first
(
course_key
=
destination_course_key
)
self
.
assertEquals
(
source_course_key
,
found_action_state
.
source_course_key
)
common/djangoapps/course_action_state/tests/test_rerun_manager.py
0 → 100644
View file @
538bec92
"""
Tests specific to the CourseRerunState Model and Manager.
"""
from
django.test
import
TestCase
from
opaque_keys.edx.locations
import
CourseLocator
from
course_action_state.models
import
CourseRerunState
from
course_action_state.managers
import
CourseRerunUIStateManager
from
student.tests.factories
import
UserFactory
class
TestCourseRerunStateManager
(
TestCase
):
"""
Test class for testing the CourseRerunUIStateManager.
"""
def
setUp
(
self
):
self
.
source_course_key
=
CourseLocator
(
"source_org"
,
"source_course_num"
,
"source_run"
)
self
.
course_key
=
CourseLocator
(
"test_org"
,
"test_course_num"
,
"test_run"
)
self
.
created_user
=
UserFactory
()
self
.
expected_rerun_state
=
{
'created_user'
:
self
.
created_user
,
'updated_user'
:
self
.
created_user
,
'course_key'
:
self
.
course_key
,
'action'
:
CourseRerunUIStateManager
.
ACTION
,
'should_display'
:
True
,
'message'
:
""
,
}
def
verify_rerun_state
(
self
):
"""
Gets the rerun state object for self.course_key and verifies that the values
of its fields equal self.expected_rerun_state.
"""
found_rerun
=
CourseRerunState
.
objects
.
find_first
(
course_key
=
self
.
course_key
)
found_rerun_state
=
{
key
:
getattr
(
found_rerun
,
key
)
for
key
in
self
.
expected_rerun_state
}
self
.
assertDictEqual
(
found_rerun_state
,
self
.
expected_rerun_state
)
return
found_rerun
def
dismiss_ui_and_verify
(
self
,
rerun
):
"""
Updates the should_display field of the rerun state object for self.course_key
and verifies its new state.
"""
user_who_dismisses_ui
=
UserFactory
()
CourseRerunState
.
objects
.
update_should_display
(
entry_id
=
rerun
.
id
,
user
=
user_who_dismisses_ui
,
should_display
=
False
,
)
self
.
expected_rerun_state
.
update
({
'updated_user'
:
user_who_dismisses_ui
,
'should_display'
:
False
,
})
self
.
verify_rerun_state
()
def
test_rerun_initiated
(
self
):
CourseRerunState
.
objects
.
initiated
(
source_course_key
=
self
.
source_course_key
,
destination_course_key
=
self
.
course_key
,
user
=
self
.
created_user
)
self
.
expected_rerun_state
.
update
(
{
'state'
:
CourseRerunUIStateManager
.
State
.
IN_PROGRESS
}
)
self
.
verify_rerun_state
()
def
test_rerun_succeeded
(
self
):
# initiate
CourseRerunState
.
objects
.
initiated
(
source_course_key
=
self
.
source_course_key
,
destination_course_key
=
self
.
course_key
,
user
=
self
.
created_user
)
# set state to succeed
CourseRerunState
.
objects
.
succeeded
(
course_key
=
self
.
course_key
)
self
.
expected_rerun_state
.
update
({
'state'
:
CourseRerunUIStateManager
.
State
.
SUCCEEDED
,
})
rerun
=
self
.
verify_rerun_state
()
# dismiss ui and verify
self
.
dismiss_ui_and_verify
(
rerun
)
def
test_rerun_failed
(
self
):
# initiate
CourseRerunState
.
objects
.
initiated
(
source_course_key
=
self
.
source_course_key
,
destination_course_key
=
self
.
course_key
,
user
=
self
.
created_user
)
# set state to fail
exception
=
Exception
(
"failure in rerunning"
)
CourseRerunState
.
objects
.
failed
(
course_key
=
self
.
course_key
,
exception
=
exception
)
self
.
expected_rerun_state
.
update
({
'state'
:
CourseRerunUIStateManager
.
State
.
FAILED
,
'message'
:
exception
.
message
,
})
rerun
=
self
.
verify_rerun_state
()
# dismiss ui and verify
self
.
dismiss_ui_and_verify
(
rerun
)
lms/envs/common.py
View file @
538bec92
...
...
@@ -1317,6 +1317,9 @@ INSTALLED_APPS = (
# Monitoring functionality
'monitoring'
,
# Course action state
'course_action_state'
)
######################### MARKETING SITE ###############################
...
...
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