Merge lp:~mhall119/developer-ubuntu-com/redirects-app into lp:developer-ubuntu-com

Proposed by Michael Hall
Status: Merged
Merged at revision: 227
Proposed branch: lp:~mhall119/developer-ubuntu-com/redirects-app
Merge into: lp:developer-ubuntu-com
Diff against target: 263 lines (+175/-23)
9 files modified
developer_portal/settings.py (+2/-0)
redirects/admin.py (+9/-0)
redirects/middleware.py (+35/-0)
redirects/migrations/0001_initial.py (+23/-0)
redirects/migrations/0002_auto_20160720_1339.py (+29/-0)
redirects/migrations/0003_auto_20160720_1355.py (+24/-0)
redirects/models.py (+19/-0)
redirects/tests.py (+34/-0)
templates/404.html (+0/-23)
To merge this branch: bzr merge lp:~mhall119/developer-ubuntu-com/redirects-app
Reviewer Review Type Date Requested Status
Ubuntu App Developer site developers Pending
Review via email: mp+300538@code.launchpad.net

Description of the change

Adds a new 'redirects' django app that will attempt to perform a 301 redirect in response to a 404 reponse, based on a number of redirects defined in the database via the admin interface.

To post a comment you must log in.
Revision history for this message
Daniel Holbach (dholbach) wrote :

Nice work! Doing a local test now.

227. By Michael Hall

Add migration script to get redirects models up to date

228. By Michael Hall

