[英]Django-filter: ModelChoiceFilter with request.user based queryset
I have a Django class based ListView listing objects.我有一个基于 Django 类的 ListView 列表对象。 These objects can be filtered based on locations.
这些对象可以根据位置进行过滤。 Now I want that the location ModelChoiceFilter only lists locations which are relevant to the current user.
现在我希望位置 ModelChoiceFilter 仅列出与当前用户相关的位置。 Relevant locations are the locations he owns.
相关地点是他拥有的地点。 How can I change the queryset?
如何更改查询集?
# models.py
from django.db import models
from django.conf import settings
from rules.contrib.models import RulesModel
from django.utils.translation import gettext_lazy as _
class Location(RulesModel):
name = models.CharField(_("Name"), max_length=200)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("Owner"),
related_name="location_owner",
on_delete=models.CASCADE,
help_text=_("Owner can view, change or delete this location."),
)
class Object(RulesModel):
name = models.CharField(_("Name"), max_length=200)
description = models.TextField(_("Description"), blank=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("Owner"),
related_name="location_owner",
on_delete=models.CASCADE,
help_text=_("Owner can view, change or delete this location."),
)
location = models.ForeignKey(
Location,
verbose_name=_("Location"),
related_name="object_location",
on_delete=models.SET_NULL,
null=True,
blank=True,
)
This is my current filters.py file which shows all the locations to the user.这是我当前的 filters.py 文件,它向用户显示所有位置。
# filters.py
from .models import Object
import django_filters
class ObjectFilter(django_filters.FilterSet):
class Meta:
model = Object
fields = ["location", ]
This is the view which by default shows objects the user owns.这是默认显示用户拥有的对象的视图。 It's possible to filter further by location.
可以按位置进一步过滤。 But the location drop-down shows too many entries.
但是位置下拉菜单显示的条目太多。
# views.py
from django.views.generic import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Object
from .filters import ObjectFilter
class ObjectListView(LoginRequiredMixin, ListView):
model = Object
paginate_by = 10
def get_queryset(self):
queryset = Object.objects.filter(owner=self.request.user)
filterset = ObjectFilter(self.request.GET, queryset=queryset)
return filterset.qs
def get_context_data(self, **kwargs):
context = super(ObjectListView, self).get_context_data(**kwargs)
filterset = ObjectFilter(self.request.GET, queryset=self.queryset)
context["filter"] = filterset
return context
I've tried to tweak the filters.py with adding a ModelChoiceFilter, but it ends up with an AttributeError: 'NoneType' object has no attribute 'request' .我尝试通过添加 ModelChoiceFilter 来调整 filters.py,但最终出现AttributeError: 'NoneType' object has no attribute 'request' 。
# filters.py
from .models import Object
import django_filters
def get_location_queryset(self):
queryset = Location.objects.filter(location__owner=self.request.user)
return queryset
class ObjectFilter(django_filters.FilterSet):
location = django_filters.filters.ModelChoiceFilter(queryset=get_location_queryset)
class Meta:
model = Object
fields = ["location", ]
I believe a few different issues are at play here.我相信这里有几个不同的问题在起作用。 First, as per the django-filter docs , when a callable is passed to
ModelChoiceFilter
, it will be invoked with Filterset.request
as its only argument.首先,根据django-filter 文档,当一个可调用对象被传递给
ModelChoiceFilter
时,它将使用Filterset.request
作为其唯一参数来调用。 So your filters.py
would need to be rewritten like so:所以你的
filters.py
需要像这样重写:
# filters.py
from .models import Object
import django_filters
def get_location_queryset(request): # updated from `self` to `request`
queryset = Location.objects.filter(location__owner=request.user)
return queryset
class ObjectFilter(django_filters.FilterSet):
location = django_filters.filters.ModelChoiceFilter(queryset=get_location_queryset)
class Meta:
model = Object
fields = ["location", ]
This is half of the puzzle.这是难题的一半。 I believe the other issue is in your view.
我相信另一个问题在你看来。
django-filter
has view classes that handle passing requests to filtersets, but this does not happen automatically using Django's generic ListView
. django-filter
具有处理将请求传递给过滤器集的视图类,但这不会使用 Django 的通用ListView
自动发生。 Try updating your view code to something like this:尝试将您的视图代码更新为以下内容:
# views.py
from django_filters.views import FilterView
class ObjectListView(LoginRequiredMixin, FilterView): # FilterView instead of ListView
model = Object
filterset_class = ObjectFilter
This should take care of passing the request for you.这应该负责为您传递请求。
Also note that, as per the django-filter docs linked above, your queryset should handle the case when request
is None
.另请注意,根据上面链接的 django-filter 文档,您的查询集应处理
request
为None
的情况。 I've personally never seen that happen in my projects, but just FYI.我个人从未在我的项目中看到过这种情况,仅供参考。
As an alternative, if you don't want to use FilterView
, I believe the code in your example is almost there:作为替代方案,如果您不想使用
FilterView
,我相信您示例中的代码几乎就在那里:
# views.py alternative
class ObjectListView(LoginRequiredMixin, ListView):
model = Object
paginate_by = 10
def get_queryset(self):
filterset = ObjectFilter(self.request)
return filterset.qs
I think this would also work with the filters.py
I specified above.我认为这也适用于我上面指定的
filters.py
。
The problem with this code:这段代码的问题:
def get_location_queryset(self):
queryset = Location.objects.filter(location__owner=self.request.user)
return queryset
is that it is a function based view, you added self
as an argument, and tried to access request
which does not exist in context of self
since self
value is undefined to us是它是基于函数的视图,您将
self
添加为参数,并尝试访问在self
上下文中不存在的request
,因为self
值对我们来说是未定义的
What i would do to filter out location based on user by creating a class based view for location filtering通过为位置过滤创建基于类的视图,我将如何根据用户过滤位置
class LocationView(ListView):
def get_queryset(self):
return Location.objects.filter(owner=self.request.user)
in filters.py:在filters.py中:
class ObjectFilter(django_filters.FilterSet):
location = django_filters.filters.ModelChoiceFilter(queryset=LocationView.as_view())
class Meta:
model = Object
fields = ["location", ]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.