diff --git a/attendance/templates/attendance/geofaceconfig/geo_face_config.html b/attendance/templates/attendance/geofaceconfig/geo_face_config.html new file mode 100644 index 000000000..2956c62f0 --- /dev/null +++ b/attendance/templates/attendance/geofaceconfig/geo_face_config.html @@ -0,0 +1,14 @@ + +{% extends "settings.html" %} +{% block settings %} + +
+
+ +
+ +
+
+ + +{% endblock settings %} \ No newline at end of file diff --git a/attendance/urls.py b/attendance/urls.py index 6d392df82..56e5fd8b2 100644 --- a/attendance/urls.py +++ b/attendance/urls.py @@ -9,6 +9,7 @@ from django.urls import path import attendance.views.clock_in_out import attendance.views.dashboard +import attendance.views.geofaceconfig import attendance.views.penalty import attendance.views.requests import attendance.views.search @@ -527,4 +528,9 @@ urlpatterns = [ "field_name_pre": "ip_address", }, ), + path( + "settings/geo-face-config/", + attendance.views.geofaceconfig.geofaceconfig, + name="geo-face-config", + ), ] diff --git a/attendance/views/geofaceconfig.py b/attendance/views/geofaceconfig.py new file mode 100644 index 000000000..ee11fd982 --- /dev/null +++ b/attendance/views/geofaceconfig.py @@ -0,0 +1,4 @@ +from django.shortcuts import render + +def geofaceconfig(request): + return render(request, "attendance/geofaceconfig/geo_face_config.html") \ No newline at end of file diff --git a/facedetection/forms.py b/facedetection/forms.py new file mode 100644 index 000000000..470a91850 --- /dev/null +++ b/facedetection/forms.py @@ -0,0 +1,20 @@ + +from base.forms import ModelForm +from facedetection.models import FaceDetection +from django.template.loader import render_to_string + +class FaceDetectionSetupForm(ModelForm): + verbose_name = "Facedetection Configuration" + + class Meta: + model = FaceDetection + exclude = ["company_id"] + + def as_p(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + context = {"form": self} + table_html = render_to_string("common_form.html", context) + return table_html + \ No newline at end of file diff --git a/facedetection/serializers.py b/facedetection/serializers.py index b2dda4847..706b16f27 100644 --- a/facedetection/serializers.py +++ b/facedetection/serializers.py @@ -3,6 +3,12 @@ from rest_framework import serializers from .models import * +class FaceDetectionSerializer(serializers.ModelSerializer): + class Meta: + model = FaceDetection + fields = "__all__" + + class EmployeeFaceDetectionSerializer(serializers.ModelSerializer): class Meta: model = EmployeeFaceDetection diff --git a/facedetection/templates/face_config.html b/facedetection/templates/face_config.html new file mode 100644 index 000000000..c0b05ad70 --- /dev/null +++ b/facedetection/templates/face_config.html @@ -0,0 +1,5 @@ +
+ {% csrf_token %} + {{form.as_p}} + +
\ No newline at end of file diff --git a/facedetection/urls.py b/facedetection/urls.py index 75338dcd0..54e9737db 100644 --- a/facedetection/urls.py +++ b/facedetection/urls.py @@ -2,4 +2,8 @@ from django.urls import path from .views import * -urlpatterns = [path("setup/", FaceDetectionGetPostAPIView.as_view())] +urlpatterns = [ + path("config/", FaceDetectionConfigAPIView.as_view()), + path("setup/", EmployeeFaceDetectionGetPostAPIView.as_view()), + path("", face_detection_config, name="face-config"), +] diff --git a/facedetection/views.py b/facedetection/views.py index e605c0eae..16609db6e 100644 --- a/facedetection/views.py +++ b/facedetection/views.py @@ -1,30 +1,166 @@ +from django.contrib import messages +from django.contrib.auth.decorators import login_required, permission_required from django.http import QueryDict -from django.shortcuts import render +from django.shortcuts import redirect, render from django.utils.decorators import method_decorator +from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView +from facedetection.forms import FaceDetectionSetupForm +from horilla.decorators import hx_request_required + from .serializers import * -class FaceDetectionGetPostAPIView(APIView): +class FaceDetectionConfigAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get_company(self, request): + try: + company = request.user.employee_get.get_company() + return company + except Exception as e: + raise serializers.ValidationError(e) + + def get_facedetection(self, request): + company = self.get_company(request) + try: + facedetection = FaceDetection.objects.get(company_id=company) + return facedetection + except Exception as e: + raise serializers.ValidationError(e) + + @method_decorator( + permission_required("facedetection.view_facedetection", raise_exception=True), + name="dispatch", + ) + def get(self, request): + serializer = FaceDetectionSerializer(self.get_facedetection(request)) + return Response(serializer.data, status=status.HTTP_200_OK) + + @method_decorator( + permission_required("facedetection.add_facedetection", raise_exception=True), + name="dispatch", + ) + def post(self, request): + data = request.data + if isinstance(data, QueryDict): + data = data.dict() + data["company_id"] = self.get_company(request).id + serializer = FaceDetectionSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @method_decorator( + permission_required("facedetection.change_facedetection", raise_exception=True), + name="dispatch", + ) + def put(self, request): + data = request.data + if isinstance(data, QueryDict): + data = data.dict() + data["company_id"] = self.get_company(request).id + serializer = FaceDetectionSerializer(self.get_facedetection(request), data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @method_decorator( + permission_required("facedetection.delete_facedetection", raise_exception=True), + name="dispatch", + ) + def delete(self, request): + self.get_facedetection(request).delete() + return Response( + {"message": "Facedetection deleted successfully"}, status=status.HTTP_200_OK + ) + + +class EmployeeFaceDetectionGetPostAPIView(APIView): permission_classes = [IsAuthenticated] @method_decorator(csrf_exempt) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) + def get_company(self, request): + try: + company = request.user.employee_get.get_company() + return company + except Exception as e: + raise serializers.ValidationError(e) + + def get_facedetection(self, request): + company = self.get_company(request) + try: + facedetection = FaceDetection.objects.get(company_id=company) + return facedetection + except Exception as e: + raise serializers.ValidationError(e) + def post(self, request): - employee_id = request.user.employee_get.id - data = request.data - if isinstance(data, QueryDict): - data = data.dict() - data["employee_id"] = employee_id - serializer = EmployeeFaceDetectionSerializer(data=data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + if self.get_facedetection(request).start: + employee_id = request.user.employee_get.id + data = request.data + if isinstance(data, QueryDict): + data = data.dict() + data["employee_id"] = employee_id + serializer = EmployeeFaceDetectionSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + raise serializers.ValidationError("Facedetection not yet started..") + + +def get_company(request): + try: + company = request.user.employee_get.get_company() + return company + except Exception as e: + raise serializers.ValidationError(e) + + +def get_facedetection(request): + company = get_company(request) + try: + location = FaceDetection.objects.get(company_id=company) + return location + except Exception as e: + raise serializers.ValidationError(e) + + +@login_required +@permission_required("geofencing.add_localbackup") +@hx_request_required +def face_detection_config(request): + print(">>>>>>>>>>>>>>>>>>>>>") + try: + form = FaceDetectionSetupForm(instance=get_facedetection(request)) + except: + form = FaceDetectionSetupForm() + + if request.method == "POST": + try: + form = FaceDetectionSetupForm( + request.POST, instance=get_facedetection(request) + ) + except: + form = FaceDetectionSetupForm(request.POST) + if form.is_valid(): + facedetection = form.save( + commit=False, + ) + facedetection.company_id = get_company(request) + facedetection.save() + messages.success(request, _("facedetection config created successfully.")) + else: + messages.info(request, "Not valid") + return render(request, "face_config.html", {"form": form}) diff --git a/geofencing/forms.py b/geofencing/forms.py new file mode 100644 index 000000000..f926680a5 --- /dev/null +++ b/geofencing/forms.py @@ -0,0 +1,20 @@ + +from base.forms import ModelForm +from .models import GeoFencing +from django.template.loader import render_to_string + +class GeoFencingSetupForm(ModelForm): + verbose_name = "Geofence Configuration" + + class Meta: + model = GeoFencing + exclude = ["company_id"] + + def as_p(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + context = {"form": self} + table_html = render_to_string("common_form.html", context) + return table_html + \ No newline at end of file diff --git a/geofencing/templates/geo_config.html b/geofencing/templates/geo_config.html new file mode 100644 index 000000000..b2ea3a134 --- /dev/null +++ b/geofencing/templates/geo_config.html @@ -0,0 +1,5 @@ +
+ {% csrf_token %} + {{form.as_p}} + +
diff --git a/geofencing/urls.py b/geofencing/urls.py index 85d049bfd..86066b0dd 100644 --- a/geofencing/urls.py +++ b/geofencing/urls.py @@ -6,4 +6,6 @@ urlpatterns = [ path("setup/", GeoFencingSetupGetPostAPIView.as_view()), path("setup//", GeoFencingSetupPutDeleteAPIView.as_view()), path("setup-check/", GeoFencingSetUpPermissionCheck.as_view()), + path("location-check/", GeoFencingEmployeeLocationCheckAPIView.as_view()), + path("config/", geo_location_config, name="geo-config"), ] diff --git a/geofencing/views.py b/geofencing/views.py index 90ce0196e..dc3dd962c 100644 --- a/geofencing/views.py +++ b/geofencing/views.py @@ -1,6 +1,9 @@ -from django.contrib.auth.decorators import permission_required +from django.contrib import messages +from django.contrib.auth.decorators import login_required, permission_required from django.http import QueryDict +from django.shortcuts import redirect, render from django.utils.decorators import method_decorator +from django.utils.translation import gettext_lazy as _ from geopy.distance import geodesic from rest_framework import status from rest_framework.pagination import PageNumberPagination @@ -8,6 +11,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView +from geofencing.forms import GeoFencingSetupForm + from .models import GeoFencing from .serializers import * @@ -86,7 +91,7 @@ class GeoFencingSetupPutDeleteAPIView(APIView): location.delete() return Response( {"message": "GeoFencing location deleted successfully"}, - status=status.HTTP_204_NO_CONTENT, + status=status.HTTP_200_OK, ) raise serializers.ValidationError("Access Denied..") @@ -111,22 +116,28 @@ class GeoFencingEmployeeLocationCheckAPIView(APIView): def post(self, request): serializer = EmployeeLocationSerializer(data=request.data) - if serializer.is_valid(): - company_location = self.get_company_location(request) - geofence_center = (company_location.latitude, company_location.longitude) - employee_location = ( - request.data.get("latitude"), - request.data.get("longitude"), - ) - distance = geodesic(geofence_center, employee_location).meters - if distance <= company_location.radius_in_meters: - return Response( - {"message": "Inside the geofence"}, status=status.HTTP_200_OK + company_location = self.get_company_location(request) + if company_location.start: + if serializer.is_valid(): + geofence_center = ( + company_location.latitude, + company_location.longitude, ) - return Response( - {"message": "Outside the geofence"}, status=status.HTTP_400_BAD_REQUEST - ) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + employee_location = ( + request.data.get("latitude"), + request.data.get("longitude"), + ) + distance = geodesic(geofence_center, employee_location).meters + if distance <= company_location.radius_in_meters: + return Response( + {"message": "Inside the geofence"}, status=status.HTTP_200_OK + ) + return Response( + {"message": "Outside the geofence"}, + status=status.HTTP_400_BAD_REQUEST, + ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + raise serializers.ValidationError("Geofencing is not yet started..") class GeoFencingSetUpPermissionCheck(APIView): @@ -137,3 +148,44 @@ class GeoFencingSetUpPermissionCheck(APIView): if geo_fencing.get(request).status_code == 200: return Response(status=200) return Response(status=400) + + +def get_company(request): + try: + company = request.user.employee_get.get_company() + return company + except Exception as e: + raise serializers.ValidationError(e) + + +def get_company_location(request): + company = get_company(request) + try: + location = GeoFencing.objects.get(company_id=company) + return location + except Exception as e: + raise serializers.ValidationError(e) + + +@login_required +@permission_required("geofencing.add_localbackup") +def geo_location_config(request): + try: + form = GeoFencingSetupForm(instance=get_company_location(request)) + except: + form = GeoFencingSetupForm() + if request.method == "POST": + try: + form = GeoFencingSetupForm( + request.POST, instance=get_company_location(request) + ) + except: + form = GeoFencingSetupForm(request.POST) + if form.is_valid(): + geofencing = form.save(commit=False) + geofencing.company_id = get_company(request) + geofencing.save() + messages.success(request, _("Geofencing config created successfully.")) + else: + messages.info(request, "Not valid") + return render(request, "geo_config.html", {"form": form}) diff --git a/static/index/index.js b/static/index/index.js index 4b572f1ca..032f0f1cc 100644 --- a/static/index/index.js +++ b/static/index/index.js @@ -40,6 +40,15 @@ function addToSelectedId(newIds, storeKey) { $(`#${storeKey}`).attr("data-ids", JSON.stringify(ids)); } +function togglePublicComments() { + if ($('#id_disable_comments').is(':checked')) { + $('#id_public_comments').prop('checked', false); + $('#id_public_comments_parent_div').hide(); + } else { + $('#id_public_comments_parent_div').show(); + } +} + function attendanceDateChange(selectElement) { var selectedDate = selectElement.val(); let parentForm = selectElement.parents().closest("form"); @@ -324,7 +333,7 @@ function checkSequence(element) { if ( stageOrder.indexOf(parseInt(stageId)) != - stageOrder.indexOf(parseInt(preStageId)) + 1 && + stageOrder.indexOf(parseInt(preStageId)) + 1 && stage.type != "cancelled" ) { Swal.fire({ diff --git a/templates/403.html b/templates/403.html index 39e227bf1..2d65f2b7b 100644 --- a/templates/403.html +++ b/templates/403.html @@ -152,4 +152,4 @@ - \ No newline at end of file + diff --git a/templates/is_active_toggle.html b/templates/is_active_toggle.html index 9bf656037..286721b8b 100644 --- a/templates/is_active_toggle.html +++ b/templates/is_active_toggle.html @@ -1,12 +1,12 @@
{% if url %} {% else %} - {% endif %} -
\ No newline at end of file + diff --git a/templates/settings.html b/templates/settings.html index 13f1679e5..1968c51e4 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -441,6 +441,18 @@ {% endif %} + + {% if perms.geofencing.add_geofencing or perms.facedetection.add_facedetection %} +
+ {% trans "Geo & Face Config" %} + +
+ {% endif %} +