【问题标题】:ManyToMany relation custom serializer多对多关系自定义序列化程序
【发布时间】:2016-08-09 18:08:49
【问题描述】:

假设我有两个模型:

class IPAddress(models.Model):
    address = models.CharField()

class Rule(models.Model):
    name = models.CharField()
    ips = models.ManyToMany(IPAddress)

我希望能够通过如下请求添加规则:

{
"name":"Foo",
"ips":["192.168.1.40", "4.4.4.4", "8.8.8.8"]
}

我还想在每个请求中为新规则构造ip(没有直接构造ip的url),所以我为管理器编写了一个这样的类:

class RuleManager(models.Manager):
    def create(self, validated_data):
        rule = Rule(name=validate_data['name'])
        rule.save()

        rule.ips = [IPAddress.objects.get_or_create(item.lower()) for item in validated_data['ips']]

但是在序列化程序中我找不到合适的方式来展示这一点,我编写了一个这样的序列化程序:

class RuleSerializer(serializers.Serializer):
    name = serializers.CharField()
    ips = serializers.SlugRelatedField(many=True, slug_field='address', validators=[], queryset=models.IPAddress.objects.all())

但问题是它会验证请求中的 ip,如果没有这样的 ip,它会返回错误,尽管我将验证器设置为空列表。

我有两个问题,如何禁用此验证?我编写序列化程序和模型的方式是否适合我的场景(我无法更改收到的请求和必须发送的响应)

【问题讨论】:

  • 您需要嵌套序列化程序来通过 API 创建ips,还是只需要在已创建 ips 的地方进行只读。

标签: python django django-models django-rest-framework


【解决方案1】:

如果需要返回Rule的实例,格式如下:

{
"name":"Foo",
"ips":["192.168.1.40", "4.4.4.4", "8.8.8.8"]
}

您可以创建一个RuleSerializer 并使用SlugRelatedField

SlugRelatedField 仅适用于已经存在的对象。由于您也将创建对象,因此您可以修改 to_internal_value 实现以创建不存在的对象 (referenced from here):

class CreatableSlugRelatedField(serializers.SlugRelatedField):

    def to_internal_value(self, data):
        try:
            return self.get_queryset().get_or_create(**{self.slug_field: data})[0]
        except ObjectDoesNotExist:
            self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data))
        except (TypeError, ValueError):
            self.fail('invalid')

class RuleSerializer(serializers.ModelSerializer):

    ips = serializers.CreatableSlugRelatedField(
        many=True,
        slug_field='address' # this is the ip address
        queryset=IPAddress.objects.all()
    )

    class Meta:
         model = Rule
         fields: ('name', 'ips')

更新:基于问题中的 cmets:

我无法更改收到的请求和必须发送的响应

但是,如果您可以使用嵌套序列化程序,尽管您的表示需要稍微改变:

{
    "name": "Foo",
    "ips": [
         {"address": "192.168.1.40"}, 
         {"address": "4.4.4.4"}, 
         {"address": "8.8.8.8"}
    ]
}

然后是嵌套的序列化程序 (more documentation here):

class IPAddressSerializer(serializers.ModelSerializer):

     class Meta:
         model = IPAddress
         fields: ('address',)

class RuleSerializer(serializers.ModelSerializer):

    ips = IPAddressSerializer(many=True)

    class Meta:
         model = Rule
         fields: ('name', 'ips')

【讨论】:

  • 这似乎是一个 hack,因为我们设置了ips read_only 字段,但我们将其用于写入,如果我们将其设置为read_only,它将从validated_data中删除,您可以通过添加来检查这一点print(validated_data) 在求create 函数。另外我有很多嵌套关系,每个都必须构造其较低级别的数据,所以我认为使用ModelManager 来构造较低级别的数据而不是使用序列化程序类的create 会更愉快。
  • 好吧,这不是黑客攻击,而是recommended by DRF。尽管您对使用read_only 是正确的。这里不需要它,因此您可以在实现中将其删除。至于在经理中写create,这完全取决于您的要求。
  • 如果您删除 read_only 字段,则会出现验证问题,正如我在问题中所述,因为默认情况下它会检查 ip 是否存在。
  • 感谢您的建议,但我必须保留在请求和响应表单上,并且无法使用嵌套序列化程序。
  • 好吧,我终于找到了这个答案:stackoverflow.com/questions/28009829/…。据此,您需要创建一个自定义 slug 相关字段,该字段也允许创建新对象。
猜你喜欢
  • 1970-01-01
  • 2015-06-16
  • 2014-11-08
  • 2013-11-20
  • 1970-01-01
  • 2020-02-15
  • 2019-09-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多