There are 3 main changes in this commit:
* CohortFactory now sets up memberships properly, so consuming tests do not
need to explicitly touch CourseUserGroup.users to add() users.
* test_get_cohort_sql_queries has been updated to 3 and 9 queries when using
and not using the cache, respectively. This is needed due to each operation
needing an extra queery to get the CourseUserGroup from the CohortMembership.
* Adding remove_user_from_cohort(), the counterpart to add_user_to_cohort().
This is also to keep tests from touching the users field directly, and keep
CohortMembership data in sync.
An issue arose recently due to ATOMIC_REQUESTS being turned on by default. It
turns out that CohortMemberships had been somewhat relying on the old default
transaction handling in order to keep CohortMemberships and the underlying
CourseUserGroup.users values in-sync.
To fix this, I've made all updates to Cohortmemberships go through an
outer_atomic(read_committed=True) block. This, is conjunction with the already
present select_for_update(), will no longer allow 2 simultaneous requests to
modify objects in memory without sharing them. Only one process will be
touching a given CohortMembership at any given time, and all changes will be
immediately comitted to the database, where the other process will see them.
I've also included some changes to get_cohort(), add_user_to_cohort(), and
remove_user_from_cohort() in order to properly make use of the new
CohortMembership system.
On devops recommendation, now handling the potential for an 'inconsistency
window' via a management command instead of a hacky "re-run the data migration"
bash script.
By using the before_after library, we can force a race condition to reliably
occur in the CohortMembership.save() method. This unit test will do just that,
and ensure that our race-condition-handling code functions as it should.
The code changes needed to get CohortMembership functioning properly.
The key of this change is twofold: first, CohortMemberships are unique
per-user, per-course. This is enforced at the database level. Secondly,
the updates are done using a select_for_update, which ensures atomicity.
These are the migrations needed for CohortMembership to function.
0005 establishes the table, 0006 will be used to move existing data
into the table as needed.
Per product guidance, we can just arbitrarily reassign problem users.
Implementing that decision, as well as the remainder of the data migration.
Also including a short script to re-run the 0006 migration after code changes
are live, to prevent a potential issue where the database become out-of-sync.
* Automatically create user partitions on course publish for each ICRV checkpoint.
* Disable partitions for ICRV checkpoints that have been deleted.
* Skip partitions that have been disabled when checking access.
* Add verification access control UI to visibility settings.
* Add verification access control UI to sequential and vertical settings.
* Add partition scheme for verification partition groups.
* Cache information used by verification partition scheme and invalidate the cache on update.
* Add location parameter to UserPartition so the partition scheme can find the associated checkpoint.
* Refactor GroupConfiguration to allow multiple user partitions.
* Add special messaging to ICRV for students in the honor track.
Authors: Zubair Arbi, Awais Qureshi, Aamir Khan, Will Daly
The existing pattern of using `override_settings(MODULESTORE=...)` prevented
us from having more than one layer of subclassing in modulestore tests.
In a structure like:
@override_settings(MODULESTORE=store_a)
class BaseTestCase(ModuleStoreTestCase):
def setUp(self):
# use store
@override_settings(MODULESTORE=store_b)
class ChildTestCase(BaseTestCase):
def setUp(self):
# use store
In this case, the store actions performed in `BaseTestCase` on behalf of
`ChildTestCase` would still use `store_a`, even though the `ChildTestCase`
had specified to use `store_b`. This is because the `override_settings`
decorator would be the innermost wrapper around the `BaseTestCase.setUp` method,
no matter what `ChildTestCase` does.
To remedy this, we move the call to `override_settings` into the
`ModuleStoreTestCase.setUp` method, and use a cleanup to remove the override.
Subclasses can just defined the `MODULESTORE` class attribute to specify which
modulestore to use _for the entire `setUp` chain_.
[PLAT-419]