Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django 2.1 compat #1562

Merged
merged 8 commits into from
Aug 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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