Skip to content

Commit

Permalink
Merge pull request #18 from MahirMahbub/feature/attendance_by_card
Browse files Browse the repository at this point in the history
Feature/attendance by card
  • Loading branch information
MahirMahbub authored Jun 3, 2023
2 parents 35d3d8d + 7cfa520 commit 776efc2
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 58 deletions.
35 changes: 32 additions & 3 deletions apps/card_portal/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from django.contrib import admin
from django.contrib.auth.forms import AdminPasswordChangeForm, PasswordChangeForm, \
ReadOnlyPasswordHashField
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _

from apps.card_portal.models import Employee, EmployeesDesignation, EmployeesImage, EmployeesDailyAttendance, Machine, \
MachinePermittedEmployee, EmployeeAccessCardUsageLog, GenericUser
MachinePermittedEmployee, EmployeeAccessCardUsageLog


# admin.site.register(GenericUser, UserAdmin)
class EmployeesImageAdminInline(admin.TabularInline):
fk_name = "employee"
model = EmployeesImage
Expand Down Expand Up @@ -123,15 +127,41 @@ def get_extra(self, request, obj=None, **kwargs):
return extra


class UserChangeForm(ModelForm):
password = ReadOnlyPasswordHashField(
label=_("Password"),
help_text=_(
"Raw passwords are not stored, so there is no way to see this "
"user’s password, but you can change the password using "
'<a href=\"../password/\">this form</a>'
),
)

class Meta:
model = Machine
fields = '__all__'

def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the
# field does not have access to the initial value
return self.initial["password"]


@admin.register(Machine)
class MachineAdmin(admin.ModelAdmin):
form = UserChangeForm
add_form = UserChangeForm
list_display = ('model', 'manufacturer')
list_filter = ('model', 'manufacturer')
search_fields = ('model', 'manufacturer')
# readonly_fields = ('last_login',)
inlines = [EmployeesMachineAdminInline]


# change_password_form = PasswordChangeForm


@admin.register(MachinePermittedEmployee)
class MachinePermittedEmployeeAdmin(admin.ModelAdmin):
list_display = ('employee', 'machine', 'start_date', 'expiry_date')
Expand All @@ -150,7 +180,6 @@ class EmployeeAccessCardUsageLogAdmin(admin.ModelAdmin):
search_fields = (
'employee__first_name', 'employee__last_name', 'employee__email', 'machine', 'access_type'
)
# readonly_fields = ('employee', 'machine', 'access_card_number', 'access_card_type', 'access_time')

def has_add_permission(self, request):
return False
Expand Down
2 changes: 1 addition & 1 deletion apps/card_portal/apps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.apps import AppConfig


class PaymentPortalConfig(AppConfig):
class AccessControlPortalConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.card_portal"
3 changes: 0 additions & 3 deletions apps/card_portal/models/employees.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.db import models
from django.utils.safestring import mark_safe


from utils.mixins import BaseModelMixin


Expand Down Expand Up @@ -81,5 +80,3 @@ def __str__(self):
return "ID: " + str(self.id) + ", EMPLOYEE_ID: " + str(
self.employee.id) + ", DATE: " + str(self.date) + ", IS_PRESENT: " + str(
self.is_present)


11 changes: 2 additions & 9 deletions apps/card_portal/models/machines.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.contrib.auth.hashers import make_password
from django.db import models
from django.utils import timezone
from apps.card_portal.models.user import AbstractUser, CallableUser

from apps.card_portal.models.user import AbstractUser
from utils.mixins import BaseModelMixin


Expand All @@ -14,13 +14,6 @@ class Machine(BaseModelMixin, AbstractUser):
manufacturer = models.CharField(max_length=128, null=True, blank=True)
employee = models.ManyToManyField(Employee, related_name='machine', through='MachinePermittedEmployee', blank=True)

# def save(self, *args, **kwargs):
# now = timezone.now()
# u = CallableUser(last_login=now)
# u.set_password(self.password)
# u.save()
# self.callableuser_ptr_id = u.id
# super().save(*args, **kwargs)
def save(self, *args, **kwargs):
# self.password =
self.password = make_password(self.password)
Expand Down
92 changes: 52 additions & 40 deletions apps/card_portal/serializers.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from django.contrib.auth.hashers import make_password, check_password
from datetime import date

from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import update_last_login
from rest_framework import serializers, status
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

from apps.card_portal.models import EmployeesDailyAttendance, Employee, EmployeeAccessCardUsageLog, Machine
from django.utils import timezone


class EmployeesDailyAttendanceCreationSerializer(serializers.Serializer): # noqa
rdf = serializers.CharField(required=True, help_text="RDF number of the employee")
date = serializers.DateField(required=True, help_text="Date of the attendance")
check_in = serializers.TimeField(required=False, default=None, help_text="Check in time of the employee")
check_out = serializers.TimeField(required=False, default=None, help_text="Check out time of the employee")
# date = serializers.DateField(required=True, help_text="Date of the attendance")
check_in = serializers.BooleanField(required=False, default=None, help_text="Check in time of the employee")
check_out = serializers.BooleanField(required=False, default=None, help_text="Check out time of the employee")

def validate(self, attrs):
if attrs.get('check_in') is None and attrs.get('check_out') is None:
Expand All @@ -20,34 +23,36 @@ def validate(self, attrs):
return attrs

def create(self, validated_data):
_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(
employee__rdf_number=validated_data['rdf'],
date=validated_data['date']
date=_date
).last()

# TODO: Check if the employee is permitted to use the machine
machine = None

