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

Proposed by Fabrice Matrat
Status: Merged
Approved by: Brad Crittenden
Approved revision: 525
Merged at revision: 520
Proposed branch: lp:~fabricematrat/charmworld/redirect-charm
Merge into: lp:charmworld
Diff against target: 470 lines (+88/-225)
3 files modified
charmworld/views/charms.py (+42/-21)
charmworld/views/tests/test_charms.py (+42/-203)
default.ini (+4/-1)
To merge this branch: bzr merge lp:~fabricematrat/charmworld/redirect-charm
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Juju Gui Bot continuous-integration Approve
j.c.sackett (community) Approve
Review via email: mp+249038@code.launchpad.net

Commit message

Add a redirect for charms
R=bac, jcsackett

Description of the change

Add a redirect for charms

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Looks good Fabrice, thanks for the branch. A few suggestions.

review: Approve (code)
Revision history for this message
j.c.sackett (jcsackett) wrote :

Fabrice--

This looks alright. I have some concerns about the redirects, but I don't think there's a better solution at this time--possibly something worth filing issues against jujucharms.com for?

Revision history for this message
j.c.sackett (jcsackett) :
review: Approve
Revision history for this message
Juju Gui Bot (juju-gui-bot) :
review: Approve (continuous-integration)
Revision history for this message
Brad Crittenden (bac) wrote :

