在Django中,如何将子类模型实例转换为另一个子类模型实例?

4

请确保您真的想要这样做,因为没有什么好的方法可以解决这个问题。 - Ignacio Vazquez-Abrams
3个回答

4

我曾经遇到过同样的问题,yuvi和arctelix的解决方案对我都没有用。yuvi的解决方案会导致错误,arctelix的解决方案则创建新对象并赋予新pk。

这里的目标是在保留原始超类及其旧pk的情况下更改子类模型。

第一步:删除旧的子类,保留超类。请查看Django文档

第二步:添加新的子类及其字段,并将超类传递给它。请查看此q

示例:一个地方可以是餐馆或咖啡馆,你想把一个餐厅变成咖啡馆;如下所示:

class Place(models.Model):
            name = models.CharField(max_length=50)
            address = models.CharField(max_length=80)

class Caffe(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

class Restaurant(Place):
    serves_tea = models.BooleanField(default=False)
    serves_coffee = models.BooleanField(default=False)

# get the objecte to be changed
rest = Restaurant.objects.get(pk=1) #arbitrary number
#delete the subclass while keeping the parent
rest.delete(keep_parents=True)

place = Place.objects.get(pk=1) # the primary key must be the same as the deleted restaurant

# Create a caffe and pass the original place
caffee = Caffe(place_ptr_id=place.pk) #this will empty the parent field

#update parent fields
caffee.__dict__.update(place.__dict__)

#add other field
........

#save the caffe
caffee.save()

这肯定是正确的做法。谢谢! - DylanYoung
尽管为了安全起见,我建议使用您链接的save_basesetattr方法,而不是字典更新。 - DylanYoung

3
我会创建一个全新的第二个模型实例,并使用它们共享属性的相同值,然后删除旧实例。在我看来,这似乎是最干净的方法。
如果ModelBase是抽象的:
instance = ModelA.objects.get(pk=1) #arbitrary        

# find parent class fields:
fields = [f.name for f in ModelBase._meta.fields]

# get the values from the modelA instance
values = dict( [(x, getattr(instance, x)) for x in fields] )

#assign same values to new instance of second model
new_instance = ModelB(**values) 

#add any additional information to new instance here

new_instance.save() #save new one
instance.delete() # remove the old one

但是,如果 ModelBase 不是抽象的,您将需要进行额外的解决方法:

fields = [f.name for f in ModelBase._meta.fields if f.name != 'id']
#... other parts are the same...

new_instance.modelbase_ptr = instance.modelbase_ptr #re-assign related parent
instance.delete() #delete this first!
new_instance.save()

谢谢,我该如何处理指向已删除实例的外键? - eugene
我还没有看过你的模型,所以无法确切地告诉你如何操作,但你只需要在删除旧模型之前将其重新指向 new_instance,就像我在第二个示例中对父模块所做的那样。 - yuvi
哦...实际上指针应该有被删除的父节点的ID,而不是被删除的节点的ID。 - eugene
不完全是这样。modelbase的子对象与父对象之间有一个O2O链接。我的理解是,您希望新实例引用与旧实例相同的父对象,对吗? - yuvi
所以,通过我编写的代码,在删除旧实例之前,将旧模型的父级重新分配给新模型。这样就可以正常工作了。 - yuvi
显示剩余2条评论

0

在yuvi的答案中,手动分配modelbase_ptr并保存失败,因为在保存之前instance.modelbase_ptr被删除。

在yuvi的答案基础上,这里提供一个更明确的例子,并且适用于抽象和非抽象转换:

  • ModelBase -> ModelChild
  • ModelChild -> ModelBase
  • ModelChild -> ModelChild

可选地保留原始id,并遵循django文档推荐的方法。

ex_model = ModelA
new_model = ModelB

ex_instance = ex_model.objects.get(pk=1) #arbitrary

# find fields required for new_model:
new_fields = [f.name for f in new_model._meta.fields]

# make new dict of existing field : value
new_fields_dict = dict( [(x, getattr(ex_instance, x, None)) for x in new_fields] )

# Save temp copy as new_model with new id
# modelbase_ptr will be created automatically as required
new_fields_dict.pop('project_ptr', None)
temp_instance = new_model(**new_fields_dict) 
temp_instance.pk = None
temp_instance.id = None
temp_instance.save()
# you must set all your related fields here
temp_instance.copy_related(ex_instance)

ex_instance.delete() 

# (optional) Save final copy as new_model with original id
final_instance = new_model(**new_fields_dict)
final_instance.save()
final_instance.copy_related(temp_instance)
temp_instance.delete()

# here are the removed fields, handle as required
removed_fields = [f.name for f in ex_model._meta.fields if f.name not in new_fields_dict.keys()]
removed_fields_dict = dict( [(x, getattr(ex_instance, x, None)) for x in removed_fields] )

在类ModelBase中:

def copy_related(self, from):
    # include all your related fields here
    self.related_field = from.related_field.all()
    self.related_field_a = from.related_field_a.all()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接