[UPDT] ATTENDANCE: Add geofencing and facedetection configuration and settings

This commit is contained in:
Horilla
2025-05-09 11:14:51 +05:30
parent 9fec5d1c19
commit 68528aed3e
16 changed files with 331 additions and 36 deletions

View File

@@ -0,0 +1,14 @@
{% extends "settings.html" %}
{% block settings %}
<div hx-get="{% url 'geo-config' %}" hx-trigger="load" hx-target="#geo"></div>
<div id="geo"></div>
<hr>
<div hx-get="{% url 'face-config' %}" hx-trigger="load" hx-target="#face"></div>
<div id="face"></div>
{% endblock settings %}

View File

@@ -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",
),
]

View File

@@ -0,0 +1,4 @@
from django.shortcuts import render
def geofaceconfig(request):
return render(request, "attendance/geofaceconfig/geo_face_config.html")

20
facedetection/forms.py Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
<form hx-post="{% url 'face-config' %}" hx-swap="none" hx-on-htmx-after-request="$('#reloadMessagesButton').click();">
{% csrf_token %}
{{form.as_p}}
</form>

View File

@@ -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"),
]

View File

@@ -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})

20
geofencing/forms.py Normal file
View File

@@ -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

View File

@@ -0,0 +1,5 @@
<form hx-post="{% url 'geo-config' %}" hx-swap="none" hx-on-htmx-after-request="$('#reloadMessagesButton').click();">
{% csrf_token %}
{{form.as_p}}
</form>

View File

@@ -6,4 +6,6 @@ urlpatterns = [
path("setup/", GeoFencingSetupGetPostAPIView.as_view()),
path("setup/<int:pk>/", GeoFencingSetupPutDeleteAPIView.as_view()),
path("setup-check/", GeoFencingSetUpPermissionCheck.as_view()),
path("location-check/", GeoFencingEmployeeLocationCheckAPIView.as_view()),
path("config/", geo_location_config, name="geo-config"),
]

View File

@@ -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})

View File

@@ -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({

View File

@@ -152,4 +152,4 @@
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
</body>
</html>
</html>

View File

@@ -1,12 +1,12 @@
<div class="oh-switch p-3">
{% if url %}
<input type="checkbox" name="is_active" id="isActiveToggleId"
hx-post={{url}}
hx-trigger="change" hx-target="#genericModalBody"
hx-post={{url}}
hx-trigger="change" hx-target="#genericModalBody"
class="oh-switch__checkbox"
{% if instance.is_active %} checked {% endif %}>
{% else %}
<input type="checkbox" id="isActiveToggleId" class="oh-switch__checkbox"
<input type="checkbox" id="isActiveToggleId" class="oh-switch__checkbox"
{% if instance.is_active %} checked {% endif %} disabled >
{% endif %}
</div>
</div>

View File

@@ -441,6 +441,18 @@
</a>
</div>
{% endif %}
{% if perms.geofencing.add_geofencing or perms.facedetection.add_facedetection %}
<div class="oh-input-group">
<a
id="geo_face_conf"
href="{% url "geo-face-config" %}"
class="oh-inner-sidebar__link oh-dropdown__link"
>{% trans "Geo & Face Config" %}
</a>
</div>
{% endif %}
</div>
</div>
</div>