将子类模型实例转换为 django 中的另一个子类模型实例?

Posted

技术标签:

【中文标题】将子类模型实例转换为 django 中的另一个子类模型实例?【英文标题】:Convert a subclass model instance to another subclass model instance in django? 【发布时间】:2014-01-30 12:48:26 【问题描述】:

我有一个 ModelBase,一个 ModelA,一个 ModelB。

我想将 ModelA 实例更改为 ModelB 实例。 (我可以处理他们拥有的属性差异)

我看到了相关的问题,但对我不太有用。

How can I create an inherited django model instance from an existing base model instance?Change class of child on django models

编辑

当您有地方 - 餐厅/酒吧关系时, 我认为能够将餐厅改成酒吧是很合理的。

【问题讨论】:

要确定这是你想要的,因为没有很好的方法。 【参考方案1】:

我将创建第二个模型的全新实例,并使用相同的共享属性值,然后删除旧实例。对我来说似乎是最干净的方式。

如果 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()

【讨论】:

谢谢,如何处理指向已删除实例的外键? 我没有看到你的模型,所以我不能确切地告诉你怎么做,但你只需要在删除旧的之前将它们重新指向 new_instance,就像我对第二个示例中的父模块。 哦..实际上指针不会有被删除的父ID吗?不是被删除的? 我有点不明白你的意思,但我可以告诉你,我验证了解决方案并且重新分配相关模型对我有用 当引用 old_instance 时,它​​不会有 modelbase 的 id 吗? (当模型库不是抽象的时)。这就是我的意思。【参考方案2】:

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

以 yuvi 的回答为基础,这里有一个更明确的例子,一般适用于抽象和非抽象转换:

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

可选择保留原始 ID,这遵循 django docs 推荐的方法。

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] )

在类模型库中:

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()

【讨论】:

【参考方案3】:

我不得不处理同样的问题,yuvi 和 arctelix 的答案都对我不起作用。 yuvi 解决方案出错,arctelix 解决方案使用新 pk 创建新对象。

这里的目标是更改子类模型,同时保持原始超类与旧 pk 相同。

首先:删除旧的子类并保留超类。检查Django documents。

第二:添加新的子类及其字段并将超类传递给它。 检查this 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()

【讨论】:

这肯定是正确的方法。谢谢! 虽然为了安全起见,我会推荐您链接的问题中存在的save_basesetattr 方法,而不是字典更新。

以上是关于将子类模型实例转换为 django 中的另一个子类模型实例?的主要内容,如果未能解决你的问题,请参考以下文章

使用 django-model-utils 自动向下转换为子类

django项目模型字段

如何将 NSObject 模型类转换为 NSManagedObject 的子类?

如何在 Django 模型上存储字典?

从不同包的另一个实例的子类调用受保护的方法

Django模型继承:使用现有超类创建子类