web-dev-qa-db-ja.com

Djangoは、関連する1対1のモデルを自動的に作成できますか?

異なるアプリに2つのモデルがあります。modelAとmodelBです。彼らは1対1の関係にあります。 Djangoは、modelAが保存されるときに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を作成してビューに保存するだけで済みますか?

モデルが別のアプリにあると編集しました。

45
vagabond

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デコレーターやget_object_or_None関数やget_config関数などのgemを含む素晴らしい小さなライブラリです)

42
John Paulett

M000が指摘したように、モデルはさまざまなアプリに存在します。作成していないアプリを使用することが多いため、更新を許可するには、論理的に関連するモデルを作成するための分離された方法が必要です。これは私の意見では推奨されるソリューションであり、非常に大規模なプロジェクトで使用しています。

信号を使用して:

あなたの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ファイルを保持する別のアプリを作成できます。

28
Dmitry

最も簡単な方法は、ModelAの saveメソッドをオーバーライドする です。

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)
14
Jarret Hardie

私はいくつかの異なる回答を集めました(それらのどれも私のためにすぐに機能しなかったため)、これを思いつきました。きれいだと思ったので共有します。

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 Adminは関連するオブジェクトを作成しようとします(インラインフォームのデフォルト値を変更した場合)。実行されたため、hasattrチェックが実行されました。Niceデコレータのヒントは、@ shadfcの回答からです モデルの作成時にOneToOneインスタンスを作成

5
jtlai

少し遅れていることは承知していますが、よりクリーンでエレガントなソリューションを考え出しました。このコードを考えてみましょう:

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)

もちろん、関連オブジェクトの整数IDを返す限り、ラムダも使用できます:)

4
realmaniek

レコードが保存された後にトリガーされる post_save-hook を使用できます。 Djangoシグナルの詳細については、 ここ を参照してください。- このページ で、モデルにフックを適用する方法の例を見つけます。

0
schneck

空のModelAを作成して返す関数を作成し、「thing」のデフォルトの名前付き引数をその関数に設定します。

0
user287544

管理パネルで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
0
megajoe

Djangoのモデル継承 を使いたいと思います。これは、次のステートメントが当てはまる場合に役立ちます。ModelAはModelBです(たとえば、RestaurantはLocationです)。

以下を定義できます。

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


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

これで、ModelAのインスタンスを作成し、field2field1を設定できます。このモデルを保存すると、field1の値を割り当てるModelBのインスタンスも作成されます。これはすべて、舞台裏で透過的に行われます。

ただし、次のことができます。

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
0