From 42018bedd83e0b9d6544c3c979f997a5954a560d Mon Sep 17 00:00:00 2001 From: Arthur Mousatov Date: Sat, 27 Jun 2026 18:29:42 -0400 Subject: [PATCH 1/2] 5971: added organizations to invitation model --- .../migrations/0168_auto_20260627_2219.py | 23 +++++++++ contentcuration/contentcuration/models.py | 47 +++++++++++++++---- 2 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py diff --git a/contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py b/contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py new file mode 100644 index 0000000000..b0cda9f674 --- /dev/null +++ b/contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.24 on 2026-06-27 22:19 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contentcuration', '0167_add_organization'), + ] + + operations = [ + migrations.AddField( + model_name='invitation', + name='organization', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contentcuration.organization'), + ), + migrations.AddConstraint( + model_name='invitation', + constraint=models.CheckConstraint(check=models.Q(models.Q(('channel__isnull', False), ('organization__isnull', True)), models.Q(('channel__isnull', True), ('organization__isnull', False)), _connector='OR'), name='channel_organization_exclusivity'), + ), + ] diff --git a/contentcuration/contentcuration/models.py b/contentcuration/contentcuration/models.py index 8c7cee3919..f5b089d17f 100644 --- a/contentcuration/contentcuration/models.py +++ b/contentcuration/contentcuration/models.py @@ -79,7 +79,7 @@ from contentcuration.constants import feedback from contentcuration.constants import user_history from contentcuration.constants.contentnode import kind_activity_map -from contentcuration.constants.organization_roles import organization_role_choices +from contentcuration.constants.organization_roles import ORGANIZATION_ADMIN, ORGANIZATION_EDITOR, ORGANIZATION_ROLE_STATUS_ACTIVE, ORGANIZATION_VIEWER, organization_role_choices from contentcuration.constants.organization_roles import ( organization_role_status_choices, ) @@ -102,6 +102,7 @@ EDIT_ACCESS = "edit" VIEW_ACCESS = "view" +ADMIN_ACCESS = "admin" DEFAULT_CONTENT_DEFAULTS = { "license": None, @@ -3730,21 +3731,51 @@ class Invitation(models.Model): ) first_name = models.CharField(max_length=100, blank=True) last_name = models.CharField(max_length=100, blank=True, null=True) + organization = models.ForeignKey( + "Organization", null=True, blank=True, on_delete=models.CASCADE + ) class Meta: verbose_name = "Invitation" verbose_name_plural = "Invitations" + constraints = [ + models.CheckConstraint( + name="channel_organization_exclusivity", + check=( + Q(channel__isnull=False, organization__isnull=True) | + Q(channel__isnull=True, organization__isnull=False) + ) + ) + ] def accept(self): user = User.objects.filter(email__iexact=self.email).first() if self.channel: - # channel is a nullable field, so check that it exists. - if self.share_mode == VIEW_ACCESS: - self.channel.editors.remove(user) - self.channel.viewers.add(user) - else: - self.channel.viewers.remove(user) - self.channel.editors.add(user) + self.accept_channel_invitation(user) + if self.organization: + self.accept_organization_invitation(user) + + def _accept_channel_invitation(self, user): + if self.share_mode == VIEW_ACCESS: + self.channel.editors.remove(user) + self.channel.viewers.add(user) + else: + self.channel.viewers.remove(user) + self.channel.editors.add(user) + + def _accept_organization_invitation(self, user): + organization_role = OrganizationRole.objects.create( + user=user, + organization=self.organization, + status=ORGANIZATION_ROLE_STATUS_ACTIVE + ) + if self.share_mode == VIEW_ACCESS: + organization_role.role = ORGANIZATION_VIEWER + elif self.share_mode == EDIT_ACCESS: + organization_role.role = ORGANIZATION_EDITOR + elif self.share_mode == ADMIN_ACCESS: + organization_role.role = ORGANIZATION_ADMIN + organization_role.save() @classmethod def filter_edit_queryset(cls, queryset, user): From ab659c6dcd4267a7bb7a019584ce0b68e095a911 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:38:48 +0000 Subject: [PATCH 2/2] [pre-commit.ci lite] apply automatic fixes --- .../migrations/0168_auto_20260627_2219.py | 32 ++++++++++++++----- contentcuration/contentcuration/models.py | 16 ++++++---- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py b/contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py index b0cda9f674..b19d0001f1 100644 --- a/contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py +++ b/contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py @@ -1,23 +1,39 @@ # Generated by Django 3.2.24 on 2026-06-27 22:19 - -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations +from django.db import models class Migration(migrations.Migration): dependencies = [ - ('contentcuration', '0167_add_organization'), + ("contentcuration", "0167_add_organization"), ] operations = [ migrations.AddField( - model_name='invitation', - name='organization', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contentcuration.organization'), + model_name="invitation", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="contentcuration.organization", + ), ), migrations.AddConstraint( - model_name='invitation', - constraint=models.CheckConstraint(check=models.Q(models.Q(('channel__isnull', False), ('organization__isnull', True)), models.Q(('channel__isnull', True), ('organization__isnull', False)), _connector='OR'), name='channel_organization_exclusivity'), + model_name="invitation", + constraint=models.CheckConstraint( + check=models.Q( + models.Q( + ("channel__isnull", False), ("organization__isnull", True) + ), + models.Q( + ("channel__isnull", True), ("organization__isnull", False) + ), + _connector="OR", + ), + name="channel_organization_exclusivity", + ), ), ] diff --git a/contentcuration/contentcuration/models.py b/contentcuration/contentcuration/models.py index f5b089d17f..d6bf5084ac 100644 --- a/contentcuration/contentcuration/models.py +++ b/contentcuration/contentcuration/models.py @@ -79,13 +79,17 @@ from contentcuration.constants import feedback from contentcuration.constants import user_history from contentcuration.constants.contentnode import kind_activity_map -from contentcuration.constants.organization_roles import ORGANIZATION_ADMIN, ORGANIZATION_EDITOR, ORGANIZATION_ROLE_STATUS_ACTIVE, ORGANIZATION_VIEWER, organization_role_choices +from contentcuration.constants.organization_roles import ORGANIZATION_ADMIN +from contentcuration.constants.organization_roles import ORGANIZATION_EDITOR +from contentcuration.constants.organization_roles import organization_role_choices +from contentcuration.constants.organization_roles import ORGANIZATION_ROLE_STATUS_ACTIVE from contentcuration.constants.organization_roles import ( organization_role_status_choices, ) from contentcuration.constants.organization_roles import ( ORGANIZATION_ROLE_STATUS_PENDING, ) +from contentcuration.constants.organization_roles import ORGANIZATION_VIEWER from contentcuration.db.models.expressions import Array from contentcuration.db.models.functions import ArrayRemove from contentcuration.db.models.functions import Unnest @@ -3742,9 +3746,9 @@ class Meta: models.CheckConstraint( name="channel_organization_exclusivity", check=( - Q(channel__isnull=False, organization__isnull=True) | - Q(channel__isnull=True, organization__isnull=False) - ) + Q(channel__isnull=False, organization__isnull=True) + | Q(channel__isnull=True, organization__isnull=False) + ), ) ] @@ -3762,12 +3766,12 @@ def _accept_channel_invitation(self, user): else: self.channel.viewers.remove(user) self.channel.editors.add(user) - + def _accept_organization_invitation(self, user): organization_role = OrganizationRole.objects.create( user=user, organization=self.organization, - status=ORGANIZATION_ROLE_STATUS_ACTIVE + status=ORGANIZATION_ROLE_STATUS_ACTIVE, ) if self.share_mode == VIEW_ACCESS: organization_role.role = ORGANIZATION_VIEWER