|
a/Allura/allura/lib/search.py |
|
b/Allura/allura/lib/search.py |
|
... |
|
... |
16 |
# under the License.
|
16 |
# under the License.
|
17 |
|
17 |
|
18 |
import re
|
18 |
import re
|
19 |
import socket
|
19 |
import socket
|
20 |
from logging import getLogger
|
20 |
from logging import getLogger
|
|
|
21 |
from urllib import urlencode
|
|
|
22 |
from itertools import imap
|
21 |
|
23 |
|
22 |
import markdown
|
24 |
import markdown
|
23 |
import jinja2
|
25 |
import jinja2
|
|
|
26 |
from tg import redirect, url
|
24 |
from pylons import tmpl_context as c, app_globals as g
|
27 |
from pylons import tmpl_context as c, app_globals as g
|
|
|
28 |
from pylons import request
|
25 |
from pysolr import SolrError
|
29 |
from pysolr import SolrError
|
26 |
|
30 |
|
|
|
31 |
from allura.lib import helpers as h
|
27 |
from .markdown_extensions import ForgeExtension
|
32 |
from .markdown_extensions import ForgeExtension
|
28 |
|
33 |
|
29 |
log = getLogger(__name__)
|
34 |
log = getLogger(__name__)
|
30 |
|
35 |
|
31 |
def solarize(obj):
|
36 |
def solarize(obj):
|
|
... |
|
... |
81 |
'mount_point_s:%s' % c.app.config.options.mount_point ]
|
86 |
'mount_point_s:%s' % c.app.config.options.mount_point ]
|
82 |
if not history:
|
87 |
if not history:
|
83 |
fq.append('is_history_b:False')
|
88 |
fq.append('is_history_b:False')
|
84 |
return search(q, fq=fq, rows=rows, short_timeout=short_timeout, ignore_errors=False, **kw)
|
89 |
return search(q, fq=fq, rows=rows, short_timeout=short_timeout, ignore_errors=False, **kw)
|
85 |
|
90 |
|
|
|
91 |
|
|
|
92 |
def search_app(q='', fq=None, **kw):
|
|
|
93 |
"""Helper for app search.
|
|
|
94 |
|
|
|
95 |
Uses dismax query parser. Matches on `title` and `text`. Handles paging, sorting, etc
|
|
|
96 |
"""
|
|
|
97 |
history = kw.pop('history', None)
|
|
|
98 |
if kw.pop('project', False):
|
|
|
99 |
redirect(c.project.url() + 'search?' + urlencode(dict(q=q, history=history)))
|
|
|
100 |
search_comments = kw.pop('search_comments', None)
|
|
|
101 |
limit = kw.pop('limit', None)
|
|
|
102 |
page = kw.pop('page', 0)
|
|
|
103 |
default = kw.pop('default', 25)
|
|
|
104 |
allowed_types = kw.pop('allowed_types', [])
|
|
|
105 |
parser = kw.pop('parser', None)
|
|
|
106 |
sort = kw.pop('sort', 'score desc')
|
|
|
107 |
fq = fq if fq else []
|
|
|
108 |
search_error = None
|
|
|
109 |
results = []
|
|
|
110 |
count = 0
|
|
|
111 |
matches = {}
|
|
|
112 |
limit, page, start = g.handle_paging(limit, page, default=default)
|
|
|
113 |
if not q:
|
|
|
114 |
q = ''
|
|
|
115 |
else:
|
|
|
116 |
# Match on both `title` and `text` by default, using 'dismax' parser.
|
|
|
117 |
# Score on `title` matches is boosted, so title match is better than body match.
|
|
|
118 |
# It's 'fuzzier' than standard parser, which matches only on `text`.
|
|
|
119 |
if search_comments:
|
|
|
120 |
allowed_types += ['Post']
|
|
|
121 |
search_params = {
|
|
|
122 |
'qt': 'dismax',
|
|
|
123 |
'qf': 'title^2 text',
|
|
|
124 |
'pf': 'title^2 text',
|
|
|
125 |
'fq': [
|
|
|
126 |
'project_id_s:%s' % c.project._id,
|
|
|
127 |
'mount_point_s:%s' % c.app.config.options.mount_point,
|
|
|
128 |
'-deleted_b:true',
|
|
|
129 |
'type_s:(%s)' % ' OR '.join(['"%s"' % t for t in allowed_types])
|
|
|
130 |
] + fq,
|
|
|
131 |
'hl': 'true',
|
|
|
132 |
'hl.simple.pre': '<strong>',
|
|
|
133 |
'hl.simple.post': '</strong>',
|
|
|
134 |
'sort': sort,
|
|
|
135 |
}
|
|
|
136 |
if not history:
|
|
|
137 |
search_params['fq'].append('is_history_b:False')
|
|
|
138 |
if parser == 'standard':
|
|
|
139 |
search_params.pop('qt', None)
|
|
|
140 |
search_params.pop('qf', None)
|
|
|
141 |
search_params.pop('pf', None)
|
|
|
142 |
try:
|
|
|
143 |
results = search(
|
|
|
144 |
q, short_timeout=True, ignore_errors=False,
|
|
|
145 |
rows=limit, start=start, **search_params)
|
|
|
146 |
except SearchError as e:
|
|
|
147 |
search_error = e
|
|
|
148 |
if results:
|
|
|
149 |
count = results.hits
|
|
|
150 |
matches = results.highlighting
|
|
|
151 |
def historize_urls(doc):
|
|
|
152 |
if doc.get('type_s', '').endswith(' Snapshot'):
|
|
|
153 |
if doc.get('url_s'):
|
|
|
154 |
doc['url_s'] = doc['url_s'] + '?version=%s' % doc.get('version_i')
|
|
|
155 |
return doc
|
|
|
156 |
def add_matches(doc):
|
|
|
157 |
m = matches.get(doc['id'], {})
|
|
|
158 |
doc['title_match'] = h.get_first(m, 'title')
|
|
|
159 |
doc['text_match'] = h.get_first(m, 'text')
|
|
|
160 |
if not doc['text_match']:
|
|
|
161 |
doc['text_match'] = h.get_first(doc, 'text')
|
|
|
162 |
return doc
|
|
|
163 |
results = imap(historize_urls, results)
|
|
|
164 |
results = imap(add_matches, results)
|
|
|
165 |
|
|
|
166 |
# Provide sort urls to the view
|
|
|
167 |
score_url = 'score desc'
|
|
|
168 |
date_url = 'mod_date_dt desc'
|
|
|
169 |
try:
|
|
|
170 |
field, order = sort.split(' ')
|
|
|
171 |
except ValueError:
|
|
|
172 |
field, order = 'score', 'desc'
|
|
|
173 |
sort = ' '.join([field, 'asc' if order == 'desc' else 'desc'])
|
|
|
174 |
if field == 'score':
|
|
|
175 |
score_url = sort
|
|
|
176 |
elif field == 'mod_date_dt':
|
|
|
177 |
date_url = sort
|
|
|
178 |
params = request.GET.copy()
|
|
|
179 |
params.update({'sort': score_url})
|
|
|
180 |
score_url = url(request.path, params=params)
|
|
|
181 |
params.update({'sort': date_url})
|
|
|
182 |
date_url = url(request.path, params=params)
|
|
|
183 |
return dict(q=q, history=history, results=results or [],
|
|
|
184 |
count=count, limit=limit, page=page, search_error=search_error,
|
|
|
185 |
sort_score_url=score_url, sort_date_url=date_url,
|
|
|
186 |
sort_field=field)
|
|
|
187 |
|
|
|
188 |
|
86 |
def find_shortlinks(text):
|
189 |
def find_shortlinks(text):
|
87 |
md = markdown.Markdown(
|
190 |
md = markdown.Markdown(
|
88 |
extensions=['codehilite', ForgeExtension(), 'tables'],
|
191 |
extensions=['codehilite', ForgeExtension(), 'tables'],
|
89 |
output_format='html4')
|
192 |
output_format='html4')
|
90 |
md.convert(text)
|
193 |
md.convert(text)
|