Django: Почему метод get_queryset() менеджера моделей (объектов) выполняется на этапе инициализации?

Я создаю SAAS сайт, который в настоящее время использует django_tenants с postgresql для физического разделения арендаторов на разные схемы. Я решил перенести все в одну схему и внедрить логическое разделение данных вместо этого.

На этапе проверки концепции я добавил новое промежуточное ПО с новой логикой работы с арендаторами, а существующее промежуточное ПО (из django_tenants) оставил на месте. Из моих настроек:

MIDDLEWARE = [
    'tenants.middleware.main.TenantMainMiddleware',
    'utilities.middleware.TenantMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Существующий из django_tenants - первый: TenantMainMiddleware - мой новый - следующая строка вниз: TenantMiddleware

В принципе, я добавлю поле ForeignKey в каждую модель, которой необходимо разделение на арендаторов, и мое промежуточное ПО будет загружать соответствующий экземпляр арендатора на основе поддоменной части домена. Например: demo.mysite.com - вызовет поиск по "demo" в таблице арендаторов. Полученный объект арендатора будет добавлен к объекту запроса, который, в свою очередь, хранится в локальном хранилище потока.

Вот мое промежуточное программное обеспечение:

class TenantMiddleware(MiddlewareMixin):

    def process_request(self, request):
        current_tenant = connection.tenant                     # This is temporary to         
        connection.set_schema_to_public()                      # allow the existing
        subdomain = self.get_subdomain_from_request(request)   # tenant code to still work

        try:
            tenant = VTenant.objects.get(subdomain__iexact=subdomain)
        except VTenant.DoesNotExist:
            raise APIInvalidRequest(_('No tenant for subdomain "%s".') % subdomain)

        request.vtenant = tenant
        setattr(_thread_locals, 'request', request)
        connection.set_tenant(current_tenant)        # Temp code for original middleware

Причина, по которой моя новая модель арендатора названа "VTenant", заключается в том, что она не будет конфликтовать с оригинальной моделью Tenant, которая все еще используется, пока я не закончу проверку концепции.

Я также создал два метода для получения запроса и/или объекта арендатора из _thread_locals:

def get_current_request():
    return getattr(_thread_locals, 'request', None)

def get_current_tenant():
    request = get_current_request()
    if request:
        return getattr(request, 'vtenant', None)

Затем я создал ModelManager с учетом арендаторов, чтобы применять фильтрацию по арендаторам всякий раз, когда get_queryset() вызывается для модели.

class TenantAwareManager(models.Manager):

    def get_queryset(self):
        tenant = get_current_tenant()
        if not tenant:
            raise APIUnexpectedCondition()
        return super().get_queryset().filter(tenant=tenant)

И затем все менеджеры для моделей с ограничениями по аренде расширяются из этого класса:

class ClientManager(TenantAwareManager):

    def get_by_natural_key(self, name):
        return self.get(name__iexact=name)


class Client(TenantModelMixin, models.Model):

    objects = ClientManager()

    name = models.TextField(
        verbose_name=_('Name'),
        unique=True,
    )
    ...aditional fields omitted...

TenantModelMixin просто добавляет поле арендатора в каждую модель. Таким образом, когда я загружаю определенную конечную точку в API сайта, она работает нормально, и фильтрует список Клиентов только до тех, которые принадлежат арендатору в домене ("demo" в данном случае).

Проблема возникает, когда я пытаюсь запустить команды управления. В этих случаях промежуточное ПО не запускается - я полагаю, потому что при выполнении команды управления не происходит фактического запроса. Но на этапе инициализации (компиляции, загрузки, чего угодно), всякий раз, когда ссылаются на объект Client.objects, выполняется get_queryset() в TenantAwareManager и вызывает исключение APIUnexpectedCondition, потому что объект vtenant не существует (и, фактически, _thread_locals, вероятно, тоже не существует).

Первое место, которое вызвало ошибку, было в классе FilterSet:

class InvoiceFilters(filters.FilterSet):
    date_from = filters.DateFilter(field_name='date', lookup_expr='gte')
    date_to = filters.DateFilter(field_name='date', lookup_expr='lte')
    due_date_from = filters.DateFilter(field_name='due_date', lookup_expr='gte')
    due_date_to = filters.DateFilter(field_name='due_date', lookup_expr='lte')

    class Meta:
        model = Invoice
        fields = {
            'id': ['exact'],
            'number': ['exact', 'iexact'],
            ...omitted...
            'client__name': ['exact', 'iexact', 'icontains'],
        }

Нарушающей строкой является фильтр для "client__name". Если я закомментирую ее, то следующее обращение к Client.objects вызовет исключение. И так далее, до тех пор, пока я буду заботиться (и смогу) закомментировать оскорбительный код.

Я не очень хорошо знаком со всей фазой загрузки/компиляции/инициализации (любой термин подходит), но я не понимаю, почему мой метод get_queryset() фактически выполняется во время этой фазы. В отладке я поставил точку останова на первой строке кода в моей команде управления, и исключение было вызвано до того, как я достиг этой точки останова.

Вернуться на верх