Skip to content

Commit

Permalink
Add permission request form for non admin
Browse files Browse the repository at this point in the history
  • Loading branch information
meomancer committed Jul 26, 2023
1 parent 17fc378 commit 9a30a89
Show file tree
Hide file tree
Showing 14 changed files with 527 additions and 232 deletions.
167 changes: 162 additions & 5 deletions django_project/core/api/access_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@
__date__ = '25/07/2023'
__copyright__ = ('Copyright 2023, Unicef')

from datetime import datetime

from django.conf import settings
from django.contrib.auth import get_user_model
from django.http import HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.urls import reverse
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from rest_framework.views import APIView

from core.email import send_email_with_html
from core.models.access_request import UserAccessRequest
from core.permissions import AdminAuthenticationPermission
from core.permissions import RoleContributorAuthenticationPermission
from core.serializer.access_request import (
AccessRequestSerializer,
AccessRequestDetailSerializer
Expand All @@ -37,7 +43,7 @@
class AccessRequestList(APIView):
"""List access request."""

permission_classes = (AdminAuthenticationPermission,)
permission_classes = (RoleContributorAuthenticationPermission,)

def get(self, request, request_type, *args, **kwargs):
"""Get access request list."""
Expand All @@ -46,7 +52,12 @@ def get(self, request, request_type, *args, **kwargs):
status = request.GET.get('status', None)
results = UserAccessRequest.objects.filter(
type=ACCESS_REQUEST_TYPE_LIST[request_type]
).order_by('-submitted_on')
)

