From b8d3076cecdd4edf78d6760fd4d00add39cadb02 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Thu, 18 Apr 2019 16:22:27 -0400 Subject: [PATCH 1/5] Django 2.2 support --- .travis.yml | 30 +++------------------ bread/bread.py | 57 +++++++++++++++------------------------ bread/utils.py | 19 ++----------- docs/changes.rst | 8 ++++++ docs/configuration.rst | 2 +- docs/installation.rst | 9 +++---- docs/urls.rst | 4 +-- runtests.py | 3 +-- setup.py | 3 +-- tests/test_add.py | 18 +++---------- tests/test_browse.py | 38 +++++++++++--------------- tests/test_delete.py | 8 +++--- tests/test_edit.py | 10 +++---- tests/test_forms.py | 18 ++++++------- tests/test_pagination.py | 14 +++++----- tests/test_permissions.py | 6 ++--- tests/test_read.py | 10 +++---- tests/test_search.py | 4 +-- tests/test_urls.py | 42 +++++++++++++---------------- tox.ini | 22 ++++++--------- 20 files changed, 118 insertions(+), 207 deletions(-) diff --git a/.travis.yml b/.travis.yml index c2d1256..c6ea14d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,37 +1,15 @@ -# Use travis container-based build system for speed sudo: false -# Ubuntu trusty (14.04) - latest that Travis offers -dist: trusty - -# Make sure all the python versions we need are pre-installed -# (apt-get is not available in the container-based build system) -addons: - apt: - sources: - - deadsnakes - packages: - - python2.7 - - python3.5 - - python3.6 +dist: xenial language: python -# The version of Python that'll be used to invoke tox. Has no effect -# on what version of Python tox uses to run each set of tests. python: - "3.5" + - "3.6" + - "3.7" -# command to install dependencies install: - - "pip install tox" + - pip install tox-travis -# command to run tests script: tox - -# Test a sampling of combinations -env: - - TOXENV=py27-django18,py27-django110,py27-django111 - - TOXENV=py35-django18,py35-django110,py35-django111 - - TOXENV=py36-django18,py36-django110,py36-django111 - - TOXENV=py27-pep8,py36-pep8 diff --git a/bread/bread.py b/bread/bread.py index f7311f6..23e43d8 100644 --- a/bread/bread.py +++ b/bread/bread.py @@ -1,13 +1,9 @@ from functools import reduce import json from operator import or_ - -from six import string_types as six_string_types -from six.moves.http_client import BAD_REQUEST -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode from django.conf import settings -from django.conf.urls import url from django.contrib.admin.utils import lookup_needs_distinct from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.models import Permission @@ -18,19 +14,10 @@ from django.db.models.sql import EmptyResultSet from django.forms.models import modelform_factory from django.http.response import HttpResponseBadRequest +from django.urls import reverse_lazy, path from vanilla import ListView, DetailView, CreateView, UpdateView, DeleteView -from .utils import validate_fieldspec, get_verbose_name, user_is_authenticated - - -from django import VERSION as django_version -if django_version >= (1, 10): - # Modern Django - from django.urls import reverse_lazy -else: - # deprecated in 1.10 - # django.core.urlresolvers to be removed in Django 2.0 - from django.core.urlresolvers import reverse_lazy +from .utils import validate_fieldspec, get_verbose_name class Http400(Exception): @@ -104,7 +91,7 @@ def dispatch(self, request, *args, **kwargs): "'permission_required' attribute to be set.") # Check if the user is logged in - if not user_is_authenticated(request.user): + if not request.user.is_authenticated: return redirect_to_login(request.get_full_path(), settings.LOGIN_URL, REDIRECT_FIELD_NAME) @@ -417,7 +404,7 @@ def get_field_label_value(self, label, evaluator, context_data): Implements the modes described in the class docstring. (q.v.) """ value = '' - if isinstance(evaluator, six_string_types): + if isinstance(evaluator, str): if hasattr(self.object, evaluator): # This is an instance attr or method attr = getattr(self.object, evaluator) @@ -446,7 +433,7 @@ class EditView(BreadViewMixin, UpdateView): def form_invalid(self, form): # Return a 400 if the form isn't valid rsp = super(EditView, self).form_invalid(form) - rsp.status_code = BAD_REQUEST + rsp.status_code = 400 return rsp @@ -457,7 +444,7 @@ class AddView(BreadViewMixin, CreateView): def form_invalid(self, form): # Return a 400 if the form isn't valid rsp = super(AddView, self).form_invalid(form) - rsp.status_code = BAD_REQUEST + rsp.status_code = 400 return rsp @@ -673,31 +660,31 @@ def get_urls(self, prefix=True): urlpatterns = [] if 'B' in self.views: urlpatterns.append( - url(r'^%s$' % prefix, - self.get_browse_view(), - name=self.browse_url_name(include_namespace=False))) + path('%s' % prefix, + self.get_browse_view(), + name=self.browse_url_name(include_namespace=False))) if 'R' in self.views: urlpatterns.append( - url(r'^%s(?P\d+)/$' % prefix, - self.get_read_view(), - name=self.read_url_name(include_namespace=False))) + path('%s/' % prefix, + self.get_read_view(), + name=self.read_url_name(include_namespace=False))) if 'E' in self.views: urlpatterns.append( - url(r'^%s(?P\d+)/edit/$' % prefix, - self.get_edit_view(), - name=self.edit_url_name(include_namespace=False))) + path('%s/edit/' % prefix, + self.get_edit_view(), + name=self.edit_url_name(include_namespace=False))) if 'A' in self.views: urlpatterns.append( - url(r'^%sadd/$' % prefix, - self.get_add_view(), - name=self.add_url_name(include_namespace=False))) + path('%sadd/' % prefix, + self.get_add_view(), + name=self.add_url_name(include_namespace=False))) if 'D' in self.views: urlpatterns.append( - url(r'^%s(?P\d+)/delete/$' % prefix, - self.get_delete_view(), - name=self.delete_url_name(include_namespace=False))) + path('%s/delete/' % prefix, + self.get_delete_view(), + name=self.delete_url_name(include_namespace=False))) return urlpatterns diff --git a/bread/utils.py b/bread/utils.py index b041966..d9070d2 100644 --- a/bread/utils.py +++ b/bread/utils.py @@ -28,23 +28,13 @@ value, similar to how references to context variables in templates work. """ -import six +import inspect from django.core.exceptions import ValidationError from django.db.models import Model from django.db.models.fields import FieldDoesNotExist from django.db.models.fields.related import RelatedField -from django import VERSION as django_version - - -if django_version >= (1, 10): - def user_is_authenticated(user): - return user.is_authenticated -else: - def user_is_authenticated(user): - return user.is_authenticated() - def get_value_or_result(model_instance, attribute_name): attr = getattr(model_instance, attribute_name) @@ -104,12 +94,7 @@ def has_required_args(func): """ Return True if the function has any required arguments. """ - if six.PY2: - from inspect import getargspec - spec = getargspec(func) - else: - from inspect import getfullargspec - spec = getfullargspec(func) + spec = inspect.getfullargspec(func) num_args = len(spec.args) # If first arg is 'self', we can ignore one arg if num_args and spec.args[0] == 'self': diff --git a/docs/changes.rst b/docs/changes.rst index 41ce749..96cac5a 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -3,6 +3,14 @@ Change Log ========== +0.6.0 - Apr 19, 2019 +-------------------- + +* Add support for Python 3.7 +* Add support for Django 2.0, 2.1 and 2.2 +* Drop support for Python < 3 +* Drop support for Django <= 1.11 + 0.5.1 - Dec 1, 2017 ------------------- diff --git a/docs/configuration.rst b/docs/configuration.rst index 05c8c6d..1faf655 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -15,7 +15,7 @@ The main class to subclass is ``Bread``:: then you can add it to a URL config something like this:: - url(r'^', include(MyBreadView().get_urls())), + path('', include(MyBreadView().get_urls())), See also :ref:`urls`. diff --git a/docs/installation.rst b/docs/installation.rst index a2d8fae..b52d92f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -3,13 +3,11 @@ Installation ============ -Django Bread is not yet in PyPI (TODO), but it's pretty -easy to install the version you want using pip from github. -Add something like this to your requirements.txt:: +Django Bread is on `PyPI `_. To install, add this to your requirements.txt:: - git+git://github.com/caktus/django_bread@0.0.6#egg=django_bread + django-bread==0.5.1 -Just change ``0.0.6`` in that example to the version that you +Just change ``0.5.1`` in that example to the version that you want to install. Or leave it out to get the latest release. Then run:: @@ -27,4 +25,3 @@ Django - diff --git a/docs/urls.rst b/docs/urls.rst index 63378a3..a9f194f 100644 --- a/docs/urls.rst +++ b/docs/urls.rst @@ -14,7 +14,7 @@ or:: urlpatterns = ( ..., - url(r'', include(MyBread().get_urls()), + path('', include(MyBread().get_urls()), ... ) @@ -70,6 +70,6 @@ choosing, e.g.:: urlpatterns = ( .... - url(r'^things/', include(MyBread().get_urls(prefix=False)), + path('things/', include(MyBread().get_urls(prefix=False)), ... ) diff --git a/runtests.py b/runtests.py index 4ff4382..b520190 100644 --- a/runtests.py +++ b/runtests.py @@ -25,7 +25,6 @@ ), SITE_ID=1, SECRET_KEY='super-secret', - # ROOT_URLCONF='selectable.tests.urls', TEMPLATES=[ { "BACKEND": 'django.template.backends.django.DjangoTemplates', @@ -42,7 +41,7 @@ def runtests(): setup() TestRunner = get_runner(settings) - test_runner = TestRunner(verbosity=2, interactive=True, failfast=False) + test_runner = TestRunner(verbosity=1, interactive=True, failfast=False) args = sys.argv[1:] or [] failures = test_runner.run_tests(args) sys.exit(failures) diff --git a/setup.py b/setup.py index d7ad690..fa01374 100644 --- a/setup.py +++ b/setup.py @@ -12,9 +12,8 @@ description='Helper for building BREAD interfaces', include_package_data=True, install_requires=[ - 'django-filter>=0.9.2,<1.0', + 'django-filter<2.2.0', 'django-vanilla-views>=1.0.3,<2.0', - 'six' ], long_description=open('README.rst').read(), classifiers=[ diff --git a/tests/test_add.py b/tests/test_add.py index 8a9d494..7b6c9a4 100644 --- a/tests/test_add.py +++ b/tests/test_add.py @@ -1,20 +1,10 @@ -from six.moves.http_client import FOUND, BAD_REQUEST, OK - from django import forms +from django.urls import reverse from bread.bread import AddView, Bread from .base import BreadTestCase from .models import BreadTestModel -from django import VERSION as django_version -if django_version >= (1, 10): - # Modern Django - from django.urls import reverse -else: - # deprecated in 1.10 - # django.core.urlresolvers to be removed in Django 2.0 - from django.core.urlresolvers import reverse - class BreadAddTest(BreadTestCase): def setUp(self): @@ -29,7 +19,7 @@ def test_new_item(self): self.give_permission('add') view = self.bread.get_add_view() rsp = view(request) - self.assertEqual(FOUND, rsp.status_code) + self.assertEqual(302, rsp.status_code) self.assertEqual(reverse(self.bread.get_url_name('browse')), rsp['Location']) item = self.model.objects.get() self.assertEqual('Fred Jones', item.name) @@ -43,7 +33,7 @@ def test_fail_validation(self): self.give_permission('add') view = self.bread.get_add_view() rsp = view(request) - self.assertEqual(BAD_REQUEST, rsp.status_code) + self.assertEqual(400, rsp.status_code) context = rsp.context_data self.assertTrue(context['bread_test_class']) form = context['form'] @@ -58,7 +48,7 @@ def test_get(self): self.give_permission('add') view = self.bread.get_add_view() rsp = view(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) form = rsp.context_data['form'] self.assertFalse(form.is_bound) rsp.render() diff --git a/tests/test_browse.py b/tests/test_browse.py index ce9dad6..ba2de25 100644 --- a/tests/test_browse.py +++ b/tests/test_browse.py @@ -1,18 +1,12 @@ import json -import six -from six.moves.http_client import OK, METHOD_NOT_ALLOWED, BAD_REQUEST +from unittest.mock import patch -from django.core.urlresolvers import reverse +from django.urls import reverse from bread.bread import BrowseView from .base import BreadTestCase from .factories import BreadTestModelFactory -if six.PY3: - from unittest.mock import patch -else: - from mock import patch - class BreadBrowseTest(BreadTestCase): @patch('bread.templatetags.bread_tags.logger') @@ -24,7 +18,7 @@ def test_get(self, mock_logger): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() self.assertTrue(rsp.context_data['bread_test_class']) body = rsp.content.decode('utf-8') @@ -41,7 +35,7 @@ def test_get_empty_list(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) def test_post(self): self.set_urls(self.bread) @@ -50,7 +44,7 @@ def test_post(self): request = self.request_factory.post(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(METHOD_NOT_ALLOWED, rsp.status_code) + self.assertEqual(405, rsp.status_code) @patch('bread.templatetags.bread_tags.logger') def test_sort_all_ascending(self, mock_logger): @@ -65,7 +59,7 @@ def test_sort_all_ascending(self, mock_logger): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() results = rsp.context_data['object_list'] i = 0 @@ -94,7 +88,7 @@ def test_sort_most_ascending_with_override_default_order(self, mock_logger): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() results = rsp.context_data['object_list'] @@ -123,7 +117,7 @@ def test_sort_all_descending(self, mock_logger): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() results = rsp.context_data['object_list'] i = 0 @@ -148,7 +142,7 @@ def test_sort_first_ascending(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() results = rsp.context_data['object_list'] i = 0 @@ -170,7 +164,7 @@ def test_sort_first_ascending_second_descending(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() results = rsp.context_data['object_list'] self.assertEqual(a, results[0]) @@ -191,7 +185,7 @@ def test_sort_first_descending_second_ascending(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() results = rsp.context_data['object_list'] self.assertEqual(a, results[0]) @@ -212,7 +206,7 @@ def test_sort_second_field_ascending(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() results = rsp.context_data['object_list'] self.assertEqual(a, results[0]) @@ -234,7 +228,7 @@ def test_sort_second_field_ascending_first_descending(self, mock_logger): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() results = rsp.context_data['object_list'] self.assertEqual(a, results[0]) @@ -264,7 +258,7 @@ def test_unorderable_column(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(BAD_REQUEST, rsp.status_code) + self.assertEqual(400, rsp.status_code) class NotDisablingSortTest(BreadTestCase): @@ -285,7 +279,7 @@ def test_sorting_on_column(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() self.assertEqual([0], json.loads(rsp.context_data['valid_sorting_columns_json'])) @@ -306,6 +300,6 @@ def test_not_sorting_on_column(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() self.assertEqual([], json.loads(rsp.context_data['valid_sorting_columns_json'])) diff --git a/tests/test_delete.py b/tests/test_delete.py index a62f3a0..688275a 100644 --- a/tests/test_delete.py +++ b/tests/test_delete.py @@ -1,6 +1,4 @@ -from six.moves.http_client import FOUND, OK - -from django.core.urlresolvers import reverse +from django.urls import reverse from django.http import Http404 from .base import BreadTestCase @@ -22,7 +20,7 @@ def test_delete_item(self): view = self.bread.get_delete_view() rsp = view(request, pk=self.item.pk) self.assertTrue(rsp.context_data['bread_test_class']) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) self.assertTrue(self.model.objects.filter(pk=self.item.pk).exists()) # Now post to confirm @@ -30,7 +28,7 @@ def test_delete_item(self): request.user = self.user view = self.bread.get_delete_view() rsp = view(request, pk=self.item.pk) - self.assertEqual(FOUND, rsp.status_code) + self.assertEqual(302, rsp.status_code) self.assertEqual(reverse(self.bread.get_url_name('browse')), rsp['Location']) self.assertFalse(self.model.objects.filter(pk=self.item.pk).exists()) diff --git a/tests/test_edit.py b/tests/test_edit.py index 1b98ca1..4c2ca3f 100644 --- a/tests/test_edit.py +++ b/tests/test_edit.py @@ -1,6 +1,4 @@ -from six.moves.http_client import FOUND, BAD_REQUEST, OK - -from django.core.urlresolvers import reverse +from django.urls import reverse from django import forms from bread.bread import EditView, Bread @@ -21,7 +19,7 @@ def test_edit_item(self): self.give_permission('change') view = self.bread.get_edit_view() rsp = view(request, pk=item.pk) - self.assertEqual(FOUND, rsp.status_code) + self.assertEqual(302, rsp.status_code) self.assertEqual(reverse(self.bread.get_url_name('browse')), rsp['Location']) item = self.model.objects.get(pk=item.pk) self.assertEqual('Fred Jones', item.name) @@ -35,7 +33,7 @@ def test_fail_validation(self): self.give_permission('change') view = self.bread.get_edit_view() rsp = view(request, pk=item.pk) - self.assertEqual(BAD_REQUEST, rsp.status_code) + self.assertEqual(400, rsp.status_code) self.assertTrue(rsp.context_data['bread_test_class']) context = rsp.context_data form = context['form'] @@ -51,7 +49,7 @@ def test_get(self): self.give_permission('change') view = self.bread.get_edit_view() rsp = view(request, pk=item.pk) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) form = rsp.context_data['form'] self.assertFalse(form.is_bound) self.assertEqual(item.pk, form.initial['id']) diff --git a/tests/test_forms.py b/tests/test_forms.py index edad624..e1f85c0 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -1,8 +1,6 @@ -from six.moves.http_client import FOUND, BAD_REQUEST, OK - from django import forms from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse +from django.urls import reverse from .models import BreadTestModel from .base import BreadTestCase @@ -39,7 +37,7 @@ def test_new_item(self): self.give_permission('add') view = self.bread.get_add_view() rsp = view(request) - self.assertEqual(FOUND, rsp.status_code) + self.assertEqual(302, rsp.status_code) self.assertEqual(reverse(self.bread.get_url_name('browse')), rsp['Location']) item = self.model.objects.get() self.assertEqual('Dan Jones', item.name) @@ -52,7 +50,7 @@ def test_fail_validation(self): self.give_permission('add') view = self.bread.get_add_view() rsp = view(request) - self.assertEqual(BAD_REQUEST, rsp.status_code) + self.assertEqual(400, rsp.status_code) context = rsp.context_data form = context['form'] errors = form.errors @@ -66,7 +64,7 @@ def test_get(self): self.give_permission('add') view = self.bread.get_add_view() rsp = view(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) form = rsp.context_data['form'] self.assertFalse(form.is_bound) rsp.render() @@ -91,7 +89,7 @@ def test_edit_item(self): self.give_permission('change') view = self.bread.get_edit_view() rsp = view(request, pk=item.pk) - self.assertEqual(FOUND, rsp.status_code) + self.assertEqual(302, rsp.status_code) self.assertEqual(reverse(self.bread.get_url_name('browse')), rsp['Location']) item = self.model.objects.get(pk=item.pk) self.assertEqual('Dan Jones', item.name) @@ -104,7 +102,7 @@ def test_fail_validation(self): self.give_permission('change') view = self.bread.get_edit_view() rsp = view(request, pk=item.pk) - self.assertEqual(BAD_REQUEST, rsp.status_code) + self.assertEqual(400, rsp.status_code) context = rsp.context_data form = context['form'] errors = form.errors @@ -119,7 +117,7 @@ def test_get(self): self.give_permission('change') view = self.bread.get_edit_view() rsp = view(request, pk=item.pk) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) form = rsp.context_data['form'] self.assertFalse(form.is_bound) self.assertEqual(item.name, form.initial['name']) @@ -147,7 +145,7 @@ def test_get(self): self.give_permission('change') view = self.bread.get_edit_view() rsp = view(request, pk=item.pk) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) form = rsp.context_data['form'] self.assertFalse(form.is_bound) self.assertNotIn('id', form.initial) diff --git a/tests/test_pagination.py b/tests/test_pagination.py index be60b27..d08fa7b 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -1,6 +1,4 @@ -from six.moves.http_client import OK, METHOD_NOT_ALLOWED - -from django.core.urlresolvers import reverse +from django.urls import reverse from django.http import Http404 from bread.bread import BrowseView @@ -30,7 +28,7 @@ def test_get(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() context = rsp.context_data object_list = context['object_list'] @@ -46,7 +44,7 @@ def test_get_second_page(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() context = rsp.context_data object_list = context['object_list'] @@ -72,7 +70,7 @@ def test_get_empty_list(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) context = rsp.context_data paginator = context['paginator'] self.assertEqual(1, paginator.num_pages) @@ -85,7 +83,7 @@ def test_post(self): request = self.request_factory.post(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(METHOD_NOT_ALLOWED, rsp.status_code) + self.assertEqual(405, rsp.status_code) def test_next_url(self): # Make sure next_url includes other query params unaltered @@ -97,7 +95,7 @@ def test_next_url(self): request = self.request_factory.get(url) request.user = self.user rsp = self.bread.get_browse_view()(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) context = rsp.context_data next_url = context['next_url'] # We don't know what order the query parms will end up in diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 3d7e1f4..1032fe4 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -1,10 +1,8 @@ -from six.moves.http_client import FOUND - from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied -from django.core.urlresolvers import reverse +from django.urls import reverse from django.test import override_settings from .base import BreadTestCase @@ -48,7 +46,7 @@ def test_when_not_logged_in(self): if self.include_post: self.post_request.user = AnonymousUser() rsp = self.view(self.post_request, pk=self.item.pk) - self.assertEqual(FOUND, rsp.status_code) + self.assertEqual(302, rsp.status_code) self.assertEqual(expected_url, rsp['Location']) def test_access_without_permission(self): diff --git a/tests/test_read.py b/tests/test_read.py index 08765b0..26017c9 100644 --- a/tests/test_read.py +++ b/tests/test_read.py @@ -1,6 +1,4 @@ -from six.moves.http_client import OK, METHOD_NOT_ALLOWED - -from django.core.urlresolvers import reverse +from django.urls import reverse from django import forms from django.http import Http404 @@ -26,7 +24,7 @@ def test_read(self): view = self.bread.get_read_view() rsp = view(request, pk=item.pk) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() self.assertTrue(rsp.context_data['bread_test_class']) body = rsp.content.decode('utf-8') @@ -50,7 +48,7 @@ def test_post(self): request = self.request_factory.post(url) request.user = self.user rsp = self.bread.get_read_view()(request) - self.assertEqual(METHOD_NOT_ALLOWED, rsp.status_code) + self.assertEqual(405, rsp.status_code) class BreadLabelValueReadTest(BreadTestCase): @@ -96,7 +94,7 @@ def test_read(self): view = self.bread.get_read_view() rsp = view(request, pk=item.pk) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) rsp.render() body = rsp.content.decode('utf-8') self.assertIn('bar', body) diff --git a/tests/test_search.py b/tests/test_search.py index 1adaa1c..384d278 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -1,6 +1,4 @@ # coding: utf-8 -from six.moves.http_client import OK - from tests.base import BreadTestCase from tests.factories import BreadTestModelFactory from bread.bread import BrowseView, Bread @@ -43,7 +41,7 @@ def get_search_results(self, q=None): request = self.request_factory.get('', data=data) request.user = self.user rsp = self.view(request) - self.assertEqual(OK, rsp.status_code) + self.assertEqual(200, rsp.status_code) return rsp.context_data['object_list'] def test_no_query_parm(self): diff --git a/tests/test_urls.py b/tests/test_urls.py index 923f3fb..16090e5 100644 --- a/tests/test_urls.py +++ b/tests/test_urls.py @@ -38,23 +38,20 @@ def test_all_views_urls_with_namespace(self): browse_pattern = [ x for x in patterns if x.name == bread.browse_url_name(include_namespace=False) - ][0].regex.pattern - self.assertEqual('^%s/$' % self.bread.plural_name, browse_pattern) + ][0].pattern + self.assertEqual('%s/' % self.bread.plural_name, str(browse_pattern)) read_pattern = [ x for x in patterns if x.name == bread.read_url_name(include_namespace=False) - ][0].regex.pattern - self.assertTrue(read_pattern.startswith('^%s/' % self.bread.plural_name)) - self.assertIn('(?P', read_pattern) + ][0].pattern + self.assertEqual('%s//' % self.bread.plural_name, str(read_pattern)) edit_pattern = [ x for x in patterns if x.name == bread.edit_url_name(include_namespace=False) - ][0].regex.pattern - self.assertTrue(edit_pattern.startswith('^%s/' % self.bread.plural_name)) - self.assertIn('(?P', edit_pattern) - self.assertTrue(edit_pattern.endswith('/edit/$')) + ][0].pattern + self.assertEqual('%s//edit/' % self.bread.plural_name, str(edit_pattern)) class BreadURLsTest(BreadTestCase): @@ -73,17 +70,14 @@ def test_all_views_urls_no_namespace(self): set([x.name for x in patterns]) ) - browse_pattern = [x for x in patterns if x.name == bread.browse_url_name()][0].regex.pattern - self.assertEqual('^%s/$' % bread.plural_name, browse_pattern) + browse_pattern = [x for x in patterns if x.name == bread.browse_url_name()][0].pattern + self.assertEqual('%s/' % bread.plural_name, str(browse_pattern)) - read_pattern = [x for x in patterns if x.name == bread.read_url_name()][0].regex.pattern - self.assertTrue(read_pattern.startswith('^%s/' % bread.plural_name)) - self.assertIn('(?P', read_pattern) + read_pattern = [x for x in patterns if x.name == bread.read_url_name()][0].pattern + self.assertEqual('%s//' % bread.plural_name, str(read_pattern)) - edit_pattern = [x for x in patterns if x.name == bread.edit_url_name()][0].regex.pattern - self.assertTrue(edit_pattern.startswith('^%s/' % bread.plural_name)) - self.assertIn('(?P', edit_pattern) - self.assertTrue(edit_pattern.endswith('/edit/$')) + edit_pattern = [x for x in patterns if x.name == bread.edit_url_name()][0].pattern + self.assertEqual('%s//edit/' % bread.plural_name, str(edit_pattern)) def test_view_subset(self): # We can do bread with a subset of the BREAD views @@ -126,11 +120,11 @@ def test_omit_prefix(self): set([x.name for x in patterns]) ) - browse_pattern = [x for x in patterns if x.name == bread.browse_url_name()][0].regex.pattern - self.assertEqual('^$', browse_pattern) + browse_pattern = [x for x in patterns if x.name == bread.browse_url_name()][0].pattern + self.assertEqual('', str(browse_pattern)) - read_pattern = [x for x in patterns if x.name == bread.read_url_name()][0].regex.pattern - self.assertEqual(r'^(?P\d+)/$', read_pattern) + read_pattern = [x for x in patterns if x.name == bread.read_url_name()][0].pattern + self.assertEqual('/', str(read_pattern)) - edit_pattern = [x for x in patterns if x.name == bread.edit_url_name()][0].regex.pattern - self.assertEqual(r'^(?P\d+)/edit/$', edit_pattern) + edit_pattern = [x for x in patterns if x.name == bread.edit_url_name()][0].pattern + self.assertEqual('/edit/', str(edit_pattern)) diff --git a/tox.ini b/tox.ini index e706a34..c50e711 100644 --- a/tox.ini +++ b/tox.ini @@ -1,34 +1,28 @@ [tox] downloadcache = {toxworkdir}/_download/ -envlist = {py27,py35,py36}-django{18,110,111}, docs, {py27,py36}-pep8 +envlist = {py35,py36,py37}-django{20,21,22}, docs, py37-pep8 whitelist_externals = /usr/bin/make [testenv] basepython = - py27: python2.7 py35: python3.5 py36: python3.6 + py37: python3.7 deps = factory_boy==2.3.1 - django18: Django>=1.8,<1.9 - django110: Django>=1.10,<1.11 - django111: Django>=1.11,<2.0 - py27: mock==1.0.1 + django20: Django>=2.0,<2.1 + django21: Django>=2.1,<2.2 + django22: Django>=2.2,<3.0 # -Wmodule so we at least see deprecation warnings commands = {envpython} -Wmodule runtests.py {posargs} [testenv:docs] -basepython = python2.7 +basepython = python3.7 deps = sphinx changedir = docs commands = /usr/bin/make html -[testenv:py27-pep8] -basepython = python2.7 -deps = flake8 -commands = flake8 - -[testenv:py36-pep8] -basepython = python3.6 +[testenv:py37-pep8] +basepython = python3.7 deps = flake8 commands = flake8 From 9fd34b5b8d17d0ee9afc9c9866634ecc16dae0ab Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Fri, 19 Apr 2019 13:46:02 -0400 Subject: [PATCH 2/5] Update README and setup.py --- .gitignore | 3 ++- README.rst | 8 ++++---- bread/migrations/0001_initial.py | 2 -- bread/migrations/0002_delete_breadtestmodel.py | 2 -- setup.py | 10 +++++----- tests/models.py | 1 - 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 6b375ef..adb799c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -*.pyc +__pycache__/ build dist .tox _build +*.egg-info/ diff --git a/README.rst b/README.rst index 233f399..4da3379 100644 --- a/README.rst +++ b/README.rst @@ -7,14 +7,14 @@ Add, Delete) views for Django models. It helps with default templates, url generation, permissions, filters, pagination, and more. -This is still very Alpha. We're starting to use it, but the documentation -is very sketchy and the design is still evolving. +This is relatively stable. We're using it in production and have attempted +to document the important parts, but feedback is welcome. Supported versions ------------------ -Django: 1.8, 1.10, 1.11 -Python: 2.7, 3.5, 3.6 +Django: 2.0, 2.1, 2.2 +Python: 3.5, 3.6, 3.7 Testing ------- diff --git a/bread/migrations/0001_initial.py b/bread/migrations/0001_initial.py index da7118a..67a3aca 100644 --- a/bread/migrations/0001_initial.py +++ b/bread/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/bread/migrations/0002_delete_breadtestmodel.py b/bread/migrations/0002_delete_breadtestmodel.py index b93e489..81138e5 100644 --- a/bread/migrations/0002_delete_breadtestmodel.py +++ b/bread/migrations/0002_delete_breadtestmodel.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/setup.py b/setup.py index fa01374..2809a8c 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ description='Helper for building BREAD interfaces', include_package_data=True, install_requires=[ - 'django-filter<2.2.0', + 'django-filter>2,<2.2.0', 'django-vanilla-views>=1.0.3,<2.0', ], long_description=open('README.rst').read(), @@ -21,14 +21,14 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Software Development :: Libraries :: Python Modules', - 'Framework :: Django :: 1.8', - 'Framework :: Django :: 1.10', - 'Framework :: Django :: 1.11', + 'Framework :: Django :: 2.0', + 'Framework :: Django :: 2.1', + 'Framework :: Django :: 2.2', ], zip_safe=False, # because we're including media that Django needs ) diff --git a/tests/models.py b/tests/models.py index b8c3047..149b5e6 100644 --- a/tests/models.py +++ b/tests/models.py @@ -9,7 +9,6 @@ to the tests, and maybe get fancier with different test models for different tests. """ -from __future__ import unicode_literals from django.db import models from django.utils.encoding import python_2_unicode_compatible From 7bfc2287d15198d9e37b4def4632481c8446a932 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Fri, 19 Apr 2019 13:54:43 -0400 Subject: [PATCH 3/5] bump version --- bread/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bread/__init__.py b/bread/__init__.py index b91b9b4..402b360 100644 --- a/bread/__init__.py +++ b/bread/__init__.py @@ -1 +1 @@ -VERSION = '0.5.1' +VERSION = '0.6.0' From c032298ec45bbd97fbad088b36d5fc164c4c6dd4 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Sun, 21 Apr 2019 08:58:04 -0400 Subject: [PATCH 4/5] Better docs regarding old versions --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 4da3379..c43fdf8 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,9 @@ Supported versions Django: 2.0, 2.1, 2.2 Python: 3.5, 3.6, 3.7 +For Python 2.7 and/or Django 1.11 support, the 0.5 release series is identical (features-wise) +to 0.6 and is available on PyPI: https://pypi.org/project/django-bread/#history + Testing ------- From 107802f4438939555eac8bae49a20770f3f9a241 Mon Sep 17 00:00:00 2001 From: Vinod Kurup Date: Sun, 21 Apr 2019 08:58:53 -0400 Subject: [PATCH 5/5] Better docs --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index b52d92f..917d3b3 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -5,9 +5,9 @@ Installation Django Bread is on `PyPI `_. To install, add this to your requirements.txt:: - django-bread==0.5.1 + django-bread==0.6.0 -Just change ``0.5.1`` in that example to the version that you +Just change ``0.6.0`` in that example to the version that you want to install. Or leave it out to get the latest release. Then run::