diff --git a/openedx/core/djangoapps/content/learning_sequences/api/outlines.py b/openedx/core/djangoapps/content/learning_sequences/api/outlines.py index ffe2ac68e4..dd8358c621 100644 --- a/openedx/core/djangoapps/content/learning_sequences/api/outlines.py +++ b/openedx/core/djangoapps/content/learning_sequences/api/outlines.py @@ -561,6 +561,17 @@ def _update_user_partition_groups(upg_data: Dict[int, FrozenSet[int]], ) model_obj.user_partition_groups.add(upg) + # Temporarily fill-in the new and old user_partition_group fields. + # Switchover to the new field will occur after all models have been repopulated. + model_obj.new_user_partition_groups.all().delete() + if upg_data: + for partition_id, group_ids in upg_data.items(): + for group_id in group_ids: + upg, _ = UserPartitionGroup.objects.get_or_create( + partition_id=partition_id, group_id=group_id + ) + model_obj.new_user_partition_groups.add(upg) + def _update_publish_report(course_outline: CourseOutlineData, content_errors: List[ContentErrorData], diff --git a/openedx/core/djangoapps/content/learning_sequences/migrations/0013_through_model_for_user_partition_groups_1.py b/openedx/core/djangoapps/content/learning_sequences/migrations/0013_through_model_for_user_partition_groups_1.py new file mode 100644 index 0000000000..23ad1b0cf3 --- /dev/null +++ b/openedx/core/djangoapps/content/learning_sequences/migrations/0013_through_model_for_user_partition_groups_1.py @@ -0,0 +1,46 @@ +# Generated by Django 2.2.24 on 2021-06-14 22:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('learning_sequences', '0012_add_user_partition_group'), + ] + + operations = [ + migrations.CreateModel( + name='SectionSequencePartitionGroup', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('course_section_sequence', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='learning_sequences.CourseSectionSequence')), + ('user_partition_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='learning_sequences.UserPartitionGroup')), + ], + options={ + 'unique_together': {('user_partition_group', 'course_section_sequence')}, + }, + ), + migrations.CreateModel( + name='SectionPartitionGroup', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('course_section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='learning_sequences.CourseSection')), + ('user_partition_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='learning_sequences.UserPartitionGroup')), + ], + options={ + 'unique_together': {('user_partition_group', 'course_section')}, + }, + ), + migrations.AddField( + model_name='coursesection', + name='new_user_partition_groups', + field=models.ManyToManyField(db_index=True, related_name='sec_user_partition_groups', related_query_name='sec_user_partition_group', through='learning_sequences.SectionPartitionGroup', to='learning_sequences.UserPartitionGroup'), + ), + migrations.AddField( + model_name='coursesectionsequence', + name='new_user_partition_groups', + field=models.ManyToManyField(db_index=True, related_name='secseq_user_partition_groups', related_query_name='secseq_user_partition_group', through='learning_sequences.SectionSequencePartitionGroup', to='learning_sequences.UserPartitionGroup'), + ), + ] diff --git a/openedx/core/djangoapps/content/learning_sequences/models.py b/openedx/core/djangoapps/content/learning_sequences/models.py index df0912a9ba..ed7fae1f74 100644 --- a/openedx/core/djangoapps/content/learning_sequences/models.py +++ b/openedx/core/djangoapps/content/learning_sequences/models.py @@ -201,6 +201,14 @@ class CourseSection(CourseContentVisibilityMixin, TimeStampedModel): user_partition_groups = models.ManyToManyField(UserPartitionGroup) + new_user_partition_groups = models.ManyToManyField( + UserPartitionGroup, + db_index=True, + related_name='sec_user_partition_groups', + related_query_name='sec_user_partition_group', + through='SectionPartitionGroup' + ) + class Meta: unique_together = [ ['course_context', 'usage_key'], @@ -210,6 +218,22 @@ class CourseSection(CourseContentVisibilityMixin, TimeStampedModel): ] +class SectionPartitionGroup(models.Model): + """ + Model which maps user partition groups to course sections. + Used for the user_partition_groups ManyToManyField field in the CourseSection model above. + Adds a cascading delete which will delete these many-to-many relations + whenever a UserPartitionGroup or CourseSection object is deleted. + """ + class Meta: + unique_together = [ + ['user_partition_group', 'course_section'], + ] + + user_partition_group = models.ForeignKey(UserPartitionGroup, on_delete=models.CASCADE) + course_section = models.ForeignKey(CourseSection, on_delete=models.CASCADE) + + class CourseSectionSequence(CourseContentVisibilityMixin, TimeStampedModel): """ This is a join+ordering table, with entries that could get wiped out and @@ -241,6 +265,14 @@ class CourseSectionSequence(CourseContentVisibilityMixin, TimeStampedModel): user_partition_groups = models.ManyToManyField(UserPartitionGroup) + new_user_partition_groups = models.ManyToManyField( + UserPartitionGroup, + db_index=True, + related_name='secseq_user_partition_groups', + related_query_name='secseq_user_partition_group', + through='SectionSequencePartitionGroup' + ) + class Meta: unique_together = [ ['course_context', 'ordering'], @@ -252,6 +284,22 @@ class CourseSectionSequence(CourseContentVisibilityMixin, TimeStampedModel): return f"{self.section.title} > {self.sequence.title}" +class SectionSequencePartitionGroup(models.Model): + """ + Model which maps user partition groups to course section sequences. + Used for the user_partition_groups ManyToManyField field in the CourseSectionSequence model above. + Adds a cascading delete which will delete these many-to-many relations + whenever a UserPartitionGroup or CourseSectionSequence object is deleted. + """ + class Meta: + unique_together = [ + ['user_partition_group', 'course_section_sequence'], + ] + + user_partition_group = models.ForeignKey(UserPartitionGroup, on_delete=models.CASCADE) + course_section_sequence = models.ForeignKey(CourseSectionSequence, on_delete=models.CASCADE) + + class CourseSequenceExam(TimeStampedModel): """ This model stores XBlock information that affects outline level information