1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

newforms-admin: Fixed #6117 -- Implemented change history for the admin. This includes the ability to track changes on a newform. Model formsets now only return the changed/new objects saved. A big thanks to Karen Tracey and Alex Gaynor.

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7507 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Brian Rosner 2008-04-29 05:45:46 +00:00
parent 95dcdf473e
commit abb7a7ff0f
4 changed files with 80 additions and 35 deletions

View File

@ -389,17 +389,26 @@ class ModelAdmin(BaseModelAdmin):
for formset in formsets:
formset.save()
# Construct the change message. TODO: Temporarily commented-out,
# as manipulator object doesn't exist anymore, and we don't yet
# have a way to get fields_added, fields_changed, fields_deleted.
# Construct the change message.
change_message = []
#if manipulator.fields_added:
#change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
#if manipulator.fields_changed:
#change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
#if manipulator.fields_deleted:
#change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
#change_message = ' '.join(change_message)
if form.changed_data:
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
if formsets:
for formset in formsets:
for added_object in formset.new_objects:
change_message.append(_('Added %s "%s".')
% (added_object._meta.verbose_name, added_object))
for changed_object, changed_fields in formset.changed_objects:
change_message.append(_('Changed %s for %s "%s".')
% (get_text_list(changed_fields, _('and')),
changed_object._meta.verbose_name,
changed_object))
for deleted_object in formset.deleted_objects:
change_message.append(_('Deleted %s "%s".')
% (deleted_object._meta.verbose_name, deleted_object))
change_message = ' '.join(change_message)
if not change_message:
change_message = _('No fields changed.')
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, force_unicode(new_object), CHANGE, change_message)

View File

@ -81,6 +81,7 @@ class BaseForm(StrAndUnicode):
self.label_suffix = label_suffix
self.empty_permitted = empty_permitted
self._errors = None # Stores the errors after clean() has been called.
self._changed_data = None
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
@ -243,19 +244,25 @@ class BaseForm(StrAndUnicode):
"""
Returns True if data differs from initial.
"""
# XXX: For now we're asking the individual widgets whether or not the
# data has changed. It would probably be more efficient to hash the
# initial data, store it in a hidden field, and compare a hash of the
# submitted data, but we'd need a way to easily get the string value
# for a given field. Right now, that logic is embedded in the render
# method of each widget.
for name, field in self.fields.items():
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
initial_value = self.initial.get(name, field.initial)
if field.widget._has_changed(initial_value, data_value):
return True
return False
return bool(self.changed_data)
def _get_changed_data(self):
if self._changed_data is None:
self._changed_data = []
# XXX: For now we're asking the individual widgets whether or not the
# data has changed. It would probably be more efficient to hash the
# initial data, store it in a hidden field, and compare a hash of the
# submitted data, but we'd need a way to easily get the string value
# for a given field. Right now, that logic is embedded in the render
# method of each widget.
for name, field in self.fields.items():
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
initial_value = self.initial.get(name, field.initial)
if field.widget._has_changed(initial_value, data_value):
self._changed_data.append(name)
return self._changed_data
changed_data = property(_get_changed_data)
def _get_media(self):
"""

View File

@ -328,8 +328,11 @@ class BaseModelFormSet(BaseFormSet):
return self.save_existing_objects(commit) + self.save_new_objects(commit)
def save_existing_objects(self, commit=True):
self.changed_objects = []
self.deleted_objects = []
if not self.get_queryset():
return []
# Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
existing_objects = {}
for obj in self.get_queryset():
@ -338,23 +341,25 @@ class BaseModelFormSet(BaseFormSet):
for form in self.initial_forms:
obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
self.deleted_objects.append(obj)
obj.delete()
else:
saved_instances.append(self.save_existing(form, obj, commit=commit))
if form.changed_data:
self.changed_objects.append((obj, form.changed_data))
saved_instances.append(self.save_existing(form, obj, commit=commit))
return saved_instances
def save_new_objects(self, commit=True):
new_objects = []
self.new_objects = []
for form in self.extra_forms:
if not form.has_changed():
continue
# If someone has marked an add form for deletion, don't save the
# object. At some point it would be nice if we didn't display
# the deletion widget for add forms.
# object.
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
continue
new_objects.append(self.save_new(form, commit=commit))
return new_objects
self.new_objects.append(self.save_new(form, commit=commit))
return self.new_objects
def add_fields(self, form, index):
"""Add a hidden field for the object's primary key."""

View File

@ -50,9 +50,9 @@ Charles Baudelaire
Gah! We forgot Paul Verlaine. Let's create a formset to edit the existing
authors with an extra form to add him. This time we'll use formset_for_queryset.
We *could* use formset_for_queryset to restrict the Author objects we edit,
but in that case we'll use it to display them in alphabetical order by name.
authors with an extra form to add him. We *could* pass in a queryset to
restrict the Author objects we edit, but in this case we'll use it to display
them in alphabetical order by name.
>>> qs = Author.objects.order_by('name')
>>> AuthorFormSet = _modelformset_factory(Author, extra=1, can_delete=False)
@ -79,8 +79,9 @@ but in that case we'll use it to display them in alphabetical order by name.
>>> formset.is_valid()
True
# Only changed or new objects are returned from formset.save()
>>> formset.save()
[<Author: Arthur Rimbaud>, <Author: Charles Baudelaire>, <Author: Paul Verlaine>]
[<Author: Paul Verlaine>]
>>> for author in Author.objects.order_by('name'):
... print author.name
@ -124,8 +125,9 @@ deltetion, make sure we don't save that form.
>>> formset.is_valid()
True
# No objects were changed or saved so nothing will come back.
>>> formset.save()
[<Author: Arthur Rimbaud>, <Author: Charles Baudelaire>, <Author: Paul Verlaine>]
[]
>>> for author in Author.objects.order_by('name'):
... print author.name
@ -133,6 +135,28 @@ Arthur Rimbaud
Charles Baudelaire
Paul Verlaine
Let's edit a record to ensure save only returns that one record.
>>> data = {
... 'form-TOTAL_FORMS': '4', # the number of forms rendered
... 'form-INITIAL_FORMS': '3', # the number of forms with initial data
... 'form-0-id': '2',
... 'form-0-name': 'Walt Whitman',
... 'form-1-id': '1',
... 'form-1-name': 'Charles Baudelaire',
... 'form-2-id': '3',
... 'form-2-name': 'Paul Verlaine',
... 'form-3-name': '',
... 'form-3-DELETE': '',
... }
>>> formset = AuthorFormSet(data=data, queryset=qs)
>>> formset.is_valid()
True
# One record has changed.
>>> formset.save()
[<Author: Walt Whitman>]
# Inline Formsets ############################################################
@ -199,7 +223,7 @@ book.
True
>>> formset.save()
[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
[<Book: Le Spleen de Paris>]
As you can see, 'Le Spleen de Paris' is now a book belonging to Charles Baudelaire.