django-multitenant
django-multitenant copied to clipboard
Tenant filter does not work in django-filter ModelChoiceFilter
Problem: sql-clause "table"."tenant_id" = current_tenant_value
dissapers from query for django-filter's ModelChoiceFilter when order_by
is added to queryset.
Code to reproduce.
models.py
from django_multitenant.fields import TenantForeignKey
from django_multitenant.mixins import TenantManagerMixin, TenantQuerySet
from django_multitenant.models import TenantModel
from django_multitenant.utils import get_current_tenant, set_current_tenant
class CompanyObjectModel(TenantModel):
"""Абстрактный класс для разделяемых по компаниям моделей"""
company = models.ForeignKey(
Company,
default=get_current_tenant,
on_delete=models.PROTECT
)
tenant_id = 'company_id'
class Meta:
abstract = True
unique_together = ["id", "company"]
class EventClass(CompanyObjectModel):
"""
Описание шаблона мероприятия (Класс вид).
Например, тренировки в зале бокса у Иванова по средам и пятницам
"""
name = models.CharField("Наименование", max_length=100)
location = TenantForeignKey(
Location,
on_delete=models.PROTECT,
verbose_name="Место проведения")
coach = TenantForeignKey(
Coach,
on_delete=models.PROTECT,
verbose_name="Тренер")
date_from = models.DateField("Начало тренировок", null=True, blank=True)
date_to = models.DateField("Окончание тренировок", null=True, blank=True)
planned_attendance = models.PositiveSmallIntegerField(
"Плановая посещаемость",
null=True,
blank=True,
validators=[MinValueValidator(0)]
)
objects = EventClassManager()
class EventClassManager(TenantManagerMixin, models.Manager):
def active(self):
return self.get_queryset().filter(
Q(date_to__isnull=True) | Q(date_to__gte=date.today())
)
def in_range(self, day_start, day_end):
return self.get_queryset().filter(
(
Q(date_from__isnull=False) & Q(date_to__isnull=False) &
Q(date_from__lte=day_end) & Q(date_to__gte=day_start)
) | (
Q(date_from__isnull=False) & Q(date_to__isnull=True) &
Q(date_from__lte=day_end)
) | (
Q(date_from__isnull=True) & Q(date_to__isnull=False) &
Q(date_to__gte=day_start)
) | (
Q(date_from__isnull=True) & Q(date_to__isnull=True)
)
)
filters.py
import django_filters
from crm import models
class VisitReportFilterNew(django_filters.FilterSet):
event_class = django_filters.ModelChoiceFilter(
label='Группа:',
field_name='event_class',
queryset=models.EventClass.objects,
empty_label=None,
widget=forms.Select(
attrs={
'class': 'selectpicker form-control',
'title': 'Группа',
}
),
required=False,
)
class Meta:
model = models.Event
fields = ('date',)
report.html
<div class="col-6 col-md-3">
<div class="form-group select">
<label for="id_event_class">{{ filter.form.event_class.label }}</label>
{{ filter.form.event_class }}
</div>
</div>
in ModelChoiseFilter queryset=models.EventClass.objects
generates valid SQL-query:
DECLARE "_django_curs_140336784169760_1" NO SCROLL
CURSOR WITH HOLD
FOR SELECT "crm_eventclass"."id",
"crm_eventclass"."company_id",
"crm_eventclass"."name",
"crm_eventclass"."location_id",
"crm_eventclass"."coach_id",
"crm_eventclass"."date_from",
"crm_eventclass"."date_to",
"crm_eventclass"."planned_attendance"
FROM "crm_eventclass"
WHERE "crm_eventclass"."company_id" = 71
But when I try to sort filter values changing queryset in ModelChoiseFilter to queryset=models.EventClass.objects.order_by("name")
. The SQL-query is ordered, but tenant filter is missing, it returns all rows from table:
DECLARE "_django_curs_140075094870816_1" NO SCROLL
CURSOR WITH HOLD
FOR SELECT "crm_eventclass"."id",
"crm_eventclass"."company_id",
"crm_eventclass"."name",
"crm_eventclass"."location_id",
"crm_eventclass"."coach_id",
"crm_eventclass"."date_from",
"crm_eventclass"."date_to",
"crm_eventclass"."planned_attendance"
FROM "crm_eventclass"
ORDER BY "crm_eventclass"."name" ASC
Versions:
Django==2.1.7
django-multitenant==2.0.0
django-filter==2.1.0
Does the filter disappear again if you change the keyword argument from queryset=models.EventClass.objects
to queryset=models.EventClass.objects.all()
?
Yes, with queryset=models.EventClass.objects.all()
filter disappears.
django-multitenant
needs to get the tenant from request before filtering. In your example, filtering happens at import time, which is before request. With django-filter
, instead of a queryset, you can use a callable which takes the ongoing request and returns a queryset. For example, you can change queryset=models.EventClass.objects.order_by("name")
to queryset=lambda _: models.EventClass.objects.order_by("name")
(_
is the ignored request parameter here). With that change, the filtering is done after the request sent and the current tenant can be detected.