Сериализация объектов внешнего ключа в 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 и Individual) являются просто идентификаторами связанных объектов.

[
      {
            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 не делает этого по умолчанию, но есть ли способ автоматической сериализации объекта и всех связанных с ним объектов? Любые советы или чтение будет высоко ценится.

 Burhan Khalid13 нояб. 2013 г., 06:50

Ответы на вопрос(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.

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

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

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

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

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

 Edgar Navasardyan30 апр. 2016 г., 23:51
@ Шен Хаохен, кажется, твоя ссылка больше недоступна. Но я согласен с вами, что вышеуказанное решение устарело. Не могли бы вы обновить ссылку?
 Alex Daro14 февр. 2016 г., 03:06
Спасибо! Я забыл про натуральный ключик;)

я создал и недавно опубликовалДжанго сериализуемой-модель как легко расширяемый способ сериализации моделей, менеджеров и наборов запросов. Когда ваши модели расширяются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 для каждого объекта модели в наборе запросов, возвращая список словарей в том же формате, что и выше.

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

 OrangeDog16 окт. 2018 г., 11:59
Кажется, не работает на отношения один ко многим.
 agilgur516 окт. 2018 г., 21:43
@OrangeDog этот вопрос задает конкретно о внешнем ключе /select_relatedНо библиотека работает над отношениями «один ко многим» и «многие ко многим». В нижней части документа есть раздел (возможно, не совсем правильно «С отношением внешнего ключа:»), который относится кprefetch_related, Преобразование примера в документах, чтобы соответствовать этому вопросу, было бы что-то вроде:Artifacts.objects.prefetch_related(*joins).all().serialize(*joins), Если вы нашли ошибку в библиотеке, пожалуйста, сообщите о проблеме.

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

Обратите внимание, я искал простую функцию, которая давала бы мне вложенные (внешний ключ) объекты / словари (которые могли бы также содержать вложенные (внешний ключ) объекты / словари) в моей модели / наборе запросов, которые я мог бы затем преобразовать в 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 будут содержать только те поля, которые включены вallowed_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, так как этот едва читаемый.

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

Ваш ответ на вопрос