mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-23 15:54:16 +03:00
Merge remote-tracking branch 'tom/master'
Conflicts: rest_framework/tests/serializer.py
This commit is contained in:
commit
5ba2437f2d
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -10,5 +10,10 @@ dist/
|
|||
*.egg-info/
|
||||
MANIFEST
|
||||
|
||||
bin/
|
||||
include/
|
||||
lib/
|
||||
local/
|
||||
|
||||
!.gitignore
|
||||
!.travis.yml
|
||||
|
|
19
README.md
19
README.md
|
@ -2,7 +2,9 @@
|
|||
|
||||
**A toolkit for building well-connected, self-describing web APIs.**
|
||||
|
||||
**Author:** Tom Christie. [Follow me on Twitter][twitter]
|
||||
**Author:** Tom Christie. [Follow me on Twitter][twitter].
|
||||
|
||||
**Support:** [REST framework discussion group][group].
|
||||
|
||||
[![build-status-image]][travis]
|
||||
|
||||
|
@ -58,6 +60,20 @@ To run the tests.
|
|||
|
||||
# Changelog
|
||||
|
||||
### 2.1.12
|
||||
|
||||
**Date**: 21st Dec 2012
|
||||
|
||||
* Bugfix: Fix bug that could occur using ChoiceField.
|
||||
* Bugfix: Fix exception in browseable API on DELETE.
|
||||
* Bugfix: Fix issue where pk was was being set to a string if set by URL kwarg.
|
||||
|
||||
## 2.1.11
|
||||
|
||||
**Date**: 17th Dec 2012
|
||||
|
||||
* Bugfix: Fix issue with M2M fields in browseable API.
|
||||
|
||||
## 2.1.10
|
||||
|
||||
**Date**: 17th Dec 2012
|
||||
|
@ -205,6 +221,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.png?branch=restframework2
|
||||
[travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=master
|
||||
[twitter]: https://twitter.com/_tomchristie
|
||||
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
||||
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
|
||||
[sandbox]: http://restframework.herokuapp.com/
|
||||
[rest-framework-2-announcement]: http://django-rest-framework.org/topics/rest-framework-2-announcement.html
|
||||
|
|
|
@ -79,6 +79,8 @@ The following people have helped make REST framework great.
|
|||
* Colin Murtaugh - [cmurtaugh]
|
||||
* Simon Pantzare - [pilt]
|
||||
* Szymon Teżewski - [sunscrapers]
|
||||
* Joel Marcotte - [joual]
|
||||
* Trey Hunner - [treyhunner]
|
||||
|
||||
Many thanks to everyone who's contributed to the project.
|
||||
|
||||
|
@ -98,10 +100,9 @@ Development of REST framework 2.0 was sponsored by [DabApps].
|
|||
|
||||
## Contact
|
||||
|
||||
To contact the author directly:
|
||||
For usage questions please see the [REST framework discussion group][group].
|
||||
|
||||
* twitter: [@_tomchristie][twitter]
|
||||
* email: [tom@tomchristie.com][email]
|
||||
You can also contact [@_tomchristie][twitter] directly on twitter.
|
||||
|
||||
[email]: mailto:tom@tomchristie.com
|
||||
[twitter]: http://twitter.com/_tomchristie
|
||||
|
@ -115,6 +116,7 @@ To contact the author directly:
|
|||
[dabapps]: http://lab.dabapps.com
|
||||
[sandbox]: http://restframework.herokuapp.com/
|
||||
[heroku]: http://www.heroku.com/
|
||||
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
||||
|
||||
[tomchristie]: https://github.com/tomchristie
|
||||
[markotibold]: https://github.com/markotibold
|
||||
|
@ -193,3 +195,5 @@ To contact the author directly:
|
|||
[cmurtaugh]: https://github.com/cmurtaugh
|
||||
[pilt]: https://github.com/pilt
|
||||
[sunscrapers]: https://github.com/sunscrapers
|
||||
[joual]: https://github.com/joual
|
||||
[treyhunner]: https://github.com/treyhunner
|
||||
|
|
|
@ -6,6 +6,20 @@
|
|||
|
||||
## 2.1.x series
|
||||
|
||||
### 2.1.12
|
||||
|
||||
**Date**: 21st Dec 2012
|
||||
|
||||
* Bugfix: Fix bug that could occur using ChoiceField.
|
||||
* Bugfix: Fix exception in browseable API on DELETE.
|
||||
* Bugfix: Fix issue where pk was was being set to a string if set by URL kwarg.
|
||||
|
||||
### 2.1.11
|
||||
|
||||
**Date**: 17th Dec 2012
|
||||
|
||||
* Bugfix: Fix issue with M2M fields in browseable API.
|
||||
|
||||
### 2.1.10
|
||||
|
||||
**Date**: 17th Dec 2012
|
||||
|
|
|
@ -163,9 +163,9 @@ You can review the final [tutorial code][repo] on GitHub, or try out a live exam
|
|||
|
||||
We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here's a few places you can start:
|
||||
|
||||
* Contribute on [GitHub][github] by reviewing and subitting issues, and making pull requests.
|
||||
* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests.
|
||||
* Join the [REST framework discussion group][group], and help build the community.
|
||||
* Follow the author [on Twitter][twitter] and say hi.
|
||||
* [Follow the author on Twitter][twitter] and say hi.
|
||||
|
||||
**Now go build awesome things.**
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
__version__ = '2.1.10'
|
||||
__version__ = '2.1.12'
|
||||
|
||||
VERSION = __version__ # synonym
|
||||
|
|
|
@ -5,6 +5,12 @@ versions of django/python, and compatibility wrappers around optional packages.
|
|||
# flake8: noqa
|
||||
import django
|
||||
|
||||
# location of patterns, url, include changes in 1.4 onwards
|
||||
try:
|
||||
from django.conf.urls import patterns, url, include
|
||||
except:
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
|
||||
# django-filter is optional
|
||||
try:
|
||||
import django_filters
|
||||
|
|
|
@ -390,6 +390,7 @@ class ManyRelatedMixin(object):
|
|||
else:
|
||||
if value == ['']:
|
||||
value = []
|
||||
|
||||
into[field_name] = [self.from_native(item) for item in value]
|
||||
|
||||
|
||||
|
@ -801,7 +802,7 @@ class ChoiceField(WritableField):
|
|||
if value == smart_unicode(k2):
|
||||
return True
|
||||
else:
|
||||
if value == smart_unicode(k):
|
||||
if value == smart_unicode(k) or value == k:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
|
@ -113,6 +113,10 @@ class UpdateModelMixin(object):
|
|||
slug_field = self.get_slug_field()
|
||||
setattr(obj, slug_field, slug)
|
||||
|
||||
# Ensure we clean the attributes so that we don't eg return integer
|
||||
# pk using a string representation, as provided by the url conf kwarg.
|
||||
obj.full_clean()
|
||||
|
||||
|
||||
class DestroyModelMixin(object):
|
||||
"""
|
||||
|
@ -120,6 +124,6 @@ class DestroyModelMixin(object):
|
|||
Should be mixed in with `SingleObjectBaseView`.
|
||||
"""
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
self.object.delete()
|
||||
obj = self.get_object()
|
||||
obj.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
@ -20,7 +20,7 @@ from rest_framework.utils import dict2xml
|
|||
from rest_framework.utils import encoders
|
||||
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
||||
from rest_framework import VERSION, status
|
||||
from rest_framework import serializers, parsers
|
||||
from rest_framework import parsers
|
||||
|
||||
|
||||
class BaseRenderer(object):
|
||||
|
|
|
@ -188,6 +188,14 @@ class Request(object):
|
|||
self._user, self._auth = self._authenticate()
|
||||
return self._auth
|
||||
|
||||
@auth.setter
|
||||
def auth(self, value):
|
||||
"""
|
||||
Sets any non-user authentication information associated with the
|
||||
request, such as an authentication token.
|
||||
"""
|
||||
self._auth = value
|
||||
|
||||
def _load_data_and_files(self):
|
||||
"""
|
||||
Parses the request content into self.DATA and self.FILES.
|
||||
|
|
|
@ -160,6 +160,9 @@ class BaseSerializer(Field):
|
|||
for key in self.opts.exclude:
|
||||
ret.pop(key, None)
|
||||
|
||||
for key, field in ret.items():
|
||||
field.initialize(parent=self, field_name=key)
|
||||
|
||||
return ret
|
||||
|
||||
#####
|
||||
|
@ -174,13 +177,6 @@ class BaseSerializer(Field):
|
|||
if parent.opts.depth:
|
||||
self.opts.depth = parent.opts.depth - 1
|
||||
|
||||
# We need to call initialize here to ensure any nested
|
||||
# serializers that will have already called initialize on their
|
||||
# descendants get updated with *their* parent.
|
||||
# We could be a bit more smart about this, but it'll do for now.
|
||||
for key, field in self.fields.items():
|
||||
field.initialize(parent=self, field_name=key)
|
||||
|
||||
#####
|
||||
# Methods to convert or revert from objects <--> primitive representations.
|
||||
|
||||
|
@ -507,8 +503,8 @@ class ModelSerializer(Serializer):
|
|||
if instance is not None:
|
||||
for key, val in attrs.items():
|
||||
setattr(instance, key, val)
|
||||
return instance
|
||||
|
||||
else:
|
||||
# Reverse relations
|
||||
for (obj, model) in self.opts.model._meta.get_all_related_m2m_objects_with_model():
|
||||
field_name = obj.field.related_query_name()
|
||||
|
@ -521,11 +517,13 @@ class ModelSerializer(Serializer):
|
|||
self.m2m_data[field.name] = attrs.pop(field.name)
|
||||
|
||||
instance = self.opts.model(**attrs)
|
||||
|
||||
try:
|
||||
instance.full_clean(exclude=self.get_validation_exclusions())
|
||||
except ValidationError, err:
|
||||
self._errors = err.message_dict
|
||||
return None
|
||||
|
||||
return instance
|
||||
|
||||
def save(self, save_m2m=True):
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from django.conf.urls.defaults import patterns
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from django.utils import simplejson as json
|
||||
from django.http import HttpResponse
|
||||
from django.test import Client, TestCase
|
||||
from django.utils import simplejson as json
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import permissions
|
||||
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
from rest_framework.compat import patterns
|
||||
from rest_framework.views import APIView
|
||||
|
||||
import base64
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
from rest_framework.compat import patterns, url
|
||||
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
||||
from rest_framework.views import APIView
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from django.test.client import RequestFactory
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.authentication import BasicAuthentication
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils import simplejson as json
|
||||
|
@ -174,7 +175,7 @@ class TestInstanceView(TestCase):
|
|||
content = {'text': 'foobar'}
|
||||
request = factory.put('/1', json.dumps(content),
|
||||
content_type='application/json')
|
||||
response = self.view(request, pk=1).render()
|
||||
response = self.view(request, pk='1').render()
|
||||
self.assertEquals(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEquals(response.data, {'id': 1, 'text': 'foobar'})
|
||||
updated = self.objects.get(id=1)
|
||||
|
@ -301,3 +302,36 @@ class TestCreateModelWithAutoNowAddField(TestCase):
|
|||
self.assertEquals(response.status_code, status.HTTP_201_CREATED)
|
||||
created = self.objects.get(id=1)
|
||||
self.assertEquals(created.content, 'foobar')
|
||||
|
||||
|
||||
# Test for particularly ugly reression with m2m in browseable API
|
||||
class ClassB(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
|
||||
class ClassA(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
childs = models.ManyToManyField(ClassB, blank=True, null=True)
|
||||
|
||||
|
||||
class ClassASerializer(serializers.ModelSerializer):
|
||||
childs = serializers.ManyPrimaryKeyRelatedField(source='childs')
|
||||
|
||||
class Meta:
|
||||
model = ClassA
|
||||
|
||||
|
||||
class ExampleView(generics.ListCreateAPIView):
|
||||
serializer_class = ClassASerializer
|
||||
model = ClassA
|
||||
|
||||
|
||||
class TestM2MBrowseableAPI(TestCase):
|
||||
def test_m2m_in_browseable_api(self):
|
||||
"""
|
||||
Test for particularly ugly reression with m2m in browseable API
|
||||
"""
|
||||
request = factory.get('/', HTTP_ACCEPT='text/html')
|
||||
view = ExampleView().as_view()
|
||||
response = view(request).render()
|
||||
self.assertEquals(response.status_code, status.HTTP_200_OK)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
from django.http import Http404
|
||||
from django.test import TestCase
|
||||
from django.template import TemplateDoesNotExist, Template
|
||||
import django.template.loader
|
||||
from rest_framework.compat import patterns, url
|
||||
from rest_framework.decorators import api_view, renderer_classes
|
||||
from rest_framework.renderers import TemplateHTMLRenderer
|
||||
from rest_framework.response import Response
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils import simplejson as json
|
||||
from rest_framework import generics, status, serializers
|
||||
from rest_framework.compat import patterns, url
|
||||
from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel
|
||||
|
||||
factory = RequestFactory()
|
||||
|
|
|
@ -52,6 +52,11 @@ class RESTFrameworkModel(models.Model):
|
|||
abstract = True
|
||||
|
||||
|
||||
class HasPositiveIntegerAsChoice(RESTFrameworkModel):
|
||||
some_choices = ((1, 'A'), (2, 'B'), (3, 'C'))
|
||||
some_integer = models.PositiveIntegerField(choices=some_choices)
|
||||
|
||||
|
||||
class Anchor(RESTFrameworkModel):
|
||||
text = models.CharField(max_length=100, default='anchor')
|
||||
|
||||
|
@ -161,7 +166,7 @@ class Photo(RESTFrameworkModel):
|
|||
|
||||
# Model for issue #324
|
||||
class BlankFieldModel(RESTFrameworkModel):
|
||||
title = models.CharField(max_length=100, blank=True, null=True)
|
||||
title = models.CharField(max_length=100, blank=True, null=False)
|
||||
|
||||
|
||||
# Model for issue #380
|
||||
|
|
358
rest_framework/tests/relations_hyperlink.py
Normal file
358
rest_framework/tests/relations_hyperlink.py
Normal file
|
@ -0,0 +1,358 @@
|
|||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from rest_framework import serializers
|
||||
from rest_framework.compat import patterns, url
|
||||
|
||||
|
||||
def dummy_view(request, pk):
|
||||
pass
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^manytomanysource/(?P<pk>[0-9]+)/$', dummy_view, name='manytomanysource-detail'),
|
||||
url(r'^manytomanytarget/(?P<pk>[0-9]+)/$', dummy_view, name='manytomanytarget-detail'),
|
||||
url(r'^foreignkeysource/(?P<pk>[0-9]+)/$', dummy_view, name='foreignkeysource-detail'),
|
||||
url(r'^foreignkeytarget/(?P<pk>[0-9]+)/$', dummy_view, name='foreignkeytarget-detail'),
|
||||
url(r'^nullableforeignkeysource/(?P<pk>[0-9]+)/$', dummy_view, name='nullableforeignkeysource-detail'),
|
||||
)
|
||||
|
||||
|
||||
# ManyToMany
|
||||
|
||||
class ManyToManyTarget(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
|
||||
class ManyToManySource(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
targets = models.ManyToManyField(ManyToManyTarget, related_name='sources')
|
||||
|
||||
|
||||
class ManyToManyTargetSerializer(serializers.HyperlinkedModelSerializer):
|
||||
sources = serializers.ManyHyperlinkedRelatedField(view_name='manytomanysource-detail')
|
||||
|
||||
class Meta:
|
||||
model = ManyToManyTarget
|
||||
|
||||
|
||||
class ManyToManySourceSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = ManyToManySource
|
||||
|
||||
|
||||
# ForeignKey
|
||||
|
||||
class ForeignKeyTarget(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
|
||||
class ForeignKeySource(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
target = models.ForeignKey(ForeignKeyTarget, related_name='sources')
|
||||
|
||||
|
||||
class ForeignKeyTargetSerializer(serializers.HyperlinkedModelSerializer):
|
||||
sources = serializers.ManyHyperlinkedRelatedField(view_name='foreignkeysource-detail', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ForeignKeyTarget
|
||||
|
||||
|
||||
class ForeignKeySourceSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = ForeignKeySource
|
||||
|
||||
|
||||
# Nullable ForeignKey
|
||||
|
||||
class NullableForeignKeySource(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True,
|
||||
related_name='nullable_sources')
|
||||
|
||||
|
||||
class NullableForeignKeySourceSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = NullableForeignKeySource
|
||||
|
||||
|
||||
# TODO: Add test that .data cannot be accessed prior to .is_valid
|
||||
|
||||
class HyperlinkedManyToManyTests(TestCase):
|
||||
urls = 'rest_framework.tests.relations_hyperlink'
|
||||
|
||||
def setUp(self):
|
||||
for idx in range(1, 4):
|
||||
target = ManyToManyTarget(name='target-%d' % idx)
|
||||
target.save()
|
||||
source = ManyToManySource(name='source-%d' % idx)
|
||||
source.save()
|
||||
for target in ManyToManyTarget.objects.all():
|
||||
source.targets.add(target)
|
||||
|
||||
def test_many_to_many_retrieve(self):
|
||||
queryset = ManyToManySource.objects.all()
|
||||
serializer = ManyToManySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/manytomanysource/1/', 'name': u'source-1', 'targets': ['/manytomanytarget/1/']},
|
||||
{'url': '/manytomanysource/2/', 'name': u'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
|
||||
{'url': '/manytomanysource/3/', 'name': u'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_reverse_many_to_many_retrieve(self):
|
||||
queryset = ManyToManyTarget.objects.all()
|
||||
serializer = ManyToManyTargetSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/manytomanytarget/1/', 'name': u'target-1', 'sources': ['/manytomanysource/1/', '/manytomanysource/2/', '/manytomanysource/3/']},
|
||||
{'url': '/manytomanytarget/2/', 'name': u'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
|
||||
{'url': '/manytomanytarget/3/', 'name': u'target-3', 'sources': ['/manytomanysource/3/']}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_many_to_many_update(self):
|
||||
data = {'url': '/manytomanysource/1/', 'name': u'source-1', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
|
||||
instance = ManyToManySource.objects.get(pk=1)
|
||||
serializer = ManyToManySourceSerializer(instance, data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
self.assertEquals(serializer.data, data)
|
||||
serializer.save()
|
||||
|
||||
# Ensure source 1 is updated, and everything else is as expected
|
||||
queryset = ManyToManySource.objects.all()
|
||||
serializer = ManyToManySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/manytomanysource/1/', 'name': u'source-1', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']},
|
||||
{'url': '/manytomanysource/2/', 'name': u'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
|
||||
{'url': '/manytomanysource/3/', 'name': u'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_reverse_many_to_many_update(self):
|
||||
data = {'url': '/manytomanytarget/1/', 'name': u'target-1', 'sources': ['/manytomanysource/1/']}
|
||||
instance = ManyToManyTarget.objects.get(pk=1)
|
||||
serializer = ManyToManyTargetSerializer(instance, data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
self.assertEquals(serializer.data, data)
|
||||
serializer.save()
|
||||
|
||||
# Ensure target 1 is updated, and everything else is as expected
|
||||
queryset = ManyToManyTarget.objects.all()
|
||||
serializer = ManyToManyTargetSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/manytomanytarget/1/', 'name': u'target-1', 'sources': ['/manytomanysource/1/']},
|
||||
{'url': '/manytomanytarget/2/', 'name': u'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
|
||||
{'url': '/manytomanytarget/3/', 'name': u'target-3', 'sources': ['/manytomanysource/3/']}
|
||||
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_many_to_many_create(self):
|
||||
data = {'url': '/manytomanysource/4/', 'name': u'source-4', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/3/']}
|
||||
serializer = ManyToManySourceSerializer(data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
obj = serializer.save()
|
||||
self.assertEquals(serializer.data, data)
|
||||
self.assertEqual(obj.name, u'source-4')
|
||||
|
||||
# Ensure source 4 is added, and everything else is as expected
|
||||
queryset = ManyToManySource.objects.all()
|
||||
serializer = ManyToManySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/manytomanysource/1/', 'name': u'source-1', 'targets': ['/manytomanytarget/1/']},
|
||||
{'url': '/manytomanysource/2/', 'name': u'source-2', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/']},
|
||||
{'url': '/manytomanysource/3/', 'name': u'source-3', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/2/', '/manytomanytarget/3/']},
|
||||
{'url': '/manytomanysource/4/', 'name': u'source-4', 'targets': ['/manytomanytarget/1/', '/manytomanytarget/3/']}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_reverse_many_to_many_create(self):
|
||||
data = {'url': '/manytomanytarget/4/', 'name': u'target-4', 'sources': ['/manytomanysource/1/', '/manytomanysource/3/']}
|
||||
serializer = ManyToManyTargetSerializer(data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
obj = serializer.save()
|
||||
self.assertEquals(serializer.data, data)
|
||||
self.assertEqual(obj.name, u'target-4')
|
||||
|
||||
# Ensure target 4 is added, and everything else is as expected
|
||||
queryset = ManyToManyTarget.objects.all()
|
||||
serializer = ManyToManyTargetSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/manytomanytarget/1/', 'name': u'target-1', 'sources': ['/manytomanysource/1/', '/manytomanysource/2/', '/manytomanysource/3/']},
|
||||
{'url': '/manytomanytarget/2/', 'name': u'target-2', 'sources': ['/manytomanysource/2/', '/manytomanysource/3/']},
|
||||
{'url': '/manytomanytarget/3/', 'name': u'target-3', 'sources': ['/manytomanysource/3/']},
|
||||
{'url': '/manytomanytarget/4/', 'name': u'target-4', 'sources': ['/manytomanysource/1/', '/manytomanysource/3/']}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
|
||||
class HyperlinkedForeignKeyTests(TestCase):
|
||||
urls = 'rest_framework.tests.relations_hyperlink'
|
||||
|
||||
def setUp(self):
|
||||
target = ForeignKeyTarget(name='target-1')
|
||||
target.save()
|
||||
new_target = ForeignKeyTarget(name='target-2')
|
||||
new_target.save()
|
||||
for idx in range(1, 4):
|
||||
source = ForeignKeySource(name='source-%d' % idx, target=target)
|
||||
source.save()
|
||||
|
||||
def test_foreign_key_retrieve(self):
|
||||
queryset = ForeignKeySource.objects.all()
|
||||
serializer = ForeignKeySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/foreignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/foreignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_reverse_foreign_key_retrieve(self):
|
||||
queryset = ForeignKeyTarget.objects.all()
|
||||
serializer = ForeignKeyTargetSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/foreignkeytarget/1/', 'name': u'target-1', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/2/', '/foreignkeysource/3/']},
|
||||
{'url': '/foreignkeytarget/2/', 'name': u'target-2', 'sources': []},
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_foreign_key_update(self):
|
||||
data = {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/2/'}
|
||||
instance = ForeignKeySource.objects.get(pk=1)
|
||||
serializer = ForeignKeySourceSerializer(instance, data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
self.assertEquals(serializer.data, data)
|
||||
serializer.save()
|
||||
|
||||
# Ensure source 1 is updated, and everything else is as expected
|
||||
queryset = ForeignKeySource.objects.all()
|
||||
serializer = ForeignKeySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/2/'},
|
||||
{'url': '/foreignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/foreignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_foreign_key_update_with_invalid_null(self):
|
||||
data = {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': None}
|
||||
instance = ForeignKeySource.objects.get(pk=1)
|
||||
serializer = ForeignKeySourceSerializer(instance, data=data)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEquals(serializer.errors, {'target': [u'Value may not be null']})
|
||||
|
||||
|
||||
class HyperlinkedNullableForeignKeyTests(TestCase):
|
||||
urls = 'rest_framework.tests.relations_hyperlink'
|
||||
|
||||
def setUp(self):
|
||||
target = ForeignKeyTarget(name='target-1')
|
||||
target.save()
|
||||
for idx in range(1, 4):
|
||||
source = NullableForeignKeySource(name='source-%d' % idx, target=target)
|
||||
source.save()
|
||||
|
||||
def test_foreign_key_create_with_valid_null(self):
|
||||
data = {'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': None}
|
||||
serializer = NullableForeignKeySourceSerializer(data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
obj = serializer.save()
|
||||
self.assertEquals(serializer.data, data)
|
||||
self.assertEqual(obj.name, u'source-4')
|
||||
|
||||
# Ensure source 4 is created, and everything else is as expected
|
||||
queryset = NullableForeignKeySource.objects.all()
|
||||
serializer = NullableForeignKeySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': None}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_foreign_key_create_with_valid_emptystring(self):
|
||||
"""
|
||||
The emptystring should be interpreted as null in the context
|
||||
of relationships.
|
||||
"""
|
||||
data = {'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': ''}
|
||||
expected_data = {'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': None}
|
||||
serializer = NullableForeignKeySourceSerializer(data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
obj = serializer.save()
|
||||
self.assertEquals(serializer.data, expected_data)
|
||||
self.assertEqual(obj.name, u'source-4')
|
||||
|
||||
# Ensure source 4 is created, and everything else is as expected
|
||||
queryset = NullableForeignKeySource.objects.all()
|
||||
serializer = NullableForeignKeySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/nullableforeignkeysource/4/', 'name': u'source-4', 'target': None}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_foreign_key_update_with_valid_null(self):
|
||||
data = {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': None}
|
||||
instance = NullableForeignKeySource.objects.get(pk=1)
|
||||
serializer = NullableForeignKeySourceSerializer(instance, data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
self.assertEquals(serializer.data, data)
|
||||
serializer.save()
|
||||
|
||||
# Ensure source 1 is updated, and everything else is as expected
|
||||
queryset = NullableForeignKeySource.objects.all()
|
||||
serializer = NullableForeignKeySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': None},
|
||||
{'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'},
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
def test_foreign_key_update_with_valid_emptystring(self):
|
||||
"""
|
||||
The emptystring should be interpreted as null in the context
|
||||
of relationships.
|
||||
"""
|
||||
data = {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': ''}
|
||||
expected_data = {'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': None}
|
||||
instance = NullableForeignKeySource.objects.get(pk=1)
|
||||
serializer = NullableForeignKeySourceSerializer(instance, data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
self.assertEquals(serializer.data, expected_data)
|
||||
serializer.save()
|
||||
|
||||
# Ensure source 1 is updated, and everything else is as expected
|
||||
queryset = NullableForeignKeySource.objects.all()
|
||||
serializer = NullableForeignKeySourceSerializer(queryset)
|
||||
expected = [
|
||||
{'url': '/nullableforeignkeysource/1/', 'name': u'source-1', 'target': None},
|
||||
{'url': '/nullableforeignkeysource/2/', 'name': u'source-2', 'target': '/foreignkeytarget/1/'},
|
||||
{'url': '/nullableforeignkeysource/3/', 'name': u'source-3', 'target': '/foreignkeytarget/1/'},
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
||||
|
||||
# reverse foreign keys MUST be read_only
|
||||
# In the general case they do not provide .remove() or .clear()
|
||||
# and cannot be arbitrarily set.
|
||||
|
||||
# def test_reverse_foreign_key_update(self):
|
||||
# data = {'id': 1, 'name': u'target-1', 'sources': [1]}
|
||||
# instance = ForeignKeyTarget.objects.get(pk=1)
|
||||
# serializer = ForeignKeyTargetSerializer(instance, data=data)
|
||||
# self.assertTrue(serializer.is_valid())
|
||||
# self.assertEquals(serializer.data, data)
|
||||
# serializer.save()
|
||||
|
||||
# # Ensure target 1 is updated, and everything else is as expected
|
||||
# queryset = ForeignKeyTarget.objects.all()
|
||||
# serializer = ForeignKeyTargetSerializer(queryset)
|
||||
# expected = [
|
||||
# {'id': 1, 'name': u'target-1', 'sources': [1]},
|
||||
# {'id': 2, 'name': u'target-2', 'sources': []},
|
||||
# ]
|
||||
# self.assertEquals(serializer.data, expected)
|
51
rest_framework/tests/relations_nested.py
Normal file
51
rest_framework/tests/relations_nested.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
# ForeignKey
|
||||
|
||||
class ForeignKeyTarget(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
|
||||
class ForeignKeySource(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
target = models.ForeignKey(ForeignKeyTarget, related_name='sources')
|
||||
|
||||
|
||||
class ForeignKeySourceSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ForeignKeySource
|
||||
|
||||
|
||||
class ForeignKeyTargetSerializer(serializers.ModelSerializer):
|
||||
sources = ForeignKeySourceSerializer()
|
||||
|
||||
class Meta:
|
||||
model = ForeignKeyTarget
|
||||
|
||||
|
||||
class ReverseForeignKeyTests(TestCase):
|
||||
def setUp(self):
|
||||
target = ForeignKeyTarget(name='target-1')
|
||||
target.save()
|
||||
new_target = ForeignKeyTarget(name='target-2')
|
||||
new_target.save()
|
||||
for idx in range(1, 4):
|
||||
source = ForeignKeySource(name='source-%d' % idx, target=target)
|
||||
source.save()
|
||||
|
||||
def test_reverse_foreign_key_retrieve(self):
|
||||
queryset = ForeignKeyTarget.objects.all()
|
||||
serializer = ForeignKeyTargetSerializer(queryset)
|
||||
expected = [
|
||||
{'id': 1, 'name': u'target-1', 'sources': [
|
||||
{'id': 1, 'name': u'source-1', 'target': 1},
|
||||
{'id': 2, 'name': u'source-2', 'target': 1},
|
||||
{'id': 3, 'name': u'source-3', 'target': 1},
|
||||
]},
|
||||
{'id': 2, 'name': u'target-2', 'sources': [
|
||||
]}
|
||||
]
|
||||
self.assertEquals(serializer.data, expected)
|
|
@ -1,13 +1,12 @@
|
|||
import pickle
|
||||
import re
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from rest_framework import status, permissions
|
||||
from rest_framework.compat import yaml
|
||||
from rest_framework.compat import yaml, patterns, url, include
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
"""
|
||||
Tests for content parsing, and form-overloaded content parsing.
|
||||
"""
|
||||
from django.conf.urls.defaults import patterns
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.test import TestCase, Client
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils import simplejson as json
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from django.test.client import RequestFactory
|
||||
from rest_framework.compat import patterns
|
||||
from rest_framework.parsers import (
|
||||
BaseParser,
|
||||
FormParser,
|
||||
|
@ -304,3 +303,11 @@ class TestUserSetter(TestCase):
|
|||
self.assertFalse(self.request.user.is_anonymous())
|
||||
logout(self.request)
|
||||
self.assertTrue(self.request.user.is_anonymous())
|
||||
|
||||
|
||||
class TestAuthSetter(TestCase):
|
||||
|
||||
def test_auth_can_be_set(self):
|
||||
request = Request(factory.get('/'))
|
||||
request.auth = 'DUMMY'
|
||||
self.assertEqual(request.auth, 'DUMMY')
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import unittest
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.compat import patterns, url, include
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from rest_framework.compat import patterns, url
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
factory = RequestFactory()
|
||||
|
|
|
@ -2,7 +2,7 @@ import datetime
|
|||
import pickle
|
||||
from django.test import TestCase
|
||||
from rest_framework import serializers, fields
|
||||
from rest_framework.tests.models import (Album, ActionItem, Anchor, BasicModel,
|
||||
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
|
||||
BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
|
||||
ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo)
|
||||
|
||||
|
@ -69,6 +69,11 @@ class AlbumsSerializer(serializers.ModelSerializer):
|
|||
model = Album
|
||||
fields = ['title'] # lists are also valid options
|
||||
|
||||
class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = HasPositiveIntegerAsChoice
|
||||
fields = ['some_integer']
|
||||
|
||||
|
||||
class BasicTests(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -285,6 +290,12 @@ class ValidationTests(TestCase):
|
|||
self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']})
|
||||
|
||||
|
||||
class PositiveIntegerAsChoiceTests(TestCase):
|
||||
def test_positive_integer_in_json_is_correctly_parsed(self):
|
||||
data = {'some_integer':1}
|
||||
serializer = PositiveIntegerAsChoiceSerializer(data=data)
|
||||
self.assertEquals(serializer.is_valid(), True)
|
||||
|
||||
class ModelValidationTests(TestCase):
|
||||
def test_validate_unique(self):
|
||||
"""
|
||||
|
@ -688,6 +699,10 @@ class BlankFieldTests(TestCase):
|
|||
serializer = self.model_serializer_class(data=self.data)
|
||||
self.assertEquals(serializer.is_valid(), True)
|
||||
|
||||
def test_create_model_null_field(self):
|
||||
serializer = self.model_serializer_class(data={'title': None})
|
||||
self.assertEquals(serializer.is_valid(), True)
|
||||
|
||||
def test_create_not_blank_field(self):
|
||||
"""
|
||||
Test to ensure blank data in a field not marked as blank=True
|
||||
|
@ -817,6 +832,7 @@ class NestedSerializerContextTests(TestCase):
|
|||
# This will raise RuntimeError if context doesn't get passed correctly to the nested Serializers
|
||||
AlbumCollectionSerializer(album_collection, context={'context_item': 'album context'}).data
|
||||
|
||||
|
||||
# Test for issue #467
|
||||
class FieldLabelTest(TestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.test import TestCase
|
|||
|
||||
NO_SETTING = ('!', None)
|
||||
|
||||
|
||||
class TestSettingsManager(object):
|
||||
"""
|
||||
A class which can modify some Django settings temporarily for a
|
||||
|
@ -57,6 +58,7 @@ class SettingsTestCase(TestCase):
|
|||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
|
||||
class TestModelsTestCase(SettingsTestCase):
|
||||
def setUp(self, *args, **kwargs):
|
||||
installed_apps = tuple(settings.INSTALLED_APPS) + ('rest_framework.tests',)
|
||||
|
|
|
@ -12,7 +12,7 @@ your authentication settings include `SessionAuthentication`.
|
|||
url(r'^auth', include('rest_framework.urls', namespace='rest_framework'))
|
||||
)
|
||||
"""
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
from rest_framework.compat import patterns, url
|
||||
|
||||
|
||||
template_name = {'template_name': 'rest_framework/login.html'}
|
||||
|
|
8
tox.ini
8
tox.ini
|
@ -12,12 +12,12 @@ deps = https://github.com/django/django/zipball/master
|
|||
|
||||
[testenv:py2.7-django1.4]
|
||||
basepython = python2.7
|
||||
deps = django==1.4.1
|
||||
deps = django==1.4.3
|
||||
django-filter==0.5.4
|
||||
|
||||
[testenv:py2.7-django1.3]
|
||||
basepython = python2.7
|
||||
deps = django==1.3.3
|
||||
deps = django==1.3.5
|
||||
django-filter==0.5.4
|
||||
|
||||
[testenv:py2.6-django1.5]
|
||||
|
@ -27,10 +27,10 @@ deps = https://github.com/django/django/zipball/master
|
|||
|
||||
[testenv:py2.6-django1.4]
|
||||
basepython = python2.6
|
||||
deps = django==1.4.1
|
||||
deps = django==1.4.3
|
||||
django-filter==0.5.4
|
||||
|
||||
[testenv:py2.6-django1.3]
|
||||
basepython = python2.6
|
||||
deps = django==1.3.3
|
||||
deps = django==1.3.5
|
||||
django-filter==0.5.4
|
||||
|
|
Loading…
Reference in New Issue
Block a user