diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fb5aa94e5c..a0f53af981 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,12 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
+Platform: Add group_access field to all xblocks. TNL-670
+
+LMS: Add support for user partitioning based on cohort. TNL-710
+
+Platform: Add base support for cohorted group configurations. TNL-649
+
Common: Add configurable reset button to units
Studio: Add support xblock validation messages on Studio unit/container page. TNL-683
diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py
index d16a4f69bd..ba14898e69 100644
--- a/cms/djangoapps/contentstore/views/course.py
+++ b/cms/djangoapps/contentstore/views/course.py
@@ -22,7 +22,7 @@ from xmodule.error_module import ErrorDescriptor
from xmodule.modulestore.django import modulestore
from xmodule.contentstore.content import StaticContent
from xmodule.tabs import PDFTextbookTabs
-from xmodule.partitions.partitions import UserPartition, Group
+from xmodule.partitions.partitions import UserPartition
from xmodule.modulestore import EdxJSONEncoder
from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError
from opaque_keys import InvalidKeyError
@@ -1173,7 +1173,7 @@ class GroupConfiguration(object):
configuration = json.loads(json_string)
except ValueError:
raise GroupConfigurationsValidationError(_("invalid JSON"))
-
+ configuration["version"] = UserPartition.VERSION
return configuration
def validate(self):
@@ -1224,14 +1224,7 @@ class GroupConfiguration(object):
"""
Get user partition for saving in course.
"""
- groups = [Group(g["id"], g["name"]) for g in self.configuration["groups"]]
-
- return UserPartition(
- self.configuration["id"],
- self.configuration["name"],
- self.configuration["description"],
- groups
- )
+ return UserPartition.from_json(self.configuration)
@staticmethod
def get_usage_info(course, store):
@@ -1345,15 +1338,12 @@ def group_configurations_list_handler(request, course_key_string):
if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
group_configuration_url = reverse_course_url('group_configurations_list_handler', course_key)
course_outline_url = reverse_course_url('course_handler', course_key)
- split_test_enabled = SPLIT_TEST_COMPONENT_TYPE in ADVANCED_COMPONENT_TYPES and SPLIT_TEST_COMPONENT_TYPE in course.advanced_modules
-
configurations = GroupConfiguration.add_usage_info(course, store)
-
return render_to_response('group_configurations.html', {
'context_course': course,
'group_configuration_url': group_configuration_url,
'course_outline_url': course_outline_url,
- 'configurations': configurations if split_test_enabled else None,
+ 'configurations': configurations if should_show_group_configurations_page(course) else None,
})
elif "application/json" in request.META.get('HTTP_ACCEPT'):
if request.method == 'POST':
@@ -1432,6 +1422,16 @@ def group_configurations_detail_handler(request, course_key_string, group_config
return JsonResponse(status=204)
+def should_show_group_configurations_page(course):
+ """
+ Returns true if Studio should show the "Group Configurations" page for the specified course.
+ """
+ return (
+ SPLIT_TEST_COMPONENT_TYPE in ADVANCED_COMPONENT_TYPES and
+ SPLIT_TEST_COMPONENT_TYPE in course.advanced_modules
+ )
+
+
def _get_course_creator_status(user):
"""
Helper method for returning the course creator status for a particular user,
diff --git a/cms/djangoapps/contentstore/views/tests/test_group_configurations.py b/cms/djangoapps/contentstore/views/tests/test_group_configurations.py
index 7d03c23c0f..5c0e5130d1 100644
--- a/cms/djangoapps/contentstore/views/tests/test_group_configurations.py
+++ b/cms/djangoapps/contentstore/views/tests/test_group_configurations.py
@@ -15,10 +15,17 @@ from xmodule.modulestore import ModuleStoreEnum
GROUP_CONFIGURATION_JSON = {
u'name': u'Test name',
+ u'scheme': u'random',
u'description': u'Test description',
+ u'version': UserPartition.VERSION,
u'groups': [
- {u'name': u'Group A'},
- {u'name': u'Group B'},
+ {
+ u'name': u'Group A',
+ u'version': 1,
+ }, {
+ u'name': u'Group B',
+ u'version': 1,
+ },
],
}
@@ -229,7 +236,8 @@ class GroupConfigurationsListHandlerTestCase(CourseTestCase, GroupConfigurations
expected = {
u'description': u'Test description',
u'name': u'Test name',
- u'version': 1,
+ u'scheme': u'random',
+ u'version': UserPartition.VERSION,
u'groups': [
{u'name': u'Group A', u'version': 1},
{u'name': u'Group B', u'version': 1},
@@ -279,15 +287,16 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
kwargs={'group_configuration_id': cid},
)
- def test_can_create_new_group_configuration_if_it_is_not_exist(self):
+ def test_can_create_new_group_configuration_if_it_does_not_exist(self):
"""
PUT new group configuration when no configurations exist in the course.
"""
expected = {
u'id': 999,
u'name': u'Test name',
+ u'scheme': u'random',
u'description': u'Test description',
- u'version': 1,
+ u'version': UserPartition.VERSION,
u'groups': [
{u'id': 0, u'name': u'Group A', u'version': 1},
{u'id': 1, u'name': u'Group B', u'version': 1},
@@ -306,12 +315,12 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
self.assertEqual(content, expected)
self.reload_course()
# Verify that user_partitions in the course contains the new group configuration.
- user_partititons = self.course.user_partitions
- self.assertEqual(len(user_partititons), 1)
- self.assertEqual(user_partititons[0].name, u'Test name')
- self.assertEqual(len(user_partititons[0].groups), 2)
- self.assertEqual(user_partititons[0].groups[0].name, u'Group A')
- self.assertEqual(user_partititons[0].groups[1].name, u'Group B')
+ user_partitions = self.course.user_partitions
+ self.assertEqual(len(user_partitions), 1)
+ self.assertEqual(user_partitions[0].name, u'Test name')
+ self.assertEqual(len(user_partitions[0].groups), 2)
+ self.assertEqual(user_partitions[0].groups[0].name, u'Group A')
+ self.assertEqual(user_partitions[0].groups[1].name, u'Group B')
def test_can_edit_group_configuration(self):
"""
@@ -323,8 +332,9 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
expected = {
u'id': self.ID,
u'name': u'New Test name',
+ u'scheme': u'random',
u'description': u'New Test description',
- u'version': 1,
+ u'version': UserPartition.VERSION,
u'groups': [
{u'id': 0, u'name': u'New Group Name', u'version': 1},
{u'id': 2, u'name': u'Group C', u'version': 1},
@@ -430,8 +440,9 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
expected = [{
'id': 0,
'name': 'Name 0',
+ 'scheme': 'random',
'description': 'Description 0',
- 'version': 1,
+ 'version': UserPartition.VERSION,
'groups': [
{'id': 0, 'name': 'Group A', 'version': 1},
{'id': 1, 'name': 'Group B', 'version': 1},
@@ -454,8 +465,9 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
expected = [{
'id': 0,
'name': 'Name 0',
+ 'scheme': 'random',
'description': 'Description 0',
- 'version': 1,
+ 'version': UserPartition.VERSION,
'groups': [
{'id': 0, 'name': 'Group A', 'version': 1},
{'id': 1, 'name': 'Group B', 'version': 1},
@@ -469,8 +481,9 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
}, {
'id': 1,
'name': 'Name 1',
+ 'scheme': 'random',
'description': 'Description 1',
- 'version': 1,
+ 'version': UserPartition.VERSION,
'groups': [
{'id': 0, 'name': 'Group A', 'version': 1},
{'id': 1, 'name': 'Group B', 'version': 1},
@@ -495,8 +508,9 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
expected = [{
'id': 0,
'name': 'Name 0',
+ 'scheme': 'random',
'description': 'Description 0',
- 'version': 1,
+ 'version': UserPartition.VERSION,
'groups': [
{'id': 0, 'name': 'Group A', 'version': 1},
{'id': 1, 'name': 'Group B', 'version': 1},
diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py
index 496eee0c52..b496a7ffc4 100644
--- a/cms/djangoapps/contentstore/views/tests/test_item.py
+++ b/cms/djangoapps/contentstore/views/tests/test_item.py
@@ -223,8 +223,9 @@ class GetItemTest(ItemTest):
GROUP_CONFIGURATION_JSON = {
u'id': 0,
u'name': u'first_partition',
+ u'scheme': u'random',
u'description': u'First Partition',
- u'version': 1,
+ u'version': UserPartition.VERSION,
u'groups': [
{u'id': 0, u'name': u'New_NAME_A', u'version': 1},
{u'id': 1, u'name': u'New_NAME_B', u'version': 1},
diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py
index d1b0ebacf1..052ecfeb57 100644
--- a/cms/djangoapps/models/settings/course_metadata.py
+++ b/cms/djangoapps/models/settings/course_metadata.py
@@ -32,6 +32,7 @@ class CourseMetadata(object):
'name', # from xblock
'tags', # from xblock
'visible_to_staff_only',
+ 'group_access',
]
@classmethod
diff --git a/cms/envs/common.py b/cms/envs/common.py
index 4055c5c944..9243f3b514 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -575,7 +575,7 @@ INSTALLED_APPS = (
'contentstore',
'course_creators',
'student', # misleading name due to sharing with lms
- 'course_groups', # not used in cms (yet), but tests run
+ 'openedx.core.djangoapps.course_groups', # not used in cms (yet), but tests run
# Tracking
'track',
@@ -607,7 +607,7 @@ INSTALLED_APPS = (
'reverification',
# User preferences
- 'user_api',
+ 'openedx.core.djangoapps.user_api',
'django_openid_auth',
'embargo',
diff --git a/cms/static/js/models/group.js b/cms/static/js/models/group.js
index 96548f7e8e..9456c2c56a 100644
--- a/cms/static/js/models/group.js
+++ b/cms/static/js/models/group.js
@@ -7,7 +7,7 @@ define([
defaults: function() {
return {
name: '',
- version: null,
+ version: 1,
order: null
};
},
diff --git a/cms/static/js/models/group_configuration.js b/cms/static/js/models/group_configuration.js
index 95c97ee3d5..0ef7a27c6e 100644
--- a/cms/static/js/models/group_configuration.js
+++ b/cms/static/js/models/group_configuration.js
@@ -9,8 +9,9 @@ function(Backbone, _, str, gettext, GroupModel, GroupCollection) {
defaults: function() {
return {
name: '',
+ scheme: 'random',
description: '',
- version: null,
+ version: 2,
groups: new GroupCollection([
{
name: gettext('Group A'),
@@ -71,6 +72,7 @@ function(Backbone, _, str, gettext, GroupModel, GroupCollection) {
return {
id: this.get('id'),
name: this.get('name'),
+ scheme: this.get('scheme'),
description: this.get('description'),
version: this.get('version'),
groups: this.get('groups').toJSON()
diff --git a/cms/static/js/spec/models/group_configuration_spec.js b/cms/static/js/spec/models/group_configuration_spec.js
index 4a1b183f19..b58532d2a5 100644
--- a/cms/static/js/spec/models/group_configuration_spec.js
+++ b/cms/static/js/spec/models/group_configuration_spec.js
@@ -99,7 +99,8 @@ define([
'id': 10,
'name': 'My Group Configuration',
'description': 'Some description',
- 'version': 1,
+ 'version': 2,
+ 'scheme': 'random',
'groups': [
{
'version': 1,
@@ -114,9 +115,10 @@ define([
'id': 10,
'name': 'My Group Configuration',
'description': 'Some description',
+ 'scheme': 'random',
'showGroups': false,
'editing': false,
- 'version': 1,
+ 'version': 2,
'groups': [
{
'version': 1,
diff --git a/cms/templates/group_configurations.html b/cms/templates/group_configurations.html
index d4d8650cef..954b1509ff 100644
--- a/cms/templates/group_configurations.html
+++ b/cms/templates/group_configurations.html
@@ -55,7 +55,7 @@
% else:
-
${_("Loading...")}
+
${_("Loading")}
% endif
diff --git a/cms/templates/settings.html b/cms/templates/settings.html
index 4746f6bb91..ea12f2453a 100644
--- a/cms/templates/settings.html
+++ b/cms/templates/settings.html
@@ -7,6 +7,7 @@
<%!
from django.utils.translation import ugettext as _
from contentstore import utils
+ from contentstore.views.course import should_show_group_configurations_page
import urllib
%>
@@ -312,10 +313,10 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
% endif
diff --git a/cms/templates/settings_advanced.html b/cms/templates/settings_advanced.html
index f4467945ff..af021a31d3 100644
--- a/cms/templates/settings_advanced.html
+++ b/cms/templates/settings_advanced.html
@@ -4,6 +4,7 @@
<%!
from django.utils.translation import ugettext as _
from contentstore import utils
+ from contentstore.views.course import should_show_group_configurations_page
from django.utils.html import escapejs
%>
<%block name="title">${_("Advanced Settings")}%block>
@@ -91,9 +92,9 @@
${_("Details & Schedule")}
${_("Grading")}
${_("Course Team")}
- % if "split_test" in context_course.advanced_modules:
- ${_("Group Configurations")}
- % endif
+ % if should_show_group_configurations_page(context_course):
+ ${_("Group Configurations")}
+ % endif
% endif
diff --git a/cms/templates/settings_graders.html b/cms/templates/settings_graders.html
index 641b06fc77..69c15d4a24 100644
--- a/cms/templates/settings_graders.html
+++ b/cms/templates/settings_graders.html
@@ -6,6 +6,7 @@
<%namespace name='static' file='static_content.html'/>
<%!
from contentstore import utils
+ from contentstore.views.course import should_show_group_configurations_page
from django.utils.translation import ugettext as _
%>
@@ -134,10 +135,10 @@
% endif
diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html
index 266c656b17..6ff87f6d4b 100644
--- a/cms/templates/widgets/header.html
+++ b/cms/templates/widgets/header.html
@@ -3,6 +3,7 @@
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from contentstore.context_processors import doc_url
+ from contentstore.views.course import should_show_group_configurations_page
%>
<%page args="online_help_token"/>
@@ -81,14 +82,14 @@
${_("Course Team")}
+ % if should_show_group_configurations_page(context_course):
+
+ ${_("Group Configurations")}
+
+ % endif
${_("Advanced Settings")}
- % if "split_test" in context_course.advanced_modules:
-
- ${_("Group Configurations")}
-
- % endif
diff --git a/cms/urls.py b/cms/urls.py
index afdec0eec8..7e06f50339 100644
--- a/cms/urls.py
+++ b/cms/urls.py
@@ -39,7 +39,7 @@ urlpatterns = patterns('', # nopep8
url(r'^xmodule/', include('pipeline_js.urls')),
url(r'^heartbeat$', include('heartbeat.urls')),
- url(r'^user_api/', include('user_api.urls')),
+ url(r'^user_api/', include('openedx.core.djangoapps.user_api.urls')),
url(r'^lang_pref/', include('lang_pref.urls')),
)
diff --git a/common/djangoapps/lang_pref/middleware.py b/common/djangoapps/lang_pref/middleware.py
index b14ea33690..da3a64ddef 100644
--- a/common/djangoapps/lang_pref/middleware.py
+++ b/common/djangoapps/lang_pref/middleware.py
@@ -2,7 +2,7 @@
Middleware for Language Preferences
"""
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
diff --git a/common/djangoapps/lang_pref/tests/test_middleware.py b/common/djangoapps/lang_pref/tests/test_middleware.py
index 68d39265b6..042e86a712 100644
--- a/common/djangoapps/lang_pref/tests/test_middleware.py
+++ b/common/djangoapps/lang_pref/tests/test_middleware.py
@@ -3,7 +3,7 @@ from django.test.client import RequestFactory
from django.contrib.sessions.middleware import SessionMiddleware
from lang_pref.middleware import LanguagePreferenceMiddleware
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
from student.tests.factories import UserFactory
diff --git a/common/djangoapps/lang_pref/tests/test_views.py b/common/djangoapps/lang_pref/tests/test_views.py
index 7d6bffd2a9..daa8e89127 100644
--- a/common/djangoapps/lang_pref/tests/test_views.py
+++ b/common/djangoapps/lang_pref/tests/test_views.py
@@ -4,7 +4,7 @@ Tests for the language setting view
from django.core.urlresolvers import reverse
from django.test import TestCase
from student.tests.factories import UserFactory
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
diff --git a/common/djangoapps/lang_pref/views.py b/common/djangoapps/lang_pref/views.py
index 78f99b73e0..df75166392 100644
--- a/common/djangoapps/lang_pref/views.py
+++ b/common/djangoapps/lang_pref/views.py
@@ -4,7 +4,7 @@ Views for accessing language preferences
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseBadRequest
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
diff --git a/common/djangoapps/student/tests/test_create_account.py b/common/djangoapps/student/tests/test_create_account.py
index de5770f298..e72dd971eb 100644
--- a/common/djangoapps/student/tests/test_create_account.py
+++ b/common/djangoapps/student/tests/test_create_account.py
@@ -12,7 +12,7 @@ from django.test import TestCase, TransactionTestCase
import mock
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
from edxmako.tests import mako_middleware_process_request
diff --git a/common/djangoapps/student/tests/test_enrollment.py b/common/djangoapps/student/tests/test_enrollment.py
index 9e92c046c4..b99126c494 100644
--- a/common/djangoapps/student/tests/test_enrollment.py
+++ b/common/djangoapps/student/tests/test_enrollment.py
@@ -106,7 +106,7 @@ class EnrollmentTest(ModuleStoreTestCase):
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
@patch.dict(settings.FEATURES, {'ENABLE_MKTG_EMAIL_OPT_IN': True})
- @patch('user_api.api.profile.update_email_opt_in')
+ @patch('openedx.core.djangoapps.user_api.api.profile.update_email_opt_in')
@ddt.data(
([], 'true'),
([], 'false'),
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index 591853fa04..b007e15a31 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -79,7 +79,7 @@ import external_auth.views
from bulk_email.models import Optout, CourseAuthorization
import shoppingcart
from shoppingcart.models import DonationConfiguration
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
import track.views
@@ -105,7 +105,7 @@ from student.helpers import (
)
from xmodule.error_module import ErrorDescriptor
from shoppingcart.models import CourseRegistrationCode
-from user_api.api import profile as profile_api
+from openedx.core.djangoapps.user_api.api import profile as profile_api
import analytics
from eventtracking import tracker
diff --git a/common/lib/xmodule/xmodule/modulestore/inheritance.py b/common/lib/xmodule/xmodule/modulestore/inheritance.py
index 97391130ef..296fdb80ca 100644
--- a/common/lib/xmodule/xmodule/modulestore/inheritance.py
+++ b/common/lib/xmodule/xmodule/modulestore/inheritance.py
@@ -57,6 +57,15 @@ class InheritanceMixin(XBlockMixin):
default=False,
scope=Scope.settings,
)
+ group_access = Dict(
+ help="A dictionary that maps which groups can be shown this block. The keys "
+ "are group configuration ids and the values are a list of group IDs. "
+ "If there is no key for a group configuration or if the list of group IDs "
+ "is empty then the block is considered visible to all. Note that this "
+ "field is ignored if the block is visible_to_staff_only.",
+ default={},
+ scope=Scope.settings,
+ )
course_edit_method = String(
display_name=_("Course Editor"),
help=_("Enter the method by which this course is edited (\"XML\" or \"Studio\")."),
@@ -142,8 +151,8 @@ class InheritanceMixin(XBlockMixin):
# This is should be scoped to content, but since it's defined in the policy
# file, it is currently scoped to settings.
user_partitions = UserPartitionList(
- display_name=_("Experiment Group Configurations"),
- help=_("Enter the configurations that govern how students are grouped for content experiments."),
+ display_name=_("Group Configurations"),
+ help=_("Enter the configurations that govern how students are grouped together."),
default=[],
scope=Scope.settings
)
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
index c8f5b5f0ea..ca3a10a3b1 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
@@ -31,6 +31,7 @@ from xmodule.modulestore.xml_exporter import export_to_xml
from xmodule.modulestore.split_mongo.split_draft import DraftVersioningModuleStore
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
from xmodule.modulestore.inheritance import InheritanceMixin
+from xmodule.partitions.tests.test_partitions import PartitionTestCase
from xmodule.x_module import XModuleMixin
from xmodule.modulestore.xml import XMLModuleStore
@@ -291,7 +292,7 @@ COURSE_DATA_NAMES = (
@ddt.ddt
@attr('mongo')
-class CrossStoreXMLRoundtrip(CourseComparisonTest):
+class CrossStoreXMLRoundtrip(CourseComparisonTest, PartitionTestCase):
"""
This class exists to test XML import and export between different modulestore
classes.
diff --git a/common/lib/xmodule/xmodule/partitions/partitions.py b/common/lib/xmodule/xmodule/partitions/partitions.py
index c2752b951f..27b6f7a75f 100644
--- a/common/lib/xmodule/xmodule/partitions/partitions.py
+++ b/common/lib/xmodule/xmodule/partitions/partitions.py
@@ -1,10 +1,20 @@
"""Defines ``Group`` and ``UserPartition`` models for partitioning"""
+
from collections import namedtuple
+from stevedore.extension import ExtensionManager
+
# We use ``id`` in this file as the IDs of our Groups and UserPartitions,
# which Pylint disapproves of.
# pylint: disable=invalid-name, redefined-builtin
+class UserPartitionError(Exception):
+ """
+ An error was found regarding user partitions.
+ """
+ pass
+
+
class Group(namedtuple("Group", "id name")):
"""
An id and name for a group of students. The id should be unique
@@ -45,7 +55,7 @@ class Group(namedtuple("Group", "id name")):
if isinstance(value, Group):
return value
- for key in ('id', 'name', 'version'):
+ for key in ("id", "name", "version"):
if key not in value:
raise TypeError("Group dict {0} missing value key '{1}'".format(
value, key))
@@ -57,21 +67,50 @@ class Group(namedtuple("Group", "id name")):
return Group(value["id"], value["name"])
-class UserPartition(namedtuple("UserPartition", "id name description groups")):
+# The Stevedore extension point namespace for user partition scheme plugins.
+USER_PARTITION_SCHEME_NAMESPACE = 'openedx.user_partition_scheme'
+
+
+class UserPartition(namedtuple("UserPartition", "id name description groups scheme")):
"""
A named way to partition users into groups, primarily intended for running
experiments. It is expected that each user will be in at most one group in a
partition.
- A Partition has an id, name, description, and a list of groups.
+ A Partition has an id, name, scheme, description, and a list of groups.
The id is intended to be unique within the context where these are used. (e.g. for
- partitions of users within a course, the ids should be unique per-course)
+ partitions of users within a course, the ids should be unique per-course).
+ The scheme is used to assign users into groups.
"""
- VERSION = 1
+ VERSION = 2
- def __new__(cls, id, name, description, groups):
+ # The collection of user partition scheme extensions.
+ scheme_extensions = None
+
+ # The default scheme to be used when upgrading version 1 partitions.
+ VERSION_1_SCHEME = "random"
+
+ def __new__(cls, id, name, description, groups, scheme=None, scheme_id=VERSION_1_SCHEME):
# pylint: disable=super-on-old-class
- return super(UserPartition, cls).__new__(cls, int(id), name, description, groups)
+ if not scheme:
+ scheme = UserPartition.get_scheme(scheme_id)
+ return super(UserPartition, cls).__new__(cls, int(id), name, description, groups, scheme)
+
+ @staticmethod
+ def get_scheme(name):
+ """
+ Returns the user partition scheme with the given name.
+ """
+ # Note: we're creating the extension manager lazily to ensure that the Python path
+ # has been correctly set up. Trying to create this statically will fail, unfortunately.
+ if not UserPartition.scheme_extensions:
+ UserPartition.scheme_extensions = ExtensionManager(namespace=USER_PARTITION_SCHEME_NAMESPACE)
+ try:
+ scheme = UserPartition.scheme_extensions[name].plugin
+ except KeyError:
+ raise UserPartitionError("Unrecognized scheme {0}".format(name))
+ scheme.name = name
+ return scheme
def to_json(self):
"""
@@ -84,6 +123,7 @@ class UserPartition(namedtuple("UserPartition", "id name description groups")):
return {
"id": self.id,
"name": self.name,
+ "scheme": self.scheme.name,
"description": self.description,
"groups": [g.to_json() for g in self.groups],
"version": UserPartition.VERSION
@@ -102,20 +142,38 @@ class UserPartition(namedtuple("UserPartition", "id name description groups")):
if isinstance(value, UserPartition):
return value
- for key in ('id', 'name', 'description', 'version', 'groups'):
+ for key in ("id", "name", "description", "version", "groups"):
if key not in value:
- raise TypeError("UserPartition dict {0} missing value key '{1}'"
- .format(value, key))
+ raise TypeError("UserPartition dict {0} missing value key '{1}'".format(value, key))
- if value["version"] != UserPartition.VERSION:
- raise TypeError("UserPartition dict {0} has unexpected version"
- .format(value))
+ if value["version"] == 1:
+ # If no scheme was provided, set it to the default ('random')
+ scheme_id = UserPartition.VERSION_1_SCHEME
+ elif value["version"] == UserPartition.VERSION:
+ if not "scheme" in value:
+ raise TypeError("UserPartition dict {0} missing value key 'scheme'".format(value))
+ scheme_id = value["scheme"]
+ else:
+ raise TypeError("UserPartition dict {0} has unexpected version".format(value))
groups = [Group.from_json(g) for g in value["groups"]]
+ scheme = UserPartition.get_scheme(scheme_id)
+ if not scheme:
+ raise TypeError("UserPartition dict {0} has unrecognized scheme {1}".format(value, scheme_id))
return UserPartition(
value["id"],
value["name"],
value["description"],
- groups
+ groups,
+ scheme,
)
+
+ def get_group(self, group_id):
+ """
+ Returns the group with the specified id.
+ """
+ for group in self.groups: # pylint: disable=no-member
+ if group.id == group_id:
+ return group
+ return None
diff --git a/common/lib/xmodule/xmodule/partitions/partitions_service.py b/common/lib/xmodule/xmodule/partitions/partitions_service.py
index 82b283167e..bb23ca8225 100644
--- a/common/lib/xmodule/xmodule/partitions/partitions_service.py
+++ b/common/lib/xmodule/xmodule/partitions/partitions_service.py
@@ -3,7 +3,6 @@ This is a service-like API that assigns tracks which groups users are in for var
user partitions. It uses the user_service key/value store provided by the LMS runtime to
persist the assignments.
"""
-import random
from abc import ABCMeta, abstractproperty
@@ -22,13 +21,11 @@ class PartitionService(object):
"""
raise NotImplementedError('Subclasses must implement course_partition')
- def __init__(self, user_tags_service, course_id, track_function):
- self.random = random.Random()
- self._user_tags_service = user_tags_service
- self._course_id = course_id
+ def __init__(self, runtime, track_function):
+ self.runtime = runtime
self._track_function = track_function
- def get_user_group_for_partition(self, user_partition_id):
+ def get_user_group_id_for_partition(self, user_partition_id):
"""
If the user is already assigned to a group in user_partition_id, return the
group_id.
@@ -53,17 +50,15 @@ class PartitionService(object):
if user_partition is None:
raise ValueError(
"Configuration problem! No user_partition with id {0} "
- "in course {1}".format(user_partition_id, self._course_id)
+ "in course {1}".format(user_partition_id, self.runtime.course_id)
)
- group_id = self._get_group(user_partition)
-
- return group_id
+ group = self._get_group(user_partition)
+ return group.id if group else None
def _get_user_partition(self, user_partition_id):
"""
- Look for a user partition with a matching id in
- in the course's partitions.
+ Look for a user partition with a matching id in the course's partitions.
Returns:
A UserPartition, or None if not found.
@@ -74,65 +69,13 @@ class PartitionService(object):
return None
- def _key_for_partition(self, user_partition):
- """
- Returns the key to use to look up and save the user's group for a particular
- condition. Always use this function rather than constructing the key directly.
- """
- return 'xblock.partition_service.partition_{0}'.format(user_partition.id)
-
def _get_group(self, user_partition):
"""
- Return the group of the current user in user_partition. If they don't already have
- one assigned, pick one and save it. Uses the runtime's user_service service to look up
- and persist the info.
+ Returns the group from the specified user partition to which the user is assigned.
+ If the user has not yet been assigned, a group will be chosen for them based upon
+ the partition's scheme.
"""
- key = self._key_for_partition(user_partition)
- scope = self._user_tags_service.COURSE_SCOPE
-
- group_id = self._user_tags_service.get_tag(scope, key)
- if group_id is not None:
- group_id = int(group_id)
-
- partition_group_ids = [group.id for group in user_partition.groups]
-
- # If a valid group id has been saved already, return it
- if group_id is not None and group_id in partition_group_ids:
- return group_id
-
- # TODO: what's the atomicity of the get above and the save here? If it's not in a
- # single transaction, we could get a situation where the user sees one state in one
- # thread, but then that decision gets overwritten--low probability, but still bad.
-
- # (If it is truly atomic, we should be fine--if one process is in the
- # process of finding no group and making one, the other should block till it
- # appears. HOWEVER, if we allow reads by the second one while the first
- # process runs the transaction, we have a problem again: could read empty,
- # have the first transaction finish, and pick a different group in a
- # different process.)
-
- # If a group id hasn't yet been saved, or the saved group id is invalid,
- # we need to pick one, save it, then return it
-
- # TODO: had a discussion in arch council about making randomization more
- # deterministic (e.g. some hash). Could do that, but need to be careful not
- # to introduce correlation between users or bias in generation.
-
- # See note above for explanation of local_random()
- group = self.random.choice(user_partition.groups)
- self._user_tags_service.set_tag(scope, key, group.id)
-
- # emit event for analytics
- # FYI - context is always user ID that is logged in, NOT the user id that is
- # being operated on. If instructor can move user explicitly, then we should
- # put in event_info the user id that is being operated on.
- event_info = {
- 'group_id': group.id,
- 'group_name': group.name,
- 'partition_id': user_partition.id,
- 'partition_name': user_partition.name
- }
- # TODO: Use the XBlock publish api instead
- self._track_function('xmodule.partitions.assigned_user_to_partition', event_info)
-
- return group.id
+ user = self.runtime.get_real_user(self.runtime.anonymous_student_id)
+ return user_partition.scheme.get_group_for_user(
+ self.runtime.course_id, user, user_partition, track_function=self._track_function
+ )
diff --git a/common/lib/xmodule/xmodule/partitions/test_partitions.py b/common/lib/xmodule/xmodule/partitions/test_partitions.py
deleted file mode 100644
index 0ff667d34e..0000000000
--- a/common/lib/xmodule/xmodule/partitions/test_partitions.py
+++ /dev/null
@@ -1,286 +0,0 @@
-"""
-Test the partitions and partitions service
-
-"""
-
-from collections import defaultdict
-from unittest import TestCase
-from mock import Mock
-
-from xmodule.partitions.partitions import Group, UserPartition
-from xmodule.partitions.partitions_service import PartitionService
-
-
-class TestGroup(TestCase):
- """Test constructing groups"""
- def test_construct(self):
- test_id = 10
- name = "Grendel"
- group = Group(test_id, name)
- self.assertEqual(group.id, test_id)
- self.assertEqual(group.name, name)
-
- def test_string_id(self):
- test_id = "10"
- name = "Grendel"
- group = Group(test_id, name)
- self.assertEqual(group.id, 10)
-
- def test_to_json(self):
- test_id = 10
- name = "Grendel"
- group = Group(test_id, name)
- jsonified = group.to_json()
- act_jsonified = {
- "id": test_id,
- "name": name,
- "version": group.VERSION
- }
- self.assertEqual(jsonified, act_jsonified)
-
- def test_from_json(self):
- test_id = 5
- name = "Grendel"
- jsonified = {
- "id": test_id,
- "name": name,
- "version": Group.VERSION
- }
- group = Group.from_json(jsonified)
- self.assertEqual(group.id, test_id)
- self.assertEqual(group.name, name)
-
- def test_from_json_broken(self):
- test_id = 5
- name = "Grendel"
- # Bad version
- jsonified = {
- "id": test_id,
- "name": name,
- "version": 9001
- }
- with self.assertRaisesRegexp(TypeError, "has unexpected version"):
- group = Group.from_json(jsonified)
-
- # Missing key "id"
- jsonified = {
- "name": name,
- "version": Group.VERSION
- }
- with self.assertRaisesRegexp(TypeError, "missing value key 'id'"):
- group = Group.from_json(jsonified)
-
- # Has extra key - should not be a problem
- jsonified = {
- "id": test_id,
- "name": name,
- "version": Group.VERSION,
- "programmer": "Cale"
- }
- group = Group.from_json(jsonified)
- self.assertNotIn("programmer", group.to_json())
-
-
-class TestUserPartition(TestCase):
- """Test constructing UserPartitions"""
- def test_construct(self):
- groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
- user_partition = UserPartition(0, 'Test Partition', 'for testing purposes', groups)
- self.assertEqual(user_partition.id, 0)
- self.assertEqual(user_partition.name, "Test Partition")
- self.assertEqual(user_partition.description, "for testing purposes")
- self.assertEqual(user_partition.groups, groups)
-
- def test_string_id(self):
- groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
- user_partition = UserPartition("70", 'Test Partition', 'for testing purposes', groups)
- self.assertEqual(user_partition.id, 70)
-
- def test_to_json(self):
- groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
- upid = 0
- upname = "Test Partition"
- updesc = "for testing purposes"
- user_partition = UserPartition(upid, upname, updesc, groups)
-
- jsonified = user_partition.to_json()
- act_jsonified = {
- "id": upid,
- "name": upname,
- "description": updesc,
- "groups": [group.to_json() for group in groups],
- "version": user_partition.VERSION
- }
- self.assertEqual(jsonified, act_jsonified)
-
- def test_from_json(self):
- groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
- upid = 1
- upname = "Test Partition"
- updesc = "For Testing Purposes"
-
- jsonified = {
- "id": upid,
- "name": upname,
- "description": updesc,
- "groups": [group.to_json() for group in groups],
- "version": UserPartition.VERSION
- }
- user_partition = UserPartition.from_json(jsonified)
- self.assertEqual(user_partition.id, upid)
- self.assertEqual(user_partition.name, upname)
- self.assertEqual(user_partition.description, updesc)
- for act_group in user_partition.groups:
- self.assertIn(act_group.id, [0, 1])
- exp_group = groups[act_group.id]
- self.assertEqual(exp_group.id, act_group.id)
- self.assertEqual(exp_group.name, act_group.name)
-
- def test_from_json_broken(self):
- groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
- upid = 1
- upname = "Test Partition"
- updesc = "For Testing Purposes"
-
- # Missing field
- jsonified = {
- "name": upname,
- "description": updesc,
- "groups": [group.to_json() for group in groups],
- "version": UserPartition.VERSION
- }
- with self.assertRaisesRegexp(TypeError, "missing value key 'id'"):
- user_partition = UserPartition.from_json(jsonified)
-
- # Wrong version (it's over 9000!)
- jsonified = {
- 'id': upid,
- "name": upname,
- "description": updesc,
- "groups": [group.to_json() for group in groups],
- "version": 9001
- }
- with self.assertRaisesRegexp(TypeError, "has unexpected version"):
- user_partition = UserPartition.from_json(jsonified)
-
- # Has extra key - should not be a problem
- jsonified = {
- 'id': upid,
- "name": upname,
- "description": updesc,
- "groups": [group.to_json() for group in groups],
- "version": UserPartition.VERSION,
- "programmer": "Cale"
- }
- user_partition = UserPartition.from_json(jsonified)
- self.assertNotIn("programmer", user_partition.to_json())
-
-
-class StaticPartitionService(PartitionService):
- """
- Mock PartitionService for testing.
- """
- def __init__(self, partitions, **kwargs):
- super(StaticPartitionService, self).__init__(**kwargs)
- self._partitions = partitions
-
- @property
- def course_partitions(self):
- return self._partitions
-
-
-class MemoryUserTagsService(object):
- """
- An implementation of a user_tags XBlock service that
- uses an in-memory dictionary for storage
- """
- COURSE_SCOPE = 'course'
-
- def __init__(self):
- self._tags = defaultdict(dict)
-
- def get_tag(self, scope, key):
- """Sets the value of ``key`` to ``value``"""
- print 'GETTING', scope, key, self._tags
- return self._tags[scope].get(key)
-
- def set_tag(self, scope, key, value):
- """Gets the value of ``key``"""
- self._tags[scope][key] = value
- print 'SET', scope, key, value, self._tags
-
-
-class TestPartitionsService(TestCase):
- """
- Test getting a user's group out of a partition
- """
-
- def setUp(self):
- groups = [Group(0, 'Group 1'), Group(1, 'Group 2')]
- self.partition_id = 0
-
- self.user_tags_service = MemoryUserTagsService()
-
- user_partition = UserPartition(self.partition_id, 'Test Partition', 'for testing purposes', groups)
- self.partitions_service = StaticPartitionService(
- [user_partition],
- user_tags_service=self.user_tags_service,
- course_id=Mock(),
- track_function=Mock()
- )
-
- def test_get_user_group_for_partition(self):
- # get a group assigned to the user
- group1 = self.partitions_service.get_user_group_for_partition(self.partition_id)
-
- # make sure we get the same group back out if we try a second time
- group2 = self.partitions_service.get_user_group_for_partition(self.partition_id)
-
- self.assertEqual(group1, group2)
-
- # test that we error if given an invalid partition id
- with self.assertRaises(ValueError):
- self.partitions_service.get_user_group_for_partition(3)
-
- def test_user_in_deleted_group(self):
- # get a group assigned to the user - should be group 0 or 1
- old_group = self.partitions_service.get_user_group_for_partition(self.partition_id)
- self.assertIn(old_group, [0, 1])
-
- # Change the group definitions! No more group 0 or 1
- groups = [Group(3, 'Group 3'), Group(4, 'Group 4')]
- user_partition = UserPartition(self.partition_id, 'Test Partition', 'for testing purposes', groups)
- self.partitions_service = StaticPartitionService(
- [user_partition],
- user_tags_service=self.user_tags_service,
- course_id=Mock(),
- track_function=Mock()
- )
-
- # Now, get a new group using the same call - should be 3 or 4
- new_group = self.partitions_service.get_user_group_for_partition(self.partition_id)
- self.assertIn(new_group, [3, 4])
-
- # We should get the same group over multiple calls
- new_group_2 = self.partitions_service.get_user_group_for_partition(self.partition_id)
- self.assertEqual(new_group, new_group_2)
-
- def test_change_group_name(self):
- # Changing the name of the group shouldn't affect anything
- # get a group assigned to the user - should be group 0 or 1
- old_group = self.partitions_service.get_user_group_for_partition(self.partition_id)
- self.assertIn(old_group, [0, 1])
-
- # Change the group names
- groups = [Group(0, 'Group 0'), Group(1, 'Group 1')]
- user_partition = UserPartition(self.partition_id, 'Test Partition', 'for testing purposes', groups)
- self.partitions_service = StaticPartitionService(
- [user_partition],
- user_tags_service=self.user_tags_service,
- course_id=Mock(),
- track_function=Mock()
- )
-
- # Now, get a new group using the same call
- new_group = self.partitions_service.get_user_group_for_partition(self.partition_id)
- self.assertEqual(old_group, new_group)
diff --git a/common/djangoapps/course_groups/__init__.py b/common/lib/xmodule/xmodule/partitions/tests/__init__.py
similarity index 100%
rename from common/djangoapps/course_groups/__init__.py
rename to common/lib/xmodule/xmodule/partitions/tests/__init__.py
diff --git a/common/lib/xmodule/xmodule/partitions/tests/test_partitions.py b/common/lib/xmodule/xmodule/partitions/tests/test_partitions.py
new file mode 100644
index 0000000000..6b13b7cb4e
--- /dev/null
+++ b/common/lib/xmodule/xmodule/partitions/tests/test_partitions.py
@@ -0,0 +1,302 @@
+"""
+Test the partitions and partitions service
+
+"""
+
+from unittest import TestCase
+from mock import Mock
+
+from stevedore.extension import Extension, ExtensionManager
+from xmodule.partitions.partitions import Group, UserPartition, UserPartitionError, USER_PARTITION_SCHEME_NAMESPACE
+from xmodule.partitions.partitions_service import PartitionService
+from xmodule.tests import get_test_system
+
+
+class TestGroup(TestCase):
+ """Test constructing groups"""
+ def test_construct(self):
+ test_id = 10
+ name = "Grendel"
+ group = Group(test_id, name)
+ self.assertEqual(group.id, test_id) # pylint: disable=no-member
+ self.assertEqual(group.name, name)
+
+ def test_string_id(self):
+ test_id = "10"
+ name = "Grendel"
+ group = Group(test_id, name)
+ self.assertEqual(group.id, 10) # pylint: disable=no-member
+
+ def test_to_json(self):
+ test_id = 10
+ name = "Grendel"
+ group = Group(test_id, name)
+ jsonified = group.to_json()
+ act_jsonified = {
+ "id": test_id,
+ "name": name,
+ "version": group.VERSION
+ }
+ self.assertEqual(jsonified, act_jsonified)
+
+ def test_from_json(self):
+ test_id = 5
+ name = "Grendel"
+ jsonified = {
+ "id": test_id,
+ "name": name,
+ "version": Group.VERSION
+ }
+ group = Group.from_json(jsonified)
+ self.assertEqual(group.id, test_id) # pylint: disable=no-member
+ self.assertEqual(group.name, name)
+
+ def test_from_json_broken(self):
+ test_id = 5
+ name = "Grendel"
+ # Bad version
+ jsonified = {
+ "id": test_id,
+ "name": name,
+ "version": 9001
+ }
+ with self.assertRaisesRegexp(TypeError, "has unexpected version"):
+ Group.from_json(jsonified)
+
+ # Missing key "id"
+ jsonified = {
+ "name": name,
+ "version": Group.VERSION
+ }
+ with self.assertRaisesRegexp(TypeError, "missing value key 'id'"):
+ Group.from_json(jsonified)
+
+ # Has extra key - should not be a problem
+ jsonified = {
+ "id": test_id,
+ "name": name,
+ "version": Group.VERSION,
+ "programmer": "Cale"
+ }
+ group = Group.from_json(jsonified)
+ self.assertNotIn("programmer", group.to_json())
+
+
+class MockUserPartitionScheme(object):
+ """
+ Mock user partition scheme
+ """
+ def __init__(self, name="mock", current_group=None, **kwargs):
+ super(MockUserPartitionScheme, self).__init__(**kwargs)
+ self.name = name
+ self.current_group = current_group
+
+ def get_group_for_user(self, course_id, user, user_partition, track_function=None): # pylint: disable=unused-argument
+ """
+ Returns the current group if set, else the first group from the specified user partition.
+ """
+ if self.current_group:
+ return self.current_group
+ groups = user_partition.groups
+ if not groups or len(groups) == 0:
+ return None
+ return groups[0]
+
+
+class PartitionTestCase(TestCase):
+ """Base class for test cases that require partitions"""
+ TEST_ID = 0
+ TEST_NAME = "Mock Partition"
+ TEST_DESCRIPTION = "for testing purposes"
+ TEST_GROUPS = [Group(0, 'Group 1'), Group(1, 'Group 2')]
+ TEST_SCHEME_NAME = "mock"
+
+ def setUp(self):
+ # Set up two user partition schemes: mock and random
+ extensions = [
+ Extension(
+ self.TEST_SCHEME_NAME, USER_PARTITION_SCHEME_NAMESPACE,
+ MockUserPartitionScheme(self.TEST_SCHEME_NAME), None
+ ),
+ Extension(
+ "random", USER_PARTITION_SCHEME_NAMESPACE, MockUserPartitionScheme("random"), None
+ ),
+ ]
+ UserPartition.scheme_extensions = ExtensionManager.make_test_instance(
+ extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE
+ )
+
+ # Create a test partition
+ self.user_partition = UserPartition(
+ self.TEST_ID,
+ self.TEST_NAME,
+ self.TEST_DESCRIPTION,
+ self.TEST_GROUPS,
+ extensions[0].plugin
+ )
+
+
+class TestUserPartition(PartitionTestCase):
+ """Test constructing UserPartitions"""
+
+ def test_construct(self):
+ user_partition = UserPartition(
+ self.TEST_ID, self.TEST_NAME, self.TEST_DESCRIPTION, self.TEST_GROUPS, MockUserPartitionScheme()
+ )
+ self.assertEqual(user_partition.id, self.TEST_ID) # pylint: disable=no-member
+ self.assertEqual(user_partition.name, self.TEST_NAME)
+ self.assertEqual(user_partition.description, self.TEST_DESCRIPTION) # pylint: disable=no-member
+ self.assertEqual(user_partition.groups, self.TEST_GROUPS) # pylint: disable=no-member
+ self.assertEquals(user_partition.scheme.name, self.TEST_SCHEME_NAME) # pylint: disable=no-member
+
+ def test_string_id(self):
+ user_partition = UserPartition(
+ "70", self.TEST_NAME, self.TEST_DESCRIPTION, self.TEST_GROUPS
+ )
+ self.assertEqual(user_partition.id, 70) # pylint: disable=no-member
+
+ def test_to_json(self):
+ jsonified = self.user_partition.to_json()
+ act_jsonified = {
+ "id": self.TEST_ID,
+ "name": self.TEST_NAME,
+ "description": self.TEST_DESCRIPTION,
+ "groups": [group.to_json() for group in self.TEST_GROUPS],
+ "version": self.user_partition.VERSION,
+ "scheme": self.TEST_SCHEME_NAME
+ }
+ self.assertEqual(jsonified, act_jsonified)
+
+ def test_from_json(self):
+ jsonified = {
+ "id": self.TEST_ID,
+ "name": self.TEST_NAME,
+ "description": self.TEST_DESCRIPTION,
+ "groups": [group.to_json() for group in self.TEST_GROUPS],
+ "version": UserPartition.VERSION,
+ "scheme": "mock",
+ }
+ user_partition = UserPartition.from_json(jsonified)
+ self.assertEqual(user_partition.id, self.TEST_ID) # pylint: disable=no-member
+ self.assertEqual(user_partition.name, self.TEST_NAME) # pylint: disable=no-member
+ self.assertEqual(user_partition.description, self.TEST_DESCRIPTION) # pylint: disable=no-member
+ for act_group in user_partition.groups: # pylint: disable=no-member
+ self.assertIn(act_group.id, [0, 1])
+ exp_group = self.TEST_GROUPS[act_group.id]
+ self.assertEqual(exp_group.id, act_group.id)
+ self.assertEqual(exp_group.name, act_group.name)
+
+ def test_version_upgrade(self):
+ # Version 1 partitions did not have a scheme specified
+ jsonified = {
+ "id": self.TEST_ID,
+ "name": self.TEST_NAME,
+ "description": self.TEST_DESCRIPTION,
+ "groups": [group.to_json() for group in self.TEST_GROUPS],
+ "version": 1,
+ }
+ user_partition = UserPartition.from_json(jsonified)
+ self.assertEqual(user_partition.scheme.name, "random") # pylint: disable=no-member
+
+ def test_from_json_broken(self):
+ # Missing field
+ jsonified = {
+ "name": self.TEST_NAME,
+ "description": self.TEST_DESCRIPTION,
+ "groups": [group.to_json() for group in self.TEST_GROUPS],
+ "version": UserPartition.VERSION,
+ "scheme": self.TEST_SCHEME_NAME,
+ }
+ with self.assertRaisesRegexp(TypeError, "missing value key 'id'"):
+ UserPartition.from_json(jsonified)
+
+ # Missing scheme
+ jsonified = {
+ 'id': self.TEST_ID,
+ "name": self.TEST_NAME,
+ "description": self.TEST_DESCRIPTION,
+ "groups": [group.to_json() for group in self.TEST_GROUPS],
+ "version": UserPartition.VERSION,
+ }
+ with self.assertRaisesRegexp(TypeError, "missing value key 'scheme'"):
+ UserPartition.from_json(jsonified)
+
+ # Invalid scheme
+ jsonified = {
+ 'id': self.TEST_ID,
+ "name": self.TEST_NAME,
+ "description": self.TEST_DESCRIPTION,
+ "groups": [group.to_json() for group in self.TEST_GROUPS],
+ "version": UserPartition.VERSION,
+ "scheme": "no_such_scheme",
+ }
+ with self.assertRaisesRegexp(UserPartitionError, "Unrecognized scheme"):
+ UserPartition.from_json(jsonified)
+
+ # Wrong version (it's over 9000!)
+ # Wrong version (it's over 9000!)
+ jsonified = {
+ 'id': self.TEST_ID,
+ "name": self.TEST_NAME,
+ "description": self.TEST_DESCRIPTION,
+ "groups": [group.to_json() for group in self.TEST_GROUPS],
+ "version": 9001,
+ "scheme": self.TEST_SCHEME_NAME,
+ }
+ with self.assertRaisesRegexp(TypeError, "has unexpected version"):
+ UserPartition.from_json(jsonified)
+
+ # Has extra key - should not be a problem
+ jsonified = {
+ 'id': self.TEST_ID,
+ "name": self.TEST_NAME,
+ "description": self.TEST_DESCRIPTION,
+ "groups": [group.to_json() for group in self.TEST_GROUPS],
+ "version": UserPartition.VERSION,
+ "scheme": "mock",
+ "programmer": "Cale",
+ }
+ user_partition = UserPartition.from_json(jsonified)
+ self.assertNotIn("programmer", user_partition.to_json())
+
+
+class StaticPartitionService(PartitionService):
+ """
+ Mock PartitionService for testing.
+ """
+ def __init__(self, partitions, **kwargs):
+ super(StaticPartitionService, self).__init__(**kwargs)
+ self._partitions = partitions
+
+ @property
+ def course_partitions(self):
+ return self._partitions
+
+
+class TestPartitionService(PartitionTestCase):
+ """
+ Test getting a user's group out of a partition
+ """
+
+ def setUp(self):
+ super(TestPartitionService, self).setUp()
+ self.partition_service = StaticPartitionService(
+ [self.user_partition],
+ runtime=get_test_system(),
+ track_function=Mock()
+ )
+
+ def test_get_user_group_id_for_partition(self):
+ # assign the first group to be returned
+ user_partition_id = self.user_partition.id # pylint: disable=no-member
+ groups = self.user_partition.groups # pylint: disable=no-member
+ self.user_partition.scheme.current_group = groups[0] # pylint: disable=no-member
+
+ # get a group assigned to the user
+ group1_id = self.partition_service.get_user_group_id_for_partition(user_partition_id)
+ self.assertEqual(group1_id, groups[0].id) # pylint: disable=no-member
+
+ # switch to the second group and verify that it is returned for the user
+ self.user_partition.scheme.current_group = groups[1] # pylint: disable=no-member
+ group2_id = self.partition_service.get_user_group_id_for_partition(user_partition_id)
+ self.assertEqual(group2_id, groups[1].id) # pylint: disable=no-member
diff --git a/common/lib/xmodule/xmodule/split_test_module.py b/common/lib/xmodule/xmodule/split_test_module.py
index 3dd59bfd68..bd392562fe 100644
--- a/common/lib/xmodule/xmodule/split_test_module.py
+++ b/common/lib/xmodule/xmodule/split_test_module.py
@@ -80,10 +80,6 @@ class SplitTestFields(object):
# location needs to actually match one of the children of this
# Block. (expected invariant that we'll need to test, and handle
# authoring tools that mess this up)
-
- # TODO: is there a way to add some validation around this, to
- # be run on course load or in studio or ....
-
group_id_to_child = ReferenceValueDict(
help=_("Which child module students in a particular group_id should see"),
scope=Scope.content
@@ -188,7 +184,7 @@ class SplitTestModule(SplitTestFields, XModule, StudioEditableModule):
partitions_service = self.runtime.service(self, 'partitions')
if not partitions_service:
return None
- return partitions_service.get_user_group_for_partition(self.user_partition_id)
+ return partitions_service.get_user_group_id_for_partition(self.user_partition_id)
@property
def is_configured(self):
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index 4c8ff94f76..44d9fc0123 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -79,13 +79,15 @@ def get_test_system(course_id=SlashSeparatedCourseKey('org', 'course', 'run')):
where `my_render_func` is a function of the form my_render_func(template, context).
"""
+ user = Mock(is_staff=False)
return TestModuleSystem(
static_url='/static',
track_function=Mock(),
get_module=Mock(),
render_template=mock_render_template,
replace_urls=str,
- user=Mock(is_staff=False),
+ user=user,
+ get_real_user=lambda(__): user,
filestore=Mock(),
debug=True,
hostname="edx.org",
diff --git a/common/lib/xmodule/xmodule/tests/test_split_test_module.py b/common/lib/xmodule/xmodule/tests/test_split_test_module.py
index b82310c845..36755a0308 100644
--- a/common/lib/xmodule/xmodule/tests/test_split_test_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_split_test_module.py
@@ -6,6 +6,7 @@ import lxml
from mock import Mock, patch
from fs.memoryfs import MemoryFS
+from xmodule.partitions.tests.test_partitions import StaticPartitionService, PartitionTestCase, MockUserPartitionScheme
from xmodule.tests.xml import factories as xml
from xmodule.tests.xml import XModuleXmlImportTest
from xmodule.tests import get_test_system
@@ -13,7 +14,6 @@ from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW
from xmodule.validation import StudioValidationMessage
from xmodule.split_test_module import SplitTestDescriptor, SplitTestFields
from xmodule.partitions.partitions import Group, UserPartition
-from xmodule.partitions.test_partitions import StaticPartitionService, MemoryUserTagsService
class SplitTestModuleFactory(xml.XmlImportFactory):
@@ -23,11 +23,12 @@ class SplitTestModuleFactory(xml.XmlImportFactory):
tag = 'split_test'
-class SplitTestModuleTest(XModuleXmlImportTest):
+class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
"""
Base class for all split_module tests.
"""
def setUp(self):
+ super(SplitTestModuleTest, self).setUp()
self.course_id = 'test_org/test_course_number/test_run'
# construct module
course = xml.CourseFactory.build()
@@ -57,16 +58,16 @@ class SplitTestModuleTest(XModuleXmlImportTest):
self.module_system.descriptor_system = self.course.runtime
self.course.runtime.export_fs = MemoryFS()
- self.tags_service = MemoryUserTagsService()
- self.module_system._services['user_tags'] = self.tags_service # pylint: disable=protected-access
-
self.partitions_service = StaticPartitionService(
[
- UserPartition(0, 'first_partition', 'First Partition', [Group("0", 'alpha'), Group("1", 'beta')]),
- UserPartition(1, 'second_partition', 'Second Partition', [Group("0", 'abel'), Group("1", 'baker'), Group("2", 'charlie')])
+ self.user_partition,
+ UserPartition(
+ 1, 'second_partition', 'Second Partition',
+ [Group("0", 'abel'), Group("1", 'baker'), Group("2", 'charlie')],
+ MockUserPartitionScheme()
+ )
],
- user_tags_service=self.tags_service,
- course_id=self.course.id,
+ runtime=self.module_system,
track_function=Mock(name='track_function'),
)
self.module_system._services['partitions'] = self.partitions_service # pylint: disable=protected-access
@@ -81,50 +82,28 @@ class SplitTestModuleLMSTest(SplitTestModuleTest):
Test the split test module
"""
- @ddt.data(('0', 'split_test_cond0'), ('1', 'split_test_cond1'))
+ @ddt.data((0, 'split_test_cond0'), (1, 'split_test_cond1'))
@ddt.unpack
def test_child(self, user_tag, child_url_name):
- self.tags_service.set_tag(
- self.tags_service.COURSE_SCOPE,
- 'xblock.partition_service.partition_0',
- user_tag
- )
-
+ self.user_partition.scheme.current_group = self.user_partition.groups[user_tag] # pylint: disable=no-member
self.assertEquals(self.split_test_module.child_descriptor.url_name, child_url_name)
- @ddt.data(('0',), ('1',))
- @ddt.unpack
- def test_child_old_tag_value(self, _user_tag):
- # If user_tag has a stale value, we should still get back a valid child url
- self.tags_service.set_tag(
- self.tags_service.COURSE_SCOPE,
- 'xblock.partition_service.partition_0',
- '2'
- )
-
- self.assertIn(self.split_test_module.child_descriptor.url_name, ['split_test_cond0', 'split_test_cond1'])
-
- @ddt.data(('0', 'HTML FOR GROUP 0'), ('1', 'HTML FOR GROUP 1'))
+ @ddt.data((0, 'HTML FOR GROUP 0'), (1, 'HTML FOR GROUP 1'))
@ddt.unpack
def test_get_html(self, user_tag, child_content):
- self.tags_service.set_tag(
- self.tags_service.COURSE_SCOPE,
- 'xblock.partition_service.partition_0',
- user_tag
- )
-
+ self.user_partition.scheme.current_group = self.user_partition.groups[user_tag] # pylint: disable=no-member
self.assertIn(
child_content,
self.module_system.render(self.split_test_module, STUDENT_VIEW).content
)
- @ddt.data(('0',), ('1',))
+ @ddt.data((0,), (1,))
@ddt.unpack
def test_child_missing_tag_value(self, _user_tag):
# If user_tag has a missing value, we should still get back a valid child url
self.assertIn(self.split_test_module.child_descriptor.url_name, ['split_test_cond0', 'split_test_cond1'])
- @ddt.data(('100',), ('200',), ('300',), ('400',), ('500',), ('600',), ('700',), ('800',), ('900',), ('1000',))
+ @ddt.data((100,), (200,), (300,), (400,), (500,), (600,), (700,), (800,), (900,), (1000,))
@ddt.unpack
def test_child_persist_new_tag_value_when_tag_missing(self, _user_tag):
# If a user_tag has a missing value, a group should be saved/persisted for that user.
diff --git a/common/test/acceptance/.coveragerc b/common/test/acceptance/.coveragerc
index 87fe2964fe..32a237f3bb 100644
--- a/common/test/acceptance/.coveragerc
+++ b/common/test/acceptance/.coveragerc
@@ -1,7 +1,7 @@
[run]
data_file = reports/bok_choy/.coverage
source = lms, cms, common/djangoapps, common/lib
-omit = lms/envs/*, cms/envs/*, common/djangoapps/terrain/*, common/djangoapps/*/migrations/*, */test*, */management/*, */urls*, */wsgi*
+omit = lms/envs/*, cms/envs/*, common/djangoapps/terrain/*, common/djangoapps/*/migrations/*, openedx/core/djangoapps/*/migrations/*, */test*, */management/*, */urls*, */wsgi*
parallel = True
[report]
diff --git a/common/test/acceptance/tests/studio/test_studio_split_test.py b/common/test/acceptance/tests/studio/test_studio_split_test.py
index 1fabdff540..defd8d879d 100644
--- a/common/test/acceptance/tests/studio/test_studio_split_test.py
+++ b/common/test/acceptance/tests/studio/test_studio_split_test.py
@@ -9,6 +9,7 @@ from nose.plugins.attrib import attr
from selenium.webdriver.support.ui import Select
from xmodule.partitions.partitions import Group, UserPartition
+from xmodule.partitions.tests.test_partitions import MockUserPartitionScheme
from bok_choy.promise import Promise, EmptyPromise
from ...fixtures.course import XBlockFixtureDesc
@@ -30,6 +31,15 @@ class SplitTestMixin(object):
"""
Mixin that contains useful methods for split_test module testing.
"""
+ @staticmethod
+ def create_user_partition_json(partition_id, name, description, groups):
+ """
+ Helper method to create user partition JSON.
+ """
+ return UserPartition(
+ partition_id, name, description, groups, MockUserPartitionScheme("random")
+ ).to_json()
+
def verify_groups(self, container, active_groups, inactive_groups, verify_missing_groups_not_present=True):
"""
Check that the groups appear and are correctly categorized as to active and inactive.
@@ -80,8 +90,18 @@ class SplitTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, 'Configuration alpha,beta', 'first', [Group("0", 'alpha'), Group("1", 'beta')]).to_json(),
- UserPartition(1, 'Configuration 0,1,2', 'second', [Group("0", 'Group 0'), Group("1", 'Group 1'), Group("2", 'Group 2')]).to_json()
+ self.create_user_partition_json(
+ 0,
+ 'Configuration alpha,beta',
+ 'first',
+ [Group("0", 'alpha'), Group("1", 'beta')]
+ ),
+ self.create_user_partition_json(
+ 1,
+ 'Configuration 0,1,2',
+ 'second',
+ [Group("0", 'Group 0'), Group("1", 'Group 1'), Group("2", 'Group 2')]
+ ),
],
},
})
@@ -124,8 +144,12 @@ class SplitTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, 'Configuration alpha,beta', 'first',
- [Group("0", 'alpha'), Group("2", 'gamma')]).to_json()
+ self.create_user_partition_json(
+ 0,
+ 'Configuration alpha,beta',
+ 'first',
+ [Group("0", 'alpha'), Group("2", 'gamma')]
+ )
],
},
})
@@ -189,7 +213,7 @@ class SplitTest(ContainerBase, SplitTestMixin):
@attr('shard_1')
class SettingsMenuTest(StudioCourseTest):
"""
- Tests that Setting menu is rendered correctly in Studio
+ Tests that Settings menu is rendered correctly in Studio
"""
def setUp(self):
@@ -324,7 +348,7 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, "Name", "Description.", groups).to_json(),
+ self.create_user_partition_json(0, "Name", "Description.", groups),
],
},
})
@@ -396,8 +420,18 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')]).to_json(),
- UserPartition(1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]).to_json(),
+ self.create_user_partition_json(
+ 0,
+ 'Name of the Group Configuration',
+ 'Description of the group configuration.',
+ [Group("0", 'Group 0'), Group("1", 'Group 1')]
+ ),
+ self.create_user_partition_json(
+ 1,
+ 'Name of second Group Configuration',
+ 'Second group configuration.',
+ [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]
+ ),
],
},
})
@@ -531,7 +565,12 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group A'), Group("1", 'Group B'), Group("2", 'Group C')]).to_json(),
+ self.create_user_partition_json(
+ 0,
+ 'Name of the Group Configuration',
+ 'Description of the group configuration.',
+ [Group("0", 'Group A'), Group("1", 'Group B'), Group("2", 'Group C')]
+ ),
],
},
})
@@ -610,8 +649,18 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, 'Name of the Group Configuration', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')]).to_json(),
- UserPartition(1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]).to_json(),
+ self.create_user_partition_json(
+ 0,
+ 'Name of the Group Configuration',
+ 'Description of the group configuration.',
+ [Group("0", 'Group 0'), Group("1", 'Group 1')]
+ ),
+ self.create_user_partition_json(
+ 1,
+ 'Name of second Group Configuration',
+ 'Second group configuration.',
+ [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]
+ ),
],
},
})
@@ -696,7 +745,12 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")]).to_json(),
+ self.create_user_partition_json(
+ 0,
+ "Name",
+ "Description.",
+ [Group("0", "Group A"), Group("1", "Group B")]
+ ),
],
},
})
@@ -728,7 +782,12 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")]).to_json(),
+ self.create_user_partition_json(
+ 0,
+ "Name",
+ "Description.",
+ [Group("0", "Group A"), Group("1", "Group B")]
+ ),
],
},
})
@@ -771,8 +830,18 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, 'Configuration 1', 'Description of the group configuration.', [Group("0", 'Group 0'), Group("1", 'Group 1')]).to_json(),
- UserPartition(1, 'Configuration 2', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]).to_json()
+ self.create_user_partition_json(
+ 0,
+ 'Configuration 1',
+ 'Description of the group configuration.',
+ [Group("0", 'Group 0'), Group("1", 'Group 1')]
+ ),
+ self.create_user_partition_json(
+ 1,
+ 'Configuration 2',
+ 'Second group configuration.',
+ [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]
+ )
],
},
})
@@ -804,7 +873,12 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")]).to_json()
+ self.create_user_partition_json(
+ 0,
+ "Name",
+ "Description.",
+ [Group("0", "Group A"), Group("1", "Group B")]
+ )
],
},
})
@@ -840,8 +914,18 @@ class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
- UserPartition(0, "Name", "Description.", [Group("0", "Group A"), Group("1", "Group B")]).to_json(),
- UserPartition(1, 'Name of second Group Configuration', 'Second group configuration.', [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]).to_json(),
+ self.create_user_partition_json(
+ 0,
+ "Name",
+ "Description.",
+ [Group("0", "Group A"), Group("1", "Group B")]
+ ),
+ self.create_user_partition_json(
+ 1,
+ 'Name of second Group Configuration',
+ 'Second group configuration.',
+ [Group("0", 'Alpha'), Group("1", 'Beta'), Group("2", 'Gamma')]
+ ),
],
},
})
diff --git a/common/test/db_cache/bok_choy_data.json b/common/test/db_cache/bok_choy_data.json
index 22d4e181be..ac7cb2495c 100644
--- a/common/test/db_cache/bok_choy_data.json
+++ b/common/test/db_cache/bok_choy_data.json
@@ -1 +1 @@
-[{"pk": 60, "model": "contenttypes.contenttype", "fields": {"model": "accesstoken", "name": "access token", "app_label": "oauth2"}}, {"pk": 120, "model": "contenttypes.contenttype", "fields": {"model": "aiclassifier", "name": "ai classifier", "app_label": "assessment"}}, {"pk": 119, "model": "contenttypes.contenttype", "fields": {"model": "aiclassifierset", "name": "ai classifier set", "app_label": "assessment"}}, {"pk": 122, "model": "contenttypes.contenttype", "fields": {"model": "aigradingworkflow", "name": "ai grading workflow", "app_label": "assessment"}}, {"pk": 121, "model": "contenttypes.contenttype", "fields": {"model": "aitrainingworkflow", "name": "ai training workflow", "app_label": "assessment"}}, {"pk": 34, "model": "contenttypes.contenttype", "fields": {"model": "anonymoususerid", "name": "anonymous user id", "app_label": "student"}}, {"pk": 63, "model": "contenttypes.contenttype", "fields": {"model": "article", "name": "article", "app_label": "wiki"}}, {"pk": 64, "model": "contenttypes.contenttype", "fields": {"model": "articleforobject", "name": "Article for object", "app_label": "wiki"}}, {"pk": 67, "model": "contenttypes.contenttype", "fields": {"model": "articleplugin", "name": "article plugin", "app_label": "wiki"}}, {"pk": 65, "model": "contenttypes.contenttype", "fields": {"model": "articlerevision", "name": "article revision", "app_label": "wiki"}}, {"pk": 72, "model": "contenttypes.contenttype", "fields": {"model": "articlesubscription", "name": "article subscription", "app_label": "wiki"}}, {"pk": 110, "model": "contenttypes.contenttype", "fields": {"model": "assessment", "name": "assessment", "app_label": "assessment"}}, {"pk": 113, "model": "contenttypes.contenttype", "fields": {"model": "assessmentfeedback", "name": "assessment feedback", "app_label": "assessment"}}, {"pk": 112, "model": "contenttypes.contenttype", "fields": {"model": "assessmentfeedbackoption", "name": "assessment feedback option", "app_label": "assessment"}}, {"pk": 111, "model": "contenttypes.contenttype", "fields": {"model": "assessmentpart", "name": "assessment part", "app_label": "assessment"}}, {"pk": 123, "model": "contenttypes.contenttype", "fields": {"model": "assessmentworkflow", "name": "assessment workflow", "app_label": "workflow"}}, {"pk": 124, "model": "contenttypes.contenttype", "fields": {"model": "assessmentworkflowstep", "name": "assessment workflow step", "app_label": "workflow"}}, {"pk": 20, "model": "contenttypes.contenttype", "fields": {"model": "association", "name": "association", "app_label": "django_openid_auth"}}, {"pk": 25, "model": "contenttypes.contenttype", "fields": {"model": "association", "name": "association", "app_label": "default"}}, {"pk": 92, "model": "contenttypes.contenttype", "fields": {"model": "certificateitem", "name": "certificate item", "app_label": "shoppingcart"}}, {"pk": 48, "model": "contenttypes.contenttype", "fields": {"model": "certificatewhitelist", "name": "certificate whitelist", "app_label": "certificates"}}, {"pk": 58, "model": "contenttypes.contenttype", "fields": {"model": "client", "name": "client", "app_label": "oauth2"}}, {"pk": 26, "model": "contenttypes.contenttype", "fields": {"model": "code", "name": "code", "app_label": "default"}}, {"pk": 4, "model": "contenttypes.contenttype", "fields": {"model": "contenttype", "name": "content type", "app_label": "contenttypes"}}, {"pk": 88, "model": "contenttypes.contenttype", "fields": {"model": "coupon", "name": "coupon", "app_label": "shoppingcart"}}, {"pk": 89, "model": "contenttypes.contenttype", "fields": {"model": "couponredemption", "name": "coupon redemption", "app_label": "shoppingcart"}}, {"pk": 46, "model": "contenttypes.contenttype", "fields": {"model": "courseaccessrole", "name": "course access role", "app_label": "student"}}, {"pk": 56, "model": "contenttypes.contenttype", "fields": {"model": "courseauthorization", "name": "course authorization", "app_label": "bulk_email"}}, {"pk": 53, "model": "contenttypes.contenttype", "fields": {"model": "courseemail", "name": "course email", "app_label": "bulk_email"}}, {"pk": 55, "model": "contenttypes.contenttype", "fields": {"model": "courseemailtemplate", "name": "course email template", "app_label": "bulk_email"}}, {"pk": 44, "model": "contenttypes.contenttype", "fields": {"model": "courseenrollment", "name": "course enrollment", "app_label": "student"}}, {"pk": 45, "model": "contenttypes.contenttype", "fields": {"model": "courseenrollmentallowed", "name": "course enrollment allowed", "app_label": "student"}}, {"pk": 93, "model": "contenttypes.contenttype", "fields": {"model": "coursemode", "name": "course mode", "app_label": "course_modes"}}, {"pk": 94, "model": "contenttypes.contenttype", "fields": {"model": "coursemodesarchive", "name": "course modes archive", "app_label": "course_modes"}}, {"pk": 86, "model": "contenttypes.contenttype", "fields": {"model": "courseregistrationcode", "name": "course registration code", "app_label": "shoppingcart"}}, {"pk": 101, "model": "contenttypes.contenttype", "fields": {"model": "coursererunstate", "name": "course rerun state", "app_label": "course_action_state"}}, {"pk": 51, "model": "contenttypes.contenttype", "fields": {"model": "coursesoftware", "name": "course software", "app_label": "licenses"}}, {"pk": 18, "model": "contenttypes.contenttype", "fields": {"model": "courseusergroup", "name": "course user group", "app_label": "course_groups"}}, {"pk": 127, "model": "contenttypes.contenttype", "fields": {"model": "coursevideo", "name": "course video", "app_label": "edxval"}}, {"pk": 108, "model": "contenttypes.contenttype", "fields": {"model": "criterion", "name": "criterion", "app_label": "assessment"}}, {"pk": 109, "model": "contenttypes.contenttype", "fields": {"model": "criterionoption", "name": "criterion option", "app_label": "assessment"}}, {"pk": 10, "model": "contenttypes.contenttype", "fields": {"model": "crontabschedule", "name": "crontab", "app_label": "djcelery"}}, {"pk": 96, "model": "contenttypes.contenttype", "fields": {"model": "darklangconfig", "name": "dark lang config", "app_label": "dark_lang"}}, {"pk": 98, "model": "contenttypes.contenttype", "fields": {"model": "embargoedcourse", "name": "embargoed course", "app_label": "embargo"}}, {"pk": 99, "model": "contenttypes.contenttype", "fields": {"model": "embargoedstate", "name": "embargoed state", "app_label": "embargo"}}, {"pk": 128, "model": "contenttypes.contenttype", "fields": {"model": "encodedvideo", "name": "encoded video", "app_label": "edxval"}}, {"pk": 57, "model": "contenttypes.contenttype", "fields": {"model": "externalauthmap", "name": "external auth map", "app_label": "external_auth"}}, {"pk": 49, "model": "contenttypes.contenttype", "fields": {"model": "generatedcertificate", "name": "generated certificate", "app_label": "certificates"}}, {"pk": 59, "model": "contenttypes.contenttype", "fields": {"model": "grant", "name": "grant", "app_label": "oauth2"}}, {"pk": 2, "model": "contenttypes.contenttype", "fields": {"model": "group", "name": "group", "app_label": "auth"}}, {"pk": 50, "model": "contenttypes.contenttype", "fields": {"model": "instructortask", "name": "instructor task", "app_label": "instructor_task"}}, {"pk": 9, "model": "contenttypes.contenttype", "fields": {"model": "intervalschedule", "name": "interval", "app_label": "djcelery"}}, {"pk": 85, "model": "contenttypes.contenttype", "fields": {"model": "invoice", "name": "invoice", "app_label": "shoppingcart"}}, {"pk": 100, "model": "contenttypes.contenttype", "fields": {"model": "ipfilter", "name": "ip filter", "app_label": "embargo"}}, {"pk": 102, "model": "contenttypes.contenttype", "fields": {"model": "linkedin", "name": "linked in", "app_label": "linkedin"}}, {"pk": 22, "model": "contenttypes.contenttype", "fields": {"model": "logentry", "name": "log entry", "app_label": "admin"}}, {"pk": 43, "model": "contenttypes.contenttype", "fields": {"model": "loginfailures", "name": "login failures", "app_label": "student"}}, {"pk": 97, "model": "contenttypes.contenttype", "fields": {"model": "midcoursereverificationwindow", "name": "midcourse reverification window", "app_label": "reverification"}}, {"pk": 15, "model": "contenttypes.contenttype", "fields": {"model": "migrationhistory", "name": "migration history", "app_label": "south"}}, {"pk": 19, "model": "contenttypes.contenttype", "fields": {"model": "nonce", "name": "nonce", "app_label": "django_openid_auth"}}, {"pk": 24, "model": "contenttypes.contenttype", "fields": {"model": "nonce", "name": "nonce", "app_label": "default"}}, {"pk": 79, "model": "contenttypes.contenttype", "fields": {"model": "note", "name": "note", "app_label": "notes"}}, {"pk": 76, "model": "contenttypes.contenttype", "fields": {"model": "notification", "name": "notification", "app_label": "django_notify"}}, {"pk": 32, "model": "contenttypes.contenttype", "fields": {"model": "offlinecomputedgrade", "name": "offline computed grade", "app_label": "courseware"}}, {"pk": 33, "model": "contenttypes.contenttype", "fields": {"model": "offlinecomputedgradelog", "name": "offline computed grade log", "app_label": "courseware"}}, {"pk": 54, "model": "contenttypes.contenttype", "fields": {"model": "optout", "name": "optout", "app_label": "bulk_email"}}, {"pk": 83, "model": "contenttypes.contenttype", "fields": {"model": "order", "name": "order", "app_label": "shoppingcart"}}, {"pk": 84, "model": "contenttypes.contenttype", "fields": {"model": "orderitem", "name": "order item", "app_label": "shoppingcart"}}, {"pk": 90, "model": "contenttypes.contenttype", "fields": {"model": "paidcourseregistration", "name": "paid course registration", "app_label": "shoppingcart"}}, {"pk": 91, "model": "contenttypes.contenttype", "fields": {"model": "paidcourseregistrationannotation", "name": "paid course registration annotation", "app_label": "shoppingcart"}}, {"pk": 42, "model": "contenttypes.contenttype", "fields": {"model": "passwordhistory", "name": "password history", "app_label": "student"}}, {"pk": 114, "model": "contenttypes.contenttype", "fields": {"model": "peerworkflow", "name": "peer workflow", "app_label": "assessment"}}, {"pk": 115, "model": "contenttypes.contenttype", "fields": {"model": "peerworkflowitem", "name": "peer workflow item", "app_label": "assessment"}}, {"pk": 41, "model": "contenttypes.contenttype", "fields": {"model": "pendingemailchange", "name": "pending email change", "app_label": "student"}}, {"pk": 40, "model": "contenttypes.contenttype", "fields": {"model": "pendingnamechange", "name": "pending name change", "app_label": "student"}}, {"pk": 12, "model": "contenttypes.contenttype", "fields": {"model": "periodictask", "name": "periodic task", "app_label": "djcelery"}}, {"pk": 11, "model": "contenttypes.contenttype", "fields": {"model": "periodictasks", "name": "periodic tasks", "app_label": "djcelery"}}, {"pk": 1, "model": "contenttypes.contenttype", "fields": {"model": "permission", "name": "permission", "app_label": "auth"}}, {"pk": 125, "model": "contenttypes.contenttype", "fields": {"model": "profile", "name": "profile", "app_label": "edxval"}}, {"pk": 17, "model": "contenttypes.contenttype", "fields": {"model": "psychometricdata", "name": "psychometric data", "app_label": "psychometrics"}}, {"pk": 78, "model": "contenttypes.contenttype", "fields": {"model": "puzzlecomplete", "name": "puzzle complete", "app_label": "foldit"}}, {"pk": 61, "model": "contenttypes.contenttype", "fields": {"model": "refreshtoken", "name": "refresh token", "app_label": "oauth2"}}, {"pk": 39, "model": "contenttypes.contenttype", "fields": {"model": "registration", "name": "registration", "app_label": "student"}}, {"pk": 87, "model": "contenttypes.contenttype", "fields": {"model": "registrationcoderedemption", "name": "registration code redemption", "app_label": "shoppingcart"}}, {"pk": 68, "model": "contenttypes.contenttype", "fields": {"model": "reusableplugin", "name": "reusable plugin", "app_label": "wiki"}}, {"pk": 70, "model": "contenttypes.contenttype", "fields": {"model": "revisionplugin", "name": "revision plugin", "app_label": "wiki"}}, {"pk": 71, "model": "contenttypes.contenttype", "fields": {"model": "revisionpluginrevision", "name": "revision plugin revision", "app_label": "wiki"}}, {"pk": 107, "model": "contenttypes.contenttype", "fields": {"model": "rubric", "name": "rubric", "app_label": "assessment"}}, {"pk": 8, "model": "contenttypes.contenttype", "fields": {"model": "tasksetmeta", "name": "saved group result", "app_label": "djcelery"}}, {"pk": 77, "model": "contenttypes.contenttype", "fields": {"model": "score", "name": "score", "app_label": "foldit"}}, {"pk": 105, "model": "contenttypes.contenttype", "fields": {"model": "score", "name": "score", "app_label": "submissions"}}, {"pk": 106, "model": "contenttypes.contenttype", "fields": {"model": "scoresummary", "name": "score summary", "app_label": "submissions"}}, {"pk": 16, "model": "contenttypes.contenttype", "fields": {"model": "servercircuit", "name": "server circuit", "app_label": "circuit"}}, {"pk": 5, "model": "contenttypes.contenttype", "fields": {"model": "session", "name": "session", "app_label": "sessions"}}, {"pk": 74, "model": "contenttypes.contenttype", "fields": {"model": "settings", "name": "settings", "app_label": "django_notify"}}, {"pk": 69, "model": "contenttypes.contenttype", "fields": {"model": "simpleplugin", "name": "simple plugin", "app_label": "wiki"}}, {"pk": 6, "model": "contenttypes.contenttype", "fields": {"model": "site", "name": "site", "app_label": "sites"}}, {"pk": 95, "model": "contenttypes.contenttype", "fields": {"model": "softwaresecurephotoverification", "name": "software secure photo verification", "app_label": "verify_student"}}, {"pk": 80, "model": "contenttypes.contenttype", "fields": {"model": "splashconfig", "name": "splash config", "app_label": "splash"}}, {"pk": 103, "model": "contenttypes.contenttype", "fields": {"model": "studentitem", "name": "student item", "app_label": "submissions"}}, {"pk": 27, "model": "contenttypes.contenttype", "fields": {"model": "studentmodule", "name": "student module", "app_label": "courseware"}}, {"pk": 28, "model": "contenttypes.contenttype", "fields": {"model": "studentmodulehistory", "name": "student module history", "app_label": "courseware"}}, {"pk": 117, "model": "contenttypes.contenttype", "fields": {"model": "studenttrainingworkflow", "name": "student training workflow", "app_label": "assessment"}}, {"pk": 118, "model": "contenttypes.contenttype", "fields": {"model": "studenttrainingworkflowitem", "name": "student training workflow item", "app_label": "assessment"}}, {"pk": 104, "model": "contenttypes.contenttype", "fields": {"model": "submission", "name": "submission", "app_label": "submissions"}}, {"pk": 75, "model": "contenttypes.contenttype", "fields": {"model": "subscription", "name": "subscription", "app_label": "django_notify"}}, {"pk": 129, "model": "contenttypes.contenttype", "fields": {"model": "subtitle", "name": "subtitle", "app_label": "edxval"}}, {"pk": 14, "model": "contenttypes.contenttype", "fields": {"model": "taskstate", "name": "task", "app_label": "djcelery"}}, {"pk": 7, "model": "contenttypes.contenttype", "fields": {"model": "taskmeta", "name": "task state", "app_label": "djcelery"}}, {"pk": 47, "model": "contenttypes.contenttype", "fields": {"model": "trackinglog", "name": "tracking log", "app_label": "track"}}, {"pk": 116, "model": "contenttypes.contenttype", "fields": {"model": "trainingexample", "name": "training example", "app_label": "assessment"}}, {"pk": 62, "model": "contenttypes.contenttype", "fields": {"model": "trustedclient", "name": "trusted client", "app_label": "oauth2_provider"}}, {"pk": 73, "model": "contenttypes.contenttype", "fields": {"model": "notificationtype", "name": "type", "app_label": "django_notify"}}, {"pk": 66, "model": "contenttypes.contenttype", "fields": {"model": "urlpath", "name": "URL path", "app_label": "wiki"}}, {"pk": 3, "model": "contenttypes.contenttype", "fields": {"model": "user", "name": "user", "app_label": "auth"}}, {"pk": 82, "model": "contenttypes.contenttype", "fields": {"model": "usercoursetag", "name": "user course tag", "app_label": "user_api"}}, {"pk": 52, "model": "contenttypes.contenttype", "fields": {"model": "userlicense", "name": "user license", "app_label": "licenses"}}, {"pk": 21, "model": "contenttypes.contenttype", "fields": {"model": "useropenid", "name": "user open id", "app_label": "django_openid_auth"}}, {"pk": 81, "model": "contenttypes.contenttype", "fields": {"model": "userpreference", "name": "user preference", "app_label": "user_api"}}, {"pk": 36, "model": "contenttypes.contenttype", "fields": {"model": "userprofile", "name": "user profile", "app_label": "student"}}, {"pk": 37, "model": "contenttypes.contenttype", "fields": {"model": "usersignupsource", "name": "user signup source", "app_label": "student"}}, {"pk": 23, "model": "contenttypes.contenttype", "fields": {"model": "usersocialauth", "name": "user social auth", "app_label": "default"}}, {"pk": 35, "model": "contenttypes.contenttype", "fields": {"model": "userstanding", "name": "user standing", "app_label": "student"}}, {"pk": 38, "model": "contenttypes.contenttype", "fields": {"model": "usertestgroup", "name": "user test group", "app_label": "student"}}, {"pk": 126, "model": "contenttypes.contenttype", "fields": {"model": "video", "name": "video", "app_label": "edxval"}}, {"pk": 13, "model": "contenttypes.contenttype", "fields": {"model": "workerstate", "name": "worker", "app_label": "djcelery"}}, {"pk": 31, "model": "contenttypes.contenttype", "fields": {"model": "xmodulestudentinfofield", "name": "x module student info field", "app_label": "courseware"}}, {"pk": 30, "model": "contenttypes.contenttype", "fields": {"model": "xmodulestudentprefsfield", "name": "x module student prefs field", "app_label": "courseware"}}, {"pk": 29, "model": "contenttypes.contenttype", "fields": {"model": "xmoduleuserstatesummaryfield", "name": "x module user state summary field", "app_label": "courseware"}}, {"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}, {"pk": 1, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:32Z", "app_name": "courseware", "migration": "0001_initial"}}, {"pk": 2, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:32Z", "app_name": "courseware", "migration": "0002_add_indexes"}}, {"pk": 3, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:32Z", "app_name": "courseware", "migration": "0003_done_grade_cache"}}, {"pk": 4, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:33Z", "app_name": "courseware", "migration": "0004_add_field_studentmodule_course_id"}}, {"pk": 5, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:33Z", "app_name": "courseware", "migration": "0005_auto__add_offlinecomputedgrade__add_unique_offlinecomputedgrade_user_c"}}, {"pk": 6, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:33Z", "app_name": "courseware", "migration": "0006_create_student_module_history"}}, {"pk": 7, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:33Z", "app_name": "courseware", "migration": "0007_allow_null_version_in_history"}}, {"pk": 8, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:33Z", "app_name": "courseware", "migration": "0008_add_xmodule_storage"}}, {"pk": 9, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:33Z", "app_name": "courseware", "migration": "0009_add_field_default"}}, {"pk": 10, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:33Z", "app_name": "courseware", "migration": "0010_rename_xblock_field_content_to_user_state_summary"}}, {"pk": 11, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0001_initial"}}, {"pk": 12, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0002_text_to_varchar_and_indexes"}}, {"pk": 13, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0003_auto__add_usertestgroup"}}, {"pk": 14, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0004_add_email_index"}}, {"pk": 15, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0005_name_change"}}, {"pk": 16, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0006_expand_meta_field"}}, {"pk": 17, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0007_convert_to_utf8"}}, {"pk": 18, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0008__auto__add_courseregistration"}}, {"pk": 19, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0009_auto__del_courseregistration__add_courseenrollment"}}, {"pk": 20, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0010_auto__chg_field_courseenrollment_course_id"}}, {"pk": 21, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0011_auto__chg_field_courseenrollment_user__del_unique_courseenrollment_use"}}, {"pk": 22, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0012_auto__add_field_userprofile_gender__add_field_userprofile_date_of_birt"}}, {"pk": 23, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0013_auto__chg_field_userprofile_meta"}}, {"pk": 24, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0014_auto__del_courseenrollment"}}, {"pk": 25, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0015_auto__add_courseenrollment__add_unique_courseenrollment_user_course_id"}}, {"pk": 26, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:34Z", "app_name": "student", "migration": "0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country"}}, {"pk": 27, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:35Z", "app_name": "student", "migration": "0017_rename_date_to_created"}}, {"pk": 28, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:35Z", "app_name": "student", "migration": "0018_auto"}}, {"pk": 29, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:35Z", "app_name": "student", "migration": "0019_create_approved_demographic_fields_fall_2012"}}, {"pk": 30, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:35Z", "app_name": "student", "migration": "0020_add_test_center_user"}}, {"pk": 31, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:35Z", "app_name": "student", "migration": "0021_remove_askbot"}}, {"pk": 32, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:35Z", "app_name": "student", "migration": "0022_auto__add_courseenrollmentallowed__add_unique_courseenrollmentallowed_"}}, {"pk": 33, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:35Z", "app_name": "student", "migration": "0023_add_test_center_registration"}}, {"pk": 34, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:35Z", "app_name": "student", "migration": "0024_add_allow_certificate"}}, {"pk": 35, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0025_auto__add_field_courseenrollmentallowed_auto_enroll"}}, {"pk": 36, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0026_auto__remove_index_student_testcenterregistration_accommodation_request"}}, {"pk": 37, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0027_add_active_flag_and_mode_to_courseware_enrollment"}}, {"pk": 38, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0028_auto__add_userstanding"}}, {"pk": 39, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0029_add_lookup_table_between_user_and_anonymous_student_id"}}, {"pk": 40, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0029_remove_pearson"}}, {"pk": 41, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0030_auto__chg_field_anonymoususerid_anonymous_user_id"}}, {"pk": 42, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0031_drop_student_anonymoususerid_temp_archive"}}, {"pk": 43, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0032_add_field_UserProfile_country_add_field_UserProfile_city"}}, {"pk": 44, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0032_auto__add_loginfailures"}}, {"pk": 45, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0033_auto__add_passwordhistory"}}, {"pk": 46, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:36Z", "app_name": "student", "migration": "0034_auto__add_courseaccessrole"}}, {"pk": 47, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:37Z", "app_name": "student", "migration": "0035_access_roles"}}, {"pk": 48, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:37Z", "app_name": "student", "migration": "0036_access_roles_orgless"}}, {"pk": 49, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:37Z", "app_name": "student", "migration": "0037_auto__add_courseregistrationcode"}}, {"pk": 50, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:37Z", "app_name": "student", "migration": "0038_auto__add_usersignupsource"}}, {"pk": 51, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:37Z", "app_name": "student", "migration": "0039_auto__del_courseregistrationcode"}}, {"pk": 52, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "student", "migration": "0040_auto__del_field_usersignupsource_user_id__add_field_usersignupsource_u"}}, {"pk": 53, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "track", "migration": "0001_initial"}}, {"pk": 54, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "track", "migration": "0002_auto__add_field_trackinglog_host__chg_field_trackinglog_event_type__ch"}}, {"pk": 55, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "certificates", "migration": "0001_added_generatedcertificates"}}, {"pk": 56, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "certificates", "migration": "0002_auto__add_field_generatedcertificate_download_url"}}, {"pk": 57, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "certificates", "migration": "0003_auto__add_field_generatedcertificate_enabled"}}, {"pk": 58, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "certificates", "migration": "0004_auto__add_field_generatedcertificate_graded_certificate_id__add_field_"}}, {"pk": 59, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "certificates", "migration": "0005_auto__add_field_generatedcertificate_name"}}, {"pk": 60, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:38Z", "app_name": "certificates", "migration": "0006_auto__chg_field_generatedcertificate_certificate_id"}}, {"pk": 61, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0007_auto__add_revokedcertificate"}}, {"pk": 62, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add"}}, {"pk": 63, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge"}}, {"pk": 64, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti"}}, {"pk": 65, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat"}}, {"pk": 66, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0012_auto__add_field_generatedcertificate_name__add_field_generatedcertific"}}, {"pk": 67, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0013_auto__add_field_generatedcertificate_error_reason"}}, {"pk": 68, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0014_adding_whitelist"}}, {"pk": 69, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "certificates", "migration": "0015_adding_mode_for_verified_certs"}}, {"pk": 70, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "instructor_task", "migration": "0001_initial"}}, {"pk": 71, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:39Z", "app_name": "instructor_task", "migration": "0002_add_subtask_field"}}, {"pk": 72, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "licenses", "migration": "0001_initial"}}, {"pk": 73, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0001_initial"}}, {"pk": 74, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0002_change_field_names"}}, {"pk": 75, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0003_add_optout_user"}}, {"pk": 76, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0004_migrate_optout_user"}}, {"pk": 77, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0005_remove_optout_email"}}, {"pk": 78, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0006_add_course_email_template"}}, {"pk": 79, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0007_load_course_email_template"}}, {"pk": 80, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0008_add_course_authorizations"}}, {"pk": 81, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:40Z", "app_name": "bulk_email", "migration": "0009_force_unique_course_ids"}}, {"pk": 82, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:41Z", "app_name": "bulk_email", "migration": "0010_auto__chg_field_optout_course_id__add_field_courseemail_template_name_"}}, {"pk": 83, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:41Z", "app_name": "external_auth", "migration": "0001_initial"}}, {"pk": 84, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:41Z", "app_name": "oauth2", "migration": "0001_initial"}}, {"pk": 85, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:41Z", "app_name": "oauth2", "migration": "0002_auto__chg_field_client_user"}}, {"pk": 86, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:41Z", "app_name": "oauth2", "migration": "0003_auto__add_field_client_name"}}, {"pk": 87, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:41Z", "app_name": "oauth2", "migration": "0004_auto__add_index_accesstoken_token"}}, {"pk": 88, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:42Z", "app_name": "oauth2_provider", "migration": "0001_initial"}}, {"pk": 89, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:42Z", "app_name": "wiki", "migration": "0001_initial"}}, {"pk": 90, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:42Z", "app_name": "wiki", "migration": "0002_auto__add_field_articleplugin_created"}}, {"pk": 91, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0003_auto__add_field_urlpath_article"}}, {"pk": 92, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0004_populate_urlpath__article"}}, {"pk": 93, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0005_auto__chg_field_urlpath_article"}}, {"pk": 94, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0006_auto__add_attachmentrevision__add_image__add_attachment"}}, {"pk": 95, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0007_auto__add_articlesubscription"}}, {"pk": 96, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0008_auto__add_simpleplugin__add_revisionpluginrevision__add_imagerevision_"}}, {"pk": 97, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0009_auto__add_field_imagerevision_width__add_field_imagerevision_height"}}, {"pk": 98, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0010_auto__chg_field_imagerevision_image"}}, {"pk": 99, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:43Z", "app_name": "wiki", "migration": "0011_auto__chg_field_imagerevision_width__chg_field_imagerevision_height"}}, {"pk": 100, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:44Z", "app_name": "django_notify", "migration": "0001_initial"}}, {"pk": 101, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:44Z", "app_name": "notifications", "migration": "0001_initial"}}, {"pk": 102, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:44Z", "app_name": "foldit", "migration": "0001_initial"}}, {"pk": 103, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:45Z", "app_name": "django_comment_client", "migration": "0001_initial"}}, {"pk": 104, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:45Z", "app_name": "django_comment_common", "migration": "0001_initial"}}, {"pk": 105, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:45Z", "app_name": "notes", "migration": "0001_initial"}}, {"pk": 106, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:45Z", "app_name": "splash", "migration": "0001_initial"}}, {"pk": 107, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:45Z", "app_name": "splash", "migration": "0002_auto__add_field_splashconfig_unaffected_url_paths"}}, {"pk": 108, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:46Z", "app_name": "user_api", "migration": "0001_initial"}}, {"pk": 109, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:46Z", "app_name": "user_api", "migration": "0002_auto__add_usercoursetags__add_unique_usercoursetags_user_course_id_key"}}, {"pk": 110, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:46Z", "app_name": "user_api", "migration": "0003_rename_usercoursetags"}}, {"pk": 111, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:46Z", "app_name": "shoppingcart", "migration": "0001_initial"}}, {"pk": 112, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:46Z", "app_name": "shoppingcart", "migration": "0002_auto__add_field_paidcourseregistration_mode"}}, {"pk": 113, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:46Z", "app_name": "shoppingcart", "migration": "0003_auto__del_field_orderitem_line_cost"}}, {"pk": 114, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:46Z", "app_name": "shoppingcart", "migration": "0004_auto__add_field_orderitem_fulfilled_time"}}, {"pk": 115, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:47Z", "app_name": "shoppingcart", "migration": "0005_auto__add_paidcourseregistrationannotation__add_field_orderitem_report"}}, {"pk": 116, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:47Z", "app_name": "shoppingcart", "migration": "0006_auto__add_field_order_refunded_time__add_field_orderitem_refund_reques"}}, {"pk": 117, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:47Z", "app_name": "shoppingcart", "migration": "0007_auto__add_field_orderitem_service_fee"}}, {"pk": 118, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:47Z", "app_name": "shoppingcart", "migration": "0008_auto__add_coupons__add_couponredemption__chg_field_certificateitem_cou"}}, {"pk": 119, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:47Z", "app_name": "shoppingcart", "migration": "0009_auto__del_coupons__add_courseregistrationcode__add_coupon__chg_field_c"}}, {"pk": 120, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:47Z", "app_name": "shoppingcart", "migration": "0010_auto__add_registrationcoderedemption__del_field_courseregistrationcode"}}, {"pk": 121, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:47Z", "app_name": "shoppingcart", "migration": "0011_auto__add_invoice__add_field_courseregistrationcode_invoice"}}, {"pk": 122, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:48Z", "app_name": "shoppingcart", "migration": "0012_auto__del_field_courseregistrationcode_transaction_group_name__del_fie"}}, {"pk": 123, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:48Z", "app_name": "shoppingcart", "migration": "0013_auto__add_field_invoice_is_valid"}}, {"pk": 124, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:48Z", "app_name": "shoppingcart", "migration": "0014_auto__del_field_invoice_tax_id__add_field_invoice_address_line_1__add_"}}, {"pk": 125, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:48Z", "app_name": "shoppingcart", "migration": "0015_auto__del_field_invoice_purchase_order_number__del_field_invoice_compa"}}, {"pk": 126, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:48Z", "app_name": "shoppingcart", "migration": "0016_auto__del_field_invoice_company_email__del_field_invoice_company_refer"}}, {"pk": 127, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:48Z", "app_name": "shoppingcart", "migration": "0017_auto__add_field_courseregistrationcode_order__chg_field_registrationco"}}, {"pk": 128, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "course_modes", "migration": "0001_initial"}}, {"pk": 129, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "course_modes", "migration": "0002_auto__add_field_coursemode_currency"}}, {"pk": 130, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "course_modes", "migration": "0003_auto__add_unique_coursemode_course_id_currency_mode_slug"}}, {"pk": 131, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "course_modes", "migration": "0004_auto__add_field_coursemode_expiration_date"}}, {"pk": 132, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "course_modes", "migration": "0005_auto__add_field_coursemode_expiration_datetime"}}, {"pk": 133, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "course_modes", "migration": "0006_expiration_date_to_datetime"}}, {"pk": 134, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "course_modes", "migration": "0007_add_description"}}, {"pk": 135, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "course_modes", "migration": "0007_auto__add_coursemodesarchive__chg_field_coursemode_course_id"}}, {"pk": 136, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "verify_student", "migration": "0001_initial"}}, {"pk": 137, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "verify_student", "migration": "0002_auto__add_field_softwaresecurephotoverification_window"}}, {"pk": 138, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "verify_student", "migration": "0003_auto__add_field_softwaresecurephotoverification_display"}}, {"pk": 139, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "dark_lang", "migration": "0001_initial"}}, {"pk": 140, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:49Z", "app_name": "dark_lang", "migration": "0002_enable_on_install"}}, {"pk": 141, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:50Z", "app_name": "reverification", "migration": "0001_initial"}}, {"pk": 142, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:50Z", "app_name": "embargo", "migration": "0001_initial"}}, {"pk": 143, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:50Z", "app_name": "course_action_state", "migration": "0001_initial"}}, {"pk": 144, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:50Z", "app_name": "course_action_state", "migration": "0002_add_rerun_display_name"}}, {"pk": 145, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:51Z", "app_name": "linkedin", "migration": "0001_initial"}}, {"pk": 146, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:51Z", "app_name": "submissions", "migration": "0001_initial"}}, {"pk": 147, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:51Z", "app_name": "submissions", "migration": "0002_auto__add_scoresummary"}}, {"pk": 148, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:51Z", "app_name": "submissions", "migration": "0003_auto__del_field_submission_answer__add_field_submission_raw_answer"}}, {"pk": 149, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:51Z", "app_name": "submissions", "migration": "0004_auto__add_field_score_reset"}}, {"pk": 150, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:52Z", "app_name": "assessment", "migration": "0001_initial"}}, {"pk": 151, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:52Z", "app_name": "assessment", "migration": "0002_auto__add_assessmentfeedbackoption__del_field_assessmentfeedback_feedb"}}, {"pk": 152, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:52Z", "app_name": "assessment", "migration": "0003_add_index_pw_course_item_student"}}, {"pk": 153, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:52Z", "app_name": "assessment", "migration": "0004_auto__add_field_peerworkflow_graded_count"}}, {"pk": 154, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:52Z", "app_name": "assessment", "migration": "0005_auto__del_field_peerworkflow_graded_count__add_field_peerworkflow_grad"}}, {"pk": 155, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:52Z", "app_name": "assessment", "migration": "0006_auto__add_field_assessmentpart_feedback"}}, {"pk": 156, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:52Z", "app_name": "assessment", "migration": "0007_auto__chg_field_assessmentpart_feedback"}}, {"pk": 157, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:53Z", "app_name": "assessment", "migration": "0008_student_training"}}, {"pk": 158, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:53Z", "app_name": "assessment", "migration": "0009_auto__add_unique_studenttrainingworkflowitem_order_num_workflow"}}, {"pk": 159, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:53Z", "app_name": "assessment", "migration": "0010_auto__add_unique_studenttrainingworkflow_submission_uuid"}}, {"pk": 160, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:53Z", "app_name": "assessment", "migration": "0011_ai_training"}}, {"pk": 161, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:53Z", "app_name": "assessment", "migration": "0012_move_algorithm_id_to_classifier_set"}}, {"pk": 162, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:53Z", "app_name": "assessment", "migration": "0013_auto__add_field_aigradingworkflow_essay_text"}}, {"pk": 163, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:53Z", "app_name": "assessment", "migration": "0014_auto__add_field_aitrainingworkflow_item_id__add_field_aitrainingworkfl"}}, {"pk": 164, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0015_auto__add_unique_aitrainingworkflow_uuid__add_unique_aigradingworkflow"}}, {"pk": 165, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0016_auto__add_field_aiclassifierset_course_id__add_field_aiclassifierset_i"}}, {"pk": 166, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0016_auto__add_field_rubric_structure_hash"}}, {"pk": 167, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0017_rubric_structure_hash"}}, {"pk": 168, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0018_auto__add_field_assessmentpart_criterion"}}, {"pk": 169, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0019_assessmentpart_criterion_field"}}, {"pk": 170, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0020_assessmentpart_criterion_not_null"}}, {"pk": 171, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0021_assessmentpart_option_nullable"}}, {"pk": 172, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0022__add_label_fields"}}, {"pk": 173, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "assessment", "migration": "0023_assign_criteria_and_option_labels"}}, {"pk": 174, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "workflow", "migration": "0001_initial"}}, {"pk": 175, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:54Z", "app_name": "workflow", "migration": "0002_auto__add_field_assessmentworkflow_course_id__add_field_assessmentwork"}}, {"pk": 176, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:55Z", "app_name": "workflow", "migration": "0003_auto__add_assessmentworkflowstep"}}, {"pk": 177, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:55Z", "app_name": "edxval", "migration": "0001_initial"}}, {"pk": 178, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:55Z", "app_name": "edxval", "migration": "0002_default_profiles"}}, {"pk": 179, "model": "south.migrationhistory", "fields": {"applied": "2014-10-06T18:53:55Z", "app_name": "django_extensions", "migration": "0001_empty"}}, {"pk": 1, "model": "edxval.profile", "fields": {"width": 1280, "profile_name": "desktop_mp4", "extension": "mp4", "height": 720}}, {"pk": 2, "model": "edxval.profile", "fields": {"width": 1280, "profile_name": "desktop_webm", "extension": "webm", "height": 720}}, {"pk": 3, "model": "edxval.profile", "fields": {"width": 960, "profile_name": "mobile_high", "extension": "mp4", "height": 540}}, {"pk": 4, "model": "edxval.profile", "fields": {"width": 640, "profile_name": "mobile_low", "extension": "mp4", "height": 360}}, {"pk": 5, "model": "edxval.profile", "fields": {"width": 1920, "profile_name": "youtube", "extension": "mp4", "height": 1080}}, {"pk": 64, "model": "auth.permission", "fields": {"codename": "add_logentry", "name": "Can add log entry", "content_type": 22}}, {"pk": 65, "model": "auth.permission", "fields": {"codename": "change_logentry", "name": "Can change log entry", "content_type": 22}}, {"pk": 66, "model": "auth.permission", "fields": {"codename": "delete_logentry", "name": "Can delete log entry", "content_type": 22}}, {"pk": 361, "model": "auth.permission", "fields": {"codename": "add_aiclassifier", "name": "Can add ai classifier", "content_type": 120}}, {"pk": 362, "model": "auth.permission", "fields": {"codename": "change_aiclassifier", "name": "Can change ai classifier", "content_type": 120}}, {"pk": 363, "model": "auth.permission", "fields": {"codename": "delete_aiclassifier", "name": "Can delete ai classifier", "content_type": 120}}, {"pk": 358, "model": "auth.permission", "fields": {"codename": "add_aiclassifierset", "name": "Can add ai classifier set", "content_type": 119}}, {"pk": 359, "model": "auth.permission", "fields": {"codename": "change_aiclassifierset", "name": "Can change ai classifier set", "content_type": 119}}, {"pk": 360, "model": "auth.permission", "fields": {"codename": "delete_aiclassifierset", "name": "Can delete ai classifier set", "content_type": 119}}, {"pk": 367, "model": "auth.permission", "fields": {"codename": "add_aigradingworkflow", "name": "Can add ai grading workflow", "content_type": 122}}, {"pk": 368, "model": "auth.permission", "fields": {"codename": "change_aigradingworkflow", "name": "Can change ai grading workflow", "content_type": 122}}, {"pk": 369, "model": "auth.permission", "fields": {"codename": "delete_aigradingworkflow", "name": "Can delete ai grading workflow", "content_type": 122}}, {"pk": 364, "model": "auth.permission", "fields": {"codename": "add_aitrainingworkflow", "name": "Can add ai training workflow", "content_type": 121}}, {"pk": 365, "model": "auth.permission", "fields": {"codename": "change_aitrainingworkflow", "name": "Can change ai training workflow", "content_type": 121}}, {"pk": 366, "model": "auth.permission", "fields": {"codename": "delete_aitrainingworkflow", "name": "Can delete ai training workflow", "content_type": 121}}, {"pk": 331, "model": "auth.permission", "fields": {"codename": "add_assessment", "name": "Can add assessment", "content_type": 110}}, {"pk": 332, "model": "auth.permission", "fields": {"codename": "change_assessment", "name": "Can change assessment", "content_type": 110}}, {"pk": 333, "model": "auth.permission", "fields": {"codename": "delete_assessment", "name": "Can delete assessment", "content_type": 110}}, {"pk": 340, "model": "auth.permission", "fields": {"codename": "add_assessmentfeedback", "name": "Can add assessment feedback", "content_type": 113}}, {"pk": 341, "model": "auth.permission", "fields": {"codename": "change_assessmentfeedback", "name": "Can change assessment feedback", "content_type": 113}}, {"pk": 342, "model": "auth.permission", "fields": {"codename": "delete_assessmentfeedback", "name": "Can delete assessment feedback", "content_type": 113}}, {"pk": 337, "model": "auth.permission", "fields": {"codename": "add_assessmentfeedbackoption", "name": "Can add assessment feedback option", "content_type": 112}}, {"pk": 338, "model": "auth.permission", "fields": {"codename": "change_assessmentfeedbackoption", "name": "Can change assessment feedback option", "content_type": 112}}, {"pk": 339, "model": "auth.permission", "fields": {"codename": "delete_assessmentfeedbackoption", "name": "Can delete assessment feedback option", "content_type": 112}}, {"pk": 334, "model": "auth.permission", "fields": {"codename": "add_assessmentpart", "name": "Can add assessment part", "content_type": 111}}, {"pk": 335, "model": "auth.permission", "fields": {"codename": "change_assessmentpart", "name": "Can change assessment part", "content_type": 111}}, {"pk": 336, "model": "auth.permission", "fields": {"codename": "delete_assessmentpart", "name": "Can delete assessment part", "content_type": 111}}, {"pk": 325, "model": "auth.permission", "fields": {"codename": "add_criterion", "name": "Can add criterion", "content_type": 108}}, {"pk": 326, "model": "auth.permission", "fields": {"codename": "change_criterion", "name": "Can change criterion", "content_type": 108}}, {"pk": 327, "model": "auth.permission", "fields": {"codename": "delete_criterion", "name": "Can delete criterion", "content_type": 108}}, {"pk": 328, "model": "auth.permission", "fields": {"codename": "add_criterionoption", "name": "Can add criterion option", "content_type": 109}}, {"pk": 329, "model": "auth.permission", "fields": {"codename": "change_criterionoption", "name": "Can change criterion option", "content_type": 109}}, {"pk": 330, "model": "auth.permission", "fields": {"codename": "delete_criterionoption", "name": "Can delete criterion option", "content_type": 109}}, {"pk": 343, "model": "auth.permission", "fields": {"codename": "add_peerworkflow", "name": "Can add peer workflow", "content_type": 114}}, {"pk": 344, "model": "auth.permission", "fields": {"codename": "change_peerworkflow", "name": "Can change peer workflow", "content_type": 114}}, {"pk": 345, "model": "auth.permission", "fields": {"codename": "delete_peerworkflow", "name": "Can delete peer workflow", "content_type": 114}}, {"pk": 346, "model": "auth.permission", "fields": {"codename": "add_peerworkflowitem", "name": "Can add peer workflow item", "content_type": 115}}, {"pk": 347, "model": "auth.permission", "fields": {"codename": "change_peerworkflowitem", "name": "Can change peer workflow item", "content_type": 115}}, {"pk": 348, "model": "auth.permission", "fields": {"codename": "delete_peerworkflowitem", "name": "Can delete peer workflow item", "content_type": 115}}, {"pk": 322, "model": "auth.permission", "fields": {"codename": "add_rubric", "name": "Can add rubric", "content_type": 107}}, {"pk": 323, "model": "auth.permission", "fields": {"codename": "change_rubric", "name": "Can change rubric", "content_type": 107}}, {"pk": 324, "model": "auth.permission", "fields": {"codename": "delete_rubric", "name": "Can delete rubric", "content_type": 107}}, {"pk": 352, "model": "auth.permission", "fields": {"codename": "add_studenttrainingworkflow", "name": "Can add student training workflow", "content_type": 117}}, {"pk": 353, "model": "auth.permission", "fields": {"codename": "change_studenttrainingworkflow", "name": "Can change student training workflow", "content_type": 117}}, {"pk": 354, "model": "auth.permission", "fields": {"codename": "delete_studenttrainingworkflow", "name": "Can delete student training workflow", "content_type": 117}}, {"pk": 355, "model": "auth.permission", "fields": {"codename": "add_studenttrainingworkflowitem", "name": "Can add student training workflow item", "content_type": 118}}, {"pk": 356, "model": "auth.permission", "fields": {"codename": "change_studenttrainingworkflowitem", "name": "Can change student training workflow item", "content_type": 118}}, {"pk": 357, "model": "auth.permission", "fields": {"codename": "delete_studenttrainingworkflowitem", "name": "Can delete student training workflow item", "content_type": 118}}, {"pk": 349, "model": "auth.permission", "fields": {"codename": "add_trainingexample", "name": "Can add training example", "content_type": 116}}, {"pk": 350, "model": "auth.permission", "fields": {"codename": "change_trainingexample", "name": "Can change training example", "content_type": 116}}, {"pk": 351, "model": "auth.permission", "fields": {"codename": "delete_trainingexample", "name": "Can delete training example", "content_type": 116}}, {"pk": 4, "model": "auth.permission", "fields": {"codename": "add_group", "name": "Can add group", "content_type": 2}}, {"pk": 5, "model": "auth.permission", "fields": {"codename": "change_group", "name": "Can change group", "content_type": 2}}, {"pk": 6, "model": "auth.permission", "fields": {"codename": "delete_group", "name": "Can delete group", "content_type": 2}}, {"pk": 1, "model": "auth.permission", "fields": {"codename": "add_permission", "name": "Can add permission", "content_type": 1}}, {"pk": 2, "model": "auth.permission", "fields": {"codename": "change_permission", "name": "Can change permission", "content_type": 1}}, {"pk": 3, "model": "auth.permission", "fields": {"codename": "delete_permission", "name": "Can delete permission", "content_type": 1}}, {"pk": 7, "model": "auth.permission", "fields": {"codename": "add_user", "name": "Can add user", "content_type": 3}}, {"pk": 8, "model": "auth.permission", "fields": {"codename": "change_user", "name": "Can change user", "content_type": 3}}, {"pk": 9, "model": "auth.permission", "fields": {"codename": "delete_user", "name": "Can delete user", "content_type": 3}}, {"pk": 166, "model": "auth.permission", "fields": {"codename": "add_courseauthorization", "name": "Can add course authorization", "content_type": 56}}, {"pk": 167, "model": "auth.permission", "fields": {"codename": "change_courseauthorization", "name": "Can change course authorization", "content_type": 56}}, {"pk": 168, "model": "auth.permission", "fields": {"codename": "delete_courseauthorization", "name": "Can delete course authorization", "content_type": 56}}, {"pk": 157, "model": "auth.permission", "fields": {"codename": "add_courseemail", "name": "Can add course email", "content_type": 53}}, {"pk": 158, "model": "auth.permission", "fields": {"codename": "change_courseemail", "name": "Can change course email", "content_type": 53}}, {"pk": 159, "model": "auth.permission", "fields": {"codename": "delete_courseemail", "name": "Can delete course email", "content_type": 53}}, {"pk": 163, "model": "auth.permission", "fields": {"codename": "add_courseemailtemplate", "name": "Can add course email template", "content_type": 55}}, {"pk": 164, "model": "auth.permission", "fields": {"codename": "change_courseemailtemplate", "name": "Can change course email template", "content_type": 55}}, {"pk": 165, "model": "auth.permission", "fields": {"codename": "delete_courseemailtemplate", "name": "Can delete course email template", "content_type": 55}}, {"pk": 160, "model": "auth.permission", "fields": {"codename": "add_optout", "name": "Can add optout", "content_type": 54}}, {"pk": 161, "model": "auth.permission", "fields": {"codename": "change_optout", "name": "Can change optout", "content_type": 54}}, {"pk": 162, "model": "auth.permission", "fields": {"codename": "delete_optout", "name": "Can delete optout", "content_type": 54}}, {"pk": 142, "model": "auth.permission", "fields": {"codename": "add_certificatewhitelist", "name": "Can add certificate whitelist", "content_type": 48}}, {"pk": 143, "model": "auth.permission", "fields": {"codename": "change_certificatewhitelist", "name": "Can change certificate whitelist", "content_type": 48}}, {"pk": 144, "model": "auth.permission", "fields": {"codename": "delete_certificatewhitelist", "name": "Can delete certificate whitelist", "content_type": 48}}, {"pk": 145, "model": "auth.permission", "fields": {"codename": "add_generatedcertificate", "name": "Can add generated certificate", "content_type": 49}}, {"pk": 146, "model": "auth.permission", "fields": {"codename": "change_generatedcertificate", "name": "Can change generated certificate", "content_type": 49}}, {"pk": 147, "model": "auth.permission", "fields": {"codename": "delete_generatedcertificate", "name": "Can delete generated certificate", "content_type": 49}}, {"pk": 46, "model": "auth.permission", "fields": {"codename": "add_servercircuit", "name": "Can add server circuit", "content_type": 16}}, {"pk": 47, "model": "auth.permission", "fields": {"codename": "change_servercircuit", "name": "Can change server circuit", "content_type": 16}}, {"pk": 48, "model": "auth.permission", "fields": {"codename": "delete_servercircuit", "name": "Can delete server circuit", "content_type": 16}}, {"pk": 10, "model": "auth.permission", "fields": {"codename": "add_contenttype", "name": "Can add content type", "content_type": 4}}, {"pk": 11, "model": "auth.permission", "fields": {"codename": "change_contenttype", "name": "Can change content type", "content_type": 4}}, {"pk": 12, "model": "auth.permission", "fields": {"codename": "delete_contenttype", "name": "Can delete content type", "content_type": 4}}, {"pk": 94, "model": "auth.permission", "fields": {"codename": "add_offlinecomputedgrade", "name": "Can add offline computed grade", "content_type": 32}}, {"pk": 95, "model": "auth.permission", "fields": {"codename": "change_offlinecomputedgrade", "name": "Can change offline computed grade", "content_type": 32}}, {"pk": 96, "model": "auth.permission", "fields": {"codename": "delete_offlinecomputedgrade", "name": "Can delete offline computed grade", "content_type": 32}}, {"pk": 97, "model": "auth.permission", "fields": {"codename": "add_offlinecomputedgradelog", "name": "Can add offline computed grade log", "content_type": 33}}, {"pk": 98, "model": "auth.permission", "fields": {"codename": "change_offlinecomputedgradelog", "name": "Can change offline computed grade log", "content_type": 33}}, {"pk": 99, "model": "auth.permission", "fields": {"codename": "delete_offlinecomputedgradelog", "name": "Can delete offline computed grade log", "content_type": 33}}, {"pk": 79, "model": "auth.permission", "fields": {"codename": "add_studentmodule", "name": "Can add student module", "content_type": 27}}, {"pk": 80, "model": "auth.permission", "fields": {"codename": "change_studentmodule", "name": "Can change student module", "content_type": 27}}, {"pk": 81, "model": "auth.permission", "fields": {"codename": "delete_studentmodule", "name": "Can delete student module", "content_type": 27}}, {"pk": 82, "model": "auth.permission", "fields": {"codename": "add_studentmodulehistory", "name": "Can add student module history", "content_type": 28}}, {"pk": 83, "model": "auth.permission", "fields": {"codename": "change_studentmodulehistory", "name": "Can change student module history", "content_type": 28}}, {"pk": 84, "model": "auth.permission", "fields": {"codename": "delete_studentmodulehistory", "name": "Can delete student module history", "content_type": 28}}, {"pk": 91, "model": "auth.permission", "fields": {"codename": "add_xmodulestudentinfofield", "name": "Can add x module student info field", "content_type": 31}}, {"pk": 92, "model": "auth.permission", "fields": {"codename": "change_xmodulestudentinfofield", "name": "Can change x module student info field", "content_type": 31}}, {"pk": 93, "model": "auth.permission", "fields": {"codename": "delete_xmodulestudentinfofield", "name": "Can delete x module student info field", "content_type": 31}}, {"pk": 88, "model": "auth.permission", "fields": {"codename": "add_xmodulestudentprefsfield", "name": "Can add x module student prefs field", "content_type": 30}}, {"pk": 89, "model": "auth.permission", "fields": {"codename": "change_xmodulestudentprefsfield", "name": "Can change x module student prefs field", "content_type": 30}}, {"pk": 90, "model": "auth.permission", "fields": {"codename": "delete_xmodulestudentprefsfield", "name": "Can delete x module student prefs field", "content_type": 30}}, {"pk": 85, "model": "auth.permission", "fields": {"codename": "add_xmoduleuserstatesummaryfield", "name": "Can add x module user state summary field", "content_type": 29}}, {"pk": 86, "model": "auth.permission", "fields": {"codename": "change_xmoduleuserstatesummaryfield", "name": "Can change x module user state summary field", "content_type": 29}}, {"pk": 87, "model": "auth.permission", "fields": {"codename": "delete_xmoduleuserstatesummaryfield", "name": "Can delete x module user state summary field", "content_type": 29}}, {"pk": 304, "model": "auth.permission", "fields": {"codename": "add_coursererunstate", "name": "Can add course rerun state", "content_type": 101}}, {"pk": 305, "model": "auth.permission", "fields": {"codename": "change_coursererunstate", "name": "Can change course rerun state", "content_type": 101}}, {"pk": 306, "model": "auth.permission", "fields": {"codename": "delete_coursererunstate", "name": "Can delete course rerun state", "content_type": 101}}, {"pk": 52, "model": "auth.permission", "fields": {"codename": "add_courseusergroup", "name": "Can add course user group", "content_type": 18}}, {"pk": 53, "model": "auth.permission", "fields": {"codename": "change_courseusergroup", "name": "Can change course user group", "content_type": 18}}, {"pk": 54, "model": "auth.permission", "fields": {"codename": "delete_courseusergroup", "name": "Can delete course user group", "content_type": 18}}, {"pk": 280, "model": "auth.permission", "fields": {"codename": "add_coursemode", "name": "Can add course mode", "content_type": 93}}, {"pk": 281, "model": "auth.permission", "fields": {"codename": "change_coursemode", "name": "Can change course mode", "content_type": 93}}, {"pk": 282, "model": "auth.permission", "fields": {"codename": "delete_coursemode", "name": "Can delete course mode", "content_type": 93}}, {"pk": 283, "model": "auth.permission", "fields": {"codename": "add_coursemodesarchive", "name": "Can add course modes archive", "content_type": 94}}, {"pk": 284, "model": "auth.permission", "fields": {"codename": "change_coursemodesarchive", "name": "Can change course modes archive", "content_type": 94}}, {"pk": 285, "model": "auth.permission", "fields": {"codename": "delete_coursemodesarchive", "name": "Can delete course modes archive", "content_type": 94}}, {"pk": 289, "model": "auth.permission", "fields": {"codename": "add_darklangconfig", "name": "Can add dark lang config", "content_type": 96}}, {"pk": 290, "model": "auth.permission", "fields": {"codename": "change_darklangconfig", "name": "Can change dark lang config", "content_type": 96}}, {"pk": 291, "model": "auth.permission", "fields": {"codename": "delete_darklangconfig", "name": "Can delete dark lang config", "content_type": 96}}, {"pk": 73, "model": "auth.permission", "fields": {"codename": "add_association", "name": "Can add association", "content_type": 25}}, {"pk": 74, "model": "auth.permission", "fields": {"codename": "change_association", "name": "Can change association", "content_type": 25}}, {"pk": 75, "model": "auth.permission", "fields": {"codename": "delete_association", "name": "Can delete association", "content_type": 25}}, {"pk": 76, "model": "auth.permission", "fields": {"codename": "add_code", "name": "Can add code", "content_type": 26}}, {"pk": 77, "model": "auth.permission", "fields": {"codename": "change_code", "name": "Can change code", "content_type": 26}}, {"pk": 78, "model": "auth.permission", "fields": {"codename": "delete_code", "name": "Can delete code", "content_type": 26}}, {"pk": 70, "model": "auth.permission", "fields": {"codename": "add_nonce", "name": "Can add nonce", "content_type": 24}}, {"pk": 71, "model": "auth.permission", "fields": {"codename": "change_nonce", "name": "Can change nonce", "content_type": 24}}, {"pk": 72, "model": "auth.permission", "fields": {"codename": "delete_nonce", "name": "Can delete nonce", "content_type": 24}}, {"pk": 67, "model": "auth.permission", "fields": {"codename": "add_usersocialauth", "name": "Can add user social auth", "content_type": 23}}, {"pk": 68, "model": "auth.permission", "fields": {"codename": "change_usersocialauth", "name": "Can change user social auth", "content_type": 23}}, {"pk": 69, "model": "auth.permission", "fields": {"codename": "delete_usersocialauth", "name": "Can delete user social auth", "content_type": 23}}, {"pk": 229, "model": "auth.permission", "fields": {"codename": "add_notification", "name": "Can add notification", "content_type": 76}}, {"pk": 230, "model": "auth.permission", "fields": {"codename": "change_notification", "name": "Can change notification", "content_type": 76}}, {"pk": 231, "model": "auth.permission", "fields": {"codename": "delete_notification", "name": "Can delete notification", "content_type": 76}}, {"pk": 220, "model": "auth.permission", "fields": {"codename": "add_notificationtype", "name": "Can add type", "content_type": 73}}, {"pk": 221, "model": "auth.permission", "fields": {"codename": "change_notificationtype", "name": "Can change type", "content_type": 73}}, {"pk": 222, "model": "auth.permission", "fields": {"codename": "delete_notificationtype", "name": "Can delete type", "content_type": 73}}, {"pk": 223, "model": "auth.permission", "fields": {"codename": "add_settings", "name": "Can add settings", "content_type": 74}}, {"pk": 224, "model": "auth.permission", "fields": {"codename": "change_settings", "name": "Can change settings", "content_type": 74}}, {"pk": 225, "model": "auth.permission", "fields": {"codename": "delete_settings", "name": "Can delete settings", "content_type": 74}}, {"pk": 226, "model": "auth.permission", "fields": {"codename": "add_subscription", "name": "Can add subscription", "content_type": 75}}, {"pk": 227, "model": "auth.permission", "fields": {"codename": "change_subscription", "name": "Can change subscription", "content_type": 75}}, {"pk": 228, "model": "auth.permission", "fields": {"codename": "delete_subscription", "name": "Can delete subscription", "content_type": 75}}, {"pk": 58, "model": "auth.permission", "fields": {"codename": "add_association", "name": "Can add association", "content_type": 20}}, {"pk": 59, "model": "auth.permission", "fields": {"codename": "change_association", "name": "Can change association", "content_type": 20}}, {"pk": 60, "model": "auth.permission", "fields": {"codename": "delete_association", "name": "Can delete association", "content_type": 20}}, {"pk": 55, "model": "auth.permission", "fields": {"codename": "add_nonce", "name": "Can add nonce", "content_type": 19}}, {"pk": 56, "model": "auth.permission", "fields": {"codename": "change_nonce", "name": "Can change nonce", "content_type": 19}}, {"pk": 57, "model": "auth.permission", "fields": {"codename": "delete_nonce", "name": "Can delete nonce", "content_type": 19}}, {"pk": 61, "model": "auth.permission", "fields": {"codename": "add_useropenid", "name": "Can add user open id", "content_type": 21}}, {"pk": 62, "model": "auth.permission", "fields": {"codename": "change_useropenid", "name": "Can change user open id", "content_type": 21}}, {"pk": 63, "model": "auth.permission", "fields": {"codename": "delete_useropenid", "name": "Can delete user open id", "content_type": 21}}, {"pk": 28, "model": "auth.permission", "fields": {"codename": "add_crontabschedule", "name": "Can add crontab", "content_type": 10}}, {"pk": 29, "model": "auth.permission", "fields": {"codename": "change_crontabschedule", "name": "Can change crontab", "content_type": 10}}, {"pk": 30, "model": "auth.permission", "fields": {"codename": "delete_crontabschedule", "name": "Can delete crontab", "content_type": 10}}, {"pk": 25, "model": "auth.permission", "fields": {"codename": "add_intervalschedule", "name": "Can add interval", "content_type": 9}}, {"pk": 26, "model": "auth.permission", "fields": {"codename": "change_intervalschedule", "name": "Can change interval", "content_type": 9}}, {"pk": 27, "model": "auth.permission", "fields": {"codename": "delete_intervalschedule", "name": "Can delete interval", "content_type": 9}}, {"pk": 34, "model": "auth.permission", "fields": {"codename": "add_periodictask", "name": "Can add periodic task", "content_type": 12}}, {"pk": 35, "model": "auth.permission", "fields": {"codename": "change_periodictask", "name": "Can change periodic task", "content_type": 12}}, {"pk": 36, "model": "auth.permission", "fields": {"codename": "delete_periodictask", "name": "Can delete periodic task", "content_type": 12}}, {"pk": 31, "model": "auth.permission", "fields": {"codename": "add_periodictasks", "name": "Can add periodic tasks", "content_type": 11}}, {"pk": 32, "model": "auth.permission", "fields": {"codename": "change_periodictasks", "name": "Can change periodic tasks", "content_type": 11}}, {"pk": 33, "model": "auth.permission", "fields": {"codename": "delete_periodictasks", "name": "Can delete periodic tasks", "content_type": 11}}, {"pk": 19, "model": "auth.permission", "fields": {"codename": "add_taskmeta", "name": "Can add task state", "content_type": 7}}, {"pk": 20, "model": "auth.permission", "fields": {"codename": "change_taskmeta", "name": "Can change task state", "content_type": 7}}, {"pk": 21, "model": "auth.permission", "fields": {"codename": "delete_taskmeta", "name": "Can delete task state", "content_type": 7}}, {"pk": 22, "model": "auth.permission", "fields": {"codename": "add_tasksetmeta", "name": "Can add saved group result", "content_type": 8}}, {"pk": 23, "model": "auth.permission", "fields": {"codename": "change_tasksetmeta", "name": "Can change saved group result", "content_type": 8}}, {"pk": 24, "model": "auth.permission", "fields": {"codename": "delete_tasksetmeta", "name": "Can delete saved group result", "content_type": 8}}, {"pk": 40, "model": "auth.permission", "fields": {"codename": "add_taskstate", "name": "Can add task", "content_type": 14}}, {"pk": 41, "model": "auth.permission", "fields": {"codename": "change_taskstate", "name": "Can change task", "content_type": 14}}, {"pk": 42, "model": "auth.permission", "fields": {"codename": "delete_taskstate", "name": "Can delete task", "content_type": 14}}, {"pk": 37, "model": "auth.permission", "fields": {"codename": "add_workerstate", "name": "Can add worker", "content_type": 13}}, {"pk": 38, "model": "auth.permission", "fields": {"codename": "change_workerstate", "name": "Can change worker", "content_type": 13}}, {"pk": 39, "model": "auth.permission", "fields": {"codename": "delete_workerstate", "name": "Can delete worker", "content_type": 13}}, {"pk": 382, "model": "auth.permission", "fields": {"codename": "add_coursevideo", "name": "Can add course video", "content_type": 127}}, {"pk": 383, "model": "auth.permission", "fields": {"codename": "change_coursevideo", "name": "Can change course video", "content_type": 127}}, {"pk": 384, "model": "auth.permission", "fields": {"codename": "delete_coursevideo", "name": "Can delete course video", "content_type": 127}}, {"pk": 385, "model": "auth.permission", "fields": {"codename": "add_encodedvideo", "name": "Can add encoded video", "content_type": 128}}, {"pk": 386, "model": "auth.permission", "fields": {"codename": "change_encodedvideo", "name": "Can change encoded video", "content_type": 128}}, {"pk": 387, "model": "auth.permission", "fields": {"codename": "delete_encodedvideo", "name": "Can delete encoded video", "content_type": 128}}, {"pk": 376, "model": "auth.permission", "fields": {"codename": "add_profile", "name": "Can add profile", "content_type": 125}}, {"pk": 377, "model": "auth.permission", "fields": {"codename": "change_profile", "name": "Can change profile", "content_type": 125}}, {"pk": 378, "model": "auth.permission", "fields": {"codename": "delete_profile", "name": "Can delete profile", "content_type": 125}}, {"pk": 388, "model": "auth.permission", "fields": {"codename": "add_subtitle", "name": "Can add subtitle", "content_type": 129}}, {"pk": 389, "model": "auth.permission", "fields": {"codename": "change_subtitle", "name": "Can change subtitle", "content_type": 129}}, {"pk": 390, "model": "auth.permission", "fields": {"codename": "delete_subtitle", "name": "Can delete subtitle", "content_type": 129}}, {"pk": 379, "model": "auth.permission", "fields": {"codename": "add_video", "name": "Can add video", "content_type": 126}}, {"pk": 380, "model": "auth.permission", "fields": {"codename": "change_video", "name": "Can change video", "content_type": 126}}, {"pk": 381, "model": "auth.permission", "fields": {"codename": "delete_video", "name": "Can delete video", "content_type": 126}}, {"pk": 295, "model": "auth.permission", "fields": {"codename": "add_embargoedcourse", "name": "Can add embargoed course", "content_type": 98}}, {"pk": 296, "model": "auth.permission", "fields": {"codename": "change_embargoedcourse", "name": "Can change embargoed course", "content_type": 98}}, {"pk": 297, "model": "auth.permission", "fields": {"codename": "delete_embargoedcourse", "name": "Can delete embargoed course", "content_type": 98}}, {"pk": 298, "model": "auth.permission", "fields": {"codename": "add_embargoedstate", "name": "Can add embargoed state", "content_type": 99}}, {"pk": 299, "model": "auth.permission", "fields": {"codename": "change_embargoedstate", "name": "Can change embargoed state", "content_type": 99}}, {"pk": 300, "model": "auth.permission", "fields": {"codename": "delete_embargoedstate", "name": "Can delete embargoed state", "content_type": 99}}, {"pk": 301, "model": "auth.permission", "fields": {"codename": "add_ipfilter", "name": "Can add ip filter", "content_type": 100}}, {"pk": 302, "model": "auth.permission", "fields": {"codename": "change_ipfilter", "name": "Can change ip filter", "content_type": 100}}, {"pk": 303, "model": "auth.permission", "fields": {"codename": "delete_ipfilter", "name": "Can delete ip filter", "content_type": 100}}, {"pk": 169, "model": "auth.permission", "fields": {"codename": "add_externalauthmap", "name": "Can add external auth map", "content_type": 57}}, {"pk": 170, "model": "auth.permission", "fields": {"codename": "change_externalauthmap", "name": "Can change external auth map", "content_type": 57}}, {"pk": 171, "model": "auth.permission", "fields": {"codename": "delete_externalauthmap", "name": "Can delete external auth map", "content_type": 57}}, {"pk": 235, "model": "auth.permission", "fields": {"codename": "add_puzzlecomplete", "name": "Can add puzzle complete", "content_type": 78}}, {"pk": 236, "model": "auth.permission", "fields": {"codename": "change_puzzlecomplete", "name": "Can change puzzle complete", "content_type": 78}}, {"pk": 237, "model": "auth.permission", "fields": {"codename": "delete_puzzlecomplete", "name": "Can delete puzzle complete", "content_type": 78}}, {"pk": 232, "model": "auth.permission", "fields": {"codename": "add_score", "name": "Can add score", "content_type": 77}}, {"pk": 233, "model": "auth.permission", "fields": {"codename": "change_score", "name": "Can change score", "content_type": 77}}, {"pk": 234, "model": "auth.permission", "fields": {"codename": "delete_score", "name": "Can delete score", "content_type": 77}}, {"pk": 148, "model": "auth.permission", "fields": {"codename": "add_instructortask", "name": "Can add instructor task", "content_type": 50}}, {"pk": 149, "model": "auth.permission", "fields": {"codename": "change_instructortask", "name": "Can change instructor task", "content_type": 50}}, {"pk": 150, "model": "auth.permission", "fields": {"codename": "delete_instructortask", "name": "Can delete instructor task", "content_type": 50}}, {"pk": 151, "model": "auth.permission", "fields": {"codename": "add_coursesoftware", "name": "Can add course software", "content_type": 51}}, {"pk": 152, "model": "auth.permission", "fields": {"codename": "change_coursesoftware", "name": "Can change course software", "content_type": 51}}, {"pk": 153, "model": "auth.permission", "fields": {"codename": "delete_coursesoftware", "name": "Can delete course software", "content_type": 51}}, {"pk": 154, "model": "auth.permission", "fields": {"codename": "add_userlicense", "name": "Can add user license", "content_type": 52}}, {"pk": 155, "model": "auth.permission", "fields": {"codename": "change_userlicense", "name": "Can change user license", "content_type": 52}}, {"pk": 156, "model": "auth.permission", "fields": {"codename": "delete_userlicense", "name": "Can delete user license", "content_type": 52}}, {"pk": 307, "model": "auth.permission", "fields": {"codename": "add_linkedin", "name": "Can add linked in", "content_type": 102}}, {"pk": 308, "model": "auth.permission", "fields": {"codename": "change_linkedin", "name": "Can change linked in", "content_type": 102}}, {"pk": 309, "model": "auth.permission", "fields": {"codename": "delete_linkedin", "name": "Can delete linked in", "content_type": 102}}, {"pk": 238, "model": "auth.permission", "fields": {"codename": "add_note", "name": "Can add note", "content_type": 79}}, {"pk": 239, "model": "auth.permission", "fields": {"codename": "change_note", "name": "Can change note", "content_type": 79}}, {"pk": 240, "model": "auth.permission", "fields": {"codename": "delete_note", "name": "Can delete note", "content_type": 79}}, {"pk": 178, "model": "auth.permission", "fields": {"codename": "add_accesstoken", "name": "Can add access token", "content_type": 60}}, {"pk": 179, "model": "auth.permission", "fields": {"codename": "change_accesstoken", "name": "Can change access token", "content_type": 60}}, {"pk": 180, "model": "auth.permission", "fields": {"codename": "delete_accesstoken", "name": "Can delete access token", "content_type": 60}}, {"pk": 172, "model": "auth.permission", "fields": {"codename": "add_client", "name": "Can add client", "content_type": 58}}, {"pk": 173, "model": "auth.permission", "fields": {"codename": "change_client", "name": "Can change client", "content_type": 58}}, {"pk": 174, "model": "auth.permission", "fields": {"codename": "delete_client", "name": "Can delete client", "content_type": 58}}, {"pk": 175, "model": "auth.permission", "fields": {"codename": "add_grant", "name": "Can add grant", "content_type": 59}}, {"pk": 176, "model": "auth.permission", "fields": {"codename": "change_grant", "name": "Can change grant", "content_type": 59}}, {"pk": 177, "model": "auth.permission", "fields": {"codename": "delete_grant", "name": "Can delete grant", "content_type": 59}}, {"pk": 181, "model": "auth.permission", "fields": {"codename": "add_refreshtoken", "name": "Can add refresh token", "content_type": 61}}, {"pk": 182, "model": "auth.permission", "fields": {"codename": "change_refreshtoken", "name": "Can change refresh token", "content_type": 61}}, {"pk": 183, "model": "auth.permission", "fields": {"codename": "delete_refreshtoken", "name": "Can delete refresh token", "content_type": 61}}, {"pk": 184, "model": "auth.permission", "fields": {"codename": "add_trustedclient", "name": "Can add trusted client", "content_type": 62}}, {"pk": 185, "model": "auth.permission", "fields": {"codename": "change_trustedclient", "name": "Can change trusted client", "content_type": 62}}, {"pk": 186, "model": "auth.permission", "fields": {"codename": "delete_trustedclient", "name": "Can delete trusted client", "content_type": 62}}, {"pk": 49, "model": "auth.permission", "fields": {"codename": "add_psychometricdata", "name": "Can add psychometric data", "content_type": 17}}, {"pk": 50, "model": "auth.permission", "fields": {"codename": "change_psychometricdata", "name": "Can change psychometric data", "content_type": 17}}, {"pk": 51, "model": "auth.permission", "fields": {"codename": "delete_psychometricdata", "name": "Can delete psychometric data", "content_type": 17}}, {"pk": 292, "model": "auth.permission", "fields": {"codename": "add_midcoursereverificationwindow", "name": "Can add midcourse reverification window", "content_type": 97}}, {"pk": 293, "model": "auth.permission", "fields": {"codename": "change_midcoursereverificationwindow", "name": "Can change midcourse reverification window", "content_type": 97}}, {"pk": 294, "model": "auth.permission", "fields": {"codename": "delete_midcoursereverificationwindow", "name": "Can delete midcourse reverification window", "content_type": 97}}, {"pk": 13, "model": "auth.permission", "fields": {"codename": "add_session", "name": "Can add session", "content_type": 5}}, {"pk": 14, "model": "auth.permission", "fields": {"codename": "change_session", "name": "Can change session", "content_type": 5}}, {"pk": 15, "model": "auth.permission", "fields": {"codename": "delete_session", "name": "Can delete session", "content_type": 5}}, {"pk": 277, "model": "auth.permission", "fields": {"codename": "add_certificateitem", "name": "Can add certificate item", "content_type": 92}}, {"pk": 278, "model": "auth.permission", "fields": {"codename": "change_certificateitem", "name": "Can change certificate item", "content_type": 92}}, {"pk": 279, "model": "auth.permission", "fields": {"codename": "delete_certificateitem", "name": "Can delete certificate item", "content_type": 92}}, {"pk": 265, "model": "auth.permission", "fields": {"codename": "add_coupon", "name": "Can add coupon", "content_type": 88}}, {"pk": 266, "model": "auth.permission", "fields": {"codename": "change_coupon", "name": "Can change coupon", "content_type": 88}}, {"pk": 267, "model": "auth.permission", "fields": {"codename": "delete_coupon", "name": "Can delete coupon", "content_type": 88}}, {"pk": 268, "model": "auth.permission", "fields": {"codename": "add_couponredemption", "name": "Can add coupon redemption", "content_type": 89}}, {"pk": 269, "model": "auth.permission", "fields": {"codename": "change_couponredemption", "name": "Can change coupon redemption", "content_type": 89}}, {"pk": 270, "model": "auth.permission", "fields": {"codename": "delete_couponredemption", "name": "Can delete coupon redemption", "content_type": 89}}, {"pk": 259, "model": "auth.permission", "fields": {"codename": "add_courseregistrationcode", "name": "Can add course registration code", "content_type": 86}}, {"pk": 260, "model": "auth.permission", "fields": {"codename": "change_courseregistrationcode", "name": "Can change course registration code", "content_type": 86}}, {"pk": 261, "model": "auth.permission", "fields": {"codename": "delete_courseregistrationcode", "name": "Can delete course registration code", "content_type": 86}}, {"pk": 256, "model": "auth.permission", "fields": {"codename": "add_invoice", "name": "Can add invoice", "content_type": 85}}, {"pk": 257, "model": "auth.permission", "fields": {"codename": "change_invoice", "name": "Can change invoice", "content_type": 85}}, {"pk": 258, "model": "auth.permission", "fields": {"codename": "delete_invoice", "name": "Can delete invoice", "content_type": 85}}, {"pk": 250, "model": "auth.permission", "fields": {"codename": "add_order", "name": "Can add order", "content_type": 83}}, {"pk": 251, "model": "auth.permission", "fields": {"codename": "change_order", "name": "Can change order", "content_type": 83}}, {"pk": 252, "model": "auth.permission", "fields": {"codename": "delete_order", "name": "Can delete order", "content_type": 83}}, {"pk": 253, "model": "auth.permission", "fields": {"codename": "add_orderitem", "name": "Can add order item", "content_type": 84}}, {"pk": 254, "model": "auth.permission", "fields": {"codename": "change_orderitem", "name": "Can change order item", "content_type": 84}}, {"pk": 255, "model": "auth.permission", "fields": {"codename": "delete_orderitem", "name": "Can delete order item", "content_type": 84}}, {"pk": 271, "model": "auth.permission", "fields": {"codename": "add_paidcourseregistration", "name": "Can add paid course registration", "content_type": 90}}, {"pk": 272, "model": "auth.permission", "fields": {"codename": "change_paidcourseregistration", "name": "Can change paid course registration", "content_type": 90}}, {"pk": 273, "model": "auth.permission", "fields": {"codename": "delete_paidcourseregistration", "name": "Can delete paid course registration", "content_type": 90}}, {"pk": 274, "model": "auth.permission", "fields": {"codename": "add_paidcourseregistrationannotation", "name": "Can add paid course registration annotation", "content_type": 91}}, {"pk": 275, "model": "auth.permission", "fields": {"codename": "change_paidcourseregistrationannotation", "name": "Can change paid course registration annotation", "content_type": 91}}, {"pk": 276, "model": "auth.permission", "fields": {"codename": "delete_paidcourseregistrationannotation", "name": "Can delete paid course registration annotation", "content_type": 91}}, {"pk": 262, "model": "auth.permission", "fields": {"codename": "add_registrationcoderedemption", "name": "Can add registration code redemption", "content_type": 87}}, {"pk": 263, "model": "auth.permission", "fields": {"codename": "change_registrationcoderedemption", "name": "Can change registration code redemption", "content_type": 87}}, {"pk": 264, "model": "auth.permission", "fields": {"codename": "delete_registrationcoderedemption", "name": "Can delete registration code redemption", "content_type": 87}}, {"pk": 16, "model": "auth.permission", "fields": {"codename": "add_site", "name": "Can add site", "content_type": 6}}, {"pk": 17, "model": "auth.permission", "fields": {"codename": "change_site", "name": "Can change site", "content_type": 6}}, {"pk": 18, "model": "auth.permission", "fields": {"codename": "delete_site", "name": "Can delete site", "content_type": 6}}, {"pk": 43, "model": "auth.permission", "fields": {"codename": "add_migrationhistory", "name": "Can add migration history", "content_type": 15}}, {"pk": 44, "model": "auth.permission", "fields": {"codename": "change_migrationhistory", "name": "Can change migration history", "content_type": 15}}, {"pk": 45, "model": "auth.permission", "fields": {"codename": "delete_migrationhistory", "name": "Can delete migration history", "content_type": 15}}, {"pk": 241, "model": "auth.permission", "fields": {"codename": "add_splashconfig", "name": "Can add splash config", "content_type": 80}}, {"pk": 242, "model": "auth.permission", "fields": {"codename": "change_splashconfig", "name": "Can change splash config", "content_type": 80}}, {"pk": 243, "model": "auth.permission", "fields": {"codename": "delete_splashconfig", "name": "Can delete splash config", "content_type": 80}}, {"pk": 100, "model": "auth.permission", "fields": {"codename": "add_anonymoususerid", "name": "Can add anonymous user id", "content_type": 34}}, {"pk": 101, "model": "auth.permission", "fields": {"codename": "change_anonymoususerid", "name": "Can change anonymous user id", "content_type": 34}}, {"pk": 102, "model": "auth.permission", "fields": {"codename": "delete_anonymoususerid", "name": "Can delete anonymous user id", "content_type": 34}}, {"pk": 136, "model": "auth.permission", "fields": {"codename": "add_courseaccessrole", "name": "Can add course access role", "content_type": 46}}, {"pk": 137, "model": "auth.permission", "fields": {"codename": "change_courseaccessrole", "name": "Can change course access role", "content_type": 46}}, {"pk": 138, "model": "auth.permission", "fields": {"codename": "delete_courseaccessrole", "name": "Can delete course access role", "content_type": 46}}, {"pk": 130, "model": "auth.permission", "fields": {"codename": "add_courseenrollment", "name": "Can add course enrollment", "content_type": 44}}, {"pk": 131, "model": "auth.permission", "fields": {"codename": "change_courseenrollment", "name": "Can change course enrollment", "content_type": 44}}, {"pk": 132, "model": "auth.permission", "fields": {"codename": "delete_courseenrollment", "name": "Can delete course enrollment", "content_type": 44}}, {"pk": 133, "model": "auth.permission", "fields": {"codename": "add_courseenrollmentallowed", "name": "Can add course enrollment allowed", "content_type": 45}}, {"pk": 134, "model": "auth.permission", "fields": {"codename": "change_courseenrollmentallowed", "name": "Can change course enrollment allowed", "content_type": 45}}, {"pk": 135, "model": "auth.permission", "fields": {"codename": "delete_courseenrollmentallowed", "name": "Can delete course enrollment allowed", "content_type": 45}}, {"pk": 127, "model": "auth.permission", "fields": {"codename": "add_loginfailures", "name": "Can add login failures", "content_type": 43}}, {"pk": 128, "model": "auth.permission", "fields": {"codename": "change_loginfailures", "name": "Can change login failures", "content_type": 43}}, {"pk": 129, "model": "auth.permission", "fields": {"codename": "delete_loginfailures", "name": "Can delete login failures", "content_type": 43}}, {"pk": 124, "model": "auth.permission", "fields": {"codename": "add_passwordhistory", "name": "Can add password history", "content_type": 42}}, {"pk": 125, "model": "auth.permission", "fields": {"codename": "change_passwordhistory", "name": "Can change password history", "content_type": 42}}, {"pk": 126, "model": "auth.permission", "fields": {"codename": "delete_passwordhistory", "name": "Can delete password history", "content_type": 42}}, {"pk": 121, "model": "auth.permission", "fields": {"codename": "add_pendingemailchange", "name": "Can add pending email change", "content_type": 41}}, {"pk": 122, "model": "auth.permission", "fields": {"codename": "change_pendingemailchange", "name": "Can change pending email change", "content_type": 41}}, {"pk": 123, "model": "auth.permission", "fields": {"codename": "delete_pendingemailchange", "name": "Can delete pending email change", "content_type": 41}}, {"pk": 118, "model": "auth.permission", "fields": {"codename": "add_pendingnamechange", "name": "Can add pending name change", "content_type": 40}}, {"pk": 119, "model": "auth.permission", "fields": {"codename": "change_pendingnamechange", "name": "Can change pending name change", "content_type": 40}}, {"pk": 120, "model": "auth.permission", "fields": {"codename": "delete_pendingnamechange", "name": "Can delete pending name change", "content_type": 40}}, {"pk": 115, "model": "auth.permission", "fields": {"codename": "add_registration", "name": "Can add registration", "content_type": 39}}, {"pk": 116, "model": "auth.permission", "fields": {"codename": "change_registration", "name": "Can change registration", "content_type": 39}}, {"pk": 117, "model": "auth.permission", "fields": {"codename": "delete_registration", "name": "Can delete registration", "content_type": 39}}, {"pk": 106, "model": "auth.permission", "fields": {"codename": "add_userprofile", "name": "Can add user profile", "content_type": 36}}, {"pk": 107, "model": "auth.permission", "fields": {"codename": "change_userprofile", "name": "Can change user profile", "content_type": 36}}, {"pk": 108, "model": "auth.permission", "fields": {"codename": "delete_userprofile", "name": "Can delete user profile", "content_type": 36}}, {"pk": 109, "model": "auth.permission", "fields": {"codename": "add_usersignupsource", "name": "Can add user signup source", "content_type": 37}}, {"pk": 110, "model": "auth.permission", "fields": {"codename": "change_usersignupsource", "name": "Can change user signup source", "content_type": 37}}, {"pk": 111, "model": "auth.permission", "fields": {"codename": "delete_usersignupsource", "name": "Can delete user signup source", "content_type": 37}}, {"pk": 103, "model": "auth.permission", "fields": {"codename": "add_userstanding", "name": "Can add user standing", "content_type": 35}}, {"pk": 104, "model": "auth.permission", "fields": {"codename": "change_userstanding", "name": "Can change user standing", "content_type": 35}}, {"pk": 105, "model": "auth.permission", "fields": {"codename": "delete_userstanding", "name": "Can delete user standing", "content_type": 35}}, {"pk": 112, "model": "auth.permission", "fields": {"codename": "add_usertestgroup", "name": "Can add user test group", "content_type": 38}}, {"pk": 113, "model": "auth.permission", "fields": {"codename": "change_usertestgroup", "name": "Can change user test group", "content_type": 38}}, {"pk": 114, "model": "auth.permission", "fields": {"codename": "delete_usertestgroup", "name": "Can delete user test group", "content_type": 38}}, {"pk": 316, "model": "auth.permission", "fields": {"codename": "add_score", "name": "Can add score", "content_type": 105}}, {"pk": 317, "model": "auth.permission", "fields": {"codename": "change_score", "name": "Can change score", "content_type": 105}}, {"pk": 318, "model": "auth.permission", "fields": {"codename": "delete_score", "name": "Can delete score", "content_type": 105}}, {"pk": 319, "model": "auth.permission", "fields": {"codename": "add_scoresummary", "name": "Can add score summary", "content_type": 106}}, {"pk": 320, "model": "auth.permission", "fields": {"codename": "change_scoresummary", "name": "Can change score summary", "content_type": 106}}, {"pk": 321, "model": "auth.permission", "fields": {"codename": "delete_scoresummary", "name": "Can delete score summary", "content_type": 106}}, {"pk": 310, "model": "auth.permission", "fields": {"codename": "add_studentitem", "name": "Can add student item", "content_type": 103}}, {"pk": 311, "model": "auth.permission", "fields": {"codename": "change_studentitem", "name": "Can change student item", "content_type": 103}}, {"pk": 312, "model": "auth.permission", "fields": {"codename": "delete_studentitem", "name": "Can delete student item", "content_type": 103}}, {"pk": 313, "model": "auth.permission", "fields": {"codename": "add_submission", "name": "Can add submission", "content_type": 104}}, {"pk": 314, "model": "auth.permission", "fields": {"codename": "change_submission", "name": "Can change submission", "content_type": 104}}, {"pk": 315, "model": "auth.permission", "fields": {"codename": "delete_submission", "name": "Can delete submission", "content_type": 104}}, {"pk": 139, "model": "auth.permission", "fields": {"codename": "add_trackinglog", "name": "Can add tracking log", "content_type": 47}}, {"pk": 140, "model": "auth.permission", "fields": {"codename": "change_trackinglog", "name": "Can change tracking log", "content_type": 47}}, {"pk": 141, "model": "auth.permission", "fields": {"codename": "delete_trackinglog", "name": "Can delete tracking log", "content_type": 47}}, {"pk": 247, "model": "auth.permission", "fields": {"codename": "add_usercoursetag", "name": "Can add user course tag", "content_type": 82}}, {"pk": 248, "model": "auth.permission", "fields": {"codename": "change_usercoursetag", "name": "Can change user course tag", "content_type": 82}}, {"pk": 249, "model": "auth.permission", "fields": {"codename": "delete_usercoursetag", "name": "Can delete user course tag", "content_type": 82}}, {"pk": 244, "model": "auth.permission", "fields": {"codename": "add_userpreference", "name": "Can add user preference", "content_type": 81}}, {"pk": 245, "model": "auth.permission", "fields": {"codename": "change_userpreference", "name": "Can change user preference", "content_type": 81}}, {"pk": 246, "model": "auth.permission", "fields": {"codename": "delete_userpreference", "name": "Can delete user preference", "content_type": 81}}, {"pk": 286, "model": "auth.permission", "fields": {"codename": "add_softwaresecurephotoverification", "name": "Can add software secure photo verification", "content_type": 95}}, {"pk": 287, "model": "auth.permission", "fields": {"codename": "change_softwaresecurephotoverification", "name": "Can change software secure photo verification", "content_type": 95}}, {"pk": 288, "model": "auth.permission", "fields": {"codename": "delete_softwaresecurephotoverification", "name": "Can delete software secure photo verification", "content_type": 95}}, {"pk": 187, "model": "auth.permission", "fields": {"codename": "add_article", "name": "Can add article", "content_type": 63}}, {"pk": 191, "model": "auth.permission", "fields": {"codename": "assign", "name": "Can change ownership of any article", "content_type": 63}}, {"pk": 188, "model": "auth.permission", "fields": {"codename": "change_article", "name": "Can change article", "content_type": 63}}, {"pk": 189, "model": "auth.permission", "fields": {"codename": "delete_article", "name": "Can delete article", "content_type": 63}}, {"pk": 192, "model": "auth.permission", "fields": {"codename": "grant", "name": "Can assign permissions to other users", "content_type": 63}}, {"pk": 190, "model": "auth.permission", "fields": {"codename": "moderate", "name": "Can edit all articles and lock/unlock/restore", "content_type": 63}}, {"pk": 193, "model": "auth.permission", "fields": {"codename": "add_articleforobject", "name": "Can add Article for object", "content_type": 64}}, {"pk": 194, "model": "auth.permission", "fields": {"codename": "change_articleforobject", "name": "Can change Article for object", "content_type": 64}}, {"pk": 195, "model": "auth.permission", "fields": {"codename": "delete_articleforobject", "name": "Can delete Article for object", "content_type": 64}}, {"pk": 202, "model": "auth.permission", "fields": {"codename": "add_articleplugin", "name": "Can add article plugin", "content_type": 67}}, {"pk": 203, "model": "auth.permission", "fields": {"codename": "change_articleplugin", "name": "Can change article plugin", "content_type": 67}}, {"pk": 204, "model": "auth.permission", "fields": {"codename": "delete_articleplugin", "name": "Can delete article plugin", "content_type": 67}}, {"pk": 196, "model": "auth.permission", "fields": {"codename": "add_articlerevision", "name": "Can add article revision", "content_type": 65}}, {"pk": 197, "model": "auth.permission", "fields": {"codename": "change_articlerevision", "name": "Can change article revision", "content_type": 65}}, {"pk": 198, "model": "auth.permission", "fields": {"codename": "delete_articlerevision", "name": "Can delete article revision", "content_type": 65}}, {"pk": 217, "model": "auth.permission", "fields": {"codename": "add_articlesubscription", "name": "Can add article subscription", "content_type": 72}}, {"pk": 218, "model": "auth.permission", "fields": {"codename": "change_articlesubscription", "name": "Can change article subscription", "content_type": 72}}, {"pk": 219, "model": "auth.permission", "fields": {"codename": "delete_articlesubscription", "name": "Can delete article subscription", "content_type": 72}}, {"pk": 205, "model": "auth.permission", "fields": {"codename": "add_reusableplugin", "name": "Can add reusable plugin", "content_type": 68}}, {"pk": 206, "model": "auth.permission", "fields": {"codename": "change_reusableplugin", "name": "Can change reusable plugin", "content_type": 68}}, {"pk": 207, "model": "auth.permission", "fields": {"codename": "delete_reusableplugin", "name": "Can delete reusable plugin", "content_type": 68}}, {"pk": 211, "model": "auth.permission", "fields": {"codename": "add_revisionplugin", "name": "Can add revision plugin", "content_type": 70}}, {"pk": 212, "model": "auth.permission", "fields": {"codename": "change_revisionplugin", "name": "Can change revision plugin", "content_type": 70}}, {"pk": 213, "model": "auth.permission", "fields": {"codename": "delete_revisionplugin", "name": "Can delete revision plugin", "content_type": 70}}, {"pk": 214, "model": "auth.permission", "fields": {"codename": "add_revisionpluginrevision", "name": "Can add revision plugin revision", "content_type": 71}}, {"pk": 215, "model": "auth.permission", "fields": {"codename": "change_revisionpluginrevision", "name": "Can change revision plugin revision", "content_type": 71}}, {"pk": 216, "model": "auth.permission", "fields": {"codename": "delete_revisionpluginrevision", "name": "Can delete revision plugin revision", "content_type": 71}}, {"pk": 208, "model": "auth.permission", "fields": {"codename": "add_simpleplugin", "name": "Can add simple plugin", "content_type": 69}}, {"pk": 209, "model": "auth.permission", "fields": {"codename": "change_simpleplugin", "name": "Can change simple plugin", "content_type": 69}}, {"pk": 210, "model": "auth.permission", "fields": {"codename": "delete_simpleplugin", "name": "Can delete simple plugin", "content_type": 69}}, {"pk": 199, "model": "auth.permission", "fields": {"codename": "add_urlpath", "name": "Can add URL path", "content_type": 66}}, {"pk": 200, "model": "auth.permission", "fields": {"codename": "change_urlpath", "name": "Can change URL path", "content_type": 66}}, {"pk": 201, "model": "auth.permission", "fields": {"codename": "delete_urlpath", "name": "Can delete URL path", "content_type": 66}}, {"pk": 370, "model": "auth.permission", "fields": {"codename": "add_assessmentworkflow", "name": "Can add assessment workflow", "content_type": 123}}, {"pk": 371, "model": "auth.permission", "fields": {"codename": "change_assessmentworkflow", "name": "Can change assessment workflow", "content_type": 123}}, {"pk": 372, "model": "auth.permission", "fields": {"codename": "delete_assessmentworkflow", "name": "Can delete assessment workflow", "content_type": 123}}, {"pk": 373, "model": "auth.permission", "fields": {"codename": "add_assessmentworkflowstep", "name": "Can add assessment workflow step", "content_type": 124}}, {"pk": 374, "model": "auth.permission", "fields": {"codename": "change_assessmentworkflowstep", "name": "Can change assessment workflow step", "content_type": 124}}, {"pk": 375, "model": "auth.permission", "fields": {"codename": "delete_assessmentworkflowstep", "name": "Can delete assessment workflow step", "content_type": 124}}, {"pk": 1, "model": "dark_lang.darklangconfig", "fields": {"change_date": "2014-10-06T18:53:49Z", "changed_by": null, "enabled": true, "released_languages": ""}}]
\ No newline at end of file
+[{"pk": 62, "model": "contenttypes.contenttype", "fields": {"model": "accesstoken", "name": "access token", "app_label": "oauth2"}}, {"pk": 128, "model": "contenttypes.contenttype", "fields": {"model": "aiclassifier", "name": "ai classifier", "app_label": "assessment"}}, {"pk": 127, "model": "contenttypes.contenttype", "fields": {"model": "aiclassifierset", "name": "ai classifier set", "app_label": "assessment"}}, {"pk": 130, "model": "contenttypes.contenttype", "fields": {"model": "aigradingworkflow", "name": "ai grading workflow", "app_label": "assessment"}}, {"pk": 129, "model": "contenttypes.contenttype", "fields": {"model": "aitrainingworkflow", "name": "ai training workflow", "app_label": "assessment"}}, {"pk": 33, "model": "contenttypes.contenttype", "fields": {"model": "anonymoususerid", "name": "anonymous user id", "app_label": "student"}}, {"pk": 65, "model": "contenttypes.contenttype", "fields": {"model": "article", "name": "article", "app_label": "wiki"}}, {"pk": 66, "model": "contenttypes.contenttype", "fields": {"model": "articleforobject", "name": "Article for object", "app_label": "wiki"}}, {"pk": 69, "model": "contenttypes.contenttype", "fields": {"model": "articleplugin", "name": "article plugin", "app_label": "wiki"}}, {"pk": 67, "model": "contenttypes.contenttype", "fields": {"model": "articlerevision", "name": "article revision", "app_label": "wiki"}}, {"pk": 74, "model": "contenttypes.contenttype", "fields": {"model": "articlesubscription", "name": "article subscription", "app_label": "wiki"}}, {"pk": 118, "model": "contenttypes.contenttype", "fields": {"model": "assessment", "name": "assessment", "app_label": "assessment"}}, {"pk": 121, "model": "contenttypes.contenttype", "fields": {"model": "assessmentfeedback", "name": "assessment feedback", "app_label": "assessment"}}, {"pk": 120, "model": "contenttypes.contenttype", "fields": {"model": "assessmentfeedbackoption", "name": "assessment feedback option", "app_label": "assessment"}}, {"pk": 119, "model": "contenttypes.contenttype", "fields": {"model": "assessmentpart", "name": "assessment part", "app_label": "assessment"}}, {"pk": 131, "model": "contenttypes.contenttype", "fields": {"model": "assessmentworkflow", "name": "assessment workflow", "app_label": "workflow"}}, {"pk": 132, "model": "contenttypes.contenttype", "fields": {"model": "assessmentworkflowstep", "name": "assessment workflow step", "app_label": "workflow"}}, {"pk": 19, "model": "contenttypes.contenttype", "fields": {"model": "association", "name": "association", "app_label": "django_openid_auth"}}, {"pk": 24, "model": "contenttypes.contenttype", "fields": {"model": "association", "name": "association", "app_label": "default"}}, {"pk": 96, "model": "contenttypes.contenttype", "fields": {"model": "certificateitem", "name": "certificate item", "app_label": "shoppingcart"}}, {"pk": 48, "model": "contenttypes.contenttype", "fields": {"model": "certificatewhitelist", "name": "certificate whitelist", "app_label": "certificates"}}, {"pk": 60, "model": "contenttypes.contenttype", "fields": {"model": "client", "name": "client", "app_label": "oauth2"}}, {"pk": 25, "model": "contenttypes.contenttype", "fields": {"model": "code", "name": "code", "app_label": "default"}}, {"pk": 4, "model": "contenttypes.contenttype", "fields": {"model": "contenttype", "name": "content type", "app_label": "contenttypes"}}, {"pk": 90, "model": "contenttypes.contenttype", "fields": {"model": "coupon", "name": "coupon", "app_label": "shoppingcart"}}, {"pk": 91, "model": "contenttypes.contenttype", "fields": {"model": "couponredemption", "name": "coupon redemption", "app_label": "shoppingcart"}}, {"pk": 45, "model": "contenttypes.contenttype", "fields": {"model": "courseaccessrole", "name": "course access role", "app_label": "student"}}, {"pk": 58, "model": "contenttypes.contenttype", "fields": {"model": "courseauthorization", "name": "course authorization", "app_label": "bulk_email"}}, {"pk": 55, "model": "contenttypes.contenttype", "fields": {"model": "courseemail", "name": "course email", "app_label": "bulk_email"}}, {"pk": 57, "model": "contenttypes.contenttype", "fields": {"model": "courseemailtemplate", "name": "course email template", "app_label": "bulk_email"}}, {"pk": 43, "model": "contenttypes.contenttype", "fields": {"model": "courseenrollment", "name": "course enrollment", "app_label": "student"}}, {"pk": 44, "model": "contenttypes.contenttype", "fields": {"model": "courseenrollmentallowed", "name": "course enrollment allowed", "app_label": "student"}}, {"pk": 99, "model": "contenttypes.contenttype", "fields": {"model": "coursemode", "name": "course mode", "app_label": "course_modes"}}, {"pk": 100, "model": "contenttypes.contenttype", "fields": {"model": "coursemodesarchive", "name": "course modes archive", "app_label": "course_modes"}}, {"pk": 93, "model": "contenttypes.contenttype", "fields": {"model": "courseregcodeitem", "name": "course reg code item", "app_label": "shoppingcart"}}, {"pk": 94, "model": "contenttypes.contenttype", "fields": {"model": "courseregcodeitemannotation", "name": "course reg code item annotation", "app_label": "shoppingcart"}}, {"pk": 88, "model": "contenttypes.contenttype", "fields": {"model": "courseregistrationcode", "name": "course registration code", "app_label": "shoppingcart"}}, {"pk": 107, "model": "contenttypes.contenttype", "fields": {"model": "coursererunstate", "name": "course rerun state", "app_label": "course_action_state"}}, {"pk": 51, "model": "contenttypes.contenttype", "fields": {"model": "coursesoftware", "name": "course software", "app_label": "licenses"}}, {"pk": 53, "model": "contenttypes.contenttype", "fields": {"model": "courseusergroup", "name": "course user group", "app_label": "course_groups"}}, {"pk": 54, "model": "contenttypes.contenttype", "fields": {"model": "courseusergrouppartitiongroup", "name": "course user group partition group", "app_label": "course_groups"}}, {"pk": 135, "model": "contenttypes.contenttype", "fields": {"model": "coursevideo", "name": "course video", "app_label": "edxval"}}, {"pk": 116, "model": "contenttypes.contenttype", "fields": {"model": "criterion", "name": "criterion", "app_label": "assessment"}}, {"pk": 117, "model": "contenttypes.contenttype", "fields": {"model": "criterionoption", "name": "criterion option", "app_label": "assessment"}}, {"pk": 10, "model": "contenttypes.contenttype", "fields": {"model": "crontabschedule", "name": "crontab", "app_label": "djcelery"}}, {"pk": 102, "model": "contenttypes.contenttype", "fields": {"model": "darklangconfig", "name": "dark lang config", "app_label": "dark_lang"}}, {"pk": 46, "model": "contenttypes.contenttype", "fields": {"model": "dashboardconfiguration", "name": "dashboard configuration", "app_label": "student"}}, {"pk": 98, "model": "contenttypes.contenttype", "fields": {"model": "donation", "name": "donation", "app_label": "shoppingcart"}}, {"pk": 97, "model": "contenttypes.contenttype", "fields": {"model": "donationconfiguration", "name": "donation configuration", "app_label": "shoppingcart"}}, {"pk": 104, "model": "contenttypes.contenttype", "fields": {"model": "embargoedcourse", "name": "embargoed course", "app_label": "embargo"}}, {"pk": 105, "model": "contenttypes.contenttype", "fields": {"model": "embargoedstate", "name": "embargoed state", "app_label": "embargo"}}, {"pk": 136, "model": "contenttypes.contenttype", "fields": {"model": "encodedvideo", "name": "encoded video", "app_label": "edxval"}}, {"pk": 59, "model": "contenttypes.contenttype", "fields": {"model": "externalauthmap", "name": "external auth map", "app_label": "external_auth"}}, {"pk": 49, "model": "contenttypes.contenttype", "fields": {"model": "generatedcertificate", "name": "generated certificate", "app_label": "certificates"}}, {"pk": 61, "model": "contenttypes.contenttype", "fields": {"model": "grant", "name": "grant", "app_label": "oauth2"}}, {"pk": 2, "model": "contenttypes.contenttype", "fields": {"model": "group", "name": "group", "app_label": "auth"}}, {"pk": 50, "model": "contenttypes.contenttype", "fields": {"model": "instructortask", "name": "instructor task", "app_label": "instructor_task"}}, {"pk": 9, "model": "contenttypes.contenttype", "fields": {"model": "intervalschedule", "name": "interval", "app_label": "djcelery"}}, {"pk": 87, "model": "contenttypes.contenttype", "fields": {"model": "invoice", "name": "invoice", "app_label": "shoppingcart"}}, {"pk": 106, "model": "contenttypes.contenttype", "fields": {"model": "ipfilter", "name": "ip filter", "app_label": "embargo"}}, {"pk": 110, "model": "contenttypes.contenttype", "fields": {"model": "linkedin", "name": "linked in", "app_label": "linkedin"}}, {"pk": 21, "model": "contenttypes.contenttype", "fields": {"model": "logentry", "name": "log entry", "app_label": "admin"}}, {"pk": 42, "model": "contenttypes.contenttype", "fields": {"model": "loginfailures", "name": "login failures", "app_label": "student"}}, {"pk": 103, "model": "contenttypes.contenttype", "fields": {"model": "midcoursereverificationwindow", "name": "midcourse reverification window", "app_label": "reverification"}}, {"pk": 15, "model": "contenttypes.contenttype", "fields": {"model": "migrationhistory", "name": "migration history", "app_label": "south"}}, {"pk": 18, "model": "contenttypes.contenttype", "fields": {"model": "nonce", "name": "nonce", "app_label": "django_openid_auth"}}, {"pk": 23, "model": "contenttypes.contenttype", "fields": {"model": "nonce", "name": "nonce", "app_label": "default"}}, {"pk": 81, "model": "contenttypes.contenttype", "fields": {"model": "note", "name": "note", "app_label": "notes"}}, {"pk": 78, "model": "contenttypes.contenttype", "fields": {"model": "notification", "name": "notification", "app_label": "django_notify"}}, {"pk": 31, "model": "contenttypes.contenttype", "fields": {"model": "offlinecomputedgrade", "name": "offline computed grade", "app_label": "courseware"}}, {"pk": 32, "model": "contenttypes.contenttype", "fields": {"model": "offlinecomputedgradelog", "name": "offline computed grade log", "app_label": "courseware"}}, {"pk": 56, "model": "contenttypes.contenttype", "fields": {"model": "optout", "name": "optout", "app_label": "bulk_email"}}, {"pk": 85, "model": "contenttypes.contenttype", "fields": {"model": "order", "name": "order", "app_label": "shoppingcart"}}, {"pk": 86, "model": "contenttypes.contenttype", "fields": {"model": "orderitem", "name": "order item", "app_label": "shoppingcart"}}, {"pk": 92, "model": "contenttypes.contenttype", "fields": {"model": "paidcourseregistration", "name": "paid course registration", "app_label": "shoppingcart"}}, {"pk": 95, "model": "contenttypes.contenttype", "fields": {"model": "paidcourseregistrationannotation", "name": "paid course registration annotation", "app_label": "shoppingcart"}}, {"pk": 41, "model": "contenttypes.contenttype", "fields": {"model": "passwordhistory", "name": "password history", "app_label": "student"}}, {"pk": 122, "model": "contenttypes.contenttype", "fields": {"model": "peerworkflow", "name": "peer workflow", "app_label": "assessment"}}, {"pk": 123, "model": "contenttypes.contenttype", "fields": {"model": "peerworkflowitem", "name": "peer workflow item", "app_label": "assessment"}}, {"pk": 40, "model": "contenttypes.contenttype", "fields": {"model": "pendingemailchange", "name": "pending email change", "app_label": "student"}}, {"pk": 39, "model": "contenttypes.contenttype", "fields": {"model": "pendingnamechange", "name": "pending name change", "app_label": "student"}}, {"pk": 12, "model": "contenttypes.contenttype", "fields": {"model": "periodictask", "name": "periodic task", "app_label": "djcelery"}}, {"pk": 11, "model": "contenttypes.contenttype", "fields": {"model": "periodictasks", "name": "periodic tasks", "app_label": "djcelery"}}, {"pk": 1, "model": "contenttypes.contenttype", "fields": {"model": "permission", "name": "permission", "app_label": "auth"}}, {"pk": 133, "model": "contenttypes.contenttype", "fields": {"model": "profile", "name": "profile", "app_label": "edxval"}}, {"pk": 17, "model": "contenttypes.contenttype", "fields": {"model": "psychometricdata", "name": "psychometric data", "app_label": "psychometrics"}}, {"pk": 80, "model": "contenttypes.contenttype", "fields": {"model": "puzzlecomplete", "name": "puzzle complete", "app_label": "foldit"}}, {"pk": 63, "model": "contenttypes.contenttype", "fields": {"model": "refreshtoken", "name": "refresh token", "app_label": "oauth2"}}, {"pk": 38, "model": "contenttypes.contenttype", "fields": {"model": "registration", "name": "registration", "app_label": "student"}}, {"pk": 89, "model": "contenttypes.contenttype", "fields": {"model": "registrationcoderedemption", "name": "registration code redemption", "app_label": "shoppingcart"}}, {"pk": 70, "model": "contenttypes.contenttype", "fields": {"model": "reusableplugin", "name": "reusable plugin", "app_label": "wiki"}}, {"pk": 72, "model": "contenttypes.contenttype", "fields": {"model": "revisionplugin", "name": "revision plugin", "app_label": "wiki"}}, {"pk": 73, "model": "contenttypes.contenttype", "fields": {"model": "revisionpluginrevision", "name": "revision plugin revision", "app_label": "wiki"}}, {"pk": 115, "model": "contenttypes.contenttype", "fields": {"model": "rubric", "name": "rubric", "app_label": "assessment"}}, {"pk": 8, "model": "contenttypes.contenttype", "fields": {"model": "tasksetmeta", "name": "saved group result", "app_label": "djcelery"}}, {"pk": 79, "model": "contenttypes.contenttype", "fields": {"model": "score", "name": "score", "app_label": "foldit"}}, {"pk": 113, "model": "contenttypes.contenttype", "fields": {"model": "score", "name": "score", "app_label": "submissions"}}, {"pk": 114, "model": "contenttypes.contenttype", "fields": {"model": "scoresummary", "name": "score summary", "app_label": "submissions"}}, {"pk": 16, "model": "contenttypes.contenttype", "fields": {"model": "servercircuit", "name": "server circuit", "app_label": "circuit"}}, {"pk": 5, "model": "contenttypes.contenttype", "fields": {"model": "session", "name": "session", "app_label": "sessions"}}, {"pk": 76, "model": "contenttypes.contenttype", "fields": {"model": "settings", "name": "settings", "app_label": "django_notify"}}, {"pk": 71, "model": "contenttypes.contenttype", "fields": {"model": "simpleplugin", "name": "simple plugin", "app_label": "wiki"}}, {"pk": 6, "model": "contenttypes.contenttype", "fields": {"model": "site", "name": "site", "app_label": "sites"}}, {"pk": 101, "model": "contenttypes.contenttype", "fields": {"model": "softwaresecurephotoverification", "name": "software secure photo verification", "app_label": "verify_student"}}, {"pk": 82, "model": "contenttypes.contenttype", "fields": {"model": "splashconfig", "name": "splash config", "app_label": "splash"}}, {"pk": 111, "model": "contenttypes.contenttype", "fields": {"model": "studentitem", "name": "student item", "app_label": "submissions"}}, {"pk": 26, "model": "contenttypes.contenttype", "fields": {"model": "studentmodule", "name": "student module", "app_label": "courseware"}}, {"pk": 27, "model": "contenttypes.contenttype", "fields": {"model": "studentmodulehistory", "name": "student module history", "app_label": "courseware"}}, {"pk": 125, "model": "contenttypes.contenttype", "fields": {"model": "studenttrainingworkflow", "name": "student training workflow", "app_label": "assessment"}}, {"pk": 126, "model": "contenttypes.contenttype", "fields": {"model": "studenttrainingworkflowitem", "name": "student training workflow item", "app_label": "assessment"}}, {"pk": 112, "model": "contenttypes.contenttype", "fields": {"model": "submission", "name": "submission", "app_label": "submissions"}}, {"pk": 77, "model": "contenttypes.contenttype", "fields": {"model": "subscription", "name": "subscription", "app_label": "django_notify"}}, {"pk": 137, "model": "contenttypes.contenttype", "fields": {"model": "subtitle", "name": "subtitle", "app_label": "edxval"}}, {"pk": 109, "model": "contenttypes.contenttype", "fields": {"model": "surveyanswer", "name": "survey answer", "app_label": "survey"}}, {"pk": 108, "model": "contenttypes.contenttype", "fields": {"model": "surveyform", "name": "survey form", "app_label": "survey"}}, {"pk": 14, "model": "contenttypes.contenttype", "fields": {"model": "taskstate", "name": "task", "app_label": "djcelery"}}, {"pk": 7, "model": "contenttypes.contenttype", "fields": {"model": "taskmeta", "name": "task state", "app_label": "djcelery"}}, {"pk": 47, "model": "contenttypes.contenttype", "fields": {"model": "trackinglog", "name": "tracking log", "app_label": "track"}}, {"pk": 124, "model": "contenttypes.contenttype", "fields": {"model": "trainingexample", "name": "training example", "app_label": "assessment"}}, {"pk": 64, "model": "contenttypes.contenttype", "fields": {"model": "trustedclient", "name": "trusted client", "app_label": "oauth2_provider"}}, {"pk": 75, "model": "contenttypes.contenttype", "fields": {"model": "notificationtype", "name": "type", "app_label": "django_notify"}}, {"pk": 68, "model": "contenttypes.contenttype", "fields": {"model": "urlpath", "name": "URL path", "app_label": "wiki"}}, {"pk": 3, "model": "contenttypes.contenttype", "fields": {"model": "user", "name": "user", "app_label": "auth"}}, {"pk": 84, "model": "contenttypes.contenttype", "fields": {"model": "usercoursetag", "name": "user course tag", "app_label": "user_api"}}, {"pk": 52, "model": "contenttypes.contenttype", "fields": {"model": "userlicense", "name": "user license", "app_label": "licenses"}}, {"pk": 20, "model": "contenttypes.contenttype", "fields": {"model": "useropenid", "name": "user open id", "app_label": "django_openid_auth"}}, {"pk": 83, "model": "contenttypes.contenttype", "fields": {"model": "userpreference", "name": "user preference", "app_label": "user_api"}}, {"pk": 35, "model": "contenttypes.contenttype", "fields": {"model": "userprofile", "name": "user profile", "app_label": "student"}}, {"pk": 36, "model": "contenttypes.contenttype", "fields": {"model": "usersignupsource", "name": "user signup source", "app_label": "student"}}, {"pk": 22, "model": "contenttypes.contenttype", "fields": {"model": "usersocialauth", "name": "user social auth", "app_label": "default"}}, {"pk": 34, "model": "contenttypes.contenttype", "fields": {"model": "userstanding", "name": "user standing", "app_label": "student"}}, {"pk": 37, "model": "contenttypes.contenttype", "fields": {"model": "usertestgroup", "name": "user test group", "app_label": "student"}}, {"pk": 134, "model": "contenttypes.contenttype", "fields": {"model": "video", "name": "video", "app_label": "edxval"}}, {"pk": 13, "model": "contenttypes.contenttype", "fields": {"model": "workerstate", "name": "worker", "app_label": "djcelery"}}, {"pk": 30, "model": "contenttypes.contenttype", "fields": {"model": "xmodulestudentinfofield", "name": "x module student info field", "app_label": "courseware"}}, {"pk": 29, "model": "contenttypes.contenttype", "fields": {"model": "xmodulestudentprefsfield", "name": "x module student prefs field", "app_label": "courseware"}}, {"pk": 28, "model": "contenttypes.contenttype", "fields": {"model": "xmoduleuserstatesummaryfield", "name": "x module user state summary field", "app_label": "courseware"}}, {"pk": 1, "model": "sites.site", "fields": {"domain": "example.com", "name": "example.com"}}, {"pk": 1, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:27Z", "app_name": "courseware", "migration": "0001_initial"}}, {"pk": 2, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:27Z", "app_name": "courseware", "migration": "0002_add_indexes"}}, {"pk": 3, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:27Z", "app_name": "courseware", "migration": "0003_done_grade_cache"}}, {"pk": 4, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:27Z", "app_name": "courseware", "migration": "0004_add_field_studentmodule_course_id"}}, {"pk": 5, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:27Z", "app_name": "courseware", "migration": "0005_auto__add_offlinecomputedgrade__add_unique_offlinecomputedgrade_user_c"}}, {"pk": 6, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:27Z", "app_name": "courseware", "migration": "0006_create_student_module_history"}}, {"pk": 7, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:27Z", "app_name": "courseware", "migration": "0007_allow_null_version_in_history"}}, {"pk": 8, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "courseware", "migration": "0008_add_xmodule_storage"}}, {"pk": 9, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "courseware", "migration": "0009_add_field_default"}}, {"pk": 10, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "courseware", "migration": "0010_rename_xblock_field_content_to_user_state_summary"}}, {"pk": 11, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "student", "migration": "0001_initial"}}, {"pk": 12, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "student", "migration": "0002_text_to_varchar_and_indexes"}}, {"pk": 13, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "student", "migration": "0003_auto__add_usertestgroup"}}, {"pk": 14, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "student", "migration": "0004_add_email_index"}}, {"pk": 15, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "student", "migration": "0005_name_change"}}, {"pk": 16, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "student", "migration": "0006_expand_meta_field"}}, {"pk": 17, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "student", "migration": "0007_convert_to_utf8"}}, {"pk": 18, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:28Z", "app_name": "student", "migration": "0008__auto__add_courseregistration"}}, {"pk": 19, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0009_auto__del_courseregistration__add_courseenrollment"}}, {"pk": 20, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0010_auto__chg_field_courseenrollment_course_id"}}, {"pk": 21, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0011_auto__chg_field_courseenrollment_user__del_unique_courseenrollment_use"}}, {"pk": 22, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0012_auto__add_field_userprofile_gender__add_field_userprofile_date_of_birt"}}, {"pk": 23, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0013_auto__chg_field_userprofile_meta"}}, {"pk": 24, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0014_auto__del_courseenrollment"}}, {"pk": 25, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0015_auto__add_courseenrollment__add_unique_courseenrollment_user_course_id"}}, {"pk": 26, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country"}}, {"pk": 27, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0017_rename_date_to_created"}}, {"pk": 28, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0018_auto"}}, {"pk": 29, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0019_create_approved_demographic_fields_fall_2012"}}, {"pk": 30, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0020_add_test_center_user"}}, {"pk": 31, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0021_remove_askbot"}}, {"pk": 32, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:29Z", "app_name": "student", "migration": "0022_auto__add_courseenrollmentallowed__add_unique_courseenrollmentallowed_"}}, {"pk": 33, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0023_add_test_center_registration"}}, {"pk": 34, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0024_add_allow_certificate"}}, {"pk": 35, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0025_auto__add_field_courseenrollmentallowed_auto_enroll"}}, {"pk": 36, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0026_auto__remove_index_student_testcenterregistration_accommodation_request"}}, {"pk": 37, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0027_add_active_flag_and_mode_to_courseware_enrollment"}}, {"pk": 38, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0028_auto__add_userstanding"}}, {"pk": 39, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0029_add_lookup_table_between_user_and_anonymous_student_id"}}, {"pk": 40, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0029_remove_pearson"}}, {"pk": 41, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0030_auto__chg_field_anonymoususerid_anonymous_user_id"}}, {"pk": 42, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0031_drop_student_anonymoususerid_temp_archive"}}, {"pk": 43, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0032_add_field_UserProfile_country_add_field_UserProfile_city"}}, {"pk": 44, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0032_auto__add_loginfailures"}}, {"pk": 45, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0033_auto__add_passwordhistory"}}, {"pk": 46, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:30Z", "app_name": "student", "migration": "0034_auto__add_courseaccessrole"}}, {"pk": 47, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:31Z", "app_name": "student", "migration": "0035_access_roles"}}, {"pk": 48, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:31Z", "app_name": "student", "migration": "0036_access_roles_orgless"}}, {"pk": 49, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:31Z", "app_name": "student", "migration": "0037_auto__add_courseregistrationcode"}}, {"pk": 50, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:31Z", "app_name": "student", "migration": "0038_auto__add_usersignupsource"}}, {"pk": 51, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:31Z", "app_name": "student", "migration": "0039_auto__del_courseregistrationcode"}}, {"pk": 52, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:31Z", "app_name": "student", "migration": "0040_auto__del_field_usersignupsource_user_id__add_field_usersignupsource_u"}}, {"pk": 53, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:31Z", "app_name": "student", "migration": "0041_add_dashboard_config"}}, {"pk": 54, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "track", "migration": "0001_initial"}}, {"pk": 55, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "track", "migration": "0002_auto__add_field_trackinglog_host__chg_field_trackinglog_event_type__ch"}}, {"pk": 56, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0001_added_generatedcertificates"}}, {"pk": 57, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0002_auto__add_field_generatedcertificate_download_url"}}, {"pk": 58, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0003_auto__add_field_generatedcertificate_enabled"}}, {"pk": 59, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0004_auto__add_field_generatedcertificate_graded_certificate_id__add_field_"}}, {"pk": 60, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0005_auto__add_field_generatedcertificate_name"}}, {"pk": 61, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0006_auto__chg_field_generatedcertificate_certificate_id"}}, {"pk": 62, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0007_auto__add_revokedcertificate"}}, {"pk": 63, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0008_auto__del_revokedcertificate__del_field_generatedcertificate_name__add"}}, {"pk": 64, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0009_auto__del_field_generatedcertificate_graded_download_url__del_field_ge"}}, {"pk": 65, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:32Z", "app_name": "certificates", "migration": "0010_auto__del_field_generatedcertificate_enabled__add_field_generatedcerti"}}, {"pk": 66, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:33Z", "app_name": "certificates", "migration": "0011_auto__del_field_generatedcertificate_certificate_id__add_field_generat"}}, {"pk": 67, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:33Z", "app_name": "certificates", "migration": "0012_auto__add_field_generatedcertificate_name__add_field_generatedcertific"}}, {"pk": 68, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:33Z", "app_name": "certificates", "migration": "0013_auto__add_field_generatedcertificate_error_reason"}}, {"pk": 69, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:33Z", "app_name": "certificates", "migration": "0014_adding_whitelist"}}, {"pk": 70, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:33Z", "app_name": "certificates", "migration": "0015_adding_mode_for_verified_certs"}}, {"pk": 71, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:33Z", "app_name": "instructor_task", "migration": "0001_initial"}}, {"pk": 72, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:33Z", "app_name": "instructor_task", "migration": "0002_add_subtask_field"}}, {"pk": 73, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:33Z", "app_name": "licenses", "migration": "0001_initial"}}, {"pk": 74, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "course_groups", "migration": "0001_initial"}}, {"pk": 75, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "course_groups", "migration": "0002_add_model_CourseUserGroupPartitionGroup"}}, {"pk": 76, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0001_initial"}}, {"pk": 77, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0002_change_field_names"}}, {"pk": 78, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0003_add_optout_user"}}, {"pk": 79, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0004_migrate_optout_user"}}, {"pk": 80, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0005_remove_optout_email"}}, {"pk": 81, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0006_add_course_email_template"}}, {"pk": 82, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0007_load_course_email_template"}}, {"pk": 83, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0008_add_course_authorizations"}}, {"pk": 84, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:34Z", "app_name": "bulk_email", "migration": "0009_force_unique_course_ids"}}, {"pk": 85, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:35Z", "app_name": "bulk_email", "migration": "0010_auto__chg_field_optout_course_id__add_field_courseemail_template_name_"}}, {"pk": 86, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:35Z", "app_name": "external_auth", "migration": "0001_initial"}}, {"pk": 87, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:35Z", "app_name": "oauth2", "migration": "0001_initial"}}, {"pk": 88, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:35Z", "app_name": "oauth2", "migration": "0002_auto__chg_field_client_user"}}, {"pk": 89, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:35Z", "app_name": "oauth2", "migration": "0003_auto__add_field_client_name"}}, {"pk": 90, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:35Z", "app_name": "oauth2", "migration": "0004_auto__add_index_accesstoken_token"}}, {"pk": 91, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:36Z", "app_name": "oauth2_provider", "migration": "0001_initial"}}, {"pk": 92, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:36Z", "app_name": "wiki", "migration": "0001_initial"}}, {"pk": 93, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:36Z", "app_name": "wiki", "migration": "0002_auto__add_field_articleplugin_created"}}, {"pk": 94, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:36Z", "app_name": "wiki", "migration": "0003_auto__add_field_urlpath_article"}}, {"pk": 95, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:36Z", "app_name": "wiki", "migration": "0004_populate_urlpath__article"}}, {"pk": 96, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:36Z", "app_name": "wiki", "migration": "0005_auto__chg_field_urlpath_article"}}, {"pk": 97, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:37Z", "app_name": "wiki", "migration": "0006_auto__add_attachmentrevision__add_image__add_attachment"}}, {"pk": 98, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:37Z", "app_name": "wiki", "migration": "0007_auto__add_articlesubscription"}}, {"pk": 99, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:37Z", "app_name": "wiki", "migration": "0008_auto__add_simpleplugin__add_revisionpluginrevision__add_imagerevision_"}}, {"pk": 100, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:37Z", "app_name": "wiki", "migration": "0009_auto__add_field_imagerevision_width__add_field_imagerevision_height"}}, {"pk": 101, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:37Z", "app_name": "wiki", "migration": "0010_auto__chg_field_imagerevision_image"}}, {"pk": 102, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:37Z", "app_name": "wiki", "migration": "0011_auto__chg_field_imagerevision_width__chg_field_imagerevision_height"}}, {"pk": 103, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:38Z", "app_name": "django_notify", "migration": "0001_initial"}}, {"pk": 104, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:38Z", "app_name": "notifications", "migration": "0001_initial"}}, {"pk": 105, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:38Z", "app_name": "foldit", "migration": "0001_initial"}}, {"pk": 106, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:39Z", "app_name": "django_comment_client", "migration": "0001_initial"}}, {"pk": 107, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:39Z", "app_name": "django_comment_common", "migration": "0001_initial"}}, {"pk": 108, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:39Z", "app_name": "notes", "migration": "0001_initial"}}, {"pk": 109, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:39Z", "app_name": "splash", "migration": "0001_initial"}}, {"pk": 110, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:39Z", "app_name": "splash", "migration": "0002_auto__add_field_splashconfig_unaffected_url_paths"}}, {"pk": 111, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "user_api", "migration": "0001_initial"}}, {"pk": 112, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "user_api", "migration": "0002_auto__add_usercoursetags__add_unique_usercoursetags_user_course_id_key"}}, {"pk": 113, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "user_api", "migration": "0003_rename_usercoursetags"}}, {"pk": 114, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "shoppingcart", "migration": "0001_initial"}}, {"pk": 115, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "shoppingcart", "migration": "0002_auto__add_field_paidcourseregistration_mode"}}, {"pk": 116, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "shoppingcart", "migration": "0003_auto__del_field_orderitem_line_cost"}}, {"pk": 117, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "shoppingcart", "migration": "0004_auto__add_field_orderitem_fulfilled_time"}}, {"pk": 118, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "shoppingcart", "migration": "0005_auto__add_paidcourseregistrationannotation__add_field_orderitem_report"}}, {"pk": 119, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "shoppingcart", "migration": "0006_auto__add_field_order_refunded_time__add_field_orderitem_refund_reques"}}, {"pk": 120, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:40Z", "app_name": "shoppingcart", "migration": "0007_auto__add_field_orderitem_service_fee"}}, {"pk": 121, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:41Z", "app_name": "shoppingcart", "migration": "0008_auto__add_coupons__add_couponredemption__chg_field_certificateitem_cou"}}, {"pk": 122, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:41Z", "app_name": "shoppingcart", "migration": "0009_auto__del_coupons__add_courseregistrationcode__add_coupon__chg_field_c"}}, {"pk": 123, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:41Z", "app_name": "shoppingcart", "migration": "0010_auto__add_registrationcoderedemption__del_field_courseregistrationcode"}}, {"pk": 124, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:41Z", "app_name": "shoppingcart", "migration": "0011_auto__add_invoice__add_field_courseregistrationcode_invoice"}}, {"pk": 125, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:41Z", "app_name": "shoppingcart", "migration": "0012_auto__del_field_courseregistrationcode_transaction_group_name__del_fie"}}, {"pk": 126, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:41Z", "app_name": "shoppingcart", "migration": "0013_auto__add_field_invoice_is_valid"}}, {"pk": 127, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "shoppingcart", "migration": "0014_auto__del_field_invoice_tax_id__add_field_invoice_address_line_1__add_"}}, {"pk": 128, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "shoppingcart", "migration": "0015_auto__del_field_invoice_purchase_order_number__del_field_invoice_compa"}}, {"pk": 129, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "shoppingcart", "migration": "0016_auto__del_field_invoice_company_email__del_field_invoice_company_refer"}}, {"pk": 130, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "shoppingcart", "migration": "0017_auto__add_field_courseregistrationcode_order__chg_field_registrationco"}}, {"pk": 131, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "shoppingcart", "migration": "0018_auto__add_donation"}}, {"pk": 132, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "shoppingcart", "migration": "0019_auto__add_donationconfiguration"}}, {"pk": 133, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "shoppingcart", "migration": "0020_auto__add_courseregcodeitem__add_courseregcodeitemannotation__add_fiel"}}, {"pk": 134, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "shoppingcart", "migration": "0021_auto__add_field_orderitem_created__add_field_orderitem_modified"}}, {"pk": 135, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "course_modes", "migration": "0001_initial"}}, {"pk": 136, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:42Z", "app_name": "course_modes", "migration": "0002_auto__add_field_coursemode_currency"}}, {"pk": 137, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "course_modes", "migration": "0003_auto__add_unique_coursemode_course_id_currency_mode_slug"}}, {"pk": 138, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "course_modes", "migration": "0004_auto__add_field_coursemode_expiration_date"}}, {"pk": 139, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "course_modes", "migration": "0005_auto__add_field_coursemode_expiration_datetime"}}, {"pk": 140, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "course_modes", "migration": "0006_expiration_date_to_datetime"}}, {"pk": 141, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "course_modes", "migration": "0007_add_description"}}, {"pk": 142, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "course_modes", "migration": "0007_auto__add_coursemodesarchive__chg_field_coursemode_course_id"}}, {"pk": 143, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "verify_student", "migration": "0001_initial"}}, {"pk": 144, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "verify_student", "migration": "0002_auto__add_field_softwaresecurephotoverification_window"}}, {"pk": 145, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "verify_student", "migration": "0003_auto__add_field_softwaresecurephotoverification_display"}}, {"pk": 146, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "dark_lang", "migration": "0001_initial"}}, {"pk": 147, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:43Z", "app_name": "dark_lang", "migration": "0002_enable_on_install"}}, {"pk": 148, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:44Z", "app_name": "reverification", "migration": "0001_initial"}}, {"pk": 149, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:44Z", "app_name": "embargo", "migration": "0001_initial"}}, {"pk": 150, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:44Z", "app_name": "course_action_state", "migration": "0001_initial"}}, {"pk": 151, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:44Z", "app_name": "course_action_state", "migration": "0002_add_rerun_display_name"}}, {"pk": 152, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:45Z", "app_name": "survey", "migration": "0001_initial"}}, {"pk": 153, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:45Z", "app_name": "linkedin", "migration": "0001_initial"}}, {"pk": 154, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:45Z", "app_name": "submissions", "migration": "0001_initial"}}, {"pk": 155, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:46Z", "app_name": "submissions", "migration": "0002_auto__add_scoresummary"}}, {"pk": 156, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:46Z", "app_name": "submissions", "migration": "0003_auto__del_field_submission_answer__add_field_submission_raw_answer"}}, {"pk": 157, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:46Z", "app_name": "submissions", "migration": "0004_auto__add_field_score_reset"}}, {"pk": 158, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:46Z", "app_name": "assessment", "migration": "0001_initial"}}, {"pk": 159, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:46Z", "app_name": "assessment", "migration": "0002_auto__add_assessmentfeedbackoption__del_field_assessmentfeedback_feedb"}}, {"pk": 160, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:46Z", "app_name": "assessment", "migration": "0003_add_index_pw_course_item_student"}}, {"pk": 161, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:46Z", "app_name": "assessment", "migration": "0004_auto__add_field_peerworkflow_graded_count"}}, {"pk": 162, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0005_auto__del_field_peerworkflow_graded_count__add_field_peerworkflow_grad"}}, {"pk": 163, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0006_auto__add_field_assessmentpart_feedback"}}, {"pk": 164, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0007_auto__chg_field_assessmentpart_feedback"}}, {"pk": 165, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0008_student_training"}}, {"pk": 166, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0009_auto__add_unique_studenttrainingworkflowitem_order_num_workflow"}}, {"pk": 167, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0010_auto__add_unique_studenttrainingworkflow_submission_uuid"}}, {"pk": 168, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0011_ai_training"}}, {"pk": 169, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0012_move_algorithm_id_to_classifier_set"}}, {"pk": 170, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:47Z", "app_name": "assessment", "migration": "0013_auto__add_field_aigradingworkflow_essay_text"}}, {"pk": 171, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0014_auto__add_field_aitrainingworkflow_item_id__add_field_aitrainingworkfl"}}, {"pk": 172, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0015_auto__add_unique_aitrainingworkflow_uuid__add_unique_aigradingworkflow"}}, {"pk": 173, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0016_auto__add_field_aiclassifierset_course_id__add_field_aiclassifierset_i"}}, {"pk": 174, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0016_auto__add_field_rubric_structure_hash"}}, {"pk": 175, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0017_rubric_structure_hash"}}, {"pk": 176, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0018_auto__add_field_assessmentpart_criterion"}}, {"pk": 177, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0019_assessmentpart_criterion_field"}}, {"pk": 178, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0020_assessmentpart_criterion_not_null"}}, {"pk": 179, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0021_assessmentpart_option_nullable"}}, {"pk": 180, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0022__add_label_fields"}}, {"pk": 181, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:48Z", "app_name": "assessment", "migration": "0023_assign_criteria_and_option_labels"}}, {"pk": 182, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:49Z", "app_name": "workflow", "migration": "0001_initial"}}, {"pk": 183, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:49Z", "app_name": "workflow", "migration": "0002_auto__add_field_assessmentworkflow_course_id__add_field_assessmentwork"}}, {"pk": 184, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:49Z", "app_name": "workflow", "migration": "0003_auto__add_assessmentworkflowstep"}}, {"pk": 185, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:49Z", "app_name": "edxval", "migration": "0001_initial"}}, {"pk": 186, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:49Z", "app_name": "edxval", "migration": "0002_default_profiles"}}, {"pk": 187, "model": "south.migrationhistory", "fields": {"applied": "2014-11-13T22:49:49Z", "app_name": "django_extensions", "migration": "0001_empty"}}, {"pk": 1, "model": "edxval.profile", "fields": {"width": 1280, "profile_name": "desktop_mp4", "extension": "mp4", "height": 720}}, {"pk": 2, "model": "edxval.profile", "fields": {"width": 1280, "profile_name": "desktop_webm", "extension": "webm", "height": 720}}, {"pk": 3, "model": "edxval.profile", "fields": {"width": 960, "profile_name": "mobile_high", "extension": "mp4", "height": 540}}, {"pk": 4, "model": "edxval.profile", "fields": {"width": 640, "profile_name": "mobile_low", "extension": "mp4", "height": 360}}, {"pk": 5, "model": "edxval.profile", "fields": {"width": 1920, "profile_name": "youtube", "extension": "mp4", "height": 1080}}, {"pk": 61, "model": "auth.permission", "fields": {"codename": "add_logentry", "name": "Can add log entry", "content_type": 21}}, {"pk": 62, "model": "auth.permission", "fields": {"codename": "change_logentry", "name": "Can change log entry", "content_type": 21}}, {"pk": 63, "model": "auth.permission", "fields": {"codename": "delete_logentry", "name": "Can delete log entry", "content_type": 21}}, {"pk": 385, "model": "auth.permission", "fields": {"codename": "add_aiclassifier", "name": "Can add ai classifier", "content_type": 128}}, {"pk": 386, "model": "auth.permission", "fields": {"codename": "change_aiclassifier", "name": "Can change ai classifier", "content_type": 128}}, {"pk": 387, "model": "auth.permission", "fields": {"codename": "delete_aiclassifier", "name": "Can delete ai classifier", "content_type": 128}}, {"pk": 382, "model": "auth.permission", "fields": {"codename": "add_aiclassifierset", "name": "Can add ai classifier set", "content_type": 127}}, {"pk": 383, "model": "auth.permission", "fields": {"codename": "change_aiclassifierset", "name": "Can change ai classifier set", "content_type": 127}}, {"pk": 384, "model": "auth.permission", "fields": {"codename": "delete_aiclassifierset", "name": "Can delete ai classifier set", "content_type": 127}}, {"pk": 391, "model": "auth.permission", "fields": {"codename": "add_aigradingworkflow", "name": "Can add ai grading workflow", "content_type": 130}}, {"pk": 392, "model": "auth.permission", "fields": {"codename": "change_aigradingworkflow", "name": "Can change ai grading workflow", "content_type": 130}}, {"pk": 393, "model": "auth.permission", "fields": {"codename": "delete_aigradingworkflow", "name": "Can delete ai grading workflow", "content_type": 130}}, {"pk": 388, "model": "auth.permission", "fields": {"codename": "add_aitrainingworkflow", "name": "Can add ai training workflow", "content_type": 129}}, {"pk": 389, "model": "auth.permission", "fields": {"codename": "change_aitrainingworkflow", "name": "Can change ai training workflow", "content_type": 129}}, {"pk": 390, "model": "auth.permission", "fields": {"codename": "delete_aitrainingworkflow", "name": "Can delete ai training workflow", "content_type": 129}}, {"pk": 355, "model": "auth.permission", "fields": {"codename": "add_assessment", "name": "Can add assessment", "content_type": 118}}, {"pk": 356, "model": "auth.permission", "fields": {"codename": "change_assessment", "name": "Can change assessment", "content_type": 118}}, {"pk": 357, "model": "auth.permission", "fields": {"codename": "delete_assessment", "name": "Can delete assessment", "content_type": 118}}, {"pk": 364, "model": "auth.permission", "fields": {"codename": "add_assessmentfeedback", "name": "Can add assessment feedback", "content_type": 121}}, {"pk": 365, "model": "auth.permission", "fields": {"codename": "change_assessmentfeedback", "name": "Can change assessment feedback", "content_type": 121}}, {"pk": 366, "model": "auth.permission", "fields": {"codename": "delete_assessmentfeedback", "name": "Can delete assessment feedback", "content_type": 121}}, {"pk": 361, "model": "auth.permission", "fields": {"codename": "add_assessmentfeedbackoption", "name": "Can add assessment feedback option", "content_type": 120}}, {"pk": 362, "model": "auth.permission", "fields": {"codename": "change_assessmentfeedbackoption", "name": "Can change assessment feedback option", "content_type": 120}}, {"pk": 363, "model": "auth.permission", "fields": {"codename": "delete_assessmentfeedbackoption", "name": "Can delete assessment feedback option", "content_type": 120}}, {"pk": 358, "model": "auth.permission", "fields": {"codename": "add_assessmentpart", "name": "Can add assessment part", "content_type": 119}}, {"pk": 359, "model": "auth.permission", "fields": {"codename": "change_assessmentpart", "name": "Can change assessment part", "content_type": 119}}, {"pk": 360, "model": "auth.permission", "fields": {"codename": "delete_assessmentpart", "name": "Can delete assessment part", "content_type": 119}}, {"pk": 349, "model": "auth.permission", "fields": {"codename": "add_criterion", "name": "Can add criterion", "content_type": 116}}, {"pk": 350, "model": "auth.permission", "fields": {"codename": "change_criterion", "name": "Can change criterion", "content_type": 116}}, {"pk": 351, "model": "auth.permission", "fields": {"codename": "delete_criterion", "name": "Can delete criterion", "content_type": 116}}, {"pk": 352, "model": "auth.permission", "fields": {"codename": "add_criterionoption", "name": "Can add criterion option", "content_type": 117}}, {"pk": 353, "model": "auth.permission", "fields": {"codename": "change_criterionoption", "name": "Can change criterion option", "content_type": 117}}, {"pk": 354, "model": "auth.permission", "fields": {"codename": "delete_criterionoption", "name": "Can delete criterion option", "content_type": 117}}, {"pk": 367, "model": "auth.permission", "fields": {"codename": "add_peerworkflow", "name": "Can add peer workflow", "content_type": 122}}, {"pk": 368, "model": "auth.permission", "fields": {"codename": "change_peerworkflow", "name": "Can change peer workflow", "content_type": 122}}, {"pk": 369, "model": "auth.permission", "fields": {"codename": "delete_peerworkflow", "name": "Can delete peer workflow", "content_type": 122}}, {"pk": 370, "model": "auth.permission", "fields": {"codename": "add_peerworkflowitem", "name": "Can add peer workflow item", "content_type": 123}}, {"pk": 371, "model": "auth.permission", "fields": {"codename": "change_peerworkflowitem", "name": "Can change peer workflow item", "content_type": 123}}, {"pk": 372, "model": "auth.permission", "fields": {"codename": "delete_peerworkflowitem", "name": "Can delete peer workflow item", "content_type": 123}}, {"pk": 346, "model": "auth.permission", "fields": {"codename": "add_rubric", "name": "Can add rubric", "content_type": 115}}, {"pk": 347, "model": "auth.permission", "fields": {"codename": "change_rubric", "name": "Can change rubric", "content_type": 115}}, {"pk": 348, "model": "auth.permission", "fields": {"codename": "delete_rubric", "name": "Can delete rubric", "content_type": 115}}, {"pk": 376, "model": "auth.permission", "fields": {"codename": "add_studenttrainingworkflow", "name": "Can add student training workflow", "content_type": 125}}, {"pk": 377, "model": "auth.permission", "fields": {"codename": "change_studenttrainingworkflow", "name": "Can change student training workflow", "content_type": 125}}, {"pk": 378, "model": "auth.permission", "fields": {"codename": "delete_studenttrainingworkflow", "name": "Can delete student training workflow", "content_type": 125}}, {"pk": 379, "model": "auth.permission", "fields": {"codename": "add_studenttrainingworkflowitem", "name": "Can add student training workflow item", "content_type": 126}}, {"pk": 380, "model": "auth.permission", "fields": {"codename": "change_studenttrainingworkflowitem", "name": "Can change student training workflow item", "content_type": 126}}, {"pk": 381, "model": "auth.permission", "fields": {"codename": "delete_studenttrainingworkflowitem", "name": "Can delete student training workflow item", "content_type": 126}}, {"pk": 373, "model": "auth.permission", "fields": {"codename": "add_trainingexample", "name": "Can add training example", "content_type": 124}}, {"pk": 374, "model": "auth.permission", "fields": {"codename": "change_trainingexample", "name": "Can change training example", "content_type": 124}}, {"pk": 375, "model": "auth.permission", "fields": {"codename": "delete_trainingexample", "name": "Can delete training example", "content_type": 124}}, {"pk": 4, "model": "auth.permission", "fields": {"codename": "add_group", "name": "Can add group", "content_type": 2}}, {"pk": 5, "model": "auth.permission", "fields": {"codename": "change_group", "name": "Can change group", "content_type": 2}}, {"pk": 6, "model": "auth.permission", "fields": {"codename": "delete_group", "name": "Can delete group", "content_type": 2}}, {"pk": 1, "model": "auth.permission", "fields": {"codename": "add_permission", "name": "Can add permission", "content_type": 1}}, {"pk": 2, "model": "auth.permission", "fields": {"codename": "change_permission", "name": "Can change permission", "content_type": 1}}, {"pk": 3, "model": "auth.permission", "fields": {"codename": "delete_permission", "name": "Can delete permission", "content_type": 1}}, {"pk": 7, "model": "auth.permission", "fields": {"codename": "add_user", "name": "Can add user", "content_type": 3}}, {"pk": 8, "model": "auth.permission", "fields": {"codename": "change_user", "name": "Can change user", "content_type": 3}}, {"pk": 9, "model": "auth.permission", "fields": {"codename": "delete_user", "name": "Can delete user", "content_type": 3}}, {"pk": 172, "model": "auth.permission", "fields": {"codename": "add_courseauthorization", "name": "Can add course authorization", "content_type": 58}}, {"pk": 173, "model": "auth.permission", "fields": {"codename": "change_courseauthorization", "name": "Can change course authorization", "content_type": 58}}, {"pk": 174, "model": "auth.permission", "fields": {"codename": "delete_courseauthorization", "name": "Can delete course authorization", "content_type": 58}}, {"pk": 163, "model": "auth.permission", "fields": {"codename": "add_courseemail", "name": "Can add course email", "content_type": 55}}, {"pk": 164, "model": "auth.permission", "fields": {"codename": "change_courseemail", "name": "Can change course email", "content_type": 55}}, {"pk": 165, "model": "auth.permission", "fields": {"codename": "delete_courseemail", "name": "Can delete course email", "content_type": 55}}, {"pk": 169, "model": "auth.permission", "fields": {"codename": "add_courseemailtemplate", "name": "Can add course email template", "content_type": 57}}, {"pk": 170, "model": "auth.permission", "fields": {"codename": "change_courseemailtemplate", "name": "Can change course email template", "content_type": 57}}, {"pk": 171, "model": "auth.permission", "fields": {"codename": "delete_courseemailtemplate", "name": "Can delete course email template", "content_type": 57}}, {"pk": 166, "model": "auth.permission", "fields": {"codename": "add_optout", "name": "Can add optout", "content_type": 56}}, {"pk": 167, "model": "auth.permission", "fields": {"codename": "change_optout", "name": "Can change optout", "content_type": 56}}, {"pk": 168, "model": "auth.permission", "fields": {"codename": "delete_optout", "name": "Can delete optout", "content_type": 56}}, {"pk": 142, "model": "auth.permission", "fields": {"codename": "add_certificatewhitelist", "name": "Can add certificate whitelist", "content_type": 48}}, {"pk": 143, "model": "auth.permission", "fields": {"codename": "change_certificatewhitelist", "name": "Can change certificate whitelist", "content_type": 48}}, {"pk": 144, "model": "auth.permission", "fields": {"codename": "delete_certificatewhitelist", "name": "Can delete certificate whitelist", "content_type": 48}}, {"pk": 145, "model": "auth.permission", "fields": {"codename": "add_generatedcertificate", "name": "Can add generated certificate", "content_type": 49}}, {"pk": 146, "model": "auth.permission", "fields": {"codename": "change_generatedcertificate", "name": "Can change generated certificate", "content_type": 49}}, {"pk": 147, "model": "auth.permission", "fields": {"codename": "delete_generatedcertificate", "name": "Can delete generated certificate", "content_type": 49}}, {"pk": 46, "model": "auth.permission", "fields": {"codename": "add_servercircuit", "name": "Can add server circuit", "content_type": 16}}, {"pk": 47, "model": "auth.permission", "fields": {"codename": "change_servercircuit", "name": "Can change server circuit", "content_type": 16}}, {"pk": 48, "model": "auth.permission", "fields": {"codename": "delete_servercircuit", "name": "Can delete server circuit", "content_type": 16}}, {"pk": 10, "model": "auth.permission", "fields": {"codename": "add_contenttype", "name": "Can add content type", "content_type": 4}}, {"pk": 11, "model": "auth.permission", "fields": {"codename": "change_contenttype", "name": "Can change content type", "content_type": 4}}, {"pk": 12, "model": "auth.permission", "fields": {"codename": "delete_contenttype", "name": "Can delete content type", "content_type": 4}}, {"pk": 91, "model": "auth.permission", "fields": {"codename": "add_offlinecomputedgrade", "name": "Can add offline computed grade", "content_type": 31}}, {"pk": 92, "model": "auth.permission", "fields": {"codename": "change_offlinecomputedgrade", "name": "Can change offline computed grade", "content_type": 31}}, {"pk": 93, "model": "auth.permission", "fields": {"codename": "delete_offlinecomputedgrade", "name": "Can delete offline computed grade", "content_type": 31}}, {"pk": 94, "model": "auth.permission", "fields": {"codename": "add_offlinecomputedgradelog", "name": "Can add offline computed grade log", "content_type": 32}}, {"pk": 95, "model": "auth.permission", "fields": {"codename": "change_offlinecomputedgradelog", "name": "Can change offline computed grade log", "content_type": 32}}, {"pk": 96, "model": "auth.permission", "fields": {"codename": "delete_offlinecomputedgradelog", "name": "Can delete offline computed grade log", "content_type": 32}}, {"pk": 76, "model": "auth.permission", "fields": {"codename": "add_studentmodule", "name": "Can add student module", "content_type": 26}}, {"pk": 77, "model": "auth.permission", "fields": {"codename": "change_studentmodule", "name": "Can change student module", "content_type": 26}}, {"pk": 78, "model": "auth.permission", "fields": {"codename": "delete_studentmodule", "name": "Can delete student module", "content_type": 26}}, {"pk": 79, "model": "auth.permission", "fields": {"codename": "add_studentmodulehistory", "name": "Can add student module history", "content_type": 27}}, {"pk": 80, "model": "auth.permission", "fields": {"codename": "change_studentmodulehistory", "name": "Can change student module history", "content_type": 27}}, {"pk": 81, "model": "auth.permission", "fields": {"codename": "delete_studentmodulehistory", "name": "Can delete student module history", "content_type": 27}}, {"pk": 88, "model": "auth.permission", "fields": {"codename": "add_xmodulestudentinfofield", "name": "Can add x module student info field", "content_type": 30}}, {"pk": 89, "model": "auth.permission", "fields": {"codename": "change_xmodulestudentinfofield", "name": "Can change x module student info field", "content_type": 30}}, {"pk": 90, "model": "auth.permission", "fields": {"codename": "delete_xmodulestudentinfofield", "name": "Can delete x module student info field", "content_type": 30}}, {"pk": 85, "model": "auth.permission", "fields": {"codename": "add_xmodulestudentprefsfield", "name": "Can add x module student prefs field", "content_type": 29}}, {"pk": 86, "model": "auth.permission", "fields": {"codename": "change_xmodulestudentprefsfield", "name": "Can change x module student prefs field", "content_type": 29}}, {"pk": 87, "model": "auth.permission", "fields": {"codename": "delete_xmodulestudentprefsfield", "name": "Can delete x module student prefs field", "content_type": 29}}, {"pk": 82, "model": "auth.permission", "fields": {"codename": "add_xmoduleuserstatesummaryfield", "name": "Can add x module user state summary field", "content_type": 28}}, {"pk": 83, "model": "auth.permission", "fields": {"codename": "change_xmoduleuserstatesummaryfield", "name": "Can change x module user state summary field", "content_type": 28}}, {"pk": 84, "model": "auth.permission", "fields": {"codename": "delete_xmoduleuserstatesummaryfield", "name": "Can delete x module user state summary field", "content_type": 28}}, {"pk": 322, "model": "auth.permission", "fields": {"codename": "add_coursererunstate", "name": "Can add course rerun state", "content_type": 107}}, {"pk": 323, "model": "auth.permission", "fields": {"codename": "change_coursererunstate", "name": "Can change course rerun state", "content_type": 107}}, {"pk": 324, "model": "auth.permission", "fields": {"codename": "delete_coursererunstate", "name": "Can delete course rerun state", "content_type": 107}}, {"pk": 157, "model": "auth.permission", "fields": {"codename": "add_courseusergroup", "name": "Can add course user group", "content_type": 53}}, {"pk": 158, "model": "auth.permission", "fields": {"codename": "change_courseusergroup", "name": "Can change course user group", "content_type": 53}}, {"pk": 159, "model": "auth.permission", "fields": {"codename": "delete_courseusergroup", "name": "Can delete course user group", "content_type": 53}}, {"pk": 160, "model": "auth.permission", "fields": {"codename": "add_courseusergrouppartitiongroup", "name": "Can add course user group partition group", "content_type": 54}}, {"pk": 161, "model": "auth.permission", "fields": {"codename": "change_courseusergrouppartitiongroup", "name": "Can change course user group partition group", "content_type": 54}}, {"pk": 162, "model": "auth.permission", "fields": {"codename": "delete_courseusergrouppartitiongroup", "name": "Can delete course user group partition group", "content_type": 54}}, {"pk": 298, "model": "auth.permission", "fields": {"codename": "add_coursemode", "name": "Can add course mode", "content_type": 99}}, {"pk": 299, "model": "auth.permission", "fields": {"codename": "change_coursemode", "name": "Can change course mode", "content_type": 99}}, {"pk": 300, "model": "auth.permission", "fields": {"codename": "delete_coursemode", "name": "Can delete course mode", "content_type": 99}}, {"pk": 301, "model": "auth.permission", "fields": {"codename": "add_coursemodesarchive", "name": "Can add course modes archive", "content_type": 100}}, {"pk": 302, "model": "auth.permission", "fields": {"codename": "change_coursemodesarchive", "name": "Can change course modes archive", "content_type": 100}}, {"pk": 303, "model": "auth.permission", "fields": {"codename": "delete_coursemodesarchive", "name": "Can delete course modes archive", "content_type": 100}}, {"pk": 307, "model": "auth.permission", "fields": {"codename": "add_darklangconfig", "name": "Can add dark lang config", "content_type": 102}}, {"pk": 308, "model": "auth.permission", "fields": {"codename": "change_darklangconfig", "name": "Can change dark lang config", "content_type": 102}}, {"pk": 309, "model": "auth.permission", "fields": {"codename": "delete_darklangconfig", "name": "Can delete dark lang config", "content_type": 102}}, {"pk": 70, "model": "auth.permission", "fields": {"codename": "add_association", "name": "Can add association", "content_type": 24}}, {"pk": 71, "model": "auth.permission", "fields": {"codename": "change_association", "name": "Can change association", "content_type": 24}}, {"pk": 72, "model": "auth.permission", "fields": {"codename": "delete_association", "name": "Can delete association", "content_type": 24}}, {"pk": 73, "model": "auth.permission", "fields": {"codename": "add_code", "name": "Can add code", "content_type": 25}}, {"pk": 74, "model": "auth.permission", "fields": {"codename": "change_code", "name": "Can change code", "content_type": 25}}, {"pk": 75, "model": "auth.permission", "fields": {"codename": "delete_code", "name": "Can delete code", "content_type": 25}}, {"pk": 67, "model": "auth.permission", "fields": {"codename": "add_nonce", "name": "Can add nonce", "content_type": 23}}, {"pk": 68, "model": "auth.permission", "fields": {"codename": "change_nonce", "name": "Can change nonce", "content_type": 23}}, {"pk": 69, "model": "auth.permission", "fields": {"codename": "delete_nonce", "name": "Can delete nonce", "content_type": 23}}, {"pk": 64, "model": "auth.permission", "fields": {"codename": "add_usersocialauth", "name": "Can add user social auth", "content_type": 22}}, {"pk": 65, "model": "auth.permission", "fields": {"codename": "change_usersocialauth", "name": "Can change user social auth", "content_type": 22}}, {"pk": 66, "model": "auth.permission", "fields": {"codename": "delete_usersocialauth", "name": "Can delete user social auth", "content_type": 22}}, {"pk": 235, "model": "auth.permission", "fields": {"codename": "add_notification", "name": "Can add notification", "content_type": 78}}, {"pk": 236, "model": "auth.permission", "fields": {"codename": "change_notification", "name": "Can change notification", "content_type": 78}}, {"pk": 237, "model": "auth.permission", "fields": {"codename": "delete_notification", "name": "Can delete notification", "content_type": 78}}, {"pk": 226, "model": "auth.permission", "fields": {"codename": "add_notificationtype", "name": "Can add type", "content_type": 75}}, {"pk": 227, "model": "auth.permission", "fields": {"codename": "change_notificationtype", "name": "Can change type", "content_type": 75}}, {"pk": 228, "model": "auth.permission", "fields": {"codename": "delete_notificationtype", "name": "Can delete type", "content_type": 75}}, {"pk": 229, "model": "auth.permission", "fields": {"codename": "add_settings", "name": "Can add settings", "content_type": 76}}, {"pk": 230, "model": "auth.permission", "fields": {"codename": "change_settings", "name": "Can change settings", "content_type": 76}}, {"pk": 231, "model": "auth.permission", "fields": {"codename": "delete_settings", "name": "Can delete settings", "content_type": 76}}, {"pk": 232, "model": "auth.permission", "fields": {"codename": "add_subscription", "name": "Can add subscription", "content_type": 77}}, {"pk": 233, "model": "auth.permission", "fields": {"codename": "change_subscription", "name": "Can change subscription", "content_type": 77}}, {"pk": 234, "model": "auth.permission", "fields": {"codename": "delete_subscription", "name": "Can delete subscription", "content_type": 77}}, {"pk": 55, "model": "auth.permission", "fields": {"codename": "add_association", "name": "Can add association", "content_type": 19}}, {"pk": 56, "model": "auth.permission", "fields": {"codename": "change_association", "name": "Can change association", "content_type": 19}}, {"pk": 57, "model": "auth.permission", "fields": {"codename": "delete_association", "name": "Can delete association", "content_type": 19}}, {"pk": 52, "model": "auth.permission", "fields": {"codename": "add_nonce", "name": "Can add nonce", "content_type": 18}}, {"pk": 53, "model": "auth.permission", "fields": {"codename": "change_nonce", "name": "Can change nonce", "content_type": 18}}, {"pk": 54, "model": "auth.permission", "fields": {"codename": "delete_nonce", "name": "Can delete nonce", "content_type": 18}}, {"pk": 58, "model": "auth.permission", "fields": {"codename": "add_useropenid", "name": "Can add user open id", "content_type": 20}}, {"pk": 59, "model": "auth.permission", "fields": {"codename": "change_useropenid", "name": "Can change user open id", "content_type": 20}}, {"pk": 60, "model": "auth.permission", "fields": {"codename": "delete_useropenid", "name": "Can delete user open id", "content_type": 20}}, {"pk": 28, "model": "auth.permission", "fields": {"codename": "add_crontabschedule", "name": "Can add crontab", "content_type": 10}}, {"pk": 29, "model": "auth.permission", "fields": {"codename": "change_crontabschedule", "name": "Can change crontab", "content_type": 10}}, {"pk": 30, "model": "auth.permission", "fields": {"codename": "delete_crontabschedule", "name": "Can delete crontab", "content_type": 10}}, {"pk": 25, "model": "auth.permission", "fields": {"codename": "add_intervalschedule", "name": "Can add interval", "content_type": 9}}, {"pk": 26, "model": "auth.permission", "fields": {"codename": "change_intervalschedule", "name": "Can change interval", "content_type": 9}}, {"pk": 27, "model": "auth.permission", "fields": {"codename": "delete_intervalschedule", "name": "Can delete interval", "content_type": 9}}, {"pk": 34, "model": "auth.permission", "fields": {"codename": "add_periodictask", "name": "Can add periodic task", "content_type": 12}}, {"pk": 35, "model": "auth.permission", "fields": {"codename": "change_periodictask", "name": "Can change periodic task", "content_type": 12}}, {"pk": 36, "model": "auth.permission", "fields": {"codename": "delete_periodictask", "name": "Can delete periodic task", "content_type": 12}}, {"pk": 31, "model": "auth.permission", "fields": {"codename": "add_periodictasks", "name": "Can add periodic tasks", "content_type": 11}}, {"pk": 32, "model": "auth.permission", "fields": {"codename": "change_periodictasks", "name": "Can change periodic tasks", "content_type": 11}}, {"pk": 33, "model": "auth.permission", "fields": {"codename": "delete_periodictasks", "name": "Can delete periodic tasks", "content_type": 11}}, {"pk": 19, "model": "auth.permission", "fields": {"codename": "add_taskmeta", "name": "Can add task state", "content_type": 7}}, {"pk": 20, "model": "auth.permission", "fields": {"codename": "change_taskmeta", "name": "Can change task state", "content_type": 7}}, {"pk": 21, "model": "auth.permission", "fields": {"codename": "delete_taskmeta", "name": "Can delete task state", "content_type": 7}}, {"pk": 22, "model": "auth.permission", "fields": {"codename": "add_tasksetmeta", "name": "Can add saved group result", "content_type": 8}}, {"pk": 23, "model": "auth.permission", "fields": {"codename": "change_tasksetmeta", "name": "Can change saved group result", "content_type": 8}}, {"pk": 24, "model": "auth.permission", "fields": {"codename": "delete_tasksetmeta", "name": "Can delete saved group result", "content_type": 8}}, {"pk": 40, "model": "auth.permission", "fields": {"codename": "add_taskstate", "name": "Can add task", "content_type": 14}}, {"pk": 41, "model": "auth.permission", "fields": {"codename": "change_taskstate", "name": "Can change task", "content_type": 14}}, {"pk": 42, "model": "auth.permission", "fields": {"codename": "delete_taskstate", "name": "Can delete task", "content_type": 14}}, {"pk": 37, "model": "auth.permission", "fields": {"codename": "add_workerstate", "name": "Can add worker", "content_type": 13}}, {"pk": 38, "model": "auth.permission", "fields": {"codename": "change_workerstate", "name": "Can change worker", "content_type": 13}}, {"pk": 39, "model": "auth.permission", "fields": {"codename": "delete_workerstate", "name": "Can delete worker", "content_type": 13}}, {"pk": 406, "model": "auth.permission", "fields": {"codename": "add_coursevideo", "name": "Can add course video", "content_type": 135}}, {"pk": 407, "model": "auth.permission", "fields": {"codename": "change_coursevideo", "name": "Can change course video", "content_type": 135}}, {"pk": 408, "model": "auth.permission", "fields": {"codename": "delete_coursevideo", "name": "Can delete course video", "content_type": 135}}, {"pk": 409, "model": "auth.permission", "fields": {"codename": "add_encodedvideo", "name": "Can add encoded video", "content_type": 136}}, {"pk": 410, "model": "auth.permission", "fields": {"codename": "change_encodedvideo", "name": "Can change encoded video", "content_type": 136}}, {"pk": 411, "model": "auth.permission", "fields": {"codename": "delete_encodedvideo", "name": "Can delete encoded video", "content_type": 136}}, {"pk": 400, "model": "auth.permission", "fields": {"codename": "add_profile", "name": "Can add profile", "content_type": 133}}, {"pk": 401, "model": "auth.permission", "fields": {"codename": "change_profile", "name": "Can change profile", "content_type": 133}}, {"pk": 402, "model": "auth.permission", "fields": {"codename": "delete_profile", "name": "Can delete profile", "content_type": 133}}, {"pk": 412, "model": "auth.permission", "fields": {"codename": "add_subtitle", "name": "Can add subtitle", "content_type": 137}}, {"pk": 413, "model": "auth.permission", "fields": {"codename": "change_subtitle", "name": "Can change subtitle", "content_type": 137}}, {"pk": 414, "model": "auth.permission", "fields": {"codename": "delete_subtitle", "name": "Can delete subtitle", "content_type": 137}}, {"pk": 403, "model": "auth.permission", "fields": {"codename": "add_video", "name": "Can add video", "content_type": 134}}, {"pk": 404, "model": "auth.permission", "fields": {"codename": "change_video", "name": "Can change video", "content_type": 134}}, {"pk": 405, "model": "auth.permission", "fields": {"codename": "delete_video", "name": "Can delete video", "content_type": 134}}, {"pk": 313, "model": "auth.permission", "fields": {"codename": "add_embargoedcourse", "name": "Can add embargoed course", "content_type": 104}}, {"pk": 314, "model": "auth.permission", "fields": {"codename": "change_embargoedcourse", "name": "Can change embargoed course", "content_type": 104}}, {"pk": 315, "model": "auth.permission", "fields": {"codename": "delete_embargoedcourse", "name": "Can delete embargoed course", "content_type": 104}}, {"pk": 316, "model": "auth.permission", "fields": {"codename": "add_embargoedstate", "name": "Can add embargoed state", "content_type": 105}}, {"pk": 317, "model": "auth.permission", "fields": {"codename": "change_embargoedstate", "name": "Can change embargoed state", "content_type": 105}}, {"pk": 318, "model": "auth.permission", "fields": {"codename": "delete_embargoedstate", "name": "Can delete embargoed state", "content_type": 105}}, {"pk": 319, "model": "auth.permission", "fields": {"codename": "add_ipfilter", "name": "Can add ip filter", "content_type": 106}}, {"pk": 320, "model": "auth.permission", "fields": {"codename": "change_ipfilter", "name": "Can change ip filter", "content_type": 106}}, {"pk": 321, "model": "auth.permission", "fields": {"codename": "delete_ipfilter", "name": "Can delete ip filter", "content_type": 106}}, {"pk": 175, "model": "auth.permission", "fields": {"codename": "add_externalauthmap", "name": "Can add external auth map", "content_type": 59}}, {"pk": 176, "model": "auth.permission", "fields": {"codename": "change_externalauthmap", "name": "Can change external auth map", "content_type": 59}}, {"pk": 177, "model": "auth.permission", "fields": {"codename": "delete_externalauthmap", "name": "Can delete external auth map", "content_type": 59}}, {"pk": 241, "model": "auth.permission", "fields": {"codename": "add_puzzlecomplete", "name": "Can add puzzle complete", "content_type": 80}}, {"pk": 242, "model": "auth.permission", "fields": {"codename": "change_puzzlecomplete", "name": "Can change puzzle complete", "content_type": 80}}, {"pk": 243, "model": "auth.permission", "fields": {"codename": "delete_puzzlecomplete", "name": "Can delete puzzle complete", "content_type": 80}}, {"pk": 238, "model": "auth.permission", "fields": {"codename": "add_score", "name": "Can add score", "content_type": 79}}, {"pk": 239, "model": "auth.permission", "fields": {"codename": "change_score", "name": "Can change score", "content_type": 79}}, {"pk": 240, "model": "auth.permission", "fields": {"codename": "delete_score", "name": "Can delete score", "content_type": 79}}, {"pk": 148, "model": "auth.permission", "fields": {"codename": "add_instructortask", "name": "Can add instructor task", "content_type": 50}}, {"pk": 149, "model": "auth.permission", "fields": {"codename": "change_instructortask", "name": "Can change instructor task", "content_type": 50}}, {"pk": 150, "model": "auth.permission", "fields": {"codename": "delete_instructortask", "name": "Can delete instructor task", "content_type": 50}}, {"pk": 151, "model": "auth.permission", "fields": {"codename": "add_coursesoftware", "name": "Can add course software", "content_type": 51}}, {"pk": 152, "model": "auth.permission", "fields": {"codename": "change_coursesoftware", "name": "Can change course software", "content_type": 51}}, {"pk": 153, "model": "auth.permission", "fields": {"codename": "delete_coursesoftware", "name": "Can delete course software", "content_type": 51}}, {"pk": 154, "model": "auth.permission", "fields": {"codename": "add_userlicense", "name": "Can add user license", "content_type": 52}}, {"pk": 155, "model": "auth.permission", "fields": {"codename": "change_userlicense", "name": "Can change user license", "content_type": 52}}, {"pk": 156, "model": "auth.permission", "fields": {"codename": "delete_userlicense", "name": "Can delete user license", "content_type": 52}}, {"pk": 331, "model": "auth.permission", "fields": {"codename": "add_linkedin", "name": "Can add linked in", "content_type": 110}}, {"pk": 332, "model": "auth.permission", "fields": {"codename": "change_linkedin", "name": "Can change linked in", "content_type": 110}}, {"pk": 333, "model": "auth.permission", "fields": {"codename": "delete_linkedin", "name": "Can delete linked in", "content_type": 110}}, {"pk": 244, "model": "auth.permission", "fields": {"codename": "add_note", "name": "Can add note", "content_type": 81}}, {"pk": 245, "model": "auth.permission", "fields": {"codename": "change_note", "name": "Can change note", "content_type": 81}}, {"pk": 246, "model": "auth.permission", "fields": {"codename": "delete_note", "name": "Can delete note", "content_type": 81}}, {"pk": 184, "model": "auth.permission", "fields": {"codename": "add_accesstoken", "name": "Can add access token", "content_type": 62}}, {"pk": 185, "model": "auth.permission", "fields": {"codename": "change_accesstoken", "name": "Can change access token", "content_type": 62}}, {"pk": 186, "model": "auth.permission", "fields": {"codename": "delete_accesstoken", "name": "Can delete access token", "content_type": 62}}, {"pk": 178, "model": "auth.permission", "fields": {"codename": "add_client", "name": "Can add client", "content_type": 60}}, {"pk": 179, "model": "auth.permission", "fields": {"codename": "change_client", "name": "Can change client", "content_type": 60}}, {"pk": 180, "model": "auth.permission", "fields": {"codename": "delete_client", "name": "Can delete client", "content_type": 60}}, {"pk": 181, "model": "auth.permission", "fields": {"codename": "add_grant", "name": "Can add grant", "content_type": 61}}, {"pk": 182, "model": "auth.permission", "fields": {"codename": "change_grant", "name": "Can change grant", "content_type": 61}}, {"pk": 183, "model": "auth.permission", "fields": {"codename": "delete_grant", "name": "Can delete grant", "content_type": 61}}, {"pk": 187, "model": "auth.permission", "fields": {"codename": "add_refreshtoken", "name": "Can add refresh token", "content_type": 63}}, {"pk": 188, "model": "auth.permission", "fields": {"codename": "change_refreshtoken", "name": "Can change refresh token", "content_type": 63}}, {"pk": 189, "model": "auth.permission", "fields": {"codename": "delete_refreshtoken", "name": "Can delete refresh token", "content_type": 63}}, {"pk": 190, "model": "auth.permission", "fields": {"codename": "add_trustedclient", "name": "Can add trusted client", "content_type": 64}}, {"pk": 191, "model": "auth.permission", "fields": {"codename": "change_trustedclient", "name": "Can change trusted client", "content_type": 64}}, {"pk": 192, "model": "auth.permission", "fields": {"codename": "delete_trustedclient", "name": "Can delete trusted client", "content_type": 64}}, {"pk": 49, "model": "auth.permission", "fields": {"codename": "add_psychometricdata", "name": "Can add psychometric data", "content_type": 17}}, {"pk": 50, "model": "auth.permission", "fields": {"codename": "change_psychometricdata", "name": "Can change psychometric data", "content_type": 17}}, {"pk": 51, "model": "auth.permission", "fields": {"codename": "delete_psychometricdata", "name": "Can delete psychometric data", "content_type": 17}}, {"pk": 310, "model": "auth.permission", "fields": {"codename": "add_midcoursereverificationwindow", "name": "Can add midcourse reverification window", "content_type": 103}}, {"pk": 311, "model": "auth.permission", "fields": {"codename": "change_midcoursereverificationwindow", "name": "Can change midcourse reverification window", "content_type": 103}}, {"pk": 312, "model": "auth.permission", "fields": {"codename": "delete_midcoursereverificationwindow", "name": "Can delete midcourse reverification window", "content_type": 103}}, {"pk": 13, "model": "auth.permission", "fields": {"codename": "add_session", "name": "Can add session", "content_type": 5}}, {"pk": 14, "model": "auth.permission", "fields": {"codename": "change_session", "name": "Can change session", "content_type": 5}}, {"pk": 15, "model": "auth.permission", "fields": {"codename": "delete_session", "name": "Can delete session", "content_type": 5}}, {"pk": 289, "model": "auth.permission", "fields": {"codename": "add_certificateitem", "name": "Can add certificate item", "content_type": 96}}, {"pk": 290, "model": "auth.permission", "fields": {"codename": "change_certificateitem", "name": "Can change certificate item", "content_type": 96}}, {"pk": 291, "model": "auth.permission", "fields": {"codename": "delete_certificateitem", "name": "Can delete certificate item", "content_type": 96}}, {"pk": 271, "model": "auth.permission", "fields": {"codename": "add_coupon", "name": "Can add coupon", "content_type": 90}}, {"pk": 272, "model": "auth.permission", "fields": {"codename": "change_coupon", "name": "Can change coupon", "content_type": 90}}, {"pk": 273, "model": "auth.permission", "fields": {"codename": "delete_coupon", "name": "Can delete coupon", "content_type": 90}}, {"pk": 274, "model": "auth.permission", "fields": {"codename": "add_couponredemption", "name": "Can add coupon redemption", "content_type": 91}}, {"pk": 275, "model": "auth.permission", "fields": {"codename": "change_couponredemption", "name": "Can change coupon redemption", "content_type": 91}}, {"pk": 276, "model": "auth.permission", "fields": {"codename": "delete_couponredemption", "name": "Can delete coupon redemption", "content_type": 91}}, {"pk": 280, "model": "auth.permission", "fields": {"codename": "add_courseregcodeitem", "name": "Can add course reg code item", "content_type": 93}}, {"pk": 281, "model": "auth.permission", "fields": {"codename": "change_courseregcodeitem", "name": "Can change course reg code item", "content_type": 93}}, {"pk": 282, "model": "auth.permission", "fields": {"codename": "delete_courseregcodeitem", "name": "Can delete course reg code item", "content_type": 93}}, {"pk": 283, "model": "auth.permission", "fields": {"codename": "add_courseregcodeitemannotation", "name": "Can add course reg code item annotation", "content_type": 94}}, {"pk": 284, "model": "auth.permission", "fields": {"codename": "change_courseregcodeitemannotation", "name": "Can change course reg code item annotation", "content_type": 94}}, {"pk": 285, "model": "auth.permission", "fields": {"codename": "delete_courseregcodeitemannotation", "name": "Can delete course reg code item annotation", "content_type": 94}}, {"pk": 265, "model": "auth.permission", "fields": {"codename": "add_courseregistrationcode", "name": "Can add course registration code", "content_type": 88}}, {"pk": 266, "model": "auth.permission", "fields": {"codename": "change_courseregistrationcode", "name": "Can change course registration code", "content_type": 88}}, {"pk": 267, "model": "auth.permission", "fields": {"codename": "delete_courseregistrationcode", "name": "Can delete course registration code", "content_type": 88}}, {"pk": 295, "model": "auth.permission", "fields": {"codename": "add_donation", "name": "Can add donation", "content_type": 98}}, {"pk": 296, "model": "auth.permission", "fields": {"codename": "change_donation", "name": "Can change donation", "content_type": 98}}, {"pk": 297, "model": "auth.permission", "fields": {"codename": "delete_donation", "name": "Can delete donation", "content_type": 98}}, {"pk": 292, "model": "auth.permission", "fields": {"codename": "add_donationconfiguration", "name": "Can add donation configuration", "content_type": 97}}, {"pk": 293, "model": "auth.permission", "fields": {"codename": "change_donationconfiguration", "name": "Can change donation configuration", "content_type": 97}}, {"pk": 294, "model": "auth.permission", "fields": {"codename": "delete_donationconfiguration", "name": "Can delete donation configuration", "content_type": 97}}, {"pk": 262, "model": "auth.permission", "fields": {"codename": "add_invoice", "name": "Can add invoice", "content_type": 87}}, {"pk": 263, "model": "auth.permission", "fields": {"codename": "change_invoice", "name": "Can change invoice", "content_type": 87}}, {"pk": 264, "model": "auth.permission", "fields": {"codename": "delete_invoice", "name": "Can delete invoice", "content_type": 87}}, {"pk": 256, "model": "auth.permission", "fields": {"codename": "add_order", "name": "Can add order", "content_type": 85}}, {"pk": 257, "model": "auth.permission", "fields": {"codename": "change_order", "name": "Can change order", "content_type": 85}}, {"pk": 258, "model": "auth.permission", "fields": {"codename": "delete_order", "name": "Can delete order", "content_type": 85}}, {"pk": 259, "model": "auth.permission", "fields": {"codename": "add_orderitem", "name": "Can add order item", "content_type": 86}}, {"pk": 260, "model": "auth.permission", "fields": {"codename": "change_orderitem", "name": "Can change order item", "content_type": 86}}, {"pk": 261, "model": "auth.permission", "fields": {"codename": "delete_orderitem", "name": "Can delete order item", "content_type": 86}}, {"pk": 277, "model": "auth.permission", "fields": {"codename": "add_paidcourseregistration", "name": "Can add paid course registration", "content_type": 92}}, {"pk": 278, "model": "auth.permission", "fields": {"codename": "change_paidcourseregistration", "name": "Can change paid course registration", "content_type": 92}}, {"pk": 279, "model": "auth.permission", "fields": {"codename": "delete_paidcourseregistration", "name": "Can delete paid course registration", "content_type": 92}}, {"pk": 286, "model": "auth.permission", "fields": {"codename": "add_paidcourseregistrationannotation", "name": "Can add paid course registration annotation", "content_type": 95}}, {"pk": 287, "model": "auth.permission", "fields": {"codename": "change_paidcourseregistrationannotation", "name": "Can change paid course registration annotation", "content_type": 95}}, {"pk": 288, "model": "auth.permission", "fields": {"codename": "delete_paidcourseregistrationannotation", "name": "Can delete paid course registration annotation", "content_type": 95}}, {"pk": 268, "model": "auth.permission", "fields": {"codename": "add_registrationcoderedemption", "name": "Can add registration code redemption", "content_type": 89}}, {"pk": 269, "model": "auth.permission", "fields": {"codename": "change_registrationcoderedemption", "name": "Can change registration code redemption", "content_type": 89}}, {"pk": 270, "model": "auth.permission", "fields": {"codename": "delete_registrationcoderedemption", "name": "Can delete registration code redemption", "content_type": 89}}, {"pk": 16, "model": "auth.permission", "fields": {"codename": "add_site", "name": "Can add site", "content_type": 6}}, {"pk": 17, "model": "auth.permission", "fields": {"codename": "change_site", "name": "Can change site", "content_type": 6}}, {"pk": 18, "model": "auth.permission", "fields": {"codename": "delete_site", "name": "Can delete site", "content_type": 6}}, {"pk": 43, "model": "auth.permission", "fields": {"codename": "add_migrationhistory", "name": "Can add migration history", "content_type": 15}}, {"pk": 44, "model": "auth.permission", "fields": {"codename": "change_migrationhistory", "name": "Can change migration history", "content_type": 15}}, {"pk": 45, "model": "auth.permission", "fields": {"codename": "delete_migrationhistory", "name": "Can delete migration history", "content_type": 15}}, {"pk": 247, "model": "auth.permission", "fields": {"codename": "add_splashconfig", "name": "Can add splash config", "content_type": 82}}, {"pk": 248, "model": "auth.permission", "fields": {"codename": "change_splashconfig", "name": "Can change splash config", "content_type": 82}}, {"pk": 249, "model": "auth.permission", "fields": {"codename": "delete_splashconfig", "name": "Can delete splash config", "content_type": 82}}, {"pk": 97, "model": "auth.permission", "fields": {"codename": "add_anonymoususerid", "name": "Can add anonymous user id", "content_type": 33}}, {"pk": 98, "model": "auth.permission", "fields": {"codename": "change_anonymoususerid", "name": "Can change anonymous user id", "content_type": 33}}, {"pk": 99, "model": "auth.permission", "fields": {"codename": "delete_anonymoususerid", "name": "Can delete anonymous user id", "content_type": 33}}, {"pk": 133, "model": "auth.permission", "fields": {"codename": "add_courseaccessrole", "name": "Can add course access role", "content_type": 45}}, {"pk": 134, "model": "auth.permission", "fields": {"codename": "change_courseaccessrole", "name": "Can change course access role", "content_type": 45}}, {"pk": 135, "model": "auth.permission", "fields": {"codename": "delete_courseaccessrole", "name": "Can delete course access role", "content_type": 45}}, {"pk": 127, "model": "auth.permission", "fields": {"codename": "add_courseenrollment", "name": "Can add course enrollment", "content_type": 43}}, {"pk": 128, "model": "auth.permission", "fields": {"codename": "change_courseenrollment", "name": "Can change course enrollment", "content_type": 43}}, {"pk": 129, "model": "auth.permission", "fields": {"codename": "delete_courseenrollment", "name": "Can delete course enrollment", "content_type": 43}}, {"pk": 130, "model": "auth.permission", "fields": {"codename": "add_courseenrollmentallowed", "name": "Can add course enrollment allowed", "content_type": 44}}, {"pk": 131, "model": "auth.permission", "fields": {"codename": "change_courseenrollmentallowed", "name": "Can change course enrollment allowed", "content_type": 44}}, {"pk": 132, "model": "auth.permission", "fields": {"codename": "delete_courseenrollmentallowed", "name": "Can delete course enrollment allowed", "content_type": 44}}, {"pk": 136, "model": "auth.permission", "fields": {"codename": "add_dashboardconfiguration", "name": "Can add dashboard configuration", "content_type": 46}}, {"pk": 137, "model": "auth.permission", "fields": {"codename": "change_dashboardconfiguration", "name": "Can change dashboard configuration", "content_type": 46}}, {"pk": 138, "model": "auth.permission", "fields": {"codename": "delete_dashboardconfiguration", "name": "Can delete dashboard configuration", "content_type": 46}}, {"pk": 124, "model": "auth.permission", "fields": {"codename": "add_loginfailures", "name": "Can add login failures", "content_type": 42}}, {"pk": 125, "model": "auth.permission", "fields": {"codename": "change_loginfailures", "name": "Can change login failures", "content_type": 42}}, {"pk": 126, "model": "auth.permission", "fields": {"codename": "delete_loginfailures", "name": "Can delete login failures", "content_type": 42}}, {"pk": 121, "model": "auth.permission", "fields": {"codename": "add_passwordhistory", "name": "Can add password history", "content_type": 41}}, {"pk": 122, "model": "auth.permission", "fields": {"codename": "change_passwordhistory", "name": "Can change password history", "content_type": 41}}, {"pk": 123, "model": "auth.permission", "fields": {"codename": "delete_passwordhistory", "name": "Can delete password history", "content_type": 41}}, {"pk": 118, "model": "auth.permission", "fields": {"codename": "add_pendingemailchange", "name": "Can add pending email change", "content_type": 40}}, {"pk": 119, "model": "auth.permission", "fields": {"codename": "change_pendingemailchange", "name": "Can change pending email change", "content_type": 40}}, {"pk": 120, "model": "auth.permission", "fields": {"codename": "delete_pendingemailchange", "name": "Can delete pending email change", "content_type": 40}}, {"pk": 115, "model": "auth.permission", "fields": {"codename": "add_pendingnamechange", "name": "Can add pending name change", "content_type": 39}}, {"pk": 116, "model": "auth.permission", "fields": {"codename": "change_pendingnamechange", "name": "Can change pending name change", "content_type": 39}}, {"pk": 117, "model": "auth.permission", "fields": {"codename": "delete_pendingnamechange", "name": "Can delete pending name change", "content_type": 39}}, {"pk": 112, "model": "auth.permission", "fields": {"codename": "add_registration", "name": "Can add registration", "content_type": 38}}, {"pk": 113, "model": "auth.permission", "fields": {"codename": "change_registration", "name": "Can change registration", "content_type": 38}}, {"pk": 114, "model": "auth.permission", "fields": {"codename": "delete_registration", "name": "Can delete registration", "content_type": 38}}, {"pk": 103, "model": "auth.permission", "fields": {"codename": "add_userprofile", "name": "Can add user profile", "content_type": 35}}, {"pk": 104, "model": "auth.permission", "fields": {"codename": "change_userprofile", "name": "Can change user profile", "content_type": 35}}, {"pk": 105, "model": "auth.permission", "fields": {"codename": "delete_userprofile", "name": "Can delete user profile", "content_type": 35}}, {"pk": 106, "model": "auth.permission", "fields": {"codename": "add_usersignupsource", "name": "Can add user signup source", "content_type": 36}}, {"pk": 107, "model": "auth.permission", "fields": {"codename": "change_usersignupsource", "name": "Can change user signup source", "content_type": 36}}, {"pk": 108, "model": "auth.permission", "fields": {"codename": "delete_usersignupsource", "name": "Can delete user signup source", "content_type": 36}}, {"pk": 100, "model": "auth.permission", "fields": {"codename": "add_userstanding", "name": "Can add user standing", "content_type": 34}}, {"pk": 101, "model": "auth.permission", "fields": {"codename": "change_userstanding", "name": "Can change user standing", "content_type": 34}}, {"pk": 102, "model": "auth.permission", "fields": {"codename": "delete_userstanding", "name": "Can delete user standing", "content_type": 34}}, {"pk": 109, "model": "auth.permission", "fields": {"codename": "add_usertestgroup", "name": "Can add user test group", "content_type": 37}}, {"pk": 110, "model": "auth.permission", "fields": {"codename": "change_usertestgroup", "name": "Can change user test group", "content_type": 37}}, {"pk": 111, "model": "auth.permission", "fields": {"codename": "delete_usertestgroup", "name": "Can delete user test group", "content_type": 37}}, {"pk": 340, "model": "auth.permission", "fields": {"codename": "add_score", "name": "Can add score", "content_type": 113}}, {"pk": 341, "model": "auth.permission", "fields": {"codename": "change_score", "name": "Can change score", "content_type": 113}}, {"pk": 342, "model": "auth.permission", "fields": {"codename": "delete_score", "name": "Can delete score", "content_type": 113}}, {"pk": 343, "model": "auth.permission", "fields": {"codename": "add_scoresummary", "name": "Can add score summary", "content_type": 114}}, {"pk": 344, "model": "auth.permission", "fields": {"codename": "change_scoresummary", "name": "Can change score summary", "content_type": 114}}, {"pk": 345, "model": "auth.permission", "fields": {"codename": "delete_scoresummary", "name": "Can delete score summary", "content_type": 114}}, {"pk": 334, "model": "auth.permission", "fields": {"codename": "add_studentitem", "name": "Can add student item", "content_type": 111}}, {"pk": 335, "model": "auth.permission", "fields": {"codename": "change_studentitem", "name": "Can change student item", "content_type": 111}}, {"pk": 336, "model": "auth.permission", "fields": {"codename": "delete_studentitem", "name": "Can delete student item", "content_type": 111}}, {"pk": 337, "model": "auth.permission", "fields": {"codename": "add_submission", "name": "Can add submission", "content_type": 112}}, {"pk": 338, "model": "auth.permission", "fields": {"codename": "change_submission", "name": "Can change submission", "content_type": 112}}, {"pk": 339, "model": "auth.permission", "fields": {"codename": "delete_submission", "name": "Can delete submission", "content_type": 112}}, {"pk": 328, "model": "auth.permission", "fields": {"codename": "add_surveyanswer", "name": "Can add survey answer", "content_type": 109}}, {"pk": 329, "model": "auth.permission", "fields": {"codename": "change_surveyanswer", "name": "Can change survey answer", "content_type": 109}}, {"pk": 330, "model": "auth.permission", "fields": {"codename": "delete_surveyanswer", "name": "Can delete survey answer", "content_type": 109}}, {"pk": 325, "model": "auth.permission", "fields": {"codename": "add_surveyform", "name": "Can add survey form", "content_type": 108}}, {"pk": 326, "model": "auth.permission", "fields": {"codename": "change_surveyform", "name": "Can change survey form", "content_type": 108}}, {"pk": 327, "model": "auth.permission", "fields": {"codename": "delete_surveyform", "name": "Can delete survey form", "content_type": 108}}, {"pk": 139, "model": "auth.permission", "fields": {"codename": "add_trackinglog", "name": "Can add tracking log", "content_type": 47}}, {"pk": 140, "model": "auth.permission", "fields": {"codename": "change_trackinglog", "name": "Can change tracking log", "content_type": 47}}, {"pk": 141, "model": "auth.permission", "fields": {"codename": "delete_trackinglog", "name": "Can delete tracking log", "content_type": 47}}, {"pk": 253, "model": "auth.permission", "fields": {"codename": "add_usercoursetag", "name": "Can add user course tag", "content_type": 84}}, {"pk": 254, "model": "auth.permission", "fields": {"codename": "change_usercoursetag", "name": "Can change user course tag", "content_type": 84}}, {"pk": 255, "model": "auth.permission", "fields": {"codename": "delete_usercoursetag", "name": "Can delete user course tag", "content_type": 84}}, {"pk": 250, "model": "auth.permission", "fields": {"codename": "add_userpreference", "name": "Can add user preference", "content_type": 83}}, {"pk": 251, "model": "auth.permission", "fields": {"codename": "change_userpreference", "name": "Can change user preference", "content_type": 83}}, {"pk": 252, "model": "auth.permission", "fields": {"codename": "delete_userpreference", "name": "Can delete user preference", "content_type": 83}}, {"pk": 304, "model": "auth.permission", "fields": {"codename": "add_softwaresecurephotoverification", "name": "Can add software secure photo verification", "content_type": 101}}, {"pk": 305, "model": "auth.permission", "fields": {"codename": "change_softwaresecurephotoverification", "name": "Can change software secure photo verification", "content_type": 101}}, {"pk": 306, "model": "auth.permission", "fields": {"codename": "delete_softwaresecurephotoverification", "name": "Can delete software secure photo verification", "content_type": 101}}, {"pk": 193, "model": "auth.permission", "fields": {"codename": "add_article", "name": "Can add article", "content_type": 65}}, {"pk": 197, "model": "auth.permission", "fields": {"codename": "assign", "name": "Can change ownership of any article", "content_type": 65}}, {"pk": 194, "model": "auth.permission", "fields": {"codename": "change_article", "name": "Can change article", "content_type": 65}}, {"pk": 195, "model": "auth.permission", "fields": {"codename": "delete_article", "name": "Can delete article", "content_type": 65}}, {"pk": 198, "model": "auth.permission", "fields": {"codename": "grant", "name": "Can assign permissions to other users", "content_type": 65}}, {"pk": 196, "model": "auth.permission", "fields": {"codename": "moderate", "name": "Can edit all articles and lock/unlock/restore", "content_type": 65}}, {"pk": 199, "model": "auth.permission", "fields": {"codename": "add_articleforobject", "name": "Can add Article for object", "content_type": 66}}, {"pk": 200, "model": "auth.permission", "fields": {"codename": "change_articleforobject", "name": "Can change Article for object", "content_type": 66}}, {"pk": 201, "model": "auth.permission", "fields": {"codename": "delete_articleforobject", "name": "Can delete Article for object", "content_type": 66}}, {"pk": 208, "model": "auth.permission", "fields": {"codename": "add_articleplugin", "name": "Can add article plugin", "content_type": 69}}, {"pk": 209, "model": "auth.permission", "fields": {"codename": "change_articleplugin", "name": "Can change article plugin", "content_type": 69}}, {"pk": 210, "model": "auth.permission", "fields": {"codename": "delete_articleplugin", "name": "Can delete article plugin", "content_type": 69}}, {"pk": 202, "model": "auth.permission", "fields": {"codename": "add_articlerevision", "name": "Can add article revision", "content_type": 67}}, {"pk": 203, "model": "auth.permission", "fields": {"codename": "change_articlerevision", "name": "Can change article revision", "content_type": 67}}, {"pk": 204, "model": "auth.permission", "fields": {"codename": "delete_articlerevision", "name": "Can delete article revision", "content_type": 67}}, {"pk": 223, "model": "auth.permission", "fields": {"codename": "add_articlesubscription", "name": "Can add article subscription", "content_type": 74}}, {"pk": 224, "model": "auth.permission", "fields": {"codename": "change_articlesubscription", "name": "Can change article subscription", "content_type": 74}}, {"pk": 225, "model": "auth.permission", "fields": {"codename": "delete_articlesubscription", "name": "Can delete article subscription", "content_type": 74}}, {"pk": 211, "model": "auth.permission", "fields": {"codename": "add_reusableplugin", "name": "Can add reusable plugin", "content_type": 70}}, {"pk": 212, "model": "auth.permission", "fields": {"codename": "change_reusableplugin", "name": "Can change reusable plugin", "content_type": 70}}, {"pk": 213, "model": "auth.permission", "fields": {"codename": "delete_reusableplugin", "name": "Can delete reusable plugin", "content_type": 70}}, {"pk": 217, "model": "auth.permission", "fields": {"codename": "add_revisionplugin", "name": "Can add revision plugin", "content_type": 72}}, {"pk": 218, "model": "auth.permission", "fields": {"codename": "change_revisionplugin", "name": "Can change revision plugin", "content_type": 72}}, {"pk": 219, "model": "auth.permission", "fields": {"codename": "delete_revisionplugin", "name": "Can delete revision plugin", "content_type": 72}}, {"pk": 220, "model": "auth.permission", "fields": {"codename": "add_revisionpluginrevision", "name": "Can add revision plugin revision", "content_type": 73}}, {"pk": 221, "model": "auth.permission", "fields": {"codename": "change_revisionpluginrevision", "name": "Can change revision plugin revision", "content_type": 73}}, {"pk": 222, "model": "auth.permission", "fields": {"codename": "delete_revisionpluginrevision", "name": "Can delete revision plugin revision", "content_type": 73}}, {"pk": 214, "model": "auth.permission", "fields": {"codename": "add_simpleplugin", "name": "Can add simple plugin", "content_type": 71}}, {"pk": 215, "model": "auth.permission", "fields": {"codename": "change_simpleplugin", "name": "Can change simple plugin", "content_type": 71}}, {"pk": 216, "model": "auth.permission", "fields": {"codename": "delete_simpleplugin", "name": "Can delete simple plugin", "content_type": 71}}, {"pk": 205, "model": "auth.permission", "fields": {"codename": "add_urlpath", "name": "Can add URL path", "content_type": 68}}, {"pk": 206, "model": "auth.permission", "fields": {"codename": "change_urlpath", "name": "Can change URL path", "content_type": 68}}, {"pk": 207, "model": "auth.permission", "fields": {"codename": "delete_urlpath", "name": "Can delete URL path", "content_type": 68}}, {"pk": 394, "model": "auth.permission", "fields": {"codename": "add_assessmentworkflow", "name": "Can add assessment workflow", "content_type": 131}}, {"pk": 395, "model": "auth.permission", "fields": {"codename": "change_assessmentworkflow", "name": "Can change assessment workflow", "content_type": 131}}, {"pk": 396, "model": "auth.permission", "fields": {"codename": "delete_assessmentworkflow", "name": "Can delete assessment workflow", "content_type": 131}}, {"pk": 397, "model": "auth.permission", "fields": {"codename": "add_assessmentworkflowstep", "name": "Can add assessment workflow step", "content_type": 132}}, {"pk": 398, "model": "auth.permission", "fields": {"codename": "change_assessmentworkflowstep", "name": "Can change assessment workflow step", "content_type": 132}}, {"pk": 399, "model": "auth.permission", "fields": {"codename": "delete_assessmentworkflowstep", "name": "Can delete assessment workflow step", "content_type": 132}}, {"pk": 1, "model": "dark_lang.darklangconfig", "fields": {"change_date": "2014-11-13T22:49:43Z", "changed_by": null, "enabled": true, "released_languages": ""}}]
\ No newline at end of file
diff --git a/common/test/db_cache/bok_choy_schema.sql b/common/test/db_cache/bok_choy_schema.sql
index a155d8e1c0..94ceb08539 100644
--- a/common/test/db_cache/bok_choy_schema.sql
+++ b/common/test/db_cache/bok_choy_schema.sql
@@ -394,7 +394,7 @@ CREATE TABLE `auth_permission` (
UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
KEY `auth_permission_e4470c6e` (`content_type_id`),
CONSTRAINT `content_type_id_refs_id_728de91f` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=391 DEFAULT CHARSET=utf8;
+) ENGINE=InnoDB AUTO_INCREMENT=415 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `auth_registration`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@@ -666,7 +666,7 @@ CREATE TABLE `course_groups_courseusergroup` (
`course_id` varchar(255) NOT NULL,
`group_type` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
- UNIQUE KEY `name` (`name`,`course_id`),
+ UNIQUE KEY `course_groups_courseusergroup_name_63f7511804c52f38_uniq` (`name`,`course_id`),
KEY `course_groups_courseusergroup_ff48d8e5` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
@@ -678,11 +678,26 @@ CREATE TABLE `course_groups_courseusergroup_users` (
`courseusergroup_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
- UNIQUE KEY `courseusergroup_id` (`courseusergroup_id`,`user_id`),
+ UNIQUE KEY `course_groups_courseus_courseusergroup_id_46691806058983eb_uniq` (`courseusergroup_id`,`user_id`),
KEY `course_groups_courseusergroup_users_caee1c64` (`courseusergroup_id`),
KEY `course_groups_courseusergroup_users_fbfc09f1` (`user_id`),
- CONSTRAINT `courseusergroup_id_refs_id_d26180aa` FOREIGN KEY (`courseusergroup_id`) REFERENCES `course_groups_courseusergroup` (`id`),
- CONSTRAINT `user_id_refs_id_bf33b47a` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+ CONSTRAINT `user_id_refs_id_779390fdbf33b47a` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `courseusergroup_id_refs_id_43167b76d26180aa` FOREIGN KEY (`courseusergroup_id`) REFERENCES `course_groups_courseusergroup` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_groups_courseusergrouppartitiongroup`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_groups_courseusergrouppartitiongroup` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `course_user_group_id` int(11) NOT NULL,
+ `partition_id` int(11) NOT NULL,
+ `group_id` int(11) NOT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `course_user_group_id` (`course_user_group_id`),
+ CONSTRAINT `course_user_group_id_refs_id_48edc507145383c4` FOREIGN KEY (`course_user_group_id`) REFERENCES `course_groups_courseusergroup` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `course_modes_coursemode`;
@@ -887,8 +902,8 @@ CREATE TABLE `django_admin_log` (
PRIMARY KEY (`id`),
KEY `django_admin_log_fbfc09f1` (`user_id`),
KEY `django_admin_log_e4470c6e` (`content_type_id`),
- CONSTRAINT `content_type_id_refs_id_288599e6` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
- CONSTRAINT `user_id_refs_id_c8665aa` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+ CONSTRAINT `user_id_refs_id_c8665aa` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `content_type_id_refs_id_288599e6` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `django_comment_client_permission`;
@@ -950,7 +965,7 @@ CREATE TABLE `django_content_type` (
`model` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `app_label` (`app_label`,`model`)
-) ENGINE=InnoDB AUTO_INCREMENT=130 DEFAULT CHARSET=utf8;
+) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `django_openid_auth_association`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@@ -1609,6 +1624,30 @@ CREATE TABLE `shoppingcart_couponredemption` (
CONSTRAINT `user_id_refs_id_c543b145e9b8167` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shoppingcart_courseregcodeitem`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shoppingcart_courseregcodeitem` (
+ `orderitem_ptr_id` int(11) NOT NULL,
+ `course_id` varchar(128) NOT NULL,
+ `mode` varchar(50) NOT NULL,
+ PRIMARY KEY (`orderitem_ptr_id`),
+ KEY `shoppingcart_courseregcodeitem_ff48d8e5` (`course_id`),
+ KEY `shoppingcart_courseregcodeitem_4160619e` (`mode`),
+ CONSTRAINT `orderitem_ptr_id_refs_id_2ea4d9d5a466f07f` FOREIGN KEY (`orderitem_ptr_id`) REFERENCES `shoppingcart_orderitem` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shoppingcart_courseregcodeitemannotation`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shoppingcart_courseregcodeitemannotation` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `course_id` varchar(128) NOT NULL,
+ `annotation` longtext,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `course_id` (`course_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `shoppingcart_courseregistrationcode`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
@@ -1632,6 +1671,31 @@ CREATE TABLE `shoppingcart_courseregistrationcode` (
CONSTRAINT `invoice_id_refs_id_6e8c54da995f0ae8` FOREIGN KEY (`invoice_id`) REFERENCES `shoppingcart_invoice` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shoppingcart_donation`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shoppingcart_donation` (
+ `orderitem_ptr_id` int(11) NOT NULL,
+ `donation_type` varchar(32) NOT NULL,
+ `course_id` varchar(255) NOT NULL,
+ PRIMARY KEY (`orderitem_ptr_id`),
+ KEY `shoppingcart_donation_ff48d8e5` (`course_id`),
+ CONSTRAINT `orderitem_ptr_id_refs_id_28d47c0cb7138a4b` FOREIGN KEY (`orderitem_ptr_id`) REFERENCES `shoppingcart_orderitem` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shoppingcart_donationconfiguration`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shoppingcart_donationconfiguration` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `change_date` datetime NOT NULL,
+ `changed_by_id` int(11) DEFAULT NULL,
+ `enabled` tinyint(1) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `shoppingcart_donationconfiguration_16905482` (`changed_by_id`),
+ CONSTRAINT `changed_by_id_refs_id_28af5c44b4a26b7f` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `shoppingcart_invoice`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
@@ -1680,6 +1744,13 @@ CREATE TABLE `shoppingcart_order` (
`bill_to_cardtype` varchar(32) NOT NULL,
`processor_reply_dump` longtext NOT NULL,
`refunded_time` datetime,
+ `company_name` varchar(255),
+ `company_contact_name` varchar(255),
+ `company_contact_email` varchar(255),
+ `recipient_name` varchar(255),
+ `recipient_email` varchar(255),
+ `customer_reference_number` varchar(63),
+ `order_type` varchar(32) NOT NULL,
PRIMARY KEY (`id`),
KEY `shoppingcart_order_fbfc09f1` (`user_id`),
CONSTRAINT `user_id_refs_id_a4b0342e1195673` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
@@ -1702,6 +1773,8 @@ CREATE TABLE `shoppingcart_orderitem` (
`refund_requested_time` datetime,
`service_fee` decimal(30,2) NOT NULL,
`list_price` decimal(30,2),
+ `created` datetime NOT NULL,
+ `modified` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `shoppingcart_orderitem_8337030b` (`order_id`),
KEY `shoppingcart_orderitem_fbfc09f1` (`user_id`),
@@ -1816,7 +1889,7 @@ CREATE TABLE `south_migrationhistory` (
`migration` varchar(255) NOT NULL,
`applied` datetime NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=180 DEFAULT CHARSET=utf8;
+) ENGINE=InnoDB AUTO_INCREMENT=188 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `splash_splashconfig`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@@ -1903,6 +1976,20 @@ CREATE TABLE `student_courseenrollmentallowed` (
KEY `student_courseenrollmentallowed_3216ff68` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `student_dashboardconfiguration`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `student_dashboardconfiguration` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `change_date` datetime NOT NULL,
+ `changed_by_id` int(11) DEFAULT NULL,
+ `enabled` tinyint(1) NOT NULL,
+ `recent_enrollment_time_delta` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `student_dashboardconfiguration_16905482` (`changed_by_id`),
+ CONSTRAINT `changed_by_id_refs_id_31b94d88eec78c18` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `student_loginfailures`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
@@ -2083,6 +2170,38 @@ CREATE TABLE `submissions_submission` (
CONSTRAINT `student_item_id_refs_id_1df1d83e00b5cccc` FOREIGN KEY (`student_item_id`) REFERENCES `submissions_studentitem` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `survey_surveyanswer`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `survey_surveyanswer` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `created` datetime NOT NULL,
+ `modified` datetime NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `form_id` int(11) NOT NULL,
+ `field_name` varchar(255) NOT NULL,
+ `field_value` varchar(1024) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `survey_surveyanswer_fbfc09f1` (`user_id`),
+ KEY `survey_surveyanswer_1d0aabf2` (`form_id`),
+ KEY `survey_surveyanswer_7e1499` (`field_name`),
+ CONSTRAINT `form_id_refs_id_5a119e5cf4c79f29` FOREIGN KEY (`form_id`) REFERENCES `survey_surveyform` (`id`),
+ CONSTRAINT `user_id_refs_id_74dcdfa0e0ad4b5e` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `survey_surveyform`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `survey_surveyform` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `created` datetime NOT NULL,
+ `modified` datetime NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `form` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `track_trackinglog`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
diff --git a/common/test/db_cache/lettuce.db b/common/test/db_cache/lettuce.db
index adfdcd262b..fc6081441e 100644
Binary files a/common/test/db_cache/lettuce.db and b/common/test/db_cache/lettuce.db differ
diff --git a/lms/.coveragerc b/lms/.coveragerc
index 72b7b037ef..3d5d9fd0af 100644
--- a/lms/.coveragerc
+++ b/lms/.coveragerc
@@ -2,7 +2,7 @@
[run]
data_file = reports/lms/.coverage
source = lms,common/djangoapps
-omit = lms/envs/*, common/djangoapps/terrain/*, common/djangoapps/*/migrations/*
+omit = lms/envs/*, common/djangoapps/terrain/*, common/djangoapps/*/migrations/*, openedx/core/djangoapps/*/migrations/*
[report]
ignore_errors = True
diff --git a/lms/djangoapps/courseware/tests/test_split_module.py b/lms/djangoapps/courseware/tests/test_split_module.py
index 2fe280c12c..ee72589459 100644
--- a/lms/djangoapps/courseware/tests/test_split_module.py
+++ b/lms/djangoapps/courseware/tests/test_split_module.py
@@ -12,7 +12,7 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.partitions.partitions import Group, UserPartition
-from user_api.tests.factories import UserCourseTagFactory
+from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
diff --git a/lms/djangoapps/courseware/tests/test_submitting_problems.py b/lms/djangoapps/courseware/tests/test_submitting_problems.py
index a51aaa6ad5..904204764b 100644
--- a/lms/djangoapps/courseware/tests/test_submitting_problems.py
+++ b/lms/djangoapps/courseware/tests/test_submitting_problems.py
@@ -27,7 +27,7 @@ from student.models import anonymous_id_for_user
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.partitions.partitions import Group, UserPartition
-from user_api.tests.factories import UserCourseTagFactory
+from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
diff --git a/lms/djangoapps/django_comment_client/forum/tests.py b/lms/djangoapps/django_comment_client/forum/tests.py
index d5df8b16db..1647fb85c4 100644
--- a/lms/djangoapps/django_comment_client/forum/tests.py
+++ b/lms/djangoapps/django_comment_client/forum/tests.py
@@ -6,11 +6,7 @@ from django.http import Http404
from django.test.client import Client, RequestFactory
from django.test.utils import override_settings
from edxmako.tests import mako_middleware_process_request
-from mock import patch, Mock, ANY, call
-from nose.tools import assert_true # pylint: disable=no-name-in-module
-from course_groups.models import CourseUserGroup
-from courseware.courses import UserNotEnrolled
from django_comment_client.forum import views
from django_comment_client.tests.group_id import (
CohortedTopicGroupIdTestMixin,
@@ -21,10 +17,15 @@ from django_comment_client.tests.utils import CohortedContentTestCase
from django_comment_client.utils import strip_none
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from util.testing import UrlResetMixin
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DATA_MOCK_MODULESTORE
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+from courseware.courses import UserNotEnrolled
+from nose.tools import assert_true # pylint: disable=E0611
+from mock import patch, Mock, ANY, call
+
+from openedx.core.djangoapps.course_groups.models import CourseUserGroup
+
log = logging.getLogger(__name__)
# pylint: disable=missing-docstring
diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py
index 04c7e505eb..339a4112ef 100644
--- a/lms/djangoapps/django_comment_client/forum/views.py
+++ b/lms/djangoapps/django_comment_client/forum/views.py
@@ -11,7 +11,12 @@ import newrelic.agent
from edxmako.shortcuts import render_to_response
from courseware.courses import get_course_with_access
-from course_groups.cohorts import is_course_cohorted, get_cohort_id, get_course_cohorts, is_commentable_cohorted
+from openedx.core.djangoapps.course_groups.cohorts import (
+ is_course_cohorted,
+ get_cohort_id,
+ get_course_cohorts,
+ is_commentable_cohorted
+)
from courseware.access import has_access
from django_comment_client.permissions import cached_has_permission
diff --git a/lms/djangoapps/django_comment_client/tests/group_id.py b/lms/djangoapps/django_comment_client/tests/group_id.py
index 943a257ef4..24f0b15fda 100644
--- a/lms/djangoapps/django_comment_client/tests/group_id.py
+++ b/lms/djangoapps/django_comment_client/tests/group_id.py
@@ -1,8 +1,6 @@
import json
import re
-from course_groups.models import CourseUserGroup
-
class GroupIdAssertionMixin(object):
def _data_or_params_cs_request(self, mock_request):
diff --git a/lms/djangoapps/django_comment_client/tests/utils.py b/lms/djangoapps/django_comment_client/tests/utils.py
index 8afde0a021..2d8d751eb3 100644
--- a/lms/djangoapps/django_comment_client/tests/utils.py
+++ b/lms/djangoapps/django_comment_client/tests/utils.py
@@ -1,7 +1,7 @@
from django.test.utils import override_settings
from mock import patch
-from course_groups.models import CourseUserGroup
+from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
from django_comment_common.models import Role
from django_comment_common.utils import seed_permissions_roles
diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py
index bf88120ffa..3044f80535 100644
--- a/lms/djangoapps/django_comment_client/utils.py
+++ b/lms/djangoapps/django_comment_client/utils.py
@@ -17,8 +17,8 @@ from django_comment_client.permissions import check_permissions_by_view, cached_
from edxmako import lookup_template
import pystache_custom as pystache
-from course_groups.cohorts import get_cohort_by_id, get_cohort_id, is_commentable_cohorted, is_course_cohorted
-from course_groups.models import CourseUserGroup
+from openedx.core.djangoapps.course_groups.cohorts import get_cohort_by_id, get_cohort_id, is_commentable_cohorted, is_course_cohorted
+from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from opaque_keys.edx.locations import i4xEncoder
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 60caded8b5..a5efa2b738 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -62,7 +62,7 @@ import instructor_analytics.basic
import instructor_analytics.distributions
import instructor_analytics.csvs
import csv
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from instructor.views import INVOICE_KEY
from submissions import api as sub_api # installed from the edx-submissions repository
diff --git a/lms/djangoapps/instructor_analytics/tests/test_basic.py b/lms/djangoapps/instructor_analytics/tests/test_basic.py
index 997311b014..c933e7e343 100644
--- a/lms/djangoapps/instructor_analytics/tests/test_basic.py
+++ b/lms/djangoapps/instructor_analytics/tests/test_basic.py
@@ -2,7 +2,6 @@
Tests for instructor.basic
"""
-from django.test import TestCase
from student.models import CourseEnrollment
from django.core.urlresolvers import reverse
from mock import patch
@@ -17,12 +16,10 @@ from instructor_analytics.basic import (
sale_record_features, sale_order_record_features, enrolled_students_features, course_registration_features,
coupon_codes_features, AVAILABLE_FEATURES, STUDENT_FEATURES, PROFILE_FEATURES
)
-from course_groups.tests.helpers import CohortFactory
-from course_groups.models import CourseUserGroup
+from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
from courseware.tests.factories import InstructorFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class TestAnalyticsBasic(ModuleStoreTestCase):
diff --git a/lms/djangoapps/instructor_task/tests/test_integration.py b/lms/djangoapps/instructor_task/tests/test_integration.py
index 5d386ddb92..eee8f124d3 100644
--- a/lms/djangoapps/instructor_task/tests/test_integration.py
+++ b/lms/djangoapps/instructor_task/tests/test_integration.py
@@ -17,7 +17,7 @@ from django.core.urlresolvers import reverse
from capa.tests.response_xml_factory import (CodeResponseXMLFactory,
CustomResponseXMLFactory)
-from user_api.tests.factories import UserCourseTagFactory
+from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
from xmodule.modulestore.tests.factories import ItemFactory
from xmodule.modulestore import ModuleStoreEnum
from xmodule.partitions.partitions import Group, UserPartition
diff --git a/lms/djangoapps/notification_prefs/features/unsubscribe.py b/lms/djangoapps/notification_prefs/features/unsubscribe.py
index 1eaab95177..987211b0cd 100644
--- a/lms/djangoapps/notification_prefs/features/unsubscribe.py
+++ b/lms/djangoapps/notification_prefs/features/unsubscribe.py
@@ -1,7 +1,7 @@
from django.contrib.auth.models import User
from lettuce import step, world
from notification_prefs import NOTIFICATION_PREF_KEY
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
USERNAME = "robot"
diff --git a/lms/djangoapps/notification_prefs/tests.py b/lms/djangoapps/notification_prefs/tests.py
index 3206dfaabc..91e84b063c 100644
--- a/lms/djangoapps/notification_prefs/tests.py
+++ b/lms/djangoapps/notification_prefs/tests.py
@@ -12,7 +12,7 @@ from notification_prefs import NOTIFICATION_PREF_KEY
from notification_prefs.views import ajax_enable, ajax_disable, ajax_status, set_subscription, UsernameCipher
from student.tests.factories import UserFactory
from edxmako.tests import mako_middleware_process_request
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from util.testing import UrlResetMixin
diff --git a/lms/djangoapps/notification_prefs/views.py b/lms/djangoapps/notification_prefs/views.py
index eef24c5ee4..3cada57beb 100644
--- a/lms/djangoapps/notification_prefs/views.py
+++ b/lms/djangoapps/notification_prefs/views.py
@@ -12,7 +12,7 @@ from django.views.decorators.http import require_GET, require_POST
from edxmako.shortcuts import render_to_response
from notification_prefs import NOTIFICATION_PREF_KEY
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
class UsernameDecryptionException(Exception):
diff --git a/lms/djangoapps/notifier_api/serializers.py b/lms/djangoapps/notifier_api/serializers.py
index 5164ad812f..a3877f1e5b 100644
--- a/lms/djangoapps/notifier_api/serializers.py
+++ b/lms/djangoapps/notifier_api/serializers.py
@@ -2,7 +2,7 @@ from django.contrib.auth.models import User
from django.http import Http404
from rest_framework import serializers
-from course_groups.cohorts import is_course_cohorted
+from openedx.core.djangoapps.course_groups.cohorts import is_course_cohorted
from notification_prefs import NOTIFICATION_PREF_KEY
from lang_pref import LANGUAGE_KEY
diff --git a/lms/djangoapps/notifier_api/tests.py b/lms/djangoapps/notifier_api/tests.py
index 5005781342..4d040d3564 100644
--- a/lms/djangoapps/notifier_api/tests.py
+++ b/lms/djangoapps/notifier_api/tests.py
@@ -5,7 +5,7 @@ from django.conf import settings
from django.test.client import RequestFactory
from django.test.utils import override_settings
-from course_groups.models import CourseUserGroup
+from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from django_comment_common.models import Role, Permission
from lang_pref import LANGUAGE_KEY
from notification_prefs import NOTIFICATION_PREF_KEY
@@ -13,8 +13,8 @@ from notifier_api.views import NotifierUsersViewSet
from opaque_keys.edx.locator import CourseLocator
from student.models import CourseEnrollment
from student.tests.factories import UserFactory, CourseEnrollmentFactory
-from user_api.models import UserPreference
-from user_api.tests.factories import UserPreferenceFactory
+from openedx.core.djangoapps.user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.tests.factories import UserPreferenceFactory
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
diff --git a/lms/djangoapps/notifier_api/views.py b/lms/djangoapps/notifier_api/views.py
index 84a38639a9..9be79b0717 100644
--- a/lms/djangoapps/notifier_api/views.py
+++ b/lms/djangoapps/notifier_api/views.py
@@ -3,7 +3,7 @@ from rest_framework.viewsets import ReadOnlyModelViewSet
from notification_prefs import NOTIFICATION_PREF_KEY
from notifier_api.serializers import NotifierUserSerializer
-from user_api.views import ApiKeyHeaderPermission
+from openedx.core.djangoapps.user_api.views import ApiKeyHeaderPermission
class NotifierUsersViewSet(ReadOnlyModelViewSet):
diff --git a/lms/djangoapps/oauth2_handler/handlers.py b/lms/djangoapps/oauth2_handler/handlers.py
index b44ab7403f..fe1957bbee 100644
--- a/lms/djangoapps/oauth2_handler/handlers.py
+++ b/lms/djangoapps/oauth2_handler/handlers.py
@@ -6,7 +6,7 @@ from django.core.cache import cache
from courseware.access import has_access
from student.models import anonymous_id_for_user
from student.models import UserProfile
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
from xmodule.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor
diff --git a/lms/djangoapps/oauth2_handler/tests.py b/lms/djangoapps/oauth2_handler/tests.py
index 36d7fbfd49..b1676e948f 100644
--- a/lms/djangoapps/oauth2_handler/tests.py
+++ b/lms/djangoapps/oauth2_handler/tests.py
@@ -9,7 +9,7 @@ from student.models import anonymous_id_for_user
from student.models import UserProfile
from student.roles import CourseStaffRole, CourseInstructorRole
from student.tests.factories import UserFactory, UserProfileFactory
-from user_api.models import UserPreference
+from openedx.core.djangoapps.user_api.models import UserPreference
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
# Will also run default tests for IDTokens and UserInfo
diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py
index b293292f3f..d315f89dda 100644
--- a/lms/djangoapps/student_account/test/test_views.py
+++ b/lms/djangoapps/student_account/test/test_views.py
@@ -16,9 +16,8 @@ from django.test.utils import override_settings
from util.testing import UrlResetMixin
from third_party_auth.tests.testutil import simulate_running_pipeline
-from user_api.api import account as account_api
-from user_api.api import profile as profile_api
-from util.bad_request_rate_limiter import BadRequestRateLimiter
+from openedx.core.djangoapps.user_api.api import account as account_api
+from openedx.core.djangoapps.user_api.api import profile as profile_api
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, mixed_store_config
)
diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py
index 5239f8f27a..646113633d 100644
--- a/lms/djangoapps/student_account/views.py
+++ b/lms/djangoapps/student_account/views.py
@@ -16,8 +16,8 @@ from edxmako.shortcuts import render_to_response, render_to_string
from microsite_configuration import microsite
import third_party_auth
-from user_api.api import account as account_api
-from user_api.api import profile as profile_api
+from openedx.core.djangoapps.user_api.api import account as account_api
+from openedx.core.djangoapps.user_api.api import profile as profile_api
from util.bad_request_rate_limiter import BadRequestRateLimiter
from student_account.helpers import auth_pipeline_urls
diff --git a/lms/djangoapps/student_profile/test/test_views.py b/lms/djangoapps/student_profile/test/test_views.py
index c24bd0ea0a..6785796ded 100644
--- a/lms/djangoapps/student_profile/test/test_views.py
+++ b/lms/djangoapps/student_profile/test/test_views.py
@@ -11,8 +11,8 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from util.testing import UrlResetMixin
-from user_api.api import account as account_api
-from user_api.api import profile as profile_api
+from openedx.core.djangoapps.user_api.api import account as account_api
+from openedx.core.djangoapps.user_api.api import profile as profile_api
from lang_pref import LANGUAGE_KEY, api as language_api
diff --git a/lms/djangoapps/student_profile/views.py b/lms/djangoapps/student_profile/views.py
index a7a6a3c0d0..51fac70c3e 100644
--- a/lms/djangoapps/student_profile/views.py
+++ b/lms/djangoapps/student_profile/views.py
@@ -11,7 +11,7 @@ from django.views.decorators.http import require_http_methods
from django_future.csrf import ensure_csrf_cookie
from django.contrib.auth.decorators import login_required
from edxmako.shortcuts import render_to_response
-from user_api.api import profile as profile_api
+from openedx.core.djangoapps.user_api.api import profile as profile_api
from lang_pref import LANGUAGE_KEY, api as language_api
import third_party_auth
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 6b260e44c0..c2fe202778 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -931,7 +931,7 @@ MIDDLEWARE_CLASSES = (
# Adds user tags to tracking events
# Must go before TrackMiddleware, to get the context set up
- 'user_api.middleware.UserTagsEventContextMiddleware',
+ 'openedx.core.djangoapps.user_api.middleware.UserTagsEventContextMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'track.middleware.TrackMiddleware',
@@ -1438,7 +1438,7 @@ INSTALLED_APPS = (
'open_ended_grading',
'psychometrics',
'licenses',
- 'course_groups',
+ 'openedx.core.djangoapps.course_groups',
'bulk_email',
# External auth (OpenID, shib)
@@ -1482,7 +1482,7 @@ INSTALLED_APPS = (
# User API
'rest_framework',
- 'user_api',
+ 'openedx.core.djangoapps.user_api',
# Shopping cart
'shoppingcart',
diff --git a/lms/lib/xblock/mixin.py b/lms/lib/xblock/mixin.py
index 08e965b922..a3055d0437 100644
--- a/lms/lib/xblock/mixin.py
+++ b/lms/lib/xblock/mixin.py
@@ -1,7 +1,9 @@
"""
Namespace that defines fields common to all blocks used in the LMS
"""
-from xblock.fields import Boolean, Scope, String, XBlockMixin
+from xblock.fields import Boolean, Scope, String, XBlockMixin, Dict
+from xblock.validation import ValidationMessage
+from xmodule.modulestore.inheritance import UserPartitionList
# Make '_' a no-op so we can scrape strings
_ = lambda text: text
@@ -53,3 +55,73 @@ class LmsBlockMixin(XBlockMixin):
default=False,
scope=Scope.settings,
)
+ group_access = Dict(
+ help="A dictionary that maps which groups can be shown this block. The keys "
+ "are group configuration ids and the values are a list of group IDs. "
+ "If there is no key for a group configuration or if the list of group IDs "
+ "is empty then the block is considered visible to all. Note that this "
+ "field is ignored if the block is visible_to_staff_only.",
+ default={},
+ scope=Scope.settings,
+ )
+
+ # Specified here so we can see what the value set at the course-level is.
+ user_partitions = UserPartitionList(
+ help=_("The list of group configurations for partitioning students in content experiments."),
+ default=[],
+ scope=Scope.settings
+ )
+
+ def _get_user_partition(self, user_partition_id):
+ """
+ Returns the user partition with the specified id, or None if there is no such partition.
+ """
+ for user_partition in self.user_partitions:
+ if user_partition.id == user_partition_id:
+ return user_partition
+
+ return None
+
+ def is_visible_to_group(self, user_partition, group):
+ """
+ Returns true if this xblock should be shown to a user in the specified user partition group.
+ This method returns true if one of the following is true:
+ - the xblock has no group_access dictionary specified
+ - if the dictionary has no key for the user partition's id
+ - if the value for the user partition's id is an empty list
+ - if the value for the user partition's id contains the specified group's id
+ """
+ if not self.group_access:
+ return True
+ group_ids = self.group_access.get(user_partition.id, [])
+ if len(group_ids) == 0:
+ return True
+ return group.id in group_ids
+
+ def validate(self):
+ """
+ Validates the state of this xblock instance.
+ """
+ _ = self.runtime.service(self, "i18n").ugettext # pylint: disable=redefined-outer-name
+ validation = super(LmsBlockMixin, self).validate()
+ for user_partition_id, group_ids in self.group_access.iteritems():
+ user_partition = self._get_user_partition(user_partition_id)
+ if not user_partition:
+ validation.add(
+ ValidationMessage(
+ ValidationMessage.ERROR,
+ _(u"This xblock refers to a deleted or invalid content group configuration.")
+ )
+ )
+ else:
+ for group_id in group_ids:
+ group = user_partition.get_group(group_id)
+ if not group:
+ validation.add(
+ ValidationMessage(
+ ValidationMessage.ERROR,
+ _(u"This xblock refers to a deleted or invalid content group.")
+ )
+ )
+
+ return validation
diff --git a/lms/lib/xblock/runtime.py b/lms/lib/xblock/runtime.py
index 2c1729ec49..f96a157acf 100644
--- a/lms/lib/xblock/runtime.py
+++ b/lms/lib/xblock/runtime.py
@@ -7,7 +7,7 @@ import xblock.reference.plugins
from django.core.urlresolvers import reverse
from django.conf import settings
-from user_api.api import course_tag as user_course_tag_api
+from openedx.core.djangoapps.user_api.api import course_tag as user_course_tag_api
from xmodule.modulestore.django import modulestore
from xmodule.x_module import ModuleSystem
from xmodule.partitions.partitions_service import PartitionService
@@ -128,13 +128,13 @@ class LmsPartitionService(PartitionService):
course.
(If and when XBlock directly provides access from one block (e.g. a split_test_module)
- to another (e.g. a course_module), this won't be neccessary, but for now it seems like
+ to another (e.g. a course_module), this won't be necessary, but for now it seems like
the least messy way to hook things through)
"""
@property
def course_partitions(self):
- course = modulestore().get_course(self._course_id)
+ course = modulestore().get_course(self.runtime.course_id)
return course.user_partitions
@@ -194,8 +194,7 @@ class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract
services = kwargs.setdefault('services', {})
services['user_tags'] = UserTagsService(self)
services['partitions'] = LmsPartitionService(
- user_tags_service=services['user_tags'],
- course_id=kwargs.get('course_id', None),
+ runtime=self,
track_function=kwargs.get('track_function', None),
)
services['fs'] = xblock.reference.plugins.FSService()
diff --git a/lms/lib/xblock/test/test_mixin.py b/lms/lib/xblock/test/test_mixin.py
new file mode 100644
index 0000000000..eeb0d8e680
--- /dev/null
+++ b/lms/lib/xblock/test/test_mixin.py
@@ -0,0 +1,123 @@
+"""
+Tests of the LMS XBlock Mixin
+"""
+
+from xblock.validation import ValidationMessage
+from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from xmodule.partitions.partitions import Group, UserPartition
+
+
+class LmsXBlockMixinTestCase(ModuleStoreTestCase):
+ """
+ Base class for XBlock mixin tests cases. A simple course with a single user partition is created
+ in setUp for all subclasses to use.
+ """
+
+ def setUp(self):
+ super(LmsXBlockMixinTestCase, self).setUp()
+ self.user_partition = UserPartition(
+ 0,
+ 'first_partition',
+ 'First Partition',
+ [
+ Group(0, 'alpha'),
+ Group(1, 'beta')
+ ]
+ )
+ self.group1 = self.user_partition.groups[0] # pylint: disable=no-member
+ self.group2 = self.user_partition.groups[1] # pylint: disable=no-member
+ self.course = CourseFactory.create(user_partitions=[self.user_partition])
+ self.section = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
+ self.subsection = ItemFactory.create(parent=self.section, category='sequential', display_name='Test Subsection')
+ self.vertical = ItemFactory.create(parent=self.subsection, category='vertical', display_name='Test Unit')
+ self.video = ItemFactory.create(parent=self.subsection, category='video', display_name='Test Video')
+
+
+class XBlockValidationTest(LmsXBlockMixinTestCase):
+ """
+ Unit tests for XBlock validation
+ """
+
+ def verify_validation_message(self, message, expected_message, expected_message_type):
+ """
+ Verify that the validation message has the expected validation message and type.
+ """
+ self.assertEqual(message.text, expected_message)
+ self.assertEqual(message.type, expected_message_type)
+
+ def test_validate_full_group_access(self):
+ """
+ Test the validation messages produced for an xblock with full group access.
+ """
+ validation = self.video.validate()
+ self.assertEqual(len(validation.messages), 0)
+
+ def test_validate_restricted_group_access(self):
+ """
+ Test the validation messages produced for an xblock with a valid group access restriction
+ """
+ self.video.group_access[self.user_partition.id] = [self.group1.id, self.group2.id] # pylint: disable=no-member
+ validation = self.video.validate()
+ self.assertEqual(len(validation.messages), 0)
+
+ def test_validate_invalid_user_partition(self):
+ """
+ Test the validation messages produced for an xblock referring to a non-existent user partition.
+ """
+ self.video.group_access[999] = [self.group1.id]
+ validation = self.video.validate()
+ self.assertEqual(len(validation.messages), 1)
+ self.verify_validation_message(
+ validation.messages[0],
+ u"This xblock refers to a deleted or invalid content group configuration.",
+ ValidationMessage.ERROR,
+ )
+
+ def test_validate_invalid_group(self):
+ """
+ Test the validation messages produced for an xblock referring to a non-existent group.
+ """
+ self.video.group_access[self.user_partition.id] = [self.group1.id, 999] # pylint: disable=no-member
+ validation = self.video.validate()
+ self.assertEqual(len(validation.messages), 1)
+ self.verify_validation_message(
+ validation.messages[0],
+ u"This xblock refers to a deleted or invalid content group.",
+ ValidationMessage.ERROR,
+ )
+
+
+class XBlockGroupAccessTest(LmsXBlockMixinTestCase):
+ """
+ Unit tests for XBlock group access.
+ """
+
+ def test_is_visible_to_group(self):
+ """
+ Test the behavior of is_visible_to_group.
+ """
+ # All groups are visible for an unrestricted xblock
+ self.assertTrue(self.video.is_visible_to_group(self.user_partition, self.group1))
+ self.assertTrue(self.video.is_visible_to_group(self.user_partition, self.group2))
+
+ # Verify that all groups are visible if the set of group ids is empty
+ self.video.group_access[self.user_partition.id] = [] # pylint: disable=no-member
+ self.assertTrue(self.video.is_visible_to_group(self.user_partition, self.group1))
+ self.assertTrue(self.video.is_visible_to_group(self.user_partition, self.group2))
+
+ # Verify that only specified groups are visible
+ self.video.group_access[self.user_partition.id] = [self.group1.id] # pylint: disable=no-member
+ self.assertTrue(self.video.is_visible_to_group(self.user_partition, self.group1))
+ self.assertFalse(self.video.is_visible_to_group(self.user_partition, self.group2))
+
+ # Verify that having an invalid user partition does not affect group visibility of other partitions
+ self.video.group_access[999] = [self.group1.id]
+ self.assertTrue(self.video.is_visible_to_group(self.user_partition, self.group1))
+ self.assertFalse(self.video.is_visible_to_group(self.user_partition, self.group2))
+
+ # Verify that group access is still correct even with invalid group ids
+ self.video.group_access.clear()
+ self.video.group_access[self.user_partition.id] = [self.group2.id, 999] # pylint: disable=no-member
+ self.assertFalse(self.video.is_visible_to_group(self.user_partition, self.group1))
+ self.assertTrue(self.video.is_visible_to_group(self.user_partition, self.group2))
diff --git a/lms/urls.py b/lms/urls.py
index bd25d6c9ca..8a5e20a966 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -60,7 +60,7 @@ urlpatterns = ('', # nopep8
url(r'^heartbeat$', include('heartbeat.urls')),
- url(r'^user_api/', include('user_api.urls')),
+ url(r'^user_api/', include('openedx.core.djangoapps.user_api.urls')),
url(r'^notifier_api/', include('notifier_api.urls')),
@@ -343,21 +343,21 @@ if settings.COURSEWARE_ENABLED:
# Cohorts management
url(r'^courses/{}/cohorts$'.format(settings.COURSE_KEY_PATTERN),
- 'course_groups.views.list_cohorts', name="cohorts"),
+ 'openedx.core.djangoapps.course_groups.views.list_cohorts', name="cohorts"),
url(r'^courses/{}/cohorts/add$'.format(settings.COURSE_KEY_PATTERN),
- 'course_groups.views.add_cohort',
+ 'openedx.core.djangoapps.course_groups.views.add_cohort',
name="add_cohort"),
url(r'^courses/{}/cohorts/(?P[0-9]+)$'.format(settings.COURSE_KEY_PATTERN),
- 'course_groups.views.users_in_cohort',
+ 'openedx.core.djangoapps.course_groups.views.users_in_cohort',
name="list_cohort"),
url(r'^courses/{}/cohorts/(?P[0-9]+)/add$'.format(settings.COURSE_KEY_PATTERN),
- 'course_groups.views.add_users_to_cohort',
+ 'openedx.core.djangoapps.course_groups.views.add_users_to_cohort',
name="add_to_cohort"),
url(r'^courses/{}/cohorts/(?P[0-9]+)/delete$'.format(settings.COURSE_KEY_PATTERN),
- 'course_groups.views.remove_user_from_cohort',
+ 'openedx.core.djangoapps.course_groups.views.remove_user_from_cohort',
name="remove_from_cohort"),
url(r'^courses/{}/cohorts/debug$'.format(settings.COURSE_KEY_PATTERN),
- 'course_groups.views.debug_cohort_mgmt',
+ 'openedx.core.djangoapps.course_groups.views.debug_cohort_mgmt',
name="debug_cohort_mgmt"),
# Open Ended Notifications
diff --git a/openedx/__init__.py b/openedx/__init__.py
new file mode 100644
index 0000000000..f3d7c7b8a8
--- /dev/null
+++ b/openedx/__init__.py
@@ -0,0 +1,9 @@
+"""
+This is the root package for Open edX. The intent is that all importable code
+from Open edX will eventually live here, including the code in the lms, cms,
+and common directories.
+
+Note: for now the code is not structured like this, and hence legacy code will
+continue to live in a number of different packages. All new code should be
+created in this package, and the legacy code will be moved here gradually.
+"""
diff --git a/openedx/core/__init__.py b/openedx/core/__init__.py
new file mode 100644
index 0000000000..fa1c85b3ad
--- /dev/null
+++ b/openedx/core/__init__.py
@@ -0,0 +1,9 @@
+"""
+This is the root package for all core Open edX functionality. In particular,
+the djangoapps subpackage is the location for all Django apps that are shared
+between LMS and CMS.
+
+Note: the majority of the core functionality currently lives in the root
+common directory. All new Django apps should be created here instead, and
+the pre-existing apps will be moved here over time.
+"""
diff --git a/common/djangoapps/course_groups/tests/__init__.py b/openedx/core/djangoapps/__init__.py
similarity index 100%
rename from common/djangoapps/course_groups/tests/__init__.py
rename to openedx/core/djangoapps/__init__.py
diff --git a/common/djangoapps/user_api/__init__.py b/openedx/core/djangoapps/course_groups/__init__.py
similarity index 100%
rename from common/djangoapps/user_api/__init__.py
rename to openedx/core/djangoapps/course_groups/__init__.py
diff --git a/common/djangoapps/course_groups/cohorts.py b/openedx/core/djangoapps/course_groups/cohorts.py
similarity index 95%
rename from common/djangoapps/course_groups/cohorts.py
rename to openedx/core/djangoapps/course_groups/cohorts.py
index d10c6309dc..958c81ee23 100644
--- a/common/djangoapps/course_groups/cohorts.py
+++ b/openedx/core/djangoapps/course_groups/cohorts.py
@@ -14,7 +14,7 @@ from django.utils.translation import ugettext as _
from courseware import courses
from eventtracking import tracker
from student.models import get_user_by_username_or_email
-from .models import CourseUserGroup
+from .models import CourseUserGroup, CourseUserGroupPartitionGroup
log = logging.getLogger(__name__)
@@ -373,3 +373,17 @@ def add_user_to_cohort(cohort, username_or_email):
)
cohort.users.add(user)
return (user, previous_cohort_name)
+
+
+def get_partition_group_id_for_cohort(cohort):
+ """
+ Get the ids of the partition and group to which this cohort has been linked
+ as a tuple of (int, int).
+
+ If the cohort has not been linked to any partition/group, both values in the
+ tuple will be None.
+ """
+ res = CourseUserGroupPartitionGroup.objects.filter(course_user_group=cohort)
+ if len(res):
+ return res[0].partition_id, res[0].group_id
+ return None, None
diff --git a/openedx/core/djangoapps/course_groups/migrations/0001_initial.py b/openedx/core/djangoapps/course_groups/migrations/0001_initial.py
new file mode 100644
index 0000000000..ca68d6628f
--- /dev/null
+++ b/openedx/core/djangoapps/course_groups/migrations/0001_initial.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models, connection
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'CourseUserGroup'
+
+ def table_exists(name):
+ return name in connection.introspection.table_names()
+
+ def index_exists(table_name, column_name):
+ return column_name in connection.introspection.get_indexes(connection.cursor(), table_name)
+
+ # Since this djangoapp has been converted to south migrations after-the-fact,
+ # these tables/indexes should already exist when migrating an existing installation.
+ if not (
+ table_exists('course_groups_courseusergroup') and
+ index_exists('course_groups_courseusergroup', 'name') and
+ index_exists('course_groups_courseusergroup', 'course_id') and
+ table_exists('course_groups_courseusergroup_users') and
+ index_exists('course_groups_courseusergroup_users', 'courseusergroup_id') and
+ index_exists('course_groups_courseusergroup_users', 'user_id')
+ ):
+ db.create_table('course_groups_courseusergroup', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)),
+ ('group_type', self.gf('django.db.models.fields.CharField')(max_length=20)),
+ ))
+ db.send_create_signal('course_groups', ['CourseUserGroup'])
+
+ # Adding unique constraint on 'CourseUserGroup', fields ['name', 'course_id']
+ db.create_unique('course_groups_courseusergroup', ['name', 'course_id'])
+
+ # Adding M2M table for field users on 'CourseUserGroup'
+ db.create_table('course_groups_courseusergroup_users', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('courseusergroup', models.ForeignKey(orm['course_groups.courseusergroup'], null=False)),
+ ('user', models.ForeignKey(orm['auth.user'], null=False))
+ ))
+ db.create_unique('course_groups_courseusergroup_users', ['courseusergroup_id', 'user_id'])
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'CourseUserGroup', fields ['name', 'course_id']
+ db.delete_unique('course_groups_courseusergroup', ['name', 'course_id'])
+
+ # Deleting model 'CourseUserGroup'
+ db.delete_table('course_groups_courseusergroup')
+
+ # Removing M2M table for field users on 'CourseUserGroup'
+ db.delete_table('course_groups_courseusergroup_users')
+
+ 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_groups.courseusergroup': {
+ 'Meta': {'unique_together': "(('name', 'course_id'),)", 'object_name': 'CourseUserGroup'},
+ 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'group_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'related_name': "'course_groups'", 'symmetrical': 'False', 'to': "orm['auth.User']"})
+ }
+ }
+
+ complete_apps = ['course_groups']
diff --git a/openedx/core/djangoapps/course_groups/migrations/0002_add_model_CourseUserGroupPartitionGroup.py b/openedx/core/djangoapps/course_groups/migrations/0002_add_model_CourseUserGroupPartitionGroup.py
new file mode 100644
index 0000000000..735dbde78b
--- /dev/null
+++ b/openedx/core/djangoapps/course_groups/migrations/0002_add_model_CourseUserGroupPartitionGroup.py
@@ -0,0 +1,82 @@
+# -*- 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 'CourseUserGroupPartitionGroup'
+ db.create_table('course_groups_courseusergrouppartitiongroup', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('course_user_group', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['course_groups.CourseUserGroup'], unique=True)),
+ ('partition_id', self.gf('django.db.models.fields.IntegerField')()),
+ ('group_id', self.gf('django.db.models.fields.IntegerField')()),
+ ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+ ))
+ db.send_create_signal('course_groups', ['CourseUserGroupPartitionGroup'])
+
+ def backwards(self, orm):
+ # Deleting model 'CourseUserGroupPartitionGroup'
+ db.delete_table('course_groups_courseusergrouppartitiongroup')
+
+ 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_groups.courseusergroup': {
+ 'Meta': {'unique_together': "(('name', 'course_id'),)", 'object_name': 'CourseUserGroup'},
+ 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'group_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'users': ('django.db.models.fields.related.ManyToManyField', [], {'db_index': 'True', 'related_name': "'course_groups'", 'symmetrical': 'False', 'to': "orm['auth.User']"})
+ },
+ 'course_groups.courseusergrouppartitiongroup': {
+ 'Meta': {'object_name': 'CourseUserGroupPartitionGroup'},
+ 'course_user_group': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['course_groups.CourseUserGroup']", 'unique': 'True'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'group_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'partition_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['course_groups']
diff --git a/common/djangoapps/user_api/api/__init__.py b/openedx/core/djangoapps/course_groups/migrations/__init__.py
similarity index 100%
rename from common/djangoapps/user_api/api/__init__.py
rename to openedx/core/djangoapps/course_groups/migrations/__init__.py
diff --git a/common/djangoapps/course_groups/models.py b/openedx/core/djangoapps/course_groups/models.py
similarity index 75%
rename from common/djangoapps/course_groups/models.py
rename to openedx/core/djangoapps/course_groups/models.py
index aae9f40604..da17f4c20c 100644
--- a/common/djangoapps/course_groups/models.py
+++ b/openedx/core/djangoapps/course_groups/models.py
@@ -35,3 +35,17 @@ class CourseUserGroup(models.Model):
COHORT = 'cohort'
GROUP_TYPE_CHOICES = ((COHORT, 'Cohort'),)
group_type = models.CharField(max_length=20, choices=GROUP_TYPE_CHOICES)
+
+
+class CourseUserGroupPartitionGroup(models.Model):
+ """
+ """
+ course_user_group = models.OneToOneField(CourseUserGroup)
+ partition_id = models.IntegerField(
+ help_text="contains the id of a cohorted partition in this course"
+ )
+ group_id = models.IntegerField(
+ help_text="contains the id of a specific group within the cohorted partition"
+ )
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
diff --git a/openedx/core/djangoapps/course_groups/partition_scheme.py b/openedx/core/djangoapps/course_groups/partition_scheme.py
new file mode 100644
index 0000000000..cc36c14373
--- /dev/null
+++ b/openedx/core/djangoapps/course_groups/partition_scheme.py
@@ -0,0 +1,75 @@
+"""
+Provides a UserPartition driver for cohorts.
+"""
+import logging
+
+from .cohorts import get_cohort, get_partition_group_id_for_cohort
+
+log = logging.getLogger(__name__)
+
+
+class CohortPartitionScheme(object):
+ """
+ This scheme uses lms cohorts (CourseUserGroups) and cohort-partition
+ mappings (CourseUserGroupPartitionGroup) to map lms users into Partition
+ Groups.
+ """
+
+ @classmethod
+ def get_group_for_user(cls, course_id, user, user_partition, track_function=None):
+ """
+ Returns the Group from the specified user partition to which the user
+ is assigned, via their cohort membership and any mappings from cohorts
+ to partitions / groups that might exist.
+
+ If the user has not yet been assigned to a cohort, an assignment *might*
+ be created on-the-fly, as determined by the course's cohort config.
+ Any such side-effects will be triggered inside the call to
+ cohorts.get_cohort().
+
+ If the user has no cohort mapping, or there is no (valid) cohort ->
+ partition group mapping found, the function returns None.
+ """
+ cohort = get_cohort(user, course_id)
+ if cohort is None:
+ # student doesn't have a cohort
+ return None
+
+ partition_id, group_id = get_partition_group_id_for_cohort(cohort)
+ if partition_id is None:
+ # cohort isn't mapped to any partition group.
+ return None
+
+ if partition_id != user_partition.id:
+ # if we have a match but the partition doesn't match the requested
+ # one it means the mapping is invalid. the previous state of the
+ # partition configuration may have been modified.
+ log.warn(
+ "partition mismatch in CohortPartitionScheme: %r",
+ {
+ "requested_partition_id": user_partition.id,
+ "found_partition_id": partition_id,
+ "found_group_id": group_id,
+ "cohort_id": cohort.id,
+ }
+ )
+ # fail silently
+ return None
+
+ group = user_partition.get_group(group_id)
+ if group is None:
+ # if we have a match but the group doesn't exist in the partition,
+ # it means the mapping is invalid. the previous state of the
+ # partition configuration may have been modified.
+ log.warn(
+ "group not found in CohortPartitionScheme: %r",
+ {
+ "requested_partition_id": user_partition.id,
+ "requested_group_id": group_id,
+ "cohort_id": cohort.id,
+ }
+ )
+ # fail silently
+ return None
+
+ return group
diff --git a/common/djangoapps/user_api/management/__init__.py b/openedx/core/djangoapps/course_groups/tests/__init__.py
similarity index 100%
rename from common/djangoapps/user_api/management/__init__.py
rename to openedx/core/djangoapps/course_groups/tests/__init__.py
diff --git a/common/djangoapps/course_groups/tests/helpers.py b/openedx/core/djangoapps/course_groups/tests/helpers.py
similarity index 98%
rename from common/djangoapps/course_groups/tests/helpers.py
rename to openedx/core/djangoapps/course_groups/tests/helpers.py
index cd7b6c9eee..e23838a5bb 100644
--- a/common/djangoapps/course_groups/tests/helpers.py
+++ b/openedx/core/djangoapps/course_groups/tests/helpers.py
@@ -3,11 +3,12 @@ Helper methods for testing cohorts.
"""
from factory import post_generation, Sequence
from factory.django import DjangoModelFactory
-from course_groups.models import CourseUserGroup
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
+from ..models import CourseUserGroup
+
class CohortFactory(DjangoModelFactory):
"""
diff --git a/common/djangoapps/course_groups/tests/test_cohorts.py b/openedx/core/djangoapps/course_groups/tests/test_cohorts.py
similarity index 79%
rename from common/djangoapps/course_groups/tests/test_cohorts.py
rename to openedx/core/djangoapps/course_groups/tests/test_cohorts.py
index b4a16af27c..68fc445ecb 100644
--- a/common/djangoapps/course_groups/tests/test_cohorts.py
+++ b/openedx/core/djangoapps/course_groups/tests/test_cohorts.py
@@ -1,24 +1,31 @@
from django.conf import settings
from django.contrib.auth.models import User
+from django.db import IntegrityError
from django.http import Http404
from django.test import TestCase
from django.test.utils import override_settings
from mock import call, patch
-from opaque_keys.edx.locations import SlashSeparatedCourseKey
-from course_groups import cohorts
-from course_groups.models import CourseUserGroup
-from course_groups.tests.helpers import topic_name_to_id, config_course_cohorts, CohortFactory
+from opaque_keys.edx.locations import SlashSeparatedCourseKey
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore.django import modulestore, clear_existing_modulestores
-from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_TOY_MODULESTORE
+from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_TOY_MODULESTORE, mixed_store_config
+from ..models import CourseUserGroup, CourseUserGroupPartitionGroup
+from .. import cohorts
+from ..tests.helpers import topic_name_to_id, config_course_cohorts, CohortFactory
# NOTE: running this with the lms.envs.test config works without
# manually overriding the modulestore. However, running with
# cms.envs.test doesn't.
-@patch("course_groups.cohorts.tracker")
+
+TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
+TEST_MAPPING = {'edX/toy/2012_Fall': 'xml'}
+TEST_DATA_MIXED_MODULESTORE = mixed_store_config(TEST_DATA_DIR, TEST_MAPPING)
+
+
+@patch("openedx.core.djangoapps.course_groups.cohorts.tracker")
class TestCohortSignals(TestCase):
def setUp(self):
self.course_key = SlashSeparatedCourseKey("dummy", "dummy", "dummy")
@@ -446,7 +453,7 @@ class TestCohorts(TestCase):
lambda: cohorts.get_cohort_by_id(course.id, cohort.id)
)
- @patch("course_groups.cohorts.tracker")
+ @patch("openedx.core.djangoapps.course_groups.cohorts.tracker")
def test_add_cohort(self, mock_tracker):
"""
Make sure cohorts.add_cohort() properly adds a cohort to a course and handles
@@ -469,7 +476,7 @@ class TestCohorts(TestCase):
lambda: cohorts.add_cohort(SlashSeparatedCourseKey("course", "does_not", "exist"), "My Cohort")
)
- @patch("course_groups.cohorts.tracker")
+ @patch("openedx.core.djangoapps.course_groups.cohorts.tracker")
def test_add_user_to_cohort(self, mock_tracker):
"""
Make sure cohorts.add_user_to_cohort() properly adds a user to a cohort and
@@ -525,3 +532,127 @@ class TestCohorts(TestCase):
User.DoesNotExist,
lambda: cohorts.add_user_to_cohort(first_cohort, "non_existent_username")
)
+
+
+@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
+class TestCohortsAndPartitionGroups(TestCase):
+
+ def setUp(self):
+ """
+ Regenerate a test course and cohorts for each test
+ """
+ self.test_course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
+ self.course = modulestore().get_course(self.test_course_key)
+
+ self.first_cohort = CohortFactory(course_id=self.course.id, name="FirstCohort")
+ self.second_cohort = CohortFactory(course_id=self.course.id, name="SecondCohort")
+
+ self.partition_id = 1
+ self.group1_id = 10
+ self.group2_id = 20
+
+ def _link_cohort_partition_group(self, cohort, partition_id, group_id):
+ """
+ Utility to create cohort -> partition group assignments in the database.
+ """
+ link = CourseUserGroupPartitionGroup(
+ course_user_group=cohort,
+ partition_id=partition_id,
+ group_id=group_id,
+ )
+ link.save()
+ return link
+
+ def test_get_partition_group_id_for_cohort(self):
+ """
+ Basic test of the partition_group_id accessor function
+ """
+ # api should return nothing for an unmapped cohort
+ self.assertEqual(
+ cohorts.get_partition_group_id_for_cohort(self.first_cohort),
+ (None, None),
+ )
+ # create a link for the cohort in the db
+ link = self._link_cohort_partition_group(
+ self.first_cohort,
+ self.partition_id,
+ self.group1_id
+ )
+ # api should return the specified partition and group
+ self.assertEqual(
+ cohorts.get_partition_group_id_for_cohort(self.first_cohort),
+ (self.partition_id, self.group1_id)
+ )
+ # delete the link in the db
+ link.delete()
+ # api should return nothing again
+ self.assertEqual(
+ cohorts.get_partition_group_id_for_cohort(self.first_cohort),
+ (None, None),
+ )
+
+ def test_multiple_cohorts(self):
+ """
+ Test that multiple cohorts can be linked to the same partition group
+ """
+ self._link_cohort_partition_group(
+ self.first_cohort,
+ self.partition_id,
+ self.group1_id,
+ )
+ self._link_cohort_partition_group(
+ self.second_cohort,
+ self.partition_id,
+ self.group1_id,
+ )
+ self.assertEqual(
+ cohorts.get_partition_group_id_for_cohort(self.first_cohort),
+ (self.partition_id, self.group1_id),
+ )
+ self.assertEqual(
+ cohorts.get_partition_group_id_for_cohort(self.second_cohort),
+ cohorts.get_partition_group_id_for_cohort(self.first_cohort),
+ )
+
+ def test_multiple_partition_groups(self):
+ """
+ Test that a cohort cannot be mapped to more than one partition group
+ """
+ self._link_cohort_partition_group(
+ self.first_cohort,
+ self.partition_id,
+ self.group1_id,
+ )
+ with self.assertRaisesRegexp(IntegrityError, 'not unique'):
+ self._link_cohort_partition_group(
+ self.first_cohort,
+ self.partition_id,
+ self.group2_id,
+ )
+
+ def test_delete_cascade(self):
+ """
+ Test that cohort -> partition group links are automatically deleted
+ when their parent cohort is deleted.
+ """
+ self._link_cohort_partition_group(
+ self.first_cohort,
+ self.partition_id,
+ self.group1_id
+ )
+ self.assertEqual(
+ cohorts.get_partition_group_id_for_cohort(self.first_cohort),
+ (self.partition_id, self.group1_id)
+ )
+ # delete the link
+ self.first_cohort.delete()
+ # api should return nothing at that point
+ self.assertEqual(
+ cohorts.get_partition_group_id_for_cohort(self.first_cohort),
+ (None, None),
+ )
+ # link should no longer exist because of delete cascade
+ with self.assertRaises(CourseUserGroupPartitionGroup.DoesNotExist):
+ CourseUserGroupPartitionGroup.objects.get(
+ course_user_group_id=self.first_cohort.id
+ )
diff --git a/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py b/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
new file mode 100644
index 0000000000..6e0f2e1a4b
--- /dev/null
+++ b/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
@@ -0,0 +1,257 @@
+"""
+Test the partitions and partitions service
+
+"""
+
+from django.conf import settings
+import django.test
+from django.test.utils import override_settings
+from mock import patch
+
+from student.tests.factories import UserFactory
+from xmodule.partitions.partitions import Group, UserPartition, UserPartitionError
+from xmodule.modulestore.django import modulestore, clear_existing_modulestores
+from xmodule.modulestore.tests.django_utils import mixed_store_config
+from opaque_keys.edx.locations import SlashSeparatedCourseKey
+
+from ..partition_scheme import CohortPartitionScheme
+from ..models import CourseUserGroupPartitionGroup
+from ..cohorts import add_user_to_cohort
+from .helpers import CohortFactory, config_course_cohorts
+
+
+TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
+TEST_MAPPING = {'edX/toy/2012_Fall': 'xml'}
+TEST_DATA_MIXED_MODULESTORE = mixed_store_config(TEST_DATA_DIR, TEST_MAPPING)
+
+
+@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
+class TestCohortPartitionScheme(django.test.TestCase):
+ """
+ Test the logic for linking a user to a partition group based on their cohort.
+ """
+
+ def setUp(self):
+ """
+ Regenerate a course with cohort configuration, partition and groups,
+ and a student for each test.
+ """
+ self.course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
+ config_course_cohorts(modulestore().get_course(self.course_key), [], cohorted=True)
+
+ self.groups = [Group(10, 'Group 10'), Group(20, 'Group 20')]
+ self.user_partition = UserPartition(
+ 0,
+ 'Test Partition',
+ 'for testing purposes',
+ self.groups,
+ scheme=CohortPartitionScheme
+ )
+ self.student = UserFactory.create()
+
+ def link_cohort_partition_group(self, cohort, partition, group):
+ """
+ Utility for creating cohort -> partition group links
+ """
+ CourseUserGroupPartitionGroup(
+ course_user_group=cohort,
+ partition_id=partition.id,
+ group_id=group.id,
+ ).save()
+
+ def unlink_cohort_partition_group(self, cohort):
+ """
+ Utility for removing cohort -> partition group links
+ """
+ CourseUserGroupPartitionGroup.objects.filter(course_user_group=cohort).delete()
+
+ def assert_student_in_group(self, group, partition=None):
+ """
+ Utility for checking that our test student comes up as assigned to the
+ specified partition (or, if None, no partition at all)
+ """
+ self.assertEqual(
+ CohortPartitionScheme.get_group_for_user(
+ self.course_key,
+ self.student,
+ partition or self.user_partition,
+ ),
+ group
+ )
+
+ def test_student_cohort_assignment(self):
+ """
+ Test that the CohortPartitionScheme continues to return the correct
+ group for a student as the student is moved in and out of different
+ cohorts.
+ """
+ first_cohort, second_cohort = [
+ CohortFactory(course_id=self.course_key) for _ in range(2)
+ ]
+ # place student 0 into first cohort
+ add_user_to_cohort(first_cohort, self.student.username)
+ self.assert_student_in_group(None)
+
+ # link first cohort to group 0 in the partition
+ self.link_cohort_partition_group(
+ first_cohort,
+ self.user_partition,
+ self.groups[0],
+ )
+ # link second cohort to to group 1 in the partition
+ self.link_cohort_partition_group(
+ second_cohort,
+ self.user_partition,
+ self.groups[1],
+ )
+ self.assert_student_in_group(self.groups[0])
+
+ # move student from first cohort to second cohort
+ add_user_to_cohort(second_cohort, self.student.username)
+ self.assert_student_in_group(self.groups[1])
+
+ # move the student out of the cohort
+ second_cohort.users.remove(self.student)
+ self.assert_student_in_group(None)
+
+ def test_cohort_partition_group_assignment(self):
+ """
+ Test that the CohortPartitionScheme returns the correct group for a
+ student in a cohort when the cohort link is created / moved / deleted.
+ """
+ test_cohort = CohortFactory(course_id=self.course_key)
+
+ # assign user to cohort (but cohort isn't linked to a partition group yet)
+ add_user_to_cohort(test_cohort, self.student.username)
+ # scheme should not yet find any link
+ self.assert_student_in_group(None)
+
+ # link cohort to group 0
+ self.link_cohort_partition_group(
+ test_cohort,
+ self.user_partition,
+ self.groups[0],
+ )
+ # now the scheme should find a link
+ self.assert_student_in_group(self.groups[0])
+
+ # link cohort to group 1 (first unlink it from group 0)
+ self.unlink_cohort_partition_group(test_cohort)
+ self.link_cohort_partition_group(
+ test_cohort,
+ self.user_partition,
+ self.groups[1],
+ )
+ # scheme should pick up the link
+ self.assert_student_in_group(self.groups[1])
+
+ # unlink cohort from anywhere
+ self.unlink_cohort_partition_group(
+ test_cohort,
+ )
+ # scheme should now return nothing
+ self.assert_student_in_group(None)
+
+ def setup_student_in_group_0(self):
+ """
+ Utility to set up a cohort, add our student to the cohort, and link
+ the cohort to self.groups[0]
+ """
+ test_cohort = CohortFactory(course_id=self.course_key)
+
+ # link cohort to group 0
+ self.link_cohort_partition_group(
+ test_cohort,
+ self.user_partition,
+ self.groups[0],
+ )
+ # place student into cohort
+ add_user_to_cohort(test_cohort, self.student.username)
+ # check link is correct
+ self.assert_student_in_group(self.groups[0])
+
+ def test_partition_changes_nondestructive(self):
+ """
+ If the name of a user partition is changed, or a group is added to the
+ partition, links from cohorts do not break.
+
+ If the name of a group is changed, links from cohorts do not break.
+ """
+ self.setup_student_in_group_0()
+
+ # to simulate a non-destructive configuration change on the course, create
+ # a new partition with the same id and scheme but with groups renamed and
+ # a group added
+ new_groups = [Group(10, 'New Group 10'), Group(20, 'New Group 20'), Group(30, 'New Group 30')]
+ new_user_partition = UserPartition(
+ 0, # same id
+ 'Different Partition',
+ 'dummy',
+ new_groups,
+ scheme=CohortPartitionScheme,
+ )
+ # the link should still work
+ self.assert_student_in_group(new_groups[0], new_user_partition)
+
+ def test_missing_group(self):
+ """
+ If the group is deleted (or its id is changed), there's no referential
+ integrity enforced, so any references from cohorts to that group will be
+ lost. A warning should be logged when links are found from cohorts to
+ groups that no longer exist.
+ """
+ self.setup_student_in_group_0()
+
+ # to simulate a destructive change on the course, create a new partition
+ # with the same id, but different group ids.
+ new_user_partition = UserPartition(
+ 0, # same id
+ 'Another Partition',
+ 'dummy',
+ [Group(11, 'Not Group 10'), Group(21, 'Not Group 20')], # different ids
+ scheme=CohortPartitionScheme,
+ )
+ # the partition will be found since it has the same id, but the group
+ # ids aren't present anymore, so the scheme returns None (and logs a
+ # warning)
+ with patch('openedx.core.djangoapps.course_groups.partition_scheme.log') as mock_log:
+ self.assert_student_in_group(None, new_user_partition)
+ self.assertTrue(mock_log.warn.called)
+ self.assertRegexpMatches(mock_log.warn.call_args[0][0], 'group not found')
+
+ def test_missing_partition(self):
+ """
+ If the user partition is deleted (or its id is changed), there's no
+ referential integrity enforced, so any references from cohorts to that
+ partition's groups will be lost. A warning should be logged when links
+ are found from cohorts to partitions that do not exist.
+ """
+ self.setup_student_in_group_0()
+
+ # to simulate another destructive change on the course, create a new
+ # partition with a different id, but using the same groups.
+ new_user_partition = UserPartition(
+ 1, # different id
+ 'Moved Partition',
+ 'dummy',
+ [Group(10, 'Group 10'), Group(20, 'Group 20')], # same ids
+ scheme=CohortPartitionScheme,
+ )
+ # the partition will not be found even though the group ids match, so the
+ # scheme returns None (and logs a warning).
+ with patch('openedx.core.djangoapps.course_groups.partition_scheme.log') as mock_log:
+ self.assert_student_in_group(None, new_user_partition)
+ self.assertTrue(mock_log.warn.called)
+ self.assertRegexpMatches(mock_log.warn.call_args[0][0], 'partition mismatch')
+
+
+class TestExtension(django.test.TestCase):
+ """
+ Ensure that the scheme extension is correctly plugged in (via entry point
+ in setup.py)
+ """
+
+ def test_get_scheme(self):
+ self.assertEqual(UserPartition.get_scheme('cohort'), CohortPartitionScheme)
+ with self.assertRaisesRegexp(UserPartitionError, 'Unrecognized scheme'):
+ UserPartition.get_scheme('other')
diff --git a/common/djangoapps/course_groups/tests/test_views.py b/openedx/core/djangoapps/course_groups/tests/test_views.py
similarity index 98%
rename from common/djangoapps/course_groups/tests/test_views.py
rename to openedx/core/djangoapps/course_groups/tests/test_views.py
index 580b469c88..9a50dda203 100644
--- a/common/djangoapps/course_groups/tests/test_views.py
+++ b/openedx/core/djangoapps/course_groups/tests/test_views.py
@@ -4,25 +4,23 @@ Tests for course group views
from collections import namedtuple
import json
+from collections import namedtuple
from django.contrib.auth.models import User
from django.http import Http404
from django.test.client import RequestFactory
from django.test.utils import override_settings
-from opaque_keys.edx.locations import SlashSeparatedCourseKey
-from course_groups.cohorts import (
- get_cohort, CohortAssignmentType, get_cohort_by_name, DEFAULT_COHORT_NAME
-)
-from course_groups.models import CourseUserGroup
-from course_groups.tests.helpers import config_course_cohorts, CohortFactory
-from course_groups.views import (
- list_cohorts, add_cohort, users_in_cohort, add_users_to_cohort, remove_user_from_cohort
-)
from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
+from opaque_keys.edx.locations import SlashSeparatedCourseKey
+
+from ..models import CourseUserGroup
+from ..views import list_cohorts, add_cohort, users_in_cohort, add_users_to_cohort, remove_user_from_cohort
+from ..cohorts import get_cohort, CohortAssignmentType, get_cohort_by_name, DEFAULT_COHORT_NAME
+from .helpers import config_course_cohorts, CohortFactory
@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
diff --git a/common/djangoapps/course_groups/views.py b/openedx/core/djangoapps/course_groups/views.py
similarity index 100%
rename from common/djangoapps/course_groups/views.py
rename to openedx/core/djangoapps/course_groups/views.py
diff --git a/common/djangoapps/user_api/management/commands/__init__.py b/openedx/core/djangoapps/user_api/__init__.py
similarity index 100%
rename from common/djangoapps/user_api/management/commands/__init__.py
rename to openedx/core/djangoapps/user_api/__init__.py
diff --git a/common/djangoapps/user_api/management/tests/__init__.py b/openedx/core/djangoapps/user_api/api/__init__.py
similarity index 100%
rename from common/djangoapps/user_api/management/tests/__init__.py
rename to openedx/core/djangoapps/user_api/api/__init__.py
diff --git a/common/djangoapps/user_api/api/account.py b/openedx/core/djangoapps/user_api/api/account.py
similarity index 99%
rename from common/djangoapps/user_api/api/account.py
rename to openedx/core/djangoapps/user_api/api/account.py
index 118838c7b4..e79969f1b7 100644
--- a/common/djangoapps/user_api/api/account.py
+++ b/openedx/core/djangoapps/user_api/api/account.py
@@ -11,8 +11,8 @@ from django.db import transaction, IntegrityError
from django.core.validators import validate_email, validate_slug, ValidationError
from django.contrib.auth.forms import PasswordResetForm
-from user_api.models import User, UserProfile, Registration, PendingEmailChange
-from user_api.helpers import intercept_errors
+from ..models import User, UserProfile, Registration, PendingEmailChange
+from ..helpers import intercept_errors
USERNAME_MIN_LENGTH = 2
diff --git a/common/djangoapps/user_api/api/course_tag.py b/openedx/core/djangoapps/user_api/api/course_tag.py
similarity index 97%
rename from common/djangoapps/user_api/api/course_tag.py
rename to openedx/core/djangoapps/user_api/api/course_tag.py
index 89e7f49b85..6aa9d7d948 100644
--- a/common/djangoapps/user_api/api/course_tag.py
+++ b/openedx/core/djangoapps/user_api/api/course_tag.py
@@ -7,7 +7,7 @@ Stores global metadata using the UserPreference model, and per-course metadata u
UserCourseTag model.
"""
-from user_api.models import UserCourseTag
+from ..models import UserCourseTag
# Scopes
# (currently only allows per-course tags. Can be expanded to support
diff --git a/common/djangoapps/user_api/api/profile.py b/openedx/core/djangoapps/user_api/api/profile.py
similarity index 97%
rename from common/djangoapps/user_api/api/profile.py
rename to openedx/core/djangoapps/user_api/api/profile.py
index 8bc2f01f9c..f0033cc919 100644
--- a/common/djangoapps/user_api/api/profile.py
+++ b/openedx/core/djangoapps/user_api/api/profile.py
@@ -13,9 +13,9 @@ from django.db import IntegrityError
from pytz import UTC
import analytics
-from user_api.models import User, UserProfile, UserPreference, UserOrgTag
-from user_api.helpers import intercept_errors
from eventtracking import tracker
+from ..models import User, UserProfile, UserPreference, UserOrgTag
+from ..helpers import intercept_errors
log = logging.getLogger(__name__)
@@ -34,6 +34,7 @@ class ProfileInvalidField(ProfileRequestError):
""" The proposed value for a field is not in a valid format. """
def __init__(self, field, value):
+ super(ProfileInvalidField, self).__init__()
self.field = field
self.value = value
diff --git a/common/djangoapps/user_api/helpers.py b/openedx/core/djangoapps/user_api/helpers.py
similarity index 97%
rename from common/djangoapps/user_api/helpers.py
rename to openedx/core/djangoapps/user_api/helpers.py
index 04bd7065e0..bb85be6fa9 100644
--- a/common/djangoapps/user_api/helpers.py
+++ b/openedx/core/djangoapps/user_api/helpers.py
@@ -12,7 +12,7 @@ from django.http import HttpResponseBadRequest
LOGGER = logging.getLogger(__name__)
-def intercept_errors(api_error, ignore_errors=[]):
+def intercept_errors(api_error, ignore_errors=None):
"""
Function decorator that intercepts exceptions
and translates them into API-specific errors (usually an "internal" error).
@@ -33,13 +33,20 @@ def intercept_errors(api_error, ignore_errors=[]):
"""
def _decorator(func):
+ """
+ Function decorator that intercepts exceptions and translates them into API-specific errors.
+ """
@wraps(func)
def _wrapped(*args, **kwargs):
+ """
+ Wrapper that evaluates a function, intercepting exceptions and translating them into
+ API-specific errors.
+ """
try:
return func(*args, **kwargs)
except Exception as ex:
# Raise the original exception if it's in our list of "ignored" errors
- for ignored in ignore_errors:
+ for ignored in ignore_errors or []:
if isinstance(ex, ignored):
raise
diff --git a/common/djangoapps/user_api/migrations/__init__.py b/openedx/core/djangoapps/user_api/management/__init__.py
similarity index 100%
rename from common/djangoapps/user_api/migrations/__init__.py
rename to openedx/core/djangoapps/user_api/management/__init__.py
diff --git a/common/djangoapps/user_api/tests/__init__.py b/openedx/core/djangoapps/user_api/management/commands/__init__.py
similarity index 100%
rename from common/djangoapps/user_api/tests/__init__.py
rename to openedx/core/djangoapps/user_api/management/commands/__init__.py
diff --git a/common/djangoapps/user_api/management/commands/email_opt_in_list.py b/openedx/core/djangoapps/user_api/management/commands/email_opt_in_list.py
similarity index 100%
rename from common/djangoapps/user_api/management/commands/email_opt_in_list.py
rename to openedx/core/djangoapps/user_api/management/commands/email_opt_in_list.py
diff --git a/openedx/core/djangoapps/user_api/management/tests/__init__.py b/openedx/core/djangoapps/user_api/management/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/user_api/management/tests/test_email_opt_in_list.py b/openedx/core/djangoapps/user_api/management/tests/test_email_opt_in_list.py
similarity index 98%
rename from common/djangoapps/user_api/management/tests/test_email_opt_in_list.py
rename to openedx/core/djangoapps/user_api/management/tests/test_email_opt_in_list.py
index f9b4389da3..58d60f0a0d 100644
--- a/common/djangoapps/user_api/management/tests/test_email_opt_in_list.py
+++ b/openedx/core/djangoapps/user_api/management/tests/test_email_opt_in_list.py
@@ -19,9 +19,9 @@ from xmodule.modulestore.tests.factories import CourseFactory
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from student.models import CourseEnrollment
-import user_api.api.profile as profile_api
-from user_api.models import UserOrgTag
-from user_api.management.commands import email_opt_in_list
+import openedx.core.djangoapps.user_api.api.profile as profile_api
+from openedx.core.djangoapps.user_api.models import UserOrgTag
+from openedx.core.djangoapps.user_api.management.commands import email_opt_in_list
MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False)
diff --git a/common/djangoapps/user_api/middleware.py b/openedx/core/djangoapps/user_api/middleware.py
similarity index 97%
rename from common/djangoapps/user_api/middleware.py
rename to openedx/core/djangoapps/user_api/middleware.py
index 1f4d4aa1cc..144ab8220e 100644
--- a/common/djangoapps/user_api/middleware.py
+++ b/openedx/core/djangoapps/user_api/middleware.py
@@ -8,7 +8,8 @@ from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from track.contexts import COURSE_REGEX
-from user_api.models import UserCourseTag
+
+from .models import UserCourseTag
class UserTagsEventContextMiddleware(object):
diff --git a/common/djangoapps/user_api/migrations/0001_initial.py b/openedx/core/djangoapps/user_api/migrations/0001_initial.py
similarity index 100%
rename from common/djangoapps/user_api/migrations/0001_initial.py
rename to openedx/core/djangoapps/user_api/migrations/0001_initial.py
diff --git a/common/djangoapps/user_api/migrations/0002_auto__add_usercoursetags__add_unique_usercoursetags_user_course_id_key.py b/openedx/core/djangoapps/user_api/migrations/0002_auto__add_usercoursetags__add_unique_usercoursetags_user_course_id_key.py
similarity index 100%
rename from common/djangoapps/user_api/migrations/0002_auto__add_usercoursetags__add_unique_usercoursetags_user_course_id_key.py
rename to openedx/core/djangoapps/user_api/migrations/0002_auto__add_usercoursetags__add_unique_usercoursetags_user_course_id_key.py
diff --git a/common/djangoapps/user_api/migrations/0003_rename_usercoursetags.py b/openedx/core/djangoapps/user_api/migrations/0003_rename_usercoursetags.py
similarity index 100%
rename from common/djangoapps/user_api/migrations/0003_rename_usercoursetags.py
rename to openedx/core/djangoapps/user_api/migrations/0003_rename_usercoursetags.py
diff --git a/common/djangoapps/user_api/migrations/0004_auto__add_userorgtag__add_unique_userorgtag_user_org_key__chg_field_us.py b/openedx/core/djangoapps/user_api/migrations/0004_auto__add_userorgtag__add_unique_userorgtag_user_org_key__chg_field_us.py
similarity index 100%
rename from common/djangoapps/user_api/migrations/0004_auto__add_userorgtag__add_unique_userorgtag_user_org_key__chg_field_us.py
rename to openedx/core/djangoapps/user_api/migrations/0004_auto__add_userorgtag__add_unique_userorgtag_user_org_key__chg_field_us.py
diff --git a/openedx/core/djangoapps/user_api/migrations/__init__.py b/openedx/core/djangoapps/user_api/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/user_api/models.py b/openedx/core/djangoapps/user_api/models.py
similarity index 100%
rename from common/djangoapps/user_api/models.py
rename to openedx/core/djangoapps/user_api/models.py
diff --git a/openedx/core/djangoapps/user_api/partition_schemes.py b/openedx/core/djangoapps/user_api/partition_schemes.py
new file mode 100644
index 0000000000..b3b40212db
--- /dev/null
+++ b/openedx/core/djangoapps/user_api/partition_schemes.py
@@ -0,0 +1,61 @@
+"""
+Provides partition support to the user service.
+"""
+
+import random
+import api.course_tag as course_tag_api
+
+from xmodule.partitions.partitions import UserPartitionError
+
+
+class RandomUserPartitionScheme(object):
+ """
+ This scheme randomly assigns users into the partition's groups.
+ """
+ RANDOM = random.Random()
+
+ @classmethod
+ def get_group_for_user(cls, course_id, user, user_partition, track_function=None):
+ """
+ Returns the group from the specified user position to which the user is assigned.
+ If the user has not yet been assigned, a group will be randomly chosen for them.
+ """
+ partition_key = cls._key_for_partition(user_partition)
+ group_id = course_tag_api.get_course_tag(user, course_id, partition_key)
+ group = user_partition.get_group(int(group_id)) if not group_id is None else None
+ if group is None:
+ if not user_partition.groups:
+ raise UserPartitionError('Cannot assign user to an empty user partition')
+
+ # pylint: disable=fixme
+ # TODO: had a discussion in arch council about making randomization more
+ # deterministic (e.g. some hash). Could do that, but need to be careful not
+ # to introduce correlation between users or bias in generation.
+ group = cls.RANDOM.choice(user_partition.groups)
+
+ # persist the value as a course tag
+ course_tag_api.set_course_tag(user, course_id, partition_key, group.id)
+
+ if track_function:
+ # emit event for analytics
+ # FYI - context is always user ID that is logged in, NOT the user id that is
+ # being operated on. If instructor can move user explicitly, then we should
+ # put in event_info the user id that is being operated on.
+ event_info = {
+ 'group_id': group.id,
+ 'group_name': group.name,
+ 'partition_id': user_partition.id,
+ 'partition_name': user_partition.name
+ }
+ # pylint: disable=fixme
+ # TODO: Use the XBlock publish api instead
+ track_function('xmodule.partitions.assigned_user_to_partition', event_info)
+
+ return group
+
+ @classmethod
+ def _key_for_partition(cls, user_partition):
+ """
+ Returns the key to use to look up and save the user's group for a given user partition.
+ """
+ return 'xblock.partition_service.partition_{0}'.format(user_partition.id)
diff --git a/common/djangoapps/user_api/serializers.py b/openedx/core/djangoapps/user_api/serializers.py
similarity index 95%
rename from common/djangoapps/user_api/serializers.py
rename to openedx/core/djangoapps/user_api/serializers.py
index 09aa25e939..7c7227fff6 100644
--- a/common/djangoapps/user_api/serializers.py
+++ b/openedx/core/djangoapps/user_api/serializers.py
@@ -1,7 +1,8 @@
from django.contrib.auth.models import User
from rest_framework import serializers
from student.models import UserProfile
-from user_api.models import UserPreference
+
+from .models import UserPreference
class UserSerializer(serializers.HyperlinkedModelSerializer):
diff --git a/openedx/core/djangoapps/user_api/tests/__init__.py b/openedx/core/djangoapps/user_api/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/user_api/tests/factories.py b/openedx/core/djangoapps/user_api/tests/factories.py
similarity index 92%
rename from common/djangoapps/user_api/tests/factories.py
rename to openedx/core/djangoapps/user_api/tests/factories.py
index 60ee7c2ddc..8b1198bef5 100644
--- a/common/djangoapps/user_api/tests/factories.py
+++ b/openedx/core/djangoapps/user_api/tests/factories.py
@@ -2,9 +2,10 @@
from factory.django import DjangoModelFactory
from factory import SubFactory
from student.tests.factories import UserFactory
-from user_api.models import UserPreference, UserCourseTag, UserOrgTag
from opaque_keys.edx.locations import SlashSeparatedCourseKey
+from ..models import UserPreference, UserCourseTag, UserOrgTag
+
# Factories are self documenting
# pylint: disable=missing-docstring
diff --git a/common/djangoapps/user_api/tests/test_account_api.py b/openedx/core/djangoapps/user_api/tests/test_account_api.py
similarity index 99%
rename from common/djangoapps/user_api/tests/test_account_api.py
rename to openedx/core/djangoapps/user_api/tests/test_account_api.py
index 3ab1aac029..27b708abce 100644
--- a/common/djangoapps/user_api/tests/test_account_api.py
+++ b/openedx/core/djangoapps/user_api/tests/test_account_api.py
@@ -12,8 +12,8 @@ from django.core import mail
from django.test import TestCase
from django.conf import settings
-from user_api.api import account as account_api
-from user_api.models import UserProfile
+from ..api import account as account_api
+from ..models import UserProfile
@ddt.ddt
diff --git a/common/djangoapps/user_api/tests/test_constants.py b/openedx/core/djangoapps/user_api/tests/test_constants.py
similarity index 100%
rename from common/djangoapps/user_api/tests/test_constants.py
rename to openedx/core/djangoapps/user_api/tests/test_constants.py
diff --git a/common/djangoapps/user_api/tests/test_course_tag_api.py b/openedx/core/djangoapps/user_api/tests/test_course_tag_api.py
similarity index 91%
rename from common/djangoapps/user_api/tests/test_course_tag_api.py
rename to openedx/core/djangoapps/user_api/tests/test_course_tag_api.py
index 51b48a56a4..a329df2a3a 100644
--- a/common/djangoapps/user_api/tests/test_course_tag_api.py
+++ b/openedx/core/djangoapps/user_api/tests/test_course_tag_api.py
@@ -4,11 +4,11 @@ Test the user course tag API.
from django.test import TestCase
from student.tests.factories import UserFactory
-from user_api.api import course_tag as course_tag_api
+from openedx.core.djangoapps.user_api.api import course_tag as course_tag_api
from opaque_keys.edx.locations import SlashSeparatedCourseKey
-class TestUserService(TestCase):
+class TestCourseTagAPI(TestCase):
"""
Test the user service
"""
diff --git a/common/djangoapps/user_api/tests/test_helpers.py b/openedx/core/djangoapps/user_api/tests/test_helpers.py
similarity index 96%
rename from common/djangoapps/user_api/tests/test_helpers.py
rename to openedx/core/djangoapps/user_api/tests/test_helpers.py
index 76fcce7ddf..713ff41206 100644
--- a/common/djangoapps/user_api/tests/test_helpers.py
+++ b/openedx/core/djangoapps/user_api/tests/test_helpers.py
@@ -4,10 +4,10 @@ Tests for helper functions.
import json
import mock
import ddt
+from django.http import HttpRequest, HttpResponse
from django.test import TestCase
from nose.tools import raises
-from django.http import HttpRequest, HttpResponse
-from user_api.helpers import (
+from ..helpers import (
intercept_errors, shim_student_view,
FormDescription, InvalidFieldError
)
@@ -49,12 +49,12 @@ class InterceptErrorsTest(TestCase):
def test_ignores_expected_errors(self):
intercepted_function(raise_error=ValueError)
- @mock.patch('user_api.helpers.LOGGER')
+ @mock.patch('openedx.core.djangoapps.user_api.helpers.LOGGER')
def test_logs_errors(self, mock_logger):
expected_log_msg = (
u"An unexpected error occurred when calling 'intercepted_function' "
u"with arguments '()' and "
- u"keyword arguments '{'raise_error': }': "
+ u"keyword arguments '{'raise_error': }': "
u"FakeInputException()"
)
diff --git a/common/djangoapps/user_api/tests/test_middleware.py b/openedx/core/djangoapps/user_api/tests/test_middleware.py
similarity index 95%
rename from common/djangoapps/user_api/tests/test_middleware.py
rename to openedx/core/djangoapps/user_api/tests/test_middleware.py
index d51ed2057e..297ad0a140 100644
--- a/common/djangoapps/user_api/tests/test_middleware.py
+++ b/openedx/core/djangoapps/user_api/tests/test_middleware.py
@@ -6,8 +6,9 @@ from django.http import HttpResponse
from django.test.client import RequestFactory
from student.tests.factories import UserFactory, AnonymousUserFactory
-from user_api.tests.factories import UserCourseTagFactory
-from user_api.middleware import UserTagsEventContextMiddleware
+
+from ..tests.factories import UserCourseTagFactory
+from ..middleware import UserTagsEventContextMiddleware
class TagsMiddlewareTest(TestCase):
@@ -29,7 +30,7 @@ class TagsMiddlewareTest(TestCase):
self.response = Mock(spec=HttpResponse)
- patcher = patch('user_api.middleware.tracker')
+ patcher = patch('openedx.core.djangoapps.user_api.middleware.tracker')
self.tracker = patcher.start()
self.addCleanup(patcher.stop)
diff --git a/common/djangoapps/user_api/tests/test_models.py b/openedx/core/djangoapps/user_api/tests/test_models.py
similarity index 95%
rename from common/djangoapps/user_api/tests/test_models.py
rename to openedx/core/djangoapps/user_api/tests/test_models.py
index fabcbec8d2..09606403ab 100644
--- a/common/djangoapps/user_api/tests/test_models.py
+++ b/openedx/core/djangoapps/user_api/tests/test_models.py
@@ -2,8 +2,9 @@ from django.db import IntegrityError
from django.test import TestCase
from xmodule.modulestore.tests.factories import CourseFactory
from student.tests.factories import UserFactory
-from user_api.tests.factories import UserPreferenceFactory, UserCourseTagFactory, UserOrgTagFactory
-from user_api.models import UserPreference
+
+from ..tests.factories import UserPreferenceFactory, UserCourseTagFactory, UserOrgTagFactory
+from ..models import UserPreference
class UserPreferenceModelTest(TestCase):
diff --git a/openedx/core/djangoapps/user_api/tests/test_partition_schemes.py b/openedx/core/djangoapps/user_api/tests/test_partition_schemes.py
new file mode 100644
index 0000000000..7d7ce3135d
--- /dev/null
+++ b/openedx/core/djangoapps/user_api/tests/test_partition_schemes.py
@@ -0,0 +1,120 @@
+"""
+Test the user api's partition extensions.
+"""
+from collections import defaultdict
+from mock import patch
+from unittest import TestCase
+
+from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme, UserPartitionError
+from student.tests.factories import UserFactory
+from xmodule.partitions.partitions import Group, UserPartition
+from xmodule.partitions.tests.test_partitions import PartitionTestCase
+
+
+class MemoryCourseTagAPI(object):
+ """
+ An implementation of a user service that uses an in-memory dictionary for storage
+ """
+ def __init__(self):
+ self._tags = defaultdict(dict)
+
+ def get_course_tag(self, __, course_id, key):
+ """Sets the value of ``key`` to ``value``"""
+ return self._tags[course_id].get(key)
+
+ def set_course_tag(self, __, course_id, key, value):
+ """Gets the value of ``key``"""
+ self._tags[course_id][key] = value
+
+
+class TestRandomUserPartitionScheme(PartitionTestCase):
+ """
+ Test getting a user's group out of a partition
+ """
+
+ MOCK_COURSE_ID = "mock-course-id"
+
+ def setUp(self):
+ super(TestRandomUserPartitionScheme, self).setUp()
+ # Patch in a memory-based user service instead of using the persistent version
+ course_tag_api = MemoryCourseTagAPI()
+ self.user_service_patcher = patch(
+ 'openedx.core.djangoapps.user_api.partition_schemes.course_tag_api', course_tag_api
+ )
+ self.user_service_patcher.start()
+
+ # Create a test user
+ self.user = UserFactory.create()
+
+ def tearDown(self):
+ self.user_service_patcher.stop()
+
+ def test_get_group_for_user(self):
+ # get a group assigned to the user
+ group1_id = RandomUserPartitionScheme.get_group_for_user(self.MOCK_COURSE_ID, self.user, self.user_partition)
+
+ # make sure we get the same group back out every time
+ for __ in range(0, 10):
+ group2_id = RandomUserPartitionScheme.get_group_for_user(self.MOCK_COURSE_ID, self.user, self.user_partition)
+ self.assertEqual(group1_id, group2_id)
+
+ def test_empty_partition(self):
+ empty_partition = UserPartition(
+ self.TEST_ID,
+ 'Test Partition',
+ 'for testing purposes',
+ [],
+ scheme=RandomUserPartitionScheme
+ )
+ # get a group assigned to the user
+ with self.assertRaisesRegexp(UserPartitionError, "Cannot assign user to an empty user partition"):
+ RandomUserPartitionScheme.get_group_for_user(self.MOCK_COURSE_ID, self.user, empty_partition)
+
+ def test_user_in_deleted_group(self):
+ # get a group assigned to the user - should be group 0 or 1
+ old_group = RandomUserPartitionScheme.get_group_for_user(self.MOCK_COURSE_ID, self.user, self.user_partition)
+ self.assertIn(old_group.id, [0, 1])
+
+ # Change the group definitions! No more group 0 or 1
+ groups = [Group(3, 'Group 3'), Group(4, 'Group 4')]
+ user_partition = UserPartition(self.TEST_ID, 'Test Partition', 'for testing purposes', groups)
+
+ # Now, get a new group using the same call - should be 3 or 4
+ new_group = RandomUserPartitionScheme.get_group_for_user(self.MOCK_COURSE_ID, self.user, user_partition)
+ self.assertIn(new_group.id, [3, 4])
+
+ # We should get the same group over multiple calls
+ new_group_2 = RandomUserPartitionScheme.get_group_for_user(self.MOCK_COURSE_ID, self.user, user_partition)
+ self.assertEqual(new_group, new_group_2)
+
+ def test_change_group_name(self):
+ # Changing the name of the group shouldn't affect anything
+ # get a group assigned to the user - should be group 0 or 1
+ old_group = RandomUserPartitionScheme.get_group_for_user(self.MOCK_COURSE_ID, self.user, self.user_partition)
+ self.assertIn(old_group.id, [0, 1])
+
+ # Change the group names
+ groups = [Group(0, 'Group 0'), Group(1, 'Group 1')]
+ user_partition = UserPartition(
+ self.TEST_ID,
+ 'Test Partition',
+ 'for testing purposes',
+ groups,
+ scheme=RandomUserPartitionScheme
+ )
+
+ # Now, get a new group using the same call
+ new_group = RandomUserPartitionScheme.get_group_for_user(self.MOCK_COURSE_ID, self.user, user_partition)
+ self.assertEqual(old_group.id, new_group.id)
+
+
+class TestExtension(TestCase):
+ """
+ Ensure that the scheme extension is correctly plugged in (via entry point
+ in setup.py)
+ """
+
+ def test_get_scheme(self):
+ self.assertEqual(UserPartition.get_scheme('random'), RandomUserPartitionScheme)
+ with self.assertRaisesRegexp(UserPartitionError, 'Unrecognized scheme'):
+ UserPartition.get_scheme('other')
diff --git a/common/djangoapps/user_api/tests/test_profile_api.py b/openedx/core/djangoapps/user_api/tests/test_profile_api.py
similarity index 98%
rename from common/djangoapps/user_api/tests/test_profile_api.py
rename to openedx/core/djangoapps/user_api/tests/test_profile_api.py
index 9e0f7e554e..297b50c4f8 100644
--- a/common/djangoapps/user_api/tests/test_profile_api.py
+++ b/openedx/core/djangoapps/user_api/tests/test_profile_api.py
@@ -10,9 +10,9 @@ from dateutil.parser import parse as parse_datetime
from xmodule.modulestore.tests.factories import CourseFactory
import datetime
-from user_api.api import account as account_api
-from user_api.api import profile as profile_api
-from user_api.models import UserProfile, UserOrgTag
+from ..api import account as account_api
+from ..api import profile as profile_api
+from ..models import UserProfile, UserOrgTag
@ddt.ddt
diff --git a/common/djangoapps/user_api/tests/test_views.py b/openedx/core/djangoapps/user_api/tests/test_views.py
similarity index 99%
rename from common/djangoapps/user_api/tests/test_views.py
rename to openedx/core/djangoapps/user_api/tests/test_views.py
index 69380eb220..c1614b235d 100644
--- a/common/djangoapps/user_api/tests/test_views.py
+++ b/openedx/core/djangoapps/user_api/tests/test_views.py
@@ -16,16 +16,16 @@ from pytz import UTC
import mock
from xmodule.modulestore.tests.factories import CourseFactory
-from user_api.api import account as account_api, profile as profile_api
-
from student.tests.factories import UserFactory
-from user_api.models import UserOrgTag
-from user_api.tests.factories import UserPreferenceFactory
+from unittest import SkipTest
from django_comment_common import models
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from third_party_auth.tests.testutil import simulate_running_pipeline
-from user_api.tests.test_constants import SORTED_COUNTRIES
+from ..api import account as account_api, profile as profile_api
+from ..models import UserOrgTag
+from ..tests.factories import UserPreferenceFactory
+from ..tests.test_constants import SORTED_COUNTRIES
TEST_API_KEY = "test_api_key"
@@ -888,7 +888,7 @@ class RegistrationViewTest(ApiTestCase):
no_extra_fields_setting = {}
with simulate_running_pipeline(
- "user_api.views.third_party_auth.pipeline",
+ "openedx.core.djangoapps.user_api.views.third_party_auth.pipeline",
"google-oauth2", email="bob@example.com",
fullname="Bob", username="Bob123"
):
diff --git a/common/djangoapps/user_api/urls.py b/openedx/core/djangoapps/user_api/urls.py
similarity index 76%
rename from common/djangoapps/user_api/urls.py
rename to openedx/core/djangoapps/user_api/urls.py
index b6b903f350..3b840cc6b5 100644
--- a/common/djangoapps/user_api/urls.py
+++ b/openedx/core/djangoapps/user_api/urls.py
@@ -1,17 +1,21 @@
-# pylint: disable=missing-docstring
+"""
+Defines the URL routes for this app.
+"""
+
from django.conf import settings
from django.conf.urls import include, patterns, url
from rest_framework import routers
-from user_api import views as user_api_views
-from user_api.models import UserPreference
+
+from . import views as user_api_views
+from .models import UserPreference
-user_api_router = routers.DefaultRouter()
-user_api_router.register(r'users', user_api_views.UserViewSet)
-user_api_router.register(r'user_prefs', user_api_views.UserPreferenceViewSet)
+USER_API_ROUTER = routers.DefaultRouter()
+USER_API_ROUTER.register(r'users', user_api_views.UserViewSet)
+USER_API_ROUTER.register(r'user_prefs', user_api_views.UserPreferenceViewSet)
urlpatterns = patterns(
'',
- url(r'^v1/', include(user_api_router.urls)),
+ url(r'^v1/', include(USER_API_ROUTER.urls)),
url(
r'^v1/preferences/(?P{})/users/$'.format(UserPreference.KEY_REGEX),
user_api_views.PreferenceUsersListView.as_view()
diff --git a/common/djangoapps/user_api/views.py b/openedx/core/djangoapps/user_api/views.py
similarity index 99%
rename from common/djangoapps/user_api/views.py
rename to openedx/core/djangoapps/user_api/views.py
index 41e69aa3f5..24791613c4 100644
--- a/common/djangoapps/user_api/views.py
+++ b/openedx/core/djangoapps/user_api/views.py
@@ -1,5 +1,6 @@
"""HTTP end-points for the User API. """
import copy
+import third_party_auth
from django.conf import settings
from django.contrib.auth.models import User
@@ -19,16 +20,15 @@ from rest_framework import viewsets
from rest_framework.views import APIView
from rest_framework.exceptions import ParseError
from django_countries import countries
-from user_api.serializers import UserSerializer, UserPreferenceSerializer
-from user_api.models import UserPreference, UserProfile
from django_comment_common.models import Role
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from edxmako.shortcuts import marketing_link
-import third_party_auth
from util.authentication import SessionAuthenticationAllowInactiveUser
-from user_api.api import account as account_api, profile as profile_api
-from user_api.helpers import FormDescription, shim_student_view, require_post_params
+from .api import account as account_api, profile as profile_api
+from .helpers import FormDescription, shim_student_view, require_post_params
+from .models import UserPreference, UserProfile
+from .serializers import UserSerializer, UserPreferenceSerializer
class ApiKeyHeaderPermission(permissions.BasePermission):
diff --git a/pavelib/tests.py b/pavelib/tests.py
index e8da51c6fc..9b1058b221 100644
--- a/pavelib/tests.py
+++ b/pavelib/tests.py
@@ -48,7 +48,7 @@ def test_system(options):
if test_id:
if not system:
system = test_id.split('/')[0]
- if system == 'common':
+ if system in ['common', 'openedx']:
system = 'lms'
opts['test_id'] = test_id
diff --git a/pavelib/utils/test/suites/nose_suite.py b/pavelib/utils/test/suites/nose_suite.py
index 4e0e0992d8..42787bc216 100644
--- a/pavelib/utils/test/suites/nose_suite.py
+++ b/pavelib/utils/test/suites/nose_suite.py
@@ -128,7 +128,7 @@ class SystemTestSuite(NoseTestSuite):
# django-nose will import them early in the test process,
# thereby making sure that we load any django models that are
# only defined in test files.
- default_test_id = "{system}/djangoapps/* common/djangoapps/*".format(
+ default_test_id = "{system}/djangoapps/* common/djangoapps/* openedx/core/djangoapps/*".format(
system=self.root
)
diff --git a/setup.py b/setup.py
index cdd8f74211..a3870e5750 100644
--- a/setup.py
+++ b/setup.py
@@ -1,14 +1,26 @@
-from setuptools import setup, find_packages
+"""
+Setup script for the Open edX package.
+"""
+
+from setuptools import setup
setup(
name="Open edX",
- version="0.1",
+ version="0.2",
install_requires=['distribute'],
requires=[],
# NOTE: These are not the names we should be installing. This tree should
- # be reorgnized to be a more conventional Python tree.
+ # be reorganized to be a more conventional Python tree.
packages=[
+ "openedx.core.djangoapps.user_api",
+ "openedx.core.djangoapps.course_groups",
"lms",
"cms",
],
+ entry_points={
+ 'openedx.user_partition_scheme': [
+ 'random = openedx.core.djangoapps.user_api.partition_schemes:RandomUserPartitionScheme',
+ 'cohort = openedx.core.djangoapps.course_groups.partition_scheme:CohortPartitionScheme',
+ ],
+ }
)