diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index d607594e06..9be7333f83 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -201,7 +201,7 @@ auto_populated_field_script = register.simple_tag(auto_populated_field_script) #@register.simple_tag def filter_interface_script_maybe(bound_field): f = bound_field.field - if f.rel and isinstance(f.rel, meta.ManyToMany) and f.rel.filter_interface: + if f.rel and isinstance(f.rel, models.ManyToMany) and f.rel.filter_interface: return '\n' % ( f.name, f.verbose_name, f.rel.filter_interface-1, ADMIN_MEDIA_PREFIX) diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 0f2d956804..ffb25f31bd 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -16,6 +16,7 @@ from django.db.models.fields.related import * from django.core.exceptions import ObjectDoesNotExist from django.db.models.exceptions import FieldDoesNotExist, BadKeywordArguments +from django.db.models.signals import Signals # Admin stages. ADD, CHANGE, BOTH = 1, 2, 3 diff --git a/django/db/models/base.py b/django/db/models/base.py index ae1d4f0093..dbef24ea9b 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1,12 +1,14 @@ -from django.db.models.manipulators import ManipulatorDescriptor, ModelAddManipulator, ModelChangeManipulator -from django.db.models.fields import Field, DateField, FileField, ImageField, AutoField -from django.db.models.fields.related import RelatedField, OneToOne, ManyToOne, ManyToMany, RECURSIVE_RELATIONSHIP_CONSTANT +from django.db.models.manipulators import ModelAddManipulator, ModelChangeManipulator +from django.db.models.fields import AutoField +from django.db.models.fields.related import OneToOne, ManyToOne from django.db.models.related import RelatedObject -from django.db.models.manager import Manager, ManagerDescriptor +from django.db.models.manager import Manager from django.db.models.query import orderlist2sql from django.db.models.options import Options from django.db import connection, backend +from django.db.models.signals import Signals +from django.dispatch import dispatcher from django.core.exceptions import ObjectDoesNotExist from django.utils.functional import curry @@ -25,6 +27,7 @@ get_module_name = lambda class_name: class_name.lower() + 's' get_verbose_name = lambda class_name: re.sub('([A-Z])', ' \\1', class_name).lower().strip() + class ModelBase(type): "Metaclass for all models" def __new__(cls, name, bases, attrs): @@ -118,6 +121,9 @@ def cmp_cls(x, y): return 1 return 0 + + + class Model(object): __metaclass__ = ModelBase @@ -128,9 +134,7 @@ class Model(object): setattr(cls, name, attribute) add_to_class = classmethod(add_to_class) - AddManipulator = ManipulatorDescriptor('AddManipulator', ModelAddManipulator) - ChangeManipulator = ManipulatorDescriptor('ChangeManipulator', ModelChangeManipulator) - + def __repr__(self): return '<%s object>' % self.__class__.__name__ @@ -141,6 +145,7 @@ class Model(object): return not self.__eq__(other) def __init__(self, *args, **kwargs): + dispatcher.send( signal = Signals.pre_init, sender = self.__class__, args=args, kwargs=kwargs) if kwargs: for f in self._meta.fields: if isinstance(f.rel, ManyToOne): @@ -171,15 +176,21 @@ class Model(object): raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0] for i, arg in enumerate(args): setattr(self, self._meta.fields[i].attname, arg) + dispatcher.send( signal = Signals.post_init, sender = self.__class__, instance=self) def _prepare(cls): + cls.add_to_class( 'AddManipulator', ModelAddManipulator) + cls.add_to_class( 'ChangeManipulator', ModelChangeManipulator) + # Creates some methods once self._meta has been populated. if cls._meta.order_with_respect_to: cls.get_next_in_order = curry(cls._get_next_or_previous_in_order, is_next=True) cls.get_previous_in_order = curry(cls._get_next_or_previous_in_order, is_next=False) - RelatedField.do_pending_lookups(cls) + dispatcher.send( signal = Signals.class_prepared, sender = cls) + + #RelatedField.do_pending_lookups(cls) _prepare = classmethod(_prepare) @@ -187,6 +198,7 @@ class Model(object): # Run any pre-save hooks. if hasattr(self, '_pre_save'): self._pre_save() + dispatcher.send( signal=Signals.pre_save, sender = self.__class__, instance = self ) non_pks = [f for f in self._meta.fields if not f.primary_key] cursor = connection.cursor() @@ -231,6 +243,8 @@ class Model(object): connection.commit() # Run any post-save hooks. + dispatcher.send(signal=Signals.pre_save, sender = self.__class__, instance = self ) + if hasattr(self, '_post_save'): self._post_save() @@ -283,6 +297,8 @@ class Model(object): # 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" % \ @@ -311,14 +327,9 @@ class Model(object): [pk_val]) setattr(self, cls._meta.pk.attname, None) - for f in cls._meta.fields: - if isinstance(f, FileField) and getattr(self, f.attname): - file_name = getattr(instance, 'get_%s_filename' % f.name)() - # If the file exists and no other object of this type references it, - # delete it from the filesystem. - if os.path.exists(file_name) and not cls._default_manager.get_list(**{'%s__exact' % f.name: getattr(self, f.name)}): - os.remove(file_name) - # Run any post-delete hooks. + + dispatcher.send(signal=Signals.post_delete, sender = cls, instance = instance ) + if hasattr(instance, '_post_delete'): instance._post_delete() diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index fb4e38bb03..8e2514d7ab 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1,3 +1,5 @@ +from django.db.models.signals import Signals +from django.dispatch import dispatcher from django.conf import settings from django.core import formfields, validators from django.core.exceptions import ObjectDoesNotExist @@ -6,6 +8,7 @@ from django.utils.text import capfirst from django.utils.translation import gettext_lazy, ngettext import datetime, os + # Random entropy string used by "default" param. NOT_PROVIDED = 'oijpwojefiojpanv' @@ -502,6 +505,20 @@ class FileField(Field): setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) setattr(cls, 'save_%s_file' % self.name, curry(cls._save_FIELD_file, field=self)) + dispatcher.connect( + self.delete_file, + signal = Signals.post_delete, + sender = cls + ) + + def delete_file(self, instance): + if getattr(instance, f.attname): + file_name = getattr(instance, 'get_%s_filename' % f.name)() + # If the file exists and no other object of this type references it, + # delete it from the filesystem. + if os.path.exists(file_name) and \ + not instance.__class__._default_manager.get_list(**{'%s__exact' % self.name: getattr(instance, self.attname)}): + os.remove(file_name) def get_manipulator_field_objs(self): return [formfields.FileUploadField, formfields.HiddenField] diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 8dbc315c76..a57e0041bb 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -3,7 +3,9 @@ from django.db.models.related import RelatedObject from django.utils.translation import gettext_lazy, string_concat from django.utils.functional import curry from django.core import formfields +from django.db.models.signals import Signals +from django.dispatch import dispatcher # Values for Relation.edit_inline. TABULAR, STACKED = 1, 2 @@ -14,6 +16,12 @@ RECURSIVE_RELATIONSHIP_CONSTANT = 'self' class RelatedField(object): pending_lookups = {} + dispatcher.connect( + lambda sender: RelatedField.do_pending_lookups(sender) , + signal = Signals.class_prepared, + weak = False) + + def add_lookup(cls, rel_cls, field): name = field.rel.to module = rel_cls.__module__ @@ -28,6 +36,8 @@ class RelatedField(object): field.do_related_class(other_cls, rel_cls) do_pending_lookups = classmethod(do_pending_lookups) + + def contribute_to_class(self, cls, name): Field.contribute_to_class(self,cls,name) other = self.rel.to diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index eb7ee13825..6ea582ba0b 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -15,11 +15,14 @@ class ManipulatorDescriptor(object): if instance != None: raise "Manipulator accessed via instance" else: - class Man(self.get_base_manipulator(type), self.base): - pass - Man.classinit(type) - Man.__name__ = self.name - return Man + if not self.man: + class Man(self.get_base_manipulator(type), self.base): + pass + + Man._prepare(type) + Man.__name__ = self.name + self.man = Man + return self.man def get_base_manipulator(self, type): if hasattr(type, 'MANIPULATOR'): @@ -30,7 +33,7 @@ class ManipulatorDescriptor(object): class AutomaticManipulator(Manipulator): - def classinit(cls, model): + def _prepare(cls, model): cls.model = model cls.manager = model._default_manager cls.opts = model._meta @@ -43,7 +46,11 @@ class AutomaticManipulator(Manipulator): setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_month), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_month), opts, 'month')) if f.unique_for_year: setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_year), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_year), opts, 'year')) - classinit = classmethod(classinit) + _prepare = classmethod(_prepare) + + def contribute_to_class(cls, other_cls, name ): + 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) diff --git a/django/db/models/signals.py b/django/db/models/signals.py new file mode 100644 index 0000000000..179ab32226 --- /dev/null +++ b/django/db/models/signals.py @@ -0,0 +1,11 @@ +class Signals(object): + class_prepared = object() + + pre_init= object() + post_init = object() + + pre_save = object() + post_save = object() + + pre_delete = object() + post_delete = object() \ No newline at end of file diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index 841cadf711..2be39f2f57 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -27,7 +27,7 @@ Internal attributes: """ from __future__ import generators import types, weakref -from dispatch import saferef, robustapply, errors +from django.dispatch import saferef, robustapply, errors __author__ = "Patrick K. O'Brien " __cvsid__ = "$Id: dispatcher.py,v 1.8 2004/11/26 06:37:33 mcfletch Exp $"