【问题标题】:Can Django automatically create a related one-to-one model?Django可以自动创建相关的一对一模型吗?
【发布时间】:2010-12-11 18:33:33
【问题描述】:

我在不同的应用中有两个模型:ModelAModelB。他们是一对一的关系。保存ModelA时,django有没有办法自动创建并保存ModelB

class ModelA(models.Model):
    name = models.CharField(max_length=30)

class ModelB(models.Model):
    thing = models.OneToOneField(ModelA, primary_key=True)
    num_widgets = IntegerField(default=0)

当我保存一个新的ModelA 时,我希望在ModelB 中自动保存一个条目。我怎样才能做到这一点?有没有办法在ModelA 中指定它?或者这不可能,我只需要在视图中创建并保存ModelB

编辑说模型在不同的应用程序中。

【问题讨论】:

标签: django django-models model


【解决方案1】:

看看django-annoying中的AutoOneToOneField。来自文档:

from annoying.fields import AutoOneToOneField

class MyProfile(models.Model):
    user = AutoOneToOneField(User, primary_key=True)
    home_page = models.URLField(max_length=255)
    icq = models.CharField(max_length=255)

(django-annoying 是一个很棒的小库,包括像 render_to decoratorget_object_or_Noneget_config 函数这样的 gem)

【讨论】:

  • 值得注意的是,在管理面板中创建新用户不会立即创建 MyProfile。它是以一种惰性的方式创建的(您第一次实际访问该配置文件对象时)。
【解决方案2】:

点赞m000pointed out

...问题的关键是模型属于不同的应用程序。这与信号的用例相匹配:“当框架中的其他地方发生动作时,允许解耦的应用程序得到通知”。其他提议的解决方案有效,但引入了不必要的 A->B 依赖关系,基本上将两个应用程序捆绑在一起。信号允许 A 与 B 保持分离。

您的模型存在于不同的应用中。您经常使用不是您编写的应用程序,因此要允许更新,您需要一种解耦的方式来创建逻辑相关的模型。这是我认为的首选解决方案,我们在一个非常大的项目中使用它。

通过使用信号:

在你的models.py:

from django.db.models import signals


def create_model_b(sender, instance, created, **kwargs):
    """Create ModelB for every new ModelA."""
    if created:
        ModelB.objects.create(thing=instance)

signals.post_save.connect(create_model_b, sender=ModelA, weak=False,
                          dispatch_uid='models.create_model_b')

如果两个应用都是现成的,您可以创建一个单独的应用来保存该 models.py 文件。

【讨论】:

  • 为此 +1。问题的关键在于模型属于不同的应用程序。这与信号的用例相匹配:“当框架中的其他地方发生动作时,允许解耦的应用程序得到通知”。其他提议的解决方案有效,但引入了不必要的 A->B 依赖关系,基本上将两个应用程序捆绑在一起。信号允许 A 与 B 保持分离。
  • @m000 谢谢!如果你不介意我会更新我的解决方案的描述,因为你已经很好地总结了它。
  • 此方法会破坏使用夹具提供 ModelA 和相关 ModelB 对象的测试。有什么建议吗?
  • @MariusGedminas 来自文档:Note also that Django stores signal handlers as weak references by default, so if your handler is a local function, it may be garbage collected. To prevent this, pass weak=False when you call the signal’s connect().
  • @MariusGedminas get_or_create 可以根据您的情况提供必要的调整。
【解决方案3】:

最直接的方法是override the save method的ModelA:

class ModelA(models.Model):
    name = models.CharField(max_length=30)

    def save(self, force_insert=False, force_update=False):
        is_new = self.id is None
        super(ModelA, self).save(force_insert, force_update)
        if is_new:
            ModelB.objects.create(thing=self)

【讨论】:

  • 这样做的麻烦在于,如果您在管理员中有一个内联表单并使用它同时创建一个 ModelB 实例,它会很遗憾地中断 - 它会尝试创建两个ModelB 死得很惨。
  • 是的,但我认为这是一个 hack。
  • 可能希望通过不将 args 命名为 super 来更加面向未来。我会建议修改。
【解决方案4】:

我知道这有点晚了,但我想出了一个更简洁、更优雅的解决方案。 考虑这段代码:

class ModelA(models.Model):
    name = models.CharField(max_length=30)

    @classmethod
    def get_new(cls):
        return cls.objects.create().id



class ModelB(models.Model):
    thing = models.OneToOneField(ModelA, primary_key=True, default=ModelA.get_new)
    num_widgets = IntegerField(default=0)

当然你也可以使用 lambda,只要你返回相关对象的整数 id :)

【讨论】:

  • 很好地使用类方法,但是,我认为信号可能更直接
  • 就我而言,这完成了创建 2 个 ModelA 记录...我不知道为什么... Django 创建模型的方式很奇怪。
【解决方案5】:

我收集了几个不同的答案(因为没有一个对我来说是直接开箱即用的)并想出了这个。觉得很干净所以分享给大家。

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=ModelA)
def create_modelb(sender, instance, created, **kwargs):
    if created:
        if not hasattr(instance, 'modelb'):
            ModelB.objects.create(thing=instance)

它按照@Dmitry 的建议使用 Signal。正如@daniel-roseman 在@jarret-hardie 的回答中所评论的那样,Django 管理员有时会尝试为您创建相关对象(如果您更改内联表单中的默认值),我遇到了这种情况,因此进行了 hasattr 检查。漂亮的装饰器提示来自@shadfc 在Create OneToOne instance on model creation 中的回答

【讨论】:

    【解决方案6】:

    您可以使用在保存记录后触发的post_save-hook。有关 django 信号的更多文档,请参阅here。在this page,您可以找到有关如何在模型上应用挂钩的示例。

    【讨论】:

      【解决方案7】:

      只需创建一个创建并返回空 ModelA 的函数,并将“thing”上的默认命名参数设置为该函数。

      【讨论】:

        【解决方案8】:

        如果您在管理面板中使用 InlineForm,则可以这样做。

        但当然在其他情况下也需要检查(例如在 DRF 或手动模型实例创建中)

        from django.contrib import admin
        from django.forms.models import BaseInlineFormSet, ModelForm
        
        class AddIfAddParentModelForm(ModelForm):
            def has_changed(self):
                has_changed = super().has_changed()
        
                if not self.instance.id:
                    has_changed = True
        
                return has_changed
        
        class CheckerInline(admin.StackedInline):
            """ Base class for checker inlines """
            extra = 0
            form = AddIfAddParentModelForm
        

        【讨论】:

          【解决方案9】:

          我想你想使用django's model inheritance。如果以下语句为真,这将很有用:ModelA 是 ModelB(例如,Restaurant 是 Location)。

          你可以定义:

          class ModelB(models.Model):
              field1 = models.CharField(...)
          
          class ModelA(ModelB):
              field2 = models.CharField(...)
          

          现在您可以创建 ModelA 的实例并设置 field2 field1。如果保存此模型,它还将创建 ModelB 的实例,该实例获取分配的 field1 的值。这一切都是在幕后透明地完成的。

          虽然您可以执行以下操作:

          a1 = ModelA()
          a1.field1 = "foo"
          a1.field2 = "bar"
          a1.save()
          a2 = ModelA.objects.get(id=a1.id)
          a2.field1 == "foo" # is True
          a2.field2 == "bar" # is True
          b1 = ModelB.objects.get(id=a1.id)
          b1.field1 == "foo" # is True
          # b1.field2 is not defined
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-23
            • 2021-05-19
            • 1970-01-01
            • 2019-01-06
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多