Search code examples
djangojsonserializationattributeerror

Django serializer error: 'NoneType' object has no attribute '_meta'


I recently found a great bit of code to JSON serialize various objects in Django. Unfortunately, the code throws an AttributeError when it encounters some types of models.

Here is the error and traceback I am trying to diagnose and solve:

AttributeError at /serial/
'NoneType' object has no attribute '_meta'
Request Method: GET
Request URL:    http://127.0.0.1:8000/serial/
Django Version: 1.2.5
Exception Type: AttributeError
Exception Value:    
'NoneType' object has no attribute '_meta'
Exception Location: /Users/jphill/apps/d_projects/smartgoal/../smartgoal/hq/custom_serializer.py in handle_m2m_field, line 183
Python Executable:  /usr/bin/python
Python Version: 2.6.7
Python Path:    ['/Users/jphill/apps/d_projects/smartgoal', '/Library/Python/2.6/site-packages/setuptools-0.6c11-py2.6.egg', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python26.zip', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/plat-darwin', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/plat-mac', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/plat-mac/lib-scriptpackages', '/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-tk', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-old', '/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib-dynload', '/Library/Python/2.6/site-packages', '/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/PyObjC']
Server time:    Fri, 11 Nov 2011 23:08:13 -0500

Environment:

Request Method: GET
Request URL: http://127.0.0.1:8000/serial/
Django Version: 1.2.5
Python Version: 2.6.7
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.admin',
 'django.contrib.admindocs',
 'hq']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware')


Traceback:
File "/Library/Python/2.6/site-packages/django/core/handlers/base.py" in get_response
  100.                     response = callback(request, *callback_args, **callback_kwargs)
File "/Users/jphill/apps/d_projects/smartgoal/hq/views.py" in serial
  164.      return json_response_from(Task.objects.all())
File "/Users/jphill/apps/d_projects/smartgoal/hq/views.py" in json_response_from
  88.     return HttpResponse(jsonSerializer.serialize(response, ), mimetype='application/json')
File "/Users/jphill/apps/d_projects/smartgoal/../smartgoal/hq/custom_serializer.py" in serialize
  34.         self.handle_object(obj)
File "/Users/jphill/apps/d_projects/smartgoal/../smartgoal/hq/custom_serializer.py" in handle_object
  75.             self.handle_queryset(object)
File "/Users/jphill/apps/d_projects/smartgoal/../smartgoal/hq/custom_serializer.py" in handle_queryset
  138.             self.handle_model(mod)
File "/Users/jphill/apps/d_projects/smartgoal/../smartgoal/hq/custom_serializer.py" in handle_model
  128.                     self.handle_m2m_field(mod, field)
File "/Users/jphill/apps/d_projects/smartgoal/../smartgoal/hq/custom_serializer.py" in handle_m2m_field
  183.         if field.rel.through._meta.auto_created:

Exception Type: AttributeError at /serial/
Exception Value: 'NoneType' object has no attribute '_meta' 

Here is the serializer code (not mine, found here http://www.traddicts.org/webdevelopment/flexible-and-simple-json-serialization-for-django/)

from io import StringIO
from django.db.models import Model
from django.db.models.query import QuerySet
from django.utils.encoding import smart_unicode
from django.utils.simplejson import dumps

class UnableToSerializeError(Exception):
    """ Error for not implemented classes """
    def __init__(self, value):
        self.value = value
        Exception.__init__(self)

    def __str__(self):
        return repr(self.value)

class JSONSerializer():
    boolean_fields = ['BooleanField', 'NullBooleanField']
    datetime_fields = ['DatetimeField', 'DateField', 'TimeField']
    number_fields = ['IntegerField', 'AutoField', 'DecimalField', 'FloatField', 'PositiveSmallIntegerField']


    def serialize(self, obj, **options):
        self.options = options

        self.stream = options.pop("stream", StringIO())
        self.selectedFields = options.pop("fields", None)
        self.ignoredFields = options.pop("ignored", None)
        self.use_natural_keys = options.pop("use_natural_keys", False)
        self.currentLoc = ''

        self.level = 0

        self.start_serialization()

        self.handle_object(obj)

        self.end_serialization()
        return self.getvalue()

    def get_string_value(self, obj, field):
        """Convert a field's value to a string."""
        return smart_unicode(field.value_to_string(obj))

    def start_serialization(self):
        """Called when serializing of the queryset starts."""
        pass

    def end_serialization(self):
        """Called when serializing of the queryset ends."""
        pass

    def start_array(self):
        """Called when serializing of an array starts."""
        self.stream.write(u'[')
    def end_array(self):
        """Called when serializing of an array ends."""
        self.stream.write(u']')

    def start_object(self):
        """Called when serializing of an object starts."""
        self.stream.write(u'{')

    def end_object(self):
        """Called when serializing of an object ends."""
        self.stream.write(u'}')

    def handle_object(self, object):
        """ Called to handle everything, looks for the correct handling """
        if isinstance(object, dict):
            self.handle_dictionary(object)
        elif isinstance(object, list):
            self.handle_list(object)
        elif isinstance(object, Model):
            self.handle_model(object)
        elif isinstance(object, QuerySet):
            self.handle_queryset(object)
        elif isinstance(object, bool):
            self.handle_simple(object)
        elif isinstance(object, int) or isinstance(object, float) or isinstance(object, long):
            self.handle_simple(object)
        elif isinstance(object, basestring):
            self.handle_simple(object)
        else:
            raise UnableToSerializeError(type(object))

    def handle_dictionary(self, d):
        """Called to handle a Dictionary"""
        i = 0
        self.start_object()
        for key, value in d.iteritems():
            self.currentLoc += key+'.'
            #self.stream.write(unicode(self.currentLoc))
            i += 1
            self.handle_simple(key)
            self.stream.write(u': ')
            self.handle_object(value)
            if i != len(d):
                self.stream.write(u', ')
            self.currentLoc = self.currentLoc[0:(len(self.currentLoc)-len(key)-1)]
        self.end_object()

    def handle_list(self, l):
        """Called to handle a list"""
        self.start_array()

        for value in l:
            self.handle_object(value)
            if l.index(value) != len(l) -1:
                self.stream.write(u', ')

        self.end_array()

    def handle_model(self, mod):
        """Called to handle a django Model"""
        self.start_object()

        for field in mod._meta.local_fields:
            if field.rel is None:
                if self.selectedFields is None or field.attname in self.selectedFields or field.attname:
                    if self.ignoredFields is None or self.currentLoc + field.attname not in self.ignoredFields:
                        self.handle_field(mod, field)
            else:
                if self.selectedFields is None or field.attname[:-3] in self.selectedFields:
                    if self.ignoredFields is None or self.currentLoc + field.attname[:-3] not in self.ignoredFields:
                        self.handle_fk_field(mod, field)
        for field in mod._meta.many_to_many:
            if self.selectedFields is None or field.attname in self.selectedFields:
                if self.ignoredFields is None or self.currentLoc + field.attname not in self.ignoredFields:
                    self.handle_m2m_field(mod, field)
        self.stream.seek(self.stream.tell()-2)
        self.end_object()

    def handle_queryset(self, queryset):
        """Called to handle a django queryset"""
        self.start_array()
        it = 0
        for mod in queryset:
            it += 1
            self.handle_model(mod)
            if queryset.count() != it:
                self.stream.write(u', ')
        self.end_array()

    def handle_field(self, mod, field):
        """Called to handle each individual (non-relational) field on an object."""
        self.handle_simple(field.name)
        if field.get_internal_type() in self.boolean_fields:
            if field.value_to_string(mod) == 'True':
                self.stream.write(u': true')
            elif field.value_to_string(mod) == 'False':
                self.stream.write(u': false')
            else:
                self.stream.write(u': undefined')
        else:
            self.stream.write(u': ')
            self.handle_simple(field.value_to_string(mod))
        self.stream.write(u', ')

    def handle_fk_field(self, mod, field):
        """Called to handle a ForeignKey field."""
        related = getattr(mod, field.name)
        if related is not None:
            if field.rel.field_name == related._meta.pk.name:
                # Related to remote object via primary key
                pk = related._get_pk_val()
            else:
                # Related to remote object via other field
                pk = getattr(related, field.rel.field_name)
            d = {
                    'pk': pk,
                }
            if self.use_natural_keys and hasattr(related, 'natural_key'):
                d.update({'natural_key': related.natural_key()})
            if type(d['pk']) == str and d['pk'].isdigit():
                d.update({'pk': int(d['pk'])})

            self.handle_simple(field.name)
            self.stream.write(u': ')
            self.handle_object(d)
            self.stream.write(u', ')

    def handle_m2m_field(self, mod, field):
        """Called to handle a ManyToManyField."""
        if field.rel.through._meta.auto_created:
            self.handle_simple(field.name)
            self.stream.write(u': ')
            self.start_array()
            hasRelationships = False
            for relobj in getattr(mod, field.name).iterator():
                hasRelationships = True
                pk = relobj._get_pk_val()
                d = {
                        'pk': pk,
                    }
                if self.use_natural_keys and hasattr(relobj, 'natural_key'):
                    d.update({'natural_key': relobj.natural_key()})
                if type(d['pk']) == str and d['pk'].isdigit():
                    d.update({'pk': int(d['pk'])})

                self.handle_simple(d)
                self.stream.write(u', ')
            if hasRelationships:
                self.stream.seek(self.stream.tell()-2)
            self.end_array()
            self.stream.write(u', ')

    def handle_simple(self, simple):
        """ Called to handle values that can be handled via simplejson """
        self.stream.write(unicode(dumps(simple)))

    def getvalue(self):
        """Return the fully serialized object (or None if the output stream is  not seekable).sss """
        if callable(getattr(self.stream, 'getvalue', None)):
            return self.stream.getvalue()

Here is the model I'm trying to serialize:

class Task(models.Model):
    """
    Model used for tracking tasks
    """
    PRIORITY_CHOICES = (
        ('0', 'None'),
        ('1', 'High'),
        ('2', 'Medium'),
        ('3', 'Low'),
    )
    name = models.CharField(max_length=255)
    completed = models.BooleanField(default=False)
    hidden = models.BooleanField(default=False)
    timestamp = models.DateField(auto_now=True)
    priority = models.CharField(default=0, max_length=1, choices=PRIORITY_CHOICES)
    creator = models.ForeignKey(User, related_name="created_task")
    # optional
    owner = models.ForeignKey(User, related_name="owned_task", blank=True, null=True)
    goal = models.ForeignKey('Goal', blank=True, null=True)
    reminder = models.DateTimeField(blank=True, null=True)
    note = models.TextField(blank=True)
    started = models.BooleanField(default=False, blank=True)
    activities = generic.GenericRelation('Activity')

And here is how I'm executing everything in views.py:

def serial(request):
     return json_response_from(Task.objects.all())

def json_response_from(response):
    jsonSerializer = JSONSerializer()
    return HttpResponse(jsonSerializer.serialize(response, use_natural_keys=True), mimetype='application/json')

Solution

  • My guess is that it's the problem with the GenericRelation. It's quite new field type and maybe it wasn't implemented when the serialization code was written.

    GenericRelation fields use ManyToMany relation internally, and probably in a way that they don't define the field.rel.through object. This causes the error in your case. You can verify this by adding some logging to see which field causes the problem.

    To solve it you can either look more into GenericRelations and modify the serialization code to support them or use some other serialization technique that supports GenericRelations.

    Clip from Django source code for GenericRelation class:

        def get_internal_type(self):
          return "ManyToManyField"