使用嵌套序列化程序,显式 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'.
----------------------------------------------------------------------
当我的 CustomerAddress
有 ForeignKey
绑定到 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)
我正试图摆脱模型之间的显式绑定。因此,我将不使用 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'.
----------------------------------------------------------------------
当我的 CustomerAddress
有 ForeignKey
绑定到 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)