[UPDT] GENERAL: Eval method change (#397)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<div class="oh-modal" id="deleteConfirmation" role="dialog" aria-labelledby="deleteConfirmation" aria-hidden="true">
|
||||
<div class="oh-modal__dialog oh-modal__dialog--custom" id="deleteConfirmationBody">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button hx-get="{% url "generic-delete" %}?model=employee.Employee&pk=13" hx-target="#deleteConfirmationBody" data-toggle="oh-modal-toggle" data-target="#deleteConfirmation">
|
||||
Delete
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="genericModal"
|
||||
|
||||
237
horilla_views/templates/generic/delete_confirmation.html
Normal file
237
horilla_views/templates/generic/delete_confirmation.html
Normal file
@@ -0,0 +1,237 @@
|
||||
{% load generic_template_filters i18n %}
|
||||
<script>
|
||||
$("#reloadMessagesButton").click()
|
||||
</script>
|
||||
<button hidden class="reload-record" hx-get="{% url "generic-delete" %}?{{request.GET.urlencode}}" hx-target="#{{confirmation_target}}">{{request.GET.urlencode}}</button>
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="deleteConfirmationLabel">{% trans "Delete Confirmation" %}</span>
|
||||
<button class="oh-modal__close--custom" onclick="$(this).closest('.oh-modal--show').removeClass('oh-modal--show')" aria-label="Close"><ion-icon name="close-outline" role="img" class="md hydrated" aria-label="close outline"></ion-icon></button>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal__dialog-body oh-modal__dialog-relative" style="padding-bottom: 0px">
|
||||
<div class="">
|
||||
|
||||
<div class="oh-card">
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-12">
|
||||
<ul class="oh-general__tabs oh-general__tabs--border oh-general__tabs--profile oh-general__tabs--no-grow oh-profile-section__tab mt-2">
|
||||
<li class="oh-general__tab">
|
||||
<a href="#" class="oh-general__tab-link" data-action="general-tab" data-target="#summary">{% trans "Summary" %}</a>
|
||||
</li>
|
||||
{% for key in model_map.keys %}
|
||||
<li class="oh-general__tab">
|
||||
<a href="#" class="oh-general__tab-link" data-action="general-tab" data-target="#{{key}}">{{key}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oh-general__tab-target oh-profile__info-tab" id="summary">
|
||||
<style>
|
||||
.check-list {
|
||||
margin: 0;
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
.check-list li {
|
||||
position: relative;
|
||||
list-style-type: none;
|
||||
padding-left: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.check-list li:hover {
|
||||
background-color: #e5ffff;
|
||||
}
|
||||
.oh-inner-sidebar__link{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Checkmark styling */
|
||||
.check-list li:not(li.x-marked):before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -2px;
|
||||
width: 5px;
|
||||
height: 11px;
|
||||
border-width: 0 2px 2px 0;
|
||||
border-style: solid;
|
||||
border-color: #00a8a8;
|
||||
transform-origin: bottom left;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* X-mark styling */
|
||||
.check-list .x-marked:before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -1px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
transparent 46%,
|
||||
red 46%,
|
||||
red 54%,
|
||||
transparent 54%
|
||||
),
|
||||
linear-gradient(
|
||||
-45deg,
|
||||
transparent 46%,
|
||||
red 46%,
|
||||
red 54%,
|
||||
transparent 54%
|
||||
);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<h5 class="mt-3 mb-2">
|
||||
{% trans "Deleting the record" %} '{{delete_object}}' {% trans "would require managing the following related objects:" %}
|
||||
</h5>
|
||||
<h6>
|
||||
{% trans "Protected Records" %} ({{protected|length}})
|
||||
</h6>
|
||||
<ul class="check-list">
|
||||
{% for summary in protected_objects_count.items %}
|
||||
<li
|
||||
onclick="
|
||||
{% if "-" not in summary.0 %}
|
||||
$(`#{{summary.0|get_id|slice:":-1"}}`).click()
|
||||
{% else %}
|
||||
$(`#{{summary.0|get_id}}`).click()
|
||||
{% endif %}
|
||||
"
|
||||
>{{summary.0|capfirst}} : {{summary.1|capfirst}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h6>
|
||||
{% trans "Other Related Records" %} ({{model_count_sum}})
|
||||
</h6>
|
||||
<ul class="check-list">
|
||||
{% for summary in related_objects_count.items %}
|
||||
<li
|
||||
onclick="
|
||||
{% if "-" not in summary.0 %}
|
||||
$(`#{{summary.0|get_id|slice:":-1"}}`).click()
|
||||
{% else %}
|
||||
$(`#{{summary.0|get_id}}`).click()
|
||||
{% endif %}
|
||||
"
|
||||
>{{summary.0|capfirst}} : {{summary.1|capfirst}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<form hx-post="{% url "generic-delete" %}?{{request.GET.urlencode}}" hx-target="#deleteConfirmationBody">
|
||||
<div class="d-flex justify-content-end">
|
||||
<div>
|
||||
<div class="mt-2">
|
||||
<input type="checkbox" required id="action">
|
||||
<label for="action">
|
||||
{% trans "I Have took manual action for the protected records" %}
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<input type="checkbox" required id="revert">
|
||||
<label for="revert">
|
||||
{% trans "I acknowledge, I wont be able to revert this " %}
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<input type="checkbox" required id="confirm">
|
||||
<label for="confirm">
|
||||
{% trans "Confirming to delete the related and protected records" %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 30px;">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary m-2">{% trans "Delete" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% for key in model_map.keys %}
|
||||
<div class="oh-general__tab-target oh-profile__info-tab" id="{{key}}">
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-3" style="width: 35% !important;">
|
||||
<div class="oh-inner-sidebar oh-resp-hidden--lg" id="mobileMenu">
|
||||
<ul class="oh-inner-sidebar__items">
|
||||
{% with models_dict=model_map|get_item:key %}
|
||||
{% for item in models_dict.items %}
|
||||
<li class="oh-inner-sidebar__item" id="{{item.0.verbose_name|lower}}item" onclick="
|
||||
localStorage.setItem('DeletenavItem','#'+$(this).attr('id'))
|
||||
">
|
||||
<a
|
||||
id="{{item.0.verbose_name|lower}}"
|
||||
onclick="$(`[data-target='#{{key}}']`).click();$(this).parent().find('button').click()"
|
||||
class="oh-inner-sidebar__link">{{item.0.verbose_name}}</a>
|
||||
<button
|
||||
hidden
|
||||
hx-get="/{{dynamic_list_path|get_item:item.0.verbose_name}}"
|
||||
hx-target="#dynamicRelatedLists{{key}}"
|
||||
></button>
|
||||
<div id="storedIds{{key}}{{item.0.verbose_name}}" data-ids="[]"></div>
|
||||
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-9" style="width: 65% !important;">
|
||||
<div class="oh-inner-sidebar-content">
|
||||
<div class="oh-inner-sidebar-content__header mt-3">
|
||||
<h2 class="oh-inner-sidebar-content__title">{% trans "Action Required" %}⚠️</h2>
|
||||
</div>
|
||||
<div class="oh-inner-sidebar-content__body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col lg-12" id="dynamicRelatedLists{{key}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer"></div>
|
||||
|
||||
<script>
|
||||
$(".oh-general__tab-link").click(function (e) {
|
||||
e.preventDefault();
|
||||
$("#deleteConfirmationBody .oh-general__tab-link--active").removeClass("oh-general__tab-link--active");
|
||||
$(this).addClass("oh-general__tab-link--active");
|
||||
var target = $(this).attr("data-target");
|
||||
$('#deleteConfirmationBody .oh-profile__info-tab').addClass("d-none");
|
||||
$(`#deleteConfirmationBody ${target}`).removeClass("d-none");
|
||||
localStorage.setItem("deleteConfirmation",target)
|
||||
});
|
||||
$(".oh-inner-sidebar__link").click(function (e) {
|
||||
e.preventDefault();
|
||||
$(this).closest("ul").find(".oh-inner-sidebar__link--active").removeClass("oh-inner-sidebar__link--active");
|
||||
$(this).addClass("oh-inner-sidebar__link--active");
|
||||
});
|
||||
target = localStorage.getItem("deleteConfirmation",null)
|
||||
navTarget = localStorage.getItem("DeletenavItem",null)
|
||||
if(target && $(`#deleteConfirmationBody .oh-general__tab-link[data-target='${target}']`).length){
|
||||
$(`#deleteConfirmationBody .oh-general__tab-link[data-target='${target}']`).click();
|
||||
if(navTarget){
|
||||
setTimeout(() => {
|
||||
$(`${navTarget}`).addClass('oh-inner-sidebar__link--active');
|
||||
$(`${navTarget} a`).addClass('oh-inner-sidebar__link--active');
|
||||
$(`${navTarget} button`).click();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
if(!$("#deleteConfirmationBody .oh-general__tab-link--active:first").length){
|
||||
$(`#deleteConfirmationBody .oh-general__tab-link:first`).click();
|
||||
}
|
||||
</script>
|
||||
@@ -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(" ", "")
|
||||
|
||||
@@ -39,4 +39,9 @@ urlpatterns = [
|
||||
views.LastAppliedFilter.as_view(),
|
||||
name="last-applied-filter",
|
||||
),
|
||||
path(
|
||||
"generic-delete",
|
||||
views.HorillaDeleteConfirmationView.as_view(),
|
||||
name="generic-delete",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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)}
|
||||
<i style="color:#989898;">(In {field.verbose_name})</i>
|
||||
"""
|
||||
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"""
|
||||
<i style="color:red;">(Record in {",".join(verbose_names)})</i>
|
||||
"""
|
||||
)
|
||||
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(
|
||||
"""
|
||||
<script>
|
||||
$("#reloadMessagesButton").click();
|
||||
$(".oh-modal--show").removeClass("oh-modal--show");
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs) -> dict:
|
||||
context = {}
|
||||
context["confirmation_target"] = self.confirmation_target
|
||||
return context
|
||||
|
||||
Reference in New Issue
Block a user