Add Python 3 support.
This commit is contained in:
parent
95b30e6207
commit
b40ccfcba3
15
.travis.yml
15
.travis.yml
@ -2,10 +2,15 @@ language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
env:
|
||||
- DJANGO_VERSION=1.4.5
|
||||
- DJANGO_VERSION=1.5.1
|
||||
- DJANGO_VERSION=1.4.6
|
||||
- DJANGO_VERSION=1.5.2
|
||||
install:
|
||||
- pip install -q Django==${DJANGO_VERSION} --use-mirrors
|
||||
- pip install -q -r requirements.txt --use-mirrors
|
||||
script: fab test
|
||||
- pip install Django==${DJANGO_VERSION}
|
||||
- pip install -r requirements.txt
|
||||
matrix:
|
||||
exclude:
|
||||
- python: "3.3"
|
||||
env: DJANGO_VERSION=1.4.6
|
||||
script: python run_tests.py
|
||||
|
@ -1,6 +1,11 @@
|
||||
CHANGES
|
||||
=======
|
||||
|
||||
v0.x.y
|
||||
------
|
||||
|
||||
* Added Python 3 support.
|
||||
|
||||
v0.6.2
|
||||
------
|
||||
|
||||
|
@ -190,6 +190,11 @@ Usage::
|
||||
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
|
||||
|
@ -174,6 +174,11 @@ not recognize.
|
||||
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."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
import imp
|
||||
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.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
|
||||
|
||||
import jinja2
|
||||
@ -23,7 +31,6 @@ def f(string, *args, **kwargs):
|
||||
>>> {{ "{0} arguments and {x} arguments"|f('positional', x='keyword') }}
|
||||
"positional arguments and keyword arguments"
|
||||
"""
|
||||
string = unicode(string)
|
||||
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
|
||||
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:
|
||||
kwargs[k] = jinja2.escape(smart_unicode(kwargs[k]))
|
||||
kwargs[k] = jinja2.escape(smart_text(kwargs[k]))
|
||||
|
||||
return jinja2.Markup(string.format(*args, **kwargs))
|
||||
|
||||
@ -54,8 +61,12 @@ def nl2br(string):
|
||||
def datetime(t, fmt=None):
|
||||
"""Call ``datetime.strftime`` with the given format string."""
|
||||
if fmt is None:
|
||||
fmt = _('%B %e, %Y')
|
||||
return smart_unicode(t.strftime(fmt.encode('utf-8'))) if t else u''
|
||||
fmt = _(u'%B %e, %Y')
|
||||
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
|
||||
|
@ -21,9 +21,13 @@ from the nuggets project at
|
||||
https://github.com/mozilla/nuggets/blob/master/safe_django_forms.py
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.utils.encoding
|
||||
import django.utils.html
|
||||
import django.utils.safestring
|
||||
from django.utils import six
|
||||
|
||||
|
||||
# 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.
|
||||
"""
|
||||
return unicode(self)
|
||||
return six.text_type(self)
|
||||
|
||||
|
||||
# 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__."""
|
||||
|
||||
def __html__(self):
|
||||
return unicode(self)
|
||||
return six.text_type(self)
|
||||
|
||||
|
||||
def patch():
|
||||
|
@ -1,8 +1,13 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
import jinja2
|
||||
|
||||
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
|
||||
|
||||
|
@ -1,20 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for the jingo's builtin helpers."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
from collections import namedtuple
|
||||
|
||||
from django.utils import six
|
||||
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_
|
||||
|
||||
import jingo
|
||||
from jingo import helpers
|
||||
from jingo import register
|
||||
|
||||
|
||||
def render(s, context={}):
|
||||
t = jingo.env.from_string(s)
|
||||
return t.render(context)
|
||||
from .utils import htmleq_, render
|
||||
|
||||
|
||||
def test_f():
|
||||
@ -35,9 +38,9 @@ def test_fe_positional():
|
||||
|
||||
|
||||
def test_fe_unicode():
|
||||
context = {'var': u'Français'}
|
||||
context = {'var': 'Français'}
|
||||
template = '{{ "Speak {0}"|fe(var) }}'
|
||||
eq_(u'Speak Français', render(template, context))
|
||||
eq_('Speak Français', render(template, context))
|
||||
|
||||
|
||||
def test_fe_markup():
|
||||
@ -113,7 +116,7 @@ def test_field_attrs():
|
||||
def __str__(self):
|
||||
attrs = self.field.widget.attrs
|
||||
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)
|
||||
|
||||
def __html__(self):
|
||||
@ -122,7 +125,7 @@ def test_field_attrs():
|
||||
f = field()
|
||||
s = render('{{ field|field_attrs(class="bar",name="baz") }}',
|
||||
{'field': f})
|
||||
eq_(s, '<input class="bar" name="baz" />')
|
||||
htmleq_(s, '<input class="bar" name="baz" />')
|
||||
|
||||
|
||||
def test_url():
|
||||
@ -157,14 +160,15 @@ def test_custom_url(s):
|
||||
def test_filter_override():
|
||||
def f(s):
|
||||
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)
|
||||
s = render('{{ s|a }}', {'s': 'Str'})
|
||||
eq_(s, 'STR')
|
||||
|
||||
def g(s):
|
||||
return s.lower()
|
||||
g.__name__ = 'a'
|
||||
g.__name__ = 'a' if six.PY3 else b'a'
|
||||
register.filter(override=False)(g)
|
||||
s = render('{{ s|a }}', {'s': 'Str'})
|
||||
eq_(s, 'STR')
|
||||
|
@ -1,34 +1,39 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
from nose.tools import eq_
|
||||
from mock import Mock
|
||||
try:
|
||||
from unittest.mock import Mock
|
||||
except ImportError:
|
||||
from mock import Mock
|
||||
|
||||
|
||||
def test_render():
|
||||
r = render(Mock(), 'jinja_app/test.html', {})
|
||||
eq_(r.content, 'HELLO')
|
||||
eq_(r.content, b'HELLO')
|
||||
|
||||
|
||||
def test_render_no_toplevel_override():
|
||||
r = render(Mock(), 'jinja_app/test_nonoverride.html', {})
|
||||
eq_(r.content, 'HELLO')
|
||||
eq_(r.content, b'HELLO')
|
||||
|
||||
|
||||
def test_render_toplevel_override():
|
||||
r = render(Mock(), 'jinja_app/test_override.html', {})
|
||||
eq_(r.content, 'HELLO')
|
||||
eq_(r.content, b'HELLO')
|
||||
|
||||
|
||||
def test_render_django():
|
||||
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():
|
||||
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():
|
||||
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.utils import six
|
||||
|
||||
from jinja2 import escape
|
||||
from nose.tools import eq_
|
||||
|
||||
import jingo
|
||||
import jingo.monkey
|
||||
from test_helpers import render
|
||||
|
||||
from .utils import render
|
||||
|
||||
|
||||
class MyForm(forms.Form):
|
||||
@ -23,5 +26,5 @@ def test_monkey_patch():
|
||||
jingo.monkey.patch()
|
||||
eq_(html, render(t, context))
|
||||
|
||||
s = unicode(form['email'])
|
||||
s = six.text_type(form['email'])
|
||||
eq_(s, render('{{ form.email }}', {'form': form}))
|
||||
|
@ -1,6 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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 jingo import get_env, render_to_string
|
||||
|
@ -1,3 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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.
|
||||
Django==1.5.1
|
||||
Django==1.5.2
|
||||
sphinx
|
||||
jinja2
|
||||
jinja2==2.7
|
||||
nose
|
||||
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',
|
||||
'Operating System :: OS Independent',
|
||||
'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',
|
||||
]
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user