mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 21:24:33 +03:00
Merge pull request #3342 from tomchristie/version-3.3
Compat updates for version 3.3
This commit is contained in:
commit
4762a73e34
11
.travis.yml
11
.travis.yml
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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+')
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
36
tox.ini
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user