You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
591 lines
25 KiB
591 lines
25 KiB
import logging
|
|
from django.contrib.auth import get_user_model
|
|
from django.db.models import Q, F
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils.translation import gettext_lazy as _
|
|
from rest_framework import status
|
|
from rest_framework.generics import GenericAPIView
|
|
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse, inline_serializer
|
|
|
|
from apps.profiles.models import (
|
|
Region,
|
|
Institution,
|
|
InstitutionMember,
|
|
InstitutionTimeline,
|
|
InstitutionMedia,
|
|
InstitutionFollower,
|
|
)
|
|
from apps.profiles.permissions import (
|
|
IsInstitutionAdminOrReadOnly,
|
|
IsInstitutionEditorOrReadOnly,
|
|
CanManageInstitutionMembers,
|
|
)
|
|
from apps.profiles.serializers import (
|
|
RegionMiniSerializer,
|
|
RegionSerializer,
|
|
InstitutionListSerializer,
|
|
InstitutionDetailSerializer,
|
|
InstitutionCreateUpdateSerializer,
|
|
InstitutionTimelineSerializer,
|
|
InstitutionMediaSerializer,
|
|
InstitutionMemberSerializer,
|
|
InstitutionMemberAddSerializer,
|
|
FollowResponseSerializer,
|
|
)
|
|
from utils.pagination import StandardResultsSetPagination, InstitutionPagination
|
|
|
|
logger = logging.getLogger(__name__)
|
|
User = get_user_model()
|
|
|
|
|
|
class RegionListCreateView(GenericAPIView):
|
|
serializer_class = RegionSerializer
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [IsAuthenticated()]
|
|
return [AllowAny()]
|
|
|
|
def get_queryset(self):
|
|
if getattr(self, 'swagger_fake_view', False):
|
|
return Region.objects.none()
|
|
qs = Region.objects.all()
|
|
if not (self.request.user and self.request.user.is_authenticated and self.request.user.is_staff):
|
|
qs = qs.filter(is_active=True)
|
|
search = self.request.query_params.get('search')
|
|
if search:
|
|
qs = qs.filter(Q(name__icontains=search) | Q(code__icontains=search) | Q(description__icontains=search))
|
|
return qs.order_by('name')
|
|
|
|
@extend_schema(
|
|
summary="List all regions",
|
|
description="Returns list of available regions for dropdowns and filtering.",
|
|
parameters=[
|
|
OpenApiParameter('search', str, description='Search query across region name, code, description'),
|
|
],
|
|
responses={200: RegionSerializer(many=True)},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
qs = self.get_queryset()
|
|
serializer = self.get_serializer(qs, many=True)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Create a new region",
|
|
description="Creates a new region (Admin or Staff required).",
|
|
request=RegionSerializer,
|
|
responses={201: RegionSerializer},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def post(self, request, *args, **kwargs):
|
|
if not (request.user.is_staff or getattr(request.user, 'is_super_admin', False)):
|
|
return Response({'error': _("Staff or admin privileges required to create regions.")}, status=status.HTTP_403_FORBIDDEN)
|
|
serializer = self.get_serializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
region = serializer.save()
|
|
return Response(self.get_serializer(region).data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
class RegionDetailView(GenericAPIView):
|
|
serializer_class = RegionSerializer
|
|
|
|
def get_permissions(self):
|
|
if self.request.method in ['PUT', 'PATCH', 'DELETE']:
|
|
return [IsAuthenticated()]
|
|
return [AllowAny()]
|
|
|
|
def get_object(self):
|
|
lookup = self.kwargs.get('pk_or_slug')
|
|
if str(lookup).isdigit():
|
|
return get_object_or_404(Region, id=int(lookup))
|
|
return get_object_or_404(Region, slug=lookup)
|
|
|
|
@extend_schema(
|
|
summary="Get region details",
|
|
description="Retrieves a region by ID or slug.",
|
|
responses={200: RegionSerializer},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def get(self, request, pk_or_slug, *args, **kwargs):
|
|
region = self.get_object()
|
|
return Response(self.get_serializer(region).data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Update region",
|
|
description="Updates region details (Admin/Staff only).",
|
|
request=RegionSerializer,
|
|
responses={200: RegionSerializer},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def patch(self, request, pk_or_slug, *args, **kwargs):
|
|
if not (request.user.is_staff or getattr(request.user, 'is_super_admin', False)):
|
|
return Response({'error': _("Staff or admin privileges required to update regions.")}, status=status.HTTP_403_FORBIDDEN)
|
|
region = self.get_object()
|
|
serializer = self.get_serializer(region, data=request.data, partial=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Delete region",
|
|
description="Deletes a region (Admin/Staff only). Cannot delete if institutions are linked.",
|
|
responses={204: OpenApiResponse(description="Region deleted")},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def delete(self, request, pk_or_slug, *args, **kwargs):
|
|
if not (request.user.is_staff or getattr(request.user, 'is_super_admin', False)):
|
|
return Response({'error': _("Staff or admin privileges required to delete regions.")}, status=status.HTTP_403_FORBIDDEN)
|
|
region = self.get_object()
|
|
if region.institutions.exists():
|
|
return Response({'error': _("Cannot delete region that has institutions assigned to it.")}, status=status.HTTP_400_BAD_REQUEST)
|
|
region.delete()
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
def get_institution_by_id_or_slug(lookup_val):
|
|
if str(lookup_val).isdigit():
|
|
return get_object_or_404(Institution, id=int(lookup_val), is_active=True)
|
|
return get_object_or_404(Institution, slug=lookup_val, is_active=True)
|
|
|
|
|
|
class InstitutionListCreateView(GenericAPIView):
|
|
serializer_class = InstitutionListSerializer
|
|
pagination_class = InstitutionPagination
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [IsAuthenticated()]
|
|
return [AllowAny()]
|
|
|
|
def get_queryset(self):
|
|
if getattr(self, 'swagger_fake_view', False):
|
|
return Institution.objects.none()
|
|
|
|
qs = Institution.objects.filter(is_active=True).select_related('region')
|
|
|
|
# Filters
|
|
search = self.request.query_params.get('search')
|
|
if search:
|
|
qs = qs.filter(
|
|
Q(name__icontains=search) |
|
|
Q(city__icontains=search) |
|
|
Q(country__icontains=search) |
|
|
Q(region__name__icontains=search) |
|
|
Q(description__icontains=search)
|
|
)
|
|
|
|
region_param = self.request.query_params.get('region')
|
|
if region_param:
|
|
if region_param.isdigit():
|
|
qs = qs.filter(region_id=int(region_param))
|
|
else:
|
|
qs = qs.filter(Q(region__slug=region_param) | Q(region__name__iexact=region_param))
|
|
|
|
country = self.request.query_params.get('country')
|
|
if country:
|
|
qs = qs.filter(country__iexact=country)
|
|
|
|
city = self.request.query_params.get('city')
|
|
if city:
|
|
qs = qs.filter(city__iexact=city)
|
|
|
|
inst_type = self.request.query_params.get('type')
|
|
if inst_type:
|
|
qs = qs.filter(type=inst_type)
|
|
|
|
tag = self.request.query_params.get('tag')
|
|
if tag:
|
|
qs = qs.filter(tags__icontains=tag)
|
|
|
|
is_featured = self.request.query_params.get('is_featured')
|
|
if is_featured is not None:
|
|
qs = qs.filter(is_featured=is_featured.lower() in ['true', '1'])
|
|
|
|
verification = self.request.query_params.get('verification_status')
|
|
if verification:
|
|
qs = qs.filter(verification_status=verification)
|
|
|
|
managed_only = self.request.query_params.get('managed_only')
|
|
if managed_only and managed_only.lower() in ['true', '1']:
|
|
if self.request.user and self.request.user.is_authenticated:
|
|
user = self.request.user
|
|
if not (getattr(user, 'is_super_admin', False) or getattr(user, 'is_superuser', False)):
|
|
if hasattr(user, 'get_managed_regions_queryset'):
|
|
qs = qs.filter(region__in=user.get_managed_regions_queryset())
|
|
else:
|
|
qs = qs.none()
|
|
|
|
return qs.order_by('-is_featured', '-created_at')
|
|
|
|
@extend_schema(
|
|
summary="List active institutions",
|
|
description="Returns a paginated list of mosques, cultural centers, and institutes with filtering.",
|
|
parameters=[
|
|
OpenApiParameter('search', str, description='Search query across name, city, country, region, description'),
|
|
OpenApiParameter('region', str, description='Filter by region ID, slug, or name'),
|
|
OpenApiParameter('managed_only', bool, description="Filter to return only institutions within caller's managed regions"),
|
|
OpenApiParameter('country', str, description='Filter by country name'),
|
|
OpenApiParameter('city', str, description='Filter by city name'),
|
|
OpenApiParameter('type', str, description='Filter by institution type (mosque, cultural_center, etc.)'),
|
|
OpenApiParameter('tag', str, description='Filter by tag string'),
|
|
OpenApiParameter('is_featured', bool, description='Filter by featured status'),
|
|
OpenApiParameter('verification_status', str, description='Filter by verification status (pending, approved, rejected)'),
|
|
],
|
|
responses={200: InstitutionListSerializer(many=True)},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
queryset = self.filter_queryset(self.get_queryset())
|
|
page = self.paginate_queryset(queryset)
|
|
if page is not None:
|
|
serializer = self.get_serializer(page, many=True, context={'request': request})
|
|
return self.get_paginated_response(serializer.data)
|
|
|
|
serializer = self.get_serializer(queryset, many=True, context={'request': request})
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Register a new institution",
|
|
description="Registers a new institution profile and designates the creator as primary Administrator.",
|
|
request=InstitutionCreateUpdateSerializer,
|
|
responses={
|
|
201: InstitutionDetailSerializer,
|
|
400: OpenApiResponse(description="Validation error"),
|
|
401: OpenApiResponse(description="Authentication required"),
|
|
},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def post(self, request, *args, **kwargs):
|
|
serializer = InstitutionCreateUpdateSerializer(data=request.data, context={'request': request})
|
|
serializer.is_valid(raise_exception=True)
|
|
institution = serializer.save()
|
|
|
|
response_serializer = InstitutionDetailSerializer(institution, context={'request': request})
|
|
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
class InstitutionDetailView(GenericAPIView):
|
|
serializer_class = InstitutionDetailSerializer
|
|
queryset = Institution.objects.filter(is_active=True)
|
|
|
|
def get_permissions(self):
|
|
if self.request.method in ['PATCH', 'PUT', 'DELETE']:
|
|
return [IsAuthenticated(), IsInstitutionEditorOrReadOnly()]
|
|
return [AllowAny()]
|
|
|
|
def get_object(self):
|
|
lookup = self.kwargs.get('pk_or_slug')
|
|
inst = get_institution_by_id_or_slug(lookup)
|
|
self.check_object_permissions(self.request, inst)
|
|
return inst
|
|
|
|
@extend_schema(
|
|
summary="Get full institution profile",
|
|
description="Retrieves complete institution information, historical timeline milestones, and media gallery.",
|
|
responses={
|
|
200: InstitutionDetailSerializer,
|
|
404: OpenApiResponse(description="Institution not found"),
|
|
},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def get(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = self.get_object()
|
|
serializer = InstitutionDetailSerializer(institution, context={'request': request})
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Update institution profile",
|
|
description="Updates details of an institution. Requires Admin or Editor role on the institution.",
|
|
request=InstitutionCreateUpdateSerializer,
|
|
responses={
|
|
200: InstitutionDetailSerializer,
|
|
403: OpenApiResponse(description="Permission denied"),
|
|
},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def patch(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = self.get_object()
|
|
serializer = InstitutionCreateUpdateSerializer(
|
|
institution,
|
|
data=request.data,
|
|
partial=True,
|
|
context={'request': request}
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
response_serializer = InstitutionDetailSerializer(institution, context={'request': request})
|
|
return Response(response_serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Deactivate / Delete institution",
|
|
description="Deactivates an institution. Permitted for institution administrator or platform administrator.",
|
|
responses={
|
|
204: OpenApiResponse(description="Institution successfully deactivated"),
|
|
403: OpenApiResponse(description="Permission denied"),
|
|
},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def delete(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = self.get_object()
|
|
# Enforce Admin role for deletion
|
|
if not institution.is_admin(request.user):
|
|
return Response({'error': _("Only institution administrators can delete this profile.")}, status=status.HTTP_403_FORBIDDEN)
|
|
institution.is_active = False
|
|
institution.save()
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
class InstitutionTimelineView(GenericAPIView):
|
|
serializer_class = InstitutionTimelineSerializer
|
|
queryset = InstitutionTimeline.objects.all()
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [IsAuthenticated(), IsInstitutionEditorOrReadOnly()]
|
|
return [AllowAny()]
|
|
|
|
@extend_schema(
|
|
summary="List timeline milestones",
|
|
description="Returns historical milestone entries for an institution.",
|
|
responses={200: InstitutionTimelineSerializer(many=True)},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def get(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
entries = institution.timeline_entries.all().order_by('order', 'id')
|
|
serializer = InstitutionTimelineSerializer(entries, many=True)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Add timeline milestone",
|
|
description="Creates a new milestone in the institution historical timeline.",
|
|
request=InstitutionTimelineSerializer,
|
|
responses={201: InstitutionTimelineSerializer},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def post(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
self.check_object_permissions(request, institution)
|
|
|
|
serializer = InstitutionTimelineSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
timeline_entry = serializer.save(institution=institution)
|
|
|
|
return Response(InstitutionTimelineSerializer(timeline_entry).data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
class InstitutionTimelineDetailView(GenericAPIView):
|
|
serializer_class = InstitutionTimelineSerializer
|
|
permission_classes = [IsAuthenticated, IsInstitutionEditorOrReadOnly]
|
|
queryset = InstitutionTimeline.objects.all()
|
|
|
|
@extend_schema(
|
|
summary="Delete timeline milestone",
|
|
description="Removes a timeline entry from the institution profile.",
|
|
responses={204: OpenApiResponse(description="Entry deleted")},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def delete(self, request, pk_or_slug, entry_id, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
self.check_object_permissions(request, institution)
|
|
|
|
entry = get_object_or_404(InstitutionTimeline, id=entry_id, institution=institution)
|
|
entry.delete()
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
class InstitutionMediaView(GenericAPIView):
|
|
serializer_class = InstitutionMediaSerializer
|
|
parser_classes = [MultiPartParser, FormParser, JSONParser]
|
|
queryset = InstitutionMedia.objects.all()
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [IsAuthenticated(), IsInstitutionEditorOrReadOnly()]
|
|
return [AllowAny()]
|
|
|
|
@extend_schema(
|
|
summary="List media gallery assets",
|
|
description="Returns uploaded photos, videos, and documents for an institution.",
|
|
responses={200: InstitutionMediaSerializer(many=True)},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def get(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
media_items = institution.media_items.all().order_by('-uploaded_at')
|
|
serializer = InstitutionMediaSerializer(media_items, many=True, context={'request': request})
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Upload media to gallery",
|
|
description="Uploads a photo, video, or publication to the institution media gallery.",
|
|
request=InstitutionMediaSerializer,
|
|
responses={201: InstitutionMediaSerializer},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def post(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
self.check_object_permissions(request, institution)
|
|
|
|
serializer = InstitutionMediaSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
media_item = serializer.save(institution=institution)
|
|
|
|
return Response(InstitutionMediaSerializer(media_item, context={'request': request}).data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
class InstitutionMediaDetailView(GenericAPIView):
|
|
serializer_class = InstitutionMediaSerializer
|
|
permission_classes = [IsAuthenticated, IsInstitutionEditorOrReadOnly]
|
|
queryset = InstitutionMedia.objects.all()
|
|
|
|
@extend_schema(
|
|
summary="Delete media item",
|
|
description="Removes an asset from the institution media gallery.",
|
|
responses={204: OpenApiResponse(description="Media deleted")},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def delete(self, request, pk_or_slug, media_id, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
self.check_object_permissions(request, institution)
|
|
|
|
item = get_object_or_404(InstitutionMedia, id=media_id, institution=institution)
|
|
item.delete()
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
class InstitutionFollowToggleView(GenericAPIView):
|
|
serializer_class = FollowResponseSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
queryset = InstitutionFollower.objects.all()
|
|
|
|
@extend_schema(
|
|
summary="Follow institution",
|
|
description="Follows the specified institution to receive updates and news.",
|
|
responses={200: FollowResponseSerializer},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def post(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
follower, created = InstitutionFollower.objects.get_or_create(
|
|
institution=institution,
|
|
user=request.user
|
|
)
|
|
if created:
|
|
Institution.objects.filter(id=institution.id).update(follower_count=F('follower_count') + 1)
|
|
institution.refresh_from_db()
|
|
|
|
return Response({
|
|
'is_following': True,
|
|
'follower_count': institution.follower_count,
|
|
'message': _("You are now following this institution.")
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Unfollow institution",
|
|
description="Unfollows the specified institution.",
|
|
responses={200: FollowResponseSerializer},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def delete(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
deleted_count, deleted_dict = InstitutionFollower.objects.filter(
|
|
institution=institution,
|
|
user=request.user
|
|
).delete()
|
|
|
|
if deleted_count > 0:
|
|
Institution.objects.filter(id=institution.id, follower_count__gt=0).update(follower_count=F('follower_count') - 1)
|
|
institution.refresh_from_db()
|
|
|
|
return Response({
|
|
'is_following': False,
|
|
'follower_count': institution.follower_count,
|
|
'message': _("You have unfollowed this institution.")
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
class InstitutionMembersView(GenericAPIView):
|
|
serializer_class = InstitutionMemberSerializer
|
|
queryset = InstitutionMember.objects.all()
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [IsAuthenticated(), CanManageInstitutionMembers()]
|
|
return [AllowAny()]
|
|
|
|
@extend_schema(
|
|
summary="List institution team members",
|
|
description="Retrieves administrative and editorial team members of the institution.",
|
|
responses={200: InstitutionMemberSerializer(many=True)},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def get(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
members = institution.members.all().select_related('user')
|
|
serializer = InstitutionMemberSerializer(members, many=True)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Delegate role / Add team member",
|
|
description="Adds or updates a member role (admin, editor, viewer) for an existing registered user by email.",
|
|
request=InstitutionMemberAddSerializer,
|
|
responses={
|
|
201: InstitutionMemberSerializer,
|
|
400: OpenApiResponse(description="User not found or invalid payload"),
|
|
403: OpenApiResponse(description="Only institution administrators can delegate roles"),
|
|
},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def post(self, request, pk_or_slug, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
self.check_object_permissions(request, institution)
|
|
|
|
serializer = InstitutionMemberAddSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
user = User.objects.get(email__iexact=serializer.validated_data['email'])
|
|
role = serializer.validated_data['role']
|
|
title = serializer.validated_data.get('title', '')
|
|
|
|
member, created = InstitutionMember.objects.update_or_create(
|
|
institution=institution,
|
|
user=user,
|
|
defaults={
|
|
'role': role,
|
|
'title': title
|
|
}
|
|
)
|
|
|
|
response_status = status.HTTP_201_CREATED if created else status.HTTP_200_OK
|
|
return Response(InstitutionMemberSerializer(member).data, status=response_status)
|
|
|
|
|
|
class InstitutionMemberDetailView(GenericAPIView):
|
|
serializer_class = InstitutionMemberSerializer
|
|
permission_classes = [IsAuthenticated, CanManageInstitutionMembers]
|
|
queryset = InstitutionMember.objects.all()
|
|
|
|
@extend_schema(
|
|
summary="Remove team member",
|
|
description="Revokes institution membership and delegated role for a user.",
|
|
responses={204: OpenApiResponse(description="Member removed")},
|
|
tags=["Institutions & Profiles"],
|
|
)
|
|
def delete(self, request, pk_or_slug, member_id, *args, **kwargs):
|
|
institution = get_institution_by_id_or_slug(pk_or_slug)
|
|
self.check_object_permissions(request, institution)
|
|
|
|
member = get_object_or_404(InstitutionMember, id=member_id, institution=institution)
|
|
|
|
# Prevent removing the sole administrator
|
|
if member.role == InstitutionMember.MemberRole.ADMIN and institution.members.filter(role=InstitutionMember.MemberRole.ADMIN).count() <= 1:
|
|
return Response({'error': _("Cannot remove the sole administrator of an institution.")}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
member.delete()
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|