mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 12:30:11 +03:00
Merge branch 'master' into localizedfloatfield
This commit is contained in:
commit
d94b00c4cb
|
@ -165,7 +165,7 @@ If all you need is simple equality-based filtering, you can set a `filter_fields
|
||||||
class ProductList(generics.ListAPIView):
|
class ProductList(generics.ListAPIView):
|
||||||
queryset = Product.objects.all()
|
queryset = Product.objects.all()
|
||||||
serializer_class = ProductSerializer
|
serializer_class = ProductSerializer
|
||||||
filter_backends = (filters.DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filter_fields = ('category', 'in_stock')
|
filter_fields = ('category', 'in_stock')
|
||||||
|
|
||||||
This will automatically create a `FilterSet` class for the given fields, and will allow you to make requests such as:
|
This will automatically create a `FilterSet` class for the given fields, and will allow you to make requests such as:
|
||||||
|
|
|
@ -51,7 +51,7 @@ Typically we wouldn't do this, but would instead register the viewset with a rou
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'users', UserViewSet)
|
router.register(r'users', UserViewSet, base_name='user')
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
|
||||||
Rather than writing your own viewsets, you'll often want to use the existing base classes that provide a default set of behavior. For example:
|
Rather than writing your own viewsets, you'll often want to use the existing base classes that provide a default set of behavior. For example:
|
||||||
|
|
|
@ -40,6 +40,33 @@ You can determine your currently installed version using `pip freeze`:
|
||||||
|
|
||||||
## 3.6.x series
|
## 3.6.x series
|
||||||
|
|
||||||
|
### 3.6.3
|
||||||
|
|
||||||
|
**Date**: [12th May 2017][3.6.3-milestone]
|
||||||
|
|
||||||
|
* Raise 404 if a URL lookup results in ValidationError. ([#5126][gh5126])
|
||||||
|
* Honor http_method_names on class based view, when generating API schemas. ([#5085][gh5085])
|
||||||
|
* Allow overridden `get_limit` in LimitOffsetPagination to return all records. ([#4437][gh4437])
|
||||||
|
* Fix partial update for the ListSerializer. ([#4222][gh4222])
|
||||||
|
* Render JSONField control correctly in browsable API. ([#4999][gh4999], [#5042][gh5042])
|
||||||
|
* Raise validation errors for invalid datetime in given timezone. ([#4987][gh4987])
|
||||||
|
* Support restricting doc & schema shortcuts to a subset of urls. ([#4979][gh4979])
|
||||||
|
* Resolve SchemaGenerator error with paginators that have no `page_size` attribute. ([#5086][gh5086], [#3692][gh3692])
|
||||||
|
* Resolve HyperlinkedRelatedField exception on string with %20 instead of space. ([#4748][gh4748], [#5078][gh5078])
|
||||||
|
* Customizable schema generator classes. ([#5082][gh5082])
|
||||||
|
* Update existing vary headers in response instead of overwriting them. ([#5047][gh5047])
|
||||||
|
* Support passing `.as_view()` to view instance. ([#5053][gh5053])
|
||||||
|
* Use correct exception handler when settings overridden on a view. ([#5055][gh5055], [#5054][gh5054])
|
||||||
|
* Update Boolean field to support 'yes' and 'no' values. ([#5038][gh5038])
|
||||||
|
* Fix unique validator for ChoiceField. ([#5004][gh5004], [#5026][gh5026], [#5028][gh5028])
|
||||||
|
* JavaScript cleanups in API Docs. ([#5001][gh5001])
|
||||||
|
* Include URL path regexs in API schemas where valid. ([#5014][gh5014])
|
||||||
|
* Correctly set scheme in coreapi TokenAuthentication. ([#5000][gh5000], [#4994][gh4994])
|
||||||
|
* HEAD requests on ViewSets should not return 405. ([#4705][gh4705], [#4973][gh4973], [#4864][gh4864])
|
||||||
|
* Support usage of 'source' in `extra_kwargs`. ([#4688][gh4688])
|
||||||
|
* Fix invalid content type for schema.js ([#4968][gh4968])
|
||||||
|
* Fix DjangoFilterBackend inheritance issues. ([#5089][gh5089], [#5117][gh5117])
|
||||||
|
|
||||||
### 3.6.2
|
### 3.6.2
|
||||||
|
|
||||||
**Date**: [10th March 2017][3.6.2-milestone]
|
**Date**: [10th March 2017][3.6.2-milestone]
|
||||||
|
@ -688,6 +715,7 @@ For older release notes, [please see the version 2.x documentation][old-release-
|
||||||
[3.6.0-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.0+Release%22
|
[3.6.0-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.0+Release%22
|
||||||
[3.6.1-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.1+Release%22
|
[3.6.1-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.1+Release%22
|
||||||
[3.6.2-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.2+Release%22
|
[3.6.2-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.2+Release%22
|
||||||
|
[3.6.3-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.3+Release%22
|
||||||
|
|
||||||
<!-- 3.0.1 -->
|
<!-- 3.0.1 -->
|
||||||
[gh2013]: https://github.com/encode/django-rest-framework/issues/2013
|
[gh2013]: https://github.com/encode/django-rest-framework/issues/2013
|
||||||
|
@ -1298,3 +1326,37 @@ For older release notes, [please see the version 2.x documentation][old-release-
|
||||||
[gh4955]: https://github.com/encode/django-rest-framework/issues/4955
|
[gh4955]: https://github.com/encode/django-rest-framework/issues/4955
|
||||||
[gh4956]: https://github.com/encode/django-rest-framework/issues/4956
|
[gh4956]: https://github.com/encode/django-rest-framework/issues/4956
|
||||||
[gh4949]: https://github.com/encode/django-rest-framework/issues/4949
|
[gh4949]: https://github.com/encode/django-rest-framework/issues/4949
|
||||||
|
<!-- 3.6.3 -->
|
||||||
|
|
||||||
|
[gh5126]: https://github.com/encode/django-rest-framework/issues/5126
|
||||||
|
[gh5085]: https://github.com/encode/django-rest-framework/issues/5085
|
||||||
|
[gh4437]: https://github.com/encode/django-rest-framework/issues/4437
|
||||||
|
[gh4222]: https://github.com/encode/django-rest-framework/issues/4222
|
||||||
|
[gh4999]: https://github.com/encode/django-rest-framework/issues/4999
|
||||||
|
[gh5042]: https://github.com/encode/django-rest-framework/issues/5042
|
||||||
|
[gh4987]: https://github.com/encode/django-rest-framework/issues/4987
|
||||||
|
[gh4979]: https://github.com/encode/django-rest-framework/issues/4979
|
||||||
|
[gh5086]: https://github.com/encode/django-rest-framework/issues/5086
|
||||||
|
[gh3692]: https://github.com/encode/django-rest-framework/issues/3692
|
||||||
|
[gh4748]: https://github.com/encode/django-rest-framework/issues/4748
|
||||||
|
[gh5078]: https://github.com/encode/django-rest-framework/issues/5078
|
||||||
|
[gh5082]: https://github.com/encode/django-rest-framework/issues/5082
|
||||||
|
[gh5047]: https://github.com/encode/django-rest-framework/issues/5047
|
||||||
|
[gh5053]: https://github.com/encode/django-rest-framework/issues/5053
|
||||||
|
[gh5055]: https://github.com/encode/django-rest-framework/issues/5055
|
||||||
|
[gh5054]: https://github.com/encode/django-rest-framework/issues/5054
|
||||||
|
[gh5038]: https://github.com/encode/django-rest-framework/issues/5038
|
||||||
|
[gh5004]: https://github.com/encode/django-rest-framework/issues/5004
|
||||||
|
[gh5026]: https://github.com/encode/django-rest-framework/issues/5026
|
||||||
|
[gh5028]: https://github.com/encode/django-rest-framework/issues/5028
|
||||||
|
[gh5001]: https://github.com/encode/django-rest-framework/issues/5001
|
||||||
|
[gh5014]: https://github.com/encode/django-rest-framework/issues/5014
|
||||||
|
[gh5000]: https://github.com/encode/django-rest-framework/issues/5000
|
||||||
|
[gh4994]: https://github.com/encode/django-rest-framework/issues/4994
|
||||||
|
[gh4705]: https://github.com/encode/django-rest-framework/issues/4705
|
||||||
|
[gh4973]: https://github.com/encode/django-rest-framework/issues/4973
|
||||||
|
[gh4864]: https://github.com/encode/django-rest-framework/issues/4864
|
||||||
|
[gh4688]: https://github.com/encode/django-rest-framework/issues/4688
|
||||||
|
[gh4968]: https://github.com/encode/django-rest-framework/issues/4968
|
||||||
|
[gh5089]: https://github.com/encode/django-rest-framework/issues/5089
|
||||||
|
[gh5117]: https://github.com/encode/django-rest-framework/issues/5117
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Optional packages which may be used with REST framework.
|
# Optional packages which may be used with REST framework.
|
||||||
markdown==2.6.4
|
markdown==2.6.4
|
||||||
django-guardian==1.4.8
|
django-guardian==1.4.8
|
||||||
django-filter==1.0.2
|
django-filter==1.0.4
|
||||||
coreapi==2.2.4
|
coreapi==2.2.4
|
||||||
coreschema==0.0.4
|
coreschema==0.0.4
|
||||||
|
|
|
@ -8,7 +8,7 @@ ______ _____ _____ _____ __
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__title__ = 'Django REST framework'
|
__title__ = 'Django REST framework'
|
||||||
__version__ = '3.6.2'
|
__version__ = '3.6.3'
|
||||||
__author__ = 'Tom Christie'
|
__author__ = 'Tom Christie'
|
||||||
__license__ = 'BSD 2-Clause'
|
__license__ = 'BSD 2-Clause'
|
||||||
__copyright__ = 'Copyright 2011-2017 Tom Christie'
|
__copyright__ = 'Copyright 2011-2017 Tom Christie'
|
||||||
|
|
|
@ -6,7 +6,11 @@ from rest_framework import serializers
|
||||||
|
|
||||||
class AuthTokenSerializer(serializers.Serializer):
|
class AuthTokenSerializer(serializers.Serializer):
|
||||||
username = serializers.CharField(label=_("Username"))
|
username = serializers.CharField(label=_("Username"))
|
||||||
password = serializers.CharField(label=_("Password"), style={'input_type': 'password'})
|
password = serializers.CharField(
|
||||||
|
label=_("Password"),
|
||||||
|
style={'input_type': 'password'},
|
||||||
|
trim_whitespace=False
|
||||||
|
)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
username = attrs.get('username')
|
username = attrs.get('username')
|
||||||
|
|
|
@ -11,6 +11,7 @@ from functools import reduce
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
|
from django.db.models.sql.constants import ORDER_PATTERN
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
@ -268,7 +269,7 @@ class OrderingFilter(BaseFilterBackend):
|
||||||
|
|
||||||
def remove_invalid_fields(self, queryset, fields, view, request):
|
def remove_invalid_fields(self, queryset, fields, view, request):
|
||||||
valid_fields = [item[0] for item in self.get_valid_fields(queryset, view, {'request': request})]
|
valid_fields = [item[0] for item in self.get_valid_fields(queryset, view, {'request': request})]
|
||||||
return [term for term in fields if term.lstrip('-') in valid_fields]
|
return [term for term in fields if term.lstrip('-') in valid_fields and ORDER_PATTERN.match(term)]
|
||||||
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
ordering = self.get_ordering(request, queryset, view)
|
ordering = self.get_ordering(request, queryset, view)
|
||||||
|
|
|
@ -606,7 +606,7 @@ class SchemaGenerator(object):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
pagination = getattr(view, 'pagination_class', None)
|
pagination = getattr(view, 'pagination_class', None)
|
||||||
if not pagination or not getattr(pagination, 'page_size', None):
|
if not pagination:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
paginator = view.pagination_class()
|
paginator = view.pagination_class()
|
||||||
|
|
|
@ -1010,7 +1010,7 @@ class ModelSerializer(Serializer):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
extra_field_kwargs = extra_kwargs.get(field_name, {})
|
extra_field_kwargs = extra_kwargs.get(field_name, {})
|
||||||
source = extra_field_kwargs.get('source') or field_name
|
source = extra_field_kwargs.get('source', '*') != '*' or field_name
|
||||||
|
|
||||||
# Determine the serializer field class and keyword arguments.
|
# Determine the serializer field class and keyword arguments.
|
||||||
field_class, field_kwargs = self.build_field(
|
field_class, field_kwargs = self.build_field(
|
||||||
|
|
|
@ -23,7 +23,7 @@ function formEntries (form) {
|
||||||
// Polyfill for new FormData(form).entries()
|
// Polyfill for new FormData(form).entries()
|
||||||
var formData = new FormData(form)
|
var formData = new FormData(form)
|
||||||
if (formData.entries !== undefined) {
|
if (formData.entries !== undefined) {
|
||||||
return formData.entries()
|
return Array.from(formData.entries())
|
||||||
}
|
}
|
||||||
|
|
||||||
var entries = []
|
var entries = []
|
||||||
|
@ -59,6 +59,8 @@ $(function () {
|
||||||
var $selectedAuthentication = $('#selected-authentication')
|
var $selectedAuthentication = $('#selected-authentication')
|
||||||
var $authControl = $('#auth-control')
|
var $authControl = $('#auth-control')
|
||||||
var $authTokenModal = $('#auth_token_modal')
|
var $authTokenModal = $('#auth_token_modal')
|
||||||
|
var $authBasicModal = $('#auth_basic_modal')
|
||||||
|
var $authSessionModal = $('#auth_session_modal')
|
||||||
|
|
||||||
// Language Control
|
// Language Control
|
||||||
$('#language-control li').click(function (event) {
|
$('#language-control li').click(function (event) {
|
||||||
|
@ -260,8 +262,8 @@ $(function () {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
window.auth = null
|
window.auth = null
|
||||||
$selectedAuthentication.text('none')
|
$selectedAuthentication.text('none')
|
||||||
$authControl.children().removeClass('active')
|
$authControl.find("[data-auth]").closest('li').removeClass('active')
|
||||||
$authControl.find("[data-auth='none']").addClass('active')
|
$authControl.find("[data-auth='none']").closest('li').addClass('active')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Authentication: token
|
// Authentication: token
|
||||||
|
@ -276,8 +278,8 @@ $(function () {
|
||||||
'token': token
|
'token': token
|
||||||
}
|
}
|
||||||
$selectedAuthentication.text('token')
|
$selectedAuthentication.text('token')
|
||||||
$authControl.children().removeClass('active')
|
$authControl.find("[data-auth]").closest('li').removeClass('active')
|
||||||
$authControl.find("[data-auth='token']").addClass('active')
|
$authControl.find("[data-auth='token']").closest('li').addClass('active')
|
||||||
$authTokenModal.modal('hide')
|
$authTokenModal.modal('hide')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -293,9 +295,9 @@ $(function () {
|
||||||
'password': password
|
'password': password
|
||||||
}
|
}
|
||||||
$selectedAuthentication.text('basic')
|
$selectedAuthentication.text('basic')
|
||||||
$authControl.children().removeClass('active')
|
$authControl.find("[data-auth]").closest('li').removeClass('active')
|
||||||
$authControl.find("[data-auth='basic']").addClass('active')
|
$authControl.find("[data-auth='basic']").closest('li').addClass('active')
|
||||||
$authTokenModal.modal('hide')
|
$authBasicModal.modal('hide')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Authentication: session
|
// Authentication: session
|
||||||
|
@ -305,8 +307,8 @@ $(function () {
|
||||||
'type': 'session'
|
'type': 'session'
|
||||||
}
|
}
|
||||||
$selectedAuthentication.text('session')
|
$selectedAuthentication.text('session')
|
||||||
$authControl.children().removeClass('active')
|
$authControl.find("[data-auth]").closest('li').removeClass('active')
|
||||||
$authControl.find("[data-auth='session']").addClass('active')
|
$authControl.find("[data-auth='session']").closest('li').addClass('active')
|
||||||
$authTokenModal.modal('hide')
|
$authSessionModal.modal('hide')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
};
|
};
|
||||||
$('#selected-authentication').text('session');
|
$('#selected-authentication').text('session');
|
||||||
$('#auth-control').children().removeClass('active');
|
$('#auth-control').children().removeClass('active');
|
||||||
$('#auth-control').find("[data-auth='session']").addClass('active');
|
$('#auth-control').find("[data-auth='session']").closest('li').addClass('active');
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -12,7 +12,7 @@ PYTEST_ARGS = {
|
||||||
'fast': ['tests', '--tb=short', '-q', '-s', '-rw'],
|
'fast': ['tests', '--tb=short', '-q', '-s', '-rw'],
|
||||||
}
|
}
|
||||||
|
|
||||||
FLAKE8_ARGS = ['rest_framework', 'tests', '--ignore=E501']
|
FLAKE8_ARGS = ['rest_framework', 'tests']
|
||||||
|
|
||||||
ISORT_ARGS = ['--recursive', '--check-only', '-o' 'uritemplate', '-p', 'tests', 'rest_framework', 'tests']
|
ISORT_ARGS = ['--recursive', '--check-only', '-o' 'uritemplate', '-p', 'tests', 'rest_framework', 'tests']
|
||||||
|
|
||||||
|
|
|
@ -3,3 +3,6 @@ universal = 1
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
license_file = LICENSE.md
|
license_file = LICENSE.md
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
ignore = E501
|
||||||
|
|
|
@ -27,3 +27,9 @@ class AuthTokenTests(TestCase):
|
||||||
def test_validate_raise_error_if_no_credentials_provided(self):
|
def test_validate_raise_error_if_no_credentials_provided(self):
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
AuthTokenSerializer().validate({})
|
AuthTokenSerializer().validate({})
|
||||||
|
|
||||||
|
def test_whitespace_in_password(self):
|
||||||
|
data = {'username': self.user.username, 'password': 'test pass '}
|
||||||
|
self.user.set_password(data['password'])
|
||||||
|
self.user.save()
|
||||||
|
assert AuthTokenSerializer(data=data).is_valid()
|
||||||
|
|
|
@ -764,6 +764,23 @@ class OrderingFilterTests(TestCase):
|
||||||
{'id': 1, 'title': 'zyx', 'text': 'abc'},
|
{'id': 1, 'title': 'zyx', 'text': 'abc'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def test_incorrecturl_extrahyphens_ordering(self):
|
||||||
|
class OrderingListView(generics.ListAPIView):
|
||||||
|
queryset = OrderingFilterModel.objects.all()
|
||||||
|
serializer_class = OrderingFilterSerializer
|
||||||
|
filter_backends = (filters.OrderingFilter,)
|
||||||
|
ordering = ('title',)
|
||||||
|
ordering_fields = ('text',)
|
||||||
|
|
||||||
|
view = OrderingListView.as_view()
|
||||||
|
request = factory.get('/', {'ordering': '--text'})
|
||||||
|
response = view(request)
|
||||||
|
assert response.data == [
|
||||||
|
{'id': 3, 'title': 'xwv', 'text': 'cde'},
|
||||||
|
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
|
||||||
|
{'id': 1, 'title': 'zyx', 'text': 'abc'},
|
||||||
|
]
|
||||||
|
|
||||||
def test_incorrectfield_ordering(self):
|
def test_incorrectfield_ordering(self):
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
queryset = OrderingFilterModel.objects.all()
|
queryset = OrderingFilterModel.objects.all()
|
||||||
|
@ -883,6 +900,7 @@ class OrderingFilterTests(TestCase):
|
||||||
queryset = OrderingFilterModel.objects.all()
|
queryset = OrderingFilterModel.objects.all()
|
||||||
filter_backends = (filters.OrderingFilter,)
|
filter_backends = (filters.OrderingFilter,)
|
||||||
ordering = ('title',)
|
ordering = ('title',)
|
||||||
|
|
||||||
# note: no ordering_fields and serializer_class specified
|
# note: no ordering_fields and serializer_class specified
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
|
|
|
@ -524,6 +524,37 @@ class TestRelationalFieldMappings(TestCase):
|
||||||
""")
|
""")
|
||||||
self.assertEqual(unicode_repr(TestSerializer()), expected)
|
self.assertEqual(unicode_repr(TestSerializer()), expected)
|
||||||
|
|
||||||
|
def test_nested_hyperlinked_relations_starred_source(self):
|
||||||
|
class TestSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = RelationalModel
|
||||||
|
depth = 1
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
extra_kwargs = {
|
||||||
|
'url': {
|
||||||
|
'source': '*',
|
||||||
|
}}
|
||||||
|
|
||||||
|
expected = dedent("""
|
||||||
|
TestSerializer():
|
||||||
|
url = HyperlinkedIdentityField(source='*', view_name='relationalmodel-detail')
|
||||||
|
foreign_key = NestedSerializer(read_only=True):
|
||||||
|
url = HyperlinkedIdentityField(view_name='foreignkeytargetmodel-detail')
|
||||||
|
name = CharField(max_length=100)
|
||||||
|
one_to_one = NestedSerializer(read_only=True):
|
||||||
|
url = HyperlinkedIdentityField(view_name='onetoonetargetmodel-detail')
|
||||||
|
name = CharField(max_length=100)
|
||||||
|
many_to_many = NestedSerializer(many=True, read_only=True):
|
||||||
|
url = HyperlinkedIdentityField(view_name='manytomanytargetmodel-detail')
|
||||||
|
name = CharField(max_length=100)
|
||||||
|
through = NestedSerializer(many=True, read_only=True):
|
||||||
|
url = HyperlinkedIdentityField(view_name='throughtargetmodel-detail')
|
||||||
|
name = CharField(max_length=100)
|
||||||
|
""")
|
||||||
|
self.maxDiff = None
|
||||||
|
self.assertEqual(unicode_repr(TestSerializer()), expected)
|
||||||
|
|
||||||
def test_nested_unique_together_relations(self):
|
def test_nested_unique_together_relations(self):
|
||||||
class TestSerializer(serializers.HyperlinkedModelSerializer):
|
class TestSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user