1
"""Search module with complex query parsing for Zinnia"""
2
from pyparsing import Word
3
from pyparsing import alphas
4
from pyparsing import WordEnd
5
from pyparsing import Combine
6
from pyparsing import opAssoc
7
from pyparsing import Optional
8
from pyparsing import OneOrMore
9
from pyparsing import StringEnd
10
from pyparsing import printables
11
from pyparsing import quotedString
12
from pyparsing import removeQuotes
13
from pyparsing import ParseResults
14
from pyparsing import CaselessLiteral
15
from pyparsing import operatorPrecedence
17
from django.db.models import Q
19
from zinnia.models import Entry
23
"""Creates the Q() object"""
24
meta = getattr(token, 'meta', None)
25
query = getattr(token, 'query', '')
28
if isinstance(query, basestring): # Unicode -> Quoted string
30
else: # List -> No quoted string (possible wildcards)
45
return Q(content__icontains=search) | \
46
Q(excerpt__icontains=search) | \
47
Q(title__icontains=search)
49
if meta == 'category':
50
if wildcards == 'BOTH':
51
return Q(categories__title__icontains=search) | \
52
Q(categories__slug__icontains=search)
53
elif wildcards == 'START':
54
return Q(categories__title__iendswith=search) | \
55
Q(categories__slug__iendswith=search)
56
elif wildcards == 'END':
57
return Q(categories__title__istartswith=search) | \
58
Q(categories__slug__istartswith=search)
60
return Q(categories__title__iexact=search) | \
61
Q(categories__slug__iexact=search)
62
elif meta == 'author':
63
if wildcards == 'BOTH':
64
return Q(authors__username__icontains=search)
65
elif wildcards == 'START':
66
return Q(authors__username__iendswith=search)
67
elif wildcards == 'END':
68
return Q(authors__username__istartswith=search)
70
return Q(authors__username__iexact=search)
71
elif meta == 'tag': # TODO: tags ignore wildcards
72
return Q(tags__icontains=search)
76
"""Appends all the Q() objects"""
82
if type(t) is ParseResults: # See tokens recursively
85
if t in ('or', 'and'): # Set the new op and go to next token
87
elif t == '-': # Next tokens needs to be negated
89
else: # Append to query the token
99
NO_BRTS = printables.replace('(', '').replace(')', '')
100
SINGLE = Word(NO_BRTS.replace('*', ''))
101
WILDCARDS = Optional('*') + SINGLE + Optional('*') + WordEnd(wordChars=NO_BRTS)
102
QUOTED = quotedString.setParseAction(removeQuotes)
104
OPER_AND = CaselessLiteral('and')
105
OPER_OR = CaselessLiteral('or')
108
TERM = Combine(Optional(Word(alphas).setResultsName('meta') + ':') +
109
(QUOTED.setResultsName('query') |
110
WILDCARDS.setResultsName('query')))
111
TERM.setParseAction(createQ)
113
EXPRESSION = operatorPrecedence(TERM, [
114
(OPER_NOT, 1, opAssoc.RIGHT),
115
(OPER_OR, 2, opAssoc.LEFT),
116
(Optional(OPER_AND, default='and'), 2, opAssoc.LEFT)])
117
EXPRESSION.setParseAction(unionQ)
119
QUERY = OneOrMore(EXPRESSION) + StringEnd()
120
QUERY.setParseAction(unionQ)
123
def advanced_search(pattern):
124
"""Parse the grammar of a pattern
125
and build a queryset with it"""
126
query_parsed = QUERY.parseString(pattern)
127
return Entry.published.filter(query_parsed[0]).distinct()