Merge pull request #3342 from tomchristie/version-3.3

Compat updates for version 3.3
This commit is contained in:
Tom Christie 2015-09-28 11:06:42 +01:00
commit 4762a73e34
20 changed files with 177 additions and 107 deletions

View File

@ -18,23 +18,16 @@ env:
- TOX_ENV=py32-django16 - TOX_ENV=py32-django16
- TOX_ENV=py27-django16 - TOX_ENV=py27-django16
- TOX_ENV=py26-django16 - TOX_ENV=py26-django16
- TOX_ENV=py34-django15
- TOX_ENV=py33-django15
- TOX_ENV=py32-django15
- TOX_ENV=py27-django15
- TOX_ENV=py26-django15
- TOX_ENV=py27-djangomaster - TOX_ENV=py27-djangomaster
- TOX_ENV=py32-djangomaster
- TOX_ENV=py33-djangomaster
- TOX_ENV=py34-djangomaster - TOX_ENV=py34-djangomaster
- TOX_ENV=py35-djangomaster
matrix: matrix:
fast_finish: true fast_finish: true
allow_failures: allow_failures:
- env: TOX_ENV=py27-djangomaster - env: TOX_ENV=py27-djangomaster
- env: TOX_ENV=py32-djangomaster
- env: TOX_ENV=py33-djangomaster
- env: TOX_ENV=py34-djangomaster - env: TOX_ENV=py34-djangomaster
- env: TOX_ENV=py35-djangomaster
install: install:
- pip install tox - pip install tox

View File

@ -37,7 +37,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements # Requirements
* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) * Python (2.6.5+, 2.7, 3.2, 3.3, 3.4)
* Django (1.5.6+, 1.6.3+, 1.7, 1.8) * Django (1.6.3+, 1.7, 1.8)
# Installation # Installation

View File

@ -438,6 +438,7 @@ Declaring a `ModelSerializer` looks like this:
class AccountSerializer(serializers.ModelSerializer): class AccountSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Account model = Account
fields = ('id', 'account_name', 'users', 'created')
By default, all the model fields on the class will be mapped to a corresponding serializer fields. By default, all the model fields on the class will be mapped to a corresponding serializer fields.
@ -459,7 +460,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
## Specifying which fields to include ## Specifying which fields to include
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`. If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`. It is strongly recommended that you explicitly set all fields that should be serialized using the `fields` attribute. This will make it less likely to result in unintentionally exposing data when your models change.
For example: For example:
@ -468,7 +469,27 @@ For example:
model = Account model = Account
fields = ('id', 'account_name', 'users', 'created') fields = ('id', 'account_name', 'users', 'created')
The names in the `fields` option will normally map to model fields on the model class. You can also set the `fields` attribute to the special value `'__all__'` to indicate that all fields in the model should be used.
For example:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = '__all__'
You can set the `exclude` attribute of the to a list of fields to be excluded from the serializer.
For example:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
exclude = ('users',)
In the example above, if the `Account` model had 3 fields `account_name`, `users`, and `created`, this will result in the fields `account_name` and `created` to be serialized.
The names in the `fields` and `exclude` attributes will normally map to model fields on the model class.
Alternatively names in the `fields` options can map to properties or methods which take no arguments that exist on the model class. Alternatively names in the `fields` options can map to properties or methods which take no arguments that exist on the model class.

View File

