Merge lp:~fabricematrat/charmworld/redirect-left into lp:charmworld

Proposed by Fabrice Matrat
Status: Merged
Approved by: Fabrice Matrat
Approved revision: 521
Merged at revision: 521
Proposed branch: lp:~fabricematrat/charmworld/redirect-left
Merge into: lp:charmworld
Diff against target: 289 lines (+38/-162)
5 files modified
charmworld/views/misc.py (+6/-17)
charmworld/views/search.py (+9/-36)
charmworld/views/tests/test_auth.py (+2/-9)
charmworld/views/tests/test_misc.py (+0/-27)
charmworld/views/tests/test_search.py (+21/-73)
To merge this branch: bzr merge lp:~fabricematrat/charmworld/redirect-left
Reviewer Review Type Date Requested Status
Juju Gui Bot continuous-integration Approve
Brad Crittenden (community) code, qa Approve
Francesco Banconi Approve
Review via email: mp+249169@code.launchpad.net

Commit message

Added redirect for search, home, charmers

Description of the change

Added redirection for home, search, charmers

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Looks good, thank you.

review: Approve
Revision history for this message
Francesco Banconi (frankban) :
Revision history for this message
Brad Crittenden (bac) wrote :

Nice, no QA yet.

review: Approve (code)
Revision history for this message
Brad Crittenden (bac) wrote :

QA-not-OK. The /charmers redirect appears to be broken, unless I am exercising it incorrectly.

review: Needs Fixing (qa)
Revision history for this message
Brad Crittenden (bac) wrote :

QA-OK. Thanks for pointing me to the new endpoint.

review: Approve (code, qa)
Revision history for this message
Juju Gui Bot (juju-gui-bot) wrote :

FAILED: Autolanding.
No commit message was specified in the merge proposal. Hit 'Add commit message' on the merge proposal web page or follow the link below. You can approve the merge proposal yourself to rerun.
https://code.launchpad.net/~fabricematrat/charmworld/redirect-left/+merge/249169/+edit-commit-message

