Merge lp:~canonical-isd-hackers/canonical-identity-provider/preferred-language into lp:canonical-identity-provider/release

Proposed by Łukasz Czyżykowski
Status: Merged
Merge reported by: Łukasz Czyżykowski
Merged at revision: not available
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/preferred-language
Merge into: lp:canonical-identity-provider/release
Diff against target: 421 lines (+222/-13)
16 files modified
doctests/stories/openid/copyright.txt (+1/-0)
identityprovider/context_processors.py (+16/-0)
identityprovider/media/ubuntu/styles.css (+37/-0)
identityprovider/middleware/useraccount.py (+4/-0)
identityprovider/models/account.py (+1/-0)
identityprovider/templates/registration/new_account.html (+1/-1)
identityprovider/templates/select_language.html (+32/-0)
identityprovider/templates/ubuntu/base.html (+8/-0)
identityprovider/templates/ubuntu/registration/login.html (+1/-0)
identityprovider/tests/test_language_selection.py (+15/-0)
identityprovider/tests/test_views_i18n.py (+25/-0)
identityprovider/tests/test_views_ui.py (+23/-8)
identityprovider/tests/utils.py (+8/-0)
identityprovider/urls.py (+4/-3)
identityprovider/views/i18n.py (+37/-0)
identityprovider/views/ui.py (+9/-1)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/preferred-language
Reviewer Review Type Date Requested Status
Danny Tamez (community) Approve
Review via email: mp+25240@code.launchpad.net

This proposal supersedes a proposal from 2010-05-13.

Description of the change

Implemented language chooser

To post a comment you must log in.
Revision history for this message
Danny Tamez (zematynnad) wrote :

Thanks for the doctest fix - all that's left is:
remove line 41 of identityprovider/tests/test_views_ui.py,
file i18n needs a license header