@ -38,6 +38,14 @@ You can determine your currently installed version using `pip freeze`:
--- ---
## 3.3.x series
### 3.3.0
**Date**: NOT YET RELEASED
* Removed support for Django Version 1.5 ([#3421][gh3421])
## 3.2.x series ## 3.2.x series
### 3.2.4 ### 3.2.4
@ -533,3 +541,6 @@ For older release notes, [please see the version 2.x documentation][old-release-
[gh3361]: https://github.com/tomchristie/django-rest-framework/issues/3361 [gh3361]: https://github.com/tomchristie/django-rest-framework/issues/3361
[gh3364]: https://github.com/tomchristie/django-rest-framework/issues/3364 [gh3364]: https://github.com/tomchristie/django-rest-framework/issues/3364
[gh3415]: https://github.com/tomchristie/django-rest-framework/issues/3415 [gh3415]: https://github.com/tomchristie/django-rest-framework/issues/3415
<!-- 3.3.0 -->
[gh3421]: https://github.com/tomchristie/django-rest-framework/pulls/3421

View File

@ -67,6 +67,14 @@ except ImportError:
from django.utils.datastructures import SortedDict as OrderedDict from django.utils.datastructures import SortedDict as OrderedDict
# unittest.SkipUnless only available in Python 2.7.
try:
import unittest
unittest.skipUnless
except (ImportError, AttributeError):
from django.utils import unittest
# contrib.postgres only supported from 1.8 onwards. # contrib.postgres only supported from 1.8 onwards.
try: try:
from django.contrib.postgres import fields as postgres_fields from django.contrib.postgres import fields as postgres_fields
@ -74,23 +82,30 @@ except ImportError:
postgres_fields = None postgres_fields = None
# Apps only exists from 1.7 onwards.
try:
from django.apps import apps
get_model = apps.get_model
except ImportError:
from django.db.models import get_model
# Import path changes from 1.7 onwards.
try:
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation
)
except ImportError:
from django.contrib.contenttypes.generic import (
GenericForeignKey, GenericRelation
)
# django-filter is optional # django-filter is optional
try: try:
import django_filters import django_filters
except ImportError: except ImportError:
django_filters = None django_filters = None
if django.VERSION >= (1, 6):
def clean_manytomany_helptext(text):
return text
else:
# Up to version 1.5 many to many fields automatically suffix
# the `help_text` attribute with hardcoded text.
def clean_manytomany_helptext(text):
if text.endswith(' Hold down "Control", or "Command" on a Mac, to select more than one.'):
text = text[:-69]
return text
# Django-guardian is optional. Import only if guardian is in INSTALLED_APPS # Django-guardian is optional. Import only if guardian is in INSTALLED_APPS
# Fixes (#1712). We keep the try/except for the test suite. # Fixes (#1712). We keep the try/except for the test suite.
guardian = None guardian = None
@ -101,14 +116,6 @@ except ImportError:
pass pass
def get_model_name(model_cls):
try:
return model_cls._meta.model_name
except AttributeError:
# < 1.6 used module_name instead of model_name
return model_cls._meta.module_name
# MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+ # MinValueValidator, MaxValueValidator et al. only accept `message` in 1.8+
if django.VERSION >= (1, 8): if django.VERSION >= (1, 8):
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
@ -144,32 +151,6 @@ else:
super(MaxLengthValidator, self).__init__(*args, **kwargs) super(MaxLengthValidator, self).__init__(*args, **kwargs)
# URLValidator only accepts `message` in 1.6+
if django.VERSION >= (1, 6):
from django.core.validators import URLValidator
else:
from django.core.validators import URLValidator as DjangoURLValidator
class URLValidator(DjangoURLValidator):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(URLValidator, self).__init__(*args, **kwargs)
# EmailValidator requires explicit regex prior to 1.6+
if django.VERSION >= (1, 6):
from django.core.validators import EmailValidator
else:
from django.core.validators import EmailValidator as DjangoEmailValidator
from django.core.validators import email_re
class EmailValidator(DjangoEmailValidator):
def __init__(self, *args, **kwargs):
super(EmailValidator, self).__init__(email_re, *args, **kwargs)
# PATCH method is not implemented by Django # PATCH method is not implemented by Django
if 'patch' not in View.http_method_names: if 'patch' not in View.http_method_names:
View.http_method_names = View.http_method_names + ['patch'] View.http_method_names = View.http_method_names + ['patch']

View File

@ -11,7 +11,9 @@ import uuid
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import RegexValidator, ip_address_validators from django.core.validators import (
EmailValidator, RegexValidator, URLValidator, ip_address_validators
)
from django.forms import FilePathField as DjangoFilePathField from django.forms import FilePathField as DjangoFilePathField
from django.forms import ImageField as DjangoImageField from django.forms import ImageField as DjangoImageField
from django.utils import six, timezone from django.utils import six, timezone
@ -23,9 +25,9 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import ISO_8601 from rest_framework import ISO_8601
from rest_framework.compat import ( from rest_framework.compat import (
EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
MinValueValidator, OrderedDict, URLValidator, duration_string, MinValueValidator, OrderedDict, duration_string, parse_duration,
parse_duration, unicode_repr, unicode_to_repr unicode_repr, unicode_to_repr
) )
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.settings import api_settings from rest_framework.settings import api_settings

View File

@ -11,9 +11,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from django.utils import six from django.utils import six
from rest_framework.compat import ( from rest_framework.compat import distinct, django_filters, guardian
distinct, django_filters, get_model_name, guardian
)
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
FilterSet = django_filters and django_filters.FilterSet or None FilterSet = django_filters and django_filters.FilterSet or None
@ -202,7 +200,7 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
model_cls = queryset.model model_cls = queryset.model
kwargs = { kwargs = {
'app_label': model_cls._meta.app_label, 'app_label': model_cls._meta.app_label,
'model_name': get_model_name(model_cls) 'model_name': model_cls._meta.model_name
} }
permission = self.perm_format % kwargs permission = self.perm_format % kwargs
if guardian.VERSION >= (1, 3): if guardian.VERSION >= (1, 3):

View File

@ -5,8 +5,6 @@ from __future__ import unicode_literals
from django.http import Http404 from django.http import Http404
from rest_framework.compat import get_model_name
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
@ -104,7 +102,7 @@ class DjangoModelPermissions(BasePermission):
""" """
kwargs = { kwargs = {
'app_label': model_cls._meta.app_label, 'app_label': model_cls._meta.app_label,
'model_name': get_model_name(model_cls) 'model_name': model_cls._meta.model_name
} }
return [perm % kwargs for perm in self.perms_map[method]] return [perm % kwargs for perm in self.perms_map[method]]
@ -166,7 +164,7 @@ class DjangoObjectPermissions(DjangoModelPermissions):
def get_required_object_permissions(self, method, model_cls): def get_required_object_permissions(self, method, model_cls):
kwargs = { kwargs = {
'app_label': model_cls._meta.app_label, 'app_label': model_cls._meta.app_label,
'model_name': get_model_name(model_cls) 'model_name': model_cls._meta.model_name
} }
return [perm % kwargs for perm in self.perms_map[method]] return [perm % kwargs for perm in self.perms_map[method]]

View File

@ -98,7 +98,7 @@ class Response(SimpleTemplateResponse):
state = super(Response, self).__getstate__() state = super(Response, self).__getstate__()
for key in ( for key in (
'accepted_renderer', 'renderer_context', 'resolver_match', 'accepted_renderer', 'renderer_context', 'resolver_match',
'client', 'request', 'wsgi_request' 'client', 'request', 'json', 'wsgi_request'
): ):
if key in state: if key in state:
del state[key] del state[key]

View File

@ -12,6 +12,8 @@ response content is handled by parsers and renderers.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import warnings
from django.db import models from django.db import models
from django.db.models.fields import Field as DjangoModelField from django.db.models.fields import Field as DjangoModelField
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
@ -51,6 +53,8 @@ LIST_SERIALIZER_KWARGS = (
'instance', 'data', 'partial', 'context', 'allow_null' 'instance', 'data', 'partial', 'context', 'allow_null'
) )
ALL_FIELDS = '__all__'
# BaseSerializer # BaseSerializer
# -------------- # --------------
@ -957,10 +961,10 @@ class ModelSerializer(Serializer):
fields = getattr(self.Meta, 'fields', None) fields = getattr(self.Meta, 'fields', None)
exclude = getattr(self.Meta, 'exclude', None) exclude = getattr(self.Meta, 'exclude', None)
if fields and not isinstance(fields, (list, tuple)): if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)):
raise TypeError( raise TypeError(
'The `fields` option must be a list or tuple. Got %s.' % 'The `fields` option must be a list or tuple or "__all__". '
type(fields).__name__ 'Got %s.' % type(fields).__name__
) )
if exclude and not isinstance(exclude, (list, tuple)): if exclude and not isinstance(exclude, (list, tuple)):
@ -976,6 +980,20 @@ class ModelSerializer(Serializer):
) )
) )
if fields is None and exclude is None:
warnings.warn(
"Creating a ModelSerializer without either the 'fields' "
"attribute or the 'exclude' attribute is pending deprecation "
"since 3.3.0. Add an explicit fields = '__all__' to the "
"{serializer_class} serializer.".format(
serializer_class=self.__class__.__name__
),
PendingDeprecationWarning
)
if fields == ALL_FIELDS:
fields = None
if fields is not None: if fields is not None:
# Ensure that all declared fields have also been included in the # Ensure that all declared fields have also been included in the
# `Meta.fields` option. # `Meta.fields` option.

