-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
allow multiple organisations per user (#231)
* 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
1 parent
f2c868f
commit 47b5001
Showing
11 changed files
with
19,517 additions
and
244 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin | ||
from .models import ( | ||
Organisation, | ||
OrganisationInvitationDetail, | ||
UserProfile, | ||
OrganisationInvitation, | ||
UserOrganisations | ||
|
@@ -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)}") | ||
|
||
|
||
|
||
|
@@ -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): | ||
|
@@ -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) |
26 changes: 26 additions & 0 deletions
26
django_project/base/migrations/0007_organisationinvitationdetail.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')}, | ||
}, | ||
), | ||
] |
18 changes: 18 additions & 0 deletions
18
django_project/base/migrations/0008_rename_aceepted_organisationinvitationdetail_accepted.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
), | ||
] |
Oops, something went wrong.