diff --git a/apps/card_portal/admin.py b/apps/card_portal/admin.py index dfd65c5..d5486e4 100644 --- a/apps/card_portal/admin.py +++ b/apps/card_portal/admin.py @@ -93,7 +93,7 @@ class EmployeesDailyAttendanceAdmin(admin.ModelAdmin): 'employee__first_name', 'employee__last_name', 'employee__email', 'date', 'in_time', 'out_time', 'is_present' ) search_fields = ('employee__first_name', 'employee__last_name', 'employee__email', 'date', 'in_time', 'out_time') - readonly_fields = ('created_at', 'updated_at', 'date', 'in_time', 'out_time', 'employee', 'is_present', "employee") + readonly_fields = ('created_at', 'updated_at', 'date', 'in_time', 'out_time', 'employee', 'is_present') def has_add_permission(self, request): return False @@ -150,8 +150,8 @@ def clean_password(self): @admin.register(Machine) class MachineAdmin(admin.ModelAdmin): - form = UserChangeForm - add_form = UserChangeForm + # form = UserChangeForm + # add_form = UserChangeForm list_display = ('model', 'manufacturer') list_filter = ('model', 'manufacturer') search_fields = ('model', 'manufacturer') diff --git a/apps/card_portal/serializers.py b/apps/card_portal/serializers.py index dad1a0d..3673f5c 100644 --- a/apps/card_portal/serializers.py +++ b/apps/card_portal/serializers.py @@ -1,7 +1,8 @@ from datetime import date - +import traceback from django.contrib.auth.hashers import check_password from django.contrib.auth.models import update_last_login +from django.db import transaction, DatabaseError from rest_framework import serializers, status from rest_framework_simplejwt.serializers import TokenObtainPairSerializer @@ -23,6 +24,7 @@ def validate(self, attrs): return attrs def create(self, validated_data): + context = self.context['request'] _date: date = timezone.now().astimezone(tz=timezone.get_current_timezone()).date() current_time = timezone.now().astimezone(tz=timezone.get_current_timezone()).time() last_attendance = EmployeesDailyAttendance.objects.filter( @@ -31,48 +33,58 @@ def create(self, validated_data): ).last() # TODO: Check if the employee is permitted to use the machine - machine = None + machine = context.user + employee = Employee.objects.filter(rdf_number=validated_data['rdf']).first() + if employee is None: + raise serializers.ValidationError({"message": "Employee not found"}) + if machine.machinepermittedemployee_set.filter(employee=employee).first() is None: + raise serializers.ValidationError({"message": "Access Denied. Employee is not permitted to use this machine"}) if last_attendance is not None: check_in, check_out, rdf = self._extract_attendance_data_from_validated_data(validated_data) if last_attendance.in_time is not None and last_attendance.out_time is not None: - if check_out is not None: - self._create_access_card_log( - employee=last_attendance.employee, - _date=_date, - time=current_time, - access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_OUT, - machine=machine, - success=True - ) - response = serializers.ValidationError({"message": "Check-in should be done first"}) - response.status_code = status.HTTP_200_OK - raise response - if current_time < last_attendance.out_time and _date <= last_attendance.date: - self._create_access_card_log( - employee=last_attendance.employee, - _date=_date, - time=current_time, - access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN, - machine=machine, - success=False - ) - response = serializers.ValidationError( - {"message": "Check-in time must be greater than check-out time of last attendance"}) - raise response - if check_in is not None: - validated_data['is_present'] = True - validated_data['in_time'] = current_time - employee = Employee.objects.get(rdf_number=rdf) - self._create_access_card_log( - employee=last_attendance.employee, - _date=_date, - time=current_time, - access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN, - machine=machine, - success=True - ) - return EmployeesDailyAttendance.objects.create(employee=employee, **validated_data) + try: + with transaction.atomic(): + if check_out is not None: + self._create_access_card_log( + employee=last_attendance.employee, + _date=_date, + time=current_time, + access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_OUT, + machine=machine, + success=True + ) + response = serializers.ValidationError({"message": "Check-in should be done first"}) + response.status_code = status.HTTP_200_OK + raise response + if current_time < last_attendance.out_time and _date <= last_attendance.date: + self._create_access_card_log( + employee=last_attendance.employee, + _date=_date, + time=current_time, + access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN, + machine=machine, + success=False + ) + response = serializers.ValidationError( + {"message": "Check-in time must be greater than check-out time of last attendance"}) + raise response + if check_in is not None: + validated_data['is_present'] = True + validated_data['in_time'] = current_time + employee = Employee.objects.get(rdf_number=rdf) + self._create_access_card_log( + employee=last_attendance.employee, + _date=_date, + time=current_time, + access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN, + machine=machine, + success=True + ) + return EmployeesDailyAttendance.objects.create(employee=employee, **validated_data) + except DatabaseError as db_exec: + print(traceback.format_exc()) + raise serializers.ValidationError({"message": "Check-in failed"}) if check_in is not None: if last_attendance.in_time is not None and last_attendance.out_time is None: response = serializers.ValidationError({"message": "Check-in already done"}) @@ -118,7 +130,7 @@ def create(self, validated_data): else: if validated_data.get('check_in') is None: check_in, check_out, rdf = self._extract_attendance_data_from_validated_data(validated_data) - employee = Employee.objects.get(rdf_number=rdf) + employee = Employee.objects.filter(rdf_number=rdf).first() self._create_access_card_log( employee=employee, _date=_date, @@ -129,17 +141,26 @@ def create(self, validated_data): ) raise serializers.ValidationError({"message": "Check-in is required"}) rdf = validated_data.pop('rdf') - employee = Employee.objects.get(rdf_number=rdf) - employee_daily_attendance = self._create_employee(rdf, validated_data, current_time, _date) - if employee_daily_attendance is not None: - self._create_access_card_log( - employee=employee, - _date=_date, - time=current_time, - access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN, - machine=machine, - success=True - ) + employee = Employee.objects.filter(rdf_number=rdf).first() + if employee is None: + raise serializers.ValidationError({"message": "Employee not found"}) + try: + with transaction.atomic(): + employee_daily_attendance = self._create_employee_attendance(rdf, validated_data, current_time, + _date, machine) + if employee_daily_attendance is not None: + self._create_access_card_log( + employee=employee, + _date=_date, + time=current_time, + access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN, + machine=machine, + success=True + ) + except DatabaseError: + traceback.print_exc() + raise serializers.ValidationError({"message": "Check-in failed. Contact the admin"}) + return employee_daily_attendance @staticmethod @@ -161,10 +182,11 @@ def _create_access_card_log(access_type, employee, _date, time, machine, success ) @staticmethod - def _create_employee(rdf, validated_data, current_time, _date): + def _create_employee_attendance(rdf, validated_data, current_time, _date, machine): validated_data['is_present'] = True validated_data['in_time'] = current_time validated_data['date'] = _date + validated_data['machine'] = machine employee = Employee.objects.get(rdf_number=rdf) validated_data.pop('check_out') validated_data.pop('check_in') @@ -197,7 +219,9 @@ class MyTokenObtainPairSerializer(TokenObtainPairSerializer): # noqa def validate(self, attrs): data = {} - self.user = Machine.objects.get(email=attrs['email']) # noqa + self.user = Machine.objects.filter(email=attrs['email']).first() # noqa + if self.user is None: + raise serializers.ValidationError({"message": "Invalid Email, Machine not found"}) if not check_password(attrs['password'], self.user.password): raise serializers.ValidationError({"message": "Invalid Password"}) refresh = self.get_token(self.user) diff --git a/apps/card_portal/views/employees.py b/apps/card_portal/views/employees.py index 3982138..53b87c0 100644 --- a/apps/card_portal/views/employees.py +++ b/apps/card_portal/views/employees.py @@ -23,7 +23,7 @@ class EmployeesDailyAttendanceViewSet(viewsets.ModelViewSet): queryset = EmployeesDailyAttendance.objects.all() http_method_names = ['post'] - # permission_classes = [MachineIsAuthenticated] + permission_classes = [IsAuthenticated] @extend_schema( request=EmployeesDailyAttendanceCreationSerializer, diff --git a/utils/mixins.py b/utils/mixins.py index 053804d..a8830e5 100644 --- a/utils/mixins.py +++ b/utils/mixins.py @@ -1,15 +1,11 @@ -from rest_framework_simplejwt.authentication import JWTAuthentication -from typing import Set - -from django.contrib.auth import get_user_model from django.db import models -from rest_framework import authentication, HTTP_HEADER_ENCODING +from django.utils.translation import gettext_lazy as _ +from drf_spectacular.contrib.rest_framework_simplejwt import SimpleJWTScheme +from rest_framework import HTTP_HEADER_ENCODING +from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework_simplejwt.exceptions import AuthenticationFailed, TokenError, InvalidToken from rest_framework_simplejwt.settings import api_settings - -from django.utils.translation import gettext_lazy as _ - - +from typing import Set class TimeStampModelMixin(models.Model): @@ -24,6 +20,7 @@ class BaseModelMixin(TimeStampModelMixin): class Meta: abstract = True + AUTH_HEADER_TYPES = api_settings.AUTH_HEADER_TYPES if not isinstance(api_settings.AUTH_HEADER_TYPES, (list, tuple)): @@ -32,6 +29,9 @@ class Meta: AUTH_HEADER_TYPE_BYTES: Set[bytes] = { h.encode(HTTP_HEADER_ENCODING) for h in AUTH_HEADER_TYPES } + + + class JWTAuthentication_(JWTAuthentication): """ An authentication plugin that authenticates requests through a JSON web @@ -144,3 +144,6 @@ def get_user(self, validated_token): raise AuthenticationFailed(_("User is inactive"), code="user_inactive") return user + +class SimpleJWTTokenUserScheme(SimpleJWTScheme): + target_class = JWTAuthentication_