Skip to content

Commit

Permalink
Merge pull request #19 from MahirMahbub/feature/attendance_by_card
Browse files Browse the repository at this point in the history
add authentication and local time, machine tracking
  • Loading branch information
MahirMahbub authored Jun 3, 2023
2 parents 776efc2 + 60b834f commit d10b1a6
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 66 deletions.
6 changes: 3 additions & 3 deletions apps/card_portal/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down
130 changes: 77 additions & 53 deletions apps/card_portal/serializers.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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(
Expand All @@ -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"})
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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')
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion apps/card_portal/views/employees.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 12 additions & 9 deletions utils/mixins.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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)):
Expand All @@ -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
Expand Down Expand Up @@ -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_

0 comments on commit d10b1a6

Please sign in to comment.