使用 django-filter 通过外键上的 CharFilter 进行过滤
Using django-filter to filter by CharFilter on foreign key
我正在尝试使用 django-filter 来显示过滤后的订单列表。每个订单都有一个设备字段,它是一个外键,如下所示:
class Order(models.Model):
device = models.ForeignKey(Device)
BLACK = 'BLACK'
CYAN = 'CYAN'
MAGENTA = 'MAGENTA'
YELLOW = 'YELLOW'
COLOR_CHOICES = (
(BLACK, 'black'),
(CYAN, 'cyan'),
(MAGENTA, 'magenta'),
(YELLOW, 'yellow'),
)
toner = models.CharField(max_length=200, choices=COLOR_CHOICES)
order_date = models.DateTimeField('order triggered date')
class Meta:
ordering = ['-order_date']
def __str__(self):
return self.toner
默认的过滤器类型是 ModelChoiceFilter,但它会显示可能包含数千个设备的非常长的列表。我希望能够使用 CharFilter 过滤文本,如下所示 filters.py:
class OrderFilter(df.FilterSet):
device = df.CharFilter(lookup_type='icontains')
order_date = df.DateFilter()
class Meta:
model = Order
fields = ['device', 'toner', 'order_date']
order_by = ['order_date']
但是,当我尝试通过设备过滤器字段中键入的文本进行过滤时,出现以下错误。
Traceback:
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
132. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/lib/python3.4/contextlib.py" in inner
30. return func(*args, **kwds)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
22. return view_func(request, *args, **kwargs)
File "/home/mwood/auto_toner_django/auto_toner/auto_toner/views.py" in OrderView
50. return render(request, 'auto_toner/order_filter.html', context, context_instance=RequestContext(request))
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/shortcuts.py" in render
89. using=using)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/loader.py" in render_to_string
115. template_name, context, context_instance, dirs, dictionary)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/engine.py" in render_to_string
221. return t.render(context_instance)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in render
209. return self._render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/test/utils.py" in instrumented_test_render
96. return self.nodelist.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in render
903. bit = self.render_node(node, context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/debug.py" in render_node
79. return node.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/loader_tags.py" in render
135. return compiled_parent._render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/test/utils.py" in instrumented_test_render
96. return self.nodelist.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in render
903. bit = self.render_node(node, context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/debug.py" in render_node
79. return node.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/loader_tags.py" in render
65. result = block.nodelist.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in render
903. bit = self.render_node(node, context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/debug.py" in render_node
79. return node.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/endless_pagination/templatetags/endless.py" in render
296. objects = self.objects.resolve(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in resolve
787. value = self._resolve_lookup(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in _resolve_lookup
839. (bit, current)) # missing attribute
Exception Type: VariableDoesNotExist at /devices/orders/
Exception Value: Failed lookup for key [qs] in '<auto_toner.filters.OrderFilter object at 0x7fd721fa32e8>'
知道我做错了什么,或者是否有更好的方法来解决这个问题?谢谢
正如评论中所建议的那样,一旦我花时间去做,使用 q 对象确实变得更简单并且依赖性更少。实际上,我从 post here 中学到了如何最简单地做到这一点,它描述了使用 q 个对象实现简单的搜索功能。用同样的原理从URL中抓取请求参数,我很容易就能想出一个sorting/filtering函数。
为了防止 link 腐烂,我 post 将 link 的内容放在这里。
Search is a feature that is – or at least, should be – present on most sites containing dynamic or large content.
There are a few projects around to tackle that. Here’s a non-exhaustive list: djangosearch, django-search (with a dash), django-sphinx.
Those search engines are great, but they seem like overkill if you just need a simple search feature for your CMS or blog.
To deal with that, I’ve come up with a generic and simple trick. All you need is copy/paste the following snippet anywhere in your project:
import re
from django.db.models import Q
def normalize_query(query_string,
findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
normspace=re.compile(r'\s{2,}').sub):
''' Splits the query string in invidual keywords, getting rid of unecessary spaces
and grouping quoted words together.
Example:
>>> normalize_query(' some random words "with quotes " and spaces')
['some', 'random', 'words', 'with quotes', 'and', 'spaces']
'''
return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)]
def get_query(query_string, search_fields):
''' Returns a query, that is a combination of Q objects. That combination
aims to search keywords within a model by testing the given search fields.
'''
query = None # Query to search for every search term
terms = normalize_query(query_string)
for term in terms:
or_query = None # Query to search for a given term in each field
for field_name in search_fields:
q = Q(**{"%s__icontains" % field_name: term})
if or_query is None:
or_query = q
else:
or_query = or_query | q
if query is None:
query = or_query
else:
query = query & or_query
return query
What the above does is generate a django.db.models.Q object (see doc) to search through your model, based on the query string and on the model’s fields that you want to search. Importantly, it also analyses the query string by splitting out the key words and allowing words to be grouped by quotes. For example, out of the following query string…
' some random words "with quotes " and spaces'
…the words 'some', 'random', 'words', 'with quotes', 'and', 'spaces' would actually be searched. It performs an AND search with all the given words, but you could easily customise it to do different kinds of search.
Then, your search view would become as simple as:
def search(request):
query_string = ''
found_entries = None
if ('q' in request.GET) and request.GET['q'].strip():
query_string = request.GET['q']
entry_query = get_query(query_string, ['title', 'body',])
found_entries = Entry.objects.filter(entry_query).order_by('-pub_date')
return render_to_response('search/search_results.html',
{ 'query_string': query_string, 'found_entries': found_entries },
context_instance=RequestContext(request))
And that’s it! I use this on a site that has about 10,000 news items and it works pretty fast…
Now you have no excuse not to add a search box to your site! ;)
我正在尝试使用 django-filter 来显示过滤后的订单列表。每个订单都有一个设备字段,它是一个外键,如下所示:
class Order(models.Model):
device = models.ForeignKey(Device)
BLACK = 'BLACK'
CYAN = 'CYAN'
MAGENTA = 'MAGENTA'
YELLOW = 'YELLOW'
COLOR_CHOICES = (
(BLACK, 'black'),
(CYAN, 'cyan'),
(MAGENTA, 'magenta'),
(YELLOW, 'yellow'),
)
toner = models.CharField(max_length=200, choices=COLOR_CHOICES)
order_date = models.DateTimeField('order triggered date')
class Meta:
ordering = ['-order_date']
def __str__(self):
return self.toner
默认的过滤器类型是 ModelChoiceFilter,但它会显示可能包含数千个设备的非常长的列表。我希望能够使用 CharFilter 过滤文本,如下所示 filters.py:
class OrderFilter(df.FilterSet):
device = df.CharFilter(lookup_type='icontains')
order_date = df.DateFilter()
class Meta:
model = Order
fields = ['device', 'toner', 'order_date']
order_by = ['order_date']
但是,当我尝试通过设备过滤器字段中键入的文本进行过滤时,出现以下错误。
Traceback:
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
132. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/lib/python3.4/contextlib.py" in inner
30. return func(*args, **kwds)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
22. return view_func(request, *args, **kwargs)
File "/home/mwood/auto_toner_django/auto_toner/auto_toner/views.py" in OrderView
50. return render(request, 'auto_toner/order_filter.html', context, context_instance=RequestContext(request))
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/shortcuts.py" in render
89. using=using)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/loader.py" in render_to_string
115. template_name, context, context_instance, dirs, dictionary)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/engine.py" in render_to_string
221. return t.render(context_instance)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in render
209. return self._render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/test/utils.py" in instrumented_test_render
96. return self.nodelist.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in render
903. bit = self.render_node(node, context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/debug.py" in render_node
79. return node.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/loader_tags.py" in render
135. return compiled_parent._render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/test/utils.py" in instrumented_test_render
96. return self.nodelist.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in render
903. bit = self.render_node(node, context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/debug.py" in render_node
79. return node.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/loader_tags.py" in render
65. result = block.nodelist.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in render
903. bit = self.render_node(node, context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/debug.py" in render_node
79. return node.render(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/endless_pagination/templatetags/endless.py" in render
296. objects = self.objects.resolve(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in resolve
787. value = self._resolve_lookup(context)
File "/home/mwood/auto_toner_django/venv_auto_toner/lib/python3.4/site-packages/django/template/base.py" in _resolve_lookup
839. (bit, current)) # missing attribute
Exception Type: VariableDoesNotExist at /devices/orders/
Exception Value: Failed lookup for key [qs] in '<auto_toner.filters.OrderFilter object at 0x7fd721fa32e8>'
知道我做错了什么,或者是否有更好的方法来解决这个问题?谢谢
正如评论中所建议的那样,一旦我花时间去做,使用 q 对象确实变得更简单并且依赖性更少。实际上,我从 post here 中学到了如何最简单地做到这一点,它描述了使用 q 个对象实现简单的搜索功能。用同样的原理从URL中抓取请求参数,我很容易就能想出一个sorting/filtering函数。
为了防止 link 腐烂,我 post 将 link 的内容放在这里。
Search is a feature that is – or at least, should be – present on most sites containing dynamic or large content.
There are a few projects around to tackle that. Here’s a non-exhaustive list: djangosearch, django-search (with a dash), django-sphinx.
Those search engines are great, but they seem like overkill if you just need a simple search feature for your CMS or blog.
To deal with that, I’ve come up with a generic and simple trick. All you need is copy/paste the following snippet anywhere in your project:
import re
from django.db.models import Q
def normalize_query(query_string,
findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
normspace=re.compile(r'\s{2,}').sub):
''' Splits the query string in invidual keywords, getting rid of unecessary spaces
and grouping quoted words together.
Example:
>>> normalize_query(' some random words "with quotes " and spaces')
['some', 'random', 'words', 'with quotes', 'and', 'spaces']
'''
return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)]
def get_query(query_string, search_fields):
''' Returns a query, that is a combination of Q objects. That combination
aims to search keywords within a model by testing the given search fields.
'''
query = None # Query to search for every search term
terms = normalize_query(query_string)
for term in terms:
or_query = None # Query to search for a given term in each field
for field_name in search_fields:
q = Q(**{"%s__icontains" % field_name: term})
if or_query is None:
or_query = q
else:
or_query = or_query | q
if query is None:
query = or_query
else:
query = query & or_query
return query
What the above does is generate a django.db.models.Q object (see doc) to search through your model, based on the query string and on the model’s fields that you want to search. Importantly, it also analyses the query string by splitting out the key words and allowing words to be grouped by quotes. For example, out of the following query string…
' some random words "with quotes " and spaces' …the words 'some', 'random', 'words', 'with quotes', 'and', 'spaces' would actually be searched. It performs an AND search with all the given words, but you could easily customise it to do different kinds of search.
Then, your search view would become as simple as:
def search(request):
query_string = ''
found_entries = None
if ('q' in request.GET) and request.GET['q'].strip():
query_string = request.GET['q']
entry_query = get_query(query_string, ['title', 'body',])
found_entries = Entry.objects.filter(entry_query).order_by('-pub_date')
return render_to_response('search/search_results.html',
{ 'query_string': query_string, 'found_entries': found_entries },
context_instance=RequestContext(request))
And that’s it! I use this on a site that has about 10,000 news items and it works pretty fast…
Now you have no excuse not to add a search box to your site! ;)