From 64676c318f11c4516faaa478f5607f1748f612f1 Mon Sep 17 00:00:00 2001 From: monsanto-pinheiro Date: Fri, 10 Apr 2020 18:12:24 +0100 Subject: [PATCH 1/2] Allow models.PROTECT in the models Catch exception 'ProtectedError' when try to remove a pk with a relation in the model. Only have to define "on_delete=models.PROTECT" in the model fields. --- bootstrap_modal_forms/mixins.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bootstrap_modal_forms/mixins.py b/bootstrap_modal_forms/mixins.py index 9874264..972002b 100644 --- a/bootstrap_modal_forms/mixins.py +++ b/bootstrap_modal_forms/mixins.py @@ -1,7 +1,7 @@ from django.contrib import messages from django.contrib.auth import login as auth_login from django.http import HttpResponseRedirect - +from django.db.models import ProtectedError class PassRequestMixin(object): """ @@ -48,11 +48,16 @@ def save(self, commit=True): class DeleteMessageMixin(object): """ Mixin which adds message to BSModalDeleteView. + Catch exceptions fired up by "on_delete=models.PROTECT" in the model fields. """ - def post(self, request, *args, **kwargs): + try: + httpResponse = super(DeleteMessageMixin, self).delete(request, *args, **kwargs) messages.success(request, self.success_message) - return super(DeleteMessageMixin, self).delete(request, *args, **kwargs) + return httpResponse + except ProtectedError: + messages.error(request, "Can not remove this item, has a relation in the database.") + return HttpResponseRedirect(self.success_url) class LoginAjaxMixin(object): From 47efa11e5c3a0d74b833a1a6fa46a86547123ee4 Mon Sep 17 00:00:00 2001 From: monsanto-pinheiro Date: Fri, 10 Apr 2020 23:43:06 +0100 Subject: [PATCH 2/2] [add] catch exception ProtectedError in DeleteMessageMixin --- bootstrap_modal_forms/mixins.py | 16 +++++----- examples/migrations/0001_initial.py | 11 ++++++- examples/models.py | 5 ++- examples/templates/examples/delete_vat.html | 22 +++++++++++++ examples/urls.py | 1 + examples/views.py | 9 ++++-- tests/tests_unit.py | 35 +++++++++++++++++---- 7 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 examples/templates/examples/delete_vat.html diff --git a/bootstrap_modal_forms/mixins.py b/bootstrap_modal_forms/mixins.py index 972002b..8f8be3f 100644 --- a/bootstrap_modal_forms/mixins.py +++ b/bootstrap_modal_forms/mixins.py @@ -51,13 +51,15 @@ class DeleteMessageMixin(object): Catch exceptions fired up by "on_delete=models.PROTECT" in the model fields. """ - try: - httpResponse = super(DeleteMessageMixin, self).delete(request, *args, **kwargs) - messages.success(request, self.success_message) - return httpResponse - except ProtectedError: - messages.error(request, "Can not remove this item, has a relation in the database.") - return HttpResponseRedirect(self.success_url) + def post(self, request, *args, **kwargs): + try: + httpResponse = super(DeleteMessageMixin, self).delete(request, *args, **kwargs) + messages.success(request, self.success_message) + return httpResponse + except ProtectedError: + messages.error(request, "Can not remove this item, has a relation in the database.") + return HttpResponseRedirect(self.success_url) + class LoginAjaxMixin(object): diff --git a/examples/migrations/0001_initial.py b/examples/migrations/0001_initial.py index eed1f88..da8d8fa 100644 --- a/examples/migrations/0001_initial.py +++ b/examples/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 2.1 on 2019-03-30 16:15 +# Generated by Django 2.1 on 2020-04-10 21:15 from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -11,6 +12,13 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Vat', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vat_percentage', models.IntegerField()), + ], + ), migrations.CreateModel( name='Book', fields=[ @@ -22,6 +30,7 @@ class Migration(migrations.Migration): ('pages', models.IntegerField(blank=True, null=True)), ('book_type', models.PositiveSmallIntegerField(choices=[(1, 'Hardcover'), (2, 'Paperback'), (3, 'E-book')])), ('timestamp', models.DateField(auto_now_add=True)), + ('vat', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='examples.Vat')), ], ), ] diff --git a/examples/models.py b/examples/models.py index fecc6d7..d92db29 100644 --- a/examples/models.py +++ b/examples/models.py @@ -1,6 +1,9 @@ from django.db import models +class Vat(models.Model): + vat_percentage = models.IntegerField() + class Book(models.Model): HARDCOVER = 1 PAPERBACK = 2 @@ -16,5 +19,5 @@ class Book(models.Model): price = models.DecimalField(max_digits=5, decimal_places=2) pages = models.IntegerField(blank=True, null=True) book_type = models.PositiveSmallIntegerField(choices=BOOK_TYPES) - + vat = models.ForeignKey(Vat, on_delete=models.PROTECT, blank=True, null=True) timestamp = models.DateField(auto_now_add=True, auto_now=False) diff --git a/examples/templates/examples/delete_vat.html b/examples/templates/examples/delete_vat.html new file mode 100644 index 0000000..ae371fd --- /dev/null +++ b/examples/templates/examples/delete_vat.html @@ -0,0 +1,22 @@ +{% load widget_tweaks %} + +
+ {% csrf_token %} + + + + + + + +
diff --git a/examples/urls.py b/examples/urls.py index 149f1ff..15cf3d8 100644 --- a/examples/urls.py +++ b/examples/urls.py @@ -9,6 +9,7 @@ path('update/', views.BookUpdateView.as_view(), name='update_book'), path('read/', views.BookReadView.as_view(), name='read_book'), path('delete/', views.BookDeleteView.as_view(), name='delete_book'), + path('delete_vat/', views.VatDeleteView.as_view(), name='delete_vat'), path('signup/', views.SignUpView.as_view(), name='signup'), path('login/', views.CustomLoginView.as_view(), name='login'), ] diff --git a/examples/views.py b/examples/views.py index 1c68fc4..e81cdba 100644 --- a/examples/views.py +++ b/examples/views.py @@ -8,7 +8,7 @@ BSModalDeleteView) from .forms import BookForm, CustomUserCreationForm, CustomAuthenticationForm -from .models import Book +from .models import Book, Vat class Index(generic.ListView): @@ -43,7 +43,12 @@ class BookDeleteView(BSModalDeleteView): success_message = 'Success: Book was deleted.' success_url = reverse_lazy('index') - +class VatDeleteView(BSModalDeleteView): + model = Vat + template_name = 'examples/delete_vat.html' + success_message = 'Success: Vat was deleted.' + success_url = reverse_lazy('index') + class SignUpView(BSModalCreateView): form_class = CustomUserCreationForm template_name = 'examples/signup.html' diff --git a/tests/tests_unit.py b/tests/tests_unit.py index 9dd8555..5625d51 100644 --- a/tests/tests_unit.py +++ b/tests/tests_unit.py @@ -2,19 +2,27 @@ from django.contrib.messages import get_messages from django.test import TestCase +from examples.models import Vat from examples.models import Book class MixinsTest(TestCase): def setUp(self): + self.vat_1 = Vat.objects.create( + vat_percentage=28 + ) + self.vat_2 = Vat.objects.create( + vat_percentage=13 + ) self.book = Book.objects.create( title='Life of Jane Doe', publication_date='2019-01-01', author='Jane Doe', price=29.99, pages=477, - book_type=2 + book_type=2, + vat=self.vat_1 ) self.user = User.objects.create_user( username='user', @@ -56,7 +64,8 @@ def test_CreateUpdateAjaxMixin(self): 'price': 19.99, 'pages': 449, # Wrong value - 'book_type': 'wrong_value' + 'book_type': 'wrong_value', + 'vat': 1 }, HTTP_X_REQUESTED_WITH='XMLHttpRequest' ) @@ -78,7 +87,8 @@ def test_CreateUpdateAjaxMixin(self): 'author': 'John Doe', 'price': 19.99, 'pages': 449, - 'book_type': 1 + 'book_type': 1, + 'vat': 1 } ) @@ -103,7 +113,8 @@ def test_CreateUpdateAjaxMixin(self): 'price': 29.99, 'pages': 477, # Wrong value - 'book_type': 'wrong_value' + 'book_type': 'wrong_value', + 'vat': 1 }, HTTP_X_REQUESTED_WITH='XMLHttpRequest' ) @@ -125,7 +136,8 @@ def test_CreateUpdateAjaxMixin(self): 'author': 'Jane Doe', 'price': 29.99, 'pages': 477, - 'book_type': 2 + 'book_type': 2, + 'vat': 1 }, ) @@ -140,11 +152,22 @@ def test_DeleteMessageMixin(self): """ Delete object through BSModalDeleteView. """ + + response = self.client.post('/delete_vat/1') + messages = get_messages(response.wsgi_request) + self.assertEqual(len(messages), 1) + self.assertEqual(str(list(iter(messages))[0]), 'Can not remove this item, has a relation in the database.') + + response = self.client.post('/delete_vat/2') + messages = get_messages(response.wsgi_request) + self.assertEqual(str(list(iter(messages))[1]), 'Success: Vat was deleted.') # Request to delete view passes message to the response response = self.client.post('/delete/1') messages = get_messages(response.wsgi_request) - self.assertEqual(len(messages), 1) + self.assertEqual(len(messages), 3) + self.assertEqual(str(list(iter(messages))[2]), 'Success: Book was deleted.') + def test_LoginAjaxMixin(self): """