DRF中的序列化器(Serializers).validated_data字段随着源值改变了。

3
我正在尝试创建一个API,用户可以创建程序并向其中添加规则。规则必须按优先级顺序执行。我正在使用Django rest框架实现这一目标,并尝试在不使用ModelSerializer的情况下使用Serializer类来实现。请提供您使用serializers.Serializer类的解决方案。
一个程序可以有多个规则,而一个规则也可以出现在多个程序中。因此,我使用了many_to_many关系。我还希望用户能够更改程序中规则的顺序。为了实现这一点,我使用了一个称为Priority的联结表,通过priority字段跟踪规则和程序之间的关系。 models.py
class Program(models.Model):
    name = models.CharField(max_length=32)
    description = models.TextField(blank=True)

    rules = models.ManyToManyField(Rule, through='Priority')

class Rule(models.Model):
    name = models.CharField(max_length=20)
    description = models.TextField(blank=True)
    rule = models.CharField(max_length=256)

class Priority(models.Model):
    program = models.ForeignKey(Program, on_delete=models.CASCADE)
    rule = models.ForeignKey(Rule, on_delete=models.CASCADE)

    priority = models.PositiveIntegerField()

    def save(self, *args, **kwargs):
        super(Priority, self).save(*args, **kwargs)

serializers.py

class RuleSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    description = serializers.CharField(allow_blank=True)
    rule = serializers.CharField(max_length=256)

    def create(self, validated_data):
        return Rule.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.description = validated_data.get('description', instance.description)
        instance.rule = validated_data.get('rule', instance.rule)
        instance.save()
        return instance

class PrioritySerializer(serializers.Serializer):
    rule_id = serializers.IntegerField(source='rule.id')
    rule_name = serializers.CharField(source='rule.name')
    rule_rule = serializers.CharField(source='rule.rule')
    priority = serializers.IntegerField()

class ProgramSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=32)
    description = serializers.CharField(style={'base_template': 'textarea.html'})
    rules = PrioritySerializer(source='priority_set', many=True)

    def create(self, validated_data):
        rules_data = validated_data.pop('rules')
        program_obj = Program.objects.create(**validated_data)

        priority = 0
        for rule in rules_data:
            rule_obj = Rule.objects.get(pk=rule.rule_id)
            priority += 1
            Priority.objects.create(program=program_obj, rule=rule_obj, priority=priority)

        return program_obj

views.py

class ProgramList(APIView):
    """
    List all programs, or create a new program.
    """
    def get(self, request, format=None):
        programs = Program.objects.all()
        serializers = ProgramSerializer(programs, many=True)
        return Response(serializers.data)

    def post(self, request, format=None):
        serializer = ProgramSerializer(data=request.data, partial=True)
        print(serializer.initial_data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

当我向 http://localhost:8000/api/programs/ 发送一个GET请求时,我收到了这个响应。
[
    {
        "id": 1,
        "name": "some",
        "description": "hahah",
        "rules": [
            {
                "rule_id": 3,
                "rule_name": "DAIEA",
                "rule_rule": "date,and,invoice,equal,amount",
                "priority": 1
            },
            {
                "rule_id": 2,
                "rule_name": "DAIEA",
                "rule_rule": "date,and,invoice,equal,amount",
                "priority": 2
            },
            {
                "rule_id": 1,
                "rule_name": "DAI=A",
                "rule_rule": "date,and,invoice,equal,amount",
                "priority": 3
            }
        ]
    },
    {
        "id": 2,
        "name": "DAI=AS",
        "description": "Date and Invoice equal Amount Sumif",
        "rules": []
    },
]

在规则字段中,我获得了一系列规则列表,但是当我使用 request.data 进行POST请求来创建一个新程序时,需要借助规则ID与规则相对应。

POST数据

{
    "name": "DAI=AS",
    "description": "Date and Invoice equal Amount Sumif",
    "rules": [
        {
            "rule_id": 1
        },
        {
            "rule_id": 2
        }
    ]
}

在序列化过程后,validated_data包含priority_set字段而不是rules字段,如下所示。
{
    'name': 'DAI=AS', 
    'description': 'Date and Invoice equal Amount Sumif', 
    'priority_set': [
        OrderedDict([('rule', {'id': 1})]), 
        OrderedDict([('rule', {'id': 2})])
     ]
}

我不希望序列化器改变对priority_set的规则。

同时,我在priority_set中获取了OrderedDict对象列表,而我需要rule对象的字典。

这是我在序列化过程中想要的结果:

{
    "name": "DAI=AS",
    "description": "Date and Invoice equal Amount Sumif",
    "rules": [
        {
            "rule_id": 1
        },
        {
            "rule_id": 2
        }
    ]
}

谢谢您提前的帮助。
1个回答

2

解决方案1:

验证数据中的字段已经使用源属性进行了重命名。您可以在序列化程序的create方法中使用重命名的属性。

对于嵌套序列化程序,似乎验证数据包含OrderedDict。您可以将其转换为常规Dict,并可以获取规则ID。

class ProgramSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=32)
    description = serializers.CharField(style={'base_template': 'textarea.html'})
    rules = PrioritySerializer(source='priority_set', many=True)

    def create(self, validated_data):
        rules_data = validated_data.pop('priority_set')
        program_obj = Program.objects.create(**validated_data)

        priority = 0
        for rule in rules_data:
            rule_obj = Rule.objects.get(pk=dict(rule)["rule"]["id"])
            priority += 1
            Priority.objects.create(program=program_obj, rule=rule_obj, priority=priority)

        return program_obj

