Сериализация объектов внешнего ключа в Django

Я работал над созданием некоторых сервисов RESTful в Django для использования с приложениями Flash и Android.

Разработка интерфейса служб была довольно простой, но у меня возникла проблема с сериализацией объектов, имеющих внешний ключ и многие-многие отношения.

У меня есть такая модель:

class Artifact( models.Model ):
    name                = models.CharField( max_length = 255 )
    year_of_origin      = models.IntegerField( max_length = 4, blank = True, null = True )
    object_type         = models.ForeignKey( ObjectType, blank = True, null = True )
    individual          = models.ForeignKey( Individual, blank = True, null = True )
    notes               = models.TextField( blank = True, null = True )

Затем я бы выполнить запрос на эту модель, как это, используя select_related(), чтобы быть уверенным, что соблюдаются отношения внешнего ключа:

artifact = Artifact.objects.select_related().get(pk=pk)

Как только у меня есть объект, я его сериализую и возвращаю его на мой взгляд:

serializers.serialize( "json", [ artifact ] )

Это то, что я возвращаю, обратите внимание, что внешние ключи (object_type и индивидуальный) являются просто идентификаторами связанных объектов.

[
      {
            pk: 1
            model: "artifacts.artifact"
            fields: {
                year_of_origin: 2010
                name: "Dummy Title"
                notes: ""
                object_type: 1
                individual: 1
            }
      }
]

Это здорово, но на что я надеялся при использовании select_related() было то, что он автоматически заполнил бы поля внешнего ключа связанным объектом, а не только идентификатором объекта.

Я недавно перешел на Django, но потратил немало времени на разработку с CakePHP.

Что мне действительно нравится в Cake ORM, так это то, что он будет следовать отношениям и создавать вложенные объекты по умолчанию с возможностью отмены связей при вызове запроса.

Это позволило очень просто абстрагировать сервисы таким образом, чтобы не требовалось никакого вмешательства в каждом конкретном случае.

Я вижу, что Django не делает этого по умолчанию, но есть ли способ автоматически сериализовать объект и все связанные с ним объекты? Любые советы или чтение будет высоко ценится.

5 ответов

Решение

У меня было похожее требование, но не для целей RESTful. Мне удалось достичь того, что мне нужно, с помощью "полного" модуля сериализации, в моем случае Django Full Serializers, Это часть wadofstuff и распространяется по новой лицензии BSD.

Wadofstuff делает это довольно легко. Например, в вашем случае вам нужно сделать следующее:

Сначала установите wadofstuff.

Во-вторых, добавьте следующую настройкуsettings.pyфайл:

SERIALIZATION_MODULES = {
    'json': 'wadofstuff.django.serializers.json'
}

В-третьих, внесите небольшое изменение в код, используемый для сериализации:

artifact = Artifact.objects.select_related().get(pk=pk)
serializers.serialize( "json", [ artifact ], indent = 4, 
    relations = ('object_type', 'individual',))

Главное изменение - этоrelationsпараметр ключевого слова. Единственный (второстепенный) уловка - использовать имена полей, формирующих отношение, а не имена связанных моделей.

Предостережение

Из документации:

Сериализаторы Wad of Stuff на 100% совместимы с сериализаторами Django при сериализации модели.При десериализации потока данныхDeserializerВ настоящее время класс работает только с сериализованными данными, возвращаемыми стандартными сериализаторами Django.

(Акцент добавлен)

Надеюсь это поможет.

ОБНОВЛЕНИЕ: На самом деле решение Manoj немного устарело, сериализатор Wad of Stuff некоторое время оставался не обновленным, и когда я пытался это сделать, кажется, что он больше не поддерживает Django 1.6.

Тем не менее, посмотрите официальный документ Django здесь. Это действительно дает возможность использовать встроенный натуральный ключ. Кажется, что встроенный сериализатор django имеет небольшую проблему с поддержкой использования ImageField как части естественного ключа. Но это может быть легко исправлено самим собой.

Я знаю, что этой теме уже много лет, но я делюсь своим решением для людей, которые все еще ищут ответ (во время моего поиска я оказался здесь).

Обратите внимание, я искал простую функцию, которая давала бы мне вложенные (внешний ключ) объекты / словари (которые могли бы также содержать вложенные (внешний ключ) объекты / словари) в моей модели / наборе запросов, которые я мог бы затем преобразовать в JSON.

В моем models.py у меня есть пользовательская функция (не в классе модели):

Models.py

