Ошибка сериализатора Django: у объекта 'NoneType' нет атрибута '_meta' - PullRequest
1 голос
/ 12 ноября 2011

Недавно я нашел большой кусок кода для сериализации JSON различных объектов в Django.К сожалению, код генерирует AttributeError, когда встречается с некоторыми типами моделей.

Вот ошибка и трассировка, которую я пытаюсь диагностировать и решить:

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' 

Вот код сериализаторане мое, найденное здесь 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()

Вот модель, которую я пытаюсь сериализовать:

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

А вот как я выполняю все в 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')

1 Ответ

3 голосов
/ 12 ноября 2011

Я предполагаю, что это проблема с GenericRelation.Это совершенно новый тип поля и, возможно, он не был реализован, когда был написан код сериализации.

Поля GenericRelation используют отношение ManyToMany внутренне и, вероятно, таким образом, что они не определяют объект field.rel.through.Это вызывает ошибку в вашем случае.Вы можете проверить это, добавив некоторые записи в журнал, чтобы увидеть, какое поле вызывает проблему.

Чтобы решить эту проблему, вы можете либо подробнее изучить GenericRelations и изменить код сериализации для их поддержки, либо использовать другой метод сериализации, который поддерживаетGenericRelations.

Клип из исходного кода Django для GenericRelation класс:

    def get_internal_type(self):
      return "ManyToManyField"
...