11

でアプリを高速化しようとしていprefetch_relatedます。リレーションをたどることがGenericForeignKeyでき、さらに深くすることもできます__が、関連するモデルにそのようなフィールドがない場合、残念ながら失敗します。

モデル構造の例を次に示します

class ModelA(models.Model):
    event_object = models.ForeignKey(SomeModelA)

class ModelB(models.Model):
    event = models.ForeignKey(SomeModelB)

class ModelC(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey()

したがってModelC、インスタンスは または のいずれかを指すことができModelAますModelB。そして、このようなクエリセットを使用して、A モデルと B モデルの両方をプリフェッチできます。ModelC.objects.all().prefetch_related('content_object')残念ながら、イベント オブジェクト (SomeModelAまたはSomeModelB)も選択する必要があります。

走ろうとしたら

ModelC.objects.all().prefetch_related('content_object', 'content_object__event_object')

ModelCを指すインスタンスしかない場合は機能しますが、フィールドがなく代わりにあるModelAために失敗する場合があります。ModelBevent_objectevent

このモデルはコード全体の多くの場所で使用されるため、フィールドの名前を変更することはお勧めできません。フィールド/列のエイリアスを作成する方法があるのだろうか。

私はこのようにしようとしていました:

class ModelB(models.Model):
    event = models.ForeignKey(SomeModelB)
    event_object = models.ForeignKey(SomeModelB, db_column='event_id', related_name='+')

DBテーブルの同じ列を指す2つのフィールドを作成します。ただし、これはsaveメソッドを壊すため機能しません。Django は、UPDATE1 つの列が 2 回配置される SQL クエリを作成し、DatabaseError を取得します。

そのようなエイリアスを作成する方法はありますか? またはprefetch_related、例外をスローしないようにする別の解決策があるでしょうか?

更新:saveメソッドには、update_fieldsこのフィールドを除外するために使用できるパラメーターがあります。ただし、1.5 で導入され、1.4 を使用しています。だから私は答えを探し続けます。

更新 #2 : @shx2 からトレースバックの提供を依頼されました。2 つの可能なトレースバックがあります。1st - 最初のオブジェクトに属性がない場合:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 72, in __repr__
    data = list(self[:REPR_OUTPUT_SIZE + 1])
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 97, in __iter__
    len(self)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 89, in __len__
    self._prefetch_related_objects()
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 570, in _prefetch_related_objects
    prefetch_related_objects(self._result_cache, self._prefetch_related_lookups)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1664, in prefetch_related_objects
    (attr, first_obj.__class__.__name__, lookup))
AttributeError: Cannot find 'event_object' on ModelB object, 'content_object__event_object' is an invalid parameter to prefetch_related()

そして、prefetch_related パラメーターが最初のオブジェクトに対して有効である場合、2 番目のトレースバックを取得します。

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 72, in __repr__
    data = list(self[:REPR_OUTPUT_SIZE + 1])
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 97, in __iter__
    len(self)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 89, in __len__
    self._prefetch_related_objects()
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 570, in _prefetch_related_objects
    prefetch_related_objects(self._result_cache, self._prefetch_related_lookups)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1680, in prefetch_related_objects
    obj_list, additional_prl = prefetch_one_level(obj_list, prefetcher, attr)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1803, in prefetch_one_level
    qs = getattr(obj, attname).all()
AttributeError: 'ModelB' object has no attribute 'event_object'
4

1 に答える 1

2

django のバグか見落としのようです。回避策として、2 段階のプリフェッチを行うカスタム マネージャーを定義してみてください。

from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType

class PrefetchWorkaroundManager(models.Manager):
    def get_queryset(self):
        q = super(PrefetchWorkaroundManager, self).get_queryset()
        content_typeA = ContentType.objects.get_for_model(ModelA)
        content_typeB = ContentType.objects.get_for_model(ModelB)
        return q.filter(content_type__pk = content_typeA.id).prefetch_related('content_object', 'content_object__event_object') | \
               q.filter(content_type__pk = content_typeB.id).prefetch_related('content_object', 'content_object__event')

class ModelC(models.Model):
    ...

    objects_prefetched = PrefetchWorkaroundManager()

プリフェッチを実行する各呼び出し元は、次ModelC.objects_prefetchedの代わりにアクセスする必要がありModelC.objectsます。

ModelC.objects_prefetched.filter(...)

確かに、私はそれをテストしていないので、そのままでは機能しない可能性があります。しかし、このアプローチは健全だと思います。

于 2013-03-19T20:08:28.330 に答える