覆盖实例属性
在 views.py
文件中,我有:
my_computer = Computer.objects.get(pk=some_value)
计算机对象有一个叫做 projects 的字段,它是一个 ManyRelatedManager
。
调用
my_projects = my_computer.projects.all()
会把 my_projects
的值设置为三个项目对象的列表。
我想要实现的是把 my_computer.projects
的值设置为上面提到的项目列表,而不是 ManyRelatedManager
。
我尝试过:
my_computer.projects = my_projects
但这并没有成功,虽然也没有报错。my_computer.projects
的值仍然是 ManyRelatedManager
。
2 个回答
1
你不能这样做。最好的办法就是换一个属性名称。
my_computer.related_projects = list(my_computer.projects.all())
1
Manager
对象实现了 __set__
方法,这意味着它们像描述符一样工作。
这就意味着你不能通过赋值来改变这个对象(只要它是另一个对象的属性 - __set__
只会在父对象的 __setattr__
上被调用 - 这里的父对象是指组合关系,而不是继承关系)。
如果你给一个管理器赋值,必须是一个类似列表的(实际上是可迭代的)值,并且这个可迭代的值必须能产生预期类型的模型。不过这意味着:
- 当你查询
my_computer.projects
时,你会再次得到一个管理器对象,里面包含你之前赋值的对象。 - 当你保存对象
my_computer
时,只有指定的对象会属于这个关系 - 之前在关系中的对象将不再与当前对象相关联。
你可能遇到这个问题的原因有三种情况:
你需要保持一个临时的列表 - 这些数据不会被存储,而是临时使用。你需要在类中创建一个普通的属性:
class Computer(models.Model): #normal database fields here def __init__(self, *args, **kwargs): super(Computer, self).__init__(*args, **kwargs) #ENSURE this attribute name does not collide with any field #I'm assuming the Many manager name is projects. self.my_projects = []
你需要同样关系的另一种表示方式 - 这样你可以更方便地访问对象,而不是调用一个奇怪的 .all() 方法,比如要做
[k.foo for k in mycomputer.my_projects]
。你需要创建一个这样的属性:class Computer(models.Model): #Normal database fields here #I'm assuming the Many manager name is projects. @property def my_projects(self): #remember: my_projects is another name. #it CANNOT collide, so I have another #name - cannot use projects as name. return list(self.projects.all()) @my_projects.setter def my_projects(self, value): #this only abstracts the name, to match #the getter. self.projects = value
你需要另一个关系(所以这不是临时数据):在你的模型中创建另一个关系,指向相同的模型,如果适用,使用相同的
through
,但要使用不同的 related_name=(你必须为同一模型的多个关系中的至少一个显式设置 related_name)。