From 8d0a46876477efea18eb62e3bdd5a34f5032ab9a Mon Sep 17 00:00:00 2001 From: Derek Hohls Date: Tue, 12 Dec 2023 08:40:18 +0200 Subject: [PATCH 01/17] Track errors across all forms and provide feedback via messages to user. --- massadmin/massadmin.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index 4eb51db..b66eabf 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -248,6 +248,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 @@ -271,6 +272,7 @@ def mass_change_view( form, change=True) else: + forms_errors.append({obj: form.errors}) form_validated = False new_object = obj prefixes = {} @@ -317,8 +319,16 @@ def mass_change_view( else: errors = form.errors errors_list = helpers.AdminErrorList(form, formsets) + # Create consolidated feedback on errors + msg = _('%s out of %s edits have errors!' % + (objects_count - changed_count, objects_count)) + messages.add_message(request, messages.ERROR, msg) + for err in forms_errors: + msg = f'

{list(err.keys())[0]}:
' \ + f'{list(err.values())[0]}

'.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("Not all forms' data are correct") except Exception: general_error = sys.exc_info()[1] From f9b0703e8249b0ae2588eae49aaefc304f239e1d Mon Sep 17 00:00:00 2001 From: Derek Hohls Date: Wed, 23 Oct 2024 13:59:42 +0200 Subject: [PATCH 02/17] Add messages dependancy --- .gitignore | 1 + massadmin/massadmin.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cbb74dd..95bd76d 100755 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ django_mass_edit.egg-info .idea .coverage .tox +.spyproject diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index b66eabf..2e24de9 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -31,7 +31,7 @@ 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 From a207691d23e543117521eb07f2cd42471564e7ea Mon Sep 17 00:00:00 2001 From: derek-richard Date: Wed, 23 Oct 2024 15:24:44 +0200 Subject: [PATCH 03/17] Add explicit separation of field and label for checkbox --- .../templates/admin/includes/mass_fieldset.html | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/massadmin/templates/admin/includes/mass_fieldset.html b/massadmin/templates/admin/includes/mass_fieldset.html index 93ada00..61ccb44 100644 --- a/massadmin/templates/admin/includes/mass_fieldset.html +++ b/massadmin/templates/admin/includes/mass_fieldset.html @@ -3,11 +3,11 @@
{% if fieldset.name %}

{{ fieldset.name }} Mass Edit

{% endif %} {% if fieldset.description %}

{{ fieldset.description|safe }}

{% endif %} - + {% if general_error %}
{{ general_error}}
{% endif %} - + {% endif %} - + {% for field in line %} {% if field.field.name in adminform.readonly_fields %} {% else %}{% if field.field.name in unique_fields %}
@@ -25,19 +25,19 @@
- + {{ field.field.name }} {% trans "is read only." %} - + {{ field.field.name }} {% trans "is unique." %} @@ -49,7 +49,8 @@ {% if field.is_checkbox %} - {{ field.field }}{{ field.label_tag }} +
{{ field.field }}
+
{{ field.label_tag }}
{% else %}
{{ field.label_tag }} @@ -65,4 +66,4 @@ {% endfor %} {% endfor %}
-
\ No newline at end of file + From cdaa6a98fd385fe3b2904e174bfad046cd7a2d40 Mon Sep 17 00:00:00 2001 From: derek-richard Date: Wed, 23 Oct 2024 15:32:00 +0200 Subject: [PATCH 04/17] Add white space before help text next to a form field --- massadmin/templates/admin/includes/mass_fieldset.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/massadmin/templates/admin/includes/mass_fieldset.html b/massadmin/templates/admin/includes/mass_fieldset.html index 61ccb44..1375fea 100644 --- a/massadmin/templates/admin/includes/mass_fieldset.html +++ b/massadmin/templates/admin/includes/mass_fieldset.html @@ -59,7 +59,9 @@ {{ field.field }} {% endif %} - {% if field.field.field.help_text %}

{{ field.field.field.help_text|safe }}

{% endif %} + {% if field.field.field.help_text %} +

 {{ field.field.field.help_text|safe }}

+ {% endif %} {% endif %}{% endif %} From 9165e3a4837d8aa4b5352e5ad399cc23b66ebb01 Mon Sep 17 00:00:00 2001 From: derek-richard Date: Wed, 23 Oct 2024 15:44:35 +0200 Subject: [PATCH 05/17] Add white space after form field names --- massadmin/templates/admin/includes/mass_fieldset.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/massadmin/templates/admin/includes/mass_fieldset.html b/massadmin/templates/admin/includes/mass_fieldset.html index 1375fea..8be4d69 100644 --- a/massadmin/templates/admin/includes/mass_fieldset.html +++ b/massadmin/templates/admin/includes/mass_fieldset.html @@ -11,7 +11,7 @@
- {% trans "Mass Update?" %} + {% trans "Update?" %} {% trans "Field" %} @@ -50,10 +50,10 @@ {% if field.is_checkbox %}
{{ field.field }}
-
{{ field.label_tag }}
+
 {{ field.label_tag }}
{% else %}
- {{ field.label_tag }} + {{ field.label_tag }} 
{{ field.field }} From f687e818668c7234ab48b58e3e140f6fe87a0c2d Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 11:04:17 +0200 Subject: [PATCH 06/17] Added exception handler for Database errors on object save --- massadmin/massadmin.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index 2e24de9..19b944a 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -37,7 +37,7 @@ 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 try: # Django>=1.9 from django.apps import apps get_model = apps.get_model @@ -291,18 +291,21 @@ 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( + try: + self.save_model( request, + new_object, form, - formset, change=True) + form.save_m2m() + for formset in formsets: + self.save_formset( + request, + form, + formset, + change=True) + except DatabaseError as err: + forms_errors.append({new_object: err.message}) change_message = self.construct_change_message( request, From a8d22a198ddaac13d5ea22441f4c0358954e1ad3 Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 11:08:49 +0200 Subject: [PATCH 07/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index 19b944a..19fc1ef 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -305,7 +305,7 @@ def mass_change_view( formset, change=True) except DatabaseError as err: - forms_errors.append({new_object: err.message}) + forms_errors.append({new_object: err}) change_message = self.construct_change_message( request, From 11a253e30ee79c0b90116d5e697f50e2b5574c7f Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 11:12:09 +0200 Subject: [PATCH 08/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index 19fc1ef..870a6ef 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -304,19 +304,19 @@ def mass_change_view( 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 DatabaseError as err: forms_errors.append({new_object: err}) - change_message = self.construct_change_message( - request, - form, - formsets) - self.log_change( - request, - new_object, - change_message) - changed_count += 1 - if changed_count == objects_count: return self.response_change(request, new_object) else: From 5d9ae476f48d1f0d3cc081e7eb0b4b7a6be8fd4b Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 11:29:15 +0200 Subject: [PATCH 09/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index 870a6ef..ebff5be 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -315,7 +315,9 @@ def mass_change_view( changed_count += 1 except DatabaseError as err: - forms_errors.append({new_object: err}) + msg = f'

Cannot add {new_object}: {err.message}

' + messages.add_message( + request, messages.ERROR, mark_safe(msg)) if changed_count == objects_count: return self.response_change(request, new_object) From fb086ea3f63cd434ad7515bd0a8dc12a681b731e Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 11:32:12 +0200 Subject: [PATCH 10/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index ebff5be..8cedd0d 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -315,7 +315,8 @@ def mass_change_view( changed_count += 1 except DatabaseError as err: - msg = f'

Cannot add {new_object}: {err.message}

' + detail = str(err.__cause__ or '') + msg = f'

Cannot add {new_object}: {detail}

' messages.add_message( request, messages.ERROR, mark_safe(msg)) From 466058e117facf442fb4c85a8c7c986e844d4dbf Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 11:54:19 +0200 Subject: [PATCH 11/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index 8cedd0d..d9c663a 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -37,7 +37,7 @@ from django.urls import reverse except ImportError: # Django<2.0 from django.core.urlresolvers import reverse -from django.db import transaction, DatabaseError +from django.db import transaction, DatabaseError, IntegrityError try: # Django>=1.9 from django.apps import apps get_model = apps.get_model @@ -314,6 +314,12 @@ def mass_change_view( change_message) changed_count += 1 + except IntegrityError: + msg = f'

Cannot add {str(new_object)}: '\ + 'it already exists in the datebase

' + messages.add_message( + request, messages.ERROR, mark_safe(msg)) + except DatabaseError as err: detail = str(err.__cause__ or '') msg = f'

Cannot add {new_object}: {detail}

' From b87ba1d3f0341a5ea408f607626d0cae5cd7dc24 Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 13:41:54 +0200 Subject: [PATCH 12/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 66 ++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index d9c663a..c5b0d23 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -291,40 +291,44 @@ def mass_change_view( if all_valid(formsets) and form_validated: # self.admin_obj.save_model(request, new_object, form, change=True) - try: - self.save_model( - request, - new_object, - form, - change=True) - form.save_m2m() - for formset in formsets: - self.save_formset( + # second atomic to prevent : + # " You can't execute queries ..." error + with transaction.atomic(): + + try: + self.save_model( request, + new_object, 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: - msg = f'

Cannot add {str(new_object)}: '\ - 'it already exists in the datebase

' - messages.add_message( - request, messages.ERROR, mark_safe(msg)) - - except DatabaseError as err: - detail = str(err.__cause__ or '') - msg = f'

Cannot add {new_object}: {detail}

' - messages.add_message( - request, messages.ERROR, mark_safe(msg)) + 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: + msg = f'

Cannot add {str(new_object)}: '\ + 'it already exists in the datebase

' + messages.add_message( + request, messages.ERROR, mark_safe(msg)) + + except DatabaseError as err: + detail = str(err.__cause__ or '') + msg = f'

Cannot add {new_object}: {detail}

' + messages.add_message( + request, messages.ERROR, mark_safe(msg)) if changed_count == objects_count: return self.response_change(request, new_object) From 1e1088ce85f18f9c05841a039df4d28e5990e448 Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 13:57:12 +0200 Subject: [PATCH 13/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index c5b0d23..d3755a2 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -64,6 +64,12 @@ 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.id]) + return '%s' % (url, obj.__unicode__()) + + def mass_change_selected(modeladmin, request, queryset): selected = queryset.values_list('pk', flat=True) @@ -319,14 +325,15 @@ def mass_change_view( changed_count += 1 except IntegrityError: - msg = f'

Cannot add {str(new_object)}: '\ - 'it already exists in the datebase

' + obj_url = url_to_edit_object(obj) + msg = f'

Cannot modify {obj_url}: '\ + 'a record already exists in the database

' messages.add_message( request, messages.ERROR, mark_safe(msg)) except DatabaseError as err: detail = str(err.__cause__ or '') - msg = f'

Cannot add {new_object}: {detail}

' + msg = f'

Cannot modify {new_object}: {detail}

' messages.add_message( request, messages.ERROR, mark_safe(msg)) @@ -336,7 +343,7 @@ def mass_change_view( errors = form.errors errors_list = helpers.AdminErrorList(form, formsets) # Create consolidated feedback on errors - msg = _('%s out of %s edits have 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: @@ -344,7 +351,7 @@ def mass_change_view( f'{list(err.values())[0]}

'.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' data are correct") + raise ValidationError("The mass edit could not be executed!") except Exception: general_error = sys.exc_info()[1] From 7b67c121aad212b4835594eff0dcdcd1d2d46938 Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 14:02:04 +0200 Subject: [PATCH 14/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index d3755a2..f4e44b7 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -66,8 +66,9 @@ def url_to_edit_object(obj): url = reverse('admin:%s_%s_change' % ( - obj._meta.app_label, obj._meta.model_name), args=[obj.id]) - return '%s' % (url, obj.__unicode__()) + obj._meta.app_label, obj._meta.model_name), args=[obj.pk]) + return '%s #%s' % ( + url, obj._meta.model_name, obj.pk) def mass_change_selected(modeladmin, request, queryset): From 96fc94a5737fe940d8ac7766b742ef0fd6066b74 Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 16:03:41 +0200 Subject: [PATCH 15/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index f4e44b7..134a2be 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -298,8 +298,8 @@ def mass_change_view( if all_valid(formsets) and form_validated: # self.admin_obj.save_model(request, new_object, form, change=True) - # second atomic to prevent : - # " You can't execute queries ..." error + # second transaction.atomic added to avoid: + # "You can't execute queries ..." error with transaction.atomic(): try: @@ -327,8 +327,11 @@ def mass_change_view( except IntegrityError: obj_url = url_to_edit_object(obj) - msg = f'

Cannot modify {obj_url}: '\ - 'a record already exists in the database

' + hint = '(Either run the mass edit without'\ + ' this; or remove the existing record)' + msg = f'

Cannot modify {obj_url}: a '\ + 'record already exists in the database'\ + f' {hint}

' messages.add_message( request, messages.ERROR, mark_safe(msg)) From c4e1a6e1d26c3fb3b71541241f745bed6afecd22 Mon Sep 17 00:00:00 2001 From: derek-richard Date: Thu, 24 Oct 2024 16:24:02 +0200 Subject: [PATCH 16/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index 134a2be..478d3cd 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -68,7 +68,7 @@ def url_to_edit_object(obj): url = reverse('admin:%s_%s_change' % ( obj._meta.app_label, obj._meta.model_name), args=[obj.pk]) return '%s #%s' % ( - url, obj._meta.model_name, obj.pk) + url, obj._meta.verbose_name, obj.pk) def mass_change_selected(modeladmin, request, queryset): From 99afd86ba954efe57640d3d911b7810b2a12597a Mon Sep 17 00:00:00 2001 From: derek-richard Date: Fri, 25 Oct 2024 10:17:54 +0200 Subject: [PATCH 17/17] Updated exception handler for Database errors on object save --- massadmin/massadmin.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/massadmin/massadmin.py b/massadmin/massadmin.py index 478d3cd..6f07dc9 100644 --- a/massadmin/massadmin.py +++ b/massadmin/massadmin.py @@ -336,12 +336,25 @@ def mass_change_view( request, messages.ERROR, mark_safe(msg)) except DatabaseError as err: - detail = str(err.__cause__ or '') - msg = f'

Cannot modify {new_object}: {detail}

' + detail = str(err.__cause__ or str(err) or '') + obj_url = url_to_edit_object(obj) + hint = 'Please attempt a manual change.' + msg = f'

Cannot modify {obj_url}: {detail} {hint}

' + 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'

Cannot modify {obj_url}: {detail} {hint}

' 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