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
16
17 from django.db.models import Q
18
19 from zinnia.models import Entry
20
21
23 """Creates the Q() object"""
24 meta = getattr(token, 'meta', None)
25 query = getattr(token, 'query', '')
26 wildcards = None
27
28 if isinstance(query, basestring):
29 search = query
30 else:
31 if len(query) == 1:
32 search = query[0]
33 elif len(query) == 3:
34 wildcards = 'BOTH'
35 search = query[1]
36 elif len(query) == 2:
37 if query[0] == '*':
38 wildcards = 'START'
39 search = query[1]
40 else:
41 wildcards = 'END'
42 search = query[0]
43
44 if not meta:
45 return Q(content__icontains=search) | \
46 Q(excerpt__icontains=search) | \
47 Q(title__icontains=search)
48
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)
59 else:
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)
69 else:
70 return Q(authors__username__iexact=search)
71 elif meta == 'tag':
72 return Q(tags__icontains=search)
73
74
76 """Appends all the Q() objects"""
77 query = Q()
78 operation = 'and'
79 negation = False
80
81 for t in token:
82 if type(t) is ParseResults:
83 query &= unionQ(t)
84 else:
85 if t in ('or', 'and'):
86 operation = t
87 elif t == '-':
88 negation = True
89 else:
90 if negation:
91 t = ~t
92 if operation == 'or':
93 query |= t
94 else:
95 query &= t
96 return query
97
98
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)
103
104 OPER_AND = CaselessLiteral('and')
105 OPER_OR = CaselessLiteral('or')
106 OPER_NOT = '-'
107
108 TERM = Combine(Optional(Word(alphas).setResultsName('meta') + ':') +
109 (QUOTED.setResultsName('query') |
110 WILDCARDS.setResultsName('query')))
111 TERM.setParseAction(createQ)
112
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)
118
119 QUERY = OneOrMore(EXPRESSION) + StringEnd()
120 QUERY.setParseAction(unionQ)
121
122
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()
128