diff --git a/asset/views.py b/asset/views.py
index 861436ce0..bada465d2 100644
--- a/asset/views.py
+++ b/asset/views.py
@@ -52,6 +52,7 @@ from asset.models import (
)
from base.methods import (
closest_numbers,
+ eval_validate,
filtersubordinates,
get_key_instances,
get_pagination,
@@ -301,7 +302,7 @@ def asset_delete(request, asset_id):
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
instances_ids = request.GET.get("requests_ids", "[]")
- instances_list = eval(instances_ids)
+ instances_list = eval_validate(instances_ids)
if status == "In use":
messages.info(request, _("Asset is in use"))
return redirect(
@@ -314,7 +315,7 @@ def asset_delete(request, asset_id):
)
else:
asset_del(request, asset)
- if len(eval(instances_ids)) <= 1:
+ if len(eval_validate(instances_ids)) <= 1:
return HttpResponse("")
if Asset.find(asset.id):
@@ -533,6 +534,38 @@ def asset_category_view_search_filter(request):
return render(request, "category/asset_category.html", context)
+def request_creation_hx_returns(referer, user):
+ """
+ Determines the hx_url and hx_target based on the referer path
+ for asset request creation
+ """
+ referer = "/" + "/".join(referer.split("/")[3:])
+ # Map referer paths to corresponding URLs and targets
+ hx_map = {
+ "/": ("asset-dashboard-requests", "dashboardAssetRequests"),
+ "/asset/dashboard/": ("asset-dashboard-requests", "dashboardAssetRequests"),
+ "/asset/asset-request-allocation-view/": (
+ "asset-request-allocation-view-search-filter",
+ "asset_request_allocation_list",
+ ),
+ "/employee/employee-profile/": (
+ "profile-asset-tab",
+ "asset_target",
+ ),
+ }
+
+ hx_url, hx_target = hx_map.get(
+ referer, (None, None)
+ ) # Default to None if not in map
+
+ if hx_url == "profile-asset-tab":
+ hx_url = reverse(hx_url, kwargs={"emp_id": user.employee_get.id})
+ else:
+ hx_url = reverse(hx_url) if hx_url else None
+
+ return hx_url, hx_target
+
+
@login_required
def asset_request_creation(request):
"""
@@ -547,14 +580,16 @@ def asset_request_creation(request):
messages displayed.
"""
# intitial = {'requested_employee_id':request.user.employee_get}
+
+ referer = request.META.get("HTTP_REFERER", "/")
+ hx_url, hx_target = request_creation_hx_returns(referer, request.user)
form = AssetRequestForm(user=request.user)
- context = {"asset_request_form": form}
+ context = {"asset_request_form": form, "hx_url": hx_url, "hx_target": hx_target}
if request.method == "POST":
form = AssetRequestForm(request.POST, user=request.user)
if form.is_valid():
form.save()
messages.success(request, _("Asset request created!"))
- return HttpResponse("")
context["asset_request_form"] = form
return render(request, "request_allocation/asset_request_creation.html", context)
diff --git a/attendance/views/requests.py b/attendance/views/requests.py
index 698f101bf..fda63f42d 100644
--- a/attendance/views/requests.py
+++ b/attendance/views/requests.py
@@ -33,6 +33,7 @@ from attendance.views.clock_in_out import early_out, late_come
from base.methods import (
choosesubordinates,
closest_numbers,
+ eval_validate,
filtersubordinates,
get_key_instances,
is_reportingmanager,
@@ -131,7 +132,7 @@ def request_new(request):
This method is used to create new attendance requests
"""
- if request.GET.get("bulk") and eval(request.GET.get("bulk")):
+ if request.GET.get("bulk") and eval_validate(request.GET.get("bulk")):
employee = request.user.employee_get
form = BulkAttendanceRequestForm(initial={"employee_id": employee})
if request.method == "POST":
@@ -758,7 +759,7 @@ def get_employee_shift(request):
employee = Employee.objects.get(id=employee_id)
shift = employee.get_shift
form = NewRequestForm()
- if request.GET.get("bulk") and eval(request.GET.get("bulk")):
+ if request.GET.get("bulk") and eval_validate(request.GET.get("bulk")):
form = BulkAttendanceRequestForm()
form.fields["shift_id"].queryset = EmployeeShift.objects.all()
form.fields["shift_id"].widget.attrs["hx-trigger"] = "load,change"
diff --git a/attendance/views/views.py b/attendance/views/views.py
index 0b6560251..1852dc6a2 100644
--- a/attendance/views/views.py
+++ b/attendance/views/views.py
@@ -104,6 +104,7 @@ from base.forms import (
from base.methods import (
choosesubordinates,
closest_numbers,
+ eval_validate,
export_data,
filtersubordinates,
get_key_instances,
@@ -1902,7 +1903,7 @@ def create_grace_time(request):
Returns:
GET : return grace time form template
"""
- is_default = eval(request.GET.get("default"))
+ is_default = eval_validate(request.GET.get("default"))
form = GraceTimeForm(initial={"is_default": is_default})
if request.method == "POST":
form = GraceTimeForm(request.POST)
@@ -2694,7 +2695,7 @@ def delete_allowed_ips(request):
allowed_ips = AttendanceAllowedIP.objects.first()
ips = allowed_ips.additional_data["allowed_ips"]
for id in ids:
- ips.pop(eval(id))
+ ips.pop(eval_validate(id))
allowed_ips.additional_data["allowed_ips"] = ips
allowed_ips.save()
diff --git a/base/methods.py b/base/methods.py
index 22c09cddf..6019641b7 100644
--- a/base/methods.py
+++ b/base/methods.py
@@ -1,3 +1,4 @@
+import ast
import calendar
import json
import os
@@ -866,3 +867,11 @@ def format_date(date_str):
except ValueError:
continue
raise ValueError(f"Invalid date format: {date_str}")
+
+
+def eval_validate(value):
+ """
+ Method to validate the dynamic value
+ """
+ value = ast.literal_eval(value)
+ return value
diff --git a/employee/admin.py b/employee/admin.py
index 9b68e86f5..4a4202f85 100644
--- a/employee/admin.py
+++ b/employee/admin.py
@@ -22,8 +22,32 @@ from employee.models import (
# Register your models here.
-admin.site.register(Employee)
+# admin.site.register(Employee)
admin.site.register(EmployeeBankDetails)
admin.site.register(EmployeeWorkInformation, SimpleHistoryAdmin)
admin.site.register([EmployeeNote, EmployeeTag, PolicyMultipleFile, Policy, BonusPoint])
admin.site.register([DisciplinaryAction, Actiontype])
+
+
+from django.contrib import admin
+
+
+class MyModelAdmin(admin.ModelAdmin):
+ def delete_view(self, request, object_id, extra_context=None):
+ # Add custom context for the delete confirmation page
+ extra_context = extra_context or {}
+ extra_context["custom_message"] = (
+ "Are you sure you want to delete this item? This action cannot be undone."
+ )
+ # Call the superclass's delete_view to render the page
+ return super().delete_view(request, object_id, extra_context=extra_context)
+
+ def get_deleted_objects(self, objs, request):
+ response = super().get_deleted_objects(objs, request)
+ print("+++++++++++++++")
+ print(response)
+ print("+++++++++++++++")
+ return response
+
+
+admin.site.register(Employee, MyModelAdmin)
diff --git a/employee/forms.py b/employee/forms.py
index d9b558de9..28d85eb56 100644
--- a/employee/forms.py
+++ b/employee/forms.py
@@ -34,7 +34,7 @@ from django.template.loader import render_to_string
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as trans
-from base.methods import reload_queryset
+from base.methods import eval_validate, reload_queryset
from employee.models import (
Actiontype,
BonusPoint,
@@ -228,7 +228,7 @@ class EmployeeForm(ModelForm):
item = item[total_zero_leads:]
if isinstance(item, list):
item = item[-1]
- if not incremented and isinstance(eval(str(item)), int):
+ if not incremented and isinstance(eval_validate(str(item)), int):
item = int(item) + 1
incremented = True
if isinstance(item, int):
diff --git a/employee/models.py b/employee/models.py
index 6e3ca5fe3..26e73b675 100644
--- a/employee/models.py
+++ b/employee/models.py
@@ -568,7 +568,7 @@ class EmployeeWorkInformation(models.Model):
)
reporting_manager_id = models.ForeignKey(
Employee,
- on_delete=models.DO_NOTHING,
+ on_delete=models.PROTECT,
blank=True,
null=True,
related_name="reporting_manager",
diff --git a/employee/policies.py b/employee/policies.py
index 87c4747e6..6a1f0bbe7 100644
--- a/employee/policies.py
+++ b/employee/policies.py
@@ -15,7 +15,12 @@ from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import gettext_lazy as _
-from base.methods import filtersubordinates, get_key_instances, paginator_qry
+from base.methods import (
+ eval_validate,
+ filtersubordinates,
+ get_key_instances,
+ paginator_qry,
+)
from employee.filters import DisciplinaryActionFilter, PolicyFilter
from employee.forms import DisciplinaryActionForm, PolicyForm
from employee.models import (
@@ -53,7 +58,7 @@ def create_policy(request):
"""
instance_id = request.GET.get("instance_id")
instance = None
- if isinstance(eval(str(instance_id)), int):
+ if isinstance(eval_validate(str(instance_id)), int):
instance = Policy.objects.filter(id=instance_id).first()
form = PolicyForm(instance=instance)
if request.method == "POST":
diff --git a/horilla_api/api_methods/employee/methods.py b/horilla_api/api_methods/employee/methods.py
index 10fdcdfa4..5c0400873 100644
--- a/horilla_api/api_methods/employee/methods.py
+++ b/horilla_api/api_methods/employee/methods.py
@@ -1,5 +1,6 @@
import re
+from base.methods import eval_validate
from base.models import *
from employee.models import *
@@ -41,7 +42,7 @@ def get_next_badge_id():
item = item[total_zero_leads:]
if isinstance(item, list):
item = item[-1]
- if not incremented and isinstance(eval(str(item)), int):
+ if not incremented and isinstance(eval_validate(str(item)), int):
item = int(item) + 1
incremented = True
if isinstance(item, int):
diff --git a/horilla_api/api_views/payroll/views.py b/horilla_api/api_views/payroll/views.py
index 32a3e62f6..430b7459f 100644
--- a/horilla_api/api_views/payroll/views.py
+++ b/horilla_api/api_views/payroll/views.py
@@ -10,6 +10,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from base.backends import ConfiguredEmailBackend
+from base.methods import eval_validate
from payroll.filters import (
AllowanceFilter,
ContractFilter,
@@ -331,7 +332,11 @@ class ReimbusementApproveRejectView(APIView):
def post(self, request, pk):
status = request.data.get("status", None)
amount = request.data.get("amount", None)
- amount = eval(request.data.get("amount")) if request.data.get("amount") else 0
+ amount = (
+ eval_validate(request.data.get("amount"))
+ if request.data.get("amount")
+ else 0
+ )
amount = max(0, amount)
reimbursement = Reimbursement.objects.filter(id=pk)
if amount:
diff --git a/horilla_automations/models.py b/horilla_automations/models.py
index 37bdbcebe..538cb4809 100644
--- a/horilla_automations/models.py
+++ b/horilla_automations/models.py
@@ -2,6 +2,7 @@ from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _trans
+from base.methods import eval_validate
from base.models import HorillaMailTemplate
from horilla.models import HorillaModel
from horilla_views.cbv_methods import render_template
@@ -73,7 +74,7 @@ class MailAutomation(HorillaModel):
method that returns the display value for `mail_to`
field
"""
- mail_to = eval(self.mail_to)
+ mail_to = eval_validate(self.mail_to)
mappings = []
for mapping in mail_to:
mapping = mapping.split("__")
diff --git a/horilla_automations/signals.py b/horilla_automations/signals.py
index 22f40b00c..47b520192 100644
--- a/horilla_automations/signals.py
+++ b/horilla_automations/signals.py
@@ -333,7 +333,7 @@ def send_mail(request, automation, instance):
mail sending method
"""
from base.backends import ConfiguredEmailBackend
- from base.methods import generate_pdf
+ from base.methods import eval_validate, generate_pdf
from horilla_automations.methods.methods import (
get_model_class,
get_related_field_model,
@@ -346,7 +346,7 @@ def send_mail(request, automation, instance):
model_class = get_related_field_model(model_class, automation.mail_details)
mail_to_instance = model_class.objects.filter(pk=pk).first()
tos = []
- for mapping in eval(automation.mail_to):
+ for mapping in eval_validate(automation.mail_to):
result = getattribute(mail_to_instance, mapping)
if isinstance(result, list):
tos = tos + result
diff --git a/horilla_views/cbv_methods.py b/horilla_views/cbv_methods.py
index bcc42a254..2fb4714d1 100644
--- a/horilla_views/cbv_methods.py
+++ b/horilla_views/cbv_methods.py
@@ -30,6 +30,7 @@ from django.utils.html import format_html
from django.utils.safestring import SafeString
from django.utils.translation import gettext_lazy as _trans
+from base.methods import eval_validate
from horilla import settings
from horilla.horilla_middlewares import _thread_locals
from horilla_views.templatetags.generic_template_filters import getattribute
@@ -487,5 +488,24 @@ def value_to_field(field: object, value: list) -> Any:
):
value = value[0]
return value
- value = eval(str(value[0]))
+ value = eval_validate(str(value[0]))
return value
+
+
+def merge_dicts(dict1, dict2):
+ """
+ Method to merge two dicts
+ """
+ merged_dict = dict1.copy()
+
+ for key, value in dict2.items():
+ if key in merged_dict:
+ for model_class, instances in value.items():
+ if model_class in merged_dict[key]:
+ merged_dict[key][model_class].extend(instances)
+ else:
+ merged_dict[key][model_class] = instances
+ else:
+ merged_dict[key] = value
+
+ return merged_dict
diff --git a/horilla_views/forms.py b/horilla_views/forms.py
index 9d8897346..cb1ce2be7 100644
--- a/horilla_views/forms.py
+++ b/horilla_views/forms.py
@@ -5,9 +5,9 @@ horilla_views/forms.py
import os
from django import forms
-from django.contrib import messages
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
+from django.db import transaction
from django.template.loader import render_to_string
from django.utils.safestring import SafeText
from django.utils.translation import gettext_lazy as _trans
@@ -20,7 +20,6 @@ from horilla_views.cbv_methods import (
MODEL_FORM_FIELD_MAP,
get_field_class_map,
structured,
- value_to_field,
)
from horilla_views.templatetags.generic_template_filters import getattribute
@@ -105,7 +104,7 @@ class DynamicBulkUpdateForm(forms.Form):
root_model: models.models.Model = None,
bulk_update_fields: list = [],
ids: list = [],
- **kwargs
+ **kwargs,
):
self.ids = ids
self.root_model = root_model
@@ -146,7 +145,6 @@ class DynamicBulkUpdateForm(forms.Form):
self.fields[key].widget.option_template_name = (
"horilla_widgets/select_option.html",
)
- print(self.fields[key].empty_values)
continue
self.fields[key] = field(
widget=widget,
@@ -168,6 +166,21 @@ class DynamicBulkUpdateForm(forms.Form):
"horilla_widgets/select_option.html",
)
+ def is_valid(self):
+ valid = True
+ try:
+ with transaction.atomic():
+ # Perform bulk update
+ self.save()
+ # Simulate error check
+ raise Exception("no_errors")
+ except Exception as e:
+ # Handle errors or validation issues
+ if not "no_errors" in str(e):
+ valid = False
+ self.add_error(None, f"Form not valid: {str(e)}")
+ return valid
+
def save(self, *args, **kwargs):
"""
Bulk save method
@@ -243,5 +256,3 @@ class DynamicBulkUpdateForm(forms.Form):
for field, file in files.items():
file_path = os.path.join(field.upload_to, file.name)
default_storage.save(file_path, ContentFile(file.read()))
-
- messages.success(self.request, _trans("Selected Records updated"))
diff --git a/horilla_views/generic/cbv/views.py b/horilla_views/generic/cbv/views.py
index e0cc25114..5de78923c 100644
--- a/horilla_views/generic/cbv/views.py
+++ b/horilla_views/generic/cbv/views.py
@@ -18,7 +18,7 @@ from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _trans
from django.views.generic import DetailView, FormView, ListView, TemplateView
-from base.methods import closest_numbers, get_key_instances
+from base.methods import closest_numbers, eval_validate, get_key_instances
from horilla.filters import FilterSet
from horilla.group_by import group_by_queryset
from horilla.horilla_middlewares import _thread_locals
@@ -142,7 +142,7 @@ class HorillaListView(ListView):
form = self.get_bulk_form()
form.verbose_name = (
form.verbose_name
- + f" ({len((eval(request.GET.get('instance_ids','[]'))))} {_trans('Records')})"
+ + f" ({len((eval_validate(request.GET.get('instance_ids','[]'))))} {_trans('Records')})"
)
return render(
request,
@@ -158,7 +158,7 @@ class HorillaListView(ListView):
return HttpResponse("You dont have permission")
instance_ids = request.GET.get("instance_ids", "[]")
- instance_ids = eval(instance_ids)
+ instance_ids = eval_validate(instance_ids)
form = DynamicBulkUpdateForm(
request.POST,
request.FILES,
@@ -168,6 +168,7 @@ class HorillaListView(ListView):
)
if instance_ids and form.is_valid():
form.save()
+ messages.success(request, _trans("Selected Records updated"))
script_id = get_short_uuid(length=3, prefix="bulk")
return HttpResponse(
@@ -219,7 +220,7 @@ class HorillaListView(ListView):
is_default=True,
).first()
if not bool(query_dict) and default_filter:
- data = eval(default_filter.filter)
+ data = eval_validate(default_filter.filter)
query_dict = QueryDict("", mutable=True)
for key, value in data.items():
query_dict[key] = value
@@ -395,8 +396,8 @@ class HorillaListView(ListView):
from import_export import fields, resources
request = getattr(_thread_locals, "request", None)
- ids = eval(request.GET["ids"])
- _columns = eval(request.GET["columns"])
+ ids = eval_validate(request.GET["ids"])
+ _columns = eval_validate(request.GET["columns"])
queryset = self.model.objects.filter(id__in=ids)
_model = self.model
@@ -515,7 +516,7 @@ class HorillaDetailedView(DetailView):
def get_context_data(self, **kwargs: Any):
context = super().get_context_data(**kwargs)
- instance_ids = eval(str(self.request.GET.get(self.ids_key)))
+ instance_ids = eval_validate(str(self.request.GET.get(self.ids_key)))
pk = context["object"].pk
if instance_ids:
@@ -663,7 +664,7 @@ class HorillaCardView(ListView):
is_default=True,
).first()
if not bool(query_dict) and default_filter:
- data = eval(default_filter.filter)
+ data = eval_validate(default_filter.filter)
query_dict = QueryDict("", mutable=True)
for key, value in data.items():
query_dict[key] = value
@@ -866,7 +867,7 @@ class HorillaFormView(FormView):
pk = self.form.instance.pk
# next/previous option in the forms
if pk and self.request.GET.get(self.ids_key):
- instance_ids = eval(str(self.request.GET.get(self.ids_key)))
+ instance_ids = eval_validate(str(self.request.GET.get(self.ids_key)))
url = resolve(self.request.path)
key = list(url.kwargs.keys())[0]
url_name = url.url_name
@@ -1140,7 +1141,7 @@ class HorillaProfileView(DetailView):
instance_ids_str = self.request.GET.get("instance_ids")
if not instance_ids_str:
instance_ids_str = "[]"
- instance_ids = eval(instance_ids_str)
+ instance_ids = eval_validate(instance_ids_str)
if instance_ids:
CACHE.set(
f"{self.request.session.session_key}hpv-instance-ids", instance_ids
diff --git a/horilla_views/templates/generic/components.html b/horilla_views/templates/generic/components.html
index fcc5b1a72..b58a4d4ab 100644
--- a/horilla_views/templates/generic/components.html
+++ b/horilla_views/templates/generic/components.html
@@ -1,3 +1,12 @@
+
+
+
+
+ $("#reloadMessagesButton").click()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% trans "Deleting the record" %} '{{delete_object}}' {% trans "would require managing the following related objects:" %}
+
+
+ {% trans "Protected Records" %} ({{protected|length}})
+
+
+ {% for summary in protected_objects_count.items %}
+ - {{summary.0|capfirst}} : {{summary.1|capfirst}}
+ {% endfor %}
+
+
+ {% trans "Other Related Records" %} ({{model_count_sum}})
+
+
+ {% for summary in related_objects_count.items %}
+ - {{summary.0|capfirst}} : {{summary.1|capfirst}}
+ {% endfor %}
+
+
+
+
+
+ {% for key in model_map.keys %}
+
+ {% endfor %}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/horilla_views/templatetags/generic_template_filters.py b/horilla_views/templatetags/generic_template_filters.py
index 3635755be..dc039b109 100644
--- a/horilla_views/templatetags/generic_template_filters.py
+++ b/horilla_views/templatetags/generic_template_filters.py
@@ -139,3 +139,11 @@ def get_item(dictionary: dict, key: str):
if dictionary:
return dictionary.get(key, "")
return ""
+
+
+@register.filter("get_id")
+def get_id(string: str):
+ """
+ Generate target/id for the generic delete summary
+ """
+ return string.split("-")[0].lower().replace(" ", "")
diff --git a/horilla_views/urls.py b/horilla_views/urls.py
index 3094592ff..074ba816f 100644
--- a/horilla_views/urls.py
+++ b/horilla_views/urls.py
@@ -39,4 +39,9 @@ urlpatterns = [
views.LastAppliedFilter.as_view(),
name="last-applied-filter",
),
+ path(
+ "generic-delete",
+ views.HorillaDeleteConfirmationView.as_view(),
+ name="generic-delete",
+ ),
]
diff --git a/horilla_views/views.py b/horilla_views/views.py
index 9c0257b5e..5b0e939aa 100644
--- a/horilla_views/views.py
+++ b/horilla_views/views.py
@@ -1,18 +1,23 @@
import importlib
+from collections import defaultdict
from django import forms
+from django.apps import apps
from django.contrib import messages
+from django.contrib.admin.utils import NestedObjects
from django.core.cache import cache as CACHE
+from django.db import router
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_protect
+from base.methods import eval_validate
from horilla_views import models
-from horilla_views.cbv_methods import get_short_uuid, login_required
+from horilla_views.cbv_methods import get_short_uuid, login_required, merge_dicts
from horilla_views.forms import SavedFilterForm
-from horilla_views.generic.cbv.views import HorillaFormView
+from horilla_views.generic.cbv.views import HorillaFormView, HorillaListView
# Create your views here.
@@ -93,7 +98,7 @@ class ReloadField(View):
)
dynamic_initial = request.GET.get("dynamic_initial", [])
parent_form.fields[cache_field].widget.attrs = field.widget.attrs
- parent_form.fields[cache_field].initial = eval(
+ parent_form.fields[cache_field].initial = eval_validate(
f"""[{dynamic_cache["value"]},{dynamic_initial}]"""
)
@@ -258,3 +263,302 @@ class LastAppliedFilter(View):
timeout=600,
)
return HttpResponse("success")
+
+
+class DynamiListView(HorillaListView):
+ """
+ DynamicListView for Generic Delete
+ """
+
+ instances = []
+
+ def get_queryset(self):
+ search = self.request.GET.get("search", "")
+
+ def _search_filter(instance):
+ return search in str(instance).lower()
+
+ return filter(_search_filter, self.instances)
+
+
+class HorillaDeleteConfirmationView(View):
+ """
+ Generic Delete Confirmation View
+ """
+
+ confirmation_target = "deleteConfirmationBody"
+
+ def get(self, *args, **kwargs):
+ """
+ GET method
+ """
+ from horilla.urls import path, urlpatterns
+
+ pk = self.request.GET["pk"]
+
+ app, MODEL_NAME = self.request.GET["model"].split(".")
+ if not self.request.user.has_perm(app + ".delete_" + MODEL_NAME.lower()):
+ return render(self.request, "no_perm.html")
+ model = apps.get_model(app, MODEL_NAME)
+
+ delete_object = model.objects.get(pk=pk)
+ objs = [delete_object]
+ using = router.db_for_write(delete_object._meta.model)
+ collector = NestedObjects(using=using, origin=objs)
+ collector.collect(objs)
+ MODEL_MAP = {}
+ PROTECTED_MODEL_MAP = {}
+ DYNAMIC_PATH_MAP = {}
+ MODEL_RELATED_FIELD_MAP = {}
+ MODEL_RELATED_PROTECTED_FIELD_MAP = {}
+
+ def format_callback(instance, protected=False):
+ if not MODEL_RELATED_FIELD_MAP.get(instance._meta.model):
+ MODEL_RELATED_FIELD_MAP[instance._meta.model] = []
+ MODEL_RELATED_PROTECTED_FIELD_MAP[instance._meta.model] = []
+
+ def find_related_field(obj, related_instance):
+ for field in obj._meta.get_fields():
+ # Check if the field is a foreign key (or related model)
+ if isinstance(
+ field, (models.models.ForeignKey, models.models.OneToOneField)
+ ):
+ # Get the field value
+ field_value = getattr(obj, field.name)
+ # If the field value matches the related instance, return the field name
+ if field_value == related_instance:
+ if "PROTECT" in field.remote_field.on_delete.__name__:
+ MODEL_RELATED_PROTECTED_FIELD_MAP[
+ instance._meta.model
+ ].append((field.name, field.verbose_name))
+ MODEL_RELATED_FIELD_MAP[instance._meta.model].append(
+ field.name
+ )
+
+ find_related_field(instance, delete_object)
+ app_label = instance._meta.app_label
+ app_label = apps.get_app_config(app_label).verbose_name
+ model = instance._meta.model
+
+ model.verbose_name = model.__name__.split("_")[0]
+
+ model_map = PROTECTED_MODEL_MAP if protected else MODEL_MAP
+
+ if app_label not in model_map:
+ model_map[app_label] = {}
+
+ if model not in model_map[app_label]:
+ model_map[app_label][model] = []
+ DYNAMIC_PATH_MAP[model.verbose_name] = (
+ f"{get_short_uuid(prefix='generic-delete',length=10)}"
+ )
+
+ class DynamiListView(HorillaListView):
+ """
+ DynamicListView for Generic Delete
+ """
+
+ instances = []
+ columns = [
+ (
+ "Record",
+ "dynamic_display_name_generic_delete",
+ ),
+ ]
+ records_per_page = 5
+
+ selected_instances_key_id = "storedIds" + app_label
+
+ def dynamic_display_name_generic_delete(self):
+
+ is_protected = False
+ classname = self.__class__.__name__
+ app_label = self._meta.app_label
+
+ app_verbose_name = apps.get_app_config(app_label).verbose_name
+ protected = PROTECTED_MODEL_MAP.get(app_verbose_name, {}).get(
+ self._meta.model, []
+ )
+ ids = [instance.pk for instance in protected]
+ if self.pk in ids:
+ is_protected = True
+
+ if "_" in classname:
+ field_name = classname.split("_", 1)[1]
+ classname = classname.split("_")[0]
+
+ object_field_name = classname.lower()
+ model = apps.get_model(app_label, classname)
+
+ field = model._meta.get_field(field_name)
+
+ return f"""
+ {getattr(self, object_field_name)}
+
(In {field.verbose_name})
+ """
+ indication = f"""
+ {self}
+ """
+ if is_protected:
+ verbose_names = [
+ str(i[1])
+ for i in list(
+ set(
+ MODEL_RELATED_PROTECTED_FIELD_MAP.get(
+ self._meta.model, ""
+ )
+ )
+ )
+ ]
+ indication = (
+ indication
+ + f"""
+
(Record in {",".join(verbose_names)})
+ """
+ )
+ return indication
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self._saved_filters = self.request.GET
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["search_url"] = "/" + self.search_url
+ return context
+
+ def get_queryset(self):
+ search = self.request.GET.get("search", "")
+
+ def _search_filter(instance):
+ return search in str(instance).lower()
+
+ self.instances = list(
+ set(
+ (
+ self.instances
+ + MODEL_MAP.get(
+ apps.get_app_config(
+ self.model._meta.app_label
+ ).verbose_name,
+ {},
+ ).get(self.model, [])
+ + PROTECTED_MODEL_MAP.get(
+ apps.get_app_config(
+ self.model._meta.app_label
+ ).verbose_name,
+ {},
+ ).get(self.model, [])
+ )
+ )
+ )
+
+ queryset = self.model.objects.filter(
+ pk__in=[
+ instance.pk
+ for instance in filter(_search_filter, self.instances)
+ ]
+ )
+ return queryset
+
+ model.dynamic_display_name_generic_delete = (
+ DynamiListView.dynamic_display_name_generic_delete
+ )
+
+ DynamiListView.model = model
+ if "_" in model.__name__:
+ DynamiListView.bulk_update_fields = [MODEL_NAME.lower()]
+ else:
+ DynamiListView.bulk_update_fields = MODEL_RELATED_FIELD_MAP.get(
+ model, []
+ )
+ DynamiListView.instances = model_map[app_label][model]
+ DynamiListView.search_url = DYNAMIC_PATH_MAP[model.verbose_name]
+ DynamiListView.selected_instances_key_id = (
+ DynamiListView.selected_instances_key_id + model.verbose_name
+ )
+
+ urlpatterns.append(
+ path(
+ DynamiListView.search_url,
+ DynamiListView.as_view(),
+ name=DynamiListView.search_url,
+ )
+ )
+ model_map[app_label][model].append(instance)
+
+ return instance
+
+ _to_delete = collector.nested(format_callback)
+ protected = [
+ format_callback(obj, protected=True) for obj in collector.protected
+ ]
+
+ model_count = {
+ model._meta.verbose_name_plural: len(objs)
+ for model, objs in collector.model_objs.items()
+ }
+
+ protected_model_count = defaultdict(int)
+
+ for obj in collector.protected:
+ model = type(obj)
+ protected_model_count[model._meta.verbose_name_plural] += 1
+ protected_model_count = dict(protected_model_count)
+
+ return render(
+ self.request,
+ "generic/delete_confirmation.html",
+ {
+ "model_map": merge_dicts(MODEL_MAP, PROTECTED_MODEL_MAP),
+ "dynamic_list_path": DYNAMIC_PATH_MAP,
+ "delete_object": delete_object,
+ "protected": protected,
+ "model_count_sum": sum(model_count.values()),
+ "related_objects_count": model_count,
+ "protected_objects_count": protected_model_count,
+ }
+ | self.get_context_data(),
+ )
+
+ def post(self, *args, **kwargs):
+ """
+ Post method to handle the delete
+ """
+ pk = self.request.GET["pk"]
+ app, MODEL_NAME = self.request.GET["model"].split(".")
+ if not self.request.user.has_perm(app + ".delete_" + MODEL_NAME.lower()):
+ return render(self.request, "no_perm.html")
+ model = apps.get_model(app, MODEL_NAME)
+ delete_object = model.objects.get(pk=pk)
+ objs = [delete_object]
+ using = router.db_for_write(delete_object._meta.model)
+ collector = NestedObjects(using=using, origin=objs)
+ collector.collect(objs)
+
+ def delete_callback(instance, protected=False):
+ try:
+ instance.delete()
+ messages.success(self.request, f"Deleted {instance}")
+ except:
+ messages.error(self.request, f"Cannot delete : {instance}")
+
+ # deleting protected objects
+ for obj in collector.protected:
+ delete_callback(obj, protected=True)
+ # deleting related objects
+ collector.nested(delete_callback)
+
+ return HttpResponse(
+ """
+
+ """
+ )
+
+ def get_context_data(self, **kwargs) -> dict:
+ context = {}
+ context["confirmation_target"] = self.confirmation_target
+ return context
diff --git a/leave/views.py b/leave/views.py
index be82b3708..246f80bc7 100644
--- a/leave/views.py
+++ b/leave/views.py
@@ -27,6 +27,7 @@ from base.forms import PenaltyAccountForm
from base.methods import (
choosesubordinates,
closest_numbers,
+ eval_validate,
export_data,
filtersubordinates,
get_key_instances,
@@ -4736,7 +4737,7 @@ if apps.is_installed("attendance"):
comp_leave_req = CompensatoryLeaveRequest.objects.get(id=comp_leave_id)
context = {
"comp_leave_req": comp_leave_req,
- "my_request": eval(request.GET.get("my_request")),
+ "my_request": eval_validate(request.GET.get("my_request")),
"instances_ids": requests_ids_json,
"previous": previous_id,
"next": next_id,
diff --git a/offboarding/views.py b/offboarding/views.py
index ef533ea55..5a7a01772 100644
--- a/offboarding/views.py
+++ b/offboarding/views.py
@@ -11,8 +11,7 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from base.context_processors import intial_notice_period
-from base.methods import closest_numbers, sortby
-from base.views import paginator_qry
+from base.methods import closest_numbers, eval_validate, paginator_qry, sortby
from employee.models import Employee
from horilla.decorators import (
hx_request_required,
@@ -178,7 +177,7 @@ def create_offboarding(request):
"""
Create offboarding view
"""
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
instance = None
if instance_id and isinstance(instance_id, int):
instance = Offboarding.objects.filter(id=instance_id).first()
@@ -236,7 +235,7 @@ def create_stage(request):
This method is used to create stages for offboardings
"""
offboarding_id = request.GET["offboarding_id"]
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
instance = None
if instance_id and isinstance(instance_id, int):
instance = OffboardingStage.objects.get(id=instance_id)
@@ -280,7 +279,7 @@ def add_employee(request):
)
end_date = datetime.today() + timedelta(days=default_notice_period)
stage_id = request.GET["stage_id"]
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
instance = None
if instance_id and isinstance(instance_id, int):
instance = OffboardingEmployee.objects.get(id=instance_id)
@@ -508,7 +507,7 @@ def add_task(request):
This method is used to add offboarding tasks
"""
stage_id = request.GET.get("stage_id")
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
employees = OffboardingEmployee.objects.filter(stage_id=stage_id)
instance = None
if instance_id:
@@ -807,7 +806,7 @@ def create_resignation_request(request):
"""
This method is used to render form to create resignation requests
"""
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
instance = None
if instance_id:
instance = ResignationLetter.objects.get(id=instance_id)
diff --git a/payroll/views/component_views.py b/payroll/views/component_views.py
index c47a5c2a3..25201a38f 100644
--- a/payroll/views/component_views.py
+++ b/payroll/views/component_views.py
@@ -29,6 +29,7 @@ import payroll.models.models
from base.backends import ConfiguredEmailBackend
from base.methods import (
closest_numbers,
+ eval_validate,
filter_own_records,
get_key_instances,
get_next_month_same_date,
@@ -1353,7 +1354,7 @@ def create_loan(request):
"""
This method is used to create and update the loan instance
"""
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
instance = LoanAccount.objects.filter(id=instance_id).first()
form = forms.LoanAccountForm(instance=instance)
if request.method == "POST":
@@ -1593,7 +1594,7 @@ def create_reimbursement(request):
"""
This method is used to create reimbursement
"""
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
instance = None
if instance_id:
instance = Reimbursement.objects.filter(id=instance_id).first()
@@ -1694,7 +1695,9 @@ def approve_reimbursements(request):
status = request.GET["status"]
if status == "canceled":
status = "rejected"
- amount = eval(request.GET.get("amount")) if request.GET.get("amount") else 0
+ amount = (
+ eval_validate(request.GET.get("amount")) if request.GET.get("amount") else 0
+ )
amount = max(0, amount)
reimbursements = Reimbursement.objects.filter(id__in=ids)
if status and len(status):
diff --git a/payroll/views/views.py b/payroll/views/views.py
index dfd35cb56..ed873ae04 100644
--- a/payroll/views/views.py
+++ b/payroll/views/views.py
@@ -21,6 +21,7 @@ from django.utils.translation import gettext_lazy as _
from base.methods import (
closest_numbers,
+ eval_validate,
export_data,
generate_colors,
generate_pdf,
@@ -188,7 +189,7 @@ def contract_status_update(request, contract_id):
@permission_required("payroll.change_contract")
def bulk_contract_status_update(request):
status = request.POST.get("status")
- ids = eval(request.POST.get("ids"))
+ ids = eval_validate(request.POST.get("ids"))
all_contracts = Contract.objects.all()
contracts = all_contracts.filter(id__in=ids)
@@ -1675,7 +1676,7 @@ def initial_notice_period(request):
"""
This method is used to set initial value notice period
"""
- notice_period = eval(request.GET["notice_period"])
+ notice_period = eval_validate(request.GET["notice_period"])
settings = PayrollGeneralSetting.objects.first()
settings = settings if settings else PayrollGeneralSetting()
settings.notice_period = max(notice_period, 0)
diff --git a/pms/views.py b/pms/views.py
index 638d94bf8..58c351d65 100644
--- a/pms/views.py
+++ b/pms/views.py
@@ -27,6 +27,7 @@ from django.utils.translation import gettext_lazy as _
from base.methods import (
closest_numbers,
+ eval_validate,
get_key_instances,
get_pagination,
paginator_qry,
@@ -3221,8 +3222,8 @@ def key_result_current_value_update(request):
This method is used to update keyresult current value
"""
try:
- current_value = eval(request.POST.get("current_value"))
- emp_kr_id = eval(request.POST.get("emp_key_result_id"))
+ current_value = eval_validate(request.POST.get("current_value"))
+ emp_kr_id = eval_validate(request.POST.get("emp_key_result_id"))
emp_kr = EmployeeKeyResult.objects.get(id=emp_kr_id)
if current_value <= emp_kr.target_value:
emp_kr.current_value = current_value
@@ -3309,7 +3310,7 @@ def create_meetings(request):
Post:
it will redirect to view_meetings.html .
"""
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
instance = None
initial = {"manager": request.user.employee_get, "employee_id": None}
if instance_id and isinstance(instance_id, int):
diff --git a/recruitment/views/views.py b/recruitment/views/views.py
index fb597519a..39aca26d2 100644
--- a/recruitment/views/views.py
+++ b/recruitment/views/views.py
@@ -43,7 +43,13 @@ from base.backends import ConfiguredEmailBackend
from base.context_processors import check_candidate_self_tracking
from base.countries import country_arr, s_a, states
from base.forms import MailTemplateForm
-from base.methods import export_data, generate_pdf, get_key_instances, sortby
+from base.methods import (
+ eval_validate,
+ export_data,
+ generate_pdf,
+ get_key_instances,
+ sortby,
+)
from base.models import EmailLog, HorillaMailTemplate, JobPosition
from employee.models import Employee, EmployeeWorkInformation
from horilla import settings
@@ -1718,7 +1724,7 @@ def form_send_mail(request, cand_id=None):
candidate_obj = None
stage_id = None
if request.GET.get("stage_id"):
- stage_id = eval(request.GET.get("stage_id"))
+ stage_id = eval_validate(request.GET.get("stage_id"))
if cand_id:
candidate_obj = Candidate.objects.get(id=cand_id)
candidates = Candidate.objects.all()
@@ -2627,7 +2633,7 @@ def create_reject_reason(request):
"""
This method is used to create/update the reject reasons
"""
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
instance = None
if instance_id:
instance = RejectReason.objects.get(id=instance_id)
@@ -2856,7 +2862,7 @@ def create_skills(request):
"""
This method is used to create the skills
"""
- instance_id = eval(str(request.GET.get("instance_id")))
+ instance_id = eval_validate(str(request.GET.get("instance_id")))
dynamic = request.GET.get("dynamic")
hx_vals = request.GET.get("data")
instance = None
@@ -2920,7 +2926,7 @@ def view_bulk_resumes(request):
"""
This function returns the bulk_resume.html page to the modal
"""
- rec_id = eval(str(request.GET.get("rec_id")))
+ rec_id = eval_validate(str(request.GET.get("rec_id")))
resumes = Resume.objects.filter(recruitment_id=rec_id)
return render(
@@ -2935,7 +2941,7 @@ def add_bulk_resumes(request):
"""
This function is used to create bulk resume
"""
- rec_id = eval(str(request.GET.get("rec_id")))
+ rec_id = eval_validate(str(request.GET.get("rec_id")))
recruitment = Recruitment.objects.get(id=rec_id)
if request.method == "POST":
files = request.FILES.getlist("files")