浏览代码

Moved LogEntry-related tests to their own test case

Thanks Tim Graham for reviewing and contributing to the patch.
Refs #21113.
Claude Paroz 9 年之前
父节点
当前提交
35c41987ec

+ 7 - 0
tests/admin_utils/admin.py

@@ -0,0 +1,7 @@
+from django.contrib import admin
+
+from .models import Article, ArticleProxy
+
+site = admin.AdminSite(name='admin')
+site.register(Article)
+site.register(ArticleProxy)

+ 5 - 0
tests/admin_utils/models.py

@@ -28,6 +28,11 @@ class Article(models.Model):
     test_from_model_with_override.short_description = "not What you Expect"
 
 
+class ArticleProxy(Article):
+    class Meta:
+        proxy = True
+
+
 @python_2_unicode_compatible
 class Count(models.Model):
     num = models.PositiveSmallIntegerField()

+ 146 - 0
tests/admin_utils/test_logentry.py

@@ -0,0 +1,146 @@
+from __future__ import unicode_literals
+
+from datetime import datetime
+
+from django.contrib.admin.models import ADDITION, CHANGE, DELETION, LogEntry
+from django.contrib.admin.utils import quote
+from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
+from django.core.urlresolvers import reverse
+from django.test import TestCase, override_settings
+from django.utils import six
+from django.utils.encoding import force_bytes
+from django.utils.html import escape
+
+from .models import Article, ArticleProxy, Site
+
+
+@override_settings(ROOT_URLCONF="admin_utils.urls")
+class LogEntryTests(TestCase):
+    def setUp(self):
+        self.user = User.objects.create(
+            password='sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158',
+            is_superuser=True, username='super',
+            first_name='Super', last_name='User', email='super@example.com',
+            is_staff=True, is_active=True, date_joined=datetime(2007, 5, 30, 13, 20, 10)
+        )
+        self.site = Site.objects.create(domain='example.org')
+        self.a1 = Article.objects.create(
+            site=self.site,
+            title="Title",
+            created=datetime(2008, 3, 18, 11, 54, 58),
+        )
+        content_type_pk = ContentType.objects.get_for_model(Article).pk
+        LogEntry.objects.log_action(
+            self.user.pk, content_type_pk, self.a1.pk, repr(self.a1), CHANGE,
+            change_message='Changed something'
+        )
+        self.client.force_login(self.user)
+
+    def test_logentry_save(self):
+        """
+        LogEntry.action_time is a timestamp of the date when the entry was
+        created. It shouldn't be updated on a subsequent save().
+        """
+        logentry = LogEntry.objects.get(content_type__model__iexact="article")
+        action_time = logentry.action_time
+        logentry.save()
+        self.assertEqual(logentry.action_time, action_time)
+
+    def test_logentry_get_edited_object(self):
+        """
+        LogEntry.get_edited_object() returns the edited object of a LogEntry
+        object.
+        """
+        logentry = LogEntry.objects.get(content_type__model__iexact="article")
+        edited_obj = logentry.get_edited_object()
+        self.assertEqual(logentry.object_id, str(edited_obj.pk))
+
+    def test_logentry_get_admin_url(self):
+        """
+        LogEntry.get_admin_url returns a URL to edit the entry's object or
+        None for non-existent (possibly deleted) models.
+        """
+        logentry = LogEntry.objects.get(content_type__model__iexact='article')
+        expected_url = reverse('admin:admin_utils_article_change', args=(quote(self.a1.pk),))
+        self.assertEqual(logentry.get_admin_url(), expected_url)
+        self.assertIn('article/%d/change/' % self.a1.pk, logentry.get_admin_url())
+
+        logentry.content_type.model = "non-existent"
+        self.assertIsNone(logentry.get_admin_url())
+
+    def test_logentry_unicode(self):
+        log_entry = LogEntry()
+
+        log_entry.action_flag = ADDITION
+        self.assertTrue(six.text_type(log_entry).startswith('Added '))
+
+        log_entry.action_flag = CHANGE
+        self.assertTrue(six.text_type(log_entry).startswith('Changed '))
+
+        log_entry.action_flag = DELETION
+        self.assertTrue(six.text_type(log_entry).startswith('Deleted '))
+
+        # Make sure custom action_flags works
+        log_entry.action_flag = 4
+        self.assertEqual(six.text_type(log_entry), 'LogEntry Object')
+
+    def test_recentactions_without_content_type(self):
+        """
+        If a LogEntry is missing content_type it will not display it in span
+        tag under the hyperlink.
+        """
+        response = self.client.get(reverse('admin:index'))
+        link = reverse('admin:admin_utils_article_change', args=(quote(self.a1.pk),))
+        should_contain = """<a href="%s">%s</a>""" % (escape(link), escape(repr(self.a1)))
+        self.assertContains(response, should_contain)
+        should_contain = "Article"
+        self.assertContains(response, should_contain)
+        logentry = LogEntry.objects.get(content_type__model__iexact='article')
+        # If the log entry doesn't have a content type it should still be
+        # possible to view the Recent Actions part (#10275).
+        logentry.content_type = None
+        logentry.save()
+
+        counted_presence_before = response.content.count(force_bytes(should_contain))
+        response = self.client.get(reverse('admin:index'))
+        counted_presence_after = response.content.count(force_bytes(should_contain))
+        self.assertEqual(counted_presence_before - 1, counted_presence_after)
+
+    def test_proxy_model_content_type_is_used_for_log_entries(self):
+        """
+        Log entries for proxy models should have the proxy model's contenttype
+        (#21084).
+        """
+        proxy_content_type = ContentType.objects.get_for_model(ArticleProxy, for_concrete_model=False)
+        post_data = {
+            'site': self.site.pk, 'title': "Foo", 'title2': "Bar",
+            'created_0': '2015-12-25', 'created_1': '00:00',
+        }
+        changelist_url = reverse('admin:admin_utils_articleproxy_changelist')
+
+        # add
+        proxy_add_url = reverse('admin:admin_utils_articleproxy_add')
+        response = self.client.post(proxy_add_url, post_data)
+        self.assertRedirects(response, changelist_url)
+        proxy_addition_log = LogEntry.objects.latest('id')
+        self.assertEqual(proxy_addition_log.action_flag, ADDITION)
+        self.assertEqual(proxy_addition_log.content_type, proxy_content_type)
+
+        # change
+        article_id = proxy_addition_log.object_id
+        proxy_change_url = reverse('admin:admin_utils_articleproxy_change', args=(article_id,))
+        post_data['title'] = 'New'
+        response = self.client.post(proxy_change_url, post_data)
+        self.assertRedirects(response, changelist_url)
+        proxy_change_log = LogEntry.objects.latest('id')
+        self.assertEqual(proxy_change_log.action_flag, CHANGE)
+        self.assertEqual(proxy_change_log.content_type, proxy_content_type)
+
+        # delete
+        proxy_delete_url = reverse('admin:admin_utils_articleproxy_delete', args=(article_id,))
+        response = self.client.post(proxy_delete_url, {'post': 'yes'})
+        self.assertRedirects(response, changelist_url)
+        proxy_delete_log = LogEntry.objects.latest('id')
+        self.assertEqual(proxy_delete_log.action_flag, DELETION)
+        self.assertEqual(proxy_delete_log.content_type, proxy_content_type)