if last_attendance is not None:
check_in, check_out, date, rdf = self._extract_attendance_data_from_validated_data(validated_data)
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=check_out,
_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 check_in < last_attendance.out_time and date <= last_attendance.date:
if current_time < last_attendance.out_time and _date <= last_attendance.date:
self._create_access_card_log(
employee=last_attendance.employee,
date=date,
time=check_in,
_date=_date,
time=current_time,
access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN,
machine=machine,
success=False
Expand All @@ -57,13 +62,12 @@ def create(self, validated_data):
raise response
if check_in is not None:
validated_data['is_present'] = True
validated_data['in_time'] = check_in
validated_data['date'] = date
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=check_in,
_date=_date,
time=current_time,
access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN,
machine=machine,
success=True
Expand All @@ -75,24 +79,24 @@ def create(self, validated_data):
response.status_code = status.HTTP_200_OK
self._create_access_card_log(
employee=last_attendance.employee,
date=date,
time=check_in,
_date=_date,
time=current_time,
access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN,
machine=machine,
success=True
)
raise response
if check_out is not None:
if last_attendance.out_time is None:
if last_attendance.in_time >= check_out:
if last_attendance.in_time >= current_time:
raise serializers.ValidationError(
{"message": "Check-out time must be greater than check-in time"})
last_attendance.out_time = check_out
last_attendance.out_time = current_time
last_attendance.save()
self._create_access_card_log(
employee=last_attendance.employee,
date=date,
time=check_out,
_date=_date,
time=current_time,
access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_OUT,
machine=machine,
success=False
Expand All @@ -101,8 +105,8 @@ def create(self, validated_data):
else:
self._create_access_card_log(
employee=last_attendance.employee,
date=date,
time=check_out,
_date=_date,
time=current_time,
access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_OUT,
machine=machine,
success=True
Expand All @@ -113,45 +117,57 @@ def create(self, validated_data):
raise response
else:
if validated_data.get('check_in') is None:
check_in, check_out, date, rdf = self._extract_attendance_data_from_validated_data(validated_data)
check_in, check_out, rdf = self._extract_attendance_data_from_validated_data(validated_data)
employee = Employee.objects.get(rdf_number=rdf)
self._create_access_card_log(
employee=employee,
date=date,
time=check_in,
_date=_date,
time=current_time,
access_type=EmployeeAccessCardUsageLog.AccessType.SIGN_IN,
machine=machine,
success=True
)
raise serializers.ValidationError({"message": "Check-in is required"})
rdf = validated_data.pop('rdf')
return self._create_employee(rdf, validated_data)
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
)
return employee_daily_attendance

@staticmethod
def _extract_attendance_data_from_validated_data(validated_data):
rdf = validated_data.pop('rdf')
date = validated_data.pop('date')
check_in = validated_data.pop('check_in', None)
check_out = validated_data.pop('check_out', None)
return check_in, check_out, date, rdf
return check_in, check_out, rdf

@staticmethod
def _create_access_card_log(access_type, employee, date, time, machine, success):
def _create_access_card_log(access_type, employee, _date, time, machine, success):
return EmployeeAccessCardUsageLog.objects.create(
employee=employee,
date=date,
date=_date,
time=time,
access_type=access_type,
machine=machine,
success=success
)

@staticmethod
def _create_employee(rdf, validated_data):
def _create_employee(rdf, validated_data, current_time, _date):
validated_data['is_present'] = True
validated_data['in_time'] = validated_data.pop('check_in')
validated_data['in_time'] = current_time
validated_data['date'] = _date
employee = Employee.objects.get(rdf_number=rdf)
validated_data.pop('check_out')
validated_data.pop('check_in')
return EmployeesDailyAttendance.objects.create(employee=employee, **validated_data)

def to_representation(self, instance):
Expand All @@ -177,20 +193,16 @@ class Meta:
fields = ['email', 'password']


class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
class MyTokenObtainPairSerializer(TokenObtainPairSerializer): # noqa

def validate(self, attrs):
data = {}
self.user = Machine.objects.get(email=attrs['email'])
self.user = Machine.objects.get(email=attrs['email']) # noqa
if not check_password(attrs['password'], self.user.password):
raise serializers.ValidationError({"message": "Invalid Password"})
refresh = self.get_token(self.user)

data["refresh"] = str(refresh)
data["access"] = str(refresh.access_token)
update_last_login(None, self.user)
# refresh = self.get_token(self.user)
#
# # assign token
# data['refresh'] = str(refresh)
# data['access'] = str(refresh.access_token)
return data
6 changes: 5 additions & 1 deletion apps/card_portal/views/employees.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from apps.card_portal.serializers import EmployeesDailyAttendanceCreationSerializer, EmployeesDailyAttendanceSerializer, \
MessageSerializer, MyTokenObtainPairSerializer


class MachineIsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
Expand All @@ -16,10 +17,12 @@ class MachineIsAuthenticated(BasePermission):
def has_permission(self, request, view):
return bool(request.user)


class EmployeesDailyAttendanceViewSet(viewsets.ModelViewSet):
serializer_class = EmployeesDailyAttendanceCreationSerializer
queryset = EmployeesDailyAttendance.objects.all()
http_method_names = ['post']

# permission_classes = [MachineIsAuthenticated]

@extend_schema(
Expand All @@ -32,5 +35,6 @@ def create(self, request):
# your non-standard behaviour
return super().create(request)


class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
serializer_class = MyTokenObtainPairSerializer
2 changes: 1 addition & 1 deletion config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Dhaka'

USE_I18N = True

Expand Down

0 comments on commit 776efc2

Please sign in to comment.