22

2 つの単純なモデルがあります。1 つは映画を表し、もう 1 つは映画の評価を表します。

class Movie(models.Model):
    id = models.AutoField(primary_key=True)

    title = models.TextField()

class Rating(models.Model):
    id = models.AutoField(primary_key=True)

    movie = models.ForeignKey(Movie)
    rating = models.FloatField()

私の期待は、最初に と その映画を参照する を作成してMovieからReview、両方をデータベースにコミットできることMovieですReview

the_hobbit = Movie(title="The Hobbit")
my_rating = Rating(movie=the_hobbit, rating=8.5)
the_hobbit.save()
my_rating.save()

驚いたことにIntegrityError、null の外部キーを指定しようとしているという不満がまだ出Movieてきました。

IntegrityError: null value in column "movie_id" violates not-null constraint

printいくつかのステートメントを追加して、これを確認しました。

print "the_hobbit.id =", the_hobbit.id           # None
print "my_rating.movie.id =", my_rating.movie.id # None
print "my_rating.movie_id =", my_rating.movie_id # None

the_hobbit.save()

print "the_hobbit.id =", the_hobbit.id           # 3
print "my_rating.movie.id =", my_rating.movie.id # 3
print "my_rating.movie_id =", my_rating.movie_id # None

my_rating.save()                                 # raises IntegrityError

.movie属性は、非 を持っているインスタンスを参照していMovieますNone .idが、インスタンスが作成されたときに持っていた.movie_id値を保持しています。NoneMovie

.movie.idをコミットしようとしたときにDjango が検索することを期待していましたReviewが、どうやらそれはしていないようです。


さておき

私の場合、.save()一部のモデルでメソッドをオーバーライドして、保存する前に外部キーの主キーを再度検索することで、この動作に対処しました。

def save(self, *a, **kw):
    for field in self._meta.fields:
        if isinstance(field, ForeignKey):
            id_attname = field.attname
            instance_attname = id_attname.rpartition("_id")[0]
            instance = getattr(self, instance_attname)
            instance_id = instance.pk
            setattr(self, id_attname, instance_id)

    return Model.save(self, *a, **kw)

これはハッキーですが、私にとってはうまくいくので、この特定の問題の解決策を実際に探しているわけではありません


Django の動作の説明を探しています。Django はどの時点で外部キーの主キーを検索しますか? 具体的にお願いします。Django ソース コードへの参照が最適です。

4

5 に答える 5

10

ドキュメントで述べられているように:

キーワード引数は、モデルで定義したフィールドの名前にすぎません。モデルのインスタンス化はデータベースに影響を与えないことに注意してください。そのためには、save()が必要です。

モデルクラスにclassmethodを追加します。

class Book(models.Model):
    title = models.CharField(max_length=100)

    @classmethod
    def create(cls, title):
        book = cls(title=title)
        # do something with the book
        return book

book = Book.create("Pride and Prejudice")

カスタムマネージャーにメソッドを追加します(通常は推奨されます)。

class BookManager(models.Manager):
    def create_book(self, title):
        book = self.create(title=title)
        # do something with the book
        return book

class Book(models.Model):
    title = models.CharField(max_length=100)

    objects = BookManager()

book = Book.objects.create_book("Pride and Prejudice")

オリジン: https ://docs.djangoproject.com/en/dev/ref/models/instances/?from = olddocs#creating-objects

the_hobbitを割り当てるときは、Movieのインスタンスを割り当てるため、データベースにアクセスしません。'save'を呼び出すと、データベースはいっぱいになりますが、変数は、データベースの突然の変更に気付かずに、メモリ内のオブジェクトを指しています。

とはいえ、シーケンスの順序を変更すると、オブジェクトも効果的に作成されます。

the_hobbit = Movie(title="The Hobbit")
the_hobbit.save()
my_rating = Rating(movie=the_hobbit, rating=8.5)
my_rating.save()
于 2012-11-29T18:30:56.987 に答える
10

主な問題は、望まれるかどうかにかかわらず、副作用に関係しています。また、変数は実際には Python のオブジェクトへのポインターです。

モデルからオブジェクトを作成するとき、まだ保存していないため、主キーはまだありません。しかし、それを保存するとき、Django は既存のオブジェクトの属性を確実に更新する必要があるのでしょうか? 主キーは論理的ですが、他の属性が更新されることも期待できます。

その例として、Django の Unicode 処理があります。データベースに入力するテキストにどのような文字セットを指定しても、Django は Unicode を取得すると、Unicode を返します。しかし、(Unicode 以外の属性を持つ) オブジェクトを作成して保存した場合、Django は既存のオブジェクトのテキスト属性を変更する必要がありますか? それはすでに少し危険に聞こえます。これが (おそらく)、Django がデータベースに保存するように要求したオブジェクトのオンザフライ更新を行わない理由です。

データベースからオブジェクトを再ロードすると、すべてが設定された完全なオブジェクトが得られますが、変数が別のオブジェクトを指すようにもなります。そのため、「古い」 Movie オブジェクトへのポインターを評価に既に指定している場合、それはあなたの例では役に立ちません。

HeddeMovie.objects.create(title="The Hobbit")が言及したのは、ここでのトリックです。データベースから映画オブジェクトを返すため、すでに ID を持っています。

the_hobbit = Movie.objects.create(title="The Hobbit")
my_rating = Rating(movie=the_hobbit, rating=8.5)
# No need to save the_hobbit, btw, it is already saved.
my_rating.save()

(自分のオブジェクトとデータベースからのオブジェクトの違いにも問題がありました。新しく作成したオブジェクトが Unicode を出力しなかったときです。ブログに載せた説明は上記と同じですが、言葉遣いが少し異なります。)

于 2012-12-04T11:15:30.583 に答える
9

Django のソースを見ると、その答えは、Django が優れた API を提供するために使用するいくつかの魔法にあります。

オブジェクトをインスタンス化するRatingと、Django は (ただし、これをジェネリックにするために間接的に設定します)self.movieを に設定しthe_hobbitます。ただし、self.movie通常のプロパティではなく、 を通じて設定されます__set____set__メソッド (上にリンク) は、値 ( ) を見て、フィールドであるため、 の代わりにthe_hobbitプロパティを設定しようとします。ただし、は None であるため、代わりにに設定するだけです。評価を保存しようとすると、もう一度検索しようとしますが、失敗します ( .movie_idmovieForeignKeythe_hobbit.pkmoviethe_hobbitmovie_idmovie

興味深いことに、この動作はDjango 1.5で変更されているようです。

それ以外の

setattr(value, self.related.field.attname, getattr(
    instance, self.related.field.rel.get_related_field().attname))
# "self.movie_id = movie.pk"

今はそうです

    related_pk = getattr(instance, self.related.field.rel.get_related_field().attname)
    if related_pk is None:
        raise ValueError('Cannot assign "%r": "%s" instance isn\'t saved in the database.' %
                            (value, instance._meta.object_name))

あなたの場合、より役立つエラーメッセージが表示されます。

于 2012-12-10T12:13:21.613 に答える
0

コメントできませんので、完成させてください...

また、データベース側の動作を変更することもできます(ただし、この場合はそうではありません)。これは、同様の問題を引き起こす可能性のあるいくつかのテストを実行する場合に役立つ場合があります(コミットで実行され、ロールバックされるため)。TransactionalTestCaseにテストをパックするのではなく、アプリの実際の動作にできるだけ近いテストを維持するために、このハッキーコマンドを使用する方がよい場合があります。

制約のプロパティと関係があります...次のSQLコマンドを実行すると、問題も解決します(PostgreSQLのみ)。

SET CONSTRAINTS [ALL / NAME] DEFERRABLE INITIALLY IMMEDIATE;
于 2012-12-04T15:24:01.040 に答える
0

私の意見では、ホビット オブジェクトでsave()メソッドを呼び出した後、そのオブジェクトは保存されます。ただし、 my_ratingオブジェクトに存在するローカル参照は、データベースに存在する値で自分自身を更新する必要があることを実際には認識していません。

したがって、my_rating.movi​​e.idを呼び出すと、django はムービー オブジェクトに対する db クエリの必要性を再び認識しないため、そのオブジェクトのローカル インスタンスに含まれる値であるNoneを取得します。

しかし、my_rating.movi​​e_idは、ローカル インスタンスに存在するデータに依存しません。これは、django にデータベースを調べて、外部キー関係を通じてそこにある情報を確認する明示的な方法です。

于 2012-12-05T13:16:51.067 に答える