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:
parent
95dcdf473e
commit
abb7a7ff0f
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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."""
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user