Even better

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmworld/views/charms.py'
--- charmworld/views/charms.py 2014-04-02 14:12:22 +0000
+++ charmworld/views/charms.py 2015-02-09 18:46:28 +0000
@@ -9,6 +9,7 @@
9import json9import json
10import os10import os
11import pymongo11import pymongo
12from pyramid.httpexceptions import HTTPMovedPermanently
12from pyramid.view import view_config13from pyramid.view import view_config
13from webob import Response14from webob import Response
1415
@@ -36,7 +37,6 @@
36 found,37 found,
37 interfaces,38 interfaces,
38 name_filter,39 name_filter,
39 sub_filter,
40)40)
4141
4242
@@ -246,42 +246,42 @@
246 route_name="charm-collection",246 route_name="charm-collection",
247 renderer="charmworld:templates/charm-collection.pt")247 renderer="charmworld:templates/charm-collection.pt")
248def charm_collection(request):248def charm_collection(request):
249 query, sub_only = sub_filter({}, request)249 redirect_url = request.registry.settings.get('redirect_jujucharms')
250 return found_charm_collection(250 location = ('{url}/solutions'.format(url=redirect_url))
251 request.db, query, sub_only, only_promulgated=True)251 raise HTTPMovedPermanently(location=location)
252252
253253
254@cached_view_config(254@cached_view_config(
255 route_name="personal-collection",255 route_name="personal-collection",
256 renderer="charmworld:templates/charm-collection.pt")256 renderer="charmworld:templates/charm-collection.pt")
257def personal_collection(request):257def personal_collection(request):
258 query, sub_only = sub_filter({258 redirect_url = request.registry.settings.get('redirect_jujucharms')
259 "owner": request.matchdict["owner"]259 location = ('{url}/q/{owner}'.format(url=redirect_url,
260 }, request)260 owner=request.matchdict["owner"]))
261 return found_charm_collection(request.db, query, sub_only)261 raise HTTPMovedPermanently(location=location)
262262
263263
264@cached_view_config(264@cached_view_config(
265 route_name="series-collection",265 route_name="series-collection",
266 renderer="charmworld:templates/charm-collection.pt")266 renderer="charmworld:templates/charm-collection.pt")
267def series_collection(request):267def series_collection(request):
268 query, sub_only = sub_filter(268 redirect_url = request.registry.settings.get('redirect_jujucharms')
269 {"series": request.matchdict["series"]}, request)269 location = ('{url}/q/{series}'.format(url=redirect_url,
270 return found_charm_collection(270 series=request.matchdict["series"]))
271 request.db, query, sub_only, only_promulgated=True)271 raise HTTPMovedPermanently(location=location)
272272
273273
274@cached_view_config(274@cached_view_config(
275 route_name="personal-series",275 route_name="personal-series",
276 renderer="charmworld:templates/charm-collection.pt")276 renderer="charmworld:templates/charm-collection.pt")
277def personal_series_collection(request):277def personal_series_collection(request):
278 query, sub_only = sub_filter(278 redirect_url = request.registry.settings.get('redirect_jujucharms')
279 {279 location = ('{url}/q/{owner}/{series}'.format(
280 "owner": request.matchdict["owner"],280 url=redirect_url,
281 "series": request.matchdict["series"]281 owner=request.matchdict["owner"],
282 },282 series=request.matchdict["series"])
283 request)283 )
284 return found_charm_collection(request.db, query, sub_only)284 raise HTTPMovedPermanently(location=location)
285285
286286
287@cached_view_config(287@cached_view_config(
@@ -292,7 +292,18 @@
292 renderer="charmworld:templates/charm.pt")292 renderer="charmworld:templates/charm.pt")
293def personal_charm(request):293def personal_charm(request):
294 _reconcile_revision(request)294 _reconcile_revision(request)
295 return _charm_view(request, find_charm(request))295 redirect_url = request.registry.settings.get('redirect_jujucharms')
296 match = request.matchdict
297 location = '{url}/u/{owner}/{charm}/{series}'.format(
298 url=redirect_url,
299 owner=match['owner'],
300 charm=match['charm'],
301 series=match['series']
302 )
303 if 'revision' in match:
304 location = '{location}/{revision}'.format(location=location,
305 revision=match['revision'])
306 raise HTTPMovedPermanently(location=location)
296307
297308
298@cached_view_config(309@cached_view_config(
@@ -316,7 +327,17 @@
316 renderer="charmworld:templates/charm.pt")327 renderer="charmworld:templates/charm.pt")
317def distro_charm(request):328def distro_charm(request):
318 _reconcile_revision(request)329 _reconcile_revision(request)
319 return _charm_view(request, find_charm(request, promulgated=True))330 redirect_url = request.registry.settings.get('redirect_jujucharms')
331 match = request.matchdict
332 location = '{url}/{charm}/{series}'.format(
333 url=redirect_url,
334 charm=match['charm'],
335 series=match['series']
336 )
337 if 'revision' in match:
338 location = '{location}/{revision}'.format(location=location,
339 revision=match['revision'])
340 raise HTTPMovedPermanently(location=location)
320341
321342
322@cached_view_config(343@cached_view_config(
323344
=== modified file 'charmworld/views/tests/test_charms.py'
--- charmworld/views/tests/test_charms.py 2014-04-02 14:12:22 +0000
+++ charmworld/views/tests/test_charms.py 2015-02-09 18:46:28 +0000
@@ -4,6 +4,8 @@
4from datetime import date4from datetime import date
5from logging import getLogger5from logging import getLogger
66
7from pyramid.httpexceptions import HTTPMovedPermanently
8
7from charmworld.jobs.ingest import (9from charmworld.jobs.ingest import (
8 add_files,10 add_files,
9)11)
@@ -131,189 +133,30 @@
131133
132 def test_charm_collection_basic(self):134 def test_charm_collection_basic(self):
133 """Simple sanity check."""135 """Simple sanity check."""
134 one_id, one = factory.makeCharm(self.db, promulgated=True)136 with self.assertRaises(HTTPMovedPermanently) as e:
135 two_id, two = factory.makeCharm(self.db, promulgated=True, owner='foo')137 charm_collection(self.getRequest())
136 three_id, three = factory.makeCharm(self.db, promulgated=False)138 self.assertIn('/solutions', e.exception.location)
137
138 response = charm_collection(self.getRequest())
139 charm_names = set(charm.name for charm in response['charms'])
140 expected = set((one['name'], two['name']))
141 self.assertEqual(expected, charm_names)
142
143 def test_charm_collection_sub_filter(self):
144 """With the subordinate filter we limit out the non-sub charms."""
145 one_id, one = factory.makeCharm(
146 self.db, subordinate=True, promulgated=True)
147 two_id, two = factory.makeCharm(self.db, promulgated=True)
148
149 request = self.getRequest()
150 request.GET['sub'] = 'true'
151 response = charm_collection(request)
152 charms = response['charms']
153 self.assertEqual(1, len(charms))
154 self.assertEqual(one['name'], charms[0].name)
155
156 def test_charm_collection_charms_with_errors(self):
157 """Charms with errors are not returned by charm_collection()."""
158 one_id, one = factory.makeCharm(self.db, promulgated=True)
159 two_id, two = factory.makeCharm(
160 self.db, charm_error=True, promulgated=True)
161 response = charm_collection(self.getRequest())
162 charms = response['charms']
163 self.assertEqual(1, len(charms))
164 self.assertEqual(one['name'], charms[0].name)
165139
166 def test_personal_collection(self):140 def test_personal_collection(self):
167 # personal_collection() returns only charms owned by the given141 request = self.getRequest()
168 # person.142 request.matchdict = {'owner': 'foo'}
169 one_id, one = factory.makeCharm(self.db, owner='foo')143 with self.assertRaises(HTTPMovedPermanently) as e:
170 two_id, two = factory.makeCharm(self.db, owner='bar')144 personal_collection(request)
171 request = self.getRequest()145 self.assertIn('/q/foo', e.exception.location)
172 request.matchdict = {'owner': 'foo'}
173 response = personal_collection(request)
174 charms = response['charms']
175 self.assertEqual(1, len(charms))
176 self.assertEqual('foo', charms[0].owner)
177
178 def test_personal_collection_sub_filter(self):
179 # If the request parameter 'sub' is set, only subordinate charms
180 # are returned.
181 one_id, one = factory.makeCharm(self.db, owner='foo')
182 two_id, two = factory.makeCharm(self.db, owner='bar')
183 three_id, three = factory.makeCharm(
184 self.db, owner='foo', subordinate=True)
185 request = self.getRequest()
186 request.GET['sub'] = 'true'
187 request.matchdict = {'owner': 'foo'}
188 response = personal_collection(request)
189 charms = response['charms']
190 self.assertEqual(1, len(charms))
191 self.assertEqual(three['name'], charms[0].name)
192 # If the filter is no given in the request, all charms are returned.
193 request = self.getRequest()
194 request.matchdict = {'owner': 'foo'}
195 response = personal_collection(request)
196 charms = response['charms']
197 self.assertEqual(2, len(charms))
198 self.assertEqual('foo', charms[0].owner)
199 self.assertEqual('foo', charms[1].owner)
200
201 def test_personal_collection_charms_with_errors(self):
202 # Charms with errors are not returned by personal_collection().
203 one_id, one = factory.makeCharm(self.db, owner='foo')
204 two_id, two = factory.makeCharm(self.db, owner='bar')
205 three_id, three = factory.makeCharm(
206 self.db, owner='foo', charm_error=True)
207 request = self.getRequest()
208 request.matchdict = {'owner': 'foo'}
209 response = personal_collection(request)
210 charms = response['charms']
211 self.assertEqual(1, len(charms))
212 self.assertEqual(one['name'], charms[0].name)
213146
214 def test_series_collection(self):147 def test_series_collection(self):
215 # series_collection() returns only promulgated charms for a148 request = self.getRequest()
216 # given series.149 request.matchdict = {'series': 'one'}
217 one_id, one = factory.makeCharm(150 with self.assertRaises(HTTPMovedPermanently) as e:
218 self.db, series='one', promulgated=True, name='charm-a')151 series_collection(request)
219 two_id, two = factory.makeCharm(152 self.assertIn('/q/one', e.exception.location)
220 self.db, series='two', promulgated=True)
221 three_id, three = factory.makeCharm(
222 self.db, series='one')
223 four_id, four = factory.makeCharm(
224 self.db, series='one', promulgated=True, owner='foo',
225 name='charm-b')
226 request = self.getRequest()
227 request.matchdict = {'series': 'one'}
228 response = series_collection(request)
229 charms = response['charms']
230 self.assertEqual(
231 ['charm-a', 'charm-b'], [charm.name for charm in charms])
232 self.assertEqual('one', charms[0].series)
233
234 def test_series_collection_sub_filter(self):
235 # If the request parameter 'sub' is set, only subordinate charms
236 # are returned.
237 one_id, one = factory.makeCharm(
238 self.db, series='one', promulgated=True)
239 two_id, two = factory.makeCharm(
240 self.db, series='two', promulgated=True)
241 three_is, three = factory.makeCharm(
242 self.db, series='one', subordinate=True, promulgated=True)
243 request = self.getRequest()
244 request.matchdict = {'series': 'one'}
245 request.GET['sub'] = 'true'
246 response = series_collection(request)
247 charms = response['charms']
248 self.assertEqual(1, len(charms))
249 self.assertEqual(three['name'], charms[0].name)
250
251 def test_series_collection_charms_with_errors(self):
252 # Charms with errors are not returned by series_collection().
253 one_id, one = factory.makeCharm(
254 self.db, series='one', promulgated=True)
255 two_id, two = factory.makeCharm(
256 self.db, series='two', promulgated=True)
257 three_is, three = factory.makeCharm(
258 self.db, series='one', charm_error=True, promulgated=True)
259 request = self.getRequest()
260 request.matchdict = {'series': 'one'}
261 response = series_collection(request)
262 charms = response['charms']
263 self.assertEqual(1, len(charms))
264 self.assertEqual(one['name'], charms[0].name)
265153
266 def test_personal_series_collection(self):154 def test_personal_series_collection(self):
267 # personal_series_collection() returns only charms for a given155 request = self.getRequest()
268 # series and person.156 request.matchdict = {'series': 'one', 'owner': 'me'}
269 one_id, one = factory.makeCharm(self.db, series='one', owner='foo')157 with self.assertRaises(HTTPMovedPermanently) as e:
270 two_id, two = factory.makeCharm(self.db, series='two', owner='foo')158 personal_series_collection(request)
271 three_id, three = factory.makeCharm(self.db, series='one', owner='bar')159 self.assertIn('/q/me/one', e.exception.location)
272 request = self.getRequest()
273 request.matchdict = {
274 'owner': 'foo',
275 'series': 'one',
276 }
277 response = personal_series_collection(request)
278 charms = response['charms']
279 self.assertEqual(1, len(charms))
280 self.assertEqual(one['name'], charms[0].name)
281
282 def test_personal_series_collection_sub_filter(self):
283 # If the parameter 'sub' is set, personal_series_collection()
284 # returns only subordinate charms for the given person ans series.
285 one_id, one = factory.makeCharm(self.db, series='one', owner='foo')
286 two_id, two = factory.makeCharm(self.db, series='two', owner='foo')
287 three_id, three = factory.makeCharm(self.db, series='one', owner='bar')
288 four_id, four = factory.makeCharm(
289 self.db, series='one', owner='foo', subordinate=True)
290 request = self.getRequest()
291 request.matchdict = {
292 'owner': 'foo',
293 'series': 'one',
294 }
295 request.GET['sub'] = 'true'
296 response = personal_series_collection(request)
297 charms = response['charms']
298 self.assertEqual(1, len(charms))
299 self.assertEqual(four['name'], charms[0].name)
300
301 def test_personal_series_collection_charms_with_errors(self):
302 # Charms with errors are not returned by series_collection().
303 one_id, one = factory.makeCharm(self.db, series='one', owner='foo')
304 two_id, two = factory.makeCharm(self.db, series='two', owner='foo')
305 three_id, three = factory.makeCharm(self.db, series='one', owner='bar')
306 four_id, four = factory.makeCharm(
307 self.db, series='one', owner='foo', charm_error=True)
308 request = self.getRequest()
309 request.matchdict = {
310 'owner': 'foo',
311 'series': 'one',
312 }
313 response = personal_series_collection(request)
314 charms = response['charms']
315 self.assertEqual(1, len(charms))
316 self.assertEqual(one['name'], charms[0].name)
317160
318 def test_interface(self):161 def test_interface(self):
319 # interface() returns all charms requiring or providing a given162 # interface() returns all charms requiring or providing a given
@@ -429,12 +272,8 @@
429 'charm': 'sample_charm',272 'charm': 'sample_charm',
430 'series': 'precise',273 'series': 'precise',
431 }274 }
432 response = distro_charm(request)275 with self.assertRaises(HTTPMovedPermanently):
433 expected_keys = set((276 distro_charm(request)
434 'charm', 'is_featured', 'format_change', 'format_proof', 'name',
435 'others', 'project', 'qadata', 'readme', 'readme_format',
436 'icon_path'))
437 self.assertEqual(expected_keys, set(response))
438277
439 def test_distro_charm_json(self):278 def test_distro_charm_json(self):
440 ignore, charm = factory.makeCharm(279 ignore, charm = factory.makeCharm(
@@ -542,9 +381,8 @@
542 two_id, two = factory.makeCharm(self.db, promulgated=True)381 two_id, two = factory.makeCharm(self.db, promulgated=True)
543 del two['summary']382 del two['summary']
544 self.db.charms.save(two)383 self.db.charms.save(two)
545 # Test that the route works.384 response = self.app.get('/charms/precise', status=301)
546 resp = self.app.get('/charms/precise', status=200)385 self.assertIn('/q/precise', response.body)
547 self.assertIn(two['short_url'], resp.body)
548386
549 def test_route_personal_charm_with_revision(self):387 def test_route_personal_charm_with_revision(self):
550 # The route /~owner/series/charm-1 finds charms using owner,388 # The route /~owner/series/charm-1 finds charms using owner,
@@ -552,8 +390,8 @@
552 # does not include the revision.390 # does not include the revision.
553 ignore, charm = factory.makeCharm(391 ignore, charm = factory.makeCharm(
554 self.db, owner='owner', series='series', name='charm', files={})392 self.db, owner='owner', series='series', name='charm', files={})
555 response = self.app.get('/~owner/series/charm-1', status=200)393 response = self.app.get('/~owner/series/charm-1', status=301)
556 self.assertIn('juju deploy cs:~owner/series/charm', response.body)394 self.assertIn('/u/owner/charm/series/1', response.body)
557395
558 def test_route_personal_charm_with_dash_and_revision(self):396 def test_route_personal_charm_with_dash_and_revision(self):
559 # /~owner/series/charm-name-1 route find the charm.397 # /~owner/series/charm-name-1 route find the charm.
@@ -562,16 +400,17 @@
562 ignore, charm = factory.makeCharm(400 ignore, charm = factory.makeCharm(
563 self.db, owner='owner', series='series', name='charm-name',401 self.db, owner='owner', series='series', name='charm-name',
564 files={})402 files={})
565 response = self.app.get('/~owner/series/charm-name-1', status=200)403 response = self.app.get('/~owner/series/charm-name-1', status=301)
566 self.assertIn('juju deploy cs:~owner/series/charm-name', response.body)404 self.assertIn('/u/owner/charm-name/series/1', response.body)
567405
568 def test_route_personal_charm_with_head_revision(self):406 def test_route_personal_charm_with_head_revision(self):
569 # the /~owner/series/charm-name-HEAD route finds the charm.407 # the /~owner/series/charm-name-HEAD route finds the charm.
570 # The juju-gui apache rewrite versionless charm urls to help the GUI.408 # The juju-gui apache rewrite versionless charm urls to help the GUI.
571 ignore, charm = factory.makeCharm(409 ignore, charm = factory.makeCharm(
572 self.db, owner='owner', series='series', name='charm', files={})410 self.db, owner='owner', series='series', name='charm', files={})
573 response = self.app.get('/~owner/series/charm-HEAD', status=200)411 response = self.app.get('/~owner/series/charm-HEAD', status=301)
574 self.assertIn('juju deploy cs:~owner/series/charm', response.body)412 self.assertIn('/u/owner/charm/series', response.body)
413 self.assertNotIn('HEAD', response.body)
575414
576 def test_route_personal_charm_without_revision(self):415 def test_route_personal_charm_without_revision(self):
577 # The route /~owner/series/charm route finds the charm using owner416 # The route /~owner/series/charm route finds the charm using owner
@@ -579,8 +418,8 @@
579 # matches the route.418 # matches the route.
580 ignore, charm = factory.makeCharm(419 ignore, charm = factory.makeCharm(
581 self.db, owner='owner', series='series', name='charm', files={})420 self.db, owner='owner', series='series', name='charm', files={})
582 response = self.app.get('/~owner/series/charm', status=200)421 response = self.app.get('/~owner/series/charm', status=301)
583 self.assertIn('juju deploy cs:~owner/series/charm', response.body)422 self.assertIn('/u/owner/charm/series', response.body)
584423
585 def test_route_personal_charm_json_with_revision(self):424 def test_route_personal_charm_json_with_revision(self):
586 # The route /~owner/series/charm-1/json finds charms using owner,425 # The route /~owner/series/charm-1/json finds charms using owner,
@@ -604,10 +443,10 @@
604 # The route /series/charm-1 route finds promulgated charms using443 # The route /series/charm-1 route finds promulgated charms using
605 # The series, charm name and version. The deploy instruction does not444 # The series, charm name and version. The deploy instruction does not
606 # include the version.445 # include the version.
607 ignore, charm = factory.makeCharm(446 factory.makeCharm(
608 self.db, promulgated=True, series='series', name='charm', files={})447 self.db, promulgated=True, series='series', name='charm', files={})
609 response = self.app.get('/series/charm-1', status=200)448 response = self.app.get('/series/charm-1', status=301)
610 self.assertIn('juju deploy cs:series/charm', response.body)449 self.assertIn('/charm/series/1', response.body)
611450
612 def test_route_promulgated_charm_with_head_revision(self):451 def test_route_promulgated_charm_with_head_revision(self):
613 # The route /series/charm-HEAD finds promulgated charms using452 # The route /series/charm-HEAD finds promulgated charms using
@@ -615,22 +454,22 @@
615 # in the deploy instruction.454 # in the deploy instruction.
616 ignore, charm = factory.makeCharm(455 ignore, charm = factory.makeCharm(
617 self.db, promulgated=True, series='series', name='charm', files={})456 self.db, promulgated=True, series='series', name='charm', files={})
618 response = self.app.get('/series/charm-HEAD', status=200)457 response = self.app.get('/series/charm-HEAD', status=301)
619 self.assertIn('juju deploy cs:series/charm', response.body)458 self.assertNotIn('HEAD', response.body)
459 self.assertIn('/charm/series', response.body)
620460
621 def test_route_promulgated_charm_without_revision(self):461 def test_route_promulgated_charm_without_revision(self):
622 # The route /series/charm finds the promulgated charms using just462 # The route /series/charm finds the promulgated charms using just
623 # the series and charm name. The deploy instruction matches the route.463 # the series and charm name. The deploy instruction matches the route.
624 ignore, charm = factory.makeCharm(464 factory.makeCharm(
625 self.db, promulgated=True, series='series', name='charm', files={})465 self.db, promulgated=True, series='series', name='charm', files={})
626 response = self.app.get('/series/charm', status=200)466 self.app.get('/series/charm', status=301)
627 self.assertIn('juju deploy cs:series/charm', response.body)
628467
629 def test_charm_short_url_route(self):468 def test_charm_short_url_route(self):
630 ignore, charm_data = factory.makeCharm(469 ignore, charm_data = factory.makeCharm(
631 self.db, promulgated=True, series='series', name='charm', files={})470 self.db, promulgated=True, series='series', name='charm', files={})
632 charm = Charm(charm_data)471 charm = Charm(charm_data)
633 self.app.get(charm.short_url, status=200)472 self.app.get(charm.short_url, status=301)
634473
635474
636class TestQAForm(ViewTestBase):475class TestQAForm(ViewTestBase):
637476
=== modified file 'default.ini'
--- default.ini 2014-01-14 14:37:56 +0000
+++ default.ini 2015-02-09 18:46:28 +0000
@@ -66,7 +66,10 @@
66run_test_builds = false66run_test_builds = false
67charm_test_url = 67charm_test_url =
68charm_test_token = 68charm_test_token =
69charm_test_job = 69charm_test_job =
70
71# New web site for redirection
72redirect_jujucharms = https://jujucharms.com
7073
71[server:main]74[server:main]
72use = egg:Paste#http75use = egg:Paste#http

Subscribers

People subscribed via source and target branches

to all changes: