在 Django 中序列化外键对象

发布于 2024-09-24 08:09:44 字数 1499 浏览 3 评论 0原文

我一直致力于在 Django 中开发一些 RESTful 服务,以便与 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_lated() 对此模型执行查询,以确保遵循外键关系:

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

一旦获得对象,我就将其序列化,并将其传递回我的视图:

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

这就是我得到的结果,请注意,外键(object_type 和 individual)只是其相关对象的 id。

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

这很棒,但我在使用 select_lated() 时希望它会自动使用相关对象填充外键字段,而不仅仅是对象的 id。

我最近转向了 Django,但投入了大量时间使用 CakePHP 进行开发。

我真正喜欢 Cake ORM 的是,它默认会遵循关系并创建嵌套对象,并且能够在调用查询时取消绑定关系。

这使得以不需要根据具体情况进行任何干预的方式抽象服务变得非常容易。

我发现 Django 默认情况下不会这样做,但是有没有办法自动序列化一个对象及其所有相关对象?任何提示或阅读将不胜感激。

I have been working on developing some RESTful Services in Django to be used with both Flash and Android apps.

Developing the services interface has been quite simple, but I have been running into an issue with serializing objects that have foreign key and many to many relationships.

I have a model like this:

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 )

Then I would perform a query on this model like this, using select_related(), to be sure that foreign key relationships are followed:

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

Once I have the object, I serialize it, and pass that back to my view:

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

This is what I get back, note that the foreign keys (object_type and individual) are just the id's to their related objects.

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

This is great, but what I was hoping for when using select_related() was that it would automatically populate the foreign key fields with the related object, not just the object's id.

I am recent convert to Django, but put in a fair amount of time developing with CakePHP.

What I really like about the Cake ORM was that it would follow the relationships and create nested objects by default, with the ability to unbind the relationships when you were calling your query.

This made it very easy to abstract the services in a way that did not require any intervention on a case by case basis.

I see that Django does not do this by default, but is there a way to automatically serialize an object and all of it's related objects? Any tips or reading would be much appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

无人问我粥可暖 2024-10-01 08:09:44

我有类似的要求,尽管不是为了 RESTful 目的。在我的例子中,我能够通过使用“完整”序列化模块来实现我所需要的<代码>Django 完整序列化器。这是 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 序列化器与 Django 序列化器 100% 兼容。 反序列化数据流时,Deserializer 类当前仅适用于标准 Django 序列化器返回的序列化数据

(强调)

希望这会有所帮助。

I had a similar requirement although not for RESTful purposes. I was able to achieve what I needed by using a "full" serializing module, in my case Django Full Serializers. This is part of wadofstuff and is distributed under the new BSD license.

Wadofstuff makes this quite easy. For e.g. in your case you'd need to do the following:

First, install wadofstuff.

Second, add the following setting to your settings.py file:

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

Third, make a slight change to the code used for serialization:

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

The key change is the relations keyword parameter. The only (minor) gotcha is to use the name of the fields forming the relation not the names of the related models.

Caveat

From the documentation:

The Wad of Stuff serializers are 100% compatible with the Django serializers when serializing a model. When deserializing a data stream the the Deserializer class currently only works with serialized data returned by the standard Django serializers.

(Emphasis added)

Hope this helps.

森罗 2024-10-01 08:09:44

更新:
实际上Manoj的解决方案有点过时,Wad of Stuff的序列化器已经有一段时间没有更新了,当我尝试时,它似乎不再支持Django 1.6。

不过,请查看此处的 Django 官方文档。它确实提供了一些使用内置自然键的方法。看来 django 的内置序列化器在支持使用 ImageField 作为自然键的一部分方面存在一些问题。但这可以很容易地由您自己解决。

UPDATE:
Actually Manoj's solution is a bit outdated, Wad of Stuff's serializer has been left un-updated for some time and when I tried that, it seems that it does not support Django 1.6 anymore.