+ 0 - 27
tests/admin_utils/tests.py

@@ -5,7 +5,6 @@ from decimal import Decimal
 
 from django import forms
 from django.conf import settings
-from django.contrib import admin
 from django.contrib.admin import helpers
 from django.contrib.admin.utils import (
     NestedObjects, display_for_field, flatten, flatten_fieldsets,
@@ -13,7 +12,6 @@ from django.contrib.admin.utils import (
 )
 from django.db import DEFAULT_DB_ALIAS, models
 from django.test import SimpleTestCase, TestCase, override_settings
-from django.utils import six
 from django.utils.formats import localize
 from django.utils.safestring import mark_safe
 
@@ -303,31 +301,6 @@ class UtilsTests(SimpleTestCase):
             ('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 ')
-        )
-
-        # Make sure custom action_flags works
-        log_entry.action_flag = 4
-        self.assertEqual(six.text_type(log_entry), 'LogEntry Object')
-
     def test_safestring_in_field_label(self):
         # safestring should not be escaped
         class MyForm(forms.Form):

+ 7 - 0
tests/admin_utils/urls.py

@@ -0,0 +1,7 @@
+from django.conf.urls import url
+
+from .admin import site
+
+urlpatterns = [
+    url(r'^test_admin/admin/', site.urls),
+]

