使用嵌套序列化程序,显式 ForeignKey 绑定替换为原始 IntegerField

Using nested serializers with explicit ForeignKey binding replaced with raw IntegerField

我正试图摆脱模型之间的显式绑定。因此,我将不使用 ForeignKey,而是使用 IntegerField 将目标模型的主键存储为一个字段。因此,我将在代码级别手动处理这种关系。这是因为,我必须将我的一些模式移动到不同的数据库实例。所以他们不能有联动。

现在,我的嵌套序列化程序遇到了问题。我正在尝试创建以下模型的实例:

 17 class Customer(models.Model):
 18     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
 19     phone_no = models.CharField(max_length=15, unique=True)

这是我的 CustomerAddress 模型,它引用了 Customer 模型:

 47 class CustomerAddress(models.Model):
 48     # customer = models.ForeignKey(Customer, related_name='cust_addresses')
 49     customer_id = models.UUIDField(default=uuid.uuid4)
 50     address = models.CharField(max_length=1000)

下面是我的序列化程序:

  7 class CustomerAddressSerializer(serializers.ModelSerializer):
  8
  9     class Meta:
 10         model = CustomerAddress
 11         depth = 1

 31 class CustomerSerializer(serializers.ModelSerializer):
 32     cust_addresses = CustomerAddressSerializer(many=True)
 33
 34     class Meta:
 35         model = Customer
 36         depth = 1
 37         fields = ('id', 'phone_no', 'cust_addresses',)
 38
 39     def create(self, validated_data):
 40         cust = Customer.objects.create(id=uuid.uuid4())
 41
 42         for addr in validated_data['cust_addresses']:
 43             address = addr['address']
 44             cust_addr = CustomerAddress.objects.create(address=address, customer_id=cust.id)
 45
 46         return cust

我的观点:

 12 class CustomerView(generics.RetrieveAPIView, generics.CreateAPIView):
 13     serializer_class = CustomerSerializer
 14
 22     def get_object(self):
 23         session = self.request.session
 24         if session.has_key('uuid'):
 25             id = session['uuid']
 26             cust = Customer.objects.get(pk=uuid.UUID(id))
 27             return cust
 28         return None

当我尝试将 post 请求从我的测试中发送到上面的视图时:

 71     def test_create_customer_address(self):
 72         cust_url = reverse('user_v1:customer')
 73         # Now we create a customer, and use it's UID in the "customer" data of /cust-address/
 74         cust_data = {"first_name": "Rohit", "last_name": "Jain", "phone_no": "xxxxxx", "email_id": "test@gmail.com", "cust_addresses": [{"city_id": 1, "address": "addr", "pin_code": "123124", "address_tag": "XYZ"}]}
 75         cust_response = self.client.post(cust_url, cust_data, format='json')
 76         print 'Post Customer'
 77         print cust_response
 78         self.assertEqual(cust_response.data['id'], str(cust_id))

我的测试失败,错误跟踪如下:

======================================================================
ERROR: test_create_customer_address (app.tests.CustomerViewTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/ubuntu/src/django-proj/app/tests.py", line 75, in test_create_customer_address
    cust_response = self.client.post(cust_url, cust_data, format='json')
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/test.py", line 168, in post
    path, data=data, format=format, content_type=content_type, **extra)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/test.py", line 90, in post
    return self.generic('POST', path, data, content_type, **extra)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/compat.py", line 231, in generic
    return self.request(**r)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/test.py", line 157, in request
    return super(APIClient, self).request(**kwargs)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/test.py", line 109, in request
    request = super(APIRequestFactory, self).request(**kwargs)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/django/test/client.py", line 466, in request
    six.reraise(*exc_info)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 132, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/views.py", line 456, in dispatch
    response = self.handle_exception(exc)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/views.py", line 453, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/generics.py", line 190, in post
    return self.create(request, *args, **kwargs)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/mixins.py", line 21, in create
    headers = self.get_success_headers(serializer.data)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 470, in data
    ret = super(Serializer, self).data
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 217, in data
    self._data = self.to_representation(self.instance)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/serializers.py", line 430, in to_representation
    attribute = field.get_attribute(instance)
  File "/home/ubuntu/Envs/rj-venv/local/lib/python2.7/site-packages/rest_framework/fields.py", line 317, in get_attribute
    raise type(exc)(msg)
AttributeError: Got AttributeError when attempting to get a value for field `cust_addresses` on serializer `CustomerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Customer` instance.
Original exception text was: 'Customer' object has no attribute 'cust_addresses'.

----------------------------------------------------------------------

当我的 CustomerAddressForeignKey 绑定到 Customer 时,所有这些都工作正常。我不知道如何解决这个问题。我试图查看源代码以查看是否需要进行一些自定义。但我不知所措。我觉得我必须以某种方式调整我的序列化程序,可能会覆盖 to_representation 方法,但我不确定。

顺便说一句,只有在创建模型实例时才会出错。对于 GET 请求,我得到了正确的 json,带有嵌套序列化程序。

有没有其他人试过这样做并成功了?应该怎么做才能使这项工作?是的,我必须删除显式外键绑定。

所以,现在我通过将 cust_addresses 作为 属性 添加到 Customer 模型来让它工作:

class Customer(models.Model):
    ... other fields

    @property
    def cust_addresses(self):
        return CustomerAddress.objects.filter(customer_id = self.id)

所以,现在我得到了正确的 JSON 响应,因为现在 field.get_attribute(instance) 可以很好地用于序列化程序中的 cust_addresses 字段。

但我怀疑这是否有效。对于每次访问,它都会触发一个数据库查询。希望有人能想出更好的办法。

我会把 REST 框架排除在外,最初只是看看你想如何在模型和 manager/queryset 级别管理这种关系。

我将从您自己的答案开始,但也许可以将 cust_addresses 设为缓存 属性,这样您就可以防止多次查找。

class Customer(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    phone_no = models.CharField(max_length=15, unique=True)

    # Only make the relationship query once.
    @cached_property
    def cust_addresses(self):
        return CustomerAddress.objects.filter(customer_id=self.id)

class CustomerAddress(models.Model):
    customer_id = models.UUIDField(default=uuid.uuid4)
    address = models.CharField(max_length=1000)

然后您可以考虑在客户 manager/queryset class 中添加预缓存行为。例如,您可以在创建客户及其地址集时预先缓存关系:

class CustomerManager(models.Manager):
    def create(phone_no, cust_addresses):
        """
        Customers and addresses are always created together.

        Usage:

        CustomerManager.objects.create(
            phone_no='123',
            cust_addresses=[{'address': 'abc'}, {'address': 'def'}]
        )
        """
        customer = super(CustomerManager, self).create(phone_no=phone_no)
        addresses = [
            CustomerAddress.objects.create(
                customer_id=instance.id
                address=item['address']
            )
            for item in cust_addresses
        ]

        # When creating both Customer and Address instances,
        # we can pre-cache the relationship.
        customer.__dict__['cust_addresses'] = addresses

        return customer

确保将 objects = CustomerManager() 添加到 Customer 模型 class。

然后您只需集成 REST 框架序列化程序即可处理。

请注意,我已放弃使用 ModelSerializer,转而使用普通的 Serializer class。对于除了快速原型制作之外的所有其他事情,我倾向于更喜欢这个——你在重复中失去的东西,你在简单和清晰中获得。

class CustomerAddressSerializer(serializers.Serializer):
    address = serializers.CharField(max_length=1000)

class CustomerSerializer(serializers.Serializer):
    id = serializers.UUIDField(read_only=True)
    phone_no = serializers.CharField(max_length=15, validators=[UniqueValidator(queryset=Customer.objects.all())])
    cust_addresses = CustomerAddressSerializer(many=True)

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