Как отфильтровать ModelChoiceFilter текущим пользователем с помощью django-filter

Я использую django-filter, который отлично работает, но у меня возникла проблема с фильтрацией раскрывающегося списка вариантов (основанного на модели) текущим пользователем. Это довольно простой и распространенный сценарий, когда у вас есть дочерняя таблица, которая имеет отношение «многие к одному» к родительской таблице. Я хочу отфильтровать таблицу дочерних записей, выбрав родителя. Это все довольно просто, стандартные вещи. Дегтярная ложка — это когда родительские записи создаются разными пользователями, и вы хотите показать в раскрывающемся списке только родительские записи, принадлежащие текущему пользователю.

Вот мой код из filter.py

import django_filters
from django import forms
from .models import Project, Task
from django_currentuser.middleware import get_current_user, get_current_authenticated_user

class MasterListFilter(django_filters.FilterSet):
    project = django_filters.ModelChoiceFilter(
        label='Projects',
        name='project_fkey',
        queryset=Project.objects.filter(deleted__isnull=True, user_fkey=3).distinct('code')
        )

    class Meta:
        model = Task
        fields = ['project']

    @property
    def qs(self):
        parent = super(MasterListFilter, self).qs
        user = get_current_user()        
        return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id) 

Этот бит работает нормально:

@property
    def qs(self):
        parent = super(MasterListFilter, self).qs
        user = get_current_user()        
        return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id) 

Это фильтрует мой основной список, чтобы отображались только записи, для которых установлен главный флаг, которые не были удалены и принадлежат текущему пользователю. Это именно то, что я хочу.

Этот следующий бит также работает и дает мне отфильтрованный раскрывающийся список, который я ищу, потому что я жестко запрограммировал 3 как user.id

queryset=Project.objects.filter(deleted__isnull=True, user_fkey=3).distinct('code'),

Очевидно, я не хочу иметь жестко закодированный идентификатор. Мне нужно получить значение текущего пользователя. Следуя той же логике, которая используется для фильтрации основной таблицы, я получаю это.

class MasterListFilter(django_filters.FilterSet):
    **user = get_current_user()**
    project = django_filters.ModelChoiceFilter(
        label='Projects',
        name='project_fkey',
        queryset=Project.objects.filter(deleted__isnull=True, user_fkey=**user.id**).distinct('code')
        )

Однако это ненадежно, так как иногда показывает правильный список, а иногда нет. Например, если я вхожу в систему, и он не показывает список (т.е. он показывает только «---------»), а затем я перезапускаю свою службу apache2, она снова начинает работать, а затем в какой-то момент снова выпадает . Понятно, что это не долгосрочное решение.

Итак, как мне надежно получить текущего пользователя в моем filter.py, чтобы я мог использовать его для фильтрации моего раскрывающегося списка фильтров.

Заранее спасибо и удачного кодирования.

EDIT: Итак, следуя предложению Wiesion, я изменил свой код, как было предложено, но все еще получаю сообщение об ошибке None Type, говорящее, что у пользователя нет идентификатора атрибута. В основном кажется, что я не получаю текущего пользователя. Итак, вернувшись к документам и попытавшись объединить их предложение с Wiesion (чье объяснение имеет смысл - спасибо Wiesion), я пришел к следующему:

def Projects(request):
    if request is None:
        return Project.objects.none()
    return lambda req: Project.objects.filter(deleted__isnull=True, user_fkey=req.user.id)

class MasterListFilter(django_filters.FilterSet):
    project = django_filters.ModelChoiceFilter(
        label='Projects',
        name='project_fkey',
        queryset=Projects
        )

    class Meta:
        model = Task
        fields = ['project']

Этот вид работает в теории, но ничего не дает мне в раскрывающемся списке, потому что

 if request is None:

возвращает True и, следовательно, дает мне пустой список.

Итак... может ли кто-нибудь увидеть, где я ошибаюсь, что мешает мне получить доступ к запросу? Очевидно, что вторая часть кода работает на основе qs, который передается из моего представления, так что, может быть, мне нужно передать что-то еще? Мой код view.py ниже:

def masterlist(request, page='0'):
    #Check to see if we have clicked a button inside the form
    if request.method == 'POST':
        return redirect ('tasks:tasklist')
    else:
        # Pre-filtering of user and Master = True etc is done in the MasterListFilter in filters.py
        # Then we compile the list for Filtering by. 
        f = MasterListFilter(request.GET, queryset=Task.objects.all())
        # Then we apply the complete list to the table, configure it and then render it.
        mastertable = MasterTable(f.qs)
        if int(page) > 0:
            RequestConfig(request, paginate={'page': page, 'per_page': 10}).configure(mastertable) 
        else:
            RequestConfig(request, paginate={'page': 1, 'per_page': 10}).configure(mastertable)  
        return render (request,'tasks/masterlist.html',{'mastertable': mastertable, 'filter': f}) 

Спасибо.


person cander    schedule 02.07.2018    source источник
comment
это когда-нибудь было решено?   -  person Alex Winkler    schedule 15.09.2020


Ответы (2)


Как указано в следующем потоке, вы должны передать запрос экземпляру фильтра в представлении: Настроить набор запросов в меню django-filter ModelChoiceFilter (выбор) и ModelMultipleChoiceFilter (множественный выбор) на основе запроса

ex:

myFilter = ReportFilter(request.GET, request=request, queryset=reports)
person Louis George    schedule 03.05.2020
comment
Конечно, если это делает вас счастливым - person Louis George; 03.05.2020

Из документов.

Аргумент набора запросов также поддерживает вызываемое поведение. Если callable передается, он будет вызываться с Filterset.request в качестве единственного аргумента. Это позволяет легко фильтровать по свойствам объекта запроса, не переопределяя FilterSet.__init__.

Это вообще не проверено, но я думаю, что что-то в этом роде, это то, что вам нужно:

class MasterListFilter(django_filters.FilterSet):
    project = django_filters.ModelChoiceFilter(
        label='Projects',
        name='project_fkey',
        queryset=lambda req: Project.objects.filter(
            deleted__isnull=True, user_fkey=req.user.id).distinct('code'),
    )

    class Meta:
        model = Task
        fields = ['project']

Кроме того, если это зависит от перезапуска веб-сервера, проверяли ли вы проблемы с кэшированием? (В случае, если django-debug-toolbar дает отличную информацию об этом)

ИЗМЕНИТЬ

Непредсказуемое поведение, скорее всего, происходит из-за того, что вы извлекаете user из определения class MasterListFilter, поэтому get_current_user() выполняется во время загрузки класса, а не во время фактического запроса, и все последующие вызовы qs будут извлекать этот запрос. Как правило, все, что связано с запросом, никогда не должно быть в определении класса, а должно быть в методе/лямбде. Таким образом, лямбда, которая получает аргумент request и создает запрос только тогда, должна точно покрывать то, что вам нужно.

ИЗМЕНИТЬ 2

Что касается вашего редактирования, следующий код имеет некоторые проблемы:

def Projects(request):
    if request is None:
        return Project.objects.none()
    return lambda req: Project.objects.filter(deleted__isnull=True, user_fkey=req.user.id)

Это либо возвращает пустой диспетчер объектов, либо вызываемый, но сам метод Project уже является вызываемым, поэтому ваш ModelChoiceFilter получит только диспетчер объектов, когда объект request равен None, иначе лямбда, но он ожидает получить объект менеджер - он не может перебирать лямбду, поэтому он должен выдать вам ошибку is not iterable. Итак, в основном вы можете попробовать:

def project_qs(request):
    # you could add some logging here to see what the arguments look like
    if not request or not 'user' in request:
        return Project.objects.none()
    return Project.objects.filter(deleted__isnull=True, user_fkey=request.user.id)

# ...
queryset=project_qs
# ...
person wiesion    schedule 02.07.2018
comment
Спасибо, Wiesion, но я все еще получаю сообщение об ошибке None Type, говорящее, что у пользователя нет идентификатора атрибута. Я предполагаю, потому что он не получает пользователя. Пожалуйста, смотрите мое редактирование выше, показывающее, что я сделал и где я нахожусь. Спасибо за вашу помощь. - person cander; 03.07.2018
comment
Обновлен мой ответ - person wiesion; 03.07.2018
comment
Спасибо. Я добавил поправки. Должен признать, что мои практические знания лямбда в лучшем случае слабы. Однако я не думаю, что это проблема. Кажется, проблема в том, что я тестирую запрос, но запроса нет. Он возвращает None каждый раз, и я вижу это в своем журнале apache2, если добавляю «запрос на печать». Следовательно, почему раньше я не получал ошибку «Не итерируемая ошибка», поскольку я никогда не добирался до этой части кода. Я просто не знаю, как получить мой request.GET для доступа к моему файлу filter.py из представления. - person cander; 03.07.2018