如何使用django-filter按当前用户过滤ModelChoiceFilter

时间:2018-07-02 13:09:47

标签: django 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'),

很明显,我不想使用硬编码的ID。我需要获取当前用户的价值。遵循用于过滤主表的相同逻辑,我最终得到了这个结果。

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中,以便可以使用它来过滤我的下拉过滤器列表。

预先感谢您,并祝您编码愉快。

编辑: 因此,按照Wiesion的建议,我按照建议更改了代码,但仍然收到“无类型错误”,表明用户没有属性ID。基本上,我没有得到当前用户。因此,回到文档并尝试将其建议与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}) 

谢谢。

2 个答案:

答案 0 :(得分:1)

如以下线程所述,您必须将请求传递到视图中的过滤器实例:Customize queryset in django-filter ModelChoiceFilter (select) and ModelMultipleChoiceFilter (multi-select) menus based on request

例如:

myFilter = ReportFilter(request.GET, request=request, queryset=reports)

答案 1 :(得分:0)

docs

  

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对此提供了深刻的见解)

编辑

不可预测的行为很可能发生,因为您正在获取user定义中的class MasterListFilter,因此get_current_user()在类加载时执行,而不是在实际请求和所有后续调用期间执行到qs的位置将检索该查询。通常,所有与请求相关的内容都不应放在类定义中,而应在方法/ lambda中。因此,接收request参数并仅创建查询的lambda应该完全满足您的需求。

编辑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,否则为lambda,但它期望接收对象管理器-它无法遍历lambda,因此应该给您一些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
# ...