accounts.views

  1from datetime import timezone
  2from drf_spectacular.utils import extend_schema, OpenApiTypes, OpenApiResponse
  3from django.shortcuts import render, redirect
  4from django.urls import reverse
  5from .models import SiteUser, Hospital, User,Role
  6from rest_framework.views import APIView
  7from rest_framework.response import Response
  8from rest_framework import status
  9from django.contrib.auth import authenticate
 10from rest_framework.authtoken.models import Token
 11from .serializers import UserSerializer, AuthTokenRequestSerializer, AuthTokenResponseSerializer,HospitalSerializer
 12from django.db.models import Q
 13from django.db import IntegrityError
 14from rest_framework.permissions import IsAuthenticated
 15from datetime import datetime
 16
 17"""
 18Accounts app views for admin dashboard, hospital/user management, and authentication.
 19
 20Endpoints documented:
 21- Admin login/logout/dashboard/profile management
 22- Hospital CRUD and user management via admin dashboard
 23- Custom authentication token API
 24- Logout and refresh user token API
 25
 26All API endpoints are documented with drf-spectacular's @extend_schema for OpenAPI generation.
 27"""
 28
 29def admin_login(request):
 30    """
 31    Admin login view.
 32    GET: Render login page.
 33    POST: Authenticate admin and start session.
 34    """
 35    if request.method == 'POST':
 36        name = request.POST.get('name')
 37        password = request.POST.get('password')
 38        user = SiteUser.objects.filter(name=name).first()
 39        if user and user.check_password(password):
 40            request.session['admin'] = True
 41            request.session['admin_name'] = name
 42            request.session['admin_id'] = user.id
 43            return redirect('admin_page')
 44        else:
 45            return render(request, 'accounts/admin_login.html', {'error': 'Invalid credentials'})
 46    return render(request, 'accounts/admin_login.html')
 47
 48def admin_logout(request):
 49    """
 50    Admin logout view. Flushes session and redirects to login.
 51    """
 52    request.session.flush()
 53    return redirect('admin_login')
 54
 55def admin_dashboard(request):
 56    """
 57    Admin dashboard view. Requires admin session.
 58    """
 59    if not request.session.get('admin'):
 60        return redirect('admin_login')
 61    return render(request, 'accounts/admin_dashboard.html')
 62
 63def admin_page(request):
 64    """
 65    Admin management page.
 66    GET: List all admins.
 67    POST: Create new admin.
 68    """
 69    if not request.session.get('admin'):
 70        return redirect('admin_login')
 71    if request.method == 'POST':
 72        name = request.POST.get('name')
 73        password = request.POST.get('password')
 74        user = SiteUser.objects.create(name=name, password=password)
 75        return redirect('admin_page')
 76    admins = SiteUser.objects.all()
 77    return render(request, 'accounts/admin_page.html', {'admins': admins})
 78
 79def delete_siteuser(request, admin_id):
 80    """
 81    Delete an admin user.
 82    """
 83    if not request.session.get('admin'):
 84        return redirect('admin_login')
 85    SiteUser.objects.filter(id=admin_id).delete()
 86    return redirect('admin_page')
 87
 88def admin_profile(request):
 89    """
 90    Admin profile view.
 91    GET: Render profile page.
 92    POST: Update admin profile.
 93    """
 94    if not request.session.get('admin'):
 95        return redirect('admin_login')
 96    if request.method == 'POST':
 97        name = request.POST.get('name')
 98        password = request.POST.get('password')
 99        user = SiteUser.objects.filter(name=name).first()
