mirror of
https://github.com/django/django.git
synced 2025-07-06 10:49:17 +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:
|
for formset in formsets:
|
||||||
formset.save()
|
formset.save()
|
||||||
|
|
||||||
# Construct the change message. TODO: Temporarily commented-out,
|
# Construct the change message.
|
||||||
# as manipulator object doesn't exist anymore, and we don't yet
|
|
||||||
# have a way to get fields_added, fields_changed, fields_deleted.
|
|
||||||
change_message = []
|
change_message = []
|
||||||
#if manipulator.fields_added:
|
if form.changed_data:
|
||||||
#change_message.append(_('Added %s.') % get_text_list(manipulator.fields_added, _('and')))
|
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
|
||||||
#if manipulator.fields_changed:
|
|
||||||
#change_message.append(_('Changed %s.') % get_text_list(manipulator.fields_changed, _('and')))
|
if formsets:
|
||||||
#if manipulator.fields_deleted:
|
for formset in formsets:
|
||||||
#change_message.append(_('Deleted %s.') % get_text_list(manipulator.fields_deleted, _('and')))
|
for added_object in formset.new_objects:
|
||||||
#change_message = ' '.join(change_message)
|
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:
|
if not change_message:
|
||||||
change_message = _('No fields changed.')
|
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)
|
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.label_suffix = label_suffix
|
||||||
self.empty_permitted = empty_permitted
|
self.empty_permitted = empty_permitted
|
||||||
self._errors = None # Stores the errors after clean() has been called.
|
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
|
# The base_fields class attribute is the *class-wide* definition of
|
||||||
# fields. Because a particular *instance* of the class might want to
|
# 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.
|
Returns True if data differs from initial.
|
||||||
"""
|
"""
|
||||||
# XXX: For now we're asking the individual widgets whether or not the
|
return bool(self.changed_data)
|
||||||
# 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
|
def _get_changed_data(self):
|
||||||
# submitted data, but we'd need a way to easily get the string value
|
if self._changed_data is None:
|
||||||
# for a given field. Right now, that logic is embedded in the render
|
self._changed_data = []
|
||||||
# method of each widget.
|
# XXX: For now we're asking the individual widgets whether or not the
|
||||||
for name, field in self.fields.items():
|
# data has changed. It would probably be more efficient to hash the
|
||||||
prefixed_name = self.add_prefix(name)
|
# initial data, store it in a hidden field, and compare a hash of the
|
||||||
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
|
# submitted data, but we'd need a way to easily get the string value
|
||||||
initial_value = self.initial.get(name, field.initial)
|
# for a given field. Right now, that logic is embedded in the render
|
||||||
if field.widget._has_changed(initial_value, data_value):
|
# method of each widget.
|
||||||
return True
|
for name, field in self.fields.items():
|
||||||
return False
|
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):
|
def _get_media(self):
|
||||||
"""
|
"""
|
||||||
|
@ -328,8 +328,11 @@ class BaseModelFormSet(BaseFormSet):
|
|||||||
return self.save_existing_objects(commit) + self.save_new_objects(commit)
|
return self.save_existing_objects(commit) + self.save_new_objects(commit)
|
||||||
|
|
||||||
def save_existing_objects(self, commit=True):
|
def save_existing_objects(self, commit=True):
|
||||||
|
self.changed_objects = []
|
||||||
|
self.deleted_objects = []
|
||||||
if not self.get_queryset():
|
if not self.get_queryset():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
|
# Put the objects from self.get_queryset into a dict so they are easy to lookup by pk
|
||||||
existing_objects = {}
|
existing_objects = {}
|
||||||
for obj in self.get_queryset():
|
for obj in self.get_queryset():
|
||||||
@ -338,23 +341,25 @@ class BaseModelFormSet(BaseFormSet):
|
|||||||
for form in self.initial_forms:
|
for form in self.initial_forms:
|
||||||
obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
|
obj = existing_objects[form.cleaned_data[self.model._meta.pk.attname]]
|
||||||
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
|
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
|
||||||
|
self.deleted_objects.append(obj)
|
||||||
obj.delete()
|
obj.delete()
|
||||||
else:
|
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
|
return saved_instances
|
||||||
|
|
||||||
def save_new_objects(self, commit=True):
|
def save_new_objects(self, commit=True):
|
||||||
new_objects = []
|
self.new_objects = []
|
||||||
for form in self.extra_forms:
|
for form in self.extra_forms:
|
||||||
if not form.has_changed():
|
if not form.has_changed():
|
||||||
continue
|
continue
|
||||||
# If someone has marked an add form for deletion, don't save the
|
# 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
|
# object.
|
||||||
# the deletion widget for add forms.
|
|
||||||
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
|
if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
|
||||||
continue
|
continue
|
||||||
new_objects.append(self.save_new(form, commit=commit))
|
self.new_objects.append(self.save_new(form, commit=commit))
|
||||||
return new_objects
|
return self.new_objects
|
||||||
|
|
||||||
def add_fields(self, form, index):
|
def add_fields(self, form, index):
|
||||||
"""Add a hidden field for the object's primary key."""
|
"""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
|
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.
|
authors with an extra form to add him. We *could* pass in a queryset to
|
||||||
We *could* use formset_for_queryset to restrict the Author objects we edit,
|
restrict the Author objects we edit, but in this case we'll use it to display
|
||||||
but in that case we'll use it to display them in alphabetical order by name.
|
them in alphabetical order by name.
|
||||||
|
|
||||||
>>> qs = Author.objects.order_by('name')
|
>>> qs = Author.objects.order_by('name')
|
||||||
>>> AuthorFormSet = _modelformset_factory(Author, extra=1, can_delete=False)
|
>>> 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()
|
>>> formset.is_valid()
|
||||||
True
|
True
|
||||||
|
|
||||||
|
# Only changed or new objects are returned from formset.save()
|
||||||
>>> formset.save()
|
>>> formset.save()
|
||||||
[<Author: Arthur Rimbaud>, <Author: Charles Baudelaire>, <Author: Paul Verlaine>]
|
[<Author: Paul Verlaine>]
|
||||||
|
|
||||||
>>> for author in Author.objects.order_by('name'):
|
>>> for author in Author.objects.order_by('name'):
|
||||||
... print author.name
|
... print author.name
|
||||||
@ -124,8 +125,9 @@ deltetion, make sure we don't save that form.
|
|||||||
>>> formset.is_valid()
|
>>> formset.is_valid()
|
||||||
True
|
True
|
||||||
|
|
||||||
|
# No objects were changed or saved so nothing will come back.
|
||||||
>>> formset.save()
|
>>> formset.save()
|
||||||
[<Author: Arthur Rimbaud>, <Author: Charles Baudelaire>, <Author: Paul Verlaine>]
|
[]
|
||||||
|
|
||||||
>>> for author in Author.objects.order_by('name'):
|
>>> for author in Author.objects.order_by('name'):
|
||||||
... print author.name
|
... print author.name
|
||||||
@ -133,6 +135,28 @@ Arthur Rimbaud
|
|||||||
Charles Baudelaire
|
Charles Baudelaire
|
||||||
Paul Verlaine
|
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 ############################################################
|
# Inline Formsets ############################################################
|
||||||
|
|
||||||
@ -199,7 +223,7 @@ book.
|
|||||||
True
|
True
|
||||||
|
|
||||||
>>> formset.save()
|
>>> 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.
|
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