+ 13 - 97
tests/admin_views/tests.py

@@ -47,19 +47,19 @@ from .models import (
     Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField,
     AdminOrderedModelMethod, Answer, Article, BarAccount, Book, Bookmark,
     Category, Chapter, ChapterXtra1, ChapterXtra2, Character, Child, Choice,
-    City, Collector, Color, Color2, ComplexSortedPerson, CoverLetter,
-    CustomArticle, CyclicOne, CyclicTwo, DooHickey, Employee, EmptyModel,
-    ExternalSubscriber, Fabric, FancyDoodad, FieldOverridePost,
-    FilteredManager, FooAccount, FoodDelivery, FunkyTag, Gallery, Grommet,
-    Inquisition, Language, MainPrepopulated, ModelWithStringPrimaryKey,
-    OtherStory, Paper, Parent, ParentWithDependentChildren, Person, Persona,
-    Picture, Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post,
-    PrePopulatedPost, Promo, Question, Recommendation, Recommender,
-    RelatedPrepopulated, Report, Restaurant, RowLevelChangePermissionModel,
-    SecretHideout, Section, ShortMessage, Simple, State, Story, Subscriber,
-    SuperSecretHideout, SuperVillain, Telegram, TitleTranslation, Topping,
-    UnchangeableObject, UndeletableObject, UnorderedObject, Villain, Vodcast,
-    Whatsit, Widget, Worker, WorkHour,
+    City, Collector, Color, ComplexSortedPerson, CoverLetter, CustomArticle,
+    CyclicOne, CyclicTwo, DooHickey, Employee, EmptyModel, ExternalSubscriber,
+    Fabric, FancyDoodad, FieldOverridePost, FilteredManager, FooAccount,
+    FoodDelivery, FunkyTag, Gallery, Grommet, Inquisition, Language,
+    MainPrepopulated, ModelWithStringPrimaryKey, OtherStory, Paper, Parent,
+    ParentWithDependentChildren, Person, Persona, Picture, Pizza, Plot,
+    PlotDetails, PluggableSearchPerson, Podcast, Post, PrePopulatedPost, Promo,
+    Question, Recommendation, Recommender, RelatedPrepopulated, Report,
+    Restaurant, RowLevelChangePermissionModel, SecretHideout, Section,
+    ShortMessage, Simple, State, Story, Subscriber, SuperSecretHideout,
+    SuperVillain, Telegram, TitleTranslation, Topping, UnchangeableObject,
+    UndeletableObject, UnorderedObject, Villain, Vodcast, Whatsit, Widget,
+    Worker, WorkHour,
 )
 
 
@@ -809,38 +809,6 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
         self.assertIs(index_match.func.admin_site, customadmin.simple_site)
         self.assertIsInstance(list_match.func.model_admin, customadmin.CustomPwdTemplateUserAdmin)
 
