1
0
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:
Robert Wittams 2005-12-22 15:07:36 +00:00
parent 7b7d1e8939
commit 79ad41be03
8 changed files with 173 additions and 113 deletions

View File

@ -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 %}

View File

@ -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.

View File

@ -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()

View File

@ -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

View File

@ -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_"))

View File

@ -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

View File

@ -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):

View File

@ -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')]
""" """