View File

@ -41,8 +41,9 @@ def optional_login(request):
except NoReverseMatch: except NoReverseMatch:
return '' return ''
snippet = "<li><a href='{href}?next={next}'>Log in</a></li>".format(href=login_url, next=escape(request.path)) snippet = "<li><a href='{href}?next={next}'>Log in</a></li>"
return snippet snippet = snippet.format(href=login_url, next=escape(request.path))
return mark_safe(snippet)
@register.simple_tag @register.simple_tag
@ -64,8 +65,8 @@ def optional_logout(request, user):
<li><a href='{href}?next={next}'>Log out</a></li> <li><a href='{href}?next={next}'>Log out</a></li>
</ul> </ul>
</li>""" </li>"""
snippet = snippet.format(user=escape(user), href=logout_url, next=escape(request.path))
return snippet.format(user=user, href=logout_url, next=escape(request.path)) return mark_safe(snippet)
@register.simple_tag @register.simple_tag

View File

@ -8,7 +8,6 @@ from django.core import validators
from django.db import models from django.db import models
from django.utils.text import capfirst from django.utils.text import capfirst
from rest_framework.compat import clean_manytomany_helptext
from rest_framework.validators import UniqueValidator from rest_framework.validators import UniqueValidator
NUMERIC_FIELD_TYPES = ( NUMERIC_FIELD_TYPES = (
@ -230,7 +229,7 @@ def get_relation_kwargs(field_name, relation_info):
if model_field: if model_field:
if model_field.verbose_name and needs_label(model_field, field_name): if model_field.verbose_name and needs_label(model_field, field_name):
kwargs['label'] = capfirst(model_field.verbose_name) kwargs['label'] = capfirst(model_field.verbose_name)
help_text = clean_manytomany_helptext(model_field.help_text) help_text = model_field.help_text
if help_text: if help_text:
kwargs['help_text'] = help_text kwargs['help_text'] = help_text
if not model_field.editable: if not model_field.editable:

View File

@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from django.utils import six from django.utils import six
from rest_framework.compat import OrderedDict from rest_framework.compat import OrderedDict, get_model
FieldInfo = namedtuple('FieldResult', [ FieldInfo = namedtuple('FieldResult', [
'pk', # Model field instance 'pk', # Model field instance
@ -45,7 +45,7 @@ def _resolve_model(obj):
""" """
if isinstance(obj, six.string_types) and len(obj.split('.')) == 2: if isinstance(obj, six.string_types) and len(obj.split('.')) == 2:
app_name, model_name = obj.split('.') app_name, model_name = obj.split('.')
resolved_model = models.get_model(app_name, model_name) resolved_model = get_model(app_name, model_name)
if resolved_model is None: if resolved_model is None:
msg = "Django did not return a model for {0}.{1}" msg = "Django did not return a model for {0}.{1}"
raise ImproperlyConfigured(msg.format(app_name, model_name)) raise ImproperlyConfigured(msg.format(app_name, model_name))

View File

@ -1,13 +1,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf.urls import patterns, url from django.conf.urls import url
from django.db import connection, connections, transaction from django.db import connection, connections, transaction
from django.http import Http404 from django.http import Http404
from django.test import TestCase, TransactionTestCase from django.test import TestCase, TransactionTestCase
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.unittest import skipUnless
from rest_framework import status from rest_framework import status
from rest_framework.compat import unittest
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
@ -35,8 +35,10 @@ class APIExceptionView(APIView):
raise APIException raise APIException
@skipUnless(connection.features.uses_savepoints, @unittest.skipUnless(
"'atomic' requires transactions and savepoints.") connection.features.uses_savepoints,
"'atomic' requires transactions and savepoints."
)
class DBTransactionTests(TestCase): class DBTransactionTests(TestCase):
def setUp(self): def setUp(self):
self.view = BasicView.as_view() self.view = BasicView.as_view()
@ -55,8 +57,10 @@ class DBTransactionTests(TestCase):
assert BasicModel.objects.count() == 1 assert BasicModel.objects.count() == 1
@skipUnless(connection.features.uses_savepoints, @unittest.skipUnless(
"'atomic' requires transactions and savepoints.") connection.features.uses_savepoints,
"'atomic' requires transactions and savepoints."
)
class DBTransactionErrorTests(TestCase): class DBTransactionErrorTests(TestCase):
def setUp(self): def setUp(self):
self.view = ErrorView.as_view() self.view = ErrorView.as_view()
@ -83,8 +87,10 @@ class DBTransactionErrorTests(TestCase):
assert BasicModel.objects.count() == 1 assert BasicModel.objects.count() == 1
@skipUnless(connection.features.uses_savepoints, @unittest.skipUnless(
"'atomic' requires transactions and savepoints.") connection.features.uses_savepoints,
"'atomic' requires transactions and savepoints."
)
class DBTransactionAPIExceptionTests(TestCase): class DBTransactionAPIExceptionTests(TestCase):
def setUp(self): def setUp(self):
self.view = APIExceptionView.as_view() self.view = APIExceptionView.as_view()
@ -113,8 +119,10 @@ class DBTransactionAPIExceptionTests(TestCase):
assert BasicModel.objects.count() == 0 assert BasicModel.objects.count() == 0
@skipUnless(connection.features.uses_savepoints, @unittest.skipUnless(
"'atomic' requires transactions and savepoints.") connection.features.uses_savepoints,
"'atomic' requires transactions and savepoints."
)
class NonAtomicDBTransactionAPIExceptionTests(TransactionTestCase): class NonAtomicDBTransactionAPIExceptionTests(TransactionTestCase):
@property @property
def urls(self): def urls(self):
@ -127,9 +135,8 @@ class NonAtomicDBTransactionAPIExceptionTests(TransactionTestCase):
BasicModel.objects.all() BasicModel.objects.all()
raise Http404 raise Http404
return patterns( return (
'', url(r'^$', NonAtomicAPIExceptionView.as_view()),
url(r'^$', NonAtomicAPIExceptionView.as_view())
) )
def setUp(self): def setUp(self):

View File

@ -8,12 +8,11 @@ from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils import unittest
from django.utils.dateparse import parse_date from django.utils.dateparse import parse_date
from django.utils.six.moves import reload_module from django.utils.six.moves import reload_module
from rest_framework import filters, generics, serializers, status from rest_framework import filters, generics, serializers, status
from rest_framework.compat import django_filters from rest_framework.compat import django_filters, unittest
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
from .models import BaseFilterableItem, BasicModel, FilterableItem from .models import BaseFilterableItem, BasicModel, FilterableItem

View File

@ -321,6 +321,21 @@ class TestRegularFieldMappings(TestCase):
ExampleSerializer() ExampleSerializer()
def test_fields_and_exclude_behavior(self):
class ImplicitFieldsSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
class ExplicitFieldsSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = '__all__'
implicit = ImplicitFieldsSerializer()
explicit = ExplicitFieldsSerializer()
assert implicit.data == explicit.data
@pytest.mark.skipif(django.VERSION < (1, 8), @pytest.mark.skipif(django.VERSION < (1, 8),
reason='DurationField is only available for django1.8+') reason='DurationField is only available for django1.8+')

View File

@ -6,13 +6,12 @@ from django.contrib.auth.models import Group, Permission, User
from django.core.urlresolvers import ResolverMatch from django.core.urlresolvers import ResolverMatch
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from django.utils import unittest
from rest_framework import ( from rest_framework import (
HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers,
status status
) )
from rest_framework.compat import get_model_name, guardian from rest_framework.compat import guardian, unittest
from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.filters import DjangoObjectPermissionsFilter
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
@ -279,7 +278,7 @@ class ObjectPermissionsIntegrationTests(TestCase):
# give everyone model level permissions, as we are not testing those # give everyone model level permissions, as we are not testing those
everyone = Group.objects.create(name='everyone') everyone = Group.objects.create(name='everyone')
model_name = get_model_name(BasicPermModel) model_name = BasicPermModel._meta.model_name
app_label = BasicPermModel._meta.app_label app_label = BasicPermModel._meta.app_label
f = '{0}_{1}'.format f = '{0}_{1}'.format
perms = { perms = {

View File

@ -1,14 +1,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.contenttypes.generic import (
GenericForeignKey, GenericRelation
)
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from rest_framework import serializers from rest_framework import serializers
from rest_framework.compat import GenericForeignKey, GenericRelation
@python_2_unicode_compatible @python_2_unicode_compatible

View File

@ -150,12 +150,12 @@ class ResolveModelWithPatchedDjangoTests(TestCase):
def setUp(self): def setUp(self):
"""Monkeypatch get_model.""" """Monkeypatch get_model."""
self.get_model = rest_framework.utils.model_meta.models.get_model self.get_model = rest_framework.utils.model_meta.get_model
def get_model(app_label, model_name): def get_model(app_label, model_name):
return None return None
rest_framework.utils.model_meta.models.get_model = get_model rest_framework.utils.model_meta.get_model = get_model
def tearDown(self): def tearDown(self):
"""Revert monkeypatching.""" """Revert monkeypatching."""

36
tox.ini
View File

@ -4,15 +4,23 @@ addopts=--tb=short
[tox] [tox]
envlist = envlist =
py27-{lint,docs}, py27-{lint,docs},
{py26,py27,py32,py33,py34}-django{15,16}, {py26,py27,py32,py33,py34}-django16,
{py27,py32,py33,py34}-django{17,18,master} {py27,py32,py33,py34}-django{17,18},
{py27,py34,py35}-django{master}
[testenv] [testenv]
basepython =
py26: python2.6
py27: python2.7
py32: python3.2
py33: python3.3
py34: python3.4
py35: python3.5
commands = ./runtests.py --fast {posargs} --coverage commands = ./runtests.py --fast {posargs} --coverage
setenv = setenv =
PYTHONDONTWRITEBYTECODE=1 PYTHONDONTWRITEBYTECODE=1
deps = deps =
django15: Django==1.5.6 # Should track minimum supported
django16: Django==1.6.3 # Should track minimum supported django16: Django==1.6.3 # Should track minimum supported
django17: Django==1.7.10 # Should track maximum supported django17: Django==1.7.10 # Should track maximum supported
django18: Django==1.8.4 # Should track maximum supported django18: Django==1.8.4 # Should track maximum supported
@ -31,3 +39,25 @@ commands = mkdocs build
deps = deps =
-rrequirements/requirements-testing.txt -rrequirements/requirements-testing.txt
-rrequirements/requirements-documentation.txt -rrequirements/requirements-documentation.txt
# Specify explicitly to exclude Django Guardian against Django master (various Pythons)
[testenv:py27-djangomaster]
deps =
https://github.com/django/django/archive/master.tar.gz
-rrequirements/requirements-testing.txt
markdown==2.5.2
django-filter==0.10.0
[testenv:py34-djangomaster]
deps =
https://github.com/django/django/archive/master.tar.gz
-rrequirements/requirements-testing.txt
markdown==2.5.2
django-filter==0.10.0
[testenv:py35-djangomaster]
deps =
https://github.com/django/django/archive/master.tar.gz
-rrequirements/requirements-testing.txt
markdown==2.5.2
django-filter==0.10.0