Add example paths to help_text in redirects

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'developer_portal/settings.py'
2--- developer_portal/settings.py 2016-05-10 16:47:50 +0000
3+++ developer_portal/settings.py 2016-07-20 13:55:40 +0000
4@@ -80,6 +80,7 @@
5 'api_docs',
6
7 'md_importer',
8+ 'redirects',
9 ]
10
11 MIDDLEWARE_CLASSES = (
12@@ -92,6 +93,7 @@
13 #
14 #'django.contrib.sessions.middleware.SessionMiddleware',
15 'developer_portal.middleware.CacheFriendlySessionMiddleware',
16+ 'redirects.middleware.Redirect404Middleware',
17
18 'django.middleware.csrf.CsrfViewMiddleware',
19 'django.contrib.auth.middleware.AuthenticationMiddleware',
20
21=== added directory 'redirects'
22=== added file 'redirects/__init__.py'
23=== added file 'redirects/admin.py'
24--- redirects/admin.py 1970-01-01 00:00:00 +0000
25+++ redirects/admin.py 2016-07-20 13:55:40 +0000
26@@ -0,0 +1,9 @@
27+from django.contrib import admin
28+from .models import PathMatchRedirect
29+
30+# Register your models here.
31+
32+@admin.register(PathMatchRedirect)
33+class PathMatchRedirectAdmin(admin.ModelAdmin):
34+ list_display = ('match', 'replace', 'preserve_extra')
35+ exclude = ('precedence',)
36
37=== added file 'redirects/middleware.py'
38--- redirects/middleware.py 1970-01-01 00:00:00 +0000
39+++ redirects/middleware.py 2016-07-20 13:55:40 +0000
40@@ -0,0 +1,35 @@
41+# -*- coding: utf-8 -*-
42+"""
43+Middleware for handling redirects
44+"""
45+from django.contrib.sessions.middleware import SessionMiddleware
46+from django.conf import settings
47+from .models import PathMatchRedirect
48+
49+class Redirect404Middleware(SessionMiddleware):
50+ """
51+ Only attempt redirects in response to a request returning a 404
52+ response code.
53+ """
54+ def process_response(self, request, response):
55+ response = super(Redirect404Middleware, self).process_response(request, response)
56+
57+ # Don't do anything unless it's 404 (not found)
58+ if response.status_code != 404:
59+ return response
60+
61+ try_redirects = PathMatchRedirect.objects.all().order_by("-precedence")
62+ for redirect in try_redirects:
63+ if request.path.startswith(redirect.match):
64+ response.status_code = 301 # permanent redirect
65+ if redirect.preserve_extra:
66+ new_path = redirect.replace + request.path[len(redirect.match):]
67+ else:
68+ new_path = redirect.replace
69+
70+ response['Location'] = new_path
71+ break;
72+
73+ return response
74+
75+
76
77=== added directory 'redirects/migrations'
78=== added file 'redirects/migrations/0001_initial.py'
79--- redirects/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
80+++ redirects/migrations/0001_initial.py 2016-07-20 13:55:40 +0000
81@@ -0,0 +1,23 @@
82+# -*- coding: utf-8 -*-
83+from __future__ import unicode_literals
84+
85+from django.db import migrations, models
86+
87+
88+class Migration(migrations.Migration):
89+
90+ dependencies = [
91+ ]
92+
93+ operations = [
94+ migrations.CreateModel(
95+ name='PathMatchRedirect',
96+ fields=[
97+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
98+ ('match', models.CharField(max_length=256)),
99+ ('replace', models.CharField(max_length=256)),
100+ ('preserve_extra', models.BooleanField(default=True)),
101+ ('precedence', models.IntegerField(help_text=b'Auto-calculated, leave blank', blank=True)),
102+ ],
103+ ),
104+ ]
105
106=== added file 'redirects/migrations/0002_auto_20160720_1339.py'
107--- redirects/migrations/0002_auto_20160720_1339.py 1970-01-01 00:00:00 +0000
108+++ redirects/migrations/0002_auto_20160720_1339.py 2016-07-20 13:55:40 +0000
109@@ -0,0 +1,29 @@
110+# -*- coding: utf-8 -*-
111+from __future__ import unicode_literals
112+
113+from django.db import migrations, models
114+
115+
116+class Migration(migrations.Migration):
117+
118+ dependencies = [
119+ ('redirects', '0001_initial'),
120+ ]
121+
122+ operations = [
123+ migrations.AlterField(
124+ model_name='pathmatchredirect',
125+ name='match',
126+ field=models.CharField(help_text=b'Path prefix to match for this redirect', max_length=256),
127+ ),
128+ migrations.AlterField(
129+ model_name='pathmatchredirect',
130+ name='preserve_extra',
131+ field=models.BooleanField(default=True, help_text=b'Should any part of the path after what was matched be appended to the replacement path?'),
132+ ),
133+ migrations.AlterField(
134+ model_name='pathmatchredirect',
135+ name='replace',
136+ field=models.CharField(help_text=b'Replacement path', max_length=256),
137+ ),
138+ ]
139
140=== added file 'redirects/migrations/0003_auto_20160720_1355.py'
141--- redirects/migrations/0003_auto_20160720_1355.py 1970-01-01 00:00:00 +0000
142+++ redirects/migrations/0003_auto_20160720_1355.py 2016-07-20 13:55:40 +0000
143@@ -0,0 +1,24 @@
144+# -*- coding: utf-8 -*-
145+from __future__ import unicode_literals
146+
147+from django.db import migrations, models
148+
149+
150+class Migration(migrations.Migration):
151+
152+ dependencies = [
153+ ('redirects', '0002_auto_20160720_1339'),
154+ ]
155+
156+ operations = [
157+ migrations.AlterField(
158+ model_name='pathmatchredirect',
159+ name='match',
160+ field=models.CharField(help_text=b'Path prefix to match for this redirect. Matches against the start of the requested path. Example: /my/path/', max_length=256),
161+ ),
162+ migrations.AlterField(
163+ model_name='pathmatchredirect',
164+ name='replace',
165+ field=models.CharField(help_text=b'Replacement path. Example: /replacement/path/', max_length=256),
166+ ),
167+ ]
168
169=== added file 'redirects/migrations/__init__.py'
170=== added file 'redirects/models.py'
171--- redirects/models.py 1970-01-01 00:00:00 +0000
172+++ redirects/models.py 2016-07-20 13:55:40 +0000
173@@ -0,0 +1,19 @@
174+from django.db import models
175+
176+# Create your models here.
177+
178+class PathMatchRedirect(models.Model):
179+
180+ match = models.CharField(max_length=256, help_text="Path prefix to match for this redirect. Matches against the start of the requested path. Example: /my/path/")
181+ replace = models.CharField(max_length=256, help_text="Replacement path. Example: /replacement/path/")
182+ preserve_extra = models.BooleanField(default=True, help_text="Should any part of the path after what was matched be appended to the replacement path?")
183+ precedence = models.IntegerField(help_text="Auto-calculated, leave blank", blank=True)
184+
185+ def save(self, *args, **kwargs):
186+ # We'll want to check more specific URL paths first, which we
187+ # can do if we reverse sort by length
188+ self.precedence = len(self.match)
189+ super(PathMatchRedirect, self).save()
190+
191+ def __unicode__(self):
192+ return self.match
193
194=== added file 'redirects/tests.py'
195--- redirects/tests.py 1970-01-01 00:00:00 +0000
196+++ redirects/tests.py 2016-07-20 13:55:40 +0000
197@@ -0,0 +1,34 @@
198+from django.test import TestCase, Client
199+from .models import PathMatchRedirect
200+
201+# Create your tests here.
202+class RedirectsCase(TestCase):
203+
204+
205+ def test_no_redirect(self):
206+ c = Client()
207+ response = c.get('/en/test/') # Needs the /en/ otherwise it tried to redirect to it
208+ self.assertEquals(404, response.status_code)
209+
210+ def test_basic_redirect(self):
211+ PathMatchRedirect.objects.create(match="/test/", replace="/replaced/", preserve_extra=False)
212+ c = Client()
213+ response = c.get('/test/')
214+ self.assertEquals(301, response.status_code)
215+ self.assertEquals('http://testserver/replaced/', response['Location'])
216+
217+ def test_redirect_precedence(self):
218+ PathMatchRedirect.objects.create(match="/test/", replace="/replaced/", preserve_extra=False)
219+ PathMatchRedirect.objects.create(match="/test/specific/", replace="/specific/", preserve_extra=False)
220+ PathMatchRedirect.objects.create(match="/test/spec", replace="/premature_match/", preserve_extra=False)
221+ c = Client()
222+ response = c.get('/test/specific/')
223+ self.assertEquals(301, response.status_code)
224+ self.assertEquals('http://testserver/specific/', response['Location'])
225+
226+ def test_redirect_preserve_extra(self):
227+ PathMatchRedirect.objects.create(match="/test/", replace="/replaced/", preserve_extra=True)
228+ c = Client()
229+ response = c.get('/test/extra/path')
230+ self.assertEquals(301, response.status_code)
231+ self.assertEquals('http://testserver/replaced/extra/path', response['Location'])
232
233=== modified file 'templates/404.html'
234--- templates/404.html 2016-05-10 13:05:43 +0000
235+++ templates/404.html 2016-07-20 13:55:40 +0000
236@@ -11,27 +11,4 @@
237
238 <div class="row">{% trans "This page does not exist or has been moved. If you feel that this is an error, please <a href='https://bugs.launchpad.net/developer-ubuntu-com/+filebug'>file a bug</a>." %}</div>
239
240-<script>
241-
242- var path = "{{ request.path }}";
243-
244- var move = [{'from':'/apps/', 'to':'/phone/apps/'},
245- {'from':'/scopes/', 'to':'/phone/scopes/'},
246- {'from':'/web/', 'to':'/phone/web/'},
247- {'from':'/start/ubuntu-for-devices/', 'to':'/phone/devices/'},
248- {'from':'/start/platform/', 'to':'/phone/platform/'},
249- {'from':'/start/quality/', 'to':'/phone/platform/quality/'},
250- {'from':'/start/ubuntu-sdk/', 'to':'/phone/platform/sdk/'},
251- {'from':'/start/', 'to':'/phone/'}]
252-
253- for (var i = 0; i < move.length; i++) {
254- var m = "/" + "{{ lang }}" + move[i]['from']
255- var t = "/" + "{{ lang }}" + move[i]['to']
256- if (path.lastIndexOf(m, 0) === 0) {
257- var new_path = path.replace(m, t);
258- window.location = new_path
259- break;
260- }
261- }
262-</script>
263 {% endblock %}

Subscribers

People subscribed via source and target branches