Skip to content

Commit

Permalink
Create endpoints to handle organization invitations
Browse files Browse the repository at this point in the history
  • Loading branch information
rajpatel24 committed Dec 30, 2024
1 parent b6a2829 commit 64a2b55
Show file tree
Hide file tree
Showing 14 changed files with 722 additions and 8 deletions.
18 changes: 18 additions & 0 deletions kobo/apps/organizations/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
INVITATION_OWNER_ERROR = (
'This account is already the owner of {organization_name}. '
'You cannot join multiple organizations with the same account. '
'To accept this invitation, you must either transfer ownership of '
'{organization_name} to a different account or sign in using a different '
'account with the same email address. If you do not already have another '
'account, you can create one.'
)

INVITATION_MEMBER_ERROR = (
'This account is already a member in {organization_name}. '
'You cannot join multiple organizations with the same account. '
'To accept this invitation, sign in using a different account with the '
'same email address. If you do not already have another account, you can '
'create one.'
)
ORG_ADMIN_ROLE = 'admin'
ORG_EXTERNAL_ROLE = 'external'
ORG_MEMBER_ROLE = 'member'
ORG_OWNER_ROLE = 'owner'
USER_DOES_NOT_EXIST_ERROR = \
'User with username or email {invitee} does not exist or is not active.'
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 4.2.15 on 2024-12-20 14:31

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('organizations', '0009_update_db_state_with_auth_user'),
]

operations = [
migrations.AddField(
model_name='organizationinvitation',
name='status',
field=models.CharField(
choices=[
('accepted', 'Accepted'),
('cancelled', 'Cancelled'),
('complete', 'Complete'),
('declined', 'Declined'),
('expired', 'Expired'),
('failed', 'Failed'),
('in_progress', 'In Progress'),
('pending', 'Pending'),
('resent', 'Resent'),
],
default='pending',
max_length=11,
),
),
]
95 changes: 94 additions & 1 deletion kobo/apps/organizations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from organizations.utils import create_organization as create_organization_base

from kpi.fields import KpiUidField
from kpi.utils.mailer import EmailMessage, Mailer

from .constants import (
ORG_ADMIN_ROLE,
Expand All @@ -46,6 +47,19 @@ class OrganizationType(models.TextChoices):
NONE = 'none', t('I am not associated with any organization')


class OrganizationInviteStatusChoices(models.TextChoices):

ACCEPTED = 'accepted'
CANCELLED = 'cancelled'
COMPLETE = 'complete'
DECLINED = 'declined'
EXPIRED = 'expired'
FAILED = 'failed'
IN_PROGRESS = 'in_progress'
PENDING = 'pending'
RESENT = 'resent'


class Organization(AbstractOrganization):
id = KpiUidField(uid_prefix='org', primary_key=True)
mmo_override = models.BooleanField(
Expand Down Expand Up @@ -273,7 +287,86 @@ class OrganizationOwner(AbstractOrganizationOwner):


class OrganizationInvitation(AbstractOrganizationInvitation):
pass
status = models.CharField(
max_length=11,
choices=OrganizationInviteStatusChoices.choices,
default=OrganizationInviteStatusChoices.PENDING,
)

def send_acceptance_email(self):

template_variables = {
'sender_username': self.invited_by.username,
'sender_email': self.invited_by.email,
'recipient_username': self.invitee.username,
'recipient_email': self.invitee.email,
'organization_name': self.invited_by.organization.name,
'base_url': settings.KOBOFORM_URL,
}

email_message = EmailMessage(
to=self.invited_by.email,
subject=t('KoboToolbox organization invitation accepted'),
plain_text_content_or_template='emails/accepted_invite.txt',
template_variables=template_variables,
html_content_or_template='emails/accepted_invite.html',
language=self.invitee.extra_details.data.get('last_ui_language'),
)

Mailer.send(email_message)

def send_invite_email(self):
"""
Sends an email to invite a user to join a team as an admin.
"""
template_variables = {
'sender_username': self.invited_by.username,
'sender_email': self.invited_by.email,
'recipient_username': (
self.invitee.username
if self.invitee
else self.invitee_identifier
),
'organization_name': self.invited_by.organization.name,
'base_url': settings.KOBOFORM_URL,
'invite_uid': self.guid,
}

email_message = EmailMessage(
to=self.invitee.email if self.invitee else self.invitee_identifier,
subject='Invitation to Join the Organization',
plain_text_content_or_template='emails/new_invite.txt',
template_variables=template_variables,
html_content_or_template='emails/new_invite.html',
language=(
self.invitee.extra_details.data.get('last_ui_language')
if self.invitee
else 'en'
),
)

Mailer.send(email_message)

def send_refusal_email(self):
template_variables = {
'sender_username': self.invited_by.username,
'sender_email': self.invited_by.email,
'recipient_username': self.invitee.username,
'recipient_email': self.invitee.email,
'organization_name': self.invited_by.organization.name,
'base_url': settings.KOBOFORM_URL,
}

email_message = EmailMessage(
to=self.invited_by.email,
subject=t('KoboToolbox organization invitation declined'),
plain_text_content_or_template='emails/declined_invite.txt',
template_variables=template_variables,
html_content_or_template='emails/declined_invite.html',
language=self.invitee.extra_details.data.get('last_ui_language'),
)

Mailer.send(email_message)


create_organization = partial(create_organization_base, model=Organization)
Loading

0 comments on commit 64a2b55

Please sign in to comment.