mirror of
https://github.com/django/django.git
synced 2025-06-05 03:29:12 +00:00
magic-removal: Manipulator and deletion fixes.
git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1765 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
7b7d1e8939
commit
79ad41be03
@ -1,6 +1,6 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="submit-row">
|
<div class="submit-row">
|
||||||
{% if show_delete_link %}<p class="float-left"><a href="delete/" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
|
{% if show_delete_link %}<p class="float-left"><a href="../delete/" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
|
||||||
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%}
|
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%}
|
||||||
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }} />{% endif %}
|
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }} />{% endif %}
|
||||||
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %}
|
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %}
|
||||||
|
@ -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):
|
def _nest_help(obj, depth, val):
|
||||||
current = obj
|
current = obj
|
||||||
@ -83,12 +95,14 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
|
|||||||
if not user.has_perm(p):
|
if not user.has_perm(p):
|
||||||
perms_needed.add(related.opts.verbose_name)
|
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
|
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()):
|
if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
|
||||||
raise PermissionDenied
|
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
|
# Populate deleted_objects, a data structure of all related objects that
|
||||||
# will also be deleted.
|
# will also be deleted.
|
||||||
|
@ -54,10 +54,10 @@ class ModelBase(type):
|
|||||||
|
|
||||||
def cmp_cls(x, y):
|
def cmp_cls(x, y):
|
||||||
for field in x._meta.fields:
|
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
|
return -1
|
||||||
for field in y._meta.fields:
|
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 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -196,14 +196,12 @@ class Model(object):
|
|||||||
def __get_pk_val(self):
|
def __get_pk_val(self):
|
||||||
return str(getattr(self, self._meta.pk.attname))
|
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()
|
pk_val = self.__get_pk_val()
|
||||||
|
|
||||||
key = (self.__class__, pk_val)
|
if pk_val in seen_objs.setdefault(self.__class__, {}):
|
||||||
|
|
||||||
if key in seen_objs or key in ignore_objs:
|
|
||||||
return
|
return
|
||||||
seen_objs[key] = self
|
seen_objs.setdefault(self.__class__, {})[pk_val] = (self, True)
|
||||||
|
|
||||||
for related in self._meta.get_all_related_objects():
|
for related in self._meta.get_all_related_objects():
|
||||||
rel_opts_name = related.get_method_name_part()
|
rel_opts_name = related.get_method_name_part()
|
||||||
@ -213,64 +211,74 @@ class Model(object):
|
|||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
sub_obj.__collect_sub_objects(seen_objs, ignore_objs)
|
sub_obj.__collect_sub_objects(seen_objs)
|
||||||
else:
|
else:
|
||||||
for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
|
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):
|
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."
|
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 = {}
|
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 = list(seen_cls)
|
||||||
cls_order.sort(cmp_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()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
for cls, pk_val, instance in seen_tups:
|
for cls in cls_order:
|
||||||
|
seen_objs[cls] = seen_objs[cls].items()
|
||||||
# Run any pre-delete hooks.
|
seen_objs[cls].sort()
|
||||||
if hasattr(instance, '_pre_delete'):
|
for pk_val,(instance, do_delete) in seen_objs[cls]:
|
||||||
instance._pre_delete()
|
|
||||||
|
# Run any pre-delete hooks.
|
||||||
dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance)
|
if do_delete:
|
||||||
|
if hasattr(instance, '_pre_delete'):
|
||||||
for related in cls._meta.get_all_related_many_to_many_objects():
|
instance._pre_delete()
|
||||||
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
|
|
||||||
(backend.quote_name(related.field.get_m2m_db_table(related.opts)),
|
dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance)
|
||||||
backend.quote_name(cls._meta.object_name.lower() + '_id')),
|
|
||||||
[pk_val])
|
for related in cls._meta.get_all_related_many_to_many_objects():
|
||||||
for f in cls._meta.many_to_many:
|
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
|
||||||
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
|
(backend.quote_name(related.field.get_m2m_db_table(related.opts)),
|
||||||
(backend.quote_name(f.get_m2m_db_table(cls._meta)),
|
backend.quote_name(cls._meta.object_name.lower() + '_id')),
|
||||||
backend.quote_name(cls._meta.object_name.lower() + '_id')),
|
[pk_val])
|
||||||
[pk_val])
|
for f in cls._meta.many_to_many:
|
||||||
for field in cls._meta.fields:
|
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
|
||||||
if field.rel and field.null and field.rel.to in seen_cls:
|
(backend.quote_name(f.get_m2m_db_table(cls._meta)),
|
||||||
cursor.execute("UPDATE %s SET %s = NULL WHERE %s=%%s" % \
|
backend.quote_name(cls._meta.object_name.lower() + '_id')),
|
||||||
(backend.quote_name(cls._meta.db_table), backend.quote_name(field.column),
|
[pk_val])
|
||||||
backend.quote_name(cls._meta.pk.column)), [pk_val])
|
|
||||||
|
for field in cls._meta.fields:
|
||||||
seen_tups.reverse()
|
if field.rel and field.null and field.rel.to in seen_cls:
|
||||||
|
cursor.execute("UPDATE %s SET %s = NULL WHERE %s=%%s" % \
|
||||||
for cls, pk_val, instance in seen_tups:
|
(backend.quote_name(cls._meta.db_table), backend.quote_name(field.column),
|
||||||
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
|
backend.quote_name(cls._meta.pk.column)), [pk_val])
|
||||||
(backend.quote_name(cls._meta.db_table), backend.quote_name(cls._meta.pk.column)),
|
setattr(instance, field.attname, None)
|
||||||
[pk_val])
|
|
||||||
|
for cls in cls_order:
|
||||||
setattr(self, cls._meta.pk.attname, None)
|
seen_objs[cls].reverse()
|
||||||
|
|
||||||
dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance)
|
for pk_val, (instance, do_delete) in seen_objs[cls]:
|
||||||
|
if do_delete:
|
||||||
if hasattr(instance, '_post_delete'):
|
cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
|
||||||
instance._post_delete()
|
(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()
|
connection.commit()
|
||||||
|
|
||||||
|
@ -213,6 +213,12 @@ class Field(object):
|
|||||||
field_objs = self.get_manipulator_field_objs()
|
field_objs = self.get_manipulator_field_objs()
|
||||||
return (field_objs,params)
|
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):
|
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
|
Returns a list of formfields.FormField instances for this field. It
|
||||||
|
@ -20,9 +20,6 @@ dispatcher.connect(
|
|||||||
)
|
)
|
||||||
|
|
||||||
class ManipulatorDescriptor(object):
|
class ManipulatorDescriptor(object):
|
||||||
class empty:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, name, base):
|
def __init__(self, name, base):
|
||||||
self.man = None
|
self.man = None
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -62,15 +59,32 @@ class AutomaticManipulator(Manipulator):
|
|||||||
setattr(other_cls, name, ManipulatorDescriptor(name, cls))
|
setattr(other_cls, name, ManipulatorDescriptor(name, cls))
|
||||||
contribute_to_class = classmethod(contribute_to_class)
|
contribute_to_class = classmethod(contribute_to_class)
|
||||||
|
|
||||||
def __init__(self, original_object=None, follow=None):
|
def __init__(self, original_object=None, follow=None, name_prefix=''):
|
||||||
self.follow = self.model._meta.get_follow(follow)
|
if name_prefix == '':
|
||||||
self.fields = []
|
self.follow = self.model._meta.get_follow(follow)
|
||||||
|
else:
|
||||||
|
self.follow = follow
|
||||||
|
self.fields_, self.children = [], []
|
||||||
self.original_object = original_object
|
self.original_object = original_object
|
||||||
|
self.name_prefix = name_prefix
|
||||||
for f in self.opts.get_data_holders(self.follow):
|
for f in self.opts.get_data_holders(self.follow):
|
||||||
fol = self.follow[f.name]
|
fol = self.follow[f.name]
|
||||||
fields = f.get_manipulator_fields(self.opts, self, self.change, follow=fol)
|
fields,manipulators = f.get_fields_and_manipulators(self.opts, self, follow=fol)
|
||||||
self.fields.extend(fields)
|
#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):
|
def save(self, new_data):
|
||||||
add, change, opts, klass = self.add, self.change, self.opts, self.model
|
add, change, opts, klass = self.add, self.change, self.opts, self.model
|
||||||
# TODO: big cleanup when core fields go -> use recursive manipulators.
|
# TODO: big cleanup when core fields go -> use recursive manipulators.
|
||||||
@ -233,37 +247,42 @@ class AutomaticManipulator(Manipulator):
|
|||||||
class ModelAddManipulator(AutomaticManipulator):
|
class ModelAddManipulator(AutomaticManipulator):
|
||||||
change = False
|
change = False
|
||||||
add = True
|
add = True
|
||||||
def __init__(self, follow=None):
|
def __init__(self, follow=None, name_prefix=''):
|
||||||
super(ModelAddManipulator, self).__init__(follow=follow)
|
super(ModelAddManipulator, self).__init__(follow=follow, name_prefix=name_prefix)
|
||||||
|
|
||||||
class ModelChangeManipulator(AutomaticManipulator):
|
class ModelChangeManipulator(AutomaticManipulator):
|
||||||
change = True
|
change = True
|
||||||
add = False
|
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."
|
assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
|
||||||
self.obj_key = obj_key
|
if isinstance(obj_key, self.model):
|
||||||
try:
|
original_object = obj_key
|
||||||
original_object = self.__class__.manager.get_object(pk=obj_key)
|
self.obj_key = getattr(original_object, self.model._meta.pk.attname)
|
||||||
except ObjectDoesNotExist:
|
else:
|
||||||
# If the object doesn't exist, this might be a manipulator for a
|
self.obj_key = obj_key
|
||||||
# one-to-one related object that hasn't created its subobject yet.
|
try:
|
||||||
# For example, this might be a Restaurant for a Place that doesn't
|
original_object = self.manager.get_object(pk=obj_key)
|
||||||
# yet have restaurant information.
|
except ObjectDoesNotExist:
|
||||||
if opts.one_to_one_field:
|
# If the object doesn't exist, this might be a manipulator for a
|
||||||
# Sanity check -- Make sure the "parent" object exists.
|
# one-to-one related object that hasn't created its subobject yet.
|
||||||
# For example, make sure the Place exists for the Restaurant.
|
# For example, this might be a Restaurant for a Place that doesn't
|
||||||
# Let the ObjectDoesNotExist exception propogate up.
|
# yet have restaurant information.
|
||||||
lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
|
if opts.one_to_one_field:
|
||||||
lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
|
# Sanity check -- Make sure the "parent" object exists.
|
||||||
null = opts.one_to_one_field.rel.to._meta.get_model_module().get_object(**lookup_kwargs)
|
# For example, make sure the Place exists for the Restaurant.
|
||||||
params = dict([(f.attname, f.get_default()) for f in opts.fields])
|
# Let the ObjectDoesNotExist exception propogate up.
|
||||||
params[opts.pk.attname] = obj_key
|
lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
|
||||||
original_object = opts.get_model_module().Klass(**params)
|
lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
|
||||||
else:
|
null = opts.one_to_one_field.rel.to._meta.get_model_module().get_object(**lookup_kwargs)
|
||||||
raise
|
params = dict([(f.attname, f.get_default()) for f in opts.fields])
|
||||||
super(ModelChangeManipulator, self).__init__(original_object=original_object, follow=follow)
|
params[opts.pk.attname] = obj_key
|
||||||
self.original_object = original_object
|
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():
|
if self.opts.get_ordered_objects():
|
||||||
self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
|
self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
|
||||||
|
@ -161,7 +161,7 @@ class Options:
|
|||||||
else:
|
else:
|
||||||
child_override = None
|
child_override = None
|
||||||
fol = f.get_follow(child_override)
|
fol = f.get_follow(child_override)
|
||||||
if fol:
|
if fol != None:
|
||||||
follow[f.name] = fol
|
follow[f.name] = fol
|
||||||
return follow
|
return follow
|
||||||
|
|
||||||
|
@ -46,24 +46,9 @@ class RelatedObject(object):
|
|||||||
if parent_instance != None:
|
if parent_instance != None:
|
||||||
func_name = 'get_%s_list' % self.get_method_name_part()
|
func_name = 'get_%s_list' % self.get_method_name_part()
|
||||||
func = getattr(parent_instance, func_name)
|
func = getattr(parent_instance, func_name)
|
||||||
list = func()
|
return 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
|
|
||||||
else:
|
else:
|
||||||
return [None for i in range(self.field.rel.num_in_admin)]
|
return []
|
||||||
|
|
||||||
|
|
||||||
def editable_fields(self):
|
def editable_fields(self):
|
||||||
"Get the fields in this class that should be edited inline."
|
"Get the fields in this class that should be edited inline."
|
||||||
@ -85,13 +70,30 @@ class RelatedObject(object):
|
|||||||
|
|
||||||
over[self.field.name] = False
|
over[self.field.name] = False
|
||||||
return self.opts.get_follow(over)
|
return self.opts.get_follow(over)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
|
return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
|
||||||
|
|
||||||
def get_manipulator_fields(self, opts, manipulator, change, follow):
|
def get_fields_and_manipulators(self, opts, manipulator, follow ):
|
||||||
# TODO: Remove core fields stuff.
|
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:
|
if manipulator.original_object:
|
||||||
meth_name = 'get_%s_count' % self.get_method_name_part()
|
meth_name = 'get_%s_count' % self.get_method_name_part()
|
||||||
count = getattr(manipulator.original_object, meth_name)()
|
count = getattr(manipulator.original_object, meth_name)()
|
||||||
@ -104,6 +106,7 @@ class RelatedObject(object):
|
|||||||
else:
|
else:
|
||||||
count = self.field.rel.num_in_admin
|
count = self.field.rel.num_in_admin
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
for f in self.opts.fields + self.opts.many_to_many:
|
for f in self.opts.fields + self.opts.many_to_many:
|
||||||
if follow.get(f.name, False):
|
if follow.get(f.name, False):
|
||||||
|
@ -86,4 +86,14 @@ Ella Fitzgerald
|
|||||||
Ultimate Ella
|
Ultimate Ella
|
||||||
>>> a2.release_date
|
>>> a2.release_date
|
||||||
datetime.date(2005, 2, 13)
|
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')]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user