From 79ad41be037d4198202005a60ede347198403910 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Thu, 22 Dec 2005 15:07:36 +0000 Subject: [PATCH] magic-removal: Manipulator and deletion fixes. git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1765 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin/templates/admin/submit_line.html | 2 +- django/contrib/admin/views/stages/delete.py | 20 ++- django/db/models/base.py | 116 ++++++++++-------- django/db/models/fields/__init__.py | 6 + django/db/models/manipulators.py | 87 ++++++++----- django/db/models/options.py | 2 +- django/db/models/related.py | 43 ++++--- tests/modeltests/manipulators/models.py | 10 ++ 8 files changed, 173 insertions(+), 113 deletions(-) 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')] + + + """