Django AttributeError: type object 'User' has no attribute '_collect_sub_objects'
Question: I'm trying to reinstall a site I haven't used in several months and am getting
an error I can't seem to find a solution for.

Running the latest trunk of django, python 2.6.

Here is the stack trace:::

    
    
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X] mod_wsgi (pid=9458): Exception occurred processing WSGI script '/home/www/vhosts/site/site.wsgi'.
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X] Traceback (most recent call last):
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/core/handlers/wsgi.py", line 273, in __call__
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     response = self.get_response(request)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/core/handlers/base.py", line 169, in get_response
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/core/handlers/base.py", line 214, in handle_uncaught_exception
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     if resolver.urlconf_module is None:
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/core/urlresolvers.py", line 274, in _get_urlconf_module
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     self._urlconf_module = import_module(self.urlconf_name)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/utils/importlib.py", line 35, in import_module
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     __import__(name)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/home/www/vhosts/site.com/site/urls.py", line 7, in <module>
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     from site.feeds import *
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/home/www/vhosts/site.com/site/feeds.py", line 6, in <module>
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     from site.content.models import *
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/home/www/vhosts/site.com/site/content/models.py", line 14, in <module>
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     from site.authors.models import Author
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/home/www/vhosts/site.com/site/authors/models.py", line 11, in <module>
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     class Author(models.Model):
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/db/models/base.py", line 97, in __new__
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     new_class.add_to_class(obj_name, obj)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/db/models/base.py", line 217, in add_to_class
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     value.contribute_to_class(cls, name)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/db/models/fields/related.py", line 891, in contribute_to_class
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     super(ForeignKey, self).contribute_to_class(cls, name)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/db/models/fields/related.py", line 112, in contribute_to_class
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     self.do_related_class(other, cls)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/usr/lib/python2.6/site-packages/django/db/models/fields/related.py", line 124, in do_related_class
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     self.contribute_to_related_class(other, self.related)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]   File "/home/www/vhosts/site.com/site/utils/nullableforeignkey.py", line 38, in contribute_to_related_class
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X]     setattr(cls, _original_csb_attr_name, cls._collect_sub_objects)
    [Fri Apr 01 19:31:00 2011] [error] [client X.X.X.X] **AttributeError: type object 'User' has no attribute '_collect_sub_objects'**
    

**Here is the file in question ::**

    
    
    from django.db import models, connection
    
    class NullableForeignKey(models.ForeignKey):
        """
        Just like a ForeignKey, but when related objects are deleted this object is
        *not* deleted. As the name implies, this field is always NULLable.
        """
    
        def __init__(self, *args, **kwargs):
            kwargs['null'] = kwargs['blank'] = True
            super(NullableForeignKey, self).__init__(*args, **kwargs)
    
        def contribute_to_related_class(self, cls, related):
            super(NullableForeignKey, self).contribute_to_related_class(cls, related)
    
            # define a method name to map the original `_collect_sub_objects` method to
            _original_csb_attr_name = '_original_collect_sub_objects'
            this_field = self
    
            def _new_collect_sub_objects(self, *args, **kwargs):
                # NULL out anything related to this object.
                qn = connection.ops.quote_name
                for related in self._meta.get_all_related_objects():
                    if isinstance(related.field, this_field.__class__):
                        table = qn(related.model._meta.db_table)
                        column = qn(related.field.column)
                        sql = "UPDATE %s SET %s = NULL WHERE %s = %%s;" % (table, column, column)
                        connection.cursor().execute(sql, [self.pk])
    
                # Now proceed with collecting sub objects that are still tied via FK
                getattr(self, _original_csb_attr_name)(*args, **kwargs)
    
            # monkey patch the related classes _collect_sub_objects method.
            # store the original method in an attr named `_original_csb_attr_name`
            if not hasattr(cls, _original_csb_attr_name):
                setattr(cls, _original_csb_attr_name, cls._collect_sub_objects)
                setattr(cls, '_collect_sub_objects', _new_collect_sub_objects)
    
    
    # Prevent deletion completely if item has related children
    from django.db.models import OneToOneField, ObjectDoesNotExist
    from django.utils.encoding import StrAndUnicode
    from django.utils.translation import ugettext as _
    
    class ForeignKeysExist(StrAndUnicode, Exception):
        def __init__(self, parent, child, field):
            '''
                Arguments:
                parent - parent instance (that contains child records)
                child  - object that have foreign key pointing to the parent instance
                field  - child instance foreign key field name
            '''
            self.parent = parent #parent instance
            self.child = child #child that has reference to this parent
            self.field = field #field in the child class that points to the parent
            self.msg = 'Child record %s.%s exists for %s' % (_(child), _(field), _(parent))
            super(ForeignKeysExist, self)
    
        def __unicode__(self):
            return self.msg
    
    class NoDeleteCascadeMixin(object):
        """
        Mixin to prevent deletion of model
        if there exists any children
        """
    
        def delete(self):
            # Copied sanity test from django.db.models.ModelBase.delete
            assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
    
            opts = self._meta
    
            for related in opts.get_all_related_objects():
                rel_accessor = related.get_accessor_name()
                if isinstance(related, OneToOneField):
                    try:
                        o = getattr(self, rel_accessor)
                    except ObjectDoesNotExist:
                        pass # No item in one to one
                    else:
                        raise ForeignKeysExist(related.parent_model._meta.object_name, opts.object_name, related.field.name)
                else:
                    rel_manager = getattr(self, rel_accessor)
                    if rel_manager.count():
                        raise ForeignKeysExist(related.parent_model._meta.object_name, related.model._meta.object_name, related.field.name)
    
            super(NoDeleteCascadeMixin, self).delete()
    


Answer: Looking at the django source code, `_collect_sub_objects` was removed
somewhere between release 1.2 and 1.3. See the
[`changes`](http://code.djangoproject.com/changeset?old_path=/django/branches/releases/1.2.X/django/db/models/base.py&old=15968&new_path=/django/branches/releases/1.3.X/django/db/models/base.py&new=15968).

* * *

Django now has an **`on_delete`** option for ForeignKeys,
[docs](http://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.on_delete).

**ForeignKey.on_delete**

> When an object referenced by a ForeignKey is deleted, Django by default
> emulates the behavior of the SQL constraint ON DELETE CASCADE and also
> deletes the object containing the ForeignKey. This behavior can be
> overridden by specifying the on_delete argument. For example, if you have a
> nullable ForeignKey and you want it to be set null when the referenced
> object is deleted:

`user = models.ForeignKey(User, blank=True, null=True,
on_delete=models.SET_NULL)`

> The possible values for on_delete are found in django.db.models:

  * CASCADE: Cascade deletes; the default.

  * PROTECT: Prevent deletion of the referenced object by raising django.db.models.ProtectedError, a subclass of django.db.IntegrityError.

  * SET_NULL: Set the ForeignKey null; this is only possible if null is True.

  * SET_DEFAULT: Set the ForeignKey to its default value; a default for the ForeignKey must be set.

  * SET(): Set the ForeignKey to the value passed to SET(), or if a callable is passed in, the result of calling it. In most cases, passing a callable will be necessary to avoid executing queries at the time your models.py is imported:
    
        def get_sentinel_user():
        return User.objects.get_or_create(username='deleted')[0]
    
    class MyModel(models.Model):
        user = models.ForeignKey(User, on_delete=models.SET(get_sentinel_user))
    

  * DO_NOTHING: Take no action. If your database backend enforces referential integrity, this will cause an IntegrityError unless you manually add a SQL ON DELETE constraint to the database field (perhaps using initial sql).

