Merge lp:~canonical-isd-hackers/canonical-identity-provider/preferred-language into lp:canonical-identity-provider/release
- preferred-language
- Merge into trunk
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 |
Related bugs: |
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.
Commit message
Description of the change
Implemented language chooser
To post a comment you must log in.
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' |
51 | Binary 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' |
53 | Binary 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' |
55 | Binary 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' |
57 | Binary 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' |
59 | Binary 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' |
61 | Binary 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' |
63 | Binary 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 %}© 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): |
Thanks for the doctest fix - all that's left is: r/tests/ test_views_ ui.py,
remove line 41 of identityprovide
file i18n needs a license header