[UPDT] ATTENDANCE: Add geofencing and facedetection configuration and settings
This commit is contained in:
@@ -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 %}
|
||||
@@ -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",
|
||||
),
|
||||
]
|
||||
|
||||
4
attendance/views/geofaceconfig.py
Normal file
4
attendance/views/geofaceconfig.py
Normal 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
20
facedetection/forms.py
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
5
facedetection/templates/face_config.html
Normal file
5
facedetection/templates/face_config.html
Normal 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>
|
||||
@@ -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"),
|
||||
]
|
||||
|
||||
@@ -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
20
geofencing/forms.py
Normal 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
|
||||
|
||||
5
geofencing/templates/geo_config.html
Normal file
5
geofencing/templates/geo_config.html
Normal 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>
|
||||
@@ -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"),
|
||||
]
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -152,4 +152,4 @@
|
||||
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user