review: Needs Fixing
Revision history for this message
Danny Tamez (zematynnad) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'doctests/stories/openid/copyright.txt'
2--- doctests/stories/openid/copyright.txt 2010-04-21 15:29:24 +0000
3+++ doctests/stories/openid/copyright.txt 2010-05-13 16:37:29 +0000
4@@ -5,6 +5,7 @@
5
6 >>> browser.open('http://openid.launchpad.dev/')
7 >>> print extract_text(find_tag_by_id(browser.contents, 'footer'))
8+ Choose your language
9 © 2009-2010 Canonical Ltd.
10 ...
11
12
13=== modified file 'identityprovider/context_processors.py'
14--- identityprovider/context_processors.py 2010-05-13 14:53:17 +0000
15+++ identityprovider/context_processors.py 2010-05-13 16:37:29 +0000
16@@ -1,3 +1,4 @@
17+# -*- coding: utf-8 -*-
18 # Copyright 2010 Canonical Ltd. This software is licensed under the
19 # GNU Affero General Public License version 3 (see the file LICENSE).
20
21@@ -13,6 +14,7 @@
22 from django.conf import settings
23
24 from identityprovider.branding import current_brand
25+from django.utils.translation import ugettext as _
26
27
28 def readonly(request):
29@@ -24,3 +26,17 @@
30 'template_dir': current_brand.template_dir,
31 'brand': current_brand,
32 }
33+
34+language_names = {'es': u'Español',
35+ 'es-ar': u'Español de Argentina',
36+ 'en': u'English',
37+ 'hu': u'Magyar',
38+ 'zh-cn': u'简体中文',
39+ 'pl': u'Polski',
40+ 'de': u'Deutsch',
41+ }
42+
43+supported = [(x, language_names[x]) for x in settings.SUPPORTED_LANGUAGES]
44+
45+def i18n(request):
46+ return {'supported_languages': supported}
47
48=== renamed directory 'identityprovider/locale/es_AR' => 'identityprovider/locale/es'
49=== added directory 'identityprovider/media/flags'
50=== added file 'identityprovider/media/flags/de.png'
51Binary files identityprovider/media/flags/de.png 1970-01-01 00:00:00 +0000 and identityprovider/media/flags/de.png 2010-05-13 16:37:29 +0000 differ
52=== added file 'identityprovider/media/flags/en.png'
53Binary files identityprovider/media/flags/en.png 1970-01-01 00:00:00 +0000 and identityprovider/media/flags/en.png 2010-05-13 16:37:29 +0000 differ
54=== added file 'identityprovider/media/flags/es-ar.png'
55Binary files identityprovider/media/flags/es-ar.png 1970-01-01 00:00:00 +0000 and identityprovider/media/flags/es-ar.png 2010-05-13 16:37:29 +0000 differ
56=== added file 'identityprovider/media/flags/es.png'
57Binary files identityprovider/media/flags/es.png 1970-01-01 00:00:00 +0000 and identityprovider/media/flags/es.png 2010-05-13 16:37:29 +0000 differ
58=== added file 'identityprovider/media/flags/hu.png'
59Binary files identityprovider/media/flags/hu.png 1970-01-01 00:00:00 +0000 and identityprovider/media/flags/hu.png 2010-05-13 16:37:29 +0000 differ
60=== added file 'identityprovider/media/flags/pl.png'
61Binary files identityprovider/media/flags/pl.png 1970-01-01 00:00:00 +0000 and identityprovider/media/flags/pl.png 2010-05-13 16:37:29 +0000 differ
62=== added file 'identityprovider/media/flags/zh-cn.png'
63Binary files identityprovider/media/flags/zh-cn.png 1970-01-01 00:00:00 +0000 and identityprovider/media/flags/zh-cn.png 2010-05-13 16:37:29 +0000 differ
64=== modified file 'identityprovider/media/ubuntu/styles.css'
65--- identityprovider/media/ubuntu/styles.css 2010-04-21 15:29:24 +0000
66+++ identityprovider/media/ubuntu/styles.css 2010-05-13 16:37:29 +0000
67@@ -408,3 +408,40 @@
68 .findoutmore {
69 font-size: 120%;
70 }
71+
72+/* Internationalization stuff */
73+#language_footer {
74+ float: right;
75+ padding: 10px 0 0;
76+ height: 20px;
77+ vertical-align: center;
78+}
79+
80+#language_footer img,
81+#language_footer a {
82+ float: left;
83+}
84+
85+#language_footer a {
86+ margin-left: 4px;
87+}
88+
89+#language_select div {
90+ margin: 4px;
91+}
92+
93+#language_select input {
94+ border: 0;
95+ border-bottom: 1px solid transparent;
96+ padding: 0 0 0 20px;
97+ color: #eb6f31;
98+}
99+
100+#language_select input:hover {
101+ cursor: pointer;
102+ text-decoration: underline;
103+ border-bottom: 1px solid #eb6f31;
104+}
105+
106+ text-decoration: none;
107+}
108
109=== modified file 'identityprovider/middleware/useraccount.py'
110--- identityprovider/middleware/useraccount.py 2010-04-21 15:29:24 +0000
111+++ identityprovider/middleware/useraccount.py 2010-05-13 16:37:29 +0000
112@@ -35,3 +35,7 @@
113 request.user = user
114 except User.DoesNotExist:
115 logout(request)
116+ if isinstance(request.user, Account):
117+ lang = request.user.preferredlanguage
118+ if lang is not None:
119+ request.session['django_language'] = lang
120
121=== modified file 'identityprovider/models/account.py'
122--- identityprovider/models/account.py 2010-05-12 16:37:14 +0000
123+++ identityprovider/models/account.py 2010-05-13 16:37:29 +0000
124@@ -70,6 +70,7 @@
125 displayname = models.TextField()
126 openid_identifier = models.TextField(default=generate_openid_identifier)
127 status_comment = models.TextField(blank=True, null=True)
128+ preferredlanguage = models.TextField(blank=True, null=True)
129 old_openid_identifier = models.TextField(blank=True, null=True)
130
131 objects = AccountManager()
132
133=== modified file 'identityprovider/templates/registration/new_account.html'
134--- identityprovider/templates/registration/new_account.html 2010-05-13 09:55:17 +0000
135+++ identityprovider/templates/registration/new_account.html 2010-05-13 16:37:29 +0000
136@@ -9,7 +9,7 @@
137 {% block "content" %}
138 <div id="auth">
139 <h2 class="main">
140- {{ brand.create_account }}
141+ {{ brand.create_account }}
142 </h2>
143
144 <p>{% blocktrans %}Enter your email address, and we will send you instructions on how to create your account.{% endblocktrans %}</p>
145
146=== added file 'identityprovider/templates/select_language.html'
147--- identityprovider/templates/select_language.html 1970-01-01 00:00:00 +0000
148+++ identityprovider/templates/select_language.html 2010-05-13 16:37:29 +0000
149@@ -0,0 +1,32 @@
150+<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
151+GNU Affero General Public License version 3 (see the file LICENSE). -->
152+
153+{% extends "base.html" %}
154+{% load i18n %}
155+
156+{% block "title" %}
157+ {% trans "Choose your language" %}
158+{% endblock %}
159+
160+{% block "content" %}
161+<div id="box">
162+ <h1 class="main">{% trans "Choose your language" %}</h1>
163+ <div id="language_select">
164+ {% for lang in supported_languages %}
165+ <form action="" method="POST">
166+ <input type="hidden" name="next" value="{{next}}">
167+ <input type="hidden" name="language" value="{{lang.0}}" />
168+ <div><input type="submit" name="submit" value="{{lang.1|capfirst}}"
169+ style="background: transparent url('/assets/identityprovider/flags/{{lang.0}}.png') center left no-repeat"/>
170+ </div>
171+ </form>
172+ {% endfor %}
173+ </div>
174+ <h2 class="main">{% trans "Can't find your language?" %}</h2>
175+ <p>{% blocktrans %}We welcome volunteers to help us translate this site to new languages. If you are able to help, please visit our <a href="https://translations.launchpad.net/canonical-identity-provider">translations site</a> to get started.{% endblocktrans %}
176+ </p>
177+</div>
178+{% endblock %}
179+
180+{% comment %} Removing language link from footer on this page {% endcomment %}
181+{% block "language_footer" %}{% endblock %}
182
183=== modified file 'identityprovider/templates/ubuntu/base.html'
184--- identityprovider/templates/ubuntu/base.html 2010-05-13 14:45:39 +0000
185+++ identityprovider/templates/ubuntu/base.html 2010-05-13 16:37:29 +0000
186@@ -45,6 +45,14 @@
187 </div>
188 </div>
189 <div id="footer">
190+ {% block "language_footer" %}
191+ <div id="language_footer">
192+ <img src="/assets/identityprovider/flags/{{ LANGUAGE_CODE }}.png">
193+ <a href="/set_language?next={{request.path_info|urlencode}}">
194+ {% trans "Choose your language" %}
195+ </a>
196+ </div>
197+ {% endblock %}
198 <p>{% blocktrans %}&copy; 2009-2010 Canonical Ltd.<br />
199 Ubuntu and Canonical are registered trademarks of Canonical Group Ltd.<br />
200 Please review our <a href="http://ubuntu.com/legal">Terms of Service</a> as well as our <a href="http://ubuntu.com/legal#privacy">Privacy Policy</a>.{% endblocktrans %}</p>
201
202=== modified file 'identityprovider/templates/ubuntu/registration/login.html'
203--- identityprovider/templates/ubuntu/registration/login.html 2010-05-13 14:45:39 +0000
204+++ identityprovider/templates/ubuntu/registration/login.html 2010-05-13 16:37:29 +0000
205@@ -22,6 +22,7 @@
206 {% if token and rpconfig %}
207 <p>
208 {% blocktrans with rpconfig.displayname as rpconfigname %}You are here because {{ rpconfigname }} uses the Ubuntu Single Sign On service.{% endblocktrans %}
209+
210 </p>
211 {% endif %}
212 <p>
213
214=== added file 'identityprovider/tests/test_language_selection.py'
215--- identityprovider/tests/test_language_selection.py 1970-01-01 00:00:00 +0000
216+++ identityprovider/tests/test_language_selection.py 2010-05-13 16:37:29 +0000
217@@ -0,0 +1,15 @@
218+from identityprovider.tests.utils import BasicAccountTestCase
219+
220+
221+class SelectLanguagePageTestCase(BasicAccountTestCase):
222+
223+ def setUp(self):
224+ self.client.login(username='mark@example.com', password='test')
225+
226+ def test_link_for_language_choosing_is_displayed_on_main_page(self):
227+ r = self.client.get('/')
228+ self.assertContains(r, 'id="language_footer"')
229+
230+ def test_link_for_language_choosing_is_not_diplayed_on_language_page(self):
231+ r = self.client.get('/set_language')
232+ self.assertNotContains(r, 'id="language_footer"')
233
234=== added file 'identityprovider/tests/test_views_i18n.py'
235--- identityprovider/tests/test_views_i18n.py 1970-01-01 00:00:00 +0000
236+++ identityprovider/tests/test_views_i18n.py 2010-05-13 16:37:29 +0000
237@@ -0,0 +1,25 @@
238+from identityprovider.tests.utils import BasicAccountTestCase
239+
240+
241+class SetLanguageTestCase(BasicAccountTestCase):
242+
243+ def setUp(self):
244+ self.disable_csrf()
245+
246+ def tearDown(self):
247+ self.reset_csrf()
248+
249+ def test_when_supplying_non_local_next_url_redirects_to_main(self):
250+ r = self.client.post('/set_language',
251+ {'language': 'es', 'next': 'http://example.com'})
252+ self.assertRedirects(r, '/')
253+
254+ def test_next_must_be_resolved_to_existing_view(self):
255+ r = self.client.post('/set_language',
256+ {'language': 'es', 'next': '/not-existing'})
257+ self.assertRedirects(r, '/')
258+
259+ def test_next_works_for_to_be_resolved_for_existing_view(self):
260+ r = self.client.post('/set_language',
261+ {'language': 'es', 'next': '/+login'})
262+ self.assertRedirects(r, '/+login')
263
264=== modified file 'identityprovider/tests/test_views_ui.py'
265--- identityprovider/tests/test_views_ui.py 2010-05-12 16:24:02 +0000
266+++ identityprovider/tests/test_views_ui.py 2010-05-13 16:37:29 +0000
267@@ -38,14 +38,6 @@
268 def tearDown(self):
269 self.reset_csrf()
270
271- def reset_csrf(self):
272- settings.MIDDLEWARE_CLASSES = self._MIDDLEWARE_CLASSES
273-
274- def disable_csrf(self):
275- self._MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES
276- settings.MIDDLEWARE_CLASSES = (x for x in self._MIDDLEWARE_CLASSES
277- if not 'csrf' in x.lower())
278-
279 def authenticate(self):
280 self.client.login(username='mark@example.com', password='test')
281
282@@ -592,3 +584,26 @@
283 self.assertTemplateUsed(r, 'registration/new_account.html')
284 msg = 'It appears that our captcha service was unable to load'
285 self.assertContains(r, msg)
286+
287+
288+class LanguagesTestCase(BasicAccountTestCase):
289+
290+ def test_preffered_lang_is_preserved_after_logout(self):
291+ account = Account.objects.get_by_email("mark@example.com")
292+ account.preferredlanguage = 'es'
293+ account.save()
294+
295+ self.client.login(username='mark@example.com', password='test')
296+ r = self.client.get('/')
297+ self.assertContains(r, 'es.png')
298+
299+ self.client.get('/+logout')
300+ r = self.client.get('/')
301+ self.assertContains(r, 'es.png')
302+
303+ def test_language_selected_before_login_is_preserved(self):
304+ self.assertNotContains(self.client.get('/'), 'es.png')
305+ self.client.post('/set_language', {'language': 'es'})
306+ self.assertContains(self.client.get('/'), 'es.png')
307+ self.client.login(username='mark@example.com', password='test')
308+ self.assertContains(self.client.get('/'), 'es.png')
309
310=== modified file 'identityprovider/tests/utils.py'
311--- identityprovider/tests/utils.py 2010-05-07 21:21:33 +0000
312+++ identityprovider/tests/utils.py 2010-05-13 16:37:29 +0000
313@@ -56,6 +56,14 @@
314 fixtures = ['lp_account', 'lp_accountpassword', 'lp_emailaddress_noperson']
315 pgsql_functions = ['generate_openid_identifier']
316
317+ def reset_csrf(self):
318+ settings.MIDDLEWARE_CLASSES = self._MIDDLEWARE_CLASSES
319+
320+ def disable_csrf(self):
321+ self._MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES
322+ settings.MIDDLEWARE_CLASSES = (x for x in self._MIDDLEWARE_CLASSES
323+ if not 'csrf' in x.lower())
324+
325
326 class LPAccountTestCase(BasicAccountTestCase):
327 def setUp(self):
328
329=== modified file 'identityprovider/urls.py'
330--- identityprovider/urls.py 2010-05-11 00:38:22 +0000
331+++ identityprovider/urls.py 2010-05-13 16:37:29 +0000
332@@ -51,6 +51,10 @@
333 (r'^%(optional_token)s\+remove-email$' % repls, 'delete_email'),
334 )
335
336+urlpatterns += patterns('identityprovider.views.i18n',
337+ (r'^set_language$', 'set_language'),
338+)
339+
340 urlpatterns += patterns('identityprovider.views.readonly',
341 (r'^readonly$', 'readonly_admin'),
342 (r'^readonly/((?P<appserver>[A-Za-z0-9\-_.:]+)/)?(?P<action>enable|disable|set|clear)(/(?P<conn>[A-Za-z0-9\-_.]+))?',
343@@ -77,9 +81,6 @@
344 {'document_root': settings.SSO_MEDIA_ROOT +
345 current_brand.template_dir}),
346 (r'^i18n/', include('django.conf.urls.i18n')),
347- (r'^set_language/$',
348- 'django.views.generic.simple.direct_to_template',
349- {'template': 'set_language.html'}),
350 )
351
352 if getattr(settings, 'TESTING', False):
353
354=== added file 'identityprovider/views/i18n.py'
355--- identityprovider/views/i18n.py 1970-01-01 00:00:00 +0000
356+++ identityprovider/views/i18n.py 2010-05-13 16:37:29 +0000
357@@ -0,0 +1,37 @@
358+# Copyright 2010 Canonical Ltd. This software is licensed under the
359+# GNU Affero General Public License version 3 (see the file LICENSE).
360+
361+from urllib import quote
362+
363+from django.conf import settings
364+from django.http import Http404
365+from django.shortcuts import render_to_response
366+from django.template import RequestContext
367+from django.views.i18n import set_language as django_set_language
368+from django.core.urlresolvers import resolve, Resolver404
369+
370+
371+def set_language(request):
372+ if request.method == 'GET':
373+ next = request.GET.get('next', '/')
374+ context = RequestContext(request, {'next': quote(next)})
375+ return render_to_response('select_language.html', context)
376+ elif request.method != 'POST':
377+ raise Http404()
378+ lang = request.POST.get('language')
379+ if lang not in settings.SUPPORTED_LANGUAGES:
380+ raise Http404('Unsupported language')
381+ if request.user.is_authenticated():
382+ account = request.user
383+ account.preferredlanguage = lang
384+ account.save()
385+ response = django_set_language(request)
386+ redirection = response['Location']
387+ if not redirection.startswith('/'):
388+ response['Location'] = '/'
389+ else:
390+ try:
391+ resolve(redirection)
392+ except Resolver404:
393+ response['Location'] = '/'
394+ return response
395
396=== modified file 'identityprovider/views/ui.py'
397--- identityprovider/views/ui.py 2010-05-13 14:53:17 +0000
398+++ identityprovider/views/ui.py 2010-05-13 16:37:29 +0000
399@@ -91,13 +91,21 @@
400
401
402 def logout(request, token=None):
403+ try:
404+ lang = request.user.preferredlanguage
405+ except AttributeError:
406+ lang = None
407+
408 # We don't want to lose session[token] when we log the user out
409 raw_orequest = request.session.get(token, None)
410 auth.logout(request)
411 if token is not None and raw_orequest is not None:
412 request.session[token] = raw_orequest
413 request.session['message'] = _("You have successfully logged out")
414- return HttpResponseRedirect('+login')
415+ response = HttpResponseRedirect('+login')
416+ if lang:
417+ response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)
418+ return response
419
420
421 def claim_token(request, authtoken):