在 Django DRFs 的 `ListCreateAPIView` POST 方法中,我们如何获取模型字段的 pk?

In Django DRFs' `ListCreateAPIView` POST method, how do we get the pk of a model field?

我有一个简单的 ModelSerializer,看起来像这样:

class DBaaSOrderSerializer(serializers.ModelSerializer):
    class Meta:
        model = DBaaSOrder
        fields = '__all__'

上面还有一些validate方法与本题无关

现在,我在基于 Class 的视图中使用上述序列化程序,如下所示:

class DBaaSOrderView(generics.ListCreateAPIView):
    serializer_class = DBaaSOrderSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        usd = UsdDBActions()
        orders = DBaaSOrder.objects.filter(user=self.request.user).order_by('updated_at')

        for change_ref, change_status, updated_at in orders.values_list('change_ref', 'change_status', 'updated_at'):
            usd_change_status = usd.get_rfc_status(change_ref)
            usd_change_updated_at = usd.get_last_mod_date(change_ref)

            if change_status != usd_change_status:
                orders.filter(change_ref=change_ref).update(change_status=usd_change_status)

            if updated_at != usd_change_updated_at:
                orders.filter(change_ref=change_ref).update(updated_at=usd_change_updated_at)

        return orders.filter(change_status__in=VALID_USD_STATUSES)[:MAX_ORDERS]

    @staticmethod
    def get_dns(value):
        dns_records = DNS.objects.all()
        for dns in dns_records:
            if dns.server in value and dns.domain in value:
                return dns

    @staticmethod
    def get_backup_plan(value):
        backup_plans = BackupPlan.objects.all()
        for backup_plan in backup_plans:
            if backup_plan.customer_policy_name in value and backup_plan.retention in value:
                return backup_plan

    def post(self, request, *args, **kwargs):
        data = request.data
        data['user'] = request.user.pk

        dns_record = self.get_dns(data['dns'])
        backup_plan = self.get_backup_plan(data['backup'])

        dns_record_pk = dns_record.pk
        backup_plan_pk = backup_plan.pk

        data['dns'] = dns_record_pk
        data['backup'] = backup_plan_pk

        serializer = DBaaSOrderSerializer(data=data)
        if serializer.is_valid():
            usd = UsdDBActions()
            data = request.data

            usd_data = {
                'organization': int(data['organization_pk']),
                'category': 'SSC.DBM.DB.ADD',
                'assignee': request.user.username,
            }

            response = usd.create_rfc(usd_data)

            try:
                result = response.json()

                if result['code'] == 200:
                    change_ref = int(response.json()['reference_number'])

                    data['user'] = request.user
                    data['change_ref'] = int(response.json()['reference_number'])
                    data['change_status'] = usd.get_rfc_status(change_ref)
                    data['dns'] = dns_record
                    data['backup'] = backup_plan

                    DBaaSOrder.objects.create(**data)

                    return Response({
                        'message': f"The RFC {data['change_ref']} has been successfully created!",
                        'code': 200
                    })

一切都按预期工作,但我不喜欢的是我有 2 种方法 get_dns()get_backup_plan() 来获取 dns 的实际模型对象或backup 个字段。没有更简单的方法吗?

这样做的原因是:

更多,dnsbackup是外键:

class DNS(models.Model):
    server = models.CharField(max_length=64)
    domain = models.CharField(max_length=64)

    def __str__(self):
        return f'{self.server} - {self.domain}'


class BackupPlan(models.Model):
    customer_policy_name = models.CharField(max_length=64)
    retention = models.CharField(max_length=64)

    def __str__(self):
        return f'{self.customer_policy_name} - {self.retention}'

主模型长这样:

class DBaaSOrder(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    dns = models.ForeignKey(DNS, on_delete=models.CASCADE)
    backup = models.ForeignKey(BackupPlan, on_delete=models.CASCADE)
    # ... other fields here

这里有两个相关的问题。首先是将“192.168.0.1 - test.domain.com”转换为 DNS 实例的逻辑。其次是您如何进行数据库查找。

这种事情总是属于序列化程序;这是负责将接收到的数据转换为正确格式的代码。这个特定的逻辑属于验证方法;它们的名称为 validate_<fieldname>,因此我们需要 validate_dnsvalidate_backup

目前您执行数据库查找的方式效率很低。我们可以通过拆分输入然后使用该数据进行直接数据库查询来更简单地做到这一点。

为了完成所有这些工作,我们需要覆盖这些字段的定义,使它们成为 CharFields,以便它们接受任意数据。然后验证方法将负责将它们转换为实际的模型实例。

所以:

class DBaaSOrderSerializer(serializers.ModelSerializer):

    dns = serializers.CharField()
    backup = serializers.CharField()

    class Meta:
        model = DBaaSOrder
        fields = '__all__'

    def validate_dns(self, value):
        try:
            server, domain = value.split(' - ')
        except ValueError:
            raise serializers.ValidationError('not in correct format')
        try:
            instance = DNS.objects.get(server=server, domain=domain)
        except DNS.DoesNotExist:
            raise serializers.ValidationError('no matching DNS')
        return instance

    def validate_backup(self, value):
        ... similar to above...

现在您可以摆脱 post 中的自定义逻辑。