Index: django/newforms/models.py
===================================================================
--- django/newforms/models.py	(revision 7321)
+++ django/newforms/models.py	(working copy)
@@ -325,8 +325,11 @@
         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():
@@ -335,13 +338,16 @@
         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
@@ -350,8 +356,8 @@
             # the deletion widget for add forms.
             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."""
Index: django/newforms/forms.py
===================================================================
--- django/newforms/forms.py	(revision 7321)
+++ django/newforms/forms.py	(working copy)
@@ -81,6 +81,7 @@
         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
@@ -257,6 +258,18 @@
                 #print field
                 return True
         return False
+    
+    def _get_changed_data(self):
+        if self._changed_data is None:
+            self._changed_data = []
+            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):
         """
Index: django/contrib/admin/options.py
===================================================================
--- django/contrib/admin/options.py	(revision 7321)
+++ django/contrib/admin/options.py	(working copy)
@@ -389,17 +389,26 @@
             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)
