Сериализация объектов внешнего ключа в Django - PullRequest
39 голосов
/ 20 сентября 2010

Я работал над разработкой некоторых служб 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 ]

25 голосов
/ 20 сентября 2010

У меня было похожее требование, но не для целей 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 .

(выделение добавлено)

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

12 голосов
/ 13 ноября 2013

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

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

1 голос
/ 27 июня 2011

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

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

0 голосов
/ 28 февраля 2019

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

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

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

0 голосов
/ 26 марта 2018

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

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

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

...