Skip to content

Commit

Permalink
allow multiple organisations per user (#231)
Browse files Browse the repository at this point in the history
* allow multiple organisations per user

* allow multiple invitations

* allow multiple invitations

* update function name to follow naming convention

---------

Co-authored-by: tinashe <[email protected]>
  • Loading branch information
tinashechiraya and tinashe authored Jan 21, 2025
1 parent f2c868f commit 47b5001
Show file tree
Hide file tree
Showing 11 changed files with 19,517 additions and 244 deletions.
285 changes: 201 additions & 84 deletions django_project/base/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import (
Organisation,
OrganisationInvitationDetail,
UserProfile,
OrganisationInvitation,
UserOrganisations
Expand All @@ -12,6 +13,83 @@
import json
from django.core.mail import EmailMultiAlternatives
from django.shortcuts import get_object_or_404
from django.contrib import messages



def update_invite(modeladmin, request, invitation):
try:
invitation_detail = OrganisationInvitationDetail.objects.get(
invitation=invitation,
organisation=invitation.organisation
)
invitation_detail.accepted = True
invitation_detail.organisation = invitation.organisation
invitation_detail.save()
except OrganisationInvitationDetail.DoesNotExist:
modeladmin.message_user(
request,
"OrganisationInvitationDetail not found for this request.",
level="error"
)
return


def send_organisation_creation_email(inviter, organisation):
"""
Sends an email to the inviter notifying them about the
creation of a new organisation
and their role as the manager.
"""
email_body = render_to_string(
"organization_manager_notification.html",
{
"user": inviter,
"organisation": organisation,
"support_email": "[email protected]",
"platform_url": settings.DJANGO_BACKEND_URL,
},
)

try:
email = EmailMultiAlternatives(
subject="Your Role as Organisation Manager",
body="",
from_email=settings.NO_REPLY_EMAIL,
to=[inviter.email],
)
email.attach_alternative(email_body, "text/html")
email.send()
except Exception as e:
raise Exception(f"Failed to send email: {str(e)}")


def send_join_acceptance_email(inviter, organisation):
"""
Sends an email to the inviter notifying them that their
join request has been accepted
and the user has been added as a member.
"""
email_body = render_to_string(
"accepted_organization_request.html",
{
"user": inviter,
"organisation": organisation,
"link": settings.DJANGO_BACKEND_URL,
},
)

try:
email = EmailMultiAlternatives(
subject="Your Join Request Has Been Approved",
body="",
from_email=settings.NO_REPLY_EMAIL,
to=[inviter.email],
)
email.attach_alternative(email_body, "text/html")
email.send()
except Exception as e:
raise Exception(f"Failed to send email: {str(e)}")



Expand All @@ -21,109 +99,150 @@ def approve_join_request(modeladmin, request, queryset):
Admin action to approve join/add requests.
Creates organisations for 'add_organisation' requests and assigns roles.
"""
for invitation in queryset:
if invitation.request_type == "add_organisation":
# Parse metadata from the invitation
metadata = json.loads(invitation.metadata or "{}")
organisation_name = metadata.get("organisationName", "")

# Create the organisation
organisation = Organisation.objects.create(name=organisation_name)

# Assign the inviter as the organisation manager
inviter = invitation.inviter
user_profile = get_object_or_404(UserProfile, user=inviter)
UserOrganisations.objects.create(
user_profile=user_profile,
organisation=organisation,
user_type='manager'
)
# Check if there is a superuser
if not User.objects.filter(is_superuser=True).exists():
modeladmin.message_user(
request,
"No admin user found to process the request.",
level=messages.ERROR
)
return

# Notify the inviter
email_body = render_to_string(
"organization_manager_notification.html",
{
"user": inviter,
"organisation": organisation,
"support_email": "[email protected]",
"platform_url": settings.DJANGO_BACKEND_URL,
},
)
for invitation in queryset:
try:
# Get the related OrganisationInvitationDetail
try:
email = EmailMultiAlternatives(
subject="Your Role as Organisation Manager",
body="",
from_email=settings.NO_REPLY_EMAIL,
to=[inviter.email],
invitation_instance = OrganisationInvitation.objects.get(
email=invitation
)
email.attach_alternative(email_body, "text/html")
email.send()
except Exception as e:
except OrganisationInvitation.DoesNotExist:
try:
invitation_instance = OrganisationInvitation.objects.get(
invitation_ptr=invitation
)
except OrganisationInvitation.DoesNotExist:
invitation_instance = None


invitation_detail = OrganisationInvitationDetail.objects.filter(
invitation=invitation_instance).first()

if invitation_detail is None:
modeladmin.message_user(
request,
f"Failed to send email: {str(e)}",
level="error",
f"OrganisationInvitationDetail not found: {invitation.id}",
level=messages.ERROR
)
continue

if invitation_detail.request_type == "add_organisation":
# Parse metadata from the invitation
metadata = json.loads(invitation_detail.metadata or "{}")
organisation_name = metadata.get("organisationName", "")

if not organisation_name:
modeladmin.message_user(
request,
"Organisation name missing from metadata.",
level=messages.ERROR
)
continue

# Create the organisation
organisation = Organisation.objects.create(
name=organisation_name
)

# Assign the inviter as the organisation manager
inviter = invitation_instance.inviter
user_profile = get_object_or_404(UserProfile, user=inviter)

modeladmin.message_user(
request,
f"Organisation '{organisation.name}' created and request "
"approved.",
)
return

elif invitation.request_type == "join_organisation":
# Process join requests
inviter = invitation.inviter
user_profile = get_object_or_404(UserProfile, user=inviter)
organisation = invitation.organisation

UserOrganisations.objects.create(
user_profile=user_profile,
organisation=organisation,
user_type='member'
)

email_body = render_to_string(
"accepted_organization_request.html",
{
"user": inviter,
"organisation": organisation,
"link": settings.DJANGO_BACKEND_URL,
},
)
try:
email = EmailMultiAlternatives(
subject="Your join request has been approved",
body="",
from_email=settings.NO_REPLY_EMAIL,
to=[inviter.email],
# Create UserOrganisations
UserOrganisations.objects.create(
user_profile=user_profile,
organisation=organisation,
user_type='manager'
)
email.attach_alternative(email_body, "text/html")
email.send()
except Exception as e:

# Update invitation status
update_invite(modeladmin, request, invitation_instance)

# Send the email (optional, catch any errors here)
try:
send_organisation_creation_email(inviter, organisation)
except Exception as e:
modeladmin.message_user(
request,
f"Failed to send email: {str(e)}",
level=messages.ERROR
)

modeladmin.message_user(
request,
f"Failed to send email: {str(e)}",
level="error",
f"Organisation '{organisation.name}' created and "
f"request approved.",
level=messages.SUCCESS
)

elif invitation_detail.request_type == "join_organisation":
# Process join requests
inviter_email = invitation_instance.email
inviter_user = get_object_or_404(User, email=inviter_email)
user_profile = get_object_or_404(
UserProfile,
user=inviter_user
)
organisation = invitation.organisation

# Create UserOrganisations
UserOrganisations.objects.create(
user_profile=user_profile,
organisation=organisation,
user_type='member'
)

update_invite(modeladmin, request, invitation_instance)

# Send the acceptance email (optional, catch any errors here)
try:
send_join_acceptance_email(
invitation_instance.inviter,
organisation
)
except Exception as e:
modeladmin.message_user(
request,
f"Failed to send email: {str(e)}",
level=messages.ERROR
)

modeladmin.message_user(
request,
"Individual has been added.",
level=messages.SUCCESS
)

except Exception as e:
modeladmin.message_user(
request, "Individual has been added."
request,
f"An error occurred while processing the request: {str(e)}",
level=messages.ERROR
)
return





class OrganisationInvitationAdmin(admin.ModelAdmin):
list_display = ('email', 'request_type', 'organisation', 'inviter')
@admin.register(OrganisationInvitationDetail)
class OrganisationInvitationDetailAdmin(admin.ModelAdmin):
list_display = (
'invitation',
'organisation',
'accepted',
'request_type'
)
actions = [approve_join_request]
list_filter = ('organisation', 'request_type')
search_fields = ('email', 'organisation__name', 'inviter__username')
list_filter = ('organisation', 'accepted', 'request_type')
search_fields = ('invitation__email', 'organisation__name')


class UserProfileInline(admin.StackedInline):
Expand Down Expand Up @@ -200,10 +319,8 @@ class OrganisationAdmin(admin.ModelAdmin):
readonly_fields = ('created_at', 'updated_at')



admin.site.register(UserOrganisations, UserOrganisationsAdmin)

admin.site.unregister(User)
admin.site.register(User, UserAdmin)
admin.site.unregister(OrganisationInvitation)
admin.site.register(OrganisationInvitation, OrganisationInvitationAdmin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 4.2.15 on 2025-01-15 10:15

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('base', '0006_userprofile_profile_image'),
]

operations = [
migrations.CreateModel(
name='OrganisationInvitationDetail',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('aceepted', models.BooleanField(default=False)),
('invitation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='details', to='base.organisationinvitation')),
('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitation_details', to='base.organisation')),
],
options={
'unique_together': {('invitation', 'organisation')},
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.15 on 2025-01-15 17:14

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('base', '0007_organisationinvitationdetail'),
]

operations = [
migrations.RenameField(
model_name='organisationinvitationdetail',
old_name='aceepted',
new_name='accepted',
),
]
Loading

0 comments on commit 47b5001

Please sign in to comment.