review: Needs Fixing (continuous-integration)
Revision history for this message
Juju Gui Bot (juju-gui-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmworld/views/misc.py'
--- charmworld/views/misc.py 2013-11-13 12:48:24 +0000
+++ charmworld/views/misc.py 2015-02-10 11:13:22 +0000
@@ -1,8 +1,6 @@
1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33from pyramid.httpexceptions import HTTPMovedPermanently
4from collections import defaultdict
5
6from pyramid.view import view_config4from pyramid.view import view_config
75
8from charmworld import cached_view_config6from charmworld import cached_view_config
@@ -27,26 +25,17 @@
27 route_name="home",25 route_name="home",
28 renderer="charmworld:templates/index.pt")26 renderer="charmworld:templates/index.pt")
29def home(request):27def home(request):
30 return {}28 redirect_url = request.registry.settings.get('redirect_jujucharms')
29 raise HTTPMovedPermanently(location=redirect_url)
3130
3231
33@view_config(32@view_config(
34 route_name="charmers",33 route_name="charmers",
35 renderer="charmworld:templates/charmers.pt")34 renderer="charmworld:templates/charmers.pt")
36def charmers(request):35def charmers(request):
37 # We want to collect which users have contributes on which charms.36 redirect_url = request.registry.settings.get('redirect_jujucharms')
38 # This however reports charm owners ordered decending by ownership.37 location = '{url}/community/charmers'.format(url=redirect_url)
39 # The largest owner is not included because we think this is ~charmers.38 raise HTTPMovedPermanently(location=location)
40 results = request.db.charms.find(
41 {}, {"name": 1, 'owner': 1, 'short_url': 1})
42
43 owner_map = defaultdict(int)
44 for i in results:
45 owner_map[i['owner']] += 1
46 owners = sorted(
47 owner_map.items(), key=lambda i: (i[1], i[0]), reverse=True)
48 owners.pop(0)
49 return {"owners": owners}
5039
5140
52@view_config(41@view_config(
5342
=== modified file 'charmworld/views/search.py'
--- charmworld/views/search.py 2014-03-18 15:20:47 +0000
+++ charmworld/views/search.py 2015-02-10 11:13:22 +0000
@@ -1,46 +1,19 @@
1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
3from pyelasticsearch.exceptions import ElasticHttpError3from pyramid.httpexceptions import HTTPMovedPermanently
4from pyramid.httpexceptions import HTTPFound
5from pyramid.renderers import render_to_response
6from pyramid.view import view_config4from pyramid.view import view_config
75
8from charmworld.search import (
9 IndexNotReady,
10 SearchServiceNotAvailable,
11)
12from charmworld.views import log
13
14
15def do_search(request):
16 text = request.params["search_text"]
17 return request.index_client.search(text)
18
196
20@view_config(7@view_config(
21 route_name="search",8 route_name="search",
22 renderer="charmworld:templates/search.pt")9 renderer="charmworld:templates/search.pt")
23def search(request):10def search(request):
11 redirect_url = request.registry.settings.get('redirect_jujucharms')
12 location = '{url}/q'.format(url=redirect_url)
24 if not "search_text" in request.params:13 if not "search_text" in request.params:
25 url = request.route_url("home")14 raise HTTPMovedPermanently(location=location)
26 return HTTPFound(location=url)15 text = request.params["search_text"].strip()
27 try:16 text = '/'.join(text.split())
28 search = do_search(request)17 location = '{location}/{search}'.format(location=location,
29 except (IndexNotReady, SearchServiceNotAvailable):18 search=text)
30 response = render_to_response('../templates/search-not-ready.pt', {},19 raise HTTPMovedPermanently(location=location)
31 request=request)
32 response.status_code = 503
33 return response
34 except ElasticHttpError as exc:
35 text = request.params["search_text"]
36 log.warning(
37 "User search error with search text='{}'".format(text))
38 log.exception(exc)
39 response = render_to_response(
40 '../templates/search-terms-bad.pt',
41 {'search_text': text},
42 request=request)
43 response.status_code = 400
44 return response
45 return dict((key, value) for key, value in search.items()
46 if key != 'matches')
4720
=== modified file 'charmworld/views/tests/test_auth.py'
--- charmworld/views/tests/test_auth.py 2013-09-04 14:48:30 +0000
+++ charmworld/views/tests/test_auth.py 2015-02-10 11:13:22 +0000
@@ -40,19 +40,12 @@
4040
41 def test_starts_with_login(self):41 def test_starts_with_login(self):
42 """Without being logged in you get the login nav link."""42 """Without being logged in you get the login nav link."""
43 resp = self.app.get('/', status=200)43 self.app.get('/', status=301)
44
45 body = resp.body
46 self.assertIn("/login/openid", body)
47 self.assertNotIn("/logout", body)
4844
49 def test_after_login_see_logout(self):45 def test_after_login_see_logout(self):
50 """Once logged in you get the logout nav link."""46 """Once logged in you get the logout nav link."""
51 with login_request(self.app) as resp:47 with login_request(self.app) as resp:
52 resp.goto('/', status=200)48 resp.goto('/', status=301)
53 body = resp.body
54 self.assertNotIn("/login/openid", body)
55 self.assertIn("/logout", body)
5649
57 def test_login_route(self):50 def test_login_route(self):
58 path = '/login/openid'51 path = '/login/openid'
5952
=== modified file 'charmworld/views/tests/test_misc.py'
--- charmworld/views/tests/test_misc.py 2013-11-14 13:38:24 +0000
+++ charmworld/views/tests/test_misc.py 2015-02-10 11:13:22 +0000
@@ -2,7 +2,6 @@
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4import os4import os
5import re
6import unittest5import unittest
76
8from mock import patch7from mock import patch
@@ -74,32 +73,6 @@
74 else:73 else:
75 self.fail('Cache-Control header not found.')74 self.fail('Cache-Control header not found.')
7675
77 def test_main_template_scheme(self):
78 """Image and stylesheet URLs respect the WSGI scheme."""
79 src_pattern = re.compile('src="([a-z]+)://')
80 css_pattern = re.compile('href="([a-z]+)://.*\.css.*"')
81 # Https enabled.
82 response = self.app.get(
83 '/', extra_environ={'wsgi.url_scheme': 'https'})
84 https_count = 0
85 for match in src_pattern.finditer(response.ubody):
86 https_count += 1
87 self.assertEqual('https', match.group(1))
88 for match in css_pattern.finditer(response.ubody):
89 https_count += 1
90 self.assertEqual('https', match.group(1))
91 self.assertTrue(https_count >= 2)
92 # Http default.
93 response = self.app.get('/')
94 http_count = 0
95 for match in src_pattern.finditer(response.ubody):
96 http_count += 1
97 self.assertEqual('http', match.group(1))
98 for match in css_pattern.finditer(response.ubody):
99 http_count += 1
100 self.assertEqual('http', match.group(1))
101 self.assertTrue(https_count == http_count)
102
10376
104class HeartbeatWebTestCase(WebTestBase):77class HeartbeatWebTestCase(WebTestBase):
10578
10679
=== modified file 'charmworld/views/tests/test_search.py'
--- charmworld/views/tests/test_search.py 2013-11-15 20:00:35 +0000
+++ charmworld/views/tests/test_search.py 2015-02-10 11:13:22 +0000
@@ -1,14 +1,8 @@
1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4from mock import patch4from pyramid.httpexceptions import HTTPMovedPermanently
5from pyelasticsearch import ElasticSearch
6from pyelasticsearch.exceptions import ElasticHttpError
75
8from charmworld.search import (
9 ElasticSearchClient,
10 IndexNotReady,
11)
12from charmworld.testing import (6from charmworld.testing import (
13 ViewTestBase,7 ViewTestBase,
14 WebTestBase,8 WebTestBase,
@@ -26,72 +20,26 @@
2620
27class SearchTests(ViewTestBase):21class SearchTests(ViewTestBase):
2822
29 @patch('charmworld.views.search.do_search',23 def test_search(self):
30 autospec=True, return_value=RETURN_VAL)24 terms = {"search_text": 'foo'}
31 def test_search(self, mock):25 request = self.getRequest(params=terms)
32 terms = {"search_text": 'foo'}26 with self.assertRaises(HTTPMovedPermanently) as e:
33 keys = ['result_total', 'results', 'search_time']27 search.search(request)
34 request = self.getRequest(params=terms)28 self.assertIn('/q/foo', e.exception.location)
35 response = search.search(request)29
3630 def test_search_multiple(self):
37 mock.assert_called_with(request)31 terms = {"search_text": 'foo bar'}
38 self.assertEqual(keys, sorted(response.keys()))32 request = self.getRequest(params=terms)
39 self.assertEqual(0, response['result_total'])33 with self.assertRaises(HTTPMovedPermanently) as e:
40 self.assertEqual([], response['results'])34 search.search(request)
41 self.assertEqual(0.0, response['search_time'])35 self.assertIn('/q/foo/bar', e.exception.location)
4236
43 @patch('charmworld.views.search.do_search',37 def test_search_with_space(self):
44 autospec=True, side_effect=IndexNotReady)38 terms = {"search_text": ' foo bar '}
45 def test_search_503(self, mock):39 request = self.getRequest(params=terms)
46 terms = {"search_text": 'foo'}40 with self.assertRaises(HTTPMovedPermanently) as e:
47 request = self.getRequest(params=terms)41 search.search(request)
48 # render_to_response requires the request to be much more like a real42 self.assertIn('/q/foo/bar', e.exception.location)
49 # request, with scheme and static stuff handling, so mock it out.
50 with patch('charmworld.views.search.render_to_response') as render:
51 response = search.search(request)
52 render.assert_called_with('../templates/search-not-ready.pt', {},
53 request=request)
54 self.assertEqual(503, response.status_code)
55
56 def test_search_503_on_connection_error(self):
57 terms = {"search_text": 'foo'}
58 request = self.getRequest(params=terms)
59 request.index_client = ElasticSearchClient(
60 ElasticSearch(['http://localhost:70']), 'foo')
61 # render_to_response requires the request to be much more like a real
62 # request, with scheme and static stuff handling, so mock it out.
63 with patch('charmworld.views.search.render_to_response') as render:
64 response = search.search(request)
65 render.assert_called_with('../templates/search-not-ready.pt', {},
66 request=request)
67 self.assertEqual(503, response.status_code)
68
69 def test_tildes_not_in_start_are_unchanged(self):
70 terms = {"search_text": 'fo~o'}
71 request = self.getRequest(params=terms)
72 request.index_client = ElasticSearchClient(
73 ElasticSearch(['http://localhost:70']), '')
74 with patch.object(request.index_client, 'search') as req_search:
75 search.search(request)
76 req_search.assert_called_with('fo~o')
77
78 def test_otherwise_tilde_causes_400(self):
79 terms = {"search_text": 'fo~o'}
80 request = self.getRequest(params=terms)
81 request.index_client = ElasticSearchClient(
82 ElasticSearch(['http://localhost:70']), '')
83
84 def mock_search(request):
85 raise ElasticHttpError('oops')
86
87 with patch.object(request.index_client, 'search', mock_search):
88 with patch('charmworld.views.search.render_to_response') as render:
89 response = search.search(request)
90 render.assert_called_with(
91 '../templates/search-terms-bad.pt',
92 {'search_text': 'fo~o'},
93 request=request)
94 self.assertEqual(400, response.status_code)
9543
9644
97class SearchJSONTests(WebTestBase):45class SearchJSONTests(WebTestBase):

Subscribers

People subscribed via source and target branches

to all changes: