Django嵌套序列化器测试报错:禁止直接赋值关联集合
解决Django嵌套序列化器反向关联集合赋值错误
问题根源
你遇到的TypeError: Direct assignment to the reverse side of a related set is prohibited. Use subscriptions.set() instead.错误,核心原因是通过ORM创建Customer对象时,直接给反向关联字段subscriptions赋值。Django中,多对一关系的反向侧(Customer.subscriptions)是关联集合,不能直接赋值,必须用.set()方法关联对象。
同时测试代码还有两处问题:
customer_attributes中的subscriptions值是无效字符串'self.subscription.id',并非合法对象引用- 测试断言直接将
customer_data['subscriptions']与单个订阅属性比较,但序列化后的subscriptions是数组(因many=True配置)
修复步骤
1. 修正测试用例的setUp方法
修改test_serializer.py的setUp逻辑,移除customer_attributes中的subscriptions字段,创建Customer后通过.set()关联订阅,或者创建订阅时直接绑定customer:
class CustomerSerializerTestCase(unittest.TestCase): def setUp(self): self.subscription_attributes = { 'title': 'Tea Partay', 'price': 50.99, 'status': 'active', 'frequency': 'monthly' } # 移除无效的subscriptions字段 self.customer_attributes = { 'first_name': 'Hai', 'last_name': 'Sall', 'email': 'sailt&peppa@saimall.com', 'address': 'this place Seattle, WA' } # 方案1:先创建Customer,再创建订阅并绑定 self.customer = Customer.objects.create(**self.customer_attributes) self.subscription = Subscription.objects.create(customer=self.customer, **self.subscription_attributes) # 方案2:先创建订阅,再通过set方法关联(二选一即可) # self.subscription = Subscription.objects.create(**self.subscription_attributes) # self.customer = Customer.objects.create(**self.customer_attributes) # self.customer.subscriptions.set([self.subscription]) self.serialzied_subscription = SubscriptionSerializer(instance=self.subscription) self.serialized_customer = CustomerSerializer(instance=self.customer)
2. 修正测试断言逻辑
修改test_customer_field_contents中的断言,适配序列化后subscriptions的数组格式:
def test_customer_field_contents(self): customer_data = self.serialized_customer.data self.assertEqual(customer_data['first_name'], self.customer_attributes['first_name']) self.assertEqual(customer_data['last_name'], self.customer_attributes['last_name']) self.assertEqual(customer_data['email'], self.customer_attributes['email']) self.assertEqual(customer_data['address'], self.customer_attributes['address']) # 订阅是数组结构,取第一个元素验证对应字段 self.assertEqual(customer_data['subscriptions'][0]['title'], self.subscription_attributes['title']) self.assertEqual(customer_data['subscriptions'][0]['price'], self.subscription_attributes['price']) self.assertEqual(customer_data['subscriptions'][0]['status'], self.subscription_attributes['status']) self.assertEqual(customer_data['subscriptions'][0]['frequency'], self.subscription_attributes['frequency'])
3. 模型字段命名优化(可选但推荐)
models.py中Subscription的外键字段customers改为单数customer,符合Django单对象关联的命名规范:
class Subscription(models.Model):#belongs_to :customers, :tea status_choice = { ('active', 'active'), ('cancelled', 'cancelled') } title = models.CharField(max_length = 100) price = models.FloatField() status = models.CharField(max_length = 100, choices = status_choice) frequency = models.CharField(max_length = 100) # 修改为单数customer customer = models.ForeignKey(Customer, related_name = 'subscriptions', blank = True, null = True, on_delete = models.CASCADE) teas = models.ForeignKey(Tea, blank = True, null = True, on_delete = models.CASCADE)
同时同步修改序列化器create方法中的对应代码:
def create(self, validated_data): subscriptions_data = validated_data.pop('subscriptions') customer = Customer.objects.create(**validated_data) for subscription_data in subscriptions_data: # 同步改为customer Subscription.objects.create(customer = customer, **subscription_data) return customer
验证修复
运行测试用例后,错误会消失,序列化后的Customer对象会包含关联的Subscription数组,测试断言可正确验证字段内容。
内容的提问来源于stack exchange,提问作者Parker Thomson
相关产品推荐
相关产品推荐

