Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#334 Allow admin bulk delete on Action model #382

Merged
merged 7 commits into from
Jun 26, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chcemvediet/apps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def init_admin_site_plus(self):
from adminplus.sites import AdminSitePlus
from django.contrib import admin
admin.site = AdminSitePlus()
admin.site.disable_action(u'delete_selected')
admin.autodiscover()

def workaround_sqlite_format_dtdelta_bug(self):
Expand Down
102 changes: 81 additions & 21 deletions chcemvediet/apps/inforequests/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,37 @@
import datetime

from django.contrib import admin
from django.contrib.admin.actions import delete_selected
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
from django.contrib.admin.utils import NestedObjects
from django.core.exceptions import PermissionDenied
from django.db import router
from django.db import router, transaction
from django.forms.models import BaseInlineFormSet
from django.utils.html import format_html

from poleno.utils.date import local_today
from poleno.utils.misc import decorate
from poleno.utils.misc import decorate, squeeze
from poleno.utils.admin import (simple_list_filter_factory, admin_obj_format,
ReadOnlyAdminInlineMixin, NoBulkDeleteAdminMixin)
ReadOnlyAdminInlineMixin)
from chcemvediet.apps.inforequests.constants import ADMIN_EXTEND_SNOOZE_BY_DAYS

from .models import Inforequest, InforequestDraft, InforequestEmail, Branch, Action


class DeleteNestedInforequestEmailAdminMixin(admin.ModelAdmin):

def get_inforequest(self, obj):
def get_inforequest(self, objs):
raise NotImplementedError

