Skip to content

Commit

Permalink
Django 2.1 compat (django-tastypie#1562)
Browse files Browse the repository at this point in the history
* Django 2.1: Set up test matrix

* Django 2.1: fix missing QUERY_TERMS (django-tastypie#1564)

* Isolate failing assertion in separate test

* query_terms: try interrogating the django field directly

* Replace class_lookups with get_lookups() which is a more complete list

* Leftover debug

* Django 2.1: 'Fix' for upstream regression regarding non-string query params in tests (django-tastypie#1565)

* Django 2.1: explicitly set blank=True on BooleanFields due to upstream regression (django-tastypie#1567)

Failing tests unrelated to this PR; those will need another compat shim (and pep8).

* Fix/django2.1 cleanups (django-tastypie#1568)

* Fix pep8

* Remove tests for Django 1.8

* More 1.8 removal, stop installing django 2 on python 2

* More dependency fixes

* Explicitly run doctests for validate_jsonp module, to bump coverage

* flake8
  • Loading branch information
georgedorn authored and pavanv committed Mar 3, 2021
1 parent 586544c commit e9e1d01
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 35 deletions.
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ env:
- MODE=flake8
- MODE=flake8-strict
- MODE=docs
- DJANGO_VERSION=dj18
- DJANGO_VERSION=dj111
- DJANGO_VERSION=dj20
- DJANGO_VERSION=dj21
- DJANGO_VERSION=djdev

matrix:
Expand All @@ -28,10 +28,14 @@ matrix:
env: MODE=flake8-strict
- python: "3.4"
env: DJANGO_VERSION=djdev
- python: "3.4"
env: DJANGO_VERSION=dj21
- python: "2.7"
env: DJANGO_VERSION=djdev
- python: "2.7"
env: DJANGO_VERSION=dj20
- python: "2.7"
env: DJANGO_VERSION=dj21

addons:
apt:
Expand Down
6 changes: 6 additions & 0 deletions docs/release_notes/dev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ dev
The current in-progress version. Put your notes here so they can be easily
copied to the release notes for the next release.

Major changes
-------------

* Dropped support for Django 1.8 (EOL).
* Compatability fixes for upstream changes, most notably the removal of QUERY_TERMS.

Bugfixes
--------

Expand Down
3 changes: 1 addition & 2 deletions tastypie/paginator.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,10 @@ def _generate_uri(self, limit, offset):
del request_params['limit']
if 'offset' in request_params:
del request_params['offset']
request_params.update({'limit': limit, 'offset': offset})
request_params.update({'limit': str(limit), 'offset': str(offset)})
encoded_params = request_params.urlencode()
except AttributeError:
request_params = {}

for k, v in self.request_data.items():
if isinstance(v, six.text_type):
request_params[k] = v.encode('utf-8')
Expand Down
19 changes: 13 additions & 6 deletions tastypie/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
from django.conf import settings
from django.conf.urls import url
from django.core.exceptions import (
ObjectDoesNotExist, MultipleObjectsReturned, ValidationError,
ObjectDoesNotExist, MultipleObjectsReturned, ValidationError, FieldDoesNotExist
)
from django.core.signals import got_request_exception
from django.core.exceptions import ImproperlyConfigured
from django.db.models.fields.related import ForeignKey
from django.db import models
try:
from django.contrib.gis.db.models.fields import GeometryField
except (ImproperlyConfigured, ImportError):
Expand All @@ -29,7 +30,7 @@
except ImportError:
from django.db.models.fields.related_descriptors import\
ReverseOneToOneDescriptor
from django.db.models.sql.constants import QUERY_TERMS

from django.http import HttpResponse, HttpResponseNotFound, Http404
from django.utils import six
from django.utils.cache import patch_cache_control, patch_vary_headers
Expand Down Expand Up @@ -2075,10 +2076,6 @@ def build_filters(self, filters=None, ignore_bad_filters=False):

qs_filters = {}

query_terms = QUERY_TERMS
if django.VERSION >= (1, 8) and GeometryField:
query_terms |= set(GeometryField.class_lookups.keys())

for filter_expr, value in filters.items():
filter_bits = filter_expr.split(LOOKUP_SEP)
field_name = filter_bits.pop(0)
Expand All @@ -2088,6 +2085,16 @@ def build_filters(self, filters=None, ignore_bad_filters=False):
# It's not a field we know about. Move along citizen.
continue

# Validate filter types other than 'exact' that are supported by the field type
try:
django_field_name = self.fields[field_name].attribute
django_field = self._meta.object_class._meta.get_field(django_field_name)
if hasattr(django_field, 'field'):
django_field = django_field.field # related field
except FieldDoesNotExist:
raise InvalidFilterError("The '%s' field is not a valid field name" % field_name)

query_terms = django_field.get_lookups().keys()
if len(filter_bits) and filter_bits[-1] in query_terms:
filter_type = filter_bits.pop()

Expand Down
4 changes: 2 additions & 2 deletions tests/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Note(models.Model):
title = models.CharField("The Title", max_length=100)
slug = models.SlugField()
content = models.TextField(blank=True)
is_active = models.BooleanField(default=True)
is_active = models.BooleanField(default=True, blank=True)
created = models.DateTimeField(default=now)
updated = models.DateTimeField(default=now)

Expand Down Expand Up @@ -86,7 +86,7 @@ class AutoNowNote(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
content = models.TextField(blank=True)
is_active = models.BooleanField(default=True)
is_active = models.BooleanField(default=True, blank=True)
created = models.DateTimeField(auto_now_add=now, null=True)
updated = models.DateTimeField(auto_now=now)

Expand Down
2 changes: 1 addition & 1 deletion tests/core/tests/paginator.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def test_custom_collection_name(self):
def test_multiple(self):
request = QueryDict('a=1&a=2')
paginator = Paginator(request, self.data_set,
resource_uri='/api/v1/notes/', limit=2, offset=2)
resource_uri='/api/v1/notes/', limit='2', offset='2')
meta = paginator.page()['meta']
self.assertEqual(meta['limit'], 2)
self.assertEqual(meta['offset'], 2)
Expand Down
11 changes: 8 additions & 3 deletions tests/core/tests/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2102,13 +2102,18 @@ def test_build_filters(self):
resource_4 = AnotherSubjectResource()
self.assertEqual(resource_4.build_filters(filters={'notes__user__startswith': 'Daniel'}), {'notes__author__startswith': 'Daniel'})

# Make sure that fields that don't have attributes can't be filtered on.
self.assertRaises(InvalidFilterError, resource_4.build_filters, filters={'notes__hello_world': 'News'})

# Make sure build_filters works even on resources without queryset
resource = NoQuerysetNoteResource()
self.assertEqual(resource.build_filters(), {})

def test_build_filters_bad(self):
"""
Test that a nonsensical filter fails validation.
"""
resource = AnotherSubjectResource()
# Make sure that fields that don't have attributes can't be filtered on.
self.assertRaises(InvalidFilterError, resource.build_filters, filters={'notes__hello_world': 'News'})

def test_custom_build_filters(self):
"""
A test derived from an example in the documentation (under Advanced Filtering).
Expand Down
11 changes: 11 additions & 0 deletions tests/validation/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from basic.models import Note
from testcases import TestCaseWithFixture
from django.test.testcases import SimpleTestCase


@override_settings(ROOT_URLCONF='validation.api.urls')
Expand Down Expand Up @@ -156,3 +157,13 @@ def test_invalid_data(self):
'annotations': ['This field is required.']
}
})


class TestJSONPValidation(SimpleTestCase):
"""
Explicitly run the doctests for tastypie.utils.validate_jsonp
"""
def test_jsonp(self):
import tastypie.utils.validate_jsonp
import doctest
doctest.testmod(tastypie.utils.validate_jsonp)
43 changes: 23 additions & 20 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[tox]
envlist =
py{27,34,35,36}-dj{18,111,20,dev}
py{27}-dj{111}
py{34}-dj{20}
py{35,36}-dj{20,21,dev}
py{27,36}-docs,
py{27,36}-flake8,
py{27,36}-flake8-strict
Expand All @@ -15,17 +17,17 @@ setenv =
PYTHONPATH = {toxinidir}:{toxinidir}/tests
PYTHONWARNINGS = always
commands =
dj{18,111,20,dev}: {[testenv]test-executable} test -p '*' core.tests --settings=settings_core
dj{18,111,20,dev}: {[testenv]test-executable} test basic.tests --settings=settings_basic
dj{18,111,20,dev}: {[testenv]test-executable} test related_resource.tests --settings=settings_related
dj{18,111,20,dev}: {[testenv]test-executable} test alphanumeric.tests --settings=settings_alphanumeric
dj{18,111,20,dev}: {[testenv]test-executable} test authorization.tests --settings=settings_authorization
dj{18,111,20,dev}: {[testenv]test-executable} test content_gfk.tests --settings=settings_content_gfk
dj{18,111,20,dev}: {[testenv]test-executable} test customuser.tests --settings=settings_customuser
dj{18,111,20,dev}: {[testenv]test-executable} test namespaced.tests --settings=settings_namespaced
dj{18,111,20,dev}: {[testenv]test-executable} test slashless.tests --settings=settings_slashless
dj{18,111,20,dev}: {[testenv]test-executable} test validation.tests --settings=settings_validation
dj{18,111,20,dev}: {[testenv]test-executable} test gis.tests --settings=settings_gis_spatialite
dj{111,20,21,dev}: {[testenv]test-executable} test -p '*' core.tests --settings=settings_core
dj{111,20,21,dev}: {[testenv]test-executable} test basic.tests --settings=settings_basic
dj{111,20,21,dev}: {[testenv]test-executable} test related_resource.tests --settings=settings_related
dj{111,20,21,dev}: {[testenv]test-executable} test alphanumeric.tests --settings=settings_alphanumeric
dj{111,20,21,dev}: {[testenv]test-executable} test authorization.tests --settings=settings_authorization
dj{111,20,21,dev}: {[testenv]test-executable} test content_gfk.tests --settings=settings_content_gfk
dj{111,20,21,dev}: {[testenv]test-executable} test customuser.tests --settings=settings_customuser
dj{111,20,21,dev}: {[testenv]test-executable} test namespaced.tests --settings=settings_namespaced
dj{111,20,21,dev}: {[testenv]test-executable} test slashless.tests --settings=settings_slashless
dj{111,20,21,dev}: {[testenv]test-executable} test validation.tests --settings=settings_validation
dj{111,20,21,dev}: {[testenv]test-executable} test gis.tests --settings=settings_gis_spatialite

docs: sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
docs: sphinx-build -W -b doctest -d {envtmpdir}/doctrees . {envtmpdir}/html
Expand All @@ -39,20 +41,21 @@ basepython =
py35: python3.5
py36: python3.6
deps =
dj18: Django>=1.8,<1.9
dj111: Django>=1.11,<1.12
dj20: Django>=2.0,<2.1
dj21: Django>=2.1,<2.2
djdev: https://github.com/django/django/archive/master.tar.gz

py27-dj{18,111}: django-oauth-plus==2.2.9
py27-dj{18,111,20,dev}: python-digest
py27-dj{18,111,20,dev}: oauth2
py27-dj{18,111,20,dev}: pysqlite==2.7.0
py{34,35,36}-dj{18,111,20,dev}: python3-digest>=1.8b4
dj{18,111,20,dev}: -r{toxinidir}/tests/requirements.txt
py27-dj{111}: django-oauth-plus==2.2.9
py27-dj{111,20,21,dev}: python-digest
py27-dj{111,20,21,dev}: oauth2
py27-dj{111,20,21,dev}: pysqlite==2.7.0
py{34,35,36}-dj{111,20,21,dev}: python3-digest>=1.8b4
dj{111,20,21,dev}: -r{toxinidir}/tests/requirements.txt

docs: Sphinx
docs: Django>=1.11,<1.12
py27-docs: Django<2.0
py{34,35,36}-docs: Django<2.2
docs: mock
docs: sphinx_rtd_theme

Expand Down

0 comments on commit e9e1d01

Please sign in to comment.