diff --git a/.travis.yml b/.travis.yml index 57bde0a..b08c34d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,21 +2,20 @@ language: python python: "2.7" env: matrix: - - TOX_ENV=py27-dj1.4.x - - TOX_ENV=py27-dj1.5.x - TOX_ENV=py27-dj1.6.x - TOX_ENV=py27-dj1.7.x - TOX_ENV=py33-dj1.6.x - TOX_ENV=py34-dj1.6.x - TOX_ENV=py33-dj1.7.x - TOX_ENV=py34-dj1.7.x - - TOX_ENV=pypy-dj1.6.x, + - TOX_ENV=pypy-dj1.6.x - TOX_ENV=pypy3-dj1.6.x install: - pip install tox script: - tox -e $TOX_ENV -#for now commented +# for now commented. We have to figure which version use for coverage +# and coveralls #after_success: # - coverage report # - pip install --quiet python-coveralls diff --git a/.tx/config b/.tx/config deleted file mode 100644 index 6992427..0000000 --- a/.tx/config +++ /dev/null @@ -1,24 +0,0 @@ -[main] -host = https://www.transifex.com -lang_map = sr@latin:sr_Latn - -[django-admin2.djadmin2po] -file_filter = djadmin2/locale//LC_MESSAGES/django.po -source_file = djadmin2/locale/en/LC_MESSAGES/django.po -source_lang = en_US -type = PO -minimum_perc = 80 - -[django-admin2.example-app] -file_filter = example/blog/locale//LC_MESSAGES/django.po -source_file = example/blog/locale/en/LC_MESSAGES/django.po -source_lang = en_US -type = PO -minimum_perc = 50 - -[django-admin2.example2-app] -file_filter = example2/polls/locale//LC_MESSAGES/django.po -source_file = example2/polls/locale/en/LC_MESSAGES/django.po -source_lang = en_US -type = PO -minimum_perc = 50 diff --git a/AUTHORS.rst b/AUTHORS.rst index 22d99c6..97a4e36 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -50,6 +50,7 @@ Developers * James Rivett-Carnac (@yarbelk / james.rivettcarnac@gmail.com) * Andrew Mosson (@amosson / amosson@tippit.com) * marangonico +* Kamil GaƂuszka (@galuszkak / galuszkak@gmail.com) Translators ----------- diff --git a/README.rst b/README.rst index 01a7f39..c65775f 100644 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ Screenshots Requirements ============= -* Django 1.5+ +* Django 1.6+ * Python 2.7+ or Python 3.3+ * django-braces_ * django-extra-views_ diff --git a/djadmin2/actions.py b/djadmin2/actions.py index d63848c..edf5c39 100644 --- a/djadmin2/actions.py +++ b/djadmin2/actions.py @@ -49,7 +49,7 @@ class BaseListAction(AdminModel2Mixin, TemplateView): objects_name = options.verbose_name else: objects_name = options.verbose_name_plural - self.objects_name = unicode(objects_name) + self.objects_name = force_text(objects_name) super(BaseListAction, self).__init__(*args, **kwargs) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index c733c32..39e7e32 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -18,7 +18,7 @@ class Admin2APISerializer(serializers.HyperlinkedModelSerializer): _default_view_name = 'admin2:%(app_label)s_%(model_name)s_api_detail' pk = fields.Field(source='pk') - __str__ = fields.Field(source='__unicode__') + __unicode__ = fields.Field(source='__str__') class Admin2APIMixin(Admin2Mixin): diff --git a/djadmin2/core.py b/djadmin2/core.py index 35ff81e..fcd8e3d 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -8,7 +8,8 @@ from __future__ import division, absolute_import, unicode_literals from django.conf.urls import patterns, include, url from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.utils.importlib import import_module + +from importlib import import_module from . import apiviews @@ -122,7 +123,7 @@ class Admin2(object): try: import_module("%s.admin2" % app_name) except ImportError as e: - if str(e) == "No module named admin2": + if str(e).startswith("No module named") and 'admin2' in str(e): continue raise e @@ -188,7 +189,7 @@ class Admin2(object): name='api_index' ), ) - for model, model_admin in self.registry.iteritems(): + for model, model_admin in self.registry.items(): model_options = utils.model_options(model) urlpatterns += patterns( '', @@ -207,3 +208,4 @@ class Admin2(object): def urls(self): # We set the application and instance namespace here return self.get_urls(), self.name, self.name + diff --git a/djadmin2/filters.py b/djadmin2/filters.py index 4151b94..7c06c49 100644 --- a/djadmin2/filters.py +++ b/djadmin2/filters.py @@ -2,18 +2,22 @@ from __future__ import division, absolute_import, unicode_literals import collections + from itertools import chain from django import forms from django.forms.util import flatatt from django.utils.html import format_html -from django.utils.encoding import force_text +from django.utils.encoding import force_text, force_bytes from django.utils.safestring import mark_safe from django.forms import widgets as django_widgets +from django.utils import six from django.utils.translation import ugettext_lazy import django_filters +from .utils import type_str + LINK_TEMPLATE = '{3}' @@ -74,7 +78,7 @@ def build_list_filter(request, model_admin, queryset): # otherwise build :mod:`django_filters.FilterSet` filters = [] for field_filter in model_admin.list_filter: - if isinstance(field_filter, basestring): + if isinstance(field_filter, six.string_types): filters.append(get_filter_for_field_name( queryset.model, field_filter, @@ -84,20 +88,16 @@ def build_list_filter(request, model_admin, queryset): filterset_dict = {} for field_filter in filters: filterset_dict[field_filter.name] = field_filter - fields = filterset_dict.keys() + fields = list(filterset_dict.keys()) filterset_dict['Meta'] = type( - b'Meta', + type_str('Meta'), (), { 'model': queryset.model, 'fields': fields, }, ) - return type( - b'%sFilterSet' % queryset.model.__name__, - (django_filters.FilterSet, ), - filterset_dict, - )(request.GET, queryset=queryset) + return type(type_str('%sFilterSet' % queryset.model.__name__),(django_filters.FilterSet, ),filterset_dict,)(request.GET, queryset=queryset) def build_date_filter(request, model_admin, queryset): @@ -117,7 +117,7 @@ def build_date_filter(request, model_admin, queryset): } return type( - b'%sDateFilterSet' % queryset.model.__name__, + type_str('%sDateFilterSet' % queryset.model.__name__), (django_filters.FilterSet,), filterset_dict, )(request.GET, queryset=queryset) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index 554560b..7721f97 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -166,7 +166,9 @@ _django_to_floppyforms_widget = { django.forms.extras.widgets.SelectDateWidget: _create_widget( floppyforms.widgets.SelectDateWidget, - init_arguments=('years', 'required')), + init_arguments= + ('years',) + if django.VERSION >= (1, 7) else ('years', 'required')), } _django_field_to_floppyform_widget = { diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index 4c4d4c8..849d465 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -26,6 +26,7 @@ from django.db.models import get_models from django.utils import six from . import utils +from django.utils.encoding import python_2_unicode_compatible, force_text logger = logging.getLogger('djadmin2') @@ -185,6 +186,7 @@ class ModelDeletePermission(BasePermission): permissions = (model_permission('{app_label}.delete_{model_name}'),) +@python_2_unicode_compatible class TemplatePermissionChecker(object): ''' Can be used in the template like: @@ -342,6 +344,12 @@ class TemplatePermissionChecker(object): def __nonzero__(self): # if no view is bound we will return false, since we don't know which # permission to check we stay save in disallowing the access + return self._cast_bool() + + def __bool__(self): + return self._cast_bool() + + def _cast_bool(self): if self._view is None: return False if self._obj is None: @@ -349,10 +357,11 @@ class TemplatePermissionChecker(object): else: return self._view.has_permission(self._obj) - def __unicode__(self): + + def __str__(self): if self._view is None: return '' - return unicode(bool(self)) + return force_text(bool(self)) def create_view_permissions(app, created_models, verbosity, **kwargs): diff --git a/djadmin2/renderers.py b/djadmin2/renderers.py index a48c7e5..0ead56f 100644 --- a/djadmin2/renderers.py +++ b/djadmin2/renderers.py @@ -10,6 +10,7 @@ from datetime import date, time, datetime from django.db import models from django.utils import formats, timezone +from django.utils.encoding import force_text from django.template.loader import render_to_string from djadmin2 import settings @@ -60,10 +61,10 @@ def title_renderer(value, field): :type value: str or unicode :param field: The model field instance :type field: django.db.models.fields.Field - :rtype: unicode + :rtype: unicode or str """ - return unicode(value).title() + return force_text(value).title() def number_renderer(value, field): diff --git a/djadmin2/tests/__init__.py b/djadmin2/tests/__init__.py index f0a8411..e69de29 100644 --- a/djadmin2/tests/__init__.py +++ b/djadmin2/tests/__init__.py @@ -1,8 +0,0 @@ -from test_admin2tags import * -from test_types import * -from test_utils import * -from test_views import * -from test_core import * -from test_actions import * -from test_auth_admin import * -from test_renderers import * diff --git a/djadmin2/tests/test_core.py b/djadmin2/tests/test_core.py index ab97c86..fe091cd 100644 --- a/djadmin2/tests/test_core.py +++ b/djadmin2/tests/test_core.py @@ -9,7 +9,7 @@ from ..types import ModelAdmin2 from ..core import Admin2 -class Thing(models.Model): +class SmallThing(models.Model): pass @@ -21,20 +21,20 @@ class Admin2Test(TestCase): self.admin2 = Admin2() def test_register(self): - self.admin2.register(Thing) - self.assertTrue(isinstance(self.admin2.registry[Thing], ModelAdmin2)) + self.admin2.register(SmallThing) + self.assertTrue(isinstance(self.admin2.registry[SmallThing], ModelAdmin2)) def test_register_error(self): - self.admin2.register(Thing) - self.assertRaises(ImproperlyConfigured, self.admin2.register, Thing) + self.admin2.register(SmallThing) + self.assertRaises(ImproperlyConfigured, self.admin2.register, SmallThing) def test_deregister(self): - self.admin2.register(Thing) - self.admin2.deregister(Thing) - self.assertTrue(Thing not in self.admin2.registry) + self.admin2.register(SmallThing) + self.admin2.deregister(SmallThing) + self.assertTrue(SmallThing not in self.admin2.registry) def test_deregister_error(self): - self.assertRaises(ImproperlyConfigured, self.admin2.deregister, Thing) + self.assertRaises(ImproperlyConfigured, self.admin2.deregister, SmallThing) def test_register_app_verbose_name(self): self.admin2.register_app_verbose_name(APP_LABEL, APP_VERBOSE_NAME) @@ -65,7 +65,7 @@ class Admin2Test(TestCase): ) def test_get_urls(self): - self.admin2.register(Thing) + self.admin2.register(SmallThing) self.assertEquals(8, len(self.admin2.get_urls())) def test_default_entries(self): diff --git a/djadmin2/tests/test_renderers.py b/djadmin2/tests/test_renderers.py index d1689ac..abf5429 100644 --- a/djadmin2/tests/test_renderers.py +++ b/djadmin2/tests/test_renderers.py @@ -7,12 +7,13 @@ from decimal import Decimal from django.test import TestCase from django.db import models from django.utils.translation import activate +from django.utils import six from .. import renderers class RendererTestModel(models.Model): - decimal = models.DecimalField(decimal_places=5) + decimal = models.DecimalField(decimal_places=5, max_digits=10) class BooleanRendererTest(TestCase): @@ -57,16 +58,16 @@ class DatetimeRendererTest(TestCase): def test_time_german(self): activate('de') - out = self.renderer(dt.time(13, 37, 01), None) + out = self.renderer(dt.time(13, 37, 1), None) self.assertEqual('13:37:01', out) def test_time_chinese(self): activate('zh') - out = self.renderer(dt.time(13, 37, 01), None) + out = self.renderer(dt.time(13, 37, 1), None) self.assertEqual('1:37 p.m.', out) def test_datetime(self): - out = self.renderer(dt.datetime(2013, 7, 6, 13, 37, 01), None) + out = self.renderer(dt.datetime(2013, 7, 6, 13, 37, 1), None) self.assertEqual('July 6, 2013, 1:37 p.m.', out) # TODO test timezone localization @@ -105,7 +106,10 @@ class NumberRendererTest(TestCase): def testEndlessFloat(self): out = self.renderer(1.0/3, None) - self.assertEqual('0.333333333333', out) + if six.PY2: + self.assertEqual('0.333333333333', out) + else: + self.assertEqual('0.3333333333333333', out) def testPlainDecimal(self): number = '0.123456789123456789123456789' diff --git a/djadmin2/tests/test_types.py b/djadmin2/tests/test_types.py index 288fd97..e36ac2e 100644 --- a/djadmin2/tests/test_types.py +++ b/djadmin2/tests/test_types.py @@ -40,7 +40,7 @@ class ImmutableAdminFactoryTests(TestCase): self.immutable_admin.d -class Thing(models.Model): +class BigThing(models.Model): pass @@ -59,7 +59,7 @@ class ModelAdminTest(TestCase): ) def test_get_index_kwargs(self): - admin_instance = ModelAdmin2(Thing, Admin2) + admin_instance = ModelAdmin2(BigThing, Admin2) self.assertIn( 'paginate_by', admin_instance.get_index_kwargs().keys() diff --git a/djadmin2/tests/test_utils.py b/djadmin2/tests/test_utils.py index 8020ee9..6c69d3e 100644 --- a/djadmin2/tests/test_utils.py +++ b/djadmin2/tests/test_utils.py @@ -1,5 +1,6 @@ from django.db import models from django.test import TestCase +from django.utils import six from .. import utils from ..views import IndexView @@ -154,10 +155,18 @@ class UtilsTest(TestCase): def __unicode__(self): return "unicode" - self.assertEquals( - utils.get_attr(Klass(), "__str__"), - "unicode" - ) + if six.PY2: + self.assertEquals( + utils.get_attr(Klass(), "__str__"), + "unicode" + ) + else: + self.assertEquals( + utils.get_attr(Klass(), "__str__"), + "str" + ) + + def test_get_attr(self): class Klass(object): diff --git a/djadmin2/types.py b/djadmin2/types.py index fc15e98..766e2aa 100644 --- a/djadmin2/types.py +++ b/djadmin2/types.py @@ -224,7 +224,7 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)): 'Cannot instantiate admin view "{}.{}". ' 'The error that got raised was: {}'.format( self.__class__.__name__, admin_view.name, e)) - raise new_exception, None, trace + raise (new_exception, None, trace) pattern_list.append( url( regex=admin_view.url, diff --git a/djadmin2/utils.py b/djadmin2/utils.py index 8823f78..9321270 100644 --- a/djadmin2/utils.py +++ b/djadmin2/utils.py @@ -6,7 +6,7 @@ from django.db.models import ManyToManyRel from django.db.models.deletion import Collector from django.db.models.related import RelatedObject from django.utils import six - +from django.utils.encoding import force_bytes, force_text def lookup_needs_distinct(opts, lookup_path): """ @@ -89,7 +89,10 @@ def get_attr(obj, attr): and the __str__ attribute. """ if attr == '__str__': - value = unicode(obj) + if six.PY2: + value = unicode(obj) + else: + value = str(obj) else: attribute = getattr(obj, attr) value = attribute() if callable(attribute) else attribute @@ -181,3 +184,10 @@ def quote(s): if c in """:/_#?;@&=+$,"<>%\\""": res[i] = '_%02X' % ord(c) return ''.join(res) + + +def type_str(text): + if six.PY2: + return force_bytes(text) + else: + return force_text(text) \ No newline at end of file diff --git a/djadmin2/views.py b/djadmin2/views.py index c50e09a..76b2d41 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -2,9 +2,11 @@ from __future__ import division, absolute_import, unicode_literals import operator +from functools import reduce + from datetime import datetime -from django.contrib.auth import get_user_model +from django.conf import settings from django.contrib.auth.forms import (PasswordChangeForm, AdminPasswordChangeForm) from django.contrib.auth.views import (logout as auth_logout, @@ -19,16 +21,14 @@ from django.utils.encoding import force_text from django.utils.text import capfirst from django.utils.translation import ugettext_lazy from django.views import generic - import extra_views from . import permissions, utils from .forms import AdminAuthenticationForm -from .models import LogEntry from .viewmixins import Admin2Mixin, AdminModel2Mixin, Admin2ModelFormMixin from .filters import build_list_filter, build_date_filter - +from .models import LogEntry class AdminView(object): @@ -292,14 +292,14 @@ class ModelListView(AdminModel2Mixin, generic.ListView): return context def _format_years(self, context): - years = context['object_list'].dates('published_date', 'year') + years = self._qs_date_or_datetime(context['object_list'], 'year') if len(years) == 1: return self._format_months(context) else: return [ (("?year=%s" % year.strftime("%Y")), year.strftime("%Y")) for year in - context['object_list'].dates('published_date', 'year') + self._qs_date_or_datetime(context['object_list'], 'year') ] def _format_months(self, context): @@ -310,7 +310,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView): ), date.strftime("%B %Y") ) for date in - context["object_list"].dates('published_date', 'month') + self._qs_date_or_datetime(context['object_list'], 'month') ] def _format_days(self, context): @@ -323,9 +323,16 @@ class ModelListView(AdminModel2Mixin, generic.ListView): ), date.strftime("%B %d") ) for date in - context["object_list"].dates('published_date', 'day') + self._qs_date_or_datetime(context['object_list'], 'day') ] + def _qs_date_or_datetime(self, object_list, type): + if isinstance(self.model._meta.get_field(self.model_admin.date_hierarchy), models.DateTimeField): + qs = object_list.datetimes(self.model_admin.date_hierarchy, type) + else: + qs = object_list.dates(self.model_admin.date_hierarchy, type) + return qs + def get_success_url(self): view_name = 'admin2:{}_{}_index'.format( self.app_label, self.model_name) @@ -511,7 +518,7 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView): default_template_name = 'auth/password_change_form.html' form_class = AdminPasswordChangeForm admin_form_class = PasswordChangeForm - model = get_user_model() + model = settings.AUTH_USER_MODEL success_url = reverse_lazy('admin2:password_change_done') def get_form_kwargs(self, **kwargs): @@ -529,6 +536,9 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView): return self.admin_form_class return super(PasswordChangeView, self).get_form_class() + def get_queryset(self): + from django.contrib.auth import get_user_model + return get_user_model()._default_manager.all() class PasswordChangeDoneView(Admin2Mixin, generic.TemplateView): diff --git a/example/blog/models.py b/example/blog/models.py index e3503f0..ff2ebb6 100644 --- a/example/blog/models.py +++ b/example/blog/models.py @@ -7,13 +7,14 @@ from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ +@python_2_unicode_compatible class Post(models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) body = models.TextField(verbose_name=_('body')) published = models.BooleanField(default=False, verbose_name=_('published')) published_date = models.DateField(blank=True, null=True) - def __unicode__(self): + def __str__(self): return self.title class Meta: @@ -21,11 +22,13 @@ class Post(models.Model): verbose_name_plural = _('posts') +@python_2_unicode_compatible class Comment(models.Model): - post = models.ForeignKey(Post, verbose_name=_('post'), related_name="comments") + post = models.ForeignKey( + Post, verbose_name=_('post'), related_name="comments") body = models.TextField(verbose_name=_('body')) - def __unicode__(self): + def __str__(self): return self.body class Meta: @@ -33,7 +36,7 @@ class Comment(models.Model): verbose_name_plural = _('comments') -#### Models needed for testing NestedObjects +# Models needed for testing NestedObjects @python_2_unicode_compatible class Count(models.Model): diff --git a/example/blog/tests/__init__.py b/example/blog/tests/__init__.py index 8a7bd1e..e69de29 100644 --- a/example/blog/tests/__init__.py +++ b/example/blog/tests/__init__.py @@ -1,11 +0,0 @@ -# make sure that everything is setup for tests. Django 1.6 doesn't necessarily -# load the urls.py before the tests are run. -import example.urls - -from test_apiviews import * -from test_builtin_api_resources import * -from test_permissions import * -from test_modelforms import * -from test_views import * -from test_nestedobjects import * -from test_filters import * diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 2f3cbe2..aca0e8d 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -1,9 +1,11 @@ +from __future__ import unicode_literals from django.contrib.auth.models import AnonymousUser, User from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.test import TestCase from django.test.client import RequestFactory -from django.utils import simplejson as json +from django.utils.encoding import force_text +import json from djadmin2 import apiviews @@ -69,7 +71,7 @@ class ListCreateAPIViewTest(APITestCase): response.render() self.assertEqual(response.status_code, 200) - self.assertIn('"__str__": "Foo"', response.content) + self.assertIn('"__unicode__": "Foo"', force_text(response.content)) def test_pagination(self): request = self.factory.get(reverse('admin2:blog_post_api_list')) @@ -81,7 +83,7 @@ class ListCreateAPIViewTest(APITestCase): response.render() self.assertEqual(response.status_code, 200) - data = json.loads(response.content) + data = json.loads(force_text(response.content)) self.assertEqual(data['count'], 0) # next and previous fields exist, but are null because we have no # content diff --git a/example/blog/tests/test_filters.py b/example/blog/tests/test_filters.py index 0896543..f3a8986 100644 --- a/example/blog/tests/test_filters.py +++ b/example/blog/tests/test_filters.py @@ -2,7 +2,6 @@ # vim:fenc=utf-8 from django.test import TestCase -from django.contrib.auth import get_user_model from django.test.client import RequestFactory from django.core.urlresolvers import reverse @@ -13,33 +12,30 @@ import djadmin2.filters as djadmin2_filters import django_filters - -class PostAdminSimple(djadmin2.ModelAdmin2): - list_filter = ['published', ] - - -class PostAdminWithFilterInstances(djadmin2.ModelAdmin2): - list_filter = [ - django_filters.BooleanFilter(name='published'), - ] - - -class FS(django_filters.FilterSet): - class Meta: - model = Post - fields = ['published'] - - -class PostAdminWithFilterSetInst(djadmin2.ModelAdmin2): - list_filter = FS - - class ListFilterBuilderTest(TestCase): def setUp(self): self.rf = RequestFactory() def test_filter_building(self): + class PostAdminSimple(djadmin2.ModelAdmin2): + list_filter = ['published', ] + + + class PostAdminWithFilterInstances(djadmin2.ModelAdmin2): + list_filter = [ + django_filters.BooleanFilter(name='published'), + ] + + class FS(django_filters.FilterSet): + class Meta: + model = Post + fields = ['published'] + + + class PostAdminWithFilterSetInst(djadmin2.ModelAdmin2): + list_filter = FS + Post.objects.create(title="post_1_title", body="body") Post.objects.create(title="post_2_title", body="another body") request = self.rf.get(reverse("admin2:dashboard")) diff --git a/example/blog/tests/test_modelforms.py b/example/blog/tests/test_modelforms.py index 9e48b60..6845d47 100644 --- a/example/blog/tests/test_modelforms.py +++ b/example/blog/tests/test_modelforms.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import forms from django.test import TestCase @@ -8,6 +10,7 @@ from ..models import Post class ModelFormFactoryTest(TestCase): + def test_modelform_factory(self): form_class = modelform_factory(Post) self.assertTrue(form_class) @@ -16,8 +19,9 @@ class ModelFormFactoryTest(TestCase): class GetFloppyformWidgetTest(TestCase): + def assertExpectWidget(self, instance, new_class_, - equal_attributes=None, new_attributes=None): + equal_attributes=None, new_attributes=None): new_instance = floppify_widget(instance) self.assertEqual(new_instance.__class__, new_class_) if equal_attributes: @@ -33,8 +37,8 @@ class GetFloppyformWidgetTest(TestCase): old_attr = getattr(instance, attribute) new_attr = getattr(new_instance, attribute) self.assertEqual(old_attr, new_attr, - 'Original widget\'s attribute was not copied: %r != %r' % - (old_attr, new_attr)) + 'Original widget\'s attribute was not copied: %r != %r' % + (old_attr, new_attr)) if new_attributes: for attribute, value in new_attributes.items(): self.assertTrue( @@ -43,8 +47,8 @@ class GetFloppyformWidgetTest(TestCase): 'generated widget %r' % (attribute, new_instance)) new_attr = getattr(new_instance, attribute) self.assertEqual(new_attr, value, - 'Generated widget\'s attribute is not as expected: ' - '%r != %r' % (new_attr, value)) + 'Generated widget\'s attribute is not as expected: ' + '%r != %r' % (new_attr, value)) def test_created_widget_doesnt_leak_attributes_into_original_widget(self): widget = forms.TextInput() @@ -146,7 +150,7 @@ class GetFloppyformWidgetTest(TestCase): widget, floppyforms.widgets.ClearableFileInput, ['initial_text', 'input_text', 'clear_checkbox_label', - 'template_with_initial', 'template_with_clear']) + 'template_with_initial', 'template_with_clear']) def test_textarea_widget(self): self.assertExpectWidget( @@ -222,7 +226,7 @@ class GetFloppyformWidgetTest(TestCase): forms.widgets.NullBooleanSelect(), floppyforms.widgets.NullBooleanSelect, ('choices', 'allow_multiple_selected',)) - + widget = forms.widgets.NullBooleanSelect() widget.choices = list(widget.choices) @@ -341,15 +345,15 @@ class GetFloppyformWidgetTest(TestCase): widget = forms.extras.widgets.SelectDateWidget( attrs={'attribute': 'value'}, - years=[2010, 2011, 2012, 2013], - required=False) + years=[2010, 2011, 2012, 2013]) self.assertExpectWidget( widget, floppyforms.widgets.SelectDateWidget, - ('attrs', 'years', 'required')) + ('attrs', 'years')) class ModelFormTest(TestCase): + def test_custom_base_form(self): class MyForm(forms.ModelForm): pass @@ -411,6 +415,7 @@ class ModelFormTest(TestCase): class FieldWidgetTest(TestCase): + def test_dont_overwrite_none_default_widget(self): # we don't create the floppyform EmailInput for the email field here # since we have overwritten the default widget. However we replace the diff --git a/example/blog/tests/test_views.py b/example/blog/tests/test_views.py index 9d31b97..3301908 100644 --- a/example/blog/tests/test_views.py +++ b/example/blog/tests/test_views.py @@ -1,18 +1,22 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from datetime import datetime from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from django.core.urlresolvers import reverse from django.test import TestCase, Client +from django.utils.encoding import force_text from ..models import Post, Comment class BaseIntegrationTest(TestCase): + """ Base TestCase for integration tests. """ + def setUp(self): self.client = Client() self.user = get_user_model()(username='user', is_staff=True, @@ -23,12 +27,14 @@ class BaseIntegrationTest(TestCase): class AdminIndexTest(BaseIntegrationTest): + def test_view_ok(self): response = self.client.get(reverse("admin2:dashboard")) self.assertContains(response, reverse("admin2:blog_post_index")) class UserListTest(BaseIntegrationTest): + def test_search_users_m2m_group(self): # This test should cause the distinct search path to exectue group = Group.objects.create(name="Test Group") @@ -40,6 +46,7 @@ class UserListTest(BaseIntegrationTest): class CommentListTest(BaseIntegrationTest): + def test_search_comments(self): # Test search across Foriegn Keys post_1 = Post.objects.create(title="post_1_title", body="body") @@ -49,7 +56,8 @@ class CommentListTest(BaseIntegrationTest): Comment.objects.create(body="comment_post_2", post=post_2) params = {"q": "post_1_title"} - response = self.client.get(reverse("admin2:blog_comment_index"), params) + response = self.client.get( + reverse("admin2:blog_comment_index"), params) self.assertContains(response, "comment_post_1_a") self.assertContains(response, "comment_post_1_b") self.assertNotContains(response, "comment_post_2") @@ -62,6 +70,7 @@ class CommentListTest(BaseIntegrationTest): class PostListTest(BaseIntegrationTest): + def _create_posts(self): Post.objects.bulk_create([ Post( @@ -129,7 +138,8 @@ class PostListTest(BaseIntegrationTest): def test_actions_displayed(self): response = self.client.get(reverse("admin2:blog_post_index")) - self.assertInHTML('Delete selected items', response.content) + self.assertInHTML( + 'Delete selected items', force_text(response.content)) def test_actions_displayed_twice(self): # If actions_on_top and actions_on_bottom are both set @@ -139,27 +149,33 @@ class PostListTest(BaseIntegrationTest): def test_delete_selected_post(self): post = Post.objects.create(title="A Post Title", body="body") - params = {'action': 'DeleteSelectedAction', 'selected_model_pk': str(post.pk)} + params = {'action': 'DeleteSelectedAction', + 'selected_model_pk': str(post.pk)} response = self.client.post(reverse("admin2:blog_post_index"), params) # caution : uses pluralization - self.assertInHTML('

