Skip to content

Commit 7c0e1ff

Browse files
committed
Merge pull request django-cms#3400 from yakky/feature/fix_language_changer
Fixes for DefaultLanguageChanger
2 parents 8447bad + 83d4686 commit 7c0e1ff

File tree

6 files changed

+88
-23
lines changed

6 files changed

+88
-23
lines changed

cms/test_utils/project/sampleapp/forms.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ class LoginForm(AuthenticationForm):
88

99
class LoginForm2(AuthenticationForm):
1010
pass
11+
12+
13+
class LoginForm3(AuthenticationForm):
14+
def __init__(self, argument, request=None, *args, **kwargs):
15+
super(LoginForm3, self).__init__(request, *args, **kwargs)
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from django.conf.urls import patterns, url
2+
from django.utils.translation import ugettext_lazy as _
23

34
"""
45
Also used in cms.tests.ApphooksTestCase
56
"""
67

78
urlpatterns = patterns('cms.test_utils.project.sampleapp.views',
8-
url(r'^current-app/$', 'current_app', name='current-app' ),
9+
url(r'^current-app/$', 'current_app', name='current-app'),
10+
url(_('page'), 'current_app', name='translated-url'),
911
)

cms/test_utils/project/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from cms.utils.compat.dj import is_installed
88
from cms.utils.conf import get_cms_setting
9-
from cms.test_utils.project.sampleapp.forms import LoginForm, LoginForm2
9+
from cms.test_utils.project.sampleapp.forms import LoginForm, LoginForm2, LoginForm3
1010

1111

1212
admin.autodiscover()
@@ -27,6 +27,8 @@
2727
kwargs={'authentication_form': LoginForm2}),
2828
url(r'^sample/login/$', 'django.contrib.auth.views.login',
2929
kwargs={'authentication_form': LoginForm}),
30+
url(r'^sample/login3/$', 'django.contrib.auth.views.login',
31+
kwargs={'authentication_form': LoginForm3}),
3032
url(r'^admin/', include(admin.site.urls)),
3133
url(r'^example/$', 'cms.test_utils.project.placeholderapp.views.example_view'),
3234
url(r'^plain_view/$', 'cms.test_utils.project.sampleapp.views.plain_view'),

cms/tests/apphooks.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from cms.utils.compat.dj import get_user_model
1616
from cms.utils.conf import get_cms_setting
1717
from cms.utils.i18n import force_language
18+
from menus.utils import DefaultLanguageChanger
1819

1920

2021
APP_NAME = 'SampleApp'
@@ -319,6 +320,23 @@ def test_get_sub_page_for_apphook_with_implicit_current_app(self):
319320

320321
apphook_pool.clear()
321322

