• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

akvo / akvo-mis / #293

21 Aug 2025 12:14PM UTC coverage: 88.112% (-0.05%) from 88.158%
#293

push

coveralls-python

ifirmawan
FE: Fix optional chaining for children in ApproversTree component

3414 of 3989 branches covered (85.59%)

Branch coverage included in aggregate %.

7133 of 7981 relevant lines covered (89.37%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.31
backend/api/v1/v1_users/views.py
1
# Create your views here.
2
import datetime
1✔
3
from math import ceil
1✔
4
from pathlib import Path
1✔
5

6
from django.contrib.auth import authenticate
1✔
7
from django.core import signing
1✔
8
from django.core.management import call_command
1✔
9
from django.core.signing import BadSignature
1✔
10
from django.db.models import Value, Q, Count
1✔
11
from django.db.models.functions import Coalesce, Concat
1✔
12
from django.http import HttpResponse
1✔
13
from django.utils import timezone
1✔
14
from drf_spectacular.types import OpenApiTypes
1✔
15
from drf_spectacular.utils import (
1✔
16
    extend_schema,
17
    inline_serializer,
18
    OpenApiParameter,
19
    OpenApiResponse,
20
)
21
from jsmin import jsmin
1✔
22
from rest_framework import status, serializers
1✔
23
from rest_framework.decorators import api_view, permission_classes
1✔
24
from rest_framework.generics import get_object_or_404
1✔
25
from rest_framework.pagination import PageNumberPagination
1✔
26
from rest_framework.permissions import IsAuthenticated
1✔
27
from rest_framework.response import Response
1✔
28
from rest_framework.views import APIView
1✔
29
from rest_framework_simplejwt.tokens import RefreshToken
1✔
30
from api.v1.v1_profile.constants import OrganisationTypes
1✔
31
from api.v1.v1_profile.models import (
1✔
32
    Administration,
33
    Levels,
34
    Role,
35
)
36
from api.v1.v1_profile.constants import FeatureAccessTypes, FeatureTypes
1✔
37
from api.v1.v1_users.models import (
1✔
38
    SystemUser,
39
    Organisation,
40
    OrganisationAttribute,
41
)
42
from api.v1.v1_users.serializers import (
1✔
43
    LoginSerializer,
44
    UserSerializer,
45
    VerifyInviteSerializer,
46
    SetUserPasswordSerializer,
47
    ListAdministrationSerializer,
48
    AddEditUserSerializer,
49
    ListUserSerializer,
50
    ListUserRequestSerializer,
51
    ListLevelSerializer,
52
    UserDetailSerializer,
53
    ForgotPasswordSerializer,
54
    OrganisationListSerializer,
55
    AddEditOrganisationSerializer,
56
    OrganisationAttributeChildrenSerializer,
57
    RoleOptionSerializer,
58
    UpdateProfileSerializer,
59
)
60
from mis.settings import REST_FRAMEWORK, WEBDOMAIN
1✔
61
from utils.custom_permissions import AddUserAccess, IsSuperAdmin
1✔
62
from utils.custom_serializer_fields import validate_serializers_message
1✔
63
from utils.default_serializers import DefaultResponseSerializer
1✔
64
from utils.email_helper import send_email
1✔
65
from utils.email_helper import ListEmailTypeRequestSerializer, EmailTypes
1✔
66

67

68
def send_email_to_user(type, user, request):
1✔
69
    url = f"{WEBDOMAIN}/login/{signing.dumps(user.pk)}"
1✔
70
    user = SystemUser.objects.get(pk=user.pk)
1✔
71
    user_forms = [
1✔
72
        uf.form for uf in user.user_form.all()
73
    ]
74
    listing = [
1✔
75
        info
76
        for role in user.user_user_role.all()
77
        for info in (
78
            {
79
                "name": "Role",
80
                "value": role.role.name if role.role else "N/A",
81
            },
82
            {
83
                "name": "Region",
84
                "value": role.administration.name,
85
            },
86
        )
87
    ]
88
    if user_forms:
1!
89
        user_forms = ", ".join([form.name for form in user_forms])
1✔
90
        listing.append({"name": "Questionnaire", "value": user_forms})
1✔
91
    # TODO Add Administration
92
    data = {
1✔
93
        "send_to": [user.email],
94
        "listing": listing,
95
        "admin": f"""{user.name}""",
96
    }
97
    if type == EmailTypes.user_invite:
1!
98
        data["button_url"] = url
1✔
99
    send_email(type=type, context=data)
1✔
100

101

102
@extend_schema(description="Use to check System health", tags=["Dev"])
1✔
103
@api_view(["GET"])
1✔
104
def health_check(request, version):
1✔
105
    return Response({"message": "OK"}, status=status.HTTP_200_OK)
×
106

107

108
@extend_schema(description="Get required configuration", tags=["Dev"])
1✔
109
@api_view(["GET"])
1✔
110
def get_config_file(request, version):
1✔
111
    if not Path("source/config/config.min.js").exists():
1!
112
        call_command("generate_config")
1✔
113
    data = jsmin(open("source/config/config.min.js", "r").read())
1✔
114
    response = HttpResponse(
1✔
115
        data, content_type="application/x-javascript; charset=utf-8"
116
    )
117
    return response
1✔
118

119

120
@extend_schema(
1✔
121
    description="Use to show email templates",
122
    tags=["Dev"],
123
    parameters=[
124
        OpenApiParameter(
125
            name="type",
126
            required=False,
127
            enum=EmailTypes.FieldStr.keys(),
128
            type=OpenApiTypes.STR,
129
            location=OpenApiParameter.QUERY,
130
        )
131
    ],
132
    responses={
133
        200: OpenApiResponse(
134
            description="HTML email template",
135
            response=OpenApiTypes.STR
136
        )
137
    },
138
    summary="To show email template by type",
139
)
140
@api_view(["GET"])
1✔
141
def email_template(request, version):
1✔
142
    serializer = ListEmailTypeRequestSerializer(data=request.GET)
×
143
    if not serializer.is_valid():
×
144
        return Response(
×
145
            {"message": validate_serializers_message(serializer.errors)},
146
            status=status.HTTP_400_BAD_REQUEST,
147
        )
148
    email_type = serializer.validated_data.get("type")
×
149
    data = {"subject": "Test", "send_to": []}
×
150
    email = send_email(type=email_type, context=data, send=False)
×
151
    return HttpResponse(email)
×
152

153

154
# TODO: Remove temp user entry and invite key from the response.
155
@extend_schema(
1✔
156
    request=LoginSerializer,
157
    responses={200: UserSerializer, 401: DefaultResponseSerializer},
158
    tags=["Auth"],
159
)
160
@api_view(["POST"])
1✔
161
def login(request, version):
1✔
162
    serializer = LoginSerializer(data=request.data)
1✔
163
    if not serializer.is_valid():
1!
164
        return Response(
×
165
            {"message": validate_serializers_message(serializer.errors)},
166
            status=status.HTTP_400_BAD_REQUEST,
167
        )
168
    # Add temp user for development purpose
169
    if not SystemUser.objects.all().count():
1✔
170
        SystemUser.objects.create_superuser(
1✔
171
            email="admin@akvo.org",
172
            password="Test105*",
173
            first_name="Admin",
174
            last_name="MIS",
175
        )
176

177
    user = authenticate(
1✔
178
        email=serializer.validated_data["email"],
179
        password=serializer.validated_data["password"],
180
    )
181

182
    if user:
1✔
183
        if user.deleted_at:
1!
184
            return Response(
×
185
                {"message": "User has been deleted"},
186
                status=status.HTTP_401_UNAUTHORIZED,
187
            )
188
        user.last_login = timezone.now()
1✔
189
        user.save()
1✔
190
        refresh = RefreshToken.for_user(user)
1✔
191
        # Get the expiration time of the new token
192
        expiration_time = datetime.datetime.fromtimestamp(
1✔
193
            refresh.access_token["exp"]
194
        )
195
        expiration_time = timezone.make_aware(expiration_time)
1✔
196

197
        data = UserSerializer(instance=user).data
1✔
198
        data["token"] = str(refresh.access_token)
1✔
199
        data["invite"] = signing.dumps(user.pk)
1✔
200
        data["expiration_time"] = expiration_time
1✔
201
        response = Response(
1✔
202
            data,
203
            status=status.HTTP_200_OK,
204
        )
205
        response.set_cookie(
1✔
206
            "AUTH_TOKEN", str(refresh.access_token), expires=expiration_time
207
        )
208
        return response
1✔
209
    return Response(
1✔
210
        {"message": "Invalid login credentials"},
211
        status=status.HTTP_401_UNAUTHORIZED,
212
    )
213

214

215
@extend_schema(
1✔
216
    responses={200: UserSerializer},
217
    tags=["Auth"],
218
    summary="Get user details from token",
219
)
220
@api_view(["GET"])
1✔
221
@permission_classes([IsAuthenticated])
1✔
222
def get_profile(request, version):
1✔
223
    # check user activity
224
    user = SystemUser.objects.filter(
1✔
225
        email=request.user, deleted_at=None
226
    ).first()
227
    if not user:
1!
228
        return Response(
×
229
            {"message": "User has been deleted"},
230
            status=status.HTTP_401_UNAUTHORIZED,
231
        )
232
    # calculate last activity
233
    now = timezone.now()
1✔
234
    last_active = user.last_login
1✔
235
    time_diff_hours = None
1✔
236
    if last_active:
1!
237
        time_diff = now - last_active
1✔
238
        time_diff_hours = time_diff.total_seconds() / 3600
1✔
239
    if time_diff_hours and time_diff_hours >= 4:
1!
240
        # revoke/logout after 4 hours inactivity
241
        return Response(
×
242
            {"message": "Expired of 4 hours inactivity"},
243
            status=status.HTTP_401_UNAUTHORIZED,
244
        )
245
    return Response(
1✔
246
        UserSerializer(instance=request.user).data, status=status.HTTP_200_OK
247
    )
248

249

250
@extend_schema(
1✔
251
    request=VerifyInviteSerializer,
252
    responses={200: DefaultResponseSerializer, 400: DefaultResponseSerializer},
253
    tags=["User"],
254
    summary="To verify invitation code",
255
)
256
@api_view(["GET"])
1✔
257
def verify_invite(request, version, invitation_id):
1✔
258
    try:
1✔
259
        pk = signing.loads(invitation_id)
1✔
260
        user = SystemUser.objects.get(pk=pk, deleted_at=None)
1✔
261
        return Response(
1✔
262
            {"name": user.get_full_name()}, status=status.HTTP_200_OK
263
        )
264
    except BadSignature:
×
265
        return Response(
×
266
            {"message": "Invalid invite code"},
267
            status=status.HTTP_400_BAD_REQUEST,
268
        )
269
    except SystemUser.DoesNotExist:
×
270
        return Response(
×
271
            {"message": "Invalid invite code"},
272
            status=status.HTTP_400_BAD_REQUEST,
273
        )
274

275

276
@extend_schema(
1✔
277
    request=SetUserPasswordSerializer,
278
    responses={200: UserSerializer},
279
    tags=["User"],
280
    summary="To set user's password",
281
)
282
@api_view(["PUT"])
1✔
283
def set_user_password(request, version):
1✔
284
    serializer = SetUserPasswordSerializer(data=request.data)
1✔
285
    if not serializer.is_valid():
1!
286
        return Response(
×
287
            {"message": validate_serializers_message(serializer.errors)},
288
            status=status.HTTP_400_BAD_REQUEST,
289
        )
290
    user: SystemUser = serializer.validated_data.get("invite")
1✔
291
    user.set_password(serializer.validated_data.get("password"))
1✔
292
    user.updated = timezone.now()
1✔
293
    user.save()
1✔
294
    refresh = RefreshToken.for_user(user)
1✔
295
    data = UserSerializer(instance=user).data
1✔
296
    data["token"] = str(refresh.access_token)
1✔
297
    # TODO: remove invite from response
298
    data["invite"] = signing.dumps(user.pk)
1✔
299
    return Response(data, status=status.HTTP_200_OK)
1✔
300

301

302
@extend_schema(
1✔
303
    responses={200: ListAdministrationSerializer},
304
    tags=["Administration"],
305
    parameters=[
306
        OpenApiParameter(
307
            name="filter",
308
            required=False,
309
            type=OpenApiTypes.NUMBER,
310
            location=OpenApiParameter.QUERY,
311
        ),
312
        OpenApiParameter(
313
            name="filter_children",
314
            required=False,
315
            type={"type": "array", "items": {"type": "number"}},
316
            location=OpenApiParameter.QUERY,
317
        ),
318
        OpenApiParameter(
319
            name="max_level",
320
            required=False,
321
            type=OpenApiTypes.NUMBER,
322
            location=OpenApiParameter.QUERY,
323
        ),
324
    ],
325
    summary="Get list of administration",
326
)
327
@api_view(["GET"])
1✔
328
def list_administration(request, version, administration_id):
1✔
329
    instance = get_object_or_404(Administration, pk=administration_id)
1✔
330
    filter = request.GET.get("filter")
1✔
331
    max_level = request.GET.get("max_level")
1✔
332
    filter_children = request.GET.getlist("filter_children")
1✔
333
    return Response(
1✔
334
        ListAdministrationSerializer(
335
            instance=instance,
336
            context={
337
                "filter": filter,
338
                "max_level": max_level,
339
                "filter_children": filter_children,
340
            },
341
        ).data,
342
        status=status.HTTP_200_OK,
343
    )
344

345

346
@extend_schema(
1✔
347
    responses={200: ListLevelSerializer(many=True)},
348
    tags=["Administration"],
349
    summary="Get list of levels",
350
)
351
@api_view(["GET"])
1✔
352
def list_levels(request, version):
1✔
353
    return Response(
×
354
        ListLevelSerializer(instance=Levels.objects.all(), many=True).data,
355
        status=status.HTTP_200_OK,
356
    )
357

358

359
@extend_schema(
1✔
360
    request=AddEditUserSerializer,
361
    responses={200: DefaultResponseSerializer},
362
    tags=["User"],
363
    description="Role Choice are SuperAdmin:1,Admin:2",
364
    summary="To add user",
365
)
366
@api_view(["POST"])
1✔
367
@permission_classes([IsAuthenticated, AddUserAccess])
1✔
368
def add_user(request, version):
1✔
369
    serializer = AddEditUserSerializer(
1✔
370
        data=request.data, context={"user": request.user}
371
    )
372
    try:
1✔
373
        if not serializer.is_valid():
1✔
374
            return Response(
1✔
375
                {
376
                    "message": validate_serializers_message(serializer.errors),
377
                    "details": serializer.errors,
378
                },
379
                status=status.HTTP_400_BAD_REQUEST,
380
            )
381
    except Exception as e:
×
382
        # Handle unexpected validation errors
383
        error_message = str(e)
×
384
        if hasattr(e, 'detail'):
×
385
            return Response(
×
386
                {
387
                    "message": validate_serializers_message(e.detail),
388
                    "details": e.detail,
389
                },
390
                status=status.HTTP_400_BAD_REQUEST,
391
            )
392
        return Response(
×
393
            {
394
                "message": error_message,
395
                "details": {"error": error_message}
396
            },
397
            status=status.HTTP_400_BAD_REQUEST,
398
        )
399

400
    user = serializer.save()
1✔
401

402
    if serializer.validated_data.get("inform_user"):
1!
403
        send_email_to_user(
1✔
404
            type=EmailTypes.user_invite, user=user, request=request
405
        )
406
    return Response(
1✔
407
        {"message": "User added successfully"},
408
        status=status.HTTP_201_CREATED,
409
    )
410

411

412
@extend_schema(
1✔
413
    responses={
414
        (200, "application/json"): inline_serializer(
415
            "UserList",
416
            fields={
417
                "current": serializers.IntegerField(),
418
                "total": serializers.IntegerField(),
419
                "total_page": serializers.IntegerField(),
420
                "data": ListUserSerializer(many=True),
421
            },
422
        )
423
    },
424
    tags=["User"],
425
    summary="Get list of users",
426
    parameters=[
427
        OpenApiParameter(
428
            name="page",
429
            required=True,
430
            type=OpenApiTypes.NUMBER,
431
            location=OpenApiParameter.QUERY,
432
        ),
433
        OpenApiParameter(
434
            name="trained",
435
            required=False,
436
            default=None,
437
            type=OpenApiTypes.BOOL,
438
            location=OpenApiParameter.QUERY,
439
        ),
440
        OpenApiParameter(
441
            name="role",
442
            required=False,
443
            type=OpenApiTypes.NUMBER,
444
            location=OpenApiParameter.QUERY,
445
        ),
446
        OpenApiParameter(
447
            name="organisation",
448
            required=False,
449
            type=OpenApiTypes.NUMBER,
450
            location=OpenApiParameter.QUERY,
451
        ),
452
        OpenApiParameter(
453
            name="administration",
454
            required=False,
455
            type=OpenApiTypes.NUMBER,
456
            location=OpenApiParameter.QUERY,
457
        ),
458
        OpenApiParameter(
459
            name="pending",
460
            required=False,
461
            type=OpenApiTypes.BOOL,
462
            location=OpenApiParameter.QUERY,
463
        ),
464
        OpenApiParameter(
465
            name="search",
466
            required=False,
467
            type=OpenApiTypes.STR,
468
            location=OpenApiParameter.QUERY,
469
        ),
470
    ],
471
)
472
@api_view(["GET"])
1✔
473
@permission_classes([IsAuthenticated, AddUserAccess])
1✔
474
def list_users(request, version):
1✔
475
    serializer = ListUserRequestSerializer(data=request.GET)
1✔
476
    if not serializer.is_valid():
1✔
477
        return Response(
1✔
478
            {"message": validate_serializers_message(serializer.errors)},
479
            status=status.HTTP_400_BAD_REQUEST,
480
        )
481

482
    filter_data = {}
1✔
483
    exclude_data = {"password__exact": ""}
1✔
484

485
    if not request.user.is_superuser:
1✔
486
        filter_data["is_superuser"] = False
1✔
487

488
    filter_adm = serializer.validated_data.get("administration")
1✔
489
    if not request.user.is_superuser:
1✔
490
        user_adm_queryset = request.user.user_user_role.filter(
1✔
491
            role__role_role_feature_access__type=FeatureTypes.user_access,
492
            role__role_role_feature_access__access=(
493
                FeatureAccessTypes.invite_user
494
            ),
495
        ).order_by(
496
            "administration__level__level"
497
        )
498
        if not filter_adm and user_adm_queryset.exists():
1✔
499
            if user_adm_queryset.count() > 1:
1!
500
                filter_adm = user_adm_queryset.first().administration.parent
×
501
            else:
502
                filter_adm = user_adm_queryset.first().administration
1✔
503
    if filter_adm:
1✔
504
        filter_path = "{0}{1}.".format(
1✔
505
            filter_adm.path, filter_adm.id
506
        ) if filter_adm.path else f"{filter_adm.id}."
507
        filter_descendants = list(
1✔
508
            Administration.objects.filter(
509
                path__startswith=filter_path
510
            ).values_list("id", flat=True)
511
        )
512
        filter_descendants.append(filter_adm.id)
1✔
513
        final_set = set(filter_descendants)
1✔
514

515
        # Apply filter by administration IDs
516
        # Only apply filtering if administration level > 0 (not national level)
517
        if filter_adm.level.level > 0:
1!
518
            filter_data["user_user_role__administration_id__in"] = list(
1✔
519
                final_set
520
            )
521
    if serializer.validated_data.get("trained") is not None:
1✔
522
        trained = (
1✔
523
            True
524
            if serializer.validated_data.get("trained").lower() == "true"
525
            else False
526
        )
527
        filter_data["trained"] = trained
1✔
528
    if serializer.validated_data.get("role"):
1✔
529
        role = serializer.validated_data.get("role")
1✔
530
        # Use direct filter on role object instead of role_id
531
        filter_data["user_user_role__role"] = role
1✔
532
    if serializer.validated_data.get("organisation"):
1✔
533
        filter_data["organisation_id"] = serializer.validated_data.get(
1✔
534
            "organisation"
535
        )
536
    if serializer.validated_data.get("pending"):
1✔
537
        filter_data["password__exact"] = ""
1✔
538
        exclude_data.pop("password__exact")
1✔
539

540
    page_size = REST_FRAMEWORK.get("PAGE_SIZE")
1✔
541
    the_past = timezone.now() - datetime.timedelta(days=10 * 365)
1✔
542
    # also filter soft deletes
543
    queryset = SystemUser.objects.filter(deleted_at=None, **filter_data)
1✔
544
    # filter by email or fullname
545
    if serializer.validated_data.get("search"):
1✔
546
        search = serializer.validated_data.get("search")
1✔
547
        queryset = queryset.annotate(
1✔
548
            fullname=Concat("first_name", Value(" "), "last_name")
549
        )
550
        queryset = queryset.filter(
1✔
551
            Q(email__icontains=search) | Q(fullname__icontains=search)
552
        )
553
    # First get unique IDs to avoid duplicates from joins
554
    # But make sure to include current user's ID
555
    user_ids = list(queryset.exclude(**exclude_data)
1✔
556
                    .values_list('id', flat=True)
557
                    .distinct())
558

559
    # Then query again with the distinct IDs
560
    queryset = (
1✔
561
        SystemUser.objects.filter(id__in=user_ids)
562
        .annotate(last_updated=Coalesce("updated", Value(the_past)))
563
        .order_by("-last_updated", "-date_joined")
564
    )
565
    paginator = PageNumberPagination()
1✔
566
    instance = paginator.paginate_queryset(queryset, request)
1✔
567
    data = {
1✔
568
        "current": request.GET.get("page"),
569
        "data": ListUserSerializer(instance=instance, many=True).data,
570
        "total": queryset.count(),
571
        "total_page": ceil(queryset.count() / page_size),
572
    }
573
    return Response(data, status=status.HTTP_200_OK)
1✔
574

575

576
@extend_schema(
1✔
577
    responses={200: RoleOptionSerializer(many=True)},
578
    tags=["User"],
579
    summary="Get list of roles",
580
)
581
@api_view(["GET"])
1✔
582
def get_user_roles(request, version):
1✔
583
    roles = Role.objects.order_by("administration_level__level").all()
×
584
    data = RoleOptionSerializer(
×
585
        instance=roles,
586
        many=True,
587
        context={"request": request},
588
    ).data
589
    return Response(data, status=status.HTTP_200_OK)
×
590

591

592
class UserEditDeleteView(APIView):
1✔
593
    permission_classes = [IsAuthenticated, AddUserAccess]
1✔
594

595
    @extend_schema(
1✔
596
        responses={200: UserDetailSerializer, 204: DefaultResponseSerializer},
597
        tags=["User"],
598
        summary="To get user details",
599
    )
600
    def get(self, request, user_id, version):
1✔
601
        instance = get_object_or_404(SystemUser, pk=user_id, deleted_at=None)
1✔
602
        return Response(
1✔
603
            UserDetailSerializer(
604
                instance=instance,
605
                context={"user": request.user}
606
            ).data,
607
            status=status.HTTP_200_OK,
608
        )
609

610
    @extend_schema(
1✔
611
        responses={
612
            204: OpenApiResponse(description="Deletion with no response")
613
        },
614
        tags=["User"],
615
        summary="To delete user",
616
    )
617
    def delete(self, request, user_id, version):
1✔
618
        login_user = SystemUser.objects.get(email=request.user)
×
619
        instance = get_object_or_404(SystemUser, pk=user_id)
×
620
        # prevent self deletion
621
        if login_user.id == instance.id:
×
622
            return Response(
×
623
                {"message": "Could not do self deletion"},
624
                status=status.HTTP_409_CONFLICT,
625
            )
626
        instance.soft_delete()
×
627
        return Response(status=status.HTTP_204_NO_CONTENT)
×
628

629
    @extend_schema(
1✔
630
        request=AddEditUserSerializer,
631
        responses={200: DefaultResponseSerializer},
632
        tags=["User"],
633
        description="Role Choice are SuperAdmin:1,Admin:2,User:4",
634
        summary="To update user",
635
    )
636
    def put(self, request, user_id, version):
1✔
637
        instance = get_object_or_404(SystemUser, pk=user_id, deleted_at=None)
×
638
        serializer = AddEditUserSerializer(
×
639
            data=request.data,
640
            context={"user": request.user},
641
            instance=instance,
642
        )
643
        if not serializer.is_valid():
×
644
            return Response(
×
645
                {"message": validate_serializers_message(serializer.errors)},
646
                status=status.HTTP_400_BAD_REQUEST,
647
            )
648

649
        user = serializer.save()
×
650

651
        # inform user by inform_user payload
652
        if serializer.validated_data.get("inform_user"):
×
653
            send_email_to_user(
×
654
                type=EmailTypes.user_update, user=user, request=request
655
            )
656
        return Response(
×
657
            {"message": "User updated successfully"}, status=status.HTTP_200_OK
658
        )
659

660

661
@extend_schema(
1✔
662
    request=ForgotPasswordSerializer,
663
    responses={200: DefaultResponseSerializer},
664
    tags=["User"],
665
    summary="To send reset password instructions",
666
)
667
@api_view(["POST"])
1✔
668
def forgot_password(request, version):
1✔
669
    serializer = ForgotPasswordSerializer(data=request.data)
1✔
670
    if not serializer.is_valid():
1✔
671
        return Response(
1✔
672
            {"message": validate_serializers_message(serializer.errors)},
673
            status=status.HTTP_400_BAD_REQUEST,
674
        )
675
    user: SystemUser = serializer.validated_data.get("email")
1✔
676
    url = f"{WEBDOMAIN}/login/{signing.dumps(user.pk)}"
1✔
677
    data = {"button_url": url, "send_to": [user.email]}
1✔
678
    send_email(type=EmailTypes.user_forgot_password, context=data)
1✔
679
    return Response(
1✔
680
        {"message": "Reset password instructions sent to your email"},
681
        status=status.HTTP_200_OK,
682
    )
683

684

685
@extend_schema(
1✔
686
    responses={200: OrganisationListSerializer(many=True)},
687
    parameters=[
688
        OpenApiParameter(
689
            name="attributes",
690
            required=False,
691
            type=OpenApiTypes.NUMBER,
692
            location=OpenApiParameter.QUERY,
693
        ),
694
        OpenApiParameter(
695
            name="id",
696
            required=False,
697
            type=OpenApiTypes.NUMBER,
698
            location=OpenApiParameter.QUERY,
699
        ),
700
        OpenApiParameter(
701
            name="search",
702
            required=False,
703
            type=OpenApiTypes.STR,
704
            location=OpenApiParameter.QUERY,
705
        ),
706
    ],
707
    tags=["Organisation"],
708
    summary="Get list of organisation",
709
)
710
@api_view(["GET"])
1✔
711
def list_organisations(request, version):
1✔
712
    id = request.GET.get("id")
1✔
713
    attributes = request.GET.get("attributes")
1✔
714
    search = request.GET.get("search")
1✔
715

716
    instance = Organisation.objects.prefetch_related(
1✔
717
        'organisation_organisation_attribute'
718
    ).annotate(user_count=Count('user_organisation')).all()
719

720
    if id:
1!
721
        instance = instance.filter(pk=id)
×
722
    if attributes and not id:
1✔
723
        ids = OrganisationAttribute.objects.filter(type=attributes).distinct(
1✔
724
            "organisation_id"
725
        )
726
        instance = instance.filter(
1✔
727
            pk__in=[o.organisation_id for o in ids]
728
        )
729
    if search and not id:
1✔
730
        instance = instance.filter(name__icontains=search)
1✔
731

732
    return Response(
1✔
733
        OrganisationListSerializer(instance=instance, many=True).data,
734
        status=status.HTTP_200_OK,
735
    )
736

737

738
@extend_schema(
1✔
739
    request=AddEditOrganisationSerializer,
740
    responses={200: DefaultResponseSerializer},
741
    tags=["Organisation"],
742
    description="Attributes are member:1,partnership:2",
743
    summary="To add new organisation",
744
)
745
@api_view(["POST"])
1✔
746
@permission_classes([IsAuthenticated, IsSuperAdmin])
1✔
747
def add_organisation(request, version):
1✔
748
    serializer = AddEditOrganisationSerializer(
1✔
749
        data=request.data,
750
        context={"attributes": request.data.get("attributes")},
751
    )
752
    if not serializer.is_valid():
1!
753
        return Response(
×
754
            {"message": validate_serializers_message(serializer.errors)},
755
            status=status.HTTP_400_BAD_REQUEST,
756
        )
757
    serializer.save()
1✔
758
    return Response(
1✔
759
        {"message": "Organisation added successfully"},
760
        status=status.HTTP_200_OK,
761
    )
762

763

764
class OrganisationEditDeleteView(APIView):
1✔
765
    permission_classes = [IsAuthenticated, IsSuperAdmin]
1✔
766

767
    @extend_schema(
1✔
768
        responses={200: OrganisationListSerializer},
769
        tags=["Organisation"],
770
        summary="To get organisation details",
771
    )
772
    def get(self, request, organisation_id, version):
1✔
773
        instance = get_object_or_404(
1✔
774
            Organisation.objects.annotate(
775
                user_count=Count('user_organisation')
776
            ),
777
            pk=organisation_id
778
        )
779
        return Response(
1✔
780
            OrganisationListSerializer(instance=instance).data,
781
            status=status.HTTP_200_OK,
782
        )
783

784
    @extend_schema(
1✔
785
        responses={
786
            204: OpenApiResponse(description="Deletion with no response")
787
        },
788
        tags=["Organisation"],
789
        summary="To delete organisation",
790
    )
791
    def delete(self, request, organisation_id, version):
1✔
792
        instance = get_object_or_404(Organisation, pk=organisation_id)
1✔
793
        instance.delete()
1✔
794
        return Response(status=status.HTTP_204_NO_CONTENT)
1✔
795

796
    @extend_schema(
1✔
797
        request=AddEditOrganisationSerializer,
798
        responses={
799
            200: DefaultResponseSerializer,
800
            400: DefaultResponseSerializer,
801
        },
802
        tags=["Organisation"],
803
        description="Attributes are member:1,partnership:2",
804
        summary="To update organisation",
805
    )
806
    def put(self, request, organisation_id, version):
1✔
807
        instance = get_object_or_404(Organisation, pk=organisation_id)
1✔
808
        serializer = AddEditOrganisationSerializer(
1✔
809
            data=request.data,
810
            context={"attributes": request.data.get("attributes")},
811
            instance=instance,
812
        )
813
        if not serializer.is_valid():
1!
814
            return Response(
×
815
                {"message": validate_serializers_message(serializer.errors)},
816
                status=status.HTTP_400_BAD_REQUEST,
817
            )
818
        serializer.save()
1✔
819
        return Response(
1✔
820
            {"message": "Organisation updated successfully"},
821
            status=status.HTTP_200_OK,
822
        )
823

824

825
@extend_schema(
1✔
826
    responses={200: OrganisationAttributeChildrenSerializer},
827
    parameters=[
828
        OpenApiParameter(
829
            name="attribute",
830
            required=True,
831
            enum=OrganisationTypes.FieldStr.keys(),
832
            type=OpenApiTypes.NUMBER,
833
            location=OpenApiParameter.QUERY,
834
        ),
835
        OpenApiParameter(
836
            name="selected_id",
837
            required=False,
838
            location=OpenApiParameter.PATH,
839
            type=OpenApiTypes.NUMBER,
840
            description="ID of the selected organization (optional)",
841
        ),
842
    ],
843
    tags=["Organisation"],
844
    summary="Get list of organisations for webform options",
845
)
846
@api_view(["GET"])
1✔
847
def list_organisation_options(request, version, selected_id=None):
1✔
848
    attribute = request.GET.get("attribute")
1✔
849
    if selected_id:
1✔
850
        return Response(
1✔
851
            {"type_id": attribute, "name": selected_id, "children": []},
852
            status=status.HTTP_200_OK,
853
        )
854
    instance = None
1✔
855
    if attribute:
1!
856
        instance = OrganisationAttribute.objects.filter(type=attribute).first()
1✔
857
    return Response(
1✔
858
        OrganisationAttributeChildrenSerializer(instance=instance).data,
859
        status=status.HTTP_200_OK,
860
    )
861

862

863
@extend_schema(
1✔
864
    request=UpdateProfileSerializer,
865
    responses={200: UserSerializer},
866
    tags=["User"],
867
    summary="To update user profile",
868
)
869
@api_view(["PUT"])
1✔
870
@permission_classes([IsAuthenticated])
1✔
871
def update_profile(request, version):
1✔
872
    serializer = UpdateProfileSerializer(
1✔
873
        data=request.data, instance=request.user
874
    )
875
    if not serializer.is_valid():
1✔
876
        return Response(
1✔
877
            {"message": validate_serializers_message(serializer.errors)},
878
            status=status.HTTP_400_BAD_REQUEST,
879
        )
880
    user = serializer.save()
1✔
881
    return Response(
1✔
882
        UserSerializer(instance=user).data,
883
        status=status.HTTP_200_OK
884
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc