【问题标题】:queryset value for SlugRelatedField when unique_together applies in django-rest当 unique_together 应用于 django-rest 时 SlugRelatedField 的查询集值
【发布时间】:2016-09-03 15:21:35
【问题描述】:

我正在为 ESP8266 构建一个简单的 API,以便在 IoT 应用程序中连接,并传递一个 JSON 字符串。在此应用程序中,每个站点(位置/地址)有多个监视器(互联网连接设备),每个站点/监视器有多个日志条目。

API 最初是使用如下端点设置的: /api/logentries/

发布一个 JSON 字符串,例如: {"site":"abcd","monitor":"xyz","data_point":"value"}

在对象模型中,Monitor 是 Site 的子对象,但是为了方便创建条目和上报,各设备发布的 LogEntry 的 JSON 格式将这个结构扁平化了,也就是说 LogEntry 模型也有 FK 关系站点和监视器。在下面的代码中,“textID”是在 API 上下文中用于站点/监视器的 ID(例如,PK 值对于 API 调用者保持“隐藏”)。

在models.py中:

class Site(models.Model):
    name = models.CharField(max_length=32)
    textID = models.CharField(max_length=32, blank=True, db_index=True, unique=True)

class Monitor(models.Model):
    textID = models.CharField(max_length=32)
    site = models.ForeignKey(Site, on_delete=models.CASCADE)

    class Meta:
        unique_together = ('site', 'textID')

class LogEntry(models.Model):
    site = models.ForeignKey(Site, on_delete=models.CASCADE)
    monitor = models.ForeignKey(Monitor, on_delete=models.CASCADE)
    data_point = models.CharField(max_length=8, default='')

为了让它在单个站点上工作,我创建了一个自定义序列化程序:

class LogEntrySerializer(serializers.HyperlinkedModelSerializer):
    site = serializers.SlugRelatedField(slug_field='textID', queryset=Site.objects.all())
    monitor = serializers.SlugRelatedField(slug_field='textID', queryset=Monitor.objects.filter())

    class Meta:
        model = LogEntry
        fields = ('pk', 'site', 'monitor', 'data_point', )

这适用于读取有效数据,并在所有监视器 ID 跨站点唯一时保存。

但是,如果两个站点的 Monitor 具有相同的 textID,例如“Site1/001”和“Site2/001”这会中断,因为 Monitor.objects.all() 会导致检索到多个记录(这是有道理的,并且是预期的行为)。

我想要做的是将第二个查询集(用于监视器)限制为指定站点,以避免此错误。

This post 几乎回答了我的问题,但是它受益于请求对象中可用的第二个字段值(用户),在这种情况下不可用。

有没有一种方法可以检索 Site.pk 或 Site.textID 以正确解析查询集值——例如queryset=Monitor.objects.filter(site__textID=xxx)--“xxx”是什么?还是我需要完全覆盖序列化程序(而不依赖于 SlugRelatedField)?或者其他一些可能有效的方法?

(顺便说一句:我认识到这可以通过将 URL 模式修改为 /api///logentries 之类的东西来实现,然后将这些信息作为请求/上下文的一部分提供,并且从规范化的角度来看会也更好。然而,这将需要重新刷新许多已经部署的设备以反映更改的 API 详细信息,所以我想尽可能避免这种更改,即使经过反思,这可能是一个更清洁的解决方案/方法——术语。)

提前致谢。

【问题讨论】:

    标签: python json django django-rest-framework


    【解决方案1】:

    感谢 Linovia 的指点,以下字段类解决了该问题:

    class MonitorRelatedField(serializers.Field):
        def to_representation(self, obj):
            return obj.textID
    
        def get_value(self, data):
            site_textID = data['site']
            monitor_textID = data['monitor']
    
            return ( site_textID, monitor_textID, )
    
        def to_internal_value(self, data):
            return Monitor.objects.get(site__textID=data[0], textID=data[1])
    

    【讨论】:

    • MonitorRelatedField 中的参数是什么?
    • 你的意思是MonitorRelatedField(serializers.Field)?正在从 django-rest-framework 继承(子类化)serializers.Field 类。
    • 没关系,我明白了。谢谢。为什么我们不能在 slug 相关字段的地方使用 CharField,因为外键只需要对象的主键值。并且不需要拥有整个对象。当我在列表序列化程序中使用 slugrelatedfield 一次转储 1000 多条记录时,我遇到了内存上限问题..
    【解决方案2】:

    您需要编写自己的SlugRelatedField 子类。适用于 SlugRelatedField 的唯一性约束不适用于您的情况。 这可以通过创建一个子字段并覆盖 get_value 以检索站点/监视器元组和 to_internal_value 以选择适当的监视器来完成。

    【讨论】:

    • 感谢 Linovia — 我不确定这是否能回答问题,因为我仍然需要一些方法来确定传递的站点 .pk 或 .textID。在这种情况下,我将如何提取站点信息(这是更广泛的请求/序列化程序的一部分)?
    • 正如我所说,您需要覆盖该字段的 get_value 以检索站点/监视器。 get_value 传递了整个请求内容,因此您可以同时获取监视器和站点值并将它们返回到 to_internal_value 将转换为对象的元组中。今天晚些时候我会写一篇关于这个的博文。
    • 啊——对不起。我错过了get_value 在您发布后我对文档的后续阅读/审查中访问请求内容之间的关系。如果随后出现博客文章,请在此处发帖,以便我验证我的理解是否正确...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-02
    • 2015-12-23
    • 2014-11-27
    • 2017-10-22
    • 2020-09-14
    • 2021-05-29
    • 2015-05-08
    相关资源
    最近更新 更多