def nested_inforequestemail_queryset(self, obj):
def nested_inforequestemail_queryset(self, objs):
using = router.db_for_write(self.model)
collector = NestedObjects(using)
collector.collect([obj])
collector.collect(objs)
to_delete = collector.nested()
inforequest = self.get_inforequest(obj)
inforequests = self.get_inforequest(objs)
actions = [obj for obj in self.nested_objects_traverse(to_delete)
if isinstance(obj, Action)]
emails = [action.email for action in actions if action.email]
inforequestemails_qs = InforequestEmail.objects.filter(inforequest=inforequest,
inforequestemails_qs = InforequestEmail.objects.filter(inforequest=inforequests,
email__in=emails)
outbound = inforequestemails_qs.filter(type=InforequestEmail.TYPES.APPLICANT_ACTION)
inbound = inforequestemails_qs.filter(type=InforequestEmail.TYPES.OBLIGEE_ACTION)
Expand All @@ -47,14 +48,14 @@ def nested_objects_traverse(self, to_delete):
yield to_delete

def render_delete_form(self, request, context):
outbound, inbound = self.nested_inforequestemail_queryset(context[u'object'])
outbound, inbound = self.nested_inforequestemail_queryset([context[u'object']])
context[u'outbound'] = [admin_obj_format(inforequestemail) for inforequestemail in outbound]
context[u'inbound'] = [admin_obj_format(inforequestemail) for inforequestemail in inbound]
return super(DeleteNestedInforequestEmailAdminMixin, self).render_delete_form(request,
context)

def delete_model(self, request, obj):
outbound, inbound = self.nested_inforequestemail_queryset(obj)
outbound, inbound = self.nested_inforequestemail_queryset([obj])
outbound.delete()
inbound.update(type=InforequestEmail.TYPES.UNDECIDED)
super(DeleteNestedInforequestEmailAdminMixin, self).delete_model(request, obj)
Expand Down Expand Up @@ -260,7 +261,7 @@ def get_queryset(self, request):
return queryset

@admin.register(Branch, site=admin.site)
class BranchAdmin(NoBulkDeleteAdminMixin, DeleteNestedInforequestEmailAdminMixin, admin.ModelAdmin):
class BranchAdmin(DeleteNestedInforequestEmailAdminMixin, admin.ModelAdmin):
date_hierarchy = None
list_display = [
u'id',
Expand Down Expand Up @@ -316,8 +317,8 @@ def get_queryset(self, request):
queryset = queryset.select_related(u'advanced_by')
return queryset

def get_inforequest(self, obj):
return obj.inforequest
def get_inforequest(self, objs):
return Inforequest.objects.filter(branch__in=objs)
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved

def delete_constraints(self, obj):
if obj.is_main:
Expand All @@ -333,7 +334,7 @@ def delete_model(self, request, obj):
return super(BranchAdmin, self).delete_model(request, obj)

@admin.register(Action, site=admin.site)
class ActionAdmin(NoBulkDeleteAdminMixin, DeleteNestedInforequestEmailAdminMixin, admin.ModelAdmin):
class ActionAdmin(DeleteNestedInforequestEmailAdminMixin, admin.ModelAdmin):
date_hierarchy = u'created'
list_display = [
u'id',
Expand Down Expand Up @@ -374,15 +375,30 @@ class ActionAdmin(NoBulkDeleteAdminMixin, DeleteNestedInforequestEmailAdminMixin
inlines = [
BranchInline,
]
actions = [
u'delete_selected'
]

def get_queryset(self, request):
queryset = super(ActionAdmin, self).get_queryset(request)
queryset = queryset.select_related(u'branch')
queryset = queryset.select_related(u'email')
return queryset

def get_inforequest(self, obj):
return obj.branch.inforequest
def get_inforequest(self, objs):
return Inforequest.objects.filter(branch__action__in=objs)

def delete_warnings(self, obj, to_delete=None):
if self.delete_constraints(obj):
return []
to_delete = to_delete or []
warnings = []
if not obj.is_last_action and obj.next_action not in to_delete:
warnings.append(format_html(squeeze(u"""
{} is not the last action in the branch. Deleting it may cause logical errors in
the inforequest history.
""").format(admin_obj_format(obj))))
return warnings

def delete_constraints(self, obj):
constraints = []
Expand All @@ -394,18 +410,62 @@ def delete_constraints(self, obj):
u'{} is the only action in the branch.'.format(admin_obj_format(obj))))
return constraints

def can_snooze(self, obj):
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
if (obj.type in [Action.TYPES.EXPIRATION, Action.TYPES.APPEAL_EXPIRATION]
and obj.previous_action):
return True
return False
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved

def render_delete_form(self, request, context):
context[u'delete_constraints'] = self.delete_constraints(context[u'object'])
context[u'ADMIN_EXTEND_SNOOZE_BY_DAYS'] = ADMIN_EXTEND_SNOOZE_BY_DAYS
obj = context[u'object']
context[u'delete_warnings'] = self.delete_warnings(obj)
context[u'delete_constraints'] = self.delete_constraints(obj)
if self.can_snooze(obj):
context[u'ADMIN_EXTEND_SNOOZE_BY_DAYS'] = ADMIN_EXTEND_SNOOZE_BY_DAYS
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
return super(ActionAdmin, self).render_delete_form(request, context)

@decorate(short_description=u'Delete selected actions')
@transaction.atomic
def delete_selected(self, request, queryset):
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
delete_warnings = []
delete_constraints = []
snoozed_actions = []
for obj in queryset:
delete_warnings += self.delete_warnings(obj, queryset)
delete_constraints += self.delete_constraints(obj)
if self.can_snooze(obj):
snoozed_actions.append(obj.previous_action)
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
outbound, inbound = self.nested_inforequestemail_queryset(queryset)

if request.POST.get(u'post'):
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
if delete_constraints:
raise PermissionDenied

template_response = delete_selected(self, request, queryset)
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved

if request.POST.get(u'post'):
if request.POST.get(u'snooze'):
for action in snoozed_actions:
action.snooze = local_today() + datetime.timedelta(days=ADMIN_EXTEND_SNOOZE_BY_DAYS)
action.save(update_fields=[u'snooze'])
outbound.delete()
inbound.update(type=InforequestEmail.TYPES.UNDECIDED)
return None

template_response.context_data.update({
u'outbound': [admin_obj_format(inforequestemail) for inforequestemail in outbound],
u'inbound': [admin_obj_format(inforequestemail) for inforequestemail in inbound],
u'snoozed_actions': [admin_obj_format(action) for action in snoozed_actions],
u'delete_warnings': delete_warnings,
u'delete_constraints': delete_constraints,
})
return template_response

def delete_model(self, request, obj):
if self.delete_constraints(obj):
raise PermissionDenied
if request.POST:
if (request.POST.get(u'snooze')
and obj.type in [Action.TYPES.EXPIRATION, Action.TYPES.APPEAL_EXPIRATION]
and obj.previous_action):
if request.POST.get(u'snooze') and self.can_snooze(obj):
previous = obj.previous_action
previous.snooze = local_today() + datetime.timedelta(days=ADMIN_EXTEND_SNOOZE_BY_DAYS)
previous.save(update_fields=[u'snooze'])
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,26 @@
<ul>{{ inbound|unordered_list }}</ul>
{% endif %}
{% endafter %}
{% if not object.is_last_action %}
{% before path=".//form" %}
<p>
<b>Warning:</b> The deleted action is not the last action in the branch. Deleting it may
cause logical errors in the inforequest history.
</p>
{% endbefore %}
{% endif %}
{% if object.type == object.TYPES.EXPIRATION or object.type == object.TYPES.APPEAL_EXPIRATION %}
{% if ADMIN_EXTEND_SNOOZE_BY_DAYS %}
{% prepend path=".//form" %}
<p><label><input name="snooze" type="checkbox" /> Extend snooze of previous action by {{ ADMIN_EXTEND_SNOOZE_BY_DAYS }} days from today.</label></p>
{% endprepend %}
{% endif %}
{% if delete_constraints %}
{% before path=".//form" %}
{% before path=".//form" %}
{% if delete_warnings %}
<ul class="messagelist">
{% for warning in delete_warnings %}
<li class="warning">{{ warning }}</li>
{% endfor %}
</ul>
{% endif %}
{% if delete_constraints %}
<div class="errornote">
<p>Delete not allowed.</p>
<ul>{{ delete_constraints|unordered_list }}</ul>
</div>
{% endbefore %}
{% set_attributes path=".//form//input[@type='submit']" disabled=True %}
{% endif %}
{% set_attributes path=".//form//input[@type='submit']" disabled=True %}
{% endif %}
{% endbefore %}
{% endamend %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% extends "admin/delete_selected_confirmation.html" %}
{% load amend prepend before after set_attributes from poleno.amend %}

{% block content %}
{% amend %}
{{ block.super }}
{% after path="./ul[last()]" %}
{% if outbound %}
<p>Outbound messages will be deleted:</p>
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
<ul>{{ outbound|unordered_list }}</ul>
{% endif %}
{% if inbound %}
<p>Inbound messages will be marked undecided:</p>
<ul>{{ inbound|unordered_list }}</ul>
{% endif %}
{% endafter %}
{% if snoozed_actions %}
{% prepend path=".//form" %}
<p>
<label><input name="snooze" type="checkbox" /> Extend snooze of previous actions to the
following deleted actions by {{ ADMIN_EXTEND_SNOOZE_BY_DAYS }} days from today:
</label>
</p>
<ul>{{ snoozed_actions|unordered_list }}</ul>
{% endprepend %}
{% endif %}
{% before path=".//form" %}
{% if delete_warnings %}
<ul class="messagelist">
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
{% for warning in delete_warnings %}
<li class="warning">{{ warning }}</li>
{% endfor %}
</ul>
{% endif %}
{% if delete_constraints %}
<div class="errornote">
<p>Delete not allowed.</p>
<ul>{{ delete_constraints|unordered_list }}</ul>
martinmacko47 marked this conversation as resolved.
Show resolved Hide resolved
</div>
{% set_attributes path=".//form//input[@type='submit']" disabled=True %}
{% endif %}
{% endbefore %}
{% endamend %}
{% endblock %}
7 changes: 0 additions & 7 deletions poleno/utils/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,3 @@ def has_add_permission(self, request, obj=None):

def has_delete_permission(self, request, obj=None):
return False

class NoBulkDeleteAdminMixin(admin.ModelAdmin):

def get_actions(self, request):
actions = super(NoBulkDeleteAdminMixin, self).get_actions(request)
actions.pop(u'delete_selected', None)
return actions