Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

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
View
@@ -362,6 +362,7 @@ answer newbie questions, and generally made Django that much better:
Mike Malone <mjmalone@gmail.com>
Martin Maney <http://www.chipy.org/Martin_Maney>
Michael Manfre <mmanfre@gmail.com>
+ Javier Mansilla <javimansilla@gmail.com>
masonsimon+django@gmail.com
Manuzhai
Petr Marhoun <petr.marhoun@gmail.com>
35 django/contrib/admin/options.py
View
@@ -8,7 +8,8 @@
inlineformset_factory, BaseInlineFormSet)
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers
-from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
+from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
+ model_format_dict, NestedObjects)
from django.contrib.admin.templatetags.admin_static import static
from django.contrib import messages
from django.views.decorators.csrf import csrf_protect
@@ -1452,7 +1453,39 @@ def get_formset(self, request, obj=None, **kwargs):
"max_num": self.max_num,
"can_delete": can_delete,
}
+
defaults.update(kwargs)
+ base_model_form = defaults['form']
+
+ class DeleteProtectedModelForm(base_model_form):
+ def hand_clean_DELETE(self):
+ """
+ We are not validating the field 'DELETE' itself because on templates
+ it's not rendered using the field information, but just using a generic
+ "deletion_field" of the InlineModelAdmin.
+ """
+ if self.cleaned_data.get('DELETE', ''):
+ using = router.db_for_write(self._meta.model)
+ collector = NestedObjects(using=using)
+ collector.collect([self.instance])
+ if collector.protected:
+ fmt_prot = [u'%s %s' % (p._meta.verbose_name, p)
+ for p in collector.protected]
+ msg_dict = {'class_name': self._meta.model._meta.verbose_name,
+ 'instance': self.instance,
+ 'related_objects': ', '.join(fmt_prot)}
+ msg = _("Deleting %(class_name)s %(instance)s would require "
+ "deleting the following protected related objects: "
+ "%(related_objects)s" % msg_dict)
+ raise ValidationError(msg)
+
+ def is_valid(self):
+ cleaned_data = self.cleaned_data
+ result = super(DeleteProtectedModelForm, self).is_valid()
+ self.hand_clean_DELETE()
+ return result
+
+ defaults['form'] = DeleteProtectedModelForm
return inlineformset_factory(self.parent_model, self.model, **defaults)
def get_fieldsets(self, request, obj=None):
7 tests/regressiontests/admin_inlines/models.py
View
@@ -128,8 +128,15 @@ class Novel(models.Model):
name = models.CharField(max_length=40)
class Chapter(models.Model):
+ name = models.CharField(max_length=40)
novel = models.ForeignKey(Novel)
+class FootNote(models.Model):
+ """
+ Model added for ticket 19838
+ """
+ chapter = models.ForeignKey(Chapter, on_delete=models.PROTECT)
+ note = models.CharField(max_length=40)
# Models for #16838
39 tests/regressiontests/admin_inlines/tests.py
View
@@ -12,7 +12,7 @@
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
- Sighting, Title)
+ Sighting, Title, Novel, Chapter, FootNote)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@@ -246,6 +246,43 @@ def test_immutable_content_type(self):
parent_ct = ContentType.objects.get_for_model(Parent)
self.assertEqual(iaf.original.content_type, parent_ct)
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
+class TestInlineProtectedOnDelete(TestCase):
+ urls = "regressiontests.admin_inlines.urls"
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ result = self.client.login(username='super', password='secret')
+ self.assertEqual(result, True)
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_deleting_inline_with_protected_delete_does_not_validate(self):
+ lotr = Novel.objects.create(name='Lord of the rings')
+ chapter = Chapter.objects.create(novel=lotr, name='Many Meetings')
+ foot_note = FootNote.objects.create(chapter=chapter, note='yadda yadda')
+
+ change_url = '/admin/admin_inlines/novel/%i/' % lotr.id
+ response = self.client.get(change_url)
+ data = {
+ 'name': lotr.name,
+ 'chapter_set-TOTAL_FORMS': 1,
+ 'chapter_set-INITIAL_FORMS': 1,
+ 'chapter_set-MAX_NUM_FORMS': 1000,
+ '_save': 'Save',
+ 'chapter_set-0-id': chapter.id,
+ 'chapter_set-0-name': chapter.name,
+ 'chapter_set-0-novel': lotr.id,
+ 'chapter_set-0-DELETE': 'on'
+ }
+ response = self.client.post(change_url, data)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Deleting chapter %s would require deleting "
+ "the following protected related objects: foot note %s"
+ % (chapter, foot_note))
+
+
class TestInlinePermissions(TestCase):
"""
Make sure the admin respects permissions for objects that are edited
Something went wrong with that request. Please try again.