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.
pull/5506/head
Neal Todd 2019-07-17 19:56:52 +01:00 zatwierdzone przez LB Johnston
rodzic cdb13b6490
commit 4a93424654
7 zmienionych plików z 79 dodań i 5 usunięć

Wyświetl plik

@ -10,6 +10,7 @@ Changelog
* Fix: Added https support for Scribd oEmbed provider (Rodrigo) * Fix: Added https support for Scribd oEmbed provider (Rodrigo)
* Fix: Changed StreamField group labels color so labels are visible (Catherine Farman) * 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: 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 2.6.1 (xx.xx.xxxx) - IN DEVELOPMENT

Wyświetl plik

@ -28,6 +28,7 @@ Bug fixes
* Added https support for Scribd oEmbed provider (Rodrigo) * Added https support for Scribd oEmbed provider (Rodrigo)
* Changed StreamField group label color so labels are visible (Catherine Farman) * 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) * 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 Upgrade considerations

Wyświetl plik

@ -439,6 +439,23 @@ class TestDeleteViewWithProtectedRelation(TestCase, WagtailTestUtils):
self.assertFalse(Author.objects.filter(id=4).exists()) 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): class TestDeleteViewModelReprPrimary(TestCase, WagtailTestUtils):
fixtures = ['modeladmintest_test.json'] fixtures = ['modeladmintest_test.json']

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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')),
],
),
]

Wyświetl plik

@ -40,6 +40,14 @@ class Book(models.Model, index.Indexed):
return self.title 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): class Token(models.Model):
key = models.CharField(max_length=40, primary_key=True) key = models.CharField(max_length=40, primary_key=True)