使用 Django 优雅地将模型实例保存到另一个模型中

81 阅读2分钟

我们在 Django 项目中定义了两个模型 A 和 B,它们都继承自一个抽象模型。其中,模型 A 用于存储相关数据,模型 B 用于存储存档数据。这两个模型具有相同的字段和方法。

现在,我们需要在模型 A 上创建一个 post_save 信号,以便在创建新记录时在模型 B 中也创建一个实例。然而,目前可用的方法并不太优雅:

a = A.objects.get(id=1)
b = B()
model_dict = a.__dict__
model_dict.pop('id')
b.__dict__ = model_dict
b.save()

这种方法涉及到手动从模型 A 中提取数据,并将其复制到模型 B 中,然后保存到数据库。它不仅复杂且易出错,而且也不适用于包含外键的模型。

2、解决方案

我们可以使用一个更优雅的方法来实现这一需求。首先,我们可以使用 itertools.zip_longest() 函数来同时迭代两个模型的字段和值列表,然后使用 dict() 函数将它们组合成一个字典:

from itertools import zip_longest

def model_to_dict(model, exclude=None):
    """
    Converts a Django model instance to a dictionary of field names and values.

    Args:
        model (django.db.models.Model): The model instance to convert.
        exclude (list of str, optional): A list of field names to exclude from the dictionary.

    Returns:
        dict: A dictionary of field names and values.
    """
    fields = model._meta.fields
    values = [getattr(model, field.name) for field in fields]
    return dict(zip_longest(fields, values, fillvalue=None))

a = A.objects.get(id=1)
data = model_to_dict(a)

然后,我们可以使用 data 字典来创建模型 B 的新实例,并将其保存到数据库:

b = B(**data)
b.pk = None
b.save()

这种方法可以很好地处理外键关系,因为它会自动将外键字段的值设置为模型 B 中相应的外键对象。

需要注意的是,此方法不适用于多对多关系。对于多对多关系,我们需要手动复制相关字段的值。

以下是一个代码示例,演示了如何使用 model_to_dict() 函数和 itertools.zip_longest() 函数来将模型 A 的实例保存到模型 B 中:

import itertools

def model_to_dict(model, exclude=None):
    """
    Converts a Django model instance to a dictionary of field names and values.

    Args:
        model (django.db.models.Model): The model instance to convert.
        exclude (list of str, optional): A list of field names to exclude from the dictionary.

    Returns:
        dict: A dictionary of field names and values.
    """
    fields = model._meta.fields
    values = [getattr(model, field.name) for field in fields]
    return dict(zip_longest(fields, values, fillvalue=None))

def save_model_to_archive(sender, instance, **kwargs):
    """
    Post-save signal handler that saves a copy of the model instance to the archive.

    Args:
        sender (django.db.models.Model): The model class that sent the signal.
        instance (django.db.models.Model): The instance that was saved.
        **kwargs: Additional arguments passed to the signal handler.
    """
    data = model_to_dict(instance)
    archive_instance = Archive(**data)
    archive_instance.pk = None
    archive_instance.save()

# Register the post-save signal handler for model A
post_save.connect(save_model_to_archive, sender=A)

此代码将创建一个名为 save_model_to_archive() 的 post-save 信号处理程序,该处理程序将在模型 A 中创建新记录时自动将模型 A 的实例保存到模型 B 中。