However, take a look at Django's official doc here. It does provide some way around using the built-in natural key. It seems that django's built-in serializer has a a little problem supporting using ImageField as part of the natural key. But that can be easily fixed by your self.

血之狂魔 2024-10-01 08:09:44

我知道这个话题已经有很多年了,但是,我正在为仍在寻找答案的人们分享我的解决方案(在我的搜索过程中,我最终来到了这里)。

请注意,我正在寻找一个简单的函数,它可以在我的模型/查询集中提供嵌套(外键)对象/字典(也可以包含嵌套(外键)对象/字典),然后我可以将其转换为 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 响应,因为这个响应几乎不可读。

欢迎发表评论。

I'm aware this topic is years old, however, I'm sharing my solution for the people still searching for an answer (during my search, I ended up here).

Please note, I was looking for a simple function which would give me nested (foreign key) objects/dictionaries (which could contain nested (foreign key) objects/dictionaries as well) within my model/queryset which I could then convert to JSON.

In my models.py, I have a custom function (not within a model class):

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

This function loops through all the fields in a models.Model object, if the models.Model is provided. I call the function within a model as follows (for completeness sake, including one entire model):

the same 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)

Note:
The nested JSON objects will only contain fields which are included in the allowed_fields of a model. Thus not including sensitive information.

To ultimately generate a JSON, I have the following view in my 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)

This ultimately provided me with all the nested details I required in a JSON response. Although I do not share the JSON response, as this one is barely readable.

Feel free to comment.

淡水深流 2024-10-01 08:09:44

您可以在此票证上找到更多信息:

允许通过指定深度来跟踪关系进行深度序列化
https://code.djangoproject.com/ticket/4656

You can find more information on this ticket:

Allow In-depth serialization by specifying depth to follow relationship
https://code.djangoproject.com/ticket/4656

蒗幽 2024-10-01 08:09:44

为这个旧问题添加更新的答案:我创建并最近发布了 django-serialized-model 作为一种易于扩展的方式来序列化模型、管理器和查询集。当您的模型扩展 SerializedModel 时,它们会收到一个可重写的 .serialize 方法,该方法内置对所有关系的支持。

使用您的示例,一旦所有涉及的模型扩展SerializedModel

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。

默认情况下,扩展 SerializedModel 还会将模型的管理器设置为 SerializedManager(如果您使用自定义管理器,则可以自行扩展),该管理器使用 SerializedQuerySet代码>.这意味着您也可以在管理器或查询集中调用 .serialize

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

这只是在查询集中的每个模型对象上调用 .serialize,返回同一字典中的字典列表格式如上。

django-serialized-model 还允许您轻松覆盖每个模型的默认行为基础上,使您能够执行以下操作:添加应用于每个模型的 .serialize 的允许列表或拒绝列表,始终序列化某些连接(因此您不必始终将它们添加为参数),还有更多!

Adding a newer answer to this older question: I created and recently published django-serializable-model as an easily extensible way to serialize models, managers, and querysets. When your models extend SerializableModel, they receive an overridable .serialize method that has built-in support for all relations.

Using your example, once all of the involved models extend SerializableModel:

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

Calling .serialize with the relations as arguments will have the library recurse over the related objects, calling .serialize on them as well. This returns a dictionary that looks like:

{
  '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 ... }
}

You can then call json.dumps on this dictionary to transform it to JSON.

By default, extending SerializableModel will also set the model's manager to SerializableManager (you can extend it yourself if you're using a custom manager) which uses SerializableQuerySet. This means you can call .serialize on a manager or queryset as well:

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

This simply calls .serialize on each model object in the queryset, returning a list of dictionaries in the same format as above.

django-serializable-model also allows you to easily override the default behavior on a per model basis, giving you the ability to do things like: add allowlists or denylists applied to each model's .serialize, always serialize certain joins (so you don't have to add them as arguments all the time), and more!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文