mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #27998 -- Made ManyToManyField changes logged in admin's object history.
This commit is contained in:
parent
451b585c2f
commit
15b465c584
1
AUTHORS
1
AUTHORS
@ -468,6 +468,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Lex Berezhny <lex@damoti.com>
|
Lex Berezhny <lex@damoti.com>
|
||||||
Liang Feng <hutuworm@gmail.com>
|
Liang Feng <hutuworm@gmail.com>
|
||||||
limodou
|
limodou
|
||||||
|
Lincoln Smith <lincoln.smith@anu.edu.au>
|
||||||
Loek van Gent <loek@barakken.nl>
|
Loek van Gent <loek@barakken.nl>
|
||||||
Loïc Bistuer <loic.bistuer@sixmedia.com>
|
Loïc Bistuer <loic.bistuer@sixmedia.com>
|
||||||
Lowe Thiderman <lowe.thiderman@gmail.com>
|
Lowe Thiderman <lowe.thiderman@gmail.com>
|
||||||
|
@ -1442,6 +1442,10 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
new_object = form.instance
|
new_object = form.instance
|
||||||
formsets, inline_instances = self._create_formsets(request, new_object, change=not add)
|
formsets, inline_instances = self._create_formsets(request, new_object, change=not add)
|
||||||
if all_valid(formsets) and form_validated:
|
if all_valid(formsets) and form_validated:
|
||||||
|
if not add:
|
||||||
|
# Evalute querysets in form.initial so that changes to
|
||||||
|
# ManyToManyFields are reflected in this change's LogEntry.
|
||||||
|
form.has_changed()
|
||||||
self.save_model(request, new_object, form, not add)
|
self.save_model(request, new_object, form, not add)
|
||||||
self.save_related(request, form, formsets, not add)
|
self.save_related(request, form, formsets, not add)
|
||||||
change_message = self.construct_change_message(request, form, formsets, add)
|
change_message = self.construct_change_message(request, form, formsets, add)
|
||||||
|
@ -35,14 +35,14 @@ from .models import (
|
|||||||
OtherStory, Paper, Parent, ParentWithDependentChildren, ParentWithUUIDPK,
|
OtherStory, Paper, Parent, ParentWithDependentChildren, ParentWithUUIDPK,
|
||||||
Person, Persona, Picture, Pizza, Plot, PlotDetails, PlotProxy,
|
Person, Persona, Picture, Pizza, Plot, PlotDetails, PlotProxy,
|
||||||
PluggableSearchPerson, Podcast, Post, PrePopulatedPost,
|
PluggableSearchPerson, Podcast, Post, PrePopulatedPost,
|
||||||
PrePopulatedPostLargeSlug, PrePopulatedSubPost, Promo, Question, Recipe,
|
PrePopulatedPostLargeSlug, PrePopulatedSubPost, Promo, Question,
|
||||||
Recommendation, Recommender, ReferencedByGenRel, ReferencedByInline,
|
ReadablePizza, Recipe, Recommendation, Recommender, ReferencedByGenRel,
|
||||||
ReferencedByParent, RelatedPrepopulated, RelatedWithUUIDPKModel, Report,
|
ReferencedByInline, ReferencedByParent, RelatedPrepopulated,
|
||||||
Reservation, Restaurant, RowLevelChangePermissionModel, Section,
|
RelatedWithUUIDPKModel, Report, Reservation, Restaurant,
|
||||||
ShortMessage, Simple, Sketch, State, Story, StumpJoke, Subscriber,
|
RowLevelChangePermissionModel, Section, ShortMessage, Simple, Sketch,
|
||||||
SuperVillain, Telegram, Thing, Topping, UnchangeableObject,
|
State, Story, StumpJoke, Subscriber, SuperVillain, Telegram, Thing,
|
||||||
UndeletableObject, UnorderedObject, UserMessenger, Villain, Vodcast,
|
Topping, UnchangeableObject, UndeletableObject, UnorderedObject,
|
||||||
Whatsit, Widget, Worker, WorkHour,
|
UserMessenger, Villain, Vodcast, Whatsit, Widget, Worker, WorkHour,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -970,6 +970,7 @@ site.register(Book, inlines=[ChapterInline])
|
|||||||
site.register(Promo)
|
site.register(Promo)
|
||||||
site.register(ChapterXtra1, ChapterXtra1Admin)
|
site.register(ChapterXtra1, ChapterXtra1Admin)
|
||||||
site.register(Pizza, PizzaAdmin)
|
site.register(Pizza, PizzaAdmin)
|
||||||
|
site.register(ReadablePizza)
|
||||||
site.register(Topping, ToppingAdmin)
|
site.register(Topping, ToppingAdmin)
|
||||||
site.register(Album, AlbumAdmin)
|
site.register(Album, AlbumAdmin)
|
||||||
site.register(Question)
|
site.register(Question)
|
||||||
|
@ -575,6 +575,13 @@ class Pizza(models.Model):
|
|||||||
toppings = models.ManyToManyField('Topping', related_name='pizzas')
|
toppings = models.ManyToManyField('Topping', related_name='pizzas')
|
||||||
|
|
||||||
|
|
||||||
|
# Pizza's ModelAdmin has readonly_fields = ['toppings'].
|
||||||
|
# toppings is editable for this model's admin.
|
||||||
|
class ReadablePizza(Pizza):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
|
||||||
class Album(models.Model):
|
class Album(models.Model):
|
||||||
owner = models.ForeignKey(User, models.SET_NULL, null=True, blank=True)
|
owner = models.ForeignKey(User, models.SET_NULL, null=True, blank=True)
|
||||||
title = models.CharField(max_length=30)
|
title = models.CharField(max_length=30)
|
||||||
|
@ -54,12 +54,13 @@ from .models import (
|
|||||||
ModelWithStringPrimaryKey, OtherStory, Paper, Parent,
|
ModelWithStringPrimaryKey, OtherStory, Paper, Parent,
|
||||||
ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture,
|
ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture,
|
||||||
Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post,
|
Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post,
|
||||||
PrePopulatedPost, Promo, Question, Recommendation, Recommender,
|
PrePopulatedPost, Promo, Question, ReadablePizza, Recommendation,
|
||||||
RelatedPrepopulated, RelatedWithUUIDPKModel, Report, Restaurant,
|
Recommender, RelatedPrepopulated, RelatedWithUUIDPKModel, Report,
|
||||||
RowLevelChangePermissionModel, SecretHideout, Section, ShortMessage,
|
Restaurant, RowLevelChangePermissionModel, SecretHideout, Section,
|
||||||
Simple, State, Story, Subscriber, SuperSecretHideout, SuperVillain,
|
ShortMessage, Simple, State, Story, Subscriber, SuperSecretHideout,
|
||||||
Telegram, TitleTranslation, Topping, UnchangeableObject, UndeletableObject,
|
SuperVillain, Telegram, TitleTranslation, Topping, UnchangeableObject,
|
||||||
UnorderedObject, Villain, Vodcast, Whatsit, Widget, Worker, WorkHour,
|
UndeletableObject, UnorderedObject, Villain, Vodcast, Whatsit, Widget,
|
||||||
|
Worker, WorkHour,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -876,6 +877,17 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
|||||||
response = self.client.get(reverse('admin:admin_views_undeletableobject_change', args=(instance.pk,)))
|
response = self.client.get(reverse('admin:admin_views_undeletableobject_change', args=(instance.pk,)))
|
||||||
self.assertNotContains(response, 'deletelink')
|
self.assertNotContains(response, 'deletelink')
|
||||||
|
|
||||||
|
def test_change_view_logs_m2m_field_changes(self):
|
||||||
|
"""Changes to ManyToManyFields are included in the object's history."""
|
||||||
|
pizza = ReadablePizza.objects.create(name='Cheese')
|
||||||
|
cheese = Topping.objects.create(name='cheese')
|
||||||
|
post_data = {'name': pizza.name, 'toppings': [cheese.pk]}
|
||||||
|
response = self.client.post(reverse('admin:admin_views_readablepizza_change', args=(pizza.pk,)), post_data)
|
||||||
|
self.assertRedirects(response, reverse('admin:admin_views_readablepizza_changelist'))
|
||||||
|
pizza_ctype = ContentType.objects.get_for_model(ReadablePizza, for_concrete_model=False)
|
||||||
|
log = LogEntry.objects.filter(content_type=pizza_ctype, object_id=pizza.pk).first()
|
||||||
|
self.assertEqual(log.get_change_message(), 'Changed toppings.')
|
||||||
|
|
||||||
def test_allows_attributeerror_to_bubble_up(self):
|
def test_allows_attributeerror_to_bubble_up(self):
|
||||||
"""
|
"""
|
||||||
AttributeErrors are allowed to bubble when raised inside a change list
|
AttributeErrors are allowed to bubble when raised inside a change list
|
||||||
|
Loading…
Reference in New Issue
Block a user