2017-11-30 26 views
0

が発生します。今すぐDjangoのORM Model.objects.raw()クエリは、私は、次の(簡体字)のモデル持っている無限の再帰エラー

class Common(models.Model): 

    id = models.BigAutoField(primary_key=True, editable=False) 
    date_created = models.DateTimeField() 

    class Meta: 
     abstract = True 

    def __init__(self, *args, **kwargs): 
     super(Common, self).__init__(*args, **kwargs) 
     self._initial_data = {} 
     self.track_fields(
      'date_created', 
     ) 

    def track_fields(self, *args): 
     for field_name in args: 
      self._initial_data[field_name] = getattr(self, field_name) 


class Directory(Common): 

    directory_path = models.TextField() 
    parent_directory = models.ForeignKey('self') 

class File(Common): 

    removed = models.BooleanField() 
    parent_directory = models.ForeignKey(Directory) 

を、私は簡略化した例(このようないくつかのオブジェクトを照会しようとしています、SQLは)最初の場所で使用された理由に全く注意を払っていない:

sql_select_dirs_of_deleted_files = ''' 
    select d.id, directory_path 
    from directory d 
    left join file f on f.parent_directory_id = d.id 
    where f.removed = true and f.id in %s 
    group by d.id, directory_path 
    order by directory_path asc 
    ''' 
dirs_of_deleted_files = Directory.objects.raw(sql_select_dirs_of_deleted_files, [tuple(file_ids)]) 
parent_of_top_dir = dirs_of_deleted_files[0].parent_directory 

dirs_of_deleted_files[0]へのアクセスライン上で無限再帰エラーが発生し

self._initial_data[field_name] = getattr(self, field_name) 
共通モデルの

私は継承とgetattrの再帰問題を認識していますが、models.Model.__getattribute__(self, field_name)を使用しても違いはありません。しかし、何の代わりに作業を行うことである。

dirs_of_deleted_files = Directory.objects \ 
    .filter(files__in=file_ids, files__removed=True) \ 
    .distinct('id', 'directory_path') \ 
    .order_by('directory_path') 

dirs_of_deleted_files[0]にアクセスしても、無限再帰エラーが発生することはありません。

共通モデルは他のいくつかのモデルに継承されており、別の場所で多くのインスタンスを明示的にインスタンス化し、このDirectory.objects.raw()メソッドを使用するまではgetattr()は決して問題を引き起こしませんでした。何故なの?私はDjangoのバグだと思うだろうが、私は自分の判断を保つだろう。

+0

あなたの例に不可欠なクラスMetaを追加しました。これとは対照的に、この例を簡素化し、一貫性を維持して問題を再現できるようにすることもできます。どちらもStackoverflowでお勧めします。幸いにもあなたの質問は十分に面白く、修正することができます。 – hynekcer

+0

非常に感謝しています!あなたは正しいです、その例は、より簡単でより完全なものになります。まもなく修正されます。 – hannu40k

答えて

1

はい、この問題はDjango ticket #22858で認識されています。

あなたは__init__()でフィールドにアクセスする場合は、クエリセット内の各項目に対して再度データベースを打つ避けるために、あなたのonly()呼び出しのすべてでそれを含める必要があります。あなたは(すべてではない、据え置きフィールド一度、一度に一つの)そのフィールドにアクセスする場合

各繰延フィールドはデータベースから取得されます。

それはdefer()ドキュメントの一般的な結果です。い__init__方法は、任意の2つの以上の遅延フィールドにアクセスする場合

問題は(すなわちフィールドが.raw().only().defer()方法により延期)、その後、すべてのアクセスは、データベースクエリをトリガし、新しい一時的なインスタンスがあることに作成された再現できます再び同じ分野が必要です。

問題のminimalized例

class SomeModel(models.Model): 
    a = models.IntegerField() 
    b = models.IntegerField() 

    def __init__(self, *args, **kwargs): 
     super(SomeModel, self).__init__(*args, **kwargs) 
     (self.a, self.b) 

class Test(TestCase): 
    def test(self): 
     SomeModel.objects.create(a=0, b=0) 
     SomeModel.objects.only('id')[0] 
     # RuntimeError: maximum recursion depth exceeded 

FIX

あなたはドン場合は、おそらく、時にはフィールドを追跡する必要がないので、あなたが条件 field_name in self.__dict__ことによってそれを修正することができます

」それをロードする必要があります。

def track_fields(self, *args): 
    for field_name in args: 
     if field_name in self.__dict__: 
      self._initial_data[field_name] = getattr(self, field_name) 
関連する問題