Add Python 3 support.
This commit is contained in:
parent
95b30e6207
commit
b40ccfcba3
19
.travis.yml
19
.travis.yml
@ -1,11 +1,16 @@
|
|||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "2.6"
|
- "2.6"
|
||||||
- "2.7"
|
- "2.7"
|
||||||
|
- "3.3"
|
||||||
env:
|
env:
|
||||||
- DJANGO_VERSION=1.4.5
|
- DJANGO_VERSION=1.4.6
|
||||||
- DJANGO_VERSION=1.5.1
|
- DJANGO_VERSION=1.5.2
|
||||||
install:
|
install:
|
||||||
- pip install -q Django==${DJANGO_VERSION} --use-mirrors
|
- pip install Django==${DJANGO_VERSION}
|
||||||
- pip install -q -r requirements.txt --use-mirrors
|
- pip install -r requirements.txt
|
||||||
script: fab test
|
matrix:
|
||||||
|
exclude:
|
||||||
|
- python: "3.3"
|
||||||
|
env: DJANGO_VERSION=1.4.6
|
||||||
|
script: python run_tests.py
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
CHANGES
|
CHANGES
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
v0.x.y
|
||||||
|
------
|
||||||
|
|
||||||
|
* Added Python 3 support.
|
||||||
|
|
||||||
v0.6.2
|
v0.6.2
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -190,6 +190,11 @@ Usage::
|
|||||||
Testing
|
Testing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Testing is handle via fabric::
|
To run the test suite, you need to define ``DJANGO_SETTINGS_MODULE`` first::
|
||||||
|
|
||||||
fab test
|
$ export DJANGO_SETTINGS_MODULE="fake_settings"
|
||||||
|
$ nosetests
|
||||||
|
|
||||||
|
or simply run::
|
||||||
|
|
||||||
|
$ python run_tests.py
|
||||||
|
@ -154,9 +154,9 @@ could create a link if I knew how to do that.
|
|||||||
|
|
||||||
The other method uses Jinja's ``trans`` tag::
|
The other method uses Jinja's ``trans`` tag::
|
||||||
|
|
||||||
{% trans user=review.user|user_link, date=review.created|datetime %}
|
{% trans user=review.user|user_link, date=review.created|datetime %}
|
||||||
by {{ user }} on {{ date }}
|
by {{ user }} on {{ date }}
|
||||||
{% endtrans %}
|
{% endtrans %}
|
||||||
|
|
||||||
``trans`` is nice when you have a lot of text or want to inject some variables
|
``trans`` is nice when you have a lot of text or want to inject some variables
|
||||||
directly. Both methods are useful, pick the one that makes you happy.
|
directly. Both methods are useful, pick the one that makes you happy.
|
||||||
@ -174,6 +174,11 @@ not recognize.
|
|||||||
Testing
|
Testing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Testing is handle via fabric::
|
To run the test suite, you need to define ``DJANGO_SETTINGS_MODULE`` first::
|
||||||
|
|
||||||
fab test
|
$ export DJANGO_SETTINGS_MODULE="fake_settings"
|
||||||
|
$ nosetests
|
||||||
|
|
||||||
|
or simply run::
|
||||||
|
|
||||||
|
$ python run_tests.py
|
||||||
|
40
fabfile.py
vendored
40
fabfile.py
vendored
@ -1,40 +0,0 @@
|
|||||||
"""
|
|
||||||
Creating standalone Django apps is a PITA because you're not in a project, so
|
|
||||||
you don't have a settings.py file. I can never remember to define
|
|
||||||
DJANGO_SETTINGS_MODULE, so I run these commands which get the right env
|
|
||||||
automatically.
|
|
||||||
"""
|
|
||||||
import functools
|
|
||||||
import os
|
|
||||||
|
|
||||||
from fabric.api import local, cd, env
|
|
||||||
from fabric.contrib.project import rsync_project
|
|
||||||
|
|
||||||
NAME = os.path.basename(os.path.dirname(__file__))
|
|
||||||
ROOT = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
|
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'fake_settings'
|
|
||||||
os.environ['PYTHONPATH'] = os.pathsep.join([ROOT,
|
|
||||||
os.path.join(ROOT, 'examples')])
|
|
||||||
|
|
||||||
env.hosts = ['jbalogh.me']
|
|
||||||
|
|
||||||
local = functools.partial(local, capture=False)
|
|
||||||
|
|
||||||
|
|
||||||
def doc(kind='html'):
|
|
||||||
with cd('docs'):
|
|
||||||
local('make clean %s' % kind)
|
|
||||||
|
|
||||||
def shell():
|
|
||||||
local('django-admin.py shell')
|
|
||||||
|
|
||||||
def test():
|
|
||||||
local('nosetests')
|
|
||||||
|
|
||||||
def cover():
|
|
||||||
local('nosetests --with-coverage')
|
|
||||||
|
|
||||||
def updoc():
|
|
||||||
doc('dirhtml')
|
|
||||||
rsync_project('p/%s' % NAME, 'docs/_build/dirhtml/', delete=True)
|
|
@ -1,4 +1,7 @@
|
|||||||
"""Adapter for using Jinja2 with Django."""
|
"""Adapter for using Jinja2 with Django."""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import imp
|
import imp
|
||||||
import logging
|
import logging
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, print_function
|
||||||
|
|
||||||
|
from django.utils import six
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.template.defaulttags import CsrfTokenNode
|
from django.template.defaulttags import CsrfTokenNode
|
||||||
from django.utils.encoding import smart_unicode
|
try:
|
||||||
|
from django.utils.encoding import smart_unicode as smart_text
|
||||||
|
except ImportError:
|
||||||
|
from django.utils.encoding import smart_text
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
@ -23,7 +31,6 @@ def f(string, *args, **kwargs):
|
|||||||
>>> {{ "{0} arguments and {x} arguments"|f('positional', x='keyword') }}
|
>>> {{ "{0} arguments and {x} arguments"|f('positional', x='keyword') }}
|
||||||
"positional arguments and keyword arguments"
|
"positional arguments and keyword arguments"
|
||||||
"""
|
"""
|
||||||
string = unicode(string)
|
|
||||||
return string.format(*args, **kwargs)
|
return string.format(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -32,12 +39,12 @@ def fe(string, *args, **kwargs):
|
|||||||
"""Format a safe string with potentially unsafe arguments, then return a
|
"""Format a safe string with potentially unsafe arguments, then return a
|
||||||
safe string."""
|
safe string."""
|
||||||
|
|
||||||
string = unicode(string)
|
string = six.text_type(string)
|
||||||
|
|
||||||
args = [jinja2.escape(smart_unicode(v)) for v in args]
|
args = [jinja2.escape(smart_text(v)) for v in args]
|
||||||
|
|
||||||
for k in kwargs:
|
for k in kwargs:
|
||||||
kwargs[k] = jinja2.escape(smart_unicode(kwargs[k]))
|
kwargs[k] = jinja2.escape(smart_text(kwargs[k]))
|
||||||
|
|
||||||
return jinja2.Markup(string.format(*args, **kwargs))
|
return jinja2.Markup(string.format(*args, **kwargs))
|
||||||
|
|
||||||
@ -54,8 +61,12 @@ def nl2br(string):
|
|||||||
def datetime(t, fmt=None):
|
def datetime(t, fmt=None):
|
||||||
"""Call ``datetime.strftime`` with the given format string."""
|
"""Call ``datetime.strftime`` with the given format string."""
|
||||||
if fmt is None:
|
if fmt is None:
|
||||||
fmt = _('%B %e, %Y')
|
fmt = _(u'%B %e, %Y')
|
||||||
return smart_unicode(t.strftime(fmt.encode('utf-8'))) if t else u''
|
if not six.PY3:
|
||||||
|
# The datetime.strftime function strictly does not
|
||||||
|
# support Unicode in Python 2 but is Unicode only in 3.x.
|
||||||
|
fmt = fmt.encode('utf-8')
|
||||||
|
return smart_text(t.strftime(fmt)) if t else ''
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
@ -21,9 +21,13 @@ from the nuggets project at
|
|||||||
https://github.com/mozilla/nuggets/blob/master/safe_django_forms.py
|
https://github.com/mozilla/nuggets/blob/master/safe_django_forms.py
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django.utils.encoding
|
import django.utils.encoding
|
||||||
import django.utils.html
|
import django.utils.html
|
||||||
import django.utils.safestring
|
import django.utils.safestring
|
||||||
|
from django.utils import six
|
||||||
|
|
||||||
|
|
||||||
# This function gets directly imported within Django, so this change needs to
|
# This function gets directly imported within Django, so this change needs to
|
||||||
@ -47,7 +51,7 @@ def __html__(self):
|
|||||||
|
|
||||||
Allows interoperability with other template engines.
|
Allows interoperability with other template engines.
|
||||||
"""
|
"""
|
||||||
return unicode(self)
|
return six.text_type(self)
|
||||||
|
|
||||||
|
|
||||||
# Django uses StrAndUnicode for classes like Form, BoundField, Widget which
|
# Django uses StrAndUnicode for classes like Form, BoundField, Widget which
|
||||||
@ -57,7 +61,7 @@ class SafeStrAndUnicode(django.utils.encoding.StrAndUnicode):
|
|||||||
"""A class whose __str__ and __html__ returns __unicode__."""
|
"""A class whose __str__ and __html__ returns __unicode__."""
|
||||||
|
|
||||||
def __html__(self):
|
def __html__(self):
|
||||||
return unicode(self)
|
return six.text_type(self)
|
||||||
|
|
||||||
|
|
||||||
def patch():
|
def patch():
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
from mock import Mock, patch, sentinel
|
try:
|
||||||
|
from unittest.mock import Mock, patch, sentinel
|
||||||
|
except ImportError:
|
||||||
|
from mock import Mock, patch, sentinel
|
||||||
|
|
||||||
import jingo
|
import jingo
|
||||||
|
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Tests for the jingo's builtin helpers."""
|
"""Tests for the jingo's builtin helpers."""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from django.utils import six
|
||||||
from jinja2 import Markup
|
from jinja2 import Markup
|
||||||
from mock import patch
|
try:
|
||||||
|
from unittest.mock import patch
|
||||||
|
except ImportError:
|
||||||
|
from mock import patch
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
|
|
||||||
import jingo
|
|
||||||
from jingo import helpers
|
from jingo import helpers
|
||||||
from jingo import register
|
from jingo import register
|
||||||
|
|
||||||
|
from .utils import htmleq_, render
|
||||||
def render(s, context={}):
|
|
||||||
t = jingo.env.from_string(s)
|
|
||||||
return t.render(context)
|
|
||||||
|
|
||||||
|
|
||||||
def test_f():
|
def test_f():
|
||||||
@ -35,9 +38,9 @@ def test_fe_positional():
|
|||||||
|
|
||||||
|
|
||||||
def test_fe_unicode():
|
def test_fe_unicode():
|
||||||
context = {'var': u'Français'}
|
context = {'var': 'Français'}
|
||||||
template = '{{ "Speak {0}"|fe(var) }}'
|
template = '{{ "Speak {0}"|fe(var) }}'
|
||||||
eq_(u'Speak Français', render(template, context))
|
eq_('Speak Français', render(template, context))
|
||||||
|
|
||||||
|
|
||||||
def test_fe_markup():
|
def test_fe_markup():
|
||||||
@ -113,7 +116,7 @@ def test_field_attrs():
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
attrs = self.field.widget.attrs
|
attrs = self.field.widget.attrs
|
||||||
attr_str = ' '.join('%s="%s"' % (k, v)
|
attr_str = ' '.join('%s="%s"' % (k, v)
|
||||||
for (k, v) in attrs.iteritems())
|
for (k, v) in six.iteritems(attrs))
|
||||||
return Markup('<input %s />' % attr_str)
|
return Markup('<input %s />' % attr_str)
|
||||||
|
|
||||||
def __html__(self):
|
def __html__(self):
|
||||||
@ -122,7 +125,7 @@ def test_field_attrs():
|
|||||||
f = field()
|
f = field()
|
||||||
s = render('{{ field|field_attrs(class="bar",name="baz") }}',
|
s = render('{{ field|field_attrs(class="bar",name="baz") }}',
|
||||||
{'field': f})
|
{'field': f})
|
||||||
eq_(s, '<input class="bar" name="baz" />')
|
htmleq_(s, '<input class="bar" name="baz" />')
|
||||||
|
|
||||||
|
|
||||||
def test_url():
|
def test_url():
|
||||||
@ -157,14 +160,15 @@ def test_custom_url(s):
|
|||||||
def test_filter_override():
|
def test_filter_override():
|
||||||
def f(s):
|
def f(s):
|
||||||
return s.upper()
|
return s.upper()
|
||||||
f.__name__ = 'a'
|
# See issue 7688: http://bugs.python.org/issue7688
|
||||||
|
f.__name__ = 'a' if six.PY3 else b'a'
|
||||||
register.filter(f)
|
register.filter(f)
|
||||||
s = render('{{ s|a }}', {'s': 'Str'})
|
s = render('{{ s|a }}', {'s': 'Str'})
|
||||||
eq_(s, 'STR')
|
eq_(s, 'STR')
|
||||||
|
|
||||||
def g(s):
|
def g(s):
|
||||||
return s.lower()
|
return s.lower()
|
||||||
g.__name__ = 'a'
|
g.__name__ = 'a' if six.PY3 else b'a'
|
||||||
register.filter(override=False)(g)
|
register.filter(override=False)(g)
|
||||||
s = render('{{ s|a }}', {'s': 'Str'})
|
s = render('{{ s|a }}', {'s': 'Str'})
|
||||||
eq_(s, 'STR')
|
eq_(s, 'STR')
|
||||||
|
@ -1,34 +1,39 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
from mock import Mock
|
try:
|
||||||
|
from unittest.mock import Mock
|
||||||
|
except ImportError:
|
||||||
|
from mock import Mock
|
||||||
|
|
||||||
|
|
||||||
def test_render():
|
def test_render():
|
||||||
r = render(Mock(), 'jinja_app/test.html', {})
|
r = render(Mock(), 'jinja_app/test.html', {})
|
||||||
eq_(r.content, 'HELLO')
|
eq_(r.content, b'HELLO')
|
||||||
|
|
||||||
|
|
||||||
def test_render_no_toplevel_override():
|
def test_render_no_toplevel_override():
|
||||||
r = render(Mock(), 'jinja_app/test_nonoverride.html', {})
|
r = render(Mock(), 'jinja_app/test_nonoverride.html', {})
|
||||||
eq_(r.content, 'HELLO')
|
eq_(r.content, b'HELLO')
|
||||||
|
|
||||||
|
|
||||||
def test_render_toplevel_override():
|
def test_render_toplevel_override():
|
||||||
r = render(Mock(), 'jinja_app/test_override.html', {})
|
r = render(Mock(), 'jinja_app/test_override.html', {})
|
||||||
eq_(r.content, 'HELLO')
|
eq_(r.content, b'HELLO')
|
||||||
|
|
||||||
|
|
||||||
def test_render_django():
|
def test_render_django():
|
||||||
r = render(Mock(), 'django_app/test.html', {})
|
r = render(Mock(), 'django_app/test.html', {})
|
||||||
eq_(r.content, 'HELLO ...\n')
|
eq_(r.content, b'HELLO ...\n')
|
||||||
|
|
||||||
|
|
||||||
def test_render_django_no_toplevel_override():
|
def test_render_django_no_toplevel_override():
|
||||||
r = render(Mock(), 'django_app/test_nonoverride.html', {})
|
r = render(Mock(), 'django_app/test_nonoverride.html', {})
|
||||||
eq_(r.content, 'HELLO ...\n')
|
eq_(r.content, b'HELLO ...\n')
|
||||||
|
|
||||||
|
|
||||||
def test_render_django_toplevel_override():
|
def test_render_django_toplevel_override():
|
||||||
r = render(Mock(), 'django_app/test_override.html', {})
|
r = render(Mock(), 'django_app/test_override.html', {})
|
||||||
eq_(r.content, 'HELLO ...\n')
|
eq_(r.content, b'HELLO ...\n')
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.utils import six
|
||||||
|
|
||||||
from jinja2 import escape
|
from jinja2 import escape
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
|
|
||||||
import jingo
|
|
||||||
import jingo.monkey
|
import jingo.monkey
|
||||||
from test_helpers import render
|
|
||||||
|
from .utils import render
|
||||||
|
|
||||||
|
|
||||||
class MyForm(forms.Form):
|
class MyForm(forms.Form):
|
||||||
@ -23,5 +26,5 @@ def test_monkey_patch():
|
|||||||
jingo.monkey.patch()
|
jingo.monkey.patch()
|
||||||
eq_(html, render(t, context))
|
eq_(html, render(t, context))
|
||||||
|
|
||||||
s = unicode(form['email'])
|
s = six.text_type(form['email'])
|
||||||
eq_(s, render('{{ form.email }}', {'form': form}))
|
eq_(s, render('{{ form.email }}', {'form': form}))
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
from mock import sentinel
|
try:
|
||||||
|
from unittest.mock import sentinel
|
||||||
|
except ImportError:
|
||||||
|
from mock import sentinel
|
||||||
from nose.tools import eq_
|
from nose.tools import eq_
|
||||||
|
|
||||||
from jingo import get_env, render_to_string
|
from jingo import get_env, render_to_string
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf.urls import patterns
|
from django.conf.urls import patterns
|
||||||
|
|
||||||
|
|
||||||
|
35
jingo/tests/utils.py
Normal file
35
jingo/tests/utils.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from django.test.html import HTMLParseError, parse_html
|
||||||
|
from nose.tools import eq_
|
||||||
|
|
||||||
|
from jingo import env
|
||||||
|
|
||||||
|
|
||||||
|
def htmleq_(html1, html2, msg=None):
|
||||||
|
"""
|
||||||
|
Asserts that two HTML snippets are semantically the same.
|
||||||
|
Whitespace in most cases is ignored, and attribute ordering is not
|
||||||
|
significant. The passed-in arguments must be valid HTML.
|
||||||
|
|
||||||
|
See ticket 16921: https://code.djangoproject.com/ticket/16921
|
||||||
|
|
||||||
|
"""
|
||||||
|
dom1 = assert_and_parse_html(html1, msg,
|
||||||
|
'First argument is not valid HTML:')
|
||||||
|
dom2 = assert_and_parse_html(html2, msg,
|
||||||
|
'Second argument is not valid HTML:')
|
||||||
|
|
||||||
|
eq_(dom1, dom2)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_and_parse_html(html, user_msg, msg):
|
||||||
|
try:
|
||||||
|
dom = parse_html(html)
|
||||||
|
except HTMLParseError as e:
|
||||||
|
standard_msg = '%s\n%s\n%s' % (user_msg, msg, e.msg)
|
||||||
|
raise AssertionError(standard_msg)
|
||||||
|
return dom
|
||||||
|
|
||||||
|
|
||||||
|
def render(s, context={}):
|
||||||
|
t = env.from_string(s)
|
||||||
|
return t.render(context)
|
@ -1,7 +1,6 @@
|
|||||||
# These are the reqs to build docs and run tests.
|
# These are the reqs to build docs and run tests.
|
||||||
Django==1.5.1
|
Django==1.5.2
|
||||||
sphinx
|
sphinx
|
||||||
jinja2
|
jinja2==2.7
|
||||||
nose
|
nose
|
||||||
mock
|
mock
|
||||||
fabric
|
|
||||||
|
13
run_tests.py
Normal file
13
run_tests.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import nose
|
||||||
|
|
||||||
|
NAME = os.path.basename(os.path.dirname(__file__))
|
||||||
|
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'fake_settings'
|
||||||
|
os.environ['PYTHONPATH'] = os.pathsep.join([ROOT,
|
||||||
|
os.path.join(ROOT, 'examples')])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
nose.main()
|
5
setup.py
5
setup.py
@ -24,6 +24,11 @@ setup(
|
|||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 2.6',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.3',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user