Skip to content

Commit

Permalink
feat: ✨ Adds a BaseOrganizationJoinRequest model to Organizations (
Browse files Browse the repository at this point in the history
…hacktoolkit#441)

- Adds a join request model to handle requests made to join an
organization.
- Invitations and requests should cover the logic to join an
organization in multiple ways.
  • Loading branch information
shreyastelkar authored Jul 11, 2024
1 parent a9d85d0 commit 236278a
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 14 deletions.
22 changes: 21 additions & 1 deletion apps/organizations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ class HtkOrganizationInvitationInline(admin.TabularInline):
can_delete = True


class HtkOrganizationJoinRequestInline(admin.TabularInline):
model = resolve_model_dynamically(
htk_setting('HTK_ORGANIZATION_JOIN_REQUEST_MODEL')
)
extra = 0
can_delete = True


class HtkOrganizationTeamMemberInline(admin.TabularInline):
model = resolve_model_dynamically(
htk_setting('HTK_ORGANIZATION_TEAM_MEMBER_MODEL')
Expand Down Expand Up @@ -68,7 +76,8 @@ class HtkOrganizationAdmin(admin.ModelAdmin):
HtkOrganizationAttributeInline,
# HtkOrganizationMemberInline,
# HtkOrganizationTeamInline,
# HtkOrganizationInvitationInline,
HtkOrganizationInvitationInline,
HtkOrganizationJoinRequestInline,
)


Expand Down Expand Up @@ -114,6 +123,17 @@ class HtkOrganizationInvitationAdmin(admin.ModelAdmin):
)


class HtkOrganizationJoinRequestAdmin(admin.ModelAdmin):
list_display = (
'id',
'organization',
'user',
'accepted',
'timestamp',
'responded_at',
)


class HtkOrganizationTeamAdmin(admin.ModelAdmin):
list_display = (
'id',
Expand Down
1 change: 1 addition & 0 deletions apps/organizations/constants/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
HTK_ORGANIZATION_ATTRIBUTE_MODEL = 'organizations.OrganizationAttribute'
HTK_ORGANIZATION_MEMBER_MODEL = 'organizations.OrganizationMember'
HTK_ORGANIZATION_INVITATION_MODEL = 'organizations.OrganizationInvitation'
HTK_ORGANIZATION_JOIN_REQUEST_MODEL = 'organizations.OrganizationJoinRequest'
HTK_ORGANIZATION_TEAM_MODEL = 'organizations.OrganizationTeam'
HTK_ORGANIZATION_TEAM_MEMBER_MODEL = 'organizations.OrganizationTeamMember'
HTK_ORGANIZATION_TEAM_POSITION_MODEL = 'organizations.OrganizationTeamPosition'
Expand Down
100 changes: 87 additions & 13 deletions apps/organizations/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Python Standard Library Imports
import hashlib
import uuid
from typing import (
Any,
Expand All @@ -10,8 +9,6 @@
from six.moves import collections_abc

# Django Imports
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models

# HTK Imports
Expand Down Expand Up @@ -185,7 +182,13 @@ def add_member(self, user, role, allow_duplicates=False):

def add_owner(self, user):
OrganizationMember = get_model_organization_member()
new_owner = self.add_member(user, OrganizationMemberRoles.OWNER)

# Owner should be an existing member
new_owner = (
self.add_member(user, OrganizationMemberRoles.OWNER)
if OrganizationMember.objects.filter(user=user)
else None
)
return new_owner

def modify_member_role(self, user, role):
Expand Down Expand Up @@ -213,12 +216,10 @@ class Meta:
)

def __str__(self):
value = (
'{organization_name} Member - {member_name} (member_email)'.format(
organization_name=self.organization.name,
member_name=self.user.profile.get_full_name(),
member_email=self.user.email,
)
value = '{organization_name} Member - {member_name} ({member_email})'.format(
organization_name=self.organization.name,
member_name=self.user.profile.get_full_name(),
member_email=self.user.email,
)
return value

Expand Down Expand Up @@ -246,10 +247,9 @@ def set_role(self, role, should_activate=False):

class BaseAbstractOrganizationInvitation(HtkBaseModel):
organization = fk_organization(related_name='invitations', required=True)
invited_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
invited_by = fk_user(
related_name='organization_invitations_sent',
required=True,
)
user = fk_user(
related_name='organization_invitations',
Expand Down Expand Up @@ -335,6 +335,80 @@ def build_notification_message__declined(self):
return msg


class BaseAbstractOrganizationJoinRequest(HtkBaseModel):
organization = fk_organization(related_name='join_requests', required=True)
user = fk_user(
related_name='organization_join_requests',
)
accepted = models.BooleanField(
default=None, null=True
) # True: accepted, False: declined, None: not responded yet
timestamp = models.DateTimeField(auto_now_add=True)
responded_at = models.DateTimeField(blank=True, null=True, default=None)

class Meta:
abstract = True
verbose_name = 'Organization Join Request'

def __str__(self):
value = '{organization_name} - {user} - {status}'.format(
organization_name=self.organization.name,
user=self.user,
status=self.status,
)
return value

def json_encode(self) -> Dict[str, Any]:
"""Returns a dictionary that can be `json.dumps()`-ed as a JSON representation of this object"""
value = {
'id': self.id,
'organization': self.organization.name,
'user': self.user.profile.get_full_name() if self.user else None,
'accepted': self.accepted,
'requested_at': self.timestamp,
'responded_at': self.responded_at,
}
return value

##
# properties

@property
def status(self) -> str:
status = (
'Requested'
if self.accepted is None
else 'Accepted' if self.accepted else 'Declined'
)

return status

##
# Notifications

def _build_notification_message(self, subject, action):
msg = '{subject_name} ({subject_username}<{subject_email}>) request to join Organization <{organization_name}> has been {action}.'.format( # noqa: E501
action=action,
subject_name=subject.profile.get_full_name(),
subject_username=subject.username,
subject_email=subject.email,
organization_name=self.organization.name,
)
return msg

def build_notification_message__created(self):
msg = self._build_notification_message(self.user, 'sent')
return msg

def build_notification_message__accepted(self):
msg = self._build_notification_message(self.user, 'accepted')
return msg

def build_notification_message__declined(self):
msg = self._build_notification_message(self.user, 'declined')
return msg


class BaseAbstractOrganizationTeam(HtkBaseModel):
name = models.CharField(max_length=128)
organization = fk_organization(related_name='teams', required=True)
Expand Down

0 comments on commit 236278a

Please sign in to comment.