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..b19d0001f1 --- /dev/null +++ b/contentcuration/contentcuration/migrations/0168_auto_20260627_2219.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.24 on 2026-06-27 22:19 +import django.db.models.deletion +from django.db import migrations +from django.db import models + + +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..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 +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 @@ -102,6 +106,7 @@ EDIT_ACCESS = "edit" VIEW_ACCESS = "view" +ADMIN_ACCESS = "admin" DEFAULT_CONTENT_DEFAULTS = { "license": None, @@ -3730,21 +3735,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):