0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-12-01 11:41:20 +01:00

Prevent exception when attempting to delete a model with a protected 1-to-1 relation

Modeladmin handles notification to the user if a model instance has protected ForeignKey
relationships. However, if the protected relation is a OneToOneField it raises an exception:

  File ".../wagtail/wagtail/contrib/modeladmin/views.py", line 742, in post
    for obj in qs.all():
AttributeError: 'MyRelatedModel' object has no attribute 'all'

because qs in this case is the related instance rather than a queryset of related instances
(as is the case for a ForeignKey).

This commit handles the OneToOneField case as well.
This commit is contained in:
Neal Todd 2019-07-17 19:56:52 +01:00 committed by LB Johnston
parent cdb13b6490
commit 4a93424654
7 changed files with 79 additions and 5 deletions

View File

@ -10,6 +10,7 @@ Changelog
* Fix: Added https support for Scribd oEmbed provider (Rodrigo)
* Fix: Changed StreamField group labels color so labels are visible (Catherine Farman)
* Fix: Prevented images with a very wide aspect ratio from being displayed distorted in the rich text editor (Iman Syed)
* Fix: Prevent exception when deleting a model with a protected One-to-one relationship (Neal Todd)
2.6.1 (xx.xx.xxxx) - IN DEVELOPMENT

View File

@ -28,6 +28,7 @@ Bug fixes
* Added https support for Scribd oEmbed provider (Rodrigo)
* Changed StreamField group label color so labels are visible (Catherine Farman)
* Prevented images with a very wide aspect ratio from being displayed distorted in the rich text editor (Iman Syed)
* Prevent exception when deleting a model with a protected One-to-one relationship (Neal Todd)
Upgrade considerations

View File

@ -439,6 +439,23 @@ class TestDeleteViewWithProtectedRelation(TestCase, WagtailTestUtils):
self.assertFalse(Author.objects.filter(id=4).exists())
def test_post_with_1to1_dependent_object(self):
response = self.post(5)
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
"'Harper Lee' is currently referenced by other objects"
)
self.assertContains(
response,
"<li><b>Solo Book:</b> To Kill a Mockingbird</li>"
)
# Author not deleted
self.assertTrue(Author.objects.filter(id=5).exists())
class TestDeleteViewModelReprPrimary(TestCase, WagtailTestUtils):
fixtures = ['modeladmintest_test.json']

View File

@ -6,11 +6,12 @@ from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.utils import (
get_fields_from_path, label_for_field, lookup_needs_distinct, prepare_lookup_value, quote, unquote)
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ImproperlyConfigured, PermissionDenied, SuspiciousOperation
from django.core.exceptions import (
ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied, SuspiciousOperation)
from django.core.paginator import InvalidPage, Paginator
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.db.models.fields.related import ManyToManyField
from django.db.models.fields.related import ManyToManyField, OneToOneRel
from django.shortcuts import get_object_or_404, redirect
from django.template.defaultfilters import filesizeformat
from django.utils.decorators import method_decorator
@ -738,9 +739,17 @@ class DeleteView(InstanceSpecificView):
obj.field, ManyToManyField))
for rel in fields:
if rel.on_delete == models.PROTECT:
qs = getattr(self.instance, rel.get_accessor_name())
for obj in qs.all():
linked_objects.append(obj)
if isinstance(rel, OneToOneRel):
try:
obj = getattr(self.instance, rel.get_accessor_name())
except ObjectDoesNotExist:
pass
else:
linked_objects.append(obj)
else:
qs = getattr(self.instance, rel.get_accessor_name())
for obj in qs.all():
linked_objects.append(obj)
context = self.get_context_data(
protected_error=True,
linked_objects=linked_objects

View File

@ -31,6 +31,14 @@
"date_of_birth": "1898-11-29"
}
},
{
"pk": 5,
"model": "modeladmintest.author",
"fields": {
"name": "Harper Lee",
"date_of_birth": "1926-04-28"
}
},
{
"pk": 1,
"model": "modeladmintest.book",
@ -63,6 +71,14 @@
"author_id": 3
}
},
{
"pk": 4,
"model": "modeladmintest.solobook",
"fields": {
"title": "To Kill a Mockingbird",
"author_id": 5
}
},
{
"pk": "boom",
"model": "modeladmintest.token",

View File

@ -0,0 +1,22 @@
# Generated by Django 2.1.10 on 2019-07-17 17:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('modeladmintest', '0007_friend'),
]
operations = [
migrations.CreateModel(
name='SoloBook',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('author', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='modeladmintest.Author')),
],
),
]

View File

@ -40,6 +40,14 @@ class Book(models.Model, index.Indexed):
return self.title
class SoloBook(models.Model):
author = models.OneToOneField(Author, on_delete=models.PROTECT)
title = models.CharField(max_length=255)
def __str__(self):
return self.title
class Token(models.Model):
key = models.CharField(max_length=40, primary_key=True)