allura
Revision | f8cf1c7537a01f86631d1f6bff3eaae3341ec6d7 (tree) |
---|---|
Time | 2012-07-12 01:01:47 |
Author | Tim Van Steenburgh <tvansteenburgh@gmai...> |
Commiter | Tim Van Steenburgh |
Merge branch 'db/4272' into dev
@@ -577,10 +577,14 @@ class Ticket(VersionedArtifact, ActivityObject): | ||
577 | 577 | custom_fields=self.custom_fields) |
578 | 578 | |
579 | 579 | @classmethod |
580 | - def paged_query(cls, query, limit=None, page=0, sort=None, columns=None, **kw): | |
581 | - """Query tickets, sorting and paginating the result.""" | |
580 | + def paged_query(cls, app_config, user, query, limit=None, page=0, sort=None, **kw): | |
581 | + """ | |
582 | + Query tickets, filtering for 'read' permission, sorting and paginating the result. | |
583 | + | |
584 | + See also paged_search which does a solr search | |
585 | + """ | |
582 | 586 | limit, page, start = g.handle_paging(limit, page, default=25) |
583 | - q = cls.query.find(dict(query, app_config_id=c.app.config._id)) | |
587 | + q = cls.query.find(dict(query, app_config_id=app_config._id)) | |
584 | 588 | q = q.sort('ticket_num') |
585 | 589 | if sort: |
586 | 590 | field, direction = sort.split() |
@@ -595,27 +599,76 @@ class Ticket(VersionedArtifact, ActivityObject): | ||
595 | 599 | tickets = [] |
596 | 600 | count = q.count() |
597 | 601 | for t in q: |
598 | - if security.has_access(t, 'read'): | |
602 | + if security.has_access(t, 'read', user, app_config.project): | |
599 | 603 | tickets.append(t) |
600 | 604 | else: |
601 | 605 | count = count -1 |
602 | - sortable_custom_fields=c.app.globals.sortable_custom_fields_shown_in_search() | |
603 | - if not columns: | |
604 | - columns = [dict(name='ticket_num', sort_name='ticket_num', label='Ticket Number', active=True), | |
605 | - dict(name='summary', sort_name='summary', label='Summary', active=True), | |
606 | - dict(name='_milestone', sort_name='custom_fields._milestone', label='Milestone', active=True), | |
607 | - dict(name='status', sort_name='status', label='Status', active=True), | |
608 | - dict(name='assigned_to', sort_name='assigned_to_username', label='Owner', active=True)] | |
609 | - for field in sortable_custom_fields: | |
610 | - columns.append( | |
611 | - dict(name=field['name'], sort_name=field['name'], label=field['label'], active=True)) | |
612 | 606 | return dict( |
613 | 607 | tickets=tickets, |
614 | - sortable_custom_fields=sortable_custom_fields, | |
615 | - columns=columns, | |
616 | 608 | count=count, q=json.dumps(query), limit=limit, page=page, sort=sort, |
617 | 609 | **kw) |
618 | 610 | |
611 | + @classmethod | |
612 | + def paged_search(cls, app_config, user, q, limit=None, page=0, sort=None, **kw): | |
613 | + """Query tickets from Solr, filtering for 'read' permission, sorting and paginating the result. | |
614 | + | |
615 | + See also paged_query which does a mongo search. | |
616 | + | |
617 | + We do the sorting and skipping right in SOLR, before we ever ask | |
618 | + Mongo for the actual tickets. Other keywords for | |
619 | + search_artifact (e.g., history) or for SOLR are accepted through | |
620 | + kw. The output is intended to be used directly in templates, | |
621 | + e.g., exposed controller methods can just: | |
622 | + | |
623 | + return paged_query(q, ...) | |
624 | + | |
625 | + If you want all the results at once instead of paged you have | |
626 | + these options: | |
627 | + - don't call this routine, search directly in mongo | |
628 | + - call this routine with a very high limit and TEST that | |
629 | + count<=limit in the result | |
630 | + limit=-1 is NOT recognized as 'all'. 500 is a reasonable limit. | |
631 | + """ | |
632 | + | |
633 | + limit, page, start = g.handle_paging(limit, page, default=25) | |
634 | + count = 0 | |
635 | + tickets = [] | |
636 | + refined_sort = sort if sort else 'ticket_num_i asc' | |
637 | + if 'ticket_num_i' not in refined_sort: | |
638 | + refined_sort += ',ticket_num_i asc' | |
639 | + try: | |
640 | + if q: | |
641 | + matches = search_artifact( | |
642 | + cls, q, | |
643 | + rows=limit, sort=refined_sort, start=start, fl='ticket_num_i', **kw) | |
644 | + else: | |
645 | + matches = None | |
646 | + solr_error = None | |
647 | + except ValueError, e: | |
648 | + solr_error = e.args[0] | |
649 | + matches = [] | |
650 | + if matches: | |
651 | + count = matches.hits | |
652 | + # ticket_numbers is in sorted order | |
653 | + ticket_numbers = [match['ticket_num_i'] for match in matches.docs] | |
654 | + # but query, unfortunately, returns results in arbitrary order | |
655 | + query = cls.query.find(dict(app_config_id=app_config._id, ticket_num={'$in':ticket_numbers})) | |
656 | + # so stick all the results in a dictionary... | |
657 | + ticket_for_num = {} | |
658 | + for t in query: | |
659 | + ticket_for_num[t.ticket_num] = t | |
660 | + # and pull them out in the order given by ticket_numbers | |
661 | + tickets = [] | |
662 | + for tn in ticket_numbers: | |
663 | + if tn in ticket_for_num: | |
664 | + if security.has_access(ticket_for_num[tn], 'read', user, app_config.project): | |
665 | + tickets.append(ticket_for_num[tn]) | |
666 | + else: | |
667 | + count = count -1 | |
668 | + return dict(tickets=tickets, | |
669 | + count=count, q=q, limit=limit, page=page, sort=sort, | |
670 | + solr_error=solr_error, **kw) | |
671 | + | |
619 | 672 | class TicketAttachment(BaseAttachment): |
620 | 673 | thumbnail_size = (100, 100) |
621 | 674 | ArtifactType=Ticket |
@@ -3,10 +3,14 @@ pylons.c = pylons.tmpl_context | ||
3 | 3 | pylons.g = pylons.app_globals |
4 | 4 | from pylons import c |
5 | 5 | |
6 | +from datadiff.tools import assert_equal | |
7 | +from mock import patch | |
8 | + | |
6 | 9 | from allura.lib import helpers as h |
7 | 10 | from allura.tests import decorators as td |
8 | 11 | from alluratest.controller import TestRestApiBase |
9 | 12 | |
13 | +from forgetracker import model as TM | |
10 | 14 | |
11 | 15 | class TestTrackerApiBase(TestRestApiBase): |
12 | 16 |
@@ -60,7 +64,7 @@ class TestRestUpdateTicket(TestTrackerApiBase): | ||
60 | 64 | self.ticket_args = ticket_view.json['ticket'] |
61 | 65 | |
62 | 66 | def test_ticket_index(self): |
63 | - tickets = self.api_post('/rest/p/test/bugs/') | |
67 | + tickets = self.api_get('/rest/p/test/bugs/') | |
64 | 68 | assert len(tickets.json['tickets']) == 1, tickets.json |
65 | 69 | assert (tickets.json['tickets'][0] |
66 | 70 | == dict(ticket_num=1, summary='test new ticket')), tickets.json['tickets'][0] |
@@ -94,22 +98,55 @@ class TestRestDiscussion(TestTrackerApiBase): | ||
94 | 98 | self.ticket_args = ticket_view.json['ticket'] |
95 | 99 | |
96 | 100 | def test_index(self): |
97 | - r = self.api_post('/rest/p/test/bugs/_discuss/') | |
101 | + r = self.api_get('/rest/p/test/bugs/_discuss/') | |
98 | 102 | assert len(r.json['discussion']['threads']) == 1, r.json |
99 | 103 | for t in r.json['discussion']['threads']: |
100 | - r = self.api_post('/rest/p/test/bugs/_discuss/thread/%s/' % t['_id']) | |
104 | + r = self.api_get('/rest/p/test/bugs/_discuss/thread/%s/' % t['_id']) | |
101 | 105 | assert len(r.json['thread']['posts']) == 0, r.json |
102 | 106 | |
103 | 107 | def test_post(self): |
104 | - discussion = self.api_post('/rest/p/test/bugs/_discuss/').json['discussion'] | |
108 | + discussion = self.api_get('/rest/p/test/bugs/_discuss/').json['discussion'] | |
105 | 109 | post = self.api_post('/rest/p/test/bugs/_discuss/thread/%s/new' % discussion['threads'][0]['_id'], |
106 | 110 | text='This is a comment', wrap_args=None) |
107 | - thread = self.api_post('/rest/p/test/bugs/_discuss/thread/%s/' % discussion['threads'][0]['_id']) | |
111 | + thread = self.api_get('/rest/p/test/bugs/_discuss/thread/%s/' % discussion['threads'][0]['_id']) | |
108 | 112 | assert len(thread.json['thread']['posts']) == 1, thread.json |
109 | 113 | assert post.json['post']['text'] == 'This is a comment', post.json |
110 | 114 | reply = self.api_post( |
111 | 115 | '/rest/p/test/bugs/_discuss/thread/%s/%s/reply' % (thread.json['thread']['_id'], post.json['post']['slug']), |
112 | 116 | text='This is a reply', wrap_args=None) |
113 | 117 | assert reply.json['post']['text'] == 'This is a reply', reply.json |
114 | - thread = self.api_post('/rest/p/test/bugs/_discuss/thread/%s/' % discussion['threads'][0]['_id']) | |
118 | + thread = self.api_get('/rest/p/test/bugs/_discuss/thread/%s/' % discussion['threads'][0]['_id']) | |
115 | 119 | assert len(thread.json['thread']['posts']) == 2, thread.json |
120 | + | |
121 | +class TestRestSearch(TestTrackerApiBase): | |
122 | + | |
123 | + @patch('forgetracker.model.Ticket.paged_search') | |
124 | + def test_no_criteria(self, paged_search): | |
125 | + paged_search.return_value = dict(tickets=[ | |
126 | + TM.Ticket(ticket_num=5, summary='our test ticket'), | |
127 | + ]) | |
128 | + r = self.api_get('/rest/p/test/bugs/search') | |
129 | + assert_equal(r.status_int, 200) | |
130 | + assert_equal(r.json, {'tickets':[ | |
131 | + {'summary': 'our test ticket', 'ticket_num': 5}, | |
132 | + ]}) | |
133 | + | |
134 | + @patch('forgetracker.model.Ticket.paged_search') | |
135 | + def test_some_criteria(self, paged_search): | |
136 | + q = 'labels:testing && status:open' | |
137 | + paged_search.return_value = dict(tickets=[ | |
138 | + TM.Ticket(ticket_num=5, summary='our test ticket'), | |
139 | + ], | |
140 | + sort='status', | |
141 | + limit=2, | |
142 | + count=1, | |
143 | + page=0, | |
144 | + q=q, | |
145 | + ) | |
146 | + r = self.api_get('/rest/p/test/bugs/search', q=q, sort='status', limit='2') | |
147 | + assert_equal(r.status_int, 200) | |
148 | + assert_equal(r.json, {'limit': 2, 'q': q, 'sort':'status', 'count': 1, | |
149 | + 'page': 0, 'tickets':[ | |
150 | + {'summary': 'our test ticket', 'ticket_num': 5}, | |
151 | + ] | |
152 | + }) |
@@ -163,12 +163,21 @@ class TestFunctionalController(TrackerTestController): | ||
163 | 163 | r = self.app.get('/p/test/bugs/search/?q=ticket', extra_environ=env) |
164 | 164 | assert '1 results' in r |
165 | 165 | assert 'Private Ticket' not in r |
166 | + # ... or in search feed... | |
167 | + r = self.app.get('/p/test/bugs/search_feed?q=ticket', extra_environ=env) | |
168 | + assert 'Private Ticket' not in r | |
166 | 169 | # ...and can't get to the private ticket directly. |
167 | 170 | r = self.app.get(ticket_view.request.url, extra_environ=env) |
168 | 171 | assert 'Private Ticket' not in r |
169 | 172 | # ... and it doesn't appear in the feed |
170 | 173 | r = self.app.get('/p/test/bugs/feed.atom') |
171 | 174 | assert 'Private Ticket' not in r |
175 | + # ... or in the API ... | |
176 | + r = self.app.get('/rest/p/test/bugs/2/') | |
177 | + assert 'Private Ticket' not in r | |
178 | + assert '/auth/?return_to' in r.headers['Location'] | |
179 | + r = self.app.get('/rest/p/test/bugs/') | |
180 | + assert 'Private Ticket' not in r | |
172 | 181 | |
173 | 182 | @td.with_tool('test', 'Tickets', 'doc-bugs') |
174 | 183 | def test_two_trackers(self): |
@@ -659,6 +668,16 @@ class TestFunctionalController(TrackerTestController): | ||
659 | 668 | assert '3 results' in response, response.showbrowser() |
660 | 669 | assert 'test third ticket' in response, response.showbrowser() |
661 | 670 | |
671 | + def test_search_feed(self): | |
672 | + self.new_ticket(summary='test first ticket') | |
673 | + ThreadLocalORMSession.flush_all() | |
674 | + M.MonQTask.run_ready() | |
675 | + ThreadLocalORMSession.flush_all() | |
676 | + response = self.app.get('/p/test/bugs/search_feed?q=test') | |
677 | + assert '<title>test first ticket</title>' in response | |
678 | + response = self.app.get('/p/test/bugs/search_feed.atom?q=test') | |
679 | + assert '<title>test first ticket</title>' in response | |
680 | + | |
662 | 681 | def test_touch(self): |
663 | 682 | self.new_ticket(summary='test touch') |
664 | 683 | h.set_context('test', 'bugs', neighborhood='Projects') |
@@ -1,12 +0,0 @@ | ||
1 | -from nose.tools import assert_true | |
2 | - | |
3 | -from allura.tests import decorators as td | |
4 | -from alluratest.controller import TestController | |
5 | - | |
6 | - | |
7 | -class TestRootController(TestController): | |
8 | - @td.with_tracker | |
9 | - def test_index(self): | |
10 | - response = self.app.get('/bugs/') | |
11 | - assert_true('bugs' in response) | |
12 | - |
@@ -49,7 +49,7 @@ def solr_search_returning_colors_are_wrong_ticket(): | ||
49 | 49 | matches = Mock() |
50 | 50 | matches.docs = [dict(ticket_num_i=ticket.ticket_num)] |
51 | 51 | search_artifact.return_value = matches |
52 | - return patch('forgetracker.tracker_main.search_artifact', search_artifact) | |
52 | + return patch('forgetracker.model.ticket.search_artifact', search_artifact) | |
53 | 53 | |
54 | 54 | def mongo_search_returning_colors_are_wrong_ticket(): |
55 | 55 | ticket = create_colors_are_wrong_ticket() |
@@ -82,4 +82,3 @@ def create_ticket(summary, custom_fields): | ||
82 | 82 | custom_fields=custom_fields) |
83 | 83 | session(ticket).flush() |
84 | 84 | return ticket |
85 | - |
@@ -306,6 +306,33 @@ class ForgeTrackerApp(Application): | ||
306 | 306 | def bins(self): |
307 | 307 | return TM.Bin.query.find(dict(app_config_id=self.config._id)).sort('summary').all() |
308 | 308 | |
309 | + | |
310 | + | |
311 | + | |
312 | + | |
313 | +### Controllers ### | |
314 | + | |
315 | +def mongo_columns(): | |
316 | + columns = [dict(name='ticket_num', sort_name='ticket_num', label='Ticket Number', active=True), | |
317 | + dict(name='summary', sort_name='summary', label='Summary', active=True), | |
318 | + dict(name='_milestone', sort_name='custom_fields._milestone', label='Milestone', active=True), | |
319 | + dict(name='status', sort_name='status', label='Status', active=True), | |
320 | + dict(name='assigned_to', sort_name='assigned_to_username', label='Owner', active=True)] | |
321 | + for field in c.app.globals.sortable_custom_fields_shown_in_search(): | |
322 | + columns.append( | |
323 | + dict(name=field['name'], sort_name=field['name'], label=field['label'], active=True)) | |
324 | + return columns | |
325 | + | |
326 | +def solr_columns(): | |
327 | + columns = [dict(name='ticket_num', sort_name='ticket_num_i', label='Ticket Number', active=True), | |
328 | + dict(name='summary', sort_name='snippet_s', label='Summary', active=True), | |
329 | + dict(name='_milestone', sort_name='_milestone_s', label='Milestone', active=True), | |
330 | + dict(name='status', sort_name='status_s', label='Status', active=True), | |
331 | + dict(name='assigned_to', sort_name='assigned_to_s', label='Owner', active=True)] | |
332 | + for field in c.app.globals.sortable_custom_fields_shown_in_search(): | |
333 | + columns.append(dict(name=field['name'], sort_name=field['sortable_name'], label=field['label'], active=True)) | |
334 | + return columns | |
335 | + | |
309 | 336 | class RootController(BaseController): |
310 | 337 | |
311 | 338 | def __init__(self): |
@@ -331,84 +358,17 @@ class RootController(BaseController): | ||
331 | 358 | bin_counts.append(dict(label=label, count=count)) |
332 | 359 | return dict(bin_counts=bin_counts) |
333 | 360 | |
334 | - def paged_query(self, q, limit=None, page=0, sort=None, columns=None, **kw): | |
335 | - """Query tickets, sorting and paginating the result. | |
336 | - | |
337 | - We do the sorting and skipping right in SOLR, before we ever ask | |
338 | - Mongo for the actual tickets. Other keywords for | |
339 | - search_artifact (e.g., history) or for SOLR are accepted through | |
340 | - kw. The output is intended to be used directly in templates, | |
341 | - e.g., exposed controller methods can just: | |
342 | - | |
343 | - return paged_query(q, ...) | |
344 | - | |
345 | - If you want all the results at once instead of paged you have | |
346 | - these options: | |
347 | - - don't call this routine, search directly in mongo | |
348 | - - call this routine with a very high limit and TEST that | |
349 | - count<=limit in the result | |
350 | - limit=-1 is NOT recognized as 'all'. 500 is a reasonable limit. | |
351 | - """ | |
352 | - | |
353 | - limit, page, start = g.handle_paging(limit, page, default=25) | |
354 | - count = 0 | |
355 | - tickets = [] | |
356 | - refined_sort = sort if sort else 'ticket_num_i asc' | |
357 | - if 'ticket_num_i' not in refined_sort: | |
358 | - refined_sort += ',ticket_num_i asc' | |
359 | - try: | |
360 | - if q: | |
361 | - matches = search_artifact( | |
362 | - TM.Ticket, q, | |
363 | - rows=limit, sort=refined_sort, start=start, fl='ticket_num_i', **kw) | |
364 | - else: | |
365 | - matches = None | |
366 | - solr_error = None | |
367 | - except ValueError, e: | |
368 | - solr_error = e.args[0] | |
369 | - matches = [] | |
370 | - if matches: | |
371 | - count = matches.hits | |
372 | - # ticket_numbers is in sorted order | |
373 | - ticket_numbers = [match['ticket_num_i'] for match in matches.docs] | |
374 | - # but query, unfortunately, returns results in arbitrary order | |
375 | - query = TM.Ticket.query.find(dict(app_config_id=c.app.config._id, ticket_num={'$in':ticket_numbers})) | |
376 | - # so stick all the results in a dictionary... | |
377 | - ticket_for_num = {} | |
378 | - for t in query: | |
379 | - ticket_for_num[t.ticket_num] = t | |
380 | - # and pull them out in the order given by ticket_numbers | |
381 | - tickets = [] | |
382 | - for tn in ticket_numbers: | |
383 | - if tn in ticket_for_num: | |
384 | - if has_access(ticket_for_num[tn], 'read'): | |
385 | - tickets.append(ticket_for_num[tn]) | |
386 | - else: | |
387 | - count = count -1 | |
388 | - sortable_custom_fields=c.app.globals.sortable_custom_fields_shown_in_search() | |
389 | - if not columns: | |
390 | - columns = [dict(name='ticket_num', sort_name='ticket_num_i', label='Ticket Number', active=True), | |
391 | - dict(name='summary', sort_name='snippet_s', label='Summary', active=True), | |
392 | - dict(name='_milestone', sort_name='_milestone_s', label='Milestone', active=True), | |
393 | - dict(name='status', sort_name='status_s', label='Status', active=True), | |
394 | - dict(name='assigned_to', sort_name='assigned_to_s', label='Owner', active=True)] | |
395 | - for field in sortable_custom_fields: | |
396 | - columns.append(dict(name=field['name'], sort_name=field['sortable_name'], label=field['label'], active=True)) | |
397 | - return dict(tickets=tickets, | |
398 | - sortable_custom_fields=sortable_custom_fields, | |
399 | - columns=columns, | |
400 | - count=count, q=q, limit=limit, page=page, sort=sort, | |
401 | - solr_error=solr_error, **kw) | |
402 | - | |
403 | 361 | @with_trailing_slash |
404 | 362 | @h.vardec |
405 | 363 | @expose('jinja:forgetracker:templates/tracker/index.html') |
406 | 364 | def index(self, limit=25, columns=None, page=0, sort='ticket_num desc', **kw): |
407 | 365 | kw.pop('q', None) # it's just our original query mangled and sent back to us |
408 | - result = TM.Ticket.paged_query(c.app.globals.not_closed_mongo_query, | |
366 | + result = TM.Ticket.paged_query(c.app.config, c.user, | |
367 | + c.app.globals.not_closed_mongo_query, | |
409 | 368 | sort=sort, limit=int(limit), |
410 | - columns=columns, page=page, **kw) | |
411 | - c.subscribe_form = W.subscribe_form | |
369 | + page=page, **kw) | |
370 | + result['columns'] = columns or mongo_columns() | |
371 | + result['sortable_custom_fields'] = c.app.globals.sortable_custom_fields_shown_in_search() | |
412 | 372 | result['subscribed'] = M.Mailbox.subscribed() |
413 | 373 | result['allow_edit'] = has_access(c.app, 'update')() |
414 | 374 | result['help_msg'] = c.app.config.options.get('TicketHelpSearch') |
@@ -418,6 +378,7 @@ class RootController(BaseController): | ||
418 | 378 | sort_split = sort.split(' ') |
419 | 379 | solr_col = _mongo_col_to_solr_col(sort_split[0]) |
420 | 380 | result['url_sort'] = '%s %s' % (solr_col, sort_split[1]) |
381 | + c.subscribe_form = W.subscribe_form | |
421 | 382 | c.ticket_search_results = W.ticket_search_results |
422 | 383 | return result |
423 | 384 |
@@ -506,7 +467,9 @@ class RootController(BaseController): | ||
506 | 467 | bin = TM.Bin.query.find(dict(app_config_id=c.app.config._id,terms=q)).first() |
507 | 468 | if project: |
508 | 469 | redirect(c.project.url() + 'search?' + urlencode(dict(q=q, history=kw.get('history')))) |
509 | - result = self.paged_query(q, page=page, sort=sort, columns=columns, **kw) | |
470 | + result = TM.Ticket.paged_search(c.app.config, c.user, q, page=page, sort=sort, **kw) | |
471 | + result['columns'] = columns or solr_columns() | |
472 | + result['sortable_custom_fields'] = c.app.globals.sortable_custom_fields_shown_in_search() | |
510 | 473 | result['allow_edit'] = has_access(c.app, 'update')() |
511 | 474 | result['bin'] = bin |
512 | 475 | result['help_msg'] = c.app.config.options.get('TicketHelpSearch') |
@@ -517,23 +480,24 @@ class RootController(BaseController): | ||
517 | 480 | @h.vardec |
518 | 481 | @expose() |
519 | 482 | @validate(validators=search_validators) |
520 | - def search_feed(self, q=None, query=None, project=None, columns=None, page=0, sort=None, **kw): | |
483 | + def search_feed(self, q=None, query=None, project=None, page=0, sort=None, **kw): | |
521 | 484 | if query and not q: |
522 | 485 | q = query |
523 | - result = self.paged_query(q, page=page, sort=sort, columns=columns, **kw) | |
486 | + result = TM.Ticket.paged_search(c.app.config, c.user, q, page=page, sort=sort, **kw) | |
524 | 487 | response.headers['Content-Type'] = '' |
525 | 488 | response.content_type = 'application/xml' |
526 | - d = dict(title='Ticket search results', link=c.app.url, description='You searched for %s' % q, language=u'en') | |
489 | + d = dict(title='Ticket search results', link=h.absurl(c.app.url), description='You searched for %s' % q, language=u'en') | |
527 | 490 | if request.environ['PATH_INFO'].endswith('.atom'): |
528 | 491 | feed = FG.Atom1Feed(**d) |
529 | 492 | else: |
530 | 493 | feed = FG.Rss201rev2Feed(**d) |
531 | 494 | for t in result['tickets']: |
495 | + url = h.absurl(t.url().encode('utf-8')) | |
532 | 496 | feed.add_item(title=t.summary, |
533 | - link=h.absurl(t.url().encode('utf-8')), | |
497 | + link=url, | |
534 | 498 | pubdate=t.mod_date, |
535 | 499 | description=t.description, |
536 | - unique_id=str(t._id), | |
500 | + unique_id=url, | |
537 | 501 | author_name=t.reported_by.display_name, |
538 | 502 | author_link=h.absurl(t.reported_by.url())) |
539 | 503 | return feed.writeString('utf-8') |
@@ -622,9 +586,11 @@ class RootController(BaseController): | ||
622 | 586 | sort=validators.UnicodeString(if_empty='ticket_num_i asc'))) |
623 | 587 | def edit(self, q=None, limit=None, page=None, sort=None, **kw): |
624 | 588 | require_access(c.app, 'update') |
625 | - result = self.paged_query(q, sort=sort, limit=limit, page=page, **kw) | |
589 | + result = TM.Ticket.paged_search(c.app.config, c.user, q, sort=sort, limit=limit, page=page, **kw) | |
626 | 590 | # if c.app.globals.milestone_names is None: |
627 | 591 | # c.app.globals.milestone_names = '' |
592 | + result['columns'] = solr_columns() | |
593 | + result['sortable_custom_fields'] = c.app.globals.sortable_custom_fields_shown_in_search() | |
628 | 594 | result['globals'] = c.app.globals |
629 | 595 | result['cancel_href'] = url( |
630 | 596 | c.app.url + 'search/', |
@@ -1338,10 +1304,14 @@ class RootRestController(BaseController): | ||
1338 | 1304 | require_access(c.app, 'read') |
1339 | 1305 | |
1340 | 1306 | @expose('json:') |
1341 | - def index(self, **kw): | |
1342 | - return dict(tickets=[ | |
1343 | - dict(ticket_num=t.ticket_num, summary=t.summary) | |
1344 | - for t in TM.Ticket.query.find(dict(app_config_id=c.app.config._id)).sort('ticket_num') ]) | |
1307 | + def index(self, limit=100, page=0, **kw): | |
1308 | + results = TM.Ticket.paged_query(c.app.config, c.user, query={}, | |
1309 | + limit=int(limit), page=int(page)) | |
1310 | + results['tickets'] = [dict(ticket_num=t.ticket_num, summary=t.summary) | |
1311 | + for t in results['tickets']] | |
1312 | + results.pop('q', None) | |
1313 | + results.pop('sort', None) | |
1314 | + return results | |
1345 | 1315 | |
1346 | 1316 | @expose() |
1347 | 1317 | @h.vardec |
@@ -1385,6 +1355,13 @@ class RootRestController(BaseController): | ||
1385 | 1355 | log.exception(e) |
1386 | 1356 | return dict(status=False, errors=[str(e)]) |
1387 | 1357 | |
1358 | + @expose('json:') | |
1359 | + def search(self, q=None, limit=100, page=0, sort=None, **kw): | |
1360 | + results = TM.Ticket.paged_search(c.app.config, c.user, q, limit, page, sort) | |
1361 | + results['tickets'] = [dict(ticket_num=t.ticket_num, summary=t.summary) | |
1362 | + for t in results['tickets']] | |
1363 | + return results | |
1364 | + | |
1388 | 1365 | @expose() |
1389 | 1366 | def _lookup(self, ticket_num, *remainder): |
1390 | 1367 | return TicketRestController(ticket_num), remainder |
@@ -1445,8 +1422,10 @@ class MilestoneController(BaseController): | ||
1445 | 1422 | sort=validators.UnicodeString(if_empty=None))) |
1446 | 1423 | def index(self, q=None, columns=None, page=0, query=None, sort=None, **kw): |
1447 | 1424 | require(has_access(c.app, 'read')) |
1448 | - result = TM.Ticket.paged_query( | |
1449 | - self.mongo_query, page=page, sort=sort, columns=columns, **kw) | |
1425 | + result = TM.Ticket.paged_query(c.app.config, c.user, | |
1426 | + self.mongo_query, page=page, sort=sort, **kw) | |
1427 | + result['columns'] = columns or mongo_columns() | |
1428 | + result['sortable_custom_fields'] = c.app.globals.sortable_custom_fields_shown_in_search() | |
1450 | 1429 | result['allow_edit'] = has_access(c.app, 'update')() |
1451 | 1430 | result['help_msg'] = c.app.config.options.get('TicketHelpSearch') |
1452 | 1431 | progress = c.app.globals.milestone_count(self.progress_key) |
@@ -1474,8 +1453,10 @@ class MilestoneController(BaseController): | ||
1474 | 1453 | sort=validators.UnicodeString(if_empty='ticket_num_i asc'))) |
1475 | 1454 | def edit(self, q=None, limit=None, page=None, sort=None, columns=None, **kw): |
1476 | 1455 | require_access(c.app, 'update') |
1477 | - result = TM.Ticket.paged_query( | |
1478 | - self.mongo_query, page=page, sort=sort, columns=columns, **kw) | |
1456 | + result = TM.Ticket.paged_query(c.app.config, c.user, | |
1457 | + self.mongo_query, page=page, sort=sort, **kw) | |
1458 | + result['columns'] = columns or mongo_columns() | |
1459 | + result['sortable_custom_fields'] = c.app.globals.sortable_custom_fields_shown_in_search() | |
1479 | 1460 | # if c.app.globals.milestone_names is None: |
1480 | 1461 | # c.app.globals.milestone_names = '' |
1481 | 1462 | result.pop('q') |