解决方案2:

您可以创建源名称为priority_setrules,并将其设置为read_only,然后可以从请求数据中提取它。

class ProgramSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=32)
    description = serializers.CharField(style={'base_template': 'textarea.html'})
    rules = PrioritySerializer(source='priority_set', many=True, read_only=True)

    def create(self, validated_data):
        rules_data = self.context["request"].data["rules"]
        program_obj = Program.objects.create(**validated_data)

        priority = 0
        for rule in rules_data:
            rule_obj = Rule.objects.get(pk=rule["rule_id"])
            priority += 1
            Priority.objects.create(program=program_obj, rule=rule_obj, priority=priority)

        return program_obj

您需要将请求上下文传递给序列化器。
serializer = ProgramSerializer(data=request.data, partial=True, context={"request": request})

解决方案3:

Priority模型中的program外键中添加related_name

program = models.ForeignKey(Program, related_name="rule", on_delete=models.CASCADE)

ProgramSerializer 中的 rules 字段重命名为其他名称,例如 rule,并删除源代码。
class ProgramSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=32)
    description = serializers.CharField(style={'base_template': 'textarea.html'})
    rule = PrioritySerializer(many=True)

    def create(self, validated_data):
        rules_data = validated_data.pop('rule')
        program_obj = Program.objects.create(**validated_data)

        priority = 0
        for rule in rules_data:
            rule_obj = Rule.objects.get(pk=dict(rule)["rule"]["id"])
            priority += 1
            Priority.objects.create(program=program_obj, rule=rule_obj, priority=priority)

        return program_obj

现在你可以使用 rule 关键字发布和检索数据。如有需要,您可以将其重命名为其他名称。


我认为解决方案1比解决方案2更好,如果我们将请求对象作为上下文传递,那么我们就没有利用PrioritySerializer来序列化规则对象?如果我错了,请纠正我。并且,通过在many_to_many中使用related_name ='rules'这样的方式,是否可以更改priority_set名称,例如:rules = models.ManyToManyField(Rule, related_name ='rules', through ='Priority')? - Mahalingam Sundararaj
没错!这样的话,你需要在Priority模型中的program外键中包含related_name,因为你必须通过Program模型对象访问它。但是,这个related_name应该与rules不同,否则会冲突。我正在添加第三种解决方案。 - kamran890
是的,我尝试添加related_name='rules',正如你所说,它创建了冲突,感谢第三个解决方案。 - Mahalingam Sundararaj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接