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

Track errors across all forms and provide feedback via messages to user. #118

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8d0a468
Track errors across all forms and provide feedback via messages to user.
Dec 12, 2023
f9b0703
Add messages dependancy
derek-richard Oct 23, 2024
a207691
Add explicit separation of field and label for checkbox
derek-richard Oct 23, 2024
cdaa6a9
Add white space before help text next to a form field
derek-richard Oct 23, 2024
9165e3a
Add white space after form field names
derek-richard Oct 23, 2024
f687e81
Added exception handler for Database errors on object save
derek-richard Oct 24, 2024
a8d22a1
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
11a253e
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
5d9ae47
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
fb086ea
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
466058e
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
b87ba1d
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
1e1088c
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
7b67c12
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
96fc94a
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
c4e1a6e
Updated exception handler for Database errors on object save
derek-richard Oct 24, 2024
99afd86
Updated exception handler for Database errors on object save
derek-richard Oct 25, 2024
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ django_mass_edit.egg-info
.idea
.coverage
.tox
.spyproject
100 changes: 75 additions & 25 deletions massadmin/massadmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
import types
import sys

from django.contrib import admin
from django.contrib import admin, messages
from django.core.exceptions import PermissionDenied, ValidationError
try:
from django.urls import reverse
except ImportError: # Django<2.0
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db import transaction, DatabaseError, IntegrityError
try: # Django>=1.9
from django.apps import apps
get_model = apps.get_model
Expand All @@ -64,6 +64,13 @@
from . import settings


def url_to_edit_object(obj):
url = reverse('admin:%s_%s_change' % (
obj._meta.app_label, obj._meta.model_name), args=[obj.pk])
return '<a href="%s" target="_">%s #%s</a>' % (
url, obj._meta.verbose_name, obj.pk)


def mass_change_selected(modeladmin, request, queryset):
selected = queryset.values_list('pk', flat=True)

Expand Down Expand Up @@ -248,6 +255,7 @@ def mass_change_view(
with transaction.atomic():
objects_count = 0
changed_count = 0
forms_errors = [] # accumulate across all forms
objects = queryset.filter(pk__in=object_ids)
for obj in objects:
objects_count += 1
Expand All @@ -271,6 +279,7 @@ def mass_change_view(
form,
change=True)
else:
forms_errors.append({obj: form.errors})
form_validated = False
new_object = obj
prefixes = {}
Expand All @@ -289,36 +298,77 @@ def mass_change_view(

if all_valid(formsets) and form_validated:
# self.admin_obj.save_model(request, new_object, form, change=True)
self.save_model(
request,
new_object,
form,
change=True)
form.save_m2m()
for formset in formsets:
self.save_formset(
request,
form,
formset,
change=True)

change_message = self.construct_change_message(
request,
form,
formsets)
self.log_change(
request,
new_object,
change_message)
changed_count += 1
# second transaction.atomic added to avoid:
# "You can't execute queries ..." error
with transaction.atomic():

try:
self.save_model(
request,
new_object,
form,
change=True)
form.save_m2m()
for formset in formsets:
self.save_formset(
request,
form,
formset,
change=True)
change_message = self.construct_change_message(
request,
form,
formsets)
self.log_change(
request,
new_object,
change_message)
changed_count += 1

except IntegrityError:
obj_url = url_to_edit_object(obj)
hint = '(Either run the mass edit without'\
' this; or remove the existing record)'
msg = f'<p>Cannot modify {obj_url}: a '\
'record already exists in the database'\
f' {hint}</p>'
messages.add_message(
request, messages.ERROR, mark_safe(msg))

except DatabaseError as err:
detail = str(err.__cause__ or str(err) or '')
obj_url = url_to_edit_object(obj)
hint = 'Please attempt a manual change.'
msg = f'<p>Cannot modify {obj_url}: {detail} {hint}</p>'
messages.add_message(
request, messages.ERROR, mark_safe(msg))

except Exception as err:
detail = str(err.__cause__ or str(err) or '')
obj_url = url_to_edit_object(obj)
hint = 'Please attempt a manual change.'
msg = f'<p>Cannot modify {obj_url}: {detail} {hint}</p>'
messages.add_message(
request, messages.ERROR, mark_safe(msg))

if changed_count == objects_count:
msg = _('%s out of %s records were successfully edited.' %
(changed_count, objects_count))
messages.add_message(request, messages.INFO, msg)
return self.response_change(request, new_object)
else:
errors = form.errors
errors_list = helpers.AdminErrorList(form, formsets)
# Create consolidated feedback on errors
msg = _('%s out of %s records cannot be successfully edited' %
(objects_count - changed_count, objects_count))
messages.add_message(request, messages.ERROR, msg)
for err in forms_errors:
msg = f'<p>{list(err.keys())[0]}:<br/>' \
f'{list(err.values())[0]}</p>'.replace('__all__', 'General')
messages.add_message(request, messages.ERROR, mark_safe(msg))
# Raise error for rollback transaction in atomic block
raise ValidationError("Not all forms is correct")
raise ValidationError("The mass edit could not be executed!")

except Exception:
general_error = sys.exc_info()[1]
Expand Down
23 changes: 13 additions & 10 deletions massadmin/templates/admin/includes/mass_fieldset.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
<fieldset class="grp-module module {{ fieldset.classes }}">
{% if fieldset.name %}<h2 class="grp-collapse-handler">{{ fieldset.name }} Mass Edit</h2>{% endif %}
{% if fieldset.description %}<div class="grp-row row"><p class="grp-description">{{ fieldset.description|safe }}</p></div>{% endif %}

{% if general_error %}
<div class="grp-errors errornote"> {{ general_error}} </div>
{% endif %}

<table>
<tr>
<th>
{% trans "Mass Update?" %}
{% trans "Update?" %}
</th>
<th>
{% trans "Field" %}
Expand All @@ -25,19 +25,19 @@
</td>
</tr>
{% endif %}

{% for field in line %}
<tr>
{% if field.field.name in adminform.readonly_fields %}
<td>

</td>
<td>
{{ field.field.name }} {% trans "is read only." %}
</td>
{% else %}{% if field.field.name in unique_fields %}
<td>

</td>
<td>
{{ field.field.name }} {% trans "is unique." %}
Expand All @@ -49,20 +49,23 @@
</td>
<td class="form-row field-{{ field.field.name }}">
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
<div>{{ field.field }}</div>
<div>&nbsp;{{ field.label_tag }}</div>
{% else %}
<div>
{{ field.label_tag }}
{{ field.label_tag }}&nbsp;
</div>
<div>
{{ field.field }}
</div>
{% endif %}
{% if field.field.field.help_text %}<p class="help">{{ field.field.field.help_text|safe }}</p>{% endif %}
{% if field.field.field.help_text %}
<p class="help text-help">&nbsp;{{ field.field.field.help_text|safe }}</p>
{% endif %}
</td>
{% endif %}{% endif %}
</tr>
{% endfor %}
{% endfor %}
</table>
</fieldset>
</fieldset>