323+
def test_default_language_changer_with_implicit_current_app(self):
324+
with SettingsOverride(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests'):
325+
titles = self.create_base_structure(NS_APP_NAME, ['en', 'de'], 'namespaced_app_ns') # nopyflakes
326+
self.reload_urls()
327+
with force_language("en"):
328+
path = reverse('namespaced_app_ns:translated-url')
329+
request = self.get_request(path)
330+
request.LANGUAGE_CODE = 'en'
331+
332+
url = DefaultLanguageChanger(request)('en')
333+
self.assertEqual(url, path)
334+
335+
url = DefaultLanguageChanger(request)('de')
336+
self.assertEqual(url, '/de%s' % path[3:].replace('/page', '/Seite'))
337+
338+
apphook_pool.clear()
339+
322340
def test_get_i18n_apphook_with_explicit_current_app(self):
323341
with SettingsOverride(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests'):
324342
titles = self.create_base_structure(NS_APP_NAME, ['en', 'de'], 'instance_1')
@@ -350,9 +368,6 @@ def test_get_i18n_apphook_with_explicit_current_app(self):
350368
reverse('namespaced_app_ns:current-app', current_app="instance_2")
351369
reverse('namespaced_app_ns:current-app')
352370

353-
354-
355-
356371
def test_apphook_include_extra_parameters(self):
357372
with SettingsOverride(ROOT_URLCONF='cms.test_utils.project.second_urls_for_apphook_tests'):
358373
self.create_base_structure(NS_APP_NAME, ['en', 'de'], 'instance_1')

cms/tests/menu_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ def test_reverse_in_changer(self):
2424
response = self.client.get('/en/sample/login_other/')
2525
self.assertContains(response, '<h1>/fr/sample/login_other/</h1>')
2626

27+
response = self.client.get('/en/sample/login3/')
28+
self.assertContains(response, '<h1>/fr/sample/login3/</h1>')
29+
2730
def test_simple_language_changer(self):
2831
func = self.get_simple_view()
2932
decorated_view = simple_language_changer(func)

menus/utils.py

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import with_statement
3+
from contextlib import contextmanager
4+
import inspect
5+
import warnings
6+
from cms.models.titlemodels import Title
37
from cms.utils import get_language_from_request
8+
from cms.utils.compat import DJANGO_1_6
49
from cms.utils.i18n import force_language, hide_untranslated
510
from django.conf import settings
611
from django.core.urlresolvers import NoReverseMatch, reverse, resolve
7-
8-
import warnings
9-
from cms.models.titlemodels import Title
10-
12+
from django.utils import six
1113

1214

1315
def mark_descendants(nodes):
1416
for node in nodes:
1517
node.descendant = True
1618
mark_descendants(node.children)
1719

20+
1821
def cut_levels(nodes, level):
1922
"""
2023
For cutting the nav_extender levels if you have a from_level in the navigation.
@@ -27,6 +30,7 @@ def cut_levels(nodes, level):
2730
result += cut_levels(node.children, level)
2831
return result
2932

33+
3034
def find_selected(nodes):
3135
"""
3236
Finds a selected nav_extender node
@@ -55,6 +59,7 @@ def get_absolute_url(self, language=None):
5559
"""
5660
request._language_changer = func
5761

62+
5863
def language_changer_decorator(language_changer):
5964
"""
6065
A decorator wrapper for set_language_changer.
@@ -126,22 +131,14 @@ def __call__(self, lang):
126131
elif view and not view.url_name in ('pages-details-by-slug', 'pages-root'):
127132
view_name = view.url_name
128133
if view.namespace:
129-
"%s:%s" % (view.namespace, view_name)
134+
view_name = "%s:%s" % (view.namespace, view_name)
130135
url = None
131-
# every class-level argument is instantiated
132-
# before reversing as reverse does not support
133-
# classes as arguments
134-
for idx, arg in enumerate(view.args):
135-
if isinstance(arg, type):
136-
view.args[idx] = arg()
137-
for key, arg in view.kwargs.items():
138-
if isinstance(arg, type):
139-
view.kwargs[key] = arg()
140136
with force_language(lang):
141-
try:
142-
url = reverse(view_name, args=view.args, kwargs=view.kwargs, current_app=view.app_name)
143-
except NoReverseMatch:
144-
pass
137+
with static_stringifier(view): # This is a fix for Django < 1.7
138+
try:
139+
url = reverse(view_name, args=view.args, kwargs=view.kwargs, current_app=view.app_name)
140+
except NoReverseMatch:
141+
pass
145142
if url:
146143
return url
147144
return '%s%s' % (self.get_page_path(lang), self.app_path)
@@ -158,3 +155,44 @@ def _wrapped(request, *args, **kwargs):
158155
_wrapped.__name__ = func.__name__
159156
_wrapped.__doc__ = func.__doc__
160157
return _wrapped
158+
159+
160+
@contextmanager
161+
def static_stringifier(view):
162+
"""
163+
In Django < 1.7 reverse tries to convert to string the arguments without
164+
checking whether they are classes or instances.
165+
166+
This context manager monkeypatches the __unicode__ method of each view
167+
argument if it's a class definition to render it a static method.
168+
Before leaving we undo the monkeypatching.
169+
"""
170+
if DJANGO_1_6:
171+
for idx, arg in enumerate(view.args):
172+
if inspect.isclass(arg):
173+
if hasattr(arg, '__unicode__'):
174+
@staticmethod
175+
def custom_str():
176+
return six.text_type(arg)
177+
arg._original = arg.__unicode__
178+
arg.__unicode__ = custom_str
179+
view.args[idx] = arg
180+
for key, arg in view.kwargs.items():
181+
if inspect.isclass(arg):
182+
if hasattr(arg, '__unicode__'):
183+
@staticmethod
184+
def custom_str():
185+
return six.text_type(arg)
186+
arg._original = arg.__unicode__
187+
arg.__unicode__ = custom_str
188+
view.kwargs[key] = arg
189+
yield
190+
if DJANGO_1_6:
191+
for idx, arg in enumerate(view.args):
192+
if inspect.isclass(arg):
193+
if hasattr(arg, '__unicode__'):
194+
arg.__unicode__ = arg._original
195+
for key, arg in view.kwargs.items():
196+
if inspect.isclass(arg):
197+
if hasattr(arg, '__unicode__'):
198+
arg.__unicode__ = arg._original

0 commit comments

Comments
 (0)