Python 3 support, and travis #8

Closed
wants to merge 8 commits into
from
View
@@ -1,2 +1,3 @@
*.pyc
*.pyo
+test.db
View
@@ -0,0 +1,19 @@
+language: python
+python:
+ - 2.6
+ - 2.7
+env:
+ - DJANGO_VERSION=1.3.7
+ - DJANGO_VERSION=1.4.5
+ - DJANGO_VERSION=1.5
+matrix:
+ include:
+ - python: 3.2
+ env: DJANGO_VERSION=1.5
+ - python: 3.3
+ env: DJANGO_VERSION=1.5
+install:
+ - pip install -e git+git://github.com/sobotklp/django-nose@b3f485e914965e0a7b7012c309864d6a6dcac3ed#egg=django-nose
+ - pip install -e git+git://github.com/django/django.git@${DJANGO_VERSION}#egg=django
+ - pip install -r requirements.txt --use-mirrors
+script: python setup.py test
View
@@ -0,0 +1,6 @@
+# Install
+Django>=1.3
+six
+
+# Tests
+django-nose
View
@@ -0,0 +1,25 @@
+# This file mainly exists to allow python setup.py test to work.
+# http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/
+import os
+import sys
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
+os.environ['REUSE_DB'] = '0'
+
+test_dir = os.path.join(os.path.dirname(__file__), 'templatetag_sugar/tests')
+sys.path.insert(0, test_dir)
+
+from django.test.utils import get_runner
+from django.conf import settings
+from django.core.management import call_command
+
+
+def runtests():
+ call_command('syncdb', interactive=False)
+ call_command('flush', interactive=False)
+ test_runner = get_runner(settings)
+ failures = test_runner(interactive=False, failfast=False).run_tests([])
+ sys.exit(failures)
+
+if __name__ == '__main__':
+ runtests()
View
@@ -1,5 +1,4 @@
-from distutils.core import setup
-
+from setuptools import setup
setup(
name = "django-templatetag-sugar",
@@ -13,13 +12,22 @@
packages = [
"templatetag_sugar",
],
+ install_requires = [
+ "Django>=1.3",
+ "six",
+ ],
+ test_suite="runtests.runtests",
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3.2",
+ "Programming Language :: Python :: 3.3",
"Framework :: Django",
]
)
@@ -6,14 +6,16 @@
from templatetag_sugar.node import SugarNode
+from six.moves import xrange
+
class Parser(object):
def __init__(self, syntax, function):
self.syntax = syntax
self.function = function
-
+
def __call__(self, parser, token):
- # we're going to be doing pop(0) a bit, so a deque is way more
+ # we're going to be doing pop(0) a bit, so a deque is way more
# efficient
bits = deque(token.split_contents())
# pop the name of the tag off
@@ -42,40 +44,43 @@ class Parsable(object):
def resolve(self, context, value):
return value
+
class NamedParsable(Parsable):
def __init__(self, name=None):
self.name = name
-
+
def syntax(self):
if self.name:
return "<%s>" % self.name
return "<arg>"
+
class Constant(Parsable):
def __init__(self, text):
self.text = text
def syntax(self):
return self.text
-
+
def parse(self, parser, bits):
if not bits:
raise TemplateSyntaxError
if bits[0] == self.text:
bits.popleft()
return None
raise TemplateSyntaxError
-
+
class Variable(NamedParsable):
def parse(self, parser, bits):
bit = bits.popleft()
val = parser.compile_filter(bit)
return [(self, self.name, val)]
-
+
def resolve(self, context, value):
return value.resolve(context)
+
class Name(NamedParsable):
def parse(self, parser, bits):
bit = bits.popleft()
@@ -85,10 +90,10 @@ def parse(self, parser, bits):
class Optional(Parsable):
def __init__(self, parts):
self.parts = parts
-
+
def syntax(self):
return "[%s]" % (" ".join(part.syntax() for part in self.parts))
-
+
def parse(self, parser, bits):
result = []
# we make a copy so that if part way through the optional part it
@@ -1,8 +1,24 @@
from django.db import models
+import six
+def python_2_unicode_compatible(klass):
+ """
+A decorator that defines __unicode__ and __str__ methods under Python 2.
+Under Python 3 it does nothing.
+
+To support Python 2 and 3 with a single code base, define a __str__ method
+returning text and apply this decorator to the class.
+"""
+ if not six.PY3:
+ klass.__unicode__ = klass.__str__
+ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+ return klass
+
+
+@python_2_unicode_compatible
class Book(models.Model):
title = models.CharField(max_length=50)
-
- def __unicode__(self):
+
+ def __str__(self):
return self.title
@@ -1,6 +1,19 @@
DATABASE_ENGINE = 'sqlite3'
INSTALLED_APPS = [
+ "django_nose",
"templatetag_sugar",
"templatetag_sugar.tests",
]
+
+SECRET_KEY="asdf"
+
+DATABASES = {
+ 'default': {
+ 'NAME': 'test.db',
+ 'ENGINE': 'django.db.backends.sqlite3',
+ }
+}
+
+TEST_RUNNER = 'django_nose.runner.NoseTestSuiteRunner'
+
@@ -3,6 +3,8 @@
from templatetag_sugar.register import tag
from templatetag_sugar.parser import Name, Variable, Constant, Optional, Model
+from six import text_type
+
register = template.Library()
@@ -14,18 +16,21 @@ def test_tag_1(context, val, asvar=None):
else:
return val
+
@tag(register, [Model(), Variable(), Optional([Constant("as"), Name()])])
def test_tag_2(context, model, limit, asvar=None):
objs = model._default_manager.all()[:limit]
if asvar:
context[asvar] = objs
return ""
- return unicode(objs)
+ return text_type(objs)
+
@tag(register, [Variable()])
def test_tag_3(context, val):
return val
+
@tag(register, [Optional([Constant("width"), Variable('width')]), Optional([Constant("height"), Variable('height')])])
def test_tag_4(context, width=None, height=None):
return "%s, %s" % (width, height)
@@ -8,87 +8,85 @@ class SugarTestCase(TestCase):
def assert_renders(self, tmpl, context, value):
tmpl = Template(tmpl)
self.assertEqual(tmpl.render(context), value)
-
+
def assert_syntax_error(self, tmpl, error):
try:
Template(tmpl)
- except TemplateSyntaxError, e:
+ except TemplateSyntaxError as e:
self.assertTrue(
str(e).endswith(error),
"%s didn't end with %s" % (str(e), error)
)
else:
self.fail("Didn't raise")
-
-
+
def test_basic(self):
self.assert_renders(
- """{% load test_tags %}{% test_tag_1 for "alex" %}""",
+ """{% load demo_tags %}{% test_tag_1 for "alex" %}""",
Context(),
"alex"
)
-
+
c = Context()
self.assert_renders(
- """{% load test_tags %}{% test_tag_1 for "brian" as name %}""",
+ """{% load demo_tags %}{% test_tag_1 for "brian" as name %}""",
c,
""
)
self.assertEqual(c["name"], "brian")
-
-
+
self.assert_renders(
- """{% load test_tags %}{% test_tag_1 for variable %}""",
+ """{% load demo_tags %}{% test_tag_1 for variable %}""",
Context({"variable": [1, 2, 3]}),
"[1, 2, 3]",
)
-
+
def test_model(self):
Book.objects.create(title="Pro Django")
self.assert_renders(
- """{% load test_tags %}{% test_tag_2 tests.Book 2 %}""",
+ """{% load demo_tags %}{% test_tag_2 tests.Book 2 %}""",
Context(),
"[<Book: Pro Django>]"
)
-
+
def test_errors(self):
self.assert_syntax_error(
- """{% load test_tags %}{% test_tag_1 for "jesse" as %}""",
+ """{% load demo_tags %}{% test_tag_1 for "jesse" as %}""",
"test_tag_1 has the following syntax: {% test_tag_1 for <arg> [as <arg>] %}"
)
-
+
self.assert_syntax_error(
- """{% load test_tags %}{% test_tag_4 width %}""",
+ """{% load demo_tags %}{% test_tag_4 width %}""",
"test_tag_4 has the following syntax: {% test_tag_4 [width <width>] [height <height>] %}"
)
def test_variable_as_string(self):
self.assert_renders(
- """{% load test_tags %}{% test_tag_3 "xela alex" %}""",
+ """{% load demo_tags %}{% test_tag_3 "xela alex" %}""",
Context(),
"xela alex",
)
def test_optional(self):
self.assert_renders(
- """{% load test_tags %}{% test_tag_4 width 100 height 200 %}""",
+ """{% load demo_tags %}{% test_tag_4 width 100 height 200 %}""",
Context(),
"100, 200",
)
-
+
self.assert_renders(
- """{% load test_tags %}{% test_tag_4 width 100 %}""",
+ """{% load demo_tags %}{% test_tag_4 width 100 %}""",
Context(),
"100, None"
)
-
+
self.assert_renders(
- """{% load test_tags %}{% test_tag_4 height 100 %}""",
+ """{% load demo_tags %}{% test_tag_4 height 100 %}""",
Context(),
"None, 100",
)
-
+
self.assert_syntax_error(
- """{% load test_tags %}{% test_tag_1 %}""",
+ """{% load demo_tags %}{% test_tag_1 %}""",
"test_tag_1 has the following syntax: {% test_tag_1 for <arg> [as <arg>] %}"
)