def django_sub_dict(obj):
    allowed_fields = obj.allowed_fields() # pick the list containing the requested fields
    sub_dict = {}
    for field in obj._meta.fields: # go through all the fields of the model (obj)
        if field.name in allowed_fields: # be sure to only pick fields requested
            if field.is_relation: # will result in true if it's a foreign key
                sub_dict[field.name] = django_sub_dict(
                    getattr(obj, field.name)) # call this function, with a new object, the model which is being referred to by the foreign key.
            else: # not a foreign key? Just include the value (e.g., float, integer, string)
                sub_dict[field.name] = getattr(obj, field.name)
    return sub_dict # returns the dict generated

Эта функция перебирает все поля в объекте models.Model, если предоставляется models.Model. Я вызываю функцию в модели следующим образом (для полноты картины, включая одну целую модель):

тот же Models.py

class sheet_categories(models.Model):
    id = models.AutoField(primary_key=True, unique=True)
    create_date = models.DateField(auto_now_add=True)
    last_change = models.DateField(auto_now=True)
    name = models.CharField(max_length=128)
    sheet_type = models.ForeignKey(
        sheet_types, models.SET_NULL, blank=False, null=True)
    balance_sheet_sort = models.IntegerField(unique=True)

    def allowed_fields(self):
        return [
                'name',
                'sheet_type',
                'balance_sheet_sort',
                ]

    def natural_key(self):
        return django_sub_dict(self) # call the custom function (which is included in this models.py)

Примечание. Вложенные объекты JSON будут содержать только те поля, которые включены в allow_fields модели. Таким образом, не включая конфиденциальную информацию.

Чтобы в конечном итоге сгенерировать JSON, у меня есть следующее представление в моем views.py.

views.py

class BalanceSheetData(ListView): # I believe this doesn't have to **be** a ListView.
    model = models.sheet_categories

    def get_queryset(self):
        return super().get_queryset().filter() # the filter is for future purposes. For now, not relevant

    def get(self, request, *args, **kwargs):
        context = {
            'queryset': serializers.serialize("json",
                                          self.get_queryset(),
                                          use_natural_foreign_keys=True, # this or the one below makes django include the natural_key() within a model. Not sure.
                                          use_natural_primary_keys=True, # this or the one above makes django include the natural_key() within a model. Not sure.
                                          ),
        }
        return JsonResponse(context)

В конечном итоге это дало мне все вложенные детали, которые мне потребовались в ответе JSON. Хотя я не разделяю ответ JSON, так как этот едва читаемый.

Не стесняйтесь комментировать.

Вы можете найти больше информации по этому билету:

Разрешить углубленную сериализацию, указав глубину для отслеживания отношений https://code.djangoproject.com/ticket/4656

Добавление более нового ответа на этот старый вопрос: я создал и недавно опубликовал https://github.com/agilgur5/django-serializable-model как легко расширяемый способ сериализации моделей, менеджеров и наборов запросов. Когда ваши модели расширяются SerializableModelони получают непоправимую .serialize метод, который имеет встроенную поддержку всех отношений.

Используя ваш пример, как только все вовлеченные модели расширяются SerializableModel:

joins = ['object_type', 'individual']
artifact = Artifact.objects.select_related(*joins).get(pk=pk)
artifact.serialize(*joins)

призвание .serialize с отношениями в качестве аргументов библиотека будет рекурсировать по связанным объектам, вызывая .serialize на них тоже. Это возвращает словарь, который выглядит как:

{
  'id': 1,
  'year_of_origin': 2010,
  'name': 'Dummy Title',
  'notes': '',
  'object_type_id': 1,
  'individual_id': 1,
  'object_type': { ... nested object here ... },
  'individual': { ... nested object here ... }
}

Вы можете позвонить json.dumps в этом словаре, чтобы преобразовать его в JSON.

По умолчанию расширение SerializableModel также установит менеджер модели на SerializableManager (вы можете расширить его самостоятельно, если вы используете собственный менеджер), который использует SerializableQuerySet, Это означает, что вы можете позвонить .serialize на менеджере или в наборе запросов:

artifacts = Artifact.objects.select_related(*joins).all()
artifacts.serialize(*joins)

Это просто звонки .serialize для каждого объекта модели в наборе запросов, возвращая список словарей в том же формате, что и выше.

https://github.com/agilgur5/django-serializable-model также позволяет вам легко переопределять поведение по умолчанию для каждой модели, давая вам возможность делать такие вещи, как: добавление белых или черных списков, примененных к каждой модели. .serializeвсегда сериализуйте определенные объединения (чтобы вам не нужно было постоянно добавлять их в качестве аргументов) и многое другое!

Другие вопросы по тегам