mirror of
https://github.com/django/django.git
synced 2024-11-19 16:04:13 +00:00
1cd6e04cd4
Objects can be fast-path deleted if there are no signals, and there are no further cascades. If fast-path is taken, the objects do not need to be loaded into memory before deletion. Thanks to Jeremy Dunck, Simon Charette and Alex Gaynor for reviewing the patch.
299 lines
9.7 KiB
Python
299 lines
9.7 KiB
Python
from __future__ import absolute_import, unicode_literals
|
|
|
|
from datetime import datetime
|
|
|
|
from django.conf import settings
|
|
from django.contrib import admin
|
|
from django.contrib.admin import helpers
|
|
from django.contrib.admin.util import (display_for_field, label_for_field,
|
|
lookup_field, NestedObjects)
|
|
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
|
|
from django.contrib.sites.models import Site
|
|
from django.db import models, DEFAULT_DB_ALIAS
|
|
from django import forms
|
|
from django.test import TestCase
|
|
from django.utils import unittest
|
|
from django.utils.formats import localize
|
|
from django.utils.safestring import mark_safe
|
|
from django.utils import six
|
|
|
|
from .models import Article, Count, Event, Location, EventGuide
|
|
|
|
|
|
class NestedObjectsTests(TestCase):
|
|
"""
|
|
Tests for ``NestedObject`` utility collection.
|
|
|
|
"""
|
|
def setUp(self):
|
|
self.n = NestedObjects(using=DEFAULT_DB_ALIAS)
|
|
self.objs = [Count.objects.create(num=i) for i in range(5)]
|
|
|
|
def _check(self, target):
|
|
self.assertEqual(self.n.nested(lambda obj: obj.num), target)
|
|
|
|
def _connect(self, i, j):
|
|
self.objs[i].parent = self.objs[j]
|
|
self.objs[i].save()
|
|
|
|
def _collect(self, *indices):
|
|
self.n.collect([self.objs[i] for i in indices])
|
|
|
|
def test_unrelated_roots(self):
|
|
self._connect(2, 1)
|
|
self._collect(0)
|
|
self._collect(1)
|
|
self._check([0, 1, [2]])
|
|
|
|
def test_siblings(self):
|
|
self._connect(1, 0)
|
|
self._connect(2, 0)
|
|
self._collect(0)
|
|
self._check([0, [1, 2]])
|
|
|
|
def test_non_added_parent(self):
|
|
self._connect(0, 1)
|
|
self._collect(0)
|
|
self._check([0])
|
|
|
|
def test_cyclic(self):
|
|
self._connect(0, 2)
|
|
self._connect(1, 0)
|
|
self._connect(2, 1)
|
|
self._collect(0)
|
|
self._check([0, [1, [2]]])
|
|
|
|
def test_queries(self):
|
|
self._connect(1, 0)
|
|
self._connect(2, 0)
|
|
# 1 query to fetch all children of 0 (1 and 2)
|
|
# 1 query to fetch all children of 1 and 2 (none)
|
|
# Should not require additional queries to populate the nested graph.
|
|
self.assertNumQueries(2, self._collect, 0)
|
|
|
|
def test_on_delete_do_nothing(self):
|
|
"""
|
|
Check that the nested collector doesn't query for DO_NOTHING objects.
|
|
"""
|
|
n = NestedObjects(using=DEFAULT_DB_ALIAS)
|
|
objs = [Event.objects.create()]
|
|
EventGuide.objects.create(event=objs[0])
|
|
with self.assertNumQueries(2):
|
|
# One for Location, one for Guest, and no query for EventGuide
|
|
n.collect(objs)
|
|
|
|
class UtilTests(unittest.TestCase):
|
|
def test_values_from_lookup_field(self):
|
|
"""
|
|
Regression test for #12654: lookup_field
|
|
"""
|
|
SITE_NAME = 'example.com'
|
|
TITLE_TEXT = 'Some title'
|
|
CREATED_DATE = datetime.min
|
|
ADMIN_METHOD = 'admin method'
|
|
SIMPLE_FUNCTION = 'function'
|
|
INSTANCE_ATTRIBUTE = 'attr'
|
|
|
|
class MockModelAdmin(object):
|
|
def get_admin_value(self, obj):
|
|
return ADMIN_METHOD
|
|
|
|
simple_function = lambda obj: SIMPLE_FUNCTION
|
|
|
|
article = Article(
|
|
site=Site(domain=SITE_NAME),
|
|
title=TITLE_TEXT,
|
|
created=CREATED_DATE,
|
|
)
|
|
article.non_field = INSTANCE_ATTRIBUTE
|
|
|
|
verifications = (
|
|
('site', SITE_NAME),
|
|
('created', localize(CREATED_DATE)),
|
|
('title', TITLE_TEXT),
|
|
('get_admin_value', ADMIN_METHOD),
|
|
(simple_function, SIMPLE_FUNCTION),
|
|
('test_from_model', article.test_from_model()),
|
|
('non_field', INSTANCE_ATTRIBUTE)
|
|
)
|
|
|
|
mock_admin = MockModelAdmin()
|
|
for name, value in verifications:
|
|
field, attr, resolved_value = lookup_field(name, article, mock_admin)
|
|
|
|
if field is not None:
|
|
resolved_value = display_for_field(resolved_value, field)
|
|
|
|
self.assertEqual(value, resolved_value)
|
|
|
|
def test_null_display_for_field(self):
|
|
"""
|
|
Regression test for #12550: display_for_field should handle None
|
|
value.
|
|
"""
|
|
display_value = display_for_field(None, models.CharField())
|
|
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
|
|
|
display_value = display_for_field(None, models.CharField(
|
|
choices=(
|
|
(None, "test_none"),
|
|
)
|
|
))
|
|
self.assertEqual(display_value, "test_none")
|
|
|
|
display_value = display_for_field(None, models.DateField())
|
|
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
|
|
|
display_value = display_for_field(None, models.TimeField())
|
|
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
|
|
|
# Regression test for #13071: NullBooleanField has special
|
|
# handling.
|
|
display_value = display_for_field(None, models.NullBooleanField())
|
|
expected = '<img src="%sadmin/img/icon-unknown.gif" alt="None" />' % settings.STATIC_URL
|
|
self.assertEqual(display_value, expected)
|
|
|
|
display_value = display_for_field(None, models.DecimalField())
|
|
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
|
|
|
display_value = display_for_field(None, models.FloatField())
|
|
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
|
|
|
|
def test_label_for_field(self):
|
|
"""
|
|
Tests for label_for_field
|
|
"""
|
|
self.assertEqual(
|
|
label_for_field("title", Article),
|
|
"title"
|
|
)
|
|
self.assertEqual(
|
|
label_for_field("title2", Article),
|
|
"another name"
|
|
)
|
|
self.assertEqual(
|
|
label_for_field("title2", Article, return_attr=True),
|
|
("another name", None)
|
|
)
|
|
|
|
self.assertEqual(
|
|
label_for_field("__unicode__", Article),
|
|
"article"
|
|
)
|
|
self.assertEqual(
|
|
label_for_field("__str__", Article),
|
|
str("article")
|
|
)
|
|
|
|
self.assertRaises(
|
|
AttributeError,
|
|
lambda: label_for_field("unknown", Article)
|
|
)
|
|
|
|
def test_callable(obj):
|
|
return "nothing"
|
|
self.assertEqual(
|
|
label_for_field(test_callable, Article),
|
|
"Test callable"
|
|
)
|
|
self.assertEqual(
|
|
label_for_field(test_callable, Article, return_attr=True),
|
|
("Test callable", test_callable)
|
|
)
|
|
|
|
self.assertEqual(
|
|
label_for_field("test_from_model", Article),
|
|
"Test from model"
|
|
)
|
|
self.assertEqual(
|
|
label_for_field("test_from_model", Article, return_attr=True),
|
|
("Test from model", Article.test_from_model)
|
|
)
|
|
self.assertEqual(
|
|
label_for_field("test_from_model_with_override", Article),
|
|
"not What you Expect"
|
|
)
|
|
|
|
self.assertEqual(
|
|
label_for_field(lambda x: "nothing", Article),
|
|
"--"
|
|
)
|
|
|
|
class MockModelAdmin(object):
|
|
def test_from_model(self, obj):
|
|
return "nothing"
|
|
test_from_model.short_description = "not Really the Model"
|
|
|
|
self.assertEqual(
|
|
label_for_field("test_from_model", Article, model_admin=MockModelAdmin),
|
|
"not Really the Model"
|
|
)
|
|
self.assertEqual(
|
|
label_for_field("test_from_model", Article,
|
|
model_admin = MockModelAdmin,
|
|
return_attr = True
|
|
),
|
|
("not Really the Model", MockModelAdmin.test_from_model)
|
|
)
|
|
|
|
def test_related_name(self):
|
|
"""
|
|
Regression test for #13963
|
|
"""
|
|
self.assertEqual(
|
|
label_for_field('location', Event, return_attr=True),
|
|
('location', None),
|
|
)
|
|
self.assertEqual(
|
|
label_for_field('event', Location, return_attr=True),
|
|
('awesome event', None),
|
|
)
|
|
self.assertEqual(
|
|
label_for_field('guest', Event, return_attr=True),
|
|
('awesome guest', None),
|
|
)
|
|
|
|
def test_logentry_unicode(self):
|
|
"""
|
|
Regression test for #15661
|
|
"""
|
|
log_entry = admin.models.LogEntry()
|
|
|
|
log_entry.action_flag = admin.models.ADDITION
|
|
self.assertTrue(
|
|
six.text_type(log_entry).startswith('Added ')
|
|
)
|
|
|
|
log_entry.action_flag = admin.models.CHANGE
|
|
self.assertTrue(
|
|
six.text_type(log_entry).startswith('Changed ')
|
|
)
|
|
|
|
log_entry.action_flag = admin.models.DELETION
|
|
self.assertTrue(
|
|
six.text_type(log_entry).startswith('Deleted ')
|
|
)
|
|
|
|
def test_safestring_in_field_label(self):
|
|
# safestring should not be escaped
|
|
class MyForm(forms.Form):
|
|
text = forms.CharField(label=mark_safe('<i>text</i>'))
|
|
cb = forms.BooleanField(label=mark_safe('<i>cb</i>'))
|
|
|
|
form = MyForm()
|
|
self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
|
|
'<label for="id_text" class="required inline"><i>text</i>:</label>')
|
|
self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
|
|
'<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>')
|
|
|
|
# normal strings needs to be escaped
|
|
class MyForm(forms.Form):
|
|
text = forms.CharField(label='&text')
|
|
cb = forms.BooleanField(label='&cb')
|
|
|
|
form = MyForm()
|
|
self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
|
|
'<label for="id_text" class="required inline">&text:</label>')
|
|
self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
|
|
'<label for="id_cb" class="vCheckboxLabel required inline">&cb</label>')
|