From 757fdcd3720599e7e2e5f08c8ba2bd0ab7351cec Mon Sep 17 00:00:00 2001 From: Horilla Date: Sat, 2 Aug 2025 16:23:04 +0530 Subject: [PATCH] [UPDT] HORILLA_API: Employee and attendance view with permsn --- .../api_serializers/attendance/serializers.py | 12 ++ horilla_api/api_urls/attendance/urls.py | 2 + horilla_api/api_urls/employee/urls.py | 2 +- horilla_api/api_views/attendance/views.py | 49 ++++++- horilla_api/api_views/employee/views.py | 133 +++++++++++------- 5 files changed, 142 insertions(+), 56 deletions(-) diff --git a/horilla_api/api_serializers/attendance/serializers.py b/horilla_api/api_serializers/attendance/serializers.py index 9c10d5598..853a2c656 100644 --- a/horilla_api/api_serializers/attendance/serializers.py +++ b/horilla_api/api_serializers/attendance/serializers.py @@ -210,3 +210,15 @@ class MailTemplateSerializer(serializers.ModelSerializer): class Meta: model = HorillaMailTemplate fields = "__all__" + + +class UserAttendanceListSerializer(serializers.ModelSerializer): + class Meta: + model = Attendance + fields = [ + "id", + "attendance_date", + "attendance_clock_in", + "attendance_clock_out", + "attendance_worked_hour", + ] diff --git a/horilla_api/api_urls/attendance/urls.py b/horilla_api/api_urls/attendance/urls.py index 012c1484e..00bc37e4b 100644 --- a/horilla_api/api_urls/attendance/urls.py +++ b/horilla_api/api_urls/attendance/urls.py @@ -55,4 +55,6 @@ urlpatterns = [ path("offline-employee-mail-send", OfflineEmployeeMailsend.as_view()), path("converted-mail-template", ConvertedMailTemplateConvert.as_view()), path("mail-templates", MailTemplateView.as_view()), + path("my-attendance/", UserAttendanceView.as_view()), + path("attendance-type-check/", AttendanceTypeAccessCheck.as_view()), ] diff --git a/horilla_api/api_urls/employee/urls.py b/horilla_api/api_urls/employee/urls.py index 6496c6663..19a74b950 100644 --- a/horilla_api/api_urls/employee/urls.py +++ b/horilla_api/api_urls/employee/urls.py @@ -3,7 +3,7 @@ from django.urls import path from ...api_views.employee import views as views urlpatterns = [ - path("employees/", views.EmployeeAPIView.as_view(), name="api-employees-list"), + # path('employees/', views.EmployeeAPIView.as_view(), name='api-employees-list'), path( "employees//", views.EmployeeAPIView.as_view(), diff --git a/horilla_api/api_views/attendance/views.py b/horilla_api/api_views/attendance/views.py index cc451889b..631f47edf 100644 --- a/horilla_api/api_views/attendance/views.py +++ b/horilla_api/api_views/attendance/views.py @@ -7,6 +7,7 @@ from django.db.models import Case, CharField, F, Value, When from django.http import QueryDict from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator +from rest_framework import status from rest_framework.pagination import PageNumberPagination from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -38,6 +39,7 @@ from ...api_serializers.attendance.serializers import ( AttendanceRequestSerializer, AttendanceSerializer, MailTemplateSerializer, + UserAttendanceListSerializer, ) # Create your views here. @@ -65,7 +67,6 @@ class ClockInAPIView(APIView): permission_classes = [IsAuthenticated] def post(self, request): - print("========", request.user.employee_get.check_online()) if not request.user.employee_get.check_online(): try: if request.user.employee_get.get_company().geo_fencing.start: @@ -158,7 +159,6 @@ class ClockOutAPIView(APIView): except: pass if request.user.employee_get.check_online(): - print("----------------") current_date = date.today() current_time = datetime.now().time() current_datetime = datetime.now() @@ -722,6 +722,7 @@ class OfflineEmployeesCountView(APIView): permission_classes = [IsAuthenticated] + @method_decorator(permission_required("employee.view_employee")) def get(self, request): count = ( EmployeeFilter({"not_in_yet": date.today()}) @@ -742,6 +743,7 @@ class OfflineEmployeesListView(APIView): permission_classes = [IsAuthenticated] + @method_decorator(permission_required("employee.view_employee")) def get(self, request): queryset = ( EmployeeFilter({"not_in_yet": date.today()}) @@ -972,3 +974,46 @@ class OfflineEmployeeMailsend(APIView): return Response(f"Email not set for {employee.get_full_name()}") except Exception as e: return Response("Something went wrong") + + +class UserAttendanceView(APIView): + permission_classes = [IsAuthenticated] + serializer_class = UserAttendanceListSerializer + + def get(self, request): + employee_id = request.user.employee_get.id + + attendance_queryset = Attendance.objects.filter( + employee_id=employee_id + ).order_by("-id") + + paginator = PageNumberPagination() + paginator.page_size = 20 + page = paginator.paginate_queryset(attendance_queryset, request) + + serializer = self.serializer_class(page, many=True) + return paginator.get_paginated_response(serializer.data) + + +class AttendanceTypeAccessCheck(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + user = request.user + employee_id = user.employee_get.id + + if user.has_perm("attendance.view_attendance"): + return Response(status=200) + + is_manager = ( + EmployeeWorkInformation.objects.filter(reporting_manager_id=employee_id) + .only("id") + .exists() + ) + + if is_manager: + return Response(status=200) + + return Response( + {"error": "Permission denied"}, status=status.HTTP_403_FORBIDDEN + ) diff --git a/horilla_api/api_views/employee/views.py b/horilla_api/api_views/employee/views.py index 3cc68bc57..fe29c3682 100644 --- a/horilla_api/api_views/employee/views.py +++ b/horilla_api/api_views/employee/views.py @@ -93,49 +93,61 @@ class EmployeeTypeAPIView(APIView): class EmployeeAPIView(APIView): """ Handles CRUD operations for employees. - - Methods: - get(request, pk=None): - - Retrieves a single employee by pk if provided. - - Retrieves and filters all employees if pk is not provided. - - post(request): - - Creates a new employee if the user has the 'employee.change_employee' permission. - - put(request, pk): - - Updates an existing employee if the user is the employee, a manager, or has 'employee.change_employee' permission. - - delete(request, pk): - - Deletes an employee if the user has the 'employee.delete_employee' permission. """ filter_backends = [DjangoFilterBackend] filterset_class = EmployeeFilter permission_classes = [IsAuthenticated] - def get(self, request, pk=None): - if pk: - try: - employee = Employee.objects.get(pk=pk) - except Employee.DoesNotExist: - return Response( - {"error": "Employee does not exist"}, - status=status.HTTP_404_NOT_FOUND, - ) + def get(self, request, pk): + user = request.user + try: + employee = Employee.objects.only( + "id", + "employee_first_name", + "employee_last_name", # include only needed fields + ).get(pk=pk) + except Employee.DoesNotExist: + return Response( + {"error": "Employee does not exist"}, status=status.HTTP_404_NOT_FOUND + ) + + # If user has global view permission + if user.has_perm("employee.view_employee"): serializer = EmployeeSerializer(employee) return Response(serializer.data) - paginator = PageNumberPagination() - employees_queryset = Employee.objects.all() - employees_filter_queryset = self.filterset_class( - request.GET, queryset=employees_queryset - ).qs - field_name = request.GET.get("groupby_field", None) - if field_name: - url = request.build_absolute_uri() - return groupby_queryset(request, url, field_name, employees_filter_queryset) - page = paginator.paginate_queryset(employees_filter_queryset, request) - serializer = EmployeeSerializer(page, many=True) - return paginator.get_paginated_response(serializer.data) + + # If employee is in user's subordinates + subordinates = user.employee_get.get_subordinate_employees() + if subordinates.filter(pk=pk).exists(): + serializer = EmployeeSerializer(employee) + return Response(serializer.data) + + # If requesting own data + if employee.pk == user.employee_get.id: + serializer = EmployeeSerializer(employee) + return Response(serializer.data) + + return Response( + {"error": "Permission denied"}, status=status.HTTP_403_FORBIDDEN + ) + + # paginator = PageNumberPagination() + # if request.user.has_perm('employee.view_employee'): + # employees_queryset = Employee.objects.all() + # elif request.user.employee_get.get_subordinate_employees(): + # employees_queryset = request.user.employee_get.get_subordinate_employees() + # else: + # employees_queryset = [request.user.employee_get] + # employees_filter_queryset = self.filterset_class( + # request.GET, queryset=employees_queryset).qs + # field_name = request.GET.get("groupby_field", None) + # if field_name: + # url = request.build_absolute_uri() + # return groupby_queryset(request, url, field_name, employees_filter_queryset) + # page = paginator.paginate_queryset(employees_filter_queryset, request) + # serializer = EmployeeSerializer(page, many=True) + # return paginator.get_paginated_response(serializer.data) @method_decorator(permission_required("employee.add_employee")) def post(self, request): @@ -176,27 +188,42 @@ class EmployeeAPIView(APIView): class EmployeeListAPIView(APIView): """ Retrieves a paginated list of employees with optional search functionality. - - Methods: - get(request): - - Returns a paginated list of employees. - - Optionally filters employees based on a search query in the first or last name. """ permission_classes = [IsAuthenticated] def get(self, request): - paginator = PageNumberPagination() - paginator.page_size = 13 - search = request.query_params.get("search", None) + user = request.user + search = request.query_params.get("search") + + # Start with a base queryset with only required fields + employees_queryset = Employee.objects.only( + "id", "employee_first_name", "employee_last_name" + ) + + # Permission-based filtering + if user.has_perm("employee.view_employee"): + pass # employees_queryset is already all employees + else: + subordinate_qs = user.employee_get.get_subordinate_employees() + if subordinate_qs.exists(): + employees_queryset = subordinate_qs.only( + "id", "employee_first_name", "employee_last_name" + ) + else: + employees_queryset = employees_queryset.filter(id=user.employee_get.id) + + # Apply search filter if provided if search: - employees_queryset = Employee.objects.filter( + employees_queryset = employees_queryset.filter( Q(employee_first_name__icontains=search) | Q(employee_last_name__icontains=search) ) - else: - employees_queryset = Employee.objects.all() + + # Paginate + paginator = PageNumberPagination() page = paginator.paginate_queryset(employees_queryset, request) + serializer = EmployeeListSerializer(page, many=True) return paginator.get_paginated_response(serializer.data) @@ -386,14 +413,14 @@ class EmployeeWorkInfoImportView(APIView): class EmployeeBulkUpdateView(APIView): """ - Endpoint for bulk updating employee and work information. + Endpoint for bulk updating employee and work information. - Permissions: - - Requires authentication and "change_employee" permission. - 0 - Methods: - put(request): - - Updates multiple employees and their work information. + Permissions: + - Requires authentication and "change_employee" permission. + + Methods: + put(request): + - Updates multiple employees and their work information. """ permission_classes = [IsAuthenticated]