2017-11-30 4 views
0

I 다음 (간체) 모델이 : 이제장고의 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]에 접근.

공통 모델은 여러 다른 모델에 상속되며 명백히 다른 장소에서 여러 번 인스턴스화되며 getattr()은이 Directory.objects.raw() 메서드를 사용할 때까지 전혀 문제를 일으키지 않습니다. 왜 안돼? 장고에있는 버그로 의심 될 것입니다.

+0

예를 들어 필수적인 클래스 인 Meta를 추가했습니다. 반대로 예제를 단순화하고 일관성있게 유지하면 문제를 재현 할 수 있습니다. 둘 다 Stackoverflow에서 권장됩니다. 다행히 당신의 질문은 충분히 흥미 롭고 고칠 수 있습니다. – hynekcer

+0

많은 감사를드립니다!맞습니다. 예가 더 간단하고 더 완벽 할 수 있습니다. 곧 고칠 것입니다. – hannu40k

답변

1

예, 문제는 Django ticket #22858에 알려져 있습니다. 당신이 __init__()에서 필드를 액세스하는 경우

, 당신은 검색어 세트의 각 항목에 대한 데이터베이스를 다시 타격 피하기 위해 only() 통화 모두에 포함시켜야합니다. 당신이 (모든 지연된 필드를 한 번에, 한 번에 하나의) 해당 필드에 액세스하는 경우

각 이연 필드가 데이터베이스에서 검색됩니다

defer() 문서의 일반적인 결과이다. 하지 __init__ 방법은 두 개 이상의 지연 필드에 액세스하는 경우

문제는 (즉, 필드 .raw(), .only(), .defer() 방법으로 연기) 다음 모든 액세스 데이터베이스 쿼리를 트리거하고 새 임시 인스턴스가 만들어집니다 재현 할 수 있습니다 같은 분야가 다시 필요합니다.

문제의을 최소한 예를

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)