# If not admin, just show the user's request
if not request.user.profile.is_admin:
results = results.filter(request_by=request.user)
results = results.order_by('-submitted_on')
if status:
results = results.filter(status=status)
return Response(status=200, data=AccessRequestSerializer(
Expand All @@ -57,11 +68,157 @@ def get(self, request, request_type, *args, **kwargs):
class AccessRequestDetail(APIView):
"""Approve/Reject access request."""

permission_classes = (AdminAuthenticationPermission,)
permission_classes = (RoleContributorAuthenticationPermission,)

def approval_request(
self, obj: UserAccessRequest, is_approve: bool, remarks: str
):
"""Approval request."""
obj.approved_date = datetime.now()
obj.approved_by = self.request.user
obj.approver_notes = remarks
obj.status = (
UserAccessRequest.RequestStatus.APPROVED if is_approve else
UserAccessRequest.RequestStatus.REJECTED
)
obj.save()

def notify_requester_new_user(self, obj: UserAccessRequest):
"""Notify new user."""
request_from = '-'
if obj.requester_first_name:
request_from = obj.requester_first_name
if obj.requester_last_name:
request_from = (
f'{request_from} {obj.requester_last_name}'
)
request_from = (
obj.requester_email if request_from == '-' else
request_from
)
url = self.request.build_absolute_uri(reverse('home-view'))
if not settings.DEBUG:
# if not dev env, then replace with https
url = url.replace('http://', 'https://')
context = {
'is_approved': (
obj.status == UserAccessRequest.RequestStatus.APPROVED
),
'request_from': request_from,
'has_admin_remarks': (
obj.approver_notes and len(obj.approver_notes) > 0
),
'admin_remarks': obj.approver_notes,
'url': url
}

if obj.status == UserAccessRequest.RequestStatus.APPROVED:
subject = 'Success! Your GeoSight account has been created'
else:
subject = 'Your GeoSight account request has been rejected'
send_email_with_html(
subject, [obj.requester_email], context,
'emails/notify_signup_request.html'
)

def notify_requester_access_request(self, obj: UserAccessRequest):
"""Approval requester for access request."""
request_from = '-'
if obj.requester_first_name:
request_from = obj.requester_first_name
if obj.requester_last_name:
request_from = (
f'{request_from} {obj.requester_last_name}'
)
request_from = (
obj.requester_email if request_from == '-' else
request_from
)
url = self.request.build_absolute_uri(reverse('home-view'))
if not settings.DEBUG:
# if not dev env, then replace with https
url = url.replace('http://', 'https://')
context = {
'is_approved': (
obj.status == UserAccessRequest.RequestStatus.APPROVED
),
'request_from': request_from,
'has_admin_remarks': (
obj.approver_notes and
len(obj.approver_notes) > 0
),
'admin_remarks': obj.approver_notes,
'url': url
}

if obj.status == UserAccessRequest.RequestStatus.APPROVED:
subject = 'Success! Your access request has been approved'
else:
subject = 'Your access request has been rejected'
send_email_with_html(
subject, [obj.requester_email], context,
'emails/notify_access_request.html',
)

def approve_new_user_access(self, obj: UserAccessRequest):
"""Approve new user."""
# create new user + set as viewer
user, created = User.objects.get_or_create(
username=obj.requester_email,
)
if created:
user.first_name = obj.requester_first_name
user.last_name = (
obj.requester_last_name if
obj.requester_last_name else ''
)
user.email = obj.requester_email
user.is_active = True
user.save()

obj.request_by = user
obj.save()
return user

def post(self, request, pk, *args, **kwargs):
"""Post data."""
if not request.user.is_staff:
return HttpResponseForbidden()

obj = get_object_or_404(UserAccessRequest, pk=pk)
is_approve = request.POST.get('is_approve', False)
remarks = request.POST.get('remarks', None)

# validate if status is Pending
if obj.status != UserAccessRequest.RequestStatus.PENDING:
return HttpResponseBadRequest(
'The request has been processed!'
)

# store approval
self.approval_request(obj, is_approve, remarks)
if obj.type == UserAccessRequest.RequestType.NEW_USER:
if is_approve:
self.approve_new_user_access(obj)

# notify requester
if obj.type == UserAccessRequest.RequestType.NEW_USER:
self.notify_requester_new_user(obj)
else:
self.notify_requester_access_request(obj)
return Response(
status=201
)

def get(self, request, pk, *args, **kwargs):
"""Get access request detail."""
request_obj = get_object_or_404(UserAccessRequest, pk=pk)
if request.user.profile.is_admin:
request_obj = get_object_or_404(UserAccessRequest, pk=pk)
else:
request_obj = get_object_or_404(
UserAccessRequest.objects.filter(request_by=request.user),
pk=pk
)
return Response(
status=200,
data=AccessRequestDetailSerializer(request_obj).data
Expand Down
48 changes: 48 additions & 0 deletions django_project/core/email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
GeoSight is UNICEF's geospatial web-based business intelligence platform.
Contact : [email protected]
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
"""
__author__ = '[email protected]'
__date__ = '26/07/2023'
__copyright__ = ('Copyright 2023, Unicef')

from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string

from core.models.preferences import SitePreferences


def send_email_with_html(
subject: str, recipient_list: list, context: dict, html_path: str
):
"""Send email with html."""
message = render_to_string(
html_path,
context
)
send_mail(
subject,
None,
settings.DEFAULT_FROM_EMAIL,
recipient_list,
html_message=message,
fail_silently=False
)


def send_email_to_admins_with_html(
subject: str, context: dict, html_path: str
):
"""Send email to admins."""
admin_emails = SitePreferences.preferences().default_admin_emails
if not admin_emails:
return
send_email_with_html(subject, admin_emails, context, html_path)
25 changes: 19 additions & 6 deletions django_project/core/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,29 @@

from rest_framework.permissions import BasePermission

from core.models.profile import ROLES


class AdminAuthenticationPermission(BasePermission):
"""Authentication just for the admin."""

def has_permission(self, request, view):
"""For checking if user has permission."""
user = request.user
if user and user.is_authenticated:
return user.is_superuser or user.is_staff or \
user.profile.role == ROLES.SUPER_ADMIN.name
return False
return user.is_authenticated and user.profile.is_admin


class RoleContributorAuthenticationPermission(BasePermission):
"""Authentication just for Role Contributor."""

def has_permission(self, request, view):
"""For checking if user has permission."""
user = request.user
return user.is_authenticated and user.profile.is_contributor


class RoleCreatorAuthenticationPermission(BasePermission):
"""Authentication just for Role Contributor."""

def has_permission(self, request, view):
"""For checking if user has permission."""
user = request.user
return user.is_authenticated and user.profile.is_creator
18 changes: 17 additions & 1 deletion django_project/core/serializer/access_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ class AccessRequestSerializer(serializers.ModelSerializer):

def get_name(self, obj: UserAccessRequest):
"""Get name of access."""
if obj.requester_first_name and obj.requester_last_name:
if obj.request_by:
return f'{obj.request_by.first_name} {obj.request_by.last_name}'
elif obj.requester_first_name and obj.requester_last_name:
return f'{obj.requester_first_name} {obj.requester_last_name}'
elif obj.requester_first_name:
return obj.requester_first_name
Expand All @@ -48,8 +50,22 @@ class Meta: # noqa: D106
class AccessRequestDetailSerializer(serializers.ModelSerializer):
"""Access request serializer for detail."""

requester_first_name = serializers.SerializerMethodField()
requester_last_name = serializers.SerializerMethodField()
approval_by = serializers.SerializerMethodField()

def get_requester_first_name(self, obj: UserAccessRequest):
"""Get name of access."""
if obj.request_by:
return obj.request_by.first_name
return '-'

def get_requester_last_name(self, obj: UserAccessRequest):
"""Get name of access."""
if obj.request_by:
return obj.request_by.last_name
return '-'

def get_approval_by(self, obj: UserAccessRequest):
"""Get approval by username."""
if obj.approved_by:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { ThemeButton } from "../Elements/Button";
* @param {React.Component} children React component to be rendered.
*/
export const ConfirmDialog = forwardRef(
({ header, onConfirmed, onRejected, children }, ref
({ header, onConfirmed, onRejected, children, ...props }, ref
) => {
const [open, setOpen] = useState(false);

Expand Down Expand Up @@ -65,6 +65,7 @@ export const ConfirmDialog = forwardRef(
 
<ThemeButton
variant="secondary Basic"
disabled={props.disabledConfirm}
onClick={() => {
onConfirmed()
setOpen(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const Notification = forwardRef(
// Ready check
useImperativeHandle(ref, () => ({
notify(newMessage, newSeverity = NotificationStatus.INFO) {
console.log('notify')
setState(
{ message: newMessage, open: true, severity: newSeverity }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default function AccessRequestDetail({ pageName }) {
data['is_approve'] = true
}
setSubmitted(true)
axios.post(window.location.href, data, {
axios.post(urls.api.detail, data, {
headers: {
'Content-Type': 'multipart/form-data',
'X-CSRFToken': csrfmiddlewaretoken
Expand All @@ -81,7 +81,7 @@ export default function AccessRequestDetail({ pageName }) {
return <Admin
pageName={pageName}
rightHeader={
data?.status === 'PENDING' ? <Fragment>
data?.status === 'PENDING' && user.is_staff ? <Fragment>

{/* APPROVE */}
<ConfirmDialog
Expand Down
Loading

0 comments on commit 9a30a89

Please sign in to comment.