-    def test_proxy_model_content_type_is_used_for_log_entries(self):
-        """
-        Log entries for proxy models should have the proxy model's content
-        type.
-
-        Regression test for #21084.
-        """
-        color2_content_type = ContentType.objects.get_for_model(Color2, for_concrete_model=False)
-
-        # add
-        color2_add_url = reverse('admin:admin_views_color2_add')
-        self.client.post(color2_add_url, {'value': 'orange'})
-
-        color2_addition_log = LogEntry.objects.all()[0]
-        self.assertEqual(color2_content_type, color2_addition_log.content_type)
-
-        # change
-        color_id = color2_addition_log.object_id
-        color2_change_url = reverse('admin:admin_views_color2_change', args=(color_id,))
-
-        self.client.post(color2_change_url, {'value': 'blue'})
-
-        color2_change_log = LogEntry.objects.all()[0]
-        self.assertEqual(color2_content_type, color2_change_log.content_type)
-
-        # delete
-        color2_delete_url = reverse('admin:admin_views_color2_delete', args=(color_id,))
-        self.client.post(color2_delete_url)
-
-        color2_delete_log = LogEntry.objects.all()[0]
-        self.assertEqual(color2_content_type, color2_delete_log.content_type)
-
     def test_adminsite_display_site_url(self):
         """
         #13749 - Admin should display link to front-end site 'View site'
@@ -2270,58 +2238,6 @@ class AdminViewStringPrimaryKeyTest(TestCase):
         should_contain = """<a href="%s">%s</a>""" % (escape(link), escape(self.pk))
         self.assertContains(response, should_contain)
 
-    def test_recentactions_without_content_type(self):
-        "If a LogEntry is missing content_type it will not display it in span tag under the hyperlink."
-        response = self.client.get(reverse('admin:index'))
-        link = reverse('admin:admin_views_modelwithstringprimarykey_change', args=(quote(self.pk),))
-        should_contain = """<a href="%s">%s</a>""" % (escape(link), escape(self.pk))
-        self.assertContains(response, should_contain)
-        should_contain = "Model with string primary key"  # capitalized in Recent Actions
-        self.assertContains(response, should_contain)
-        logentry = LogEntry.objects.get(content_type__model__iexact='modelwithstringprimarykey')
-        # http://code.djangoproject.com/ticket/10275
-        # if the log entry doesn't have a content type it should still be
-        # possible to view the Recent Actions part
-        logentry.content_type = None
-        logentry.save()
-
-        counted_presence_before = response.content.count(force_bytes(should_contain))
-        response = self.client.get(reverse('admin:index'))
-        counted_presence_after = response.content.count(force_bytes(should_contain))
-        self.assertEqual(counted_presence_before - 1,
-            counted_presence_after)
-
-    def test_logentry_get_admin_url(self):
-        """
-        LogEntry.get_admin_url returns a URL to edit the entry's object or
-        None for non-existent (possibly deleted) models.
-        """
-        log_entry_model = "modelwithstringprimarykey"  # capitalized in Recent Actions
-        logentry = LogEntry.objects.get(content_type__model__iexact=log_entry_model)
-        desired_admin_url = reverse('admin:admin_views_modelwithstringprimarykey_change', args=(quote(self.pk),))
-
-        self.assertEqual(logentry.get_admin_url(), desired_admin_url)
-        self.assertIn(iri_to_uri(quote(self.pk)), logentry.get_admin_url())
-
-        logentry.content_type.model = "non-existent"
-        self.assertEqual(logentry.get_admin_url(), None)
-
-    def test_logentry_get_edited_object(self):
-        "LogEntry.get_edited_object returns the edited object of a given LogEntry object"
-        logentry = LogEntry.objects.get(content_type__model__iexact="modelwithstringprimarykey")
-        edited_obj = logentry.get_edited_object()
-        self.assertEqual(logentry.object_id, str(edited_obj.pk))
-
-    def test_logentry_save(self):
-        """
-        LogEntry.action_time is a timestamp of the date when the entry was
-        created. It shouldn't be updated on a subsequent save().
-        """
-        logentry = LogEntry.objects.get(content_type__model__iexact="modelwithstringprimarykey")
-        action_time = logentry.action_time
-        logentry.save()
-        self.assertEqual(logentry.action_time, action_time)
-
     def test_deleteconfirmation_link(self):
         "The link from the delete confirmation page referring back to the changeform of the object should be quoted"
         url = reverse('admin:admin_views_modelwithstringprimarykey_delete', args=(quote(self.pk),))