使用 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! ;)