Fix: Replace delete_selected with LDAP-safe custom action
Django's delete_selected passes queryset with dn__in filter that ldapdb doesn't support. Custom delete_selected_ldap reads DNs from POST and deletes objects one by one. Also hides standard delete_selected from action choices.
This commit is contained in:
parent
6050cd9d26
commit
130dafc5a2
@ -127,6 +127,7 @@ class LdapUserAdmin(admin.ModelAdmin):
|
||||
)
|
||||
|
||||
actions = [
|
||||
'delete_selected_ldap',
|
||||
'sync_from_ldap_to_django',
|
||||
'sync_from_django_to_ldap',
|
||||
'show_sync_diff',
|
||||
@ -509,27 +510,62 @@ class LdapUserAdmin(admin.ModelAdmin):
|
||||
"""Разрешаем удаление только суперпользователям."""
|
||||
return request.user.is_superuser
|
||||
|
||||
def get_deleted_objects(self, objs, request):
|
||||
"""Override: ldapdb не поддерживает dn__in lookup."""
|
||||
# Собираем объекты вручную (objs может быть queryset с __in)
|
||||
resolved = []
|
||||
for obj in self._resolve_from_qs_or_list(objs):
|
||||
resolved.append(obj)
|
||||
def get_action_choices(self, request, default_choices=admin.ModelAdmin.action_form):
|
||||
"""Убираем стандартный delete_selected (ldapdb не поддерживает dn__in)."""
|
||||
choices = super().get_action_choices(request, default_choices)
|
||||
return [
|
||||
c for c in choices
|
||||
if not (isinstance(c[0], str) and c[0] == 'delete_selected')
|
||||
]
|
||||
|
||||
deletable = [str(o) for o in resolved]
|
||||
perms_needed = set()
|
||||
protected = []
|
||||
model_count = {self.opts.verbose_name_plural: len(resolved)}
|
||||
return deletable, model_count, perms_needed, protected
|
||||
@admin.action(description='🗑️ Удалить выбранных пользователей из LDAP')
|
||||
def delete_selected_ldap(self, request, queryset):
|
||||
"""Кастомное удаление — обходит dn__in, читает DN из POST."""
|
||||
users = self._resolve_ldap_users(request)
|
||||
if not users:
|
||||
self.message_user(
|
||||
request, 'Не удалось найти выбранных пользователей.',
|
||||
level=messages.WARNING,
|
||||
)
|
||||
return
|
||||
|
||||
def _resolve_from_qs_or_list(self, objs):
|
||||
"""Итерирует LDAP объекты, обходя неподдерживаемые lookups."""
|
||||
try:
|
||||
yield from objs
|
||||
except Exception:
|
||||
# Fallback: если queryset с __in упал,
|
||||
# достаём DN из POST и читаем по одному
|
||||
pass
|
||||
if request.POST.get('post') != 'yes':
|
||||
# Показываем confirmation page
|
||||
from django.template.response import TemplateResponse
|
||||
context = {
|
||||
**self.admin_site.each_context(request),
|
||||
'title': 'Подтверждение удаления',
|
||||
'objects_name': 'LDAP пользователей',
|
||||
'deletable_objects': [str(u) for u in users],
|
||||
'queryset': users,
|
||||
'opts': self.model._meta,
|
||||
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
|
||||
'media': self.media,
|
||||
}
|
||||
request.current_app = self.admin_site.name
|
||||
return TemplateResponse(
|
||||
request,
|
||||
'admin/ldap_delete_confirmation.html',
|
||||
context,
|
||||
)
|
||||
|
||||
deleted = 0
|
||||
for user in users:
|
||||
try:
|
||||
user.delete()
|
||||
deleted += 1
|
||||
except Exception as e:
|
||||
self.message_user(
|
||||
request,
|
||||
f'Ошибка удаления {user.cn}: {e}',
|
||||
level=messages.ERROR,
|
||||
)
|
||||
if deleted:
|
||||
self.message_user(
|
||||
request,
|
||||
f'Удалено: {deleted} пользователей из LDAP.',
|
||||
level=messages.SUCCESS,
|
||||
)
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""Базовый queryset без slice - пагинация через list_per_page."""
|
||||
|
||||
24
backend/templates/admin/ldap_delete_confirmation.html
Normal file
24
backend/templates/admin/ldap_delete_confirmation.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">{% csrf_token %}
|
||||
<div>
|
||||
<p>{% trans "Are you sure you want to delete the selected" %} {{ objects_name }}?</p>
|
||||
<ul>
|
||||
{% for obj in deletable_objects %}
|
||||
<li>{{ obj }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% for user in queryset %}
|
||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ user.dn }}" />
|
||||
{% endfor %}
|
||||
<input type="hidden" name="action" value="delete_selected_ldap" />
|
||||
<input type="hidden" name="post" value="yes" />
|
||||
<input type="submit" value="{% trans 'Yes, I'm sure' %}" />
|
||||
<a href="." class="button cancel-link">{% trans "No, take me back" %}</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user