我正试图摆脱我的模型之间的显式绑定。因此,我将使用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。
有没有其他人试图做这样的事情并成功了?应该怎么做才能使这项工作?是的,我要删除显式的外键绑定。
答案 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
字段。
但我怀疑这甚至是远程有效的。对于每次访问,它将触发数据库查询。希望有人能想出更好的方法。