Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

19838 protected fk not raising errors on delete #797

Closed
wants to merge 4 commits into from

2 participants

Javier Mansilla Ramiro Morales
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
1  AUTHORS
@@ -362,6 +362,7 @@ answer newbie questions, and generally made Django that much better:
362 362 Mike Malone <mjmalone@gmail.com>
363 363 Martin Maney <http://www.chipy.org/Martin_Maney>
364 364 Michael Manfre <mmanfre@gmail.com>
  365 + Javier Mansilla <javimansilla@gmail.com>
365 366 masonsimon+django@gmail.com
366 367 Manuzhai
367 368 Petr Marhoun <petr.marhoun@gmail.com>
35 django/contrib/admin/options.py
@@ -8,7 +8,8 @@
8 8 inlineformset_factory, BaseInlineFormSet)
9 9 from django.contrib.contenttypes.models import ContentType
10 10 from django.contrib.admin import widgets, helpers
11   -from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
  11 +from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
  12 + model_format_dict, NestedObjects)
12 13 from django.contrib.admin.templatetags.admin_static import static
13 14 from django.contrib import messages
14 15 from django.views.decorators.csrf import csrf_protect
@@ -1452,7 +1453,39 @@ def get_formset(self, request, obj=None, **kwargs):
1452 1453 "max_num": self.max_num,
1453 1454 "can_delete": can_delete,
1454 1455 }
  1456 +
1455 1457 defaults.update(kwargs)
  1458 + base_model_form = defaults['form']
  1459 +
  1460 + class DeleteProtectedModelForm(base_model_form):
  1461 + def hand_clean_DELETE(self):
  1462 + """
  1463 + We are not validating the field 'DELETE' itself because on templates
  1464 + it's not rendered using the field information, but just using a generic
  1465 + "deletion_field" of the InlineModelAdmin.
  1466 + """
  1467 + if self.cleaned_data.get('DELETE', ''):
  1468 + using = router.db_for_write(self._meta.model)
  1469 + collector = NestedObjects(using=using)
  1470 + collector.collect([self.instance])
  1471 + if collector.protected:
  1472 + fmt_prot = [u'%s %s' % (p._meta.verbose_name, p)
  1473 + for p in collector.protected]
  1474 + msg_dict = {'class_name': self._meta.model._meta.verbose_name,
  1475 + 'instance': self.instance,
  1476 + 'related_objects': ', '.join(fmt_prot)}
  1477 + msg = _("Deleting %(class_name)s %(instance)s would require "
  1478 + "deleting the following protected related objects: "
  1479 + "%(related_objects)s" % msg_dict)
  1480 + raise ValidationError(msg)
  1481 +
  1482 + def is_valid(self):
  1483 + cleaned_data = self.cleaned_data
  1484 + result = super(DeleteProtectedModelForm, self).is_valid()
  1485 + self.hand_clean_DELETE()
  1486 + return result
  1487 +
  1488 + defaults['form'] = DeleteProtectedModelForm
1456 1489 return inlineformset_factory(self.parent_model, self.model, **defaults)
1457 1490
1458 1491 def get_fieldsets(self, request, obj=None):
7 tests/regressiontests/admin_inlines/models.py
@@ -128,8 +128,15 @@ class Novel(models.Model):
128 128 name = models.CharField(max_length=40)
129 129
130 130 class Chapter(models.Model):
  131 + name = models.CharField(max_length=40)
131 132 novel = models.ForeignKey(Novel)
132 133
  134 +class FootNote(models.Model):
  135 + """
  136 + Model added for ticket 19838
  137 + """
  138 + chapter = models.ForeignKey(Chapter, on_delete=models.PROTECT)
  139 + note = models.CharField(max_length=40)
133 140
134 141 # Models for #16838
135 142
39 tests/regressiontests/admin_inlines/tests.py
@@ -12,7 +12,7 @@
12 12 from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
13 13 OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
14 14 ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
15   - Sighting, Title)
  15 + Sighting, Title, Novel, Chapter, FootNote)
16 16
17 17
18 18 @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@@ -246,6 +246,43 @@ def test_immutable_content_type(self):
246 246 parent_ct = ContentType.objects.get_for_model(Parent)
247 247 self.assertEqual(iaf.original.content_type, parent_ct)
248 248
  249 +@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
  250 +class TestInlineProtectedOnDelete(TestCase):
  251 + urls = "regressiontests.admin_inlines.urls"
  252 + fixtures = ['admin-views-users.xml']
  253 +
  254 + def setUp(self):
  255 + result = self.client.login(username='super', password='secret')
  256 + self.assertEqual(result, True)
  257 +
  258 + def tearDown(self):
  259 + self.client.logout()
  260 +
  261 + def test_deleting_inline_with_protected_delete_does_not_validate(self):
  262 + lotr = Novel.objects.create(name='Lord of the rings')
  263 + chapter = Chapter.objects.create(novel=lotr, name='Many Meetings')
  264 + foot_note = FootNote.objects.create(chapter=chapter, note='yadda yadda')
  265 +
  266 + change_url = '/admin/admin_inlines/novel/%i/' % lotr.id
  267 + response = self.client.get(change_url)
  268 + data = {
  269 + 'name': lotr.name,
  270 + 'chapter_set-TOTAL_FORMS': 1,
  271 + 'chapter_set-INITIAL_FORMS': 1,
  272 + 'chapter_set-MAX_NUM_FORMS': 1000,
  273 + '_save': 'Save',
  274 + 'chapter_set-0-id': chapter.id,
  275 + 'chapter_set-0-name': chapter.name,
  276 + 'chapter_set-0-novel': lotr.id,
  277 + 'chapter_set-0-DELETE': 'on'
  278 + }
  279 + response = self.client.post(change_url, data)
  280 + self.assertEqual(response.status_code, 200)
  281 + self.assertContains(response, "Deleting chapter %s would require deleting "
  282 + "the following protected related objects: foot note %s"
  283 + % (chapter, foot_note))
  284 +
  285 +
249 286 class TestInlinePermissions(TestCase):
250 287 """
251 288 Make sure the admin respects permissions for objects that are edited

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.