Skip to content

Commit

Permalink
Support Django 1.10 [refs #35]
Browse files Browse the repository at this point in the history
  • Loading branch information
st4lk committed Jan 8, 2017
1 parent 6a67e46 commit ed4ace9
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 87 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ generate_rst.py
build/
.coverage
.cache
*.sqlite
4 changes: 0 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ env:
- TOXENV=py27-django18
- TOXENV=py27-django19

branches:
only:
- master

install: pip install --quiet tox

# command to run tests
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,24 @@ solid_i18n contains middleware and url patterns to use default language at root

Default language is set in settings.LANGUAGE_CODE.

Deprecation notice
------------------
Starting from [Django 1.10](https://docs.djangoproject.com/en/dev/releases/1.10/#internationalization), built-in `i18n_patterns` accept optional argument `prefix_default_language`. If it is `False`, then Django will serve url without language prefix by itself. Look [docs](https://docs.djangoproject.com/en/dev/topics/i18n/translation/#django.conf.urls.i18n.i18n_patterns) for more details.

This package can still be useful in following cases (look below for settings details):
- You need `settings.SOLID_I18N_USE_REDIRECTS = True` behaviour
- You need `settings.SOLID_I18N_HANDLE_DEFAULT_PREFIX = True` behaviour
- You need `settings.SOLID_I18N_DEFAULT_PREFIX_REDIRECT = True` behaviour
- You need `settings.SOLID_I18N_PREFIX_STRICT = True` behaviour

In all other cases no need in current package, just use Django>=1.10.


Requirements
-----------

- python (2.7, 3.4, 3.5)
- django (1.8, 1.9)
- django (1.8, 1.9, 1.10)

Release notes
-------------
Expand Down Expand Up @@ -75,7 +87,7 @@ Quick start
from django.conf.urls import patterns, include, url
from solid_i18n.urls import solid_i18n_patterns

urlpatterns = solid_i18n_patterns('',
urlpatterns = solid_i18n_patterns(
url(r'^about/$', 'about.view', name='about'),
url(r'^news/', include(news_patterns, namespace='news')),
)
Expand Down
Binary file removed example/db.sqlite
Binary file not shown.
49 changes: 23 additions & 26 deletions example/example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
location = lambda x: os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', x)

DEBUG = True
TEMPLATE_DEBUG = DEBUG

ADMINS = (
# ('Your Name', '[email protected]'),
Expand Down Expand Up @@ -99,13 +98,6 @@
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'x%u66q%jc7$&cy3%w2knw84tqc8i@e^9ag_f19w6ace@5a8jj0'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
Expand All @@ -117,29 +109,34 @@
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.contrib.messages.context_processors.messages",
"django.core.context_processors.request",
'django.core.context_processors.i18n',
'example.context_processors.solid_i18n',
)

ROOT_URLCONF = 'example.urls'

# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'example.wsgi.application'

TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
location('templates'),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'DIRS': [
location('templates'),
],
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
'django.template.context_processors.i18n',
'example.context_processors.solid_i18n',
],
},
},
]

INSTALLED_APPS = (
'django.contrib.auth',
Expand Down
11 changes: 6 additions & 5 deletions example/example/urls.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
from django.conf.urls import patterns, url, include
from solid_i18n.urls import solid_i18n_patterns
from django.conf.urls import url, include
from django.views.generic import TemplateView

urlpatterns = solid_i18n_patterns('',
from solid_i18n.urls import solid_i18n_patterns

urlpatterns = solid_i18n_patterns(
url(r'^$', TemplateView.as_view(template_name="home.html"), name='home'),
url(r'^about/$', TemplateView.as_view(template_name="about.html"),
name='about'),
)

# without i18n
urlpatterns += patterns('',
urlpatterns += [
url(r'^onelang/', TemplateView.as_view(template_name="onelang.html"),
name='onelang'),
url(r'^i18n/', include('django.conf.urls.i18n')),
)
]
17 changes: 10 additions & 7 deletions example/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
try:
from importlib import reload # builtin reload deprecated since version 3.4
except ImportError:
try:
from imp import reload
except ImportError:
pass
from django.conf import settings
from django.test import TestCase
from django.core.urlresolvers import clear_url_caches
Expand All @@ -12,13 +19,8 @@
except ImportError:
class TransRealMixin(object):
pass
try:
from importlib import reload # builtin reload deprecated since version 3.4
except ImportError:
try:
from imp import reload
except ImportError:
pass

from solid_i18n.urls import is_language_prefix_patterns_used


def reload_urlconf(urlconf=None, urls_attr='urlpatterns'):
Expand All @@ -34,6 +36,7 @@ def setUp(self):
# Make sure the cache is empty before we are doing our tests.
super(URLTestCaseBase, self).tearDown()
clear_url_caches()
is_language_prefix_patterns_used.cache_clear()
reload_urlconf()

def tearDown(self):
Expand Down
2 changes: 1 addition & 1 deletion example/tests/test_solid_urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys

from django.core.urlresolvers import reverse
from django.conf.urls import url
from django.utils import translation
Expand Down
12 changes: 6 additions & 6 deletions example/tests/urls_noni18n.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from django.conf.urls import patterns, url, include
from django.conf.urls import url, include
from django.views.generic import TemplateView

urlpatterns = patterns('',
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name="home.html"), name='home'),
url(r'^about/$', TemplateView.as_view(template_name="about.html"),
name='about'),
)
]

urlpatterns += patterns('',
urlpatterns += [
url(r'^onelang/', TemplateView.as_view(template_name="onelang.html"),
name='onelang'),
(r'^i18n/', include('django.conf.urls.i18n')),
)
url(r'^i18n/', include('django.conf.urls.i18n')),
]
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
django>=1.8,<1.10
django>=1.8,<1.11
48 changes: 19 additions & 29 deletions solid_i18n/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@

from django import VERSION as DJANGO_VERSION
from django.conf import settings
from django.core.urlresolvers import (is_valid_path, get_resolver,
get_script_prefix)
from django.core.urlresolvers import is_valid_path, get_script_prefix
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
from django.utils.cache import patch_vary_headers
from django.middleware.locale import LocaleMiddleware
from django.utils import translation as trans
from django.utils.cache import patch_vary_headers
from django.utils.translation.trans_real import language_code_prefix_re
from django.middleware.locale import LocaleMiddleware
from django.utils.functional import cached_property

from .urlresolvers import SolidLocaleRegexURLResolver
from .memory import set_language_from_path
from .contrib import get_full_path
from .memory import set_language_from_path
from .urls import is_language_prefix_patterns_used

strict_language_code_prefix_re = re.compile(
r'^/({0})(/|$)'.format(
Expand All @@ -27,6 +25,7 @@
flags=re.IGNORECASE
)


def get_language_from_path(path):
"""
django.utils.translation wrapper does't allow/pass strict argument
Expand Down Expand Up @@ -63,7 +62,8 @@ def default_lang(self):
return settings.LANGUAGE_CODE

def process_request(self, request):
check_path = self.is_language_prefix_patterns_used
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
check_path = is_language_prefix_patterns_used(urlconf)
language_path = get_language_from_path(request.path_info)
if check_path and not self.use_redirects:
language = language_path or self.default_lang
Expand All @@ -76,21 +76,22 @@ def process_request(self, request):
def process_response(self, request, response):
language = trans.get_language()
language_from_path = get_language_from_path(request.path_info)
if (getattr(settings, 'SOLID_I18N_DEFAULT_PREFIX_REDIRECT', False)
and language_from_path == self.default_lang
and self.is_language_prefix_patterns_used):
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
i18n_patterns_used = is_language_prefix_patterns_used(urlconf)
if (getattr(settings, 'SOLID_I18N_DEFAULT_PREFIX_REDIRECT', False) and
language_from_path == self.default_lang and
i18n_patterns_used):
redirect = self.perform_redirect(request, '', is_permanent=True)
if redirect:
return redirect
elif self.use_redirects:
if (response.status_code == 404 and not language_from_path
and self.is_language_prefix_patterns_used
and language != self.default_lang):
if (response.status_code == 404 and not language_from_path and
i18n_patterns_used and
language != self.default_lang):
redirect = self.perform_redirect(request, language)
if redirect:
return redirect
if not (self.is_language_prefix_patterns_used
and language_from_path):
if not (i18n_patterns_used and language_from_path):
patch_vary_headers(response, ('Accept-Language',))
if 'Content-Language' not in response:
response['Content-Language'] = language
Expand Down Expand Up @@ -118,8 +119,8 @@ def perform_redirect(self, request, language, is_permanent=False):
path_valid = is_valid_path(language_path, urlconf)
path_needs_slash = (
not path_valid and (
settings.APPEND_SLASH and not language_path.endswith('/')
and is_valid_path('%s/' % language_path, urlconf)
settings.APPEND_SLASH and not language_path.endswith('/') and
is_valid_path('%s/' % language_path, urlconf)
)
)

Expand All @@ -142,14 +143,3 @@ def perform_redirect(self, request, language, is_permanent=False):
return self.response_default_language_redirect_class(language_url)
else:
return self.response_redirect_class(language_url)

@cached_property
def is_language_prefix_patterns_used(self):
"""
Returns `True` if the `SolidLocaleRegexURLResolver` is used
at root level of the urlpatterns, else it returns `False`.
"""
for url_pattern in get_resolver(None).url_patterns:
if isinstance(url_pattern, SolidLocaleRegexURLResolver):
return True
return False
21 changes: 18 additions & 3 deletions solid_i18n/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import warnings
from django import VERSION as DJANGO_VERSION
from django.conf import settings
from django.conf.urls import patterns
from django.utils import six
from django.utils import lru_cache, six
from django.core.urlresolvers import get_resolver

from .urlresolvers import SolidLocaleRegexURLResolver


Expand All @@ -14,7 +16,8 @@ def solid_i18n_patterns(prefix, *args):
Do not adds any language code prefix to default language URL pattern.
Default language must be set in settings.LANGUAGE_CODE
"""
if isinstance(prefix, six.string_types):
if DJANGO_VERSION < (1, 10) and isinstance(prefix, six.string_types):
from django.conf.urls import patterns
warnings.warn(
"Calling solid_i18n_patterns() with the `prefix` argument and with "
"tuples instead of django.conf.urls.url() instances is deprecated and "
Expand All @@ -29,3 +32,15 @@ def solid_i18n_patterns(prefix, *args):
if not settings.USE_I18N:
return pattern_list
return [SolidLocaleRegexURLResolver(pattern_list)]


@lru_cache.lru_cache(maxsize=None)
def is_language_prefix_patterns_used(urlconf):
"""
Returns `True` if the `SolidLocaleRegexURLResolver` is used
at root level of the urlpatterns, else it returns `False`.
"""
for url_pattern in get_resolver(urlconf).url_patterns:
if isinstance(url_pattern, SolidLocaleRegexURLResolver):
return True
return False
7 changes: 4 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
envlist=
py{27,34,35}-django{18,19},
py{27,34,35}-django{18,19,110},

[testenv]
basepython =
Expand All @@ -10,15 +10,16 @@ basepython =
deps =
django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10
py27-django19: coverage
django110: Django>=1.10,<1.11
py27-django110: coverage
-rrequirements_test.txt
setenv =
PYTHONPATH = {toxinidir}/example
LC_ALL = en_US.utf-8
commands =
py.test

[testenv:py27-django19]
[testenv:py27-django110]
commands =
coverage run --source=solid_i18n -m py.test
coverage report

0 comments on commit ed4ace9

Please sign in to comment.