Как отфильтровать ModelChoiceFilter по текущему пользователю, используя django-фильтр - PullRequest
0 голосов
/ 02 июля 2018

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

Вот мой код от filters.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, я изменил свой код в соответствии с предложением, но все равно получаю сообщение об ошибке типа «Нет», говорящее, что у пользователя нет идентификатора атрибута. В общем, кажется, я не получаю текущего пользователя. Итак, возвращаясь к документам и пытаясь объединить их предложение с 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}) 

Спасибо.

1 Ответ

0 голосов
/ 02 июля 2018

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

Аргумент queryset также поддерживает вызываемое поведение. Если вызываемый будет вызван с 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 дает отличную информацию об этом)

EDIT

Непредсказуемое поведение, скорее всего, происходит из-за того, что вы извлекаете 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
# ...
...