Are you sure you want to delete the selected post? The following item will be deleted:

', response.content) + self.assertInHTML( + '

Are you sure you want to delete the selected post? The following item will be deleted:

', force_text(response.content)) def test_delete_selected_post_confirmation(self): post = Post.objects.create(title="A Post Title", body="body") - params = {'action': 'DeleteSelectedAction', 'selected_model_pk': str(post.pk), 'confirmed': 'yes'} + params = {'action': 'DeleteSelectedAction', + 'selected_model_pk': str(post.pk), 'confirmed': 'yes'} response = self.client.post(reverse("admin2:blog_post_index"), params) self.assertRedirects(response, reverse("admin2:blog_post_index")) def test_delete_selected_post_none_selected(self): Post.objects.create(title="A Post Title", body="body") params = {'action': 'DeleteSelectedAction'} - response = self.client.post(reverse("admin2:blog_post_index"), params, follow=True) - self.assertContains(response, "Items must be selected in order to perform actions on them. No items have been changed.") + response = self.client.post( + reverse("admin2:blog_post_index"), params, follow=True) + self.assertContains( + response, "Items must be selected in order to perform actions on them. No items have been changed.") def test_search_posts(self): Post.objects.create(title="A Post Title", body="body") Post.objects.create(title="Another Post Title", body="body") - Post.objects.create(title="Post With Keyword In Body", body="another post body") + Post.objects.create( + title="Post With Keyword In Body", body="another post body") params = {"q": "another"} response = self.client.get(reverse("admin2:blog_post_index"), params) self.assertContains(response, "Another Post Title") @@ -167,12 +183,14 @@ class PostListTest(BaseIntegrationTest): self.assertNotContains(response, "A Post Title") def test_renderer_title(self): - Post.objects.create(title='a lowercase title', body='body', published=False) + Post.objects.create( + title='a lowercase title', body='body', published=False) response = self.client.get(reverse('admin2:blog_post_index')) self.assertContains(response, 'A Lowercase Title') def test_renderer_body(self): - Post.objects.create(title='title', body='a lowercase body', published=False) + Post.objects.create( + title='title', body='a lowercase body', published=False) response = self.client.get(reverse('admin2:blog_post_index')) self.assertContains(response, 'a lowercase body') @@ -311,7 +329,8 @@ class PostListTestCustomAction(BaseIntegrationTest): def test_publish_action_displayed_in_list(self): response = self.client.get(reverse("admin2:blog_post_index")) - self.assertInHTML('Publish selected items', response.content) + self.assertInHTML( + 'Publish selected items', force_text(response.content)) def test_publish_selected_items(self): post = Post.objects.create(title="A Post Title", @@ -329,7 +348,8 @@ class PostListTestCustomAction(BaseIntegrationTest): def test_unpublish_action_displayed_in_list(self): response = self.client.get(reverse("admin2:blog_post_index")) - self.assertInHTML('Unpublish selected items', response.content) + self.assertInHTML( + 'Unpublish selected items', force_text(response.content)) def test_unpublish_selected_items(self): post = Post.objects.create(title="A Post Title", @@ -346,6 +366,7 @@ class PostListTestCustomAction(BaseIntegrationTest): class PostDetailViewTest(BaseIntegrationTest): + def test_view_ok(self): post = Post.objects.create(title="A Post Title", body="body") response = self.client.get(reverse("admin2:blog_post_detail", @@ -354,9 +375,11 @@ class PostDetailViewTest(BaseIntegrationTest): class PostCreateViewTest(BaseIntegrationTest): + def test_view_ok(self): response = self.client.get(reverse("admin2:blog_post_create")) - self.assertNotIn('''enctype="multipart/form-data"''', response.content) + self.assertNotIn( + '''enctype="multipart/form-data"''', force_text(response.content)) self.assertEqual(response.status_code, 200) def test_create_post(self): @@ -422,6 +445,7 @@ class PostCreateViewTest(BaseIntegrationTest): class PostDeleteViewTest(BaseIntegrationTest): + def test_view_ok(self): post = Post.objects.create(title="A Post Title", body="body") response = self.client.get(reverse("admin2:blog_post_delete", @@ -437,9 +461,11 @@ class PostDeleteViewTest(BaseIntegrationTest): class PostDeleteActionTest(BaseIntegrationTest): + """ Tests the behaviour of the 'Delete selected items' action. """ + def test_confirmation_page(self): p1 = Post.objects.create(title="A Post Title", body="body") p2 = Post.objects.create(title="A Post Title", body="body") diff --git a/example/example/settings.py b/example/example/settings.py index 161a76f..7768d08 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -130,6 +130,7 @@ INSTALLED_APPS = ( 'djadmin2.themes.djadmin2theme_default', 'blog', 'files', + 'polls' ) # A sample logging configuration. The only tangible logging @@ -162,25 +163,4 @@ LOGGING = { } -ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" - - -########## TOOLBAR CONFIGURATION -# See: https://github.com/django-debug-toolbar/django-debug-toolbar#installation -INSTALLED_APPS += ( - 'debug_toolbar', -) - -# See: https://github.com/django-debug-toolbar/django-debug-toolbar#installation -INTERNAL_IPS = ('127.0.0.1',) - -# See: https://github.com/django-debug-toolbar/django-debug-toolbar#installation -MIDDLEWARE_CLASSES += ( - 'debug_toolbar.middleware.DebugToolbarMiddleware', -) - -DEBUG_TOOLBAR_CONFIG = { - 'INTERCEPT_REDIRECTS': False, - 'SHOW_TEMPLATE_CONTEXT': True, -} -########## END TOOLBAR CONFIGURATION +ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" \ No newline at end of file diff --git a/example/files/models.py b/example/files/models.py index 701b4fa..b373326 100644 --- a/example/files/models.py +++ b/example/files/models.py @@ -2,14 +2,17 @@ from __future__ import division, absolute_import, unicode_literals from django.db import models +from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ +@python_2_unicode_compatible class CaptionedFile(models.Model): caption = models.CharField(max_length=200, verbose_name=_('caption')) - publication = models.FileField(upload_to='media', verbose_name=_('Uploaded File')) + publication = models.FileField( + upload_to='media', verbose_name=_('Uploaded File')) - def __unicode__(self): + def __str__(self): return self.caption class Meta: @@ -17,11 +20,13 @@ class CaptionedFile(models.Model): verbose_name_plural = _('Captioned Files') +@python_2_unicode_compatible class UncaptionedFile(models.Model): - publication = models.FileField(upload_to='media', verbose_name=_('Uploaded File')) + publication = models.FileField( + upload_to='media', verbose_name=_('Uploaded File')) - def __unicode__(self): - return unicode(self.publication) + def __str__(self): + return self.publication class Meta: verbose_name = _('Uncaptioned File') diff --git a/example/files/tests/__init__.py b/example/files/tests/__init__.py index efb306e..e69de29 100644 --- a/example/files/tests/__init__.py +++ b/example/files/tests/__init__.py @@ -1,2 +0,0 @@ -from test_models import * -from test_views import * diff --git a/example/files/tests/test_views.py b/example/files/tests/test_views.py index 82859eb..d36a456 100644 --- a/example/files/tests/test_views.py +++ b/example/files/tests/test_views.py @@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from django.test import TestCase, Client from django.utils import timezone +from django.utils.encoding import force_text from ..models import CaptionedFile @@ -12,9 +13,11 @@ fixture_file = path.join(fixture_dir, 'pubtest.txt') class BaseIntegrationTest(TestCase): + """ Base TestCase for integration tests. """ + def setUp(self): self.client = Client() self.user = get_user_model()(username='user', is_staff=True, @@ -25,55 +28,77 @@ class BaseIntegrationTest(TestCase): class AdminIndexTest(BaseIntegrationTest): + def test_view_ok(self): response = self.client.get(reverse("admin2:dashboard")) - self.assertContains(response, reverse("admin2:files_captionedfile_index")) + self.assertContains( + response, reverse("admin2:files_captionedfile_index")) class CaptionedFileListTest(BaseIntegrationTest): + def test_view_ok(self): - captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) + captioned_file = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) response = self.client.get(reverse("admin2:files_captionedfile_index")) self.assertContains(response, captioned_file.caption) def test_actions_displayed(self): response = self.client.get(reverse("admin2:files_captionedfile_index")) - self.assertInHTML('Delete selected items', response.content) + self.assertInHTML( + 'Delete selected items', force_text(response.content)) def test_delete_selected_captioned_file(self): - captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) - params = {'action': 'DeleteSelectedAction', 'selected_model_pk': str(captioned_file.pk)} - response = self.client.post(reverse("admin2:files_captionedfile_index"), params) - self.assertInHTML('

Are you sure you want to delete the selected Captioned File? The following item will be deleted:

', response.content) + captioned_file = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) + params = {'action': 'DeleteSelectedAction', + 'selected_model_pk': str(captioned_file.pk)} + response = self.client.post( + reverse("admin2:files_captionedfile_index"), params) + self.assertInHTML( + '

Are you sure you want to delete the selected Captioned File? The following item will be deleted:

', force_text(response.content)) def test_delete_selected_captioned_file_confirmation(self): - captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) - params = {'action': 'DeleteSelectedAction', 'selected_model_pk': str(captioned_file.pk), 'confirmed': 'yes'} - response = self.client.post(reverse("admin2:files_captionedfile_index"), params) - self.assertRedirects(response, reverse("admin2:files_captionedfile_index")) + captioned_file = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) + params = {'action': 'DeleteSelectedAction', 'selected_model_pk': str( + captioned_file.pk), 'confirmed': 'yes'} + response = self.client.post( + reverse("admin2:files_captionedfile_index"), params) + self.assertRedirects( + response, reverse("admin2:files_captionedfile_index")) def test_delete_selected_captioned_file_none_selected(self): - CaptionedFile.objects.create(caption="some file", publication=fixture_file) + CaptionedFile.objects.create( + caption="some file", publication=fixture_file) params = {'action': 'DeleteSelectedAction'} - response = self.client.post(reverse("admin2:files_captionedfile_index"), params, follow=True) - self.assertContains(response, "Items must be selected in order to perform actions on them. No items have been changed.") + response = self.client.post( + reverse("admin2:files_captionedfile_index"), params, follow=True) + self.assertContains( + response, "Items must be selected in order to perform actions on them. No items have been changed.") class CaptionedFileDetailViewTest(BaseIntegrationTest): + def test_view_ok(self): - captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) - response = self.client.get(reverse("admin2:files_captionedfile_detail", args=(captioned_file.pk, ))) + captioned_file = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) + response = self.client.get( + reverse("admin2:files_captionedfile_detail", args=(captioned_file.pk, ))) self.assertContains(response, captioned_file.caption) class CaptionedFileCreateViewTest(BaseIntegrationTest): + def test_view_ok(self): - response = self.client.get(reverse("admin2:files_captionedfile_create")) - self.assertIn('''enctype="multipart/form-data"''', response.content) + response = self.client.get( + reverse("admin2:files_captionedfile_create")) + self.assertIn( + 'enctype="multipart/form-data"', force_text(response.content)) self.assertEqual(response.status_code, 200) def test_create_captioned_file(self): - with open(fixture_file, 'r') as fp: + with open(fixture_file, 'rb') as fp: params = { "caption": "some file", "publication": fp, @@ -81,15 +106,17 @@ class CaptionedFileCreateViewTest(BaseIntegrationTest): response = self.client.post(reverse("admin2:files_captionedfile_create"), params, follow=True) - self.assertTrue(CaptionedFile.objects.filter(caption="some file").exists()) - self.assertRedirects(response, reverse("admin2:files_captionedfile_index")) + self.assertTrue( + CaptionedFile.objects.filter(caption="some file").exists()) + self.assertRedirects( + response, reverse("admin2:files_captionedfile_index")) def test_save_and_add_another_redirects_to_create(self): """ Tests that choosing 'Save and add another' from the model create page redirects the user to the model create page. """ - with open(fixture_file, 'r') as fp: + with open(fixture_file, 'rb') as fp: params = { "caption": "some file", "publication": fp, @@ -97,15 +124,17 @@ class CaptionedFileCreateViewTest(BaseIntegrationTest): } response = self.client.post(reverse("admin2:files_captionedfile_create"), params) - self.assertTrue(CaptionedFile.objects.filter(caption="some file").exists()) - self.assertRedirects(response, reverse("admin2:files_captionedfile_create")) + self.assertTrue( + CaptionedFile.objects.filter(caption="some file").exists()) + self.assertRedirects( + response, reverse("admin2:files_captionedfile_create")) def test_save_and_continue_editing_redirects_to_update(self): """ Tests that choosing "Save and continue editing" redirects the user to the model update form. """ - with open(fixture_file, 'r') as fp: + with open(fixture_file, 'rb') as fp: params = { "caption": "some file", "publication": fp, @@ -119,27 +148,36 @@ class CaptionedFileCreateViewTest(BaseIntegrationTest): class CaptionedFileDeleteViewTest(BaseIntegrationTest): + def test_view_ok(self): - captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) + captioned_file = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) response = self.client.get(reverse("admin2:files_captionedfile_delete", args=(captioned_file.pk, ))) self.assertContains(response, captioned_file.caption) def test_delete_captioned_file(self): - captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) + captioned_file = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) response = self.client.post(reverse("admin2:files_captionedfile_delete", args=(captioned_file.pk, ))) - self.assertRedirects(response, reverse("admin2:files_captionedfile_index")) - self.assertFalse(CaptionedFile.objects.filter(pk=captioned_file.pk).exists()) + self.assertRedirects( + response, reverse("admin2:files_captionedfile_index")) + self.assertFalse( + CaptionedFile.objects.filter(pk=captioned_file.pk).exists()) class FileDeleteActionTest(BaseIntegrationTest): + """ Tests the behaviour of the 'Delete selected items' action. """ + def test_confirmation_page(self): - cf1 = captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) - cf2 = captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) + cf1 = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) + cf2 = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) params = { 'action': 'DeleteSelectedAction', 'selected_model_pk': [cf1.pk, cf2.pk] @@ -150,8 +188,10 @@ class FileDeleteActionTest(BaseIntegrationTest): self.assertContains(response, cf2.caption) def test_results_page(self): - cf1 = captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) - cf2 = captioned_file = CaptionedFile.objects.create(caption="some file", publication=fixture_file) + cf1 = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) + cf2 = CaptionedFile.objects.create( + caption="some file", publication=fixture_file) params = { 'action': 'DeleteSelectedAction', 'selected_model_pk': [cf1.pk, cf2.pk], diff --git a/example/__init__.py b/example/polls/__init__.py similarity index 100% rename from example/__init__.py rename to example/polls/__init__.py diff --git a/example2/polls/admin.py b/example/polls/admin.py similarity index 100% rename from example2/polls/admin.py rename to example/polls/admin.py diff --git a/example2/polls/admin2.py b/example/polls/admin2.py similarity index 100% rename from example2/polls/admin2.py rename to example/polls/admin2.py diff --git a/example2/polls/locale/de/LC_MESSAGES/django.mo b/example/polls/locale/de/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/de/LC_MESSAGES/django.mo rename to example/polls/locale/de/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/de/LC_MESSAGES/django.po b/example/polls/locale/de/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/de/LC_MESSAGES/django.po rename to example/polls/locale/de/LC_MESSAGES/django.po diff --git a/example2/polls/locale/en/LC_MESSAGES/django.mo b/example/polls/locale/en/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/en/LC_MESSAGES/django.mo rename to example/polls/locale/en/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/en/LC_MESSAGES/django.po b/example/polls/locale/en/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/en/LC_MESSAGES/django.po rename to example/polls/locale/en/LC_MESSAGES/django.po diff --git a/example2/polls/locale/fr/LC_MESSAGES/django.mo b/example/polls/locale/fr/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/fr/LC_MESSAGES/django.mo rename to example/polls/locale/fr/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/fr/LC_MESSAGES/django.po b/example/polls/locale/fr/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/fr/LC_MESSAGES/django.po rename to example/polls/locale/fr/LC_MESSAGES/django.po diff --git a/example2/polls/locale/it/LC_MESSAGES/django.mo b/example/polls/locale/it/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/it/LC_MESSAGES/django.mo rename to example/polls/locale/it/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/it/LC_MESSAGES/django.po b/example/polls/locale/it/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/it/LC_MESSAGES/django.po rename to example/polls/locale/it/LC_MESSAGES/django.po diff --git a/example2/polls/locale/nl/LC_MESSAGES/django.mo b/example/polls/locale/nl/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/nl/LC_MESSAGES/django.mo rename to example/polls/locale/nl/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/nl/LC_MESSAGES/django.po b/example/polls/locale/nl/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/nl/LC_MESSAGES/django.po rename to example/polls/locale/nl/LC_MESSAGES/django.po diff --git a/example2/polls/locale/pl_PL/LC_MESSAGES/django.mo b/example/polls/locale/pl_PL/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/pl_PL/LC_MESSAGES/django.mo rename to example/polls/locale/pl_PL/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/pl_PL/LC_MESSAGES/django.po b/example/polls/locale/pl_PL/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/pl_PL/LC_MESSAGES/django.po rename to example/polls/locale/pl_PL/LC_MESSAGES/django.po diff --git a/example2/polls/locale/pt_BR/LC_MESSAGES/django.mo b/example/polls/locale/pt_BR/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/pt_BR/LC_MESSAGES/django.mo rename to example/polls/locale/pt_BR/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/pt_BR/LC_MESSAGES/django.po b/example/polls/locale/pt_BR/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/pt_BR/LC_MESSAGES/django.po rename to example/polls/locale/pt_BR/LC_MESSAGES/django.po diff --git a/example2/polls/locale/sk/LC_MESSAGES/django.mo b/example/polls/locale/sk/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/sk/LC_MESSAGES/django.mo rename to example/polls/locale/sk/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/sk/LC_MESSAGES/django.po b/example/polls/locale/sk/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/sk/LC_MESSAGES/django.po rename to example/polls/locale/sk/LC_MESSAGES/django.po diff --git a/example2/polls/locale/tl_PH/LC_MESSAGES/django.mo b/example/polls/locale/tl_PH/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/tl_PH/LC_MESSAGES/django.mo rename to example/polls/locale/tl_PH/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/tl_PH/LC_MESSAGES/django.po b/example/polls/locale/tl_PH/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/tl_PH/LC_MESSAGES/django.po rename to example/polls/locale/tl_PH/LC_MESSAGES/django.po diff --git a/example2/polls/locale/zh/LC_MESSAGES/django.mo b/example/polls/locale/zh/LC_MESSAGES/django.mo similarity index 100% rename from example2/polls/locale/zh/LC_MESSAGES/django.mo rename to example/polls/locale/zh/LC_MESSAGES/django.mo diff --git a/example2/polls/locale/zh/LC_MESSAGES/django.po b/example/polls/locale/zh/LC_MESSAGES/django.po similarity index 100% rename from example2/polls/locale/zh/LC_MESSAGES/django.po rename to example/polls/locale/zh/LC_MESSAGES/django.po diff --git a/example2/polls/models.py b/example/polls/models.py similarity index 80% rename from example2/polls/models.py rename to example/polls/models.py index 0bb467c..44cc4c2 100644 --- a/example2/polls/models.py +++ b/example/polls/models.py @@ -3,16 +3,18 @@ from __future__ import division, absolute_import, unicode_literals import datetime +from django.utils.encoding import python_2_unicode_compatible from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +@python_2_unicode_compatible class Poll(models.Model): question = models.CharField(max_length=200, verbose_name=_('question')) pub_date = models.DateTimeField(verbose_name=_('date published')) - def __unicode__(self): + def __str__(self): return self.question def was_published_recently(self): @@ -26,12 +28,14 @@ class Poll(models.Model): verbose_name_plural = _('polls') +@python_2_unicode_compatible class Choice(models.Model): poll = models.ForeignKey(Poll, verbose_name=_('poll')) - choice_text = models.CharField(max_length=200, verbose_name=_('choice text')) + choice_text = models.CharField( + max_length=200, verbose_name=_('choice text')) votes = models.IntegerField(default=0, verbose_name=_('votes')) - def __unicode__(self): + def __str__(self): return self.choice_text class Meta: diff --git a/example2/polls/templates/home.html b/example/polls/templates/home.html similarity index 100% rename from example2/polls/templates/home.html rename to example/polls/templates/home.html diff --git a/example2/example2/__init__.py b/example/polls/tests/__init__.py similarity index 100% rename from example2/example2/__init__.py rename to example/polls/tests/__init__.py diff --git a/example2/polls/tests/test_models.py b/example/polls/tests/test_models.py similarity index 100% rename from example2/polls/tests/test_models.py rename to example/polls/tests/test_models.py diff --git a/example2/polls/tests/test_views.py b/example/polls/tests/test_views.py similarity index 74% rename from example2/polls/tests/test_views.py rename to example/polls/tests/test_views.py index 05ea30e..67ab56a 100644 --- a/example2/polls/tests/test_views.py +++ b/example/polls/tests/test_views.py @@ -2,14 +2,17 @@ from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from django.test import TestCase, Client from django.utils import timezone +from django.utils.encoding import force_text from ..models import Poll class BaseIntegrationTest(TestCase): + """ Base TestCase for integration tests. """ + def setUp(self): self.client = Client() self.user = get_user_model()(username='user', is_staff=True, @@ -20,48 +23,63 @@ class BaseIntegrationTest(TestCase): class AdminIndexTest(BaseIntegrationTest): + def test_view_ok(self): response = self.client.get(reverse("admin2:dashboard")) self.assertContains(response, reverse("admin2:polls_poll_index")) class PollListTest(BaseIntegrationTest): + def test_view_ok(self): - poll = Poll.objects.create(question="some question", pub_date=timezone.now()) + poll = Poll.objects.create( + question="some question", pub_date=timezone.now()) response = self.client.get(reverse("admin2:polls_poll_index")) self.assertContains(response, poll.question) def test_actions_displayed(self): response = self.client.get(reverse("admin2:polls_poll_index")) - self.assertInHTML('Delete selected items', response.content) + self.assertInHTML( + 'Delete selected items', force_text(response.content)) def test_delete_selected_poll(self): - poll = Poll.objects.create(question="some question", pub_date=timezone.now()) - params = {'action': 'DeleteSelectedAction', 'selected_model_pk': str(poll.pk)} + poll = Poll.objects.create( + question="some question", pub_date=timezone.now()) + params = {'action': 'DeleteSelectedAction', + 'selected_model_pk': str(poll.pk)} response = self.client.post(reverse("admin2:polls_poll_index"), params) - self.assertInHTML('

Are you sure you want to delete the selected poll? All of the following items will be deleted:

', response.content) + self.assertInHTML( + '

Are you sure you want to delete the selected poll? The following item will be deleted:

', force_text(response.content)) def test_delete_selected_poll_confirmation(self): - poll = Poll.objects.create(question="some question", pub_date=timezone.now()) - params = {'action': 'DeleteSelectedAction', 'selected_model_pk': str(poll.pk), 'confirmed': 'yes'} + poll = Poll.objects.create( + question="some question", pub_date=timezone.now()) + params = {'action': 'DeleteSelectedAction', + 'selected_model_pk': str(poll.pk), 'confirmed': 'yes'} response = self.client.post(reverse("admin2:polls_poll_index"), params) self.assertRedirects(response, reverse("admin2:polls_poll_index")) def test_delete_selected_poll_none_selected(self): Poll.objects.create(question="some question", pub_date=timezone.now()) params = {'action': 'DeleteSelectedAction'} - response = self.client.post(reverse("admin2:polls_poll_index"), params, follow=True) - self.assertContains(response, "Items must be selected in order to perform actions on them. No items have been changed.") + response = self.client.post( + reverse("admin2:polls_poll_index"), params, follow=True) + self.assertContains( + response, "Items must be selected in order to perform actions on them. No items have been changed.") class PollDetailViewTest(BaseIntegrationTest): + def test_view_ok(self): - poll = Poll.objects.create(question="some question", pub_date=timezone.now()) - response = self.client.get(reverse("admin2:polls_poll_detail", args=(poll.pk, ))) + poll = Poll.objects.create( + question="some question", pub_date=timezone.now()) + response = self.client.get( + reverse("admin2:polls_poll_detail", args=(poll.pk, ))) self.assertContains(response, poll.question) class PollCreateViewTest(BaseIntegrationTest): + def test_view_ok(self): response = self.client.get(reverse("admin2:polls_poll_create")) self.assertEqual(response.status_code, 200) @@ -119,14 +137,17 @@ class PollCreateViewTest(BaseIntegrationTest): class PollDeleteViewTest(BaseIntegrationTest): + def test_view_ok(self): - poll = Poll.objects.create(question="some question", pub_date=timezone.now()) + poll = Poll.objects.create( + question="some question", pub_date=timezone.now()) response = self.client.get(reverse("admin2:polls_poll_delete", args=(poll.pk, ))) self.assertContains(response, poll.question) def test_delete_poll(self): - poll = Poll.objects.create(question="some question", pub_date=timezone.now()) + poll = Poll.objects.create( + question="some question", pub_date=timezone.now()) response = self.client.post(reverse("admin2:polls_poll_delete", args=(poll.pk, ))) self.assertRedirects(response, reverse("admin2:polls_poll_index")) @@ -134,12 +155,16 @@ class PollDeleteViewTest(BaseIntegrationTest): class PollDeleteActionTest(BaseIntegrationTest): + """ Tests the behaviour of the 'Delete selected items' action. """ + def test_confirmation_page(self): - p1 = Poll.objects.create(question="some question", pub_date=timezone.now()) - p2 = Poll.objects.create(question="some question", pub_date=timezone.now()) + p1 = Poll.objects.create( + question="some question", pub_date=timezone.now()) + p2 = Poll.objects.create( + question="some question", pub_date=timezone.now()) params = { 'action': 'DeleteSelectedAction', 'selected_model_pk': [p1.pk, p2.pk] @@ -150,8 +175,10 @@ class PollDeleteActionTest(BaseIntegrationTest): self.assertContains(response, p2.question) def test_results_page(self): - p1 = Poll.objects.create(question="some question", pub_date=timezone.now()) - p2 = Poll.objects.create(question="some question", pub_date=timezone.now()) + p1 = Poll.objects.create( + question="some question", pub_date=timezone.now()) + p2 = Poll.objects.create( + question="some question", pub_date=timezone.now()) params = { 'action': 'DeleteSelectedAction', 'selected_model_pk': [p1.pk, p2.pk], diff --git a/example2/polls/views.py b/example/polls/views.py similarity index 100% rename from example2/polls/views.py rename to example/polls/views.py diff --git a/example2/example2/settings.py b/example2/example2/settings.py deleted file mode 100644 index c59a247..0000000 --- a/example2/example2/settings.py +++ /dev/null @@ -1,188 +0,0 @@ -# Django settings for example2 project. - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -ADMINS = ( - # ('Your Name', 'your_email@example.com'), -) - -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'example2.db', - } -} - -# Hosts/domain names that are valid for this site; required if DEBUG is False -# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts -ALLOWED_HOSTS = [] - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# In a Windows environment this must be set to your system time zone. -TIME_ZONE = 'America/Chicago' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale. -USE_L10N = True - -# If you set this to False, Django will not use timezone-aware datetimes. -USE_TZ = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/var/www/example.com/media/" -MEDIA_ROOT = '' - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash. -# Examples: "http://example.com/media/", "http://media.example.com/" -MEDIA_URL = '' - -# Absolute path to the directory static files should be collected to. -# Don't put anything in this directory yourself; store your static files -# in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/var/www/example.com/static/" -STATIC_ROOT = '' - -# URL prefix for static files. -# Example: "http://example.com/static/", "http://static.example.com/" -STATIC_URL = '/static/' - -# Additional locations of static files -STATICFILES_DIRS = ( - # Put strings here, like "/home/html/static" or "C:/www/django/static". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -# List of finder classes that know how to find static files in -# various locations. -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', -) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = 'vid$84s%19vhcss+(n$*pbc=nad2oab@^2s532_iesz2f6q=(z' - -# 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.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - # Uncomment the next line for simple clickjacking protection: - # 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -ROOT_URLCONF = 'example2.urls' - -# Python dotted path to the WSGI application used by Django's runserver. -WSGI_APPLICATION = 'example2.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. -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', - 'floppyforms', - 'rest_framework', - 'djadmin2', - 'djadmin2.themes.djadmin2theme_default', - 'crispy_forms', - 'polls', -) - -try: - import django_extensions - INSTALLED_APPS += ( - 'django_extensions', - ) -except ImportError: - pass - -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error when DEBUG=False. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } -} - - -ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" - -########## TOOLBAR CONFIGURATION -# See: https://github.com/django-debug-toolbar/django-debug-toolbar#installation -INSTALLED_APPS += ( - 'debug_toolbar', -) - -# See: https://github.com/django-debug-toolbar/django-debug-toolbar#installation -INTERNAL_IPS = ('127.0.0.1',) - -# See: https://github.com/django-debug-toolbar/django-debug-toolbar#installation -MIDDLEWARE_CLASSES += ( - 'debug_toolbar.middleware.DebugToolbarMiddleware', -) - -DEBUG_TOOLBAR_CONFIG = { - 'INTERCEPT_REDIRECTS': False, - 'SHOW_TEMPLATE_CONTEXT': True, -} -########## END TOOLBAR CONFIGURATION diff --git a/example2/example2/urls.py b/example2/example2/urls.py deleted file mode 100644 index fe81b34..0000000 --- a/example2/example2/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.conf.urls import patterns, include, url -from django.contrib import admin -from django.views.generic import TemplateView - -admin.autodiscover() - -import djadmin2 - -djadmin2.default.autodiscover() - -urlpatterns = patterns('', - url(r'^admin2/', include(djadmin2.default.urls)), - url(r'^admin/', include(admin.site.urls)), - url(r'^$', TemplateView.as_view(template_name="home.html")), -) diff --git a/example2/example2/wsgi.py b/example2/example2/wsgi.py deleted file mode 100644 index 9a01d4d..0000000 --- a/example2/example2/wsgi.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -WSGI config for example2 project. - -This module contains the WSGI application used by Django's development server -and any production WSGI deployments. It should expose a module-level variable -named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover -this application via the ``WSGI_APPLICATION`` setting. - -Usually you will have the standard Django WSGI application here, but it also -might make sense to replace the whole Django WSGI application with a custom one -that later delegates to the Django one. For example, you could introduce WSGI -middleware here, or combine a Django application with an application of another -framework. - -""" -import os - -# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks -# if running multiple sites in the same mod_wsgi process. To fix this, use -# mod_wsgi daemon mode with each site in its own daemon process, or use -# os.environ["DJANGO_SETTINGS_MODULE"] = "example2.settings" -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example2.settings") - -# This application object is used by any WSGI server configured to use this -# file. This includes Django's development server, if the WSGI_APPLICATION -# setting points here. -from django.core.wsgi import get_wsgi_application -application = get_wsgi_application() - -# Apply WSGI middleware here. -# from helloworld.wsgi import HelloWorldApplication -# application = HelloWorldApplication(application) diff --git a/example2/manage.py b/example2/manage.py deleted file mode 100755 index c13d5ca..0000000 --- a/example2/manage.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example2.settings") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/example2/polls/__init__.py b/example2/polls/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/example2/polls/tests/__init__.py b/example2/polls/tests/__init__.py deleted file mode 100644 index efb306e..0000000 --- a/example2/polls/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from test_models import * -from test_views import * diff --git a/requirements.txt b/requirements.txt index 305bdc0..1482e62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,8 @@ -django>=1.5.0 -django-braces>=1.0.0 -djangorestframework>=2.3.3 -django-debug-toolbar>=0.9.4 -coverage>=3.6 -django-extra-views>=0.6.2 -django-floppyforms>=1.1 -Sphinx>=1.2b1 -django-filter>=0.6 +django-extra-views>=0.6.5 +django-braces>=1.3.0 +djangorestframework<=2.4.4 +django-floppyforms<=1.2 +django-filter>=0.7 django-crispy-forms>=1.3.2 +django-debug-toolbar>=0.9.4 +pytz==2014.7 diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..52a3f23 --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,3 @@ +-rrequirements.txt +pytest +pytest-django \ No newline at end of file diff --git a/runtests.py b/runtests.py deleted file mode 100755 index 3e58b51..0000000 --- a/runtests.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings' -exampleproject_dir = os.path.join(os.path.dirname(__file__), 'example') -sys.path.insert(0, exampleproject_dir) - -from django.test.utils import get_runner -from django.conf import settings - - -def runtests(tests=('blog', 'files', 'djadmin2')): - ''' - Takes a list as first argument, enumerating the apps and specific testcases - that should be executed. The syntax is the same as for what you would pass - to the ``django-admin.py test`` command. - - Examples:: - - # run the default test suite - runtests() - - # only run the tests from application ``blog`` - runtests(['blog']) - - # only run testcase class ``Admin2Test`` from app ``djadmin2`` - runtests(['djadmin2.Admin2Test']) - - # run all tests from application ``blog`` and the test named - # ``test_register`` on the ``djadmin2.Admin2Test`` testcase. - runtests(['djadmin2.Admin2Test.test_register', 'blog']) - ''' - TestRunner = get_runner(settings) - test_runner = TestRunner(verbosity=1, interactive=True) - failures = test_runner.run_tests(tests) - sys.exit(bool(failures)) - - -if __name__ == '__main__': - if len(sys.argv) > 1: - tests = sys.argv[1:] - runtests(tests) - else: - runtests() diff --git a/setup.py b/setup.py index 0152e3c..29d9130 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from setuptools import setup +from setuptools.command.test import test as TestCommand import re import os import sys @@ -80,6 +81,25 @@ if sys.argv[-1] == 'publish': LONG_DESCRIPTION = remove_screenshots(open('README.rst').read()) HISTORY = open('HISTORY.rst').read() +class PyTest(TestCommand): + user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = [] + + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + #import here, cause outside the eggs aren't loaded + sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'example')) + import pytest + errno = pytest.main(self.pytest_args) + sys.exit(errno) + setup( name='django-admin2', version=version, @@ -105,16 +125,21 @@ setup( license='MIT', packages=get_packages('djadmin2'), include_package_data=True, - test_suite='runtests.runtests', + #test_suite='runtests.runtests', install_requires=[ - 'django>=1.5.0', - 'django-braces>=1.0.0', - 'django-extra-views>=0.6.2', - 'djangorestframework>=2.3.3', - 'django-floppyforms>=1.1', - 'django-filter>=0.6', - 'django-crispy-forms>=1.3.2' + 'django>=1.6.0', + 'django-extra-views>=0.6.5', + 'django-braces>=1.3.0', + 'djangorestframework<=2.4.4', + 'django-floppyforms<=1.2', + 'django-filter>=0.7', + 'django-crispy-forms>=1.3.2', + 'pytz==2014.7' ], + extras_require={ + 'testing': ['pytest', 'pytest-django', 'pytest-ipdb'], + }, + cmdclass = {'test': PyTest}, zip_safe=False, ) diff --git a/tox.ini b/tox.ini index 501ac46..7df12c5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,35 +1,17 @@ [tox] # for py 3.x we are using only django 1.6.x as 1.5.x had only "experimental py3 support" -envlist = py27-dj1.4.x, py27-dj1.5.x, py27-dj1.6.x, py27-dj1.7.x, - py33-dj1.6.x, py34-dj1.6.x, py33-dj1.7.x, py34-dj1.7.x, - pypy-dj1.6.x, pypy3-dj1.6.x, +envlist = py27-dj1.6.x, py27-dj1.7.x, py33-dj1.6.x, py34-dj1.6.x, + py33-dj1.7.x, py34-dj1.7.x, pypy-dj1.6.x, pypy3-dj1.6.x, skipsdist = True [testenv] -commands = python runtests.py -deps = - django-braces>=1.0.0 - django-extra-views>=0.6.2 - djangorestframework>=2.3.3 - django-floppyforms>=1.1 - django-filter>=0.6 - django-crispy-forms>=1.3.2 - django-debug-toolbar>=0.9.4 - -[testenv:py27-dj1.4.x] -basepython=python2.7 -deps = - Django>=1.4,<1.5 - {[testenv]deps} - -[testenv:py27-dj1.5.x] -basepython=python2.7 -deps = - Django>=1.5,<1.6 - {[testenv]deps} +commands = py.test [] +deps = -rrequirements_test.txt +setenv= + DJANGO_SETTINGS_MODULE = example.settings + PYTHONPATH = {toxinidir}/example:{toxinidir} [testenv:py27-dj1.6.x] -commands = coverage run runtests.py basepython=python2.7 deps = Django>=1.6,<1.7