100        if user and user.check_password(password):
101            user.name = name
102            user.password = password
103            user.save()
104            return redirect('admin_profile')
105        else:
106            return render(request, 'accounts/admin_profile.html', {'error': 'Invalid credentials'})
107    return render(request, 'accounts/admin_profile.html')
108    
109def hospitals(request):
110    """
111    Hospital management view.
112    GET: List all hospitals.
113    POST: Create new hospital and default ADMIN role.
114    """
115    if not request.session.get('admin'):
116        return redirect('admin_login')
117    if request.method == 'POST':
118        name = request.POST.get('name')
119        address = request.POST.get('address')
120        hospital = Hospital.objects.create(name=name, address=address)
121        Role.objects.get_or_create(name="ADMIN",is_protected=True,hospital=hospital,is_approver=True,is_responder=True,is_creator=True)
122        return redirect('hospitals')
123    hospitals = Hospital.objects.all()
124    return render(request, 'accounts/hospitals.html', {'hospitals': hospitals})
125
126def delete_hospital(request, hospital_id):
127    """
128    Delete a hospital.
129    """
130    if not request.session.get('admin'):
131        return redirect('admin_login')
132    Hospital.objects.filter(id=hospital_id).delete()
133    return redirect('hospitals')
134
135def switch_hospital(request, hospital_id):
136    """
137    Switch current hospital for a user (hardcoded phone number).
138    """
139    if not request.session.get('admin'):
140        return redirect('admin_login')
141    User.objects.filter(phone_number=1234567890).update(hospital_id=hospital_id,role=Role.objects.get(name="ADMIN",hospital_id=hospital_id))
142    return redirect('http://localhost:3000/logout/')
143
144def hospital_users(request , hospital_id):
145    """
146    Hospital user management view.
147    GET: List hospital staff/superusers.
148    POST: Create new hospital user with ADMIN role.
149    """
150    if not request.session.get('admin'):
151        return redirect('admin_login')
152    hospital = Hospital.objects.get(id=hospital_id)
153    if request.method == 'POST':
154        try:
155            institution_id = request.POST.get('institution_id')
156            phone_number = request.POST.get('phone_number')
157            user = User.objects.create_user(institution_id=institution_id, role=Role.objects.get(name="ADMIN",hospital=hospital), phone_number=phone_number, hospital=hospital)
158        except Role.DoesNotExist as e:
159            return render(request, 'accounts/hospital_users.html', {'error': "it seems that the role does not exist, please create a role first"})
160        except IntegrityError as e:
161            return render(request, 'accounts/hospital_users.html', {'error': "it seems that the phone number or institution ID already exists, please try again with a different one"})
162            
163        return redirect('hospital_users', hospital_id=hospital_id)
164    users = User.objects.filter(hospital=hospital).filter(Q(is_staff=True) | Q(is_superuser=True))
165    return render(request, 'accounts/hospital_users.html', {'users': users})
166
167def delete_hospital_user(request, hospital_id, user_id):
168    """
169    Delete a hospital user.
170    """
171    if not request.session.get('admin'):
172        return redirect('admin_login')
173    User.objects.filter(id=user_id, hospital_id=hospital_id).delete()
174    return redirect('hospital_users', hospital_id=hospital_id)
175
176# ---- custom auth token view
177
178class CustomAuthToken(APIView):
179    """
180    Custom authentication token API.
181
182    POST:
183        - Authenticates user using phone and password.
184        - Returns token, user info, and hospital info.
185    """
186    permission_classes = []  
187    @extend_schema(
188        request=AuthTokenRequestSerializer,
189        responses={
190            200: AuthTokenResponseSerializer,
191            400: OpenApiTypes.OBJECT,
192            404: OpenApiTypes.OBJECT,
193        },
194        summary="Custom Auth Token",
195        description="Authenticate using institution_id and password under a specific hospital ID (passed in URL)."
196    )
197    def post(self, request, *args, **kwargs):
198        try:
199            phone = request.data.get('phone')
200            password = request.data.get('password')
201            fcm_token = request.data.get('fcm_token', None)
202            print(f"request.data: {request.data}")
203            if not phone or not password:
204                return Response({'error': 'Phone number and password are required.'}, status=status.HTTP_400_BAD_REQUEST)
205            user = authenticate(request, phone_number=phone, password=password)
206            if user is not None:
207                token, created = Token.objects.get_or_create(user=user)
208                user.is_logged_in = True
209                user.fcm_token = fcm_token if fcm_token else user.fcm_token
210                user.last_login = datetime.now()
211                user.fcm_token_updated_at = datetime.now() if fcm_token else user.fcm_token_updated_at
212                user.save()
213                user_data = UserSerializer(user).data
214                data = {'token': token.key, 'user': user_data, 'hospital':HospitalSerializer(user.hospital).data}
215                return Response(data, status=status.HTTP_200_OK)
216            else:
217                return Response({'error': 'Invalid credentials.'}, status=status.HTTP_400_BAD_REQUEST)
218        except Hospital.DoesNotExist:
219            return Response({'error': 'Hospital does not exist.'}, status=status.HTTP_404_NOT_FOUND)
220
221class LogoutView(APIView):
222    """
223    Logout API for authenticated users.
224
225    POST:
226        - Logs out the user and updates login status.
227        - Returns success or error message.
228    """
229    permission_classes = [IsAuthenticated]
230    @extend_schema(
231    summary="Logout user",
232    description="Logs out the authenticated user.",
233    responses={
234        200: OpenApiResponse(description="Logged out successfully"),
235        400: OpenApiResponse(description="Invalid token"),
236    }
237    )
238    def post(self, request):
239        try:
240            if request.user.is_superuser:
241                request.user.hospital = None   
242                request.user.save()
243            else:
244                request.user.is_logged_in = False
245                request.user.save()
246            return Response({"message": "Logged out successfully"}, status=status.HTTP_200_OK)
247        except (AttributeError, Token.DoesNotExist):
248            return Response({"message": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST)
249        
250class RefreshUserView(APIView):
251    """
252    Refresh user token API.
253
254    POST:
255        - Returns refreshed token and user info.
256    """
257    permission_classes = [IsAuthenticated]
258    @extend_schema(
259    summary="Refresh user token",
260    description="Refreshes and returns the user's token and user's info.",
261    responses={
262        200: AuthTokenResponseSerializer,
263        400: OpenApiResponse(description="Invalid token"),
264    }
265    )
266    def post(self, request):
267        try:
268            user = request.user
269            token, created = Token.objects.get_or_create(user=user)
270            user_data = UserSerializer(user).data
271            data = {'token': token.key, 'user': user_data, 'hospital':HospitalSerializer(user.hospital).data}
272            return Response(data, status=status.HTTP_200_OK)
273        
274        except (AttributeError, Token.DoesNotExist):
275            return Response({"message": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST)
def admin_login(request):
30def admin_login(request):
31    """
32    Admin login view.
33    GET: Render login page.
34    POST: Authenticate admin and start session.
35    """
36    if request.method == 'POST':
37        name = request.POST.get('name')
38        password = request.POST.get('password')
39        user = SiteUser.objects.filter(name=name).first()
40        if user and user.check_password(password):
41            request.session['admin'] = True
42            request.session['admin_name'] = name
43            request.session['admin_id'] = user.id
44            return redirect('admin_page')
45        else:
46            return render(request, 'accounts/admin_login.html', {'error': 'Invalid credentials'})
47    return render(request, 'accounts/admin_login.html')

Admin login view. GET: Render login page. POST: Authenticate admin and start session.

def admin_logout(request):
49def admin_logout(request):
50    """
51    Admin logout view. Flushes session and redirects to login.
52    """
53    request.session.flush()
54    return redirect('admin_login')

Admin logout view. Flushes session and redirects to login.

def admin_dashboard(request):
56def admin_dashboard(request):
57    """
58    Admin dashboard view. Requires admin session.
59    """
60    if not request.session.get('admin'):
61        return redirect('admin_login')
62    return render(request, 'accounts/admin_dashboard.html')

Admin dashboard view. Requires admin session.

def admin_page(request):
64def admin_page(request):
65    """
66    Admin management page.
67    GET: List all admins.
68    POST: Create new admin.
69    """
70    if not request.session.get('admin'):
71        return redirect('admin_login')
72    if request.method == 'POST':
73        name = request.POST.get('name')
74        password = request.POST.get('password')
75        user = SiteUser.objects.create(name=name, password=password)
76        return redirect('admin_page')
77    admins = SiteUser.objects.all()
78    return render(request, 'accounts/admin_page.html', {'admins': admins})

Admin management page. GET: List all admins. POST: Create new admin.

def delete_siteuser(request, admin_id):
80def delete_siteuser(request, admin_id):
81    """
82    Delete an admin user.
83    """
84    if not request.session.get('admin'):
85        return redirect('admin_login')
86    SiteUser.objects.filter(id=admin_id).delete()
87    return redirect('admin_page')

Delete an admin user.

def admin_profile(request):
 89def admin_profile(request):
 90    """
 91    Admin profile view.
 92    GET: Render profile page.
 93    POST: Update admin profile.
 94    """
 95    if not request.session.get('admin'):
 96        return redirect('admin_login')
 97    if request.method == 'POST':
 98        name = request.POST.get('name')
 99        password = request.POST.get('password')
100        user = SiteUser.objects.filter(name=name).first()
101        if user and user.check_password(password):
102            user.name = name
103            user.password = password
104            user.save()
105            return redirect('admin_profile')
106        else:
107            return render(request, 'accounts/admin_profile.html', {'error': 'Invalid credentials'})
108    return render(request, 'accounts/admin_profile.html')

Admin profile view. GET: Render profile page. POST: Update admin profile.

def hospitals(request):
110def hospitals(request):
111    """
112    Hospital management view.
113    GET: List all hospitals.
114    POST: Create new hospital and default ADMIN role.
115    """
116    if not request.session.get('admin'):
117        return redirect('admin_login')
118    if request.method == 'POST':
119        name = request.POST.get('name')
120        address = request.POST.get('address')
121        hospital = Hospital.objects.create(name=name, address=address)
122        Role.objects.get_or_create(name="ADMIN",is_protected=True,hospital=hospital,is_approver=True,is_responder=True,is_creator=True)
123        return redirect('hospitals')
124    hospitals = Hospital.objects.all()
125    return render(request, 'accounts/hospitals.html', {'hospitals': hospitals})

Hospital management view. GET: List all hospitals. POST: Create new hospital and default ADMIN role.

def delete_hospital(request, hospital_id):
127def delete_hospital(request, hospital_id):
128    """
129    Delete a hospital.
130    """
131    if not request.session.get('admin'):
132        return redirect('admin_login')
133    Hospital.objects.filter(id=hospital_id).delete()
134    return redirect('hospitals')

Delete a hospital.

def switch_hospital(request, hospital_id):
136def switch_hospital(request, hospital_id):
137    """
138    Switch current hospital for a user (hardcoded phone number).
139    """
140    if not request.session.get('admin'):
141        return redirect('admin_login')
142    User.objects.filter(phone_number=1234567890).update(hospital_id=hospital_id,role=Role.objects.get(name="ADMIN",hospital_id=hospital_id))
143    return redirect('http://localhost:3000/logout/')

Switch current hospital for a user (hardcoded phone number).

def hospital_users(request, hospital_id):
145def hospital_users(request , hospital_id):
146    """
147    Hospital user management view.
148    GET: List hospital staff/superusers.
149    POST: Create new hospital user with ADMIN role.
150    """
151    if not request.session.get('admin'):
152        return redirect('admin_login')
153    hospital = Hospital.objects.get(id=hospital_id)
154    if request.method == 'POST':
155        try:
156            institution_id = request.POST.get('institution_id')
157            phone_number = request.POST.get('phone_number')
158            user = User.objects.create_user(institution_id=institution_id, role=Role.objects.get(name="ADMIN",hospital=hospital), phone_number=phone_number, hospital=hospital)
159        except Role.DoesNotExist as e:
160            return render(request, 'accounts/hospital_users.html', {'error': "it seems that the role does not exist, please create a role first"})
161        except IntegrityError as e:
162            return render(request, 'accounts/hospital_users.html', {'error': "it seems that the phone number or institution ID already exists, please try again with a different one"})
163            
164        return redirect('hospital_users', hospital_id=hospital_id)
165    users = User.objects.filter(hospital=hospital).filter(Q(is_staff=True) | Q(is_superuser=True))
166    return render(request, 'accounts/hospital_users.html', {'users': users})

Hospital user management view. GET: List hospital staff/superusers. POST: Create new hospital user with ADMIN role.

def delete_hospital_user(request, hospital_id, user_id):
168def delete_hospital_user(request, hospital_id, user_id):
169    """
170    Delete a hospital user.
171    """
172    if not request.session.get('admin'):
173        return redirect('admin_login')
174    User.objects.filter(id=user_id, hospital_id=hospital_id).delete()
175    return redirect('hospital_users', hospital_id=hospital_id)

Delete a hospital user.

class CustomAuthToken(rest_framework.views.APIView):
179class CustomAuthToken(APIView):
180    """
181    Custom authentication token API.
182
183    POST:
184        - Authenticates user using phone and password.
185        - Returns token, user info, and hospital info.
186    """
187    permission_classes = []  
188    @extend_schema(
189        request=AuthTokenRequestSerializer,
190        responses={
191            200: AuthTokenResponseSerializer,
192            400: OpenApiTypes.OBJECT,
193            404: OpenApiTypes.OBJECT,
194        },
195        summary="Custom Auth Token",
196        description="Authenticate using institution_id and password under a specific hospital ID (passed in URL)."
197    )
198    def post(self, request, *args, **kwargs):
199        try:
200            phone = request.data.get('phone')
201            password = request.data.get('password')
202            fcm_token = request.data.get('fcm_token', None)
203            print(f"request.data: {request.data}")
204            if not phone or not password:
205                return Response({'error': 'Phone number and password are required.'}, status=status.HTTP_400_BAD_REQUEST)
206            user = authenticate(request, phone_number=phone, password=password)
207            if user is not None:
208                token, created = Token.objects.get_or_create(user=user)
209                user.is_logged_in = True
210                user.fcm_token = fcm_token if fcm_token else user.fcm_token
211                user.last_login = datetime.now()
212                user.fcm_token_updated_at = datetime.now() if fcm_token else user.fcm_token_updated_at
213                user.save()
214                user_data = UserSerializer(user).data
215                data = {'token': token.key, 'user': user_data, 'hospital':HospitalSerializer(user.hospital).data}
216                return Response(data, status=status.HTTP_200_OK)
217            else:
218                return Response({'error': 'Invalid credentials.'}, status=status.HTTP_400_BAD_REQUEST)
219        except Hospital.DoesNotExist:
220            return Response({'error': 'Hospital does not exist.'}, status=status.HTTP_404_NOT_FOUND)

Custom authentication token API.

POST: - Authenticates user using phone and password. - Returns token, user info, and hospital info.

permission_classes = []
@extend_schema(request=AuthTokenRequestSerializer, responses={200: AuthTokenResponseSerializer, 400: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT}, summary='Custom Auth Token', description='Authenticate using institution_id and password under a specific hospital ID (passed in URL).')
def post(self, request, *args, **kwargs):
188    @extend_schema(
189        request=AuthTokenRequestSerializer,
190        responses={
191            200: AuthTokenResponseSerializer,
192            400: OpenApiTypes.OBJECT,
193            404: OpenApiTypes.OBJECT,
194        },
195        summary="Custom Auth Token",
196        description="Authenticate using institution_id and password under a specific hospital ID (passed in URL)."
197    )
198    def post(self, request, *args, **kwargs):
199        try:
200            phone = request.data.get('phone')
201            password = request.data.get('password')
202            fcm_token = request.data.get('fcm_token', None)
203            print(f"request.data: {request.data}")
204            if not phone or not password:
205                return Response({'error': 'Phone number and password are required.'}, status=status.HTTP_400_BAD_REQUEST)
206            user = authenticate(request, phone_number=phone, password=password)
207            if user is not None:
208                token, created = Token.objects.get_or_create(user=user)
209                user.is_logged_in = True
210                user.fcm_token = fcm_token if fcm_token else user.fcm_token
211                user.last_login = datetime.now()
212                user.fcm_token_updated_at = datetime.now() if fcm_token else user.fcm_token_updated_at
213                user.save()
214                user_data = UserSerializer(user).data
215                data = {'token': token.key, 'user': user_data, 'hospital':HospitalSerializer(user.hospital).data}
216                return Response(data, status=status.HTTP_200_OK)
217            else:
218                return Response({'error': 'Invalid credentials.'}, status=status.HTTP_400_BAD_REQUEST)
219        except Hospital.DoesNotExist:
220            return Response({'error': 'Hospital does not exist.'}, status=status.HTTP_404_NOT_FOUND)
class LogoutView(rest_framework.views.APIView):
222class LogoutView(APIView):
223    """
224    Logout API for authenticated users.
225
226    POST:
227        - Logs out the user and updates login status.
228        - Returns success or error message.
229    """
230    permission_classes = [IsAuthenticated]
231    @extend_schema(
232    summary="Logout user",
233    description="Logs out the authenticated user.",
234    responses={
235        200: OpenApiResponse(description="Logged out successfully"),
236        400: OpenApiResponse(description="Invalid token"),
237    }
238    )
239    def post(self, request):
240        try:
241            if request.user.is_superuser:
242                request.user.hospital = None   
243                request.user.save()
244            else:
245                request.user.is_logged_in = False
246                request.user.save()
247            return Response({"message": "Logged out successfully"}, status=status.HTTP_200_OK)
248        except (AttributeError, Token.DoesNotExist):
249            return Response({"message": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST)

Logout API for authenticated users.

POST: - Logs out the user and updates login status. - Returns success or error message.

permission_classes = [<class 'rest_framework.permissions.IsAuthenticated'>]
@extend_schema(summary='Logout user', description='Logs out the authenticated user.', responses={200: OpenApiResponse(description='Logged out successfully'), 400: OpenApiResponse(description='Invalid token')})
def post(self, request):
231    @extend_schema(
232    summary="Logout user",
233    description="Logs out the authenticated user.",
234    responses={
235        200: OpenApiResponse(description="Logged out successfully"),
236        400: OpenApiResponse(description="Invalid token"),
237    }
238    )
239    def post(self, request):
240        try:
241            if request.user.is_superuser:
242                request.user.hospital = None   
243                request.user.save()
244            else:
245                request.user.is_logged_in = False
246                request.user.save()
247            return Response({"message": "Logged out successfully"}, status=status.HTTP_200_OK)
248        except (AttributeError, Token.DoesNotExist):
249            return Response({"message": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST)
class RefreshUserView(rest_framework.views.APIView):
251class RefreshUserView(APIView):
252    """
253    Refresh user token API.
254
255    POST:
256        - Returns refreshed token and user info.
257    """
258    permission_classes = [IsAuthenticated]
259    @extend_schema(
260    summary="Refresh user token",
261    description="Refreshes and returns the user's token and user's info.",
262    responses={
263        200: AuthTokenResponseSerializer,
264        400: OpenApiResponse(description="Invalid token"),
265    }
266    )
267    def post(self, request):
268        try:
269            user = request.user
270            token, created = Token.objects.get_or_create(user=user)
271            user_data = UserSerializer(user).data
272            data = {'token': token.key, 'user': user_data, 'hospital':HospitalSerializer(user.hospital).data}
273            return Response(data, status=status.HTTP_200_OK)
274        
275        except (AttributeError, Token.DoesNotExist):
276            return Response({"message": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST)

Refresh user token API.

POST: - Returns refreshed token and user info.

permission_classes = [<class 'rest_framework.permissions.IsAuthenticated'>]
@extend_schema(summary='Refresh user token', description="Refreshes and returns the user's token and user's info.", responses={200: AuthTokenResponseSerializer, 400: OpenApiResponse(description='Invalid token')})
def post(self, request):
259    @extend_schema(
260    summary="Refresh user token",
261    description="Refreshes and returns the user's token and user's info.",
262    responses={
263        200: AuthTokenResponseSerializer,
264        400: OpenApiResponse(description="Invalid token"),
265    }
266    )
267    def post(self, request):
268        try:
269            user = request.user
270            token, created = Token.objects.get_or_create(user=user)
271            user_data = UserSerializer(user).data
272            data = {'token': token.key, 'user': user_data, 'hospital':HospitalSerializer(user.hospital).data}
273            return Response(data, status=status.HTTP_200_OK)
274        
275        except (AttributeError, Token.DoesNotExist):
276            return Response({"message": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST)