12

DjangoのコンテンツタイプフレームワークからGenericRelation fieldというものを誤解しているに違いありません。GenericRelationのinverseの使い方

最小限の自己完結型の例を作成するには、チュートリアルのpollsサンプルアプリケーションを使用します。 Choiceモデルに、一般的な外部キーフィールドを追加し、新しいThingモデル作成:クリーンDBで

class Choice(models.Model): 
    ... 
    content_type = models.ForeignKey(ContentType) 
    object_id = models.PositiveIntegerField() 
    thing = GenericForeignKey('content_type', 'object_id') 

class Thing(models.Model): 
    choices = GenericRelation(Choice, related_query_name='things') 

を、テーブルを同期、およびいくつかのインスタンスを作成しますので、これまでのところ

>>> poll = Poll.objects.create(question='the question', pk=123) 
>>> thing = Thing.objects.create(pk=456) 
>>> choice = Choice.objects.create(choice_text='the choice', pk=789, poll=poll, thing=thing) 
>>> choice.thing.pk 
456 
>>> thing.choices.get().pk 
789 

を良い - 関係は、インスタンスから両方向に働く。しかし、クエリセットから、逆の関係は非常に奇妙です:

>>> Choice.objects.values_list('things', flat=1) 
[456] 
>>> Thing.objects.values_list('choices', flat=1) 
[456] 

逆の関係がthingから再びIDを私に与えなぜ?私の代わりに次のような結果と同等の選択の主キーを、期待:

>>> Thing.objects.values_list('choices__pk', flat=1) 
[789] 

これらのORMクエリは次のようにSQLを生成:

>>> print Thing.objects.values_list('choices__pk', flat=1).query 
SELECT "polls_choice"."id" FROM "polls_thing" LEFT OUTER JOIN "polls_choice" ON ("polls_thing"."id" = "polls_choice"."object_id" AND ("polls_choice"."content_type_id" = 10)) 
>>> print Thing.objects.values_list('choices', flat=1).query 
SELECT "polls_choice"."object_id" FROM "polls_thing" LEFT OUTER JOIN "polls_choice" ON ("polls_thing"."id" = "polls_choice"."object_id" AND ("polls_choice"."content_type_id" = 10)) 

Djangoのドキュメントは、一般的に優れているが、私はできません2番目のクエリがなぜその動作のドキュメントを見つけるのか理解してください。間違ったテーブルからデータを完全に返すようです。

+0

*注意:* Djangoのバージョンは '(1、7、11、 'final'、0)'です。私はこれをDjango 1.8で再現することはできません。 – wim

+0

これは、Django 1.7では1.8に修正することを決めたということでしょうか? – mgilson

+0

可能ですが、リリースノートの中で高低を検索して見つけられませんでした。 'git bisect'がそれを見つけることができると思います.... – wim

答えて

7

TL; DRこれはDjango 1.7のバグで、Django 1.8で修正されました。

変更をマスターし、直接行って、あまりない非推奨期間、下行きませんでした下位互換性を維持することが本当に困難だったことを考えると驚くべきことです。もっと驚くべきことは、修正プログラムが現在動作しているコードの動作を変更するため、1.8 release notesにその問題が記載されていないことです。

残りの回答は、git bisect runを使用してコミットを見つけた方法の説明です。それは何よりも私の自身の参考のためにここにあります。だから、大きなプロジェクトをもう一度二等分する必要があるなら、私はここに戻ってきます。


最初に、問題を再現するためのdjangoクローンとテストプロジェクトをセットアップしました。私はここでvirtualenvwrapperを使っていましたが、あなたが望むように分離を行うことができます。

cd /tmp 
git clone https://github.com/django/django.git 
cd django 
git checkout tags/1.7 
mkvirtualenv djbisect 
export PYTHONPATH=/tmp/django # get django clone into sys.path 
python ./django/bin/django-admin.py startproject djbisect 
export PYTHONPATH=$PYTHONPATH:/tmp/django/djbisect # test project into sys.path 
export DJANGO_SETTINGS_MODULE=djbisect.mysettings 

次のファイルを作成します。今、私たちはgit bisect runで使用するtest_script.pyを作成し、作業プロジェクトを持っている

# /tmp/django/djbisect/djbisect/mysettings.py 
from djbisect.settings import * 
INSTALLED_APPS += ('djbisect',) 

#!/usr/bin/env python 
import subprocess, os, sys 

db_fname = '/tmp/django/djbisect/db.sqlite3' 
if os.path.exists(db_fname): 
    os.unlink(db_fname) 

cmd = 'python /tmp/django/djbisect/manage.py migrate --noinput' 
subprocess.check_call(cmd.split()) 

import django 
django.setup() 

from django.contrib.contenttypes.models import ContentType 
from djbisect.models import GFKmodel, GRmodel 

ct = ContentType.objects.get_for_model(GRmodel) 
y = GRmodel.objects.create(pk=456) 
x = GFKmodel.objects.create(pk=789, content_type=ct, object_id=y.pk) 

query1 = GRmodel.objects.values_list('related_gfk', flat=1) 
query2 = GRmodel.objects.values_list('related_gfk__pk', flat=1) 

print(query1) 
print(query2) 

print(query1.query) 
print(query2.query) 

if query1[0] == 789 == query2[0]: 
    print('FIXED') 
    sys.exit(1) 
else: 
    print('UNFIXED') 
    sys.exit(0) 

# /tmp/django/djbisect/djbisect/models.py 
from django.db import models 
from django.contrib.contenttypes.models import ContentType 
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation 

class GFKmodel(models.Model): 
    content_type = models.ForeignKey(ContentType) 
    object_id = models.PositiveIntegerField() 
    gfk = GenericForeignKey() 

class GRmodel(models.Model): 
    related_gfk = GenericRelation(GFKmodel) 

も、このいずれかを

スクリプトは実行可能でなければならないので、フラグにchmod +x test_script.pyを追加してください。これは、Djangoがクローンに登録されているディレクトリ、つまり/tmp/django/test_script.pyにあります。これは、import djangoがsite-packagesからのバージョンではなく、ローカルでチェックアウトされたdjangoプロジェクトを最初にピックアップするからです。

gitの二分のユーザーインターフェースは、見つけるために設計されていたところ、あるバグが修正さたとき、あなたが見つけるためにしようとしているとき登場バグなので、「悪い」と「良い」の通常の接頭辞が後方にあります。これはやや逆さまに見えるかもしれませんが、バグがあればテストスクリプトは成功(リターンコード0)し、バグが修正された場合は(ゼロ以外のリターンコードで)失敗するはずです。これは私を数回上手くしました!

git bisect start --term-new=fixed --term-old=unfixed 
git bisect fixed tags/1.8 
git bisect unfixed tags/1.7 
git bisect run ./test_script.py 

このプロセスでは、最終的にバグが修正されたコミットを見つける自動検索が行われます。 Django 1.7とDjango 1.8の間にはたくさんのコミットがあったので、時間がかかりました。

SELECT "djbisect_gfkmodel"."object_id" FROM "djbisect_grmodel" LEFT OUTER JOIN "djbisect_gfkmodel" ON ("djbisect_grmodel"."id" = "djbisect_gfkmodel"."object_id" AND ("djbisect_gfkmodel"."content_type_id" = 8)) 

クエリが(間違ったテーブルからデータを取得します)不正なSQLから変更されている場所を正確にコミットしています

1c5cbf5e5d5b350f4df4aca6431d46c767d3785a is the first fixed commit 
commit 1c5cbf5e5d5b350f4df4aca6431d46c767d3785a 
Author: Anssi Kääriäinen <[email protected]> 
Date: Wed Dec 17 09:47:58 2014 +0200 

    Fixed #24002 -- GenericRelation filtering targets related model's pk 

    Previously Publisher.objects.filter(book=val) would target 
    book.object_id if book is a GenericRelation. This is inconsistent to 
    filtering over reverse foreign key relations, where the target is the 
    related model's primary key. 

:それは1362の改正、およそ10段階、そして最終的に出力を二分しました正しいバージョン:

SELECT "djbisect_gfkmodel"."id" FROM "djbisect_grmodel" LEFT OUTER JOIN "djbisect_gfkmodel" ON ("djbisect_grmodel"."id" = "djbisect_gfkmodel"."object_id" AND ("djbisect_gfkmodel"."content_type_id" = 8)) 

もちろん、コミットハッシュから、githubでプルリクエストとチケットを簡単に見つけることができます。 Djangoを二分することは、移行のために設定するのが難しいかもしれません!

1

コメント - 遅すぎる答えを - 最も削除

A問題#24002の後方互換性のない修正のない重要な結果がGenericRelatedObjectManager(例えばthingsは)長い時間を設定するクエリの動作を停止し、それを使用することができることです唯一のフィルターなど

>>> choice.things.all() 
TypeError: unhashable type: 'GenericRelatedObjectManager' 
# originally before 1c5cbf5e5: [<Thing: Thing object>] 

のは、バージョン1.8.3で、マスターブランチに#24940で半年後に修正されました。この問題は重要ではありませんでした。一般名thingはクエリ(choice.thing)なしで簡単に機能するため、この使用法が文書化されているか文書化されていないかは不明です。

ドキュメント:Reverse generic relationsrelated_query_nameの設定

は戻って、これと関連するオブジェクトから関係を作成します。これにより、関連するオブジェクトからのクエリとフィルタリングが可能になります。

ジェネリックのみの代わりに特定のリレーション名を使用できるといいでしょう。 docs:taged_item.bookmarksの例はtaged_item.content_objectより読みやすいですが、それを実装するのは価値がありません。

関連する問題