Search code examples
djangopython-2.6

Django AttributeError: type object 'User' has no attribute '_collect_sub_objects'


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

Solution

  • Looking at the django source code, _collect_sub_objects was removed somewhere between release 1.2 and 1.3. See the changes.


    Django now has an on_delete option for ForeignKeys, docs.

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