diff --git a/django/contrib/admin/templates/admin/submit_line.html b/django/contrib/admin/templates/admin/submit_line.html
index 25f581963e..cf6376eb21 100644
--- a/django/contrib/admin/templates/admin/submit_line.html
+++ b/django/contrib/admin/templates/admin/submit_line.html
@@ -1,6 +1,6 @@
{% load i18n %}
-{% if show_delete_link %}
{% trans "Delete" %}
{% endif %}
+{% if show_delete_link %}
{% trans "Delete" %}
{% endif %}
{% if show_save_as_new %}
{%endif%}
{% if show_save_and_add_another %}
{% endif %}
{% if show_save_and_continue %}
{% endif %}
diff --git a/django/contrib/admin/views/stages/delete.py b/django/contrib/admin/views/stages/delete.py
index 9ec51ffe9a..cd4714e8c4 100644
--- a/django/contrib/admin/views/stages/delete.py
+++ b/django/contrib/admin/views/stages/delete.py
@@ -1,3 +1,15 @@
+from django.contrib.admin.views.decorators import staff_member_required
+from django.contrib.admin.views.main import get_model_and_app
+from django.core.extensions import get_object_or_404,render_to_response
+from django.core.extensions import DjangoContext as Context
+from django.utils.text import capfirst
+from django.utils.html import escape, strip_tags
+from django.db import models
+try:
+ from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
+except ImportError:
+ raise ImproperlyConfigured, "You don't have 'django.contrib.admin' in INSTALLED_APPS."
+from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
def _nest_help(obj, depth, val):
current = obj
@@ -83,12 +95,14 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
if not user.has_perm(p):
perms_needed.add(related.opts.verbose_name)
-def delete_stage(request, app_label, module_name, object_id):
+def delete_stage(request, path, object_id):
import sets
- mod, opts = _get_mod_opts(app_label, module_name)
+ #mod, opts = _get_mod_opts(app_label, module_name)
+ model, app_label = get_model_and_app(path)
+ opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
raise PermissionDenied
- obj = get_object_or_404(mod, pk=object_id)
+ obj = get_object_or_404(model, pk=object_id)
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
diff --git a/django/db/models/base.py b/django/db/models/base.py
index d6c61b5129..192299fed0 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -54,10 +54,10 @@ class ModelBase(type):
def cmp_cls(x, y):
for field in x._meta.fields:
- if field.rel and field.null and field.rel.to == y:
+ if field.rel and not field.null and field.rel.to == y:
return -1
for field in y._meta.fields:
- if field.rel and field.null and field.rel.to == x:
+ if field.rel and not field.null and field.rel.to == x:
return 1
return 0
@@ -196,14 +196,12 @@ class Model(object):
def __get_pk_val(self):
return str(getattr(self, self._meta.pk.attname))
- def __collect_sub_objects(self, seen_objs, ignore_objs):
+ def __collect_sub_objects(self, seen_objs):
pk_val = self.__get_pk_val()
- key = (self.__class__, pk_val)
-
- if key in seen_objs or key in ignore_objs:
+ if pk_val in seen_objs.setdefault(self.__class__, {}):
return
- seen_objs[key] = self
+ seen_objs.setdefault(self.__class__, {})[pk_val] = (self, True)
for related in self._meta.get_all_related_objects():
rel_opts_name = related.get_method_name_part()
@@ -213,64 +211,74 @@ class Model(object):
except ObjectDoesNotExist:
pass
else:
- sub_obj.__collect_sub_objects(seen_objs, ignore_objs)
+ sub_obj.__collect_sub_objects(seen_objs)
else:
for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
- sub_obj.__collect_sub_objects(seen_objs, ignore_objs)
+ sub_obj.__collect_sub_objects(seen_objs)
def delete(self, ignore_objects=None):
assert getattr(self, self._meta.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID."
- ignore_objects = ignore_objects and dict([(o.__class__,o.__get_pk_val()) for o in ignore_objects]) or {}
+ seen_objs = {}
+ if ignore_objects:
+ for obj in ignore_objects:
+ ignore_objs.setdefault(self.__class__,{})[obj.__get_pk_val()] = (obj, False)
seen_objs = {}
- self.__collect_sub_objects(seen_objs, ignore_objects)
+ self.__collect_sub_objects(seen_objs)
- seen_cls = set([cls for cls,pk in seen_objs.keys()])
+ #TODO: create a total class ordering rather than this sorting, which is
+ # only a partial ordering, and also is done each delete..
+ seen_cls = set(seen_objs.keys())
cls_order = list(seen_cls)
cls_order.sort(cmp_cls)
-
- seen_tups = [ (cls, pk_val, instance) for (cls, pk_val),instance in seen_objs.items() ]
- seen_tups.sort(lambda x,y: cmp(cls_order.index(x[0]), cls_order.index(y[0])))
-
+
cursor = connection.cursor()
-
- for cls, pk_val, instance in seen_tups:
-
- # Run any pre-delete hooks.
- if hasattr(instance, '_pre_delete'):
- instance._pre_delete()
-
- dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance)
-
- for related in cls._meta.get_all_related_many_to_many_objects():
- cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
- (backend.quote_name(related.field.get_m2m_db_table(related.opts)),
- backend.quote_name(cls._meta.object_name.lower() + '_id')),
- [pk_val])
- for f in cls._meta.many_to_many:
- cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
- (backend.quote_name(f.get_m2m_db_table(cls._meta)),
- backend.quote_name(cls._meta.object_name.lower() + '_id')),
- [pk_val])
- for field in cls._meta.fields:
- if field.rel and field.null and field.rel.to in seen_cls:
- cursor.execute("UPDATE %s SET %s = NULL WHERE %s=%%s" % \
- (backend.quote_name(cls._meta.db_table), backend.quote_name(field.column),
- backend.quote_name(cls._meta.pk.column)), [pk_val])
-
- seen_tups.reverse()
-
- for cls, pk_val, instance in seen_tups:
- cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
- (backend.quote_name(cls._meta.db_table), backend.quote_name(cls._meta.pk.column)),
- [pk_val])
-
- setattr(self, cls._meta.pk.attname, None)
-
- dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance)
-
- if hasattr(instance, '_post_delete'):
- instance._post_delete()
+
+ for cls in cls_order:
+ seen_objs[cls] = seen_objs[cls].items()
+ seen_objs[cls].sort()
+ for pk_val,(instance, do_delete) in seen_objs[cls]:
+
+ # Run any pre-delete hooks.
+ if do_delete:
+ if hasattr(instance, '_pre_delete'):
+ instance._pre_delete()
+
+ dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance)
+
+ for related in cls._meta.get_all_related_many_to_many_objects():
+ cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
+ (backend.quote_name(related.field.get_m2m_db_table(related.opts)),
+ backend.quote_name(cls._meta.object_name.lower() + '_id')),
+ [pk_val])
+ for f in cls._meta.many_to_many:
+ cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
+ (backend.quote_name(f.get_m2m_db_table(cls._meta)),
+ backend.quote_name(cls._meta.object_name.lower() + '_id')),
+ [pk_val])
+
+ for field in cls._meta.fields:
+ if field.rel and field.null and field.rel.to in seen_cls:
+ cursor.execute("UPDATE %s SET %s = NULL WHERE %s=%%s" % \
+ (backend.quote_name(cls._meta.db_table), backend.quote_name(field.column),
+ backend.quote_name(cls._meta.pk.column)), [pk_val])
+ setattr(instance, field.attname, None)
+
+ for cls in cls_order:
+ seen_objs[cls].reverse()
+
+ for pk_val, (instance, do_delete) in seen_objs[cls]:
+ if do_delete:
+ cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
+ (backend.quote_name(cls._meta.db_table), backend.quote_name(cls._meta.pk.column)),
+ [pk_val])
+
+ setattr(instance, cls._meta.pk.attname, None)
+
+ dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance)
+
+ if hasattr(instance, '_post_delete'):
+ instance._post_delete()
connection.commit()
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 1698f97362..d17a5b5cf9 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -213,6 +213,12 @@ class Field(object):
field_objs = self.get_manipulator_field_objs()
return (field_objs,params)
+ def get_fields_and_manipulators(self, opts, manipulator, follow ):
+ change = manipulator.change
+ rel = manipulator.name_prefix != ''
+ name_prefix = manipulator.name_prefix
+ return (self.get_manipulator_fields(opts, manipulator, change,name_prefix, rel, follow), [] )
+
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
"""
Returns a list of formfields.FormField instances for this field. It
diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py
index 8ad1a62ab7..a66bd5a31f 100644
--- a/django/db/models/manipulators.py
+++ b/django/db/models/manipulators.py
@@ -20,9 +20,6 @@ dispatcher.connect(
)
class ManipulatorDescriptor(object):
- class empty:
- pass
-
def __init__(self, name, base):
self.man = None
self.name = name
@@ -62,15 +59,32 @@ class AutomaticManipulator(Manipulator):
setattr(other_cls, name, ManipulatorDescriptor(name, cls))
contribute_to_class = classmethod(contribute_to_class)
- def __init__(self, original_object=None, follow=None):
- self.follow = self.model._meta.get_follow(follow)
- self.fields = []
+ def __init__(self, original_object=None, follow=None, name_prefix=''):
+ if name_prefix == '':
+ self.follow = self.model._meta.get_follow(follow)
+ else:
+ self.follow = follow
+ self.fields_, self.children = [], []
self.original_object = original_object
+ self.name_prefix = name_prefix
for f in self.opts.get_data_holders(self.follow):
fol = self.follow[f.name]
- fields = f.get_manipulator_fields(self.opts, self, self.change, follow=fol)
- self.fields.extend(fields)
-
+ fields,manipulators = f.get_fields_and_manipulators(self.opts, self, follow=fol)
+ #fields = f.get_manipulator_fields(self.opts, self, self.change, follow=fol)
+ self.fields_.extend(fields)
+ self.children.append(manipulators)
+ #print self.fields_
+
+ def get_fields(self):
+ l = list(self.fields_)
+ for child in self.children:
+ for manip in child:
+ l.extend(manip.fields)
+ #print l
+ return l
+
+ fields = property(get_fields)
+
def save(self, new_data):
add, change, opts, klass = self.add, self.change, self.opts, self.model
# TODO: big cleanup when core fields go -> use recursive manipulators.
@@ -233,37 +247,42 @@ class AutomaticManipulator(Manipulator):
class ModelAddManipulator(AutomaticManipulator):
change = False
add = True
- def __init__(self, follow=None):
- super(ModelAddManipulator, self).__init__(follow=follow)
+ def __init__(self, follow=None, name_prefix=''):
+ super(ModelAddManipulator, self).__init__(follow=follow, name_prefix=name_prefix)
class ModelChangeManipulator(AutomaticManipulator):
change = True
add = False
- def __init__(self, obj_key=None, follow=None):
+ def __init__(self, obj_key=None, follow=None, name_prefix=''):
assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
- self.obj_key = obj_key
- try:
- original_object = self.__class__.manager.get_object(pk=obj_key)
- except ObjectDoesNotExist:
- # If the object doesn't exist, this might be a manipulator for a
- # one-to-one related object that hasn't created its subobject yet.
- # For example, this might be a Restaurant for a Place that doesn't
- # yet have restaurant information.
- if opts.one_to_one_field:
- # Sanity check -- Make sure the "parent" object exists.
- # For example, make sure the Place exists for the Restaurant.
- # Let the ObjectDoesNotExist exception propogate up.
- lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
- lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
- null = opts.one_to_one_field.rel.to._meta.get_model_module().get_object(**lookup_kwargs)
- params = dict([(f.attname, f.get_default()) for f in opts.fields])
- params[opts.pk.attname] = obj_key
- original_object = opts.get_model_module().Klass(**params)
- else:
- raise
- super(ModelChangeManipulator, self).__init__(original_object=original_object, follow=follow)
- self.original_object = original_object
+ if isinstance(obj_key, self.model):
+ original_object = obj_key
+ self.obj_key = getattr(original_object, self.model._meta.pk.attname)
+ else:
+ self.obj_key = obj_key
+ try:
+ original_object = self.manager.get_object(pk=obj_key)
+ except ObjectDoesNotExist:
+ # If the object doesn't exist, this might be a manipulator for a
+ # one-to-one related object that hasn't created its subobject yet.
+ # For example, this might be a Restaurant for a Place that doesn't
+ # yet have restaurant information.
+ if opts.one_to_one_field:
+ # Sanity check -- Make sure the "parent" object exists.
+ # For example, make sure the Place exists for the Restaurant.
+ # Let the ObjectDoesNotExist exception propogate up.
+ lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
+ lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
+ null = opts.one_to_one_field.rel.to._meta.get_model_module().get_object(**lookup_kwargs)
+ params = dict([(f.attname, f.get_default()) for f in opts.fields])
+ params[opts.pk.attname] = obj_key
+ original_object = opts.get_model_module().Klass(**params)
+ else:
+ raise
+
+ super(ModelChangeManipulator, self).__init__(original_object=original_object, follow=follow, name_prefix=name_prefix)
+ #self.original_object = original_object
if self.opts.get_ordered_objects():
self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 4f3b3e4e8b..b2c94350ae 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -161,7 +161,7 @@ class Options:
else:
child_override = None
fol = f.get_follow(child_override)
- if fol:
+ if fol != None:
follow[f.name] = fol
return follow
diff --git a/django/db/models/related.py b/django/db/models/related.py
index 63df71ab0f..14256e5f43 100644
--- a/django/db/models/related.py
+++ b/django/db/models/related.py
@@ -46,24 +46,9 @@ class RelatedObject(object):
if parent_instance != None:
func_name = 'get_%s_list' % self.get_method_name_part()
func = getattr(parent_instance, func_name)
- list = func()
-
- count = len(list) + self.field.rel.num_extra_on_change
- if self.field.rel.min_num_in_admin:
- count = max(count, self.field.rel.min_num_in_admin)
- if self.field.rel.max_num_in_admin:
- count = min(count, self.field.rel.max_num_in_admin)
-
- change = count - len(list)
- if change > 0:
- return list + [None for i in range(change)]
- if change < 0:
- return list[:change]
- else: # Just right
- return list
+ return func()
else:
- return [None for i in range(self.field.rel.num_in_admin)]
-
+ return []
def editable_fields(self):
"Get the fields in this class that should be edited inline."
@@ -85,13 +70,30 @@ class RelatedObject(object):
over[self.field.name] = False
return self.opts.get_follow(over)
-
+
def __repr__(self):
return "
" % (self.name, self.field.name)
- def get_manipulator_fields(self, opts, manipulator, change, follow):
- # TODO: Remove core fields stuff.
+ def get_fields_and_manipulators(self, opts, manipulator, follow ):
+ return ([], self.get_manipulators(manipulator, follow) )
+ def get_manipulators(self,parent_manipulator, follow):
+
+ man_class = self.model.ChangeManipulator
+
+ if parent_manipulator.original_object:
+ meth_name = 'get_%s_list' % self.get_method_name_part()
+ list = getattr(parent_manipulator.original_object, meth_name)()
+
+ manipulators = []
+ for i,obj in enumerate(list):
+ prefix = '%s.%d.' % (self.var_name, i)
+ manipulators.append(man_class(obj,follow, prefix) )
+ return manipulators
+
+ def get_manipulator_fields(self, opts, manipulator, follow):
+ # TODO: Remove core fields stuff.
+ change = manipulator.change
if manipulator.original_object:
meth_name = 'get_%s_count' % self.get_method_name_part()
count = getattr(manipulator.original_object, meth_name)()
@@ -104,6 +106,7 @@ class RelatedObject(object):
else:
count = self.field.rel.num_in_admin
fields = []
+
for i in range(count):
for f in self.opts.fields + self.opts.many_to_many:
if follow.get(f.name, False):
diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
index 643151cfc5..9b69c5ee80 100644
--- a/tests/modeltests/manipulators/models.py
+++ b/tests/modeltests/manipulators/models.py
@@ -86,4 +86,14 @@ Ella Fitzgerald
Ultimate Ella
>>> a2.release_date
datetime.date(2005, 2, 13)
+
+# Test follow (inline editing) functionality.
+>>> follow = {"albums":True}
+>>> man = Musician.ChangeManipulator(m1 ,follow=follow)
+>>> data = man.flatten_data()
+>>> sorted(data.items(), cmp=lambda x,y: cmp(x[0],y[0]))
+[('album.0.id', 1), ('album.0.name', 'Ella and Basie'), ('album.0.release_date', ''), ('album.1.id', 2), ('album.1.name', 'Ultimate Ella'), ('album.1.release_date', '2005-02-13'), ('first_name', 'Ella'), ('id', 1L), ('last_name', 'Fitzgerald')]
+
+
+
"""