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

时间:2015-07-08 14:50:04

标签: python django django-rest-framework

我正试图摆脱我的模型之间的显式绑定。因此,我将使用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

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

 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'.

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

当我CustomerAddress ForeignKey绑定到Customer时,所有这一切都正常。我对如何解决这个问题没有任何线索。我试着查看源代码,看看是否需要进行一些自定义。但我很茫然。我觉得我不得不以某种方式调整我的序列化程序,可能会覆盖to_representation方法,但我不确定。

BTW,仅在创建模型实例时出现错误。对于GET请求,我使用嵌套的序列化程序获得了正确的json。

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

2 个答案:

答案 0 :(得分:2)

我会将REST框架排除在等式之外,最初只是看看你想如何在模型和经理/查询集层面管理这种关系。

我从你自己的答案中开始,但也许让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)

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

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模型类。

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

请注意,我已放弃使用ModelSerializer支持普通Serializer课程。对于快速原型制作以外的所有事情,我倾向于选择这一点 - 你在简单和清晰方面获得的重复损失。

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)

答案 1 :(得分:0)

所以,现在我通过将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字段。

但我怀疑这甚至是远程有效的。对于每次访问,它将触发数据库查询。希望有人能想出更好的方法。