You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 20:15:42