Поля Serializers.validated_data были заменены исходным значением в DRF

Я пытаюсь создать api, где пользователь может создавать программы и добавлять к ним правила. Правила должны выполняться в порядке приоритета. Я использую Django rest framework для достижения этой цели и пытаюсь достичь этого с помощью Serializer без использования ModelSerializer. Предложите свое решение, используя класс serializers.Serializer

В одной программе может быть много правил, а одно правило может быть во многих программах. Поэтому я использую отношения многие_к_многим, а также хочу, чтобы пользователь мог изменять порядок правил в программе, для этого я использую сквозную таблицу 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)

Когда я делаю запрос get к http://localhost:8000/api/programs/, я получил такой ответ

[
    {
        "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": []
    },
]

Я получил список правил в поле rules, но когда я делаю post запрос с этим request.data для создания новой программы с правилами с помощью rule.id,

Почтовые данные:

{
    "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

А также, я получаю список OrderedDict в priority_set, вместо этого мне нужен словарь объектов правил

Это то, что я хочу получить после процесса сериализации,

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

Заранее спасибо

Решение 1:

Поля в проверенных данных переименовываются с помощью атрибута source. Вы можете использовать переименованный атрибут в методе 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:

Вы можете сделать rules с именем источника priority_set как 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})
Вернуться на верх