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/
|
*.egg-info/
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
|
bin/
|
||||||
|
include/
|
||||||
|
lib/
|
||||||
|
local/
|
||||||
|
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!.travis.yml
|
!.travis.yml
|
||||||
|
|
19
README.md
19
README.md
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
**A toolkit for building well-connected, self-describing web APIs.**
|
**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]
|
[![build-status-image]][travis]
|
||||||
|
|
||||||
|
@ -58,6 +60,20 @@ To run the tests.
|
||||||
|
|
||||||
# Changelog
|
# 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
|
## 2.1.10
|
||||||
|
|
||||||
**Date**: 17th Dec 2012
|
**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
|
[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
|
[travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=master
|
||||||
[twitter]: https://twitter.com/_tomchristie
|
[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
|
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
|
||||||
[sandbox]: http://restframework.herokuapp.com/
|
[sandbox]: http://restframework.herokuapp.com/
|
||||||
[rest-framework-2-announcement]: http://django-rest-framework.org/topics/rest-framework-2-announcement.html
|
[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]
|
* Colin Murtaugh - [cmurtaugh]
|
||||||
* Simon Pantzare - [pilt]
|
* Simon Pantzare - [pilt]
|
||||||
* Szymon Teżewski - [sunscrapers]
|
* Szymon Teżewski - [sunscrapers]
|
||||||
|
* Joel Marcotte - [joual]
|
||||||
|
* Trey Hunner - [treyhunner]
|
||||||
|
|
||||||
Many thanks to everyone who's contributed to the project.
|
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
|
## Contact
|
||||||
|
|
||||||
To contact the author directly:
|
For usage questions please see the [REST framework discussion group][group].
|
||||||
|
|
||||||
* twitter: [@_tomchristie][twitter]
|
You can also contact [@_tomchristie][twitter] directly on twitter.
|
||||||
* email: [tom@tomchristie.com][email]
|
|
||||||
|
|
||||||
[email]: mailto:tom@tomchristie.com
|
[email]: mailto:tom@tomchristie.com
|
||||||
[twitter]: http://twitter.com/_tomchristie
|
[twitter]: http://twitter.com/_tomchristie
|
||||||
|
@ -115,6 +116,7 @@ To contact the author directly:
|
||||||
[dabapps]: http://lab.dabapps.com
|
[dabapps]: http://lab.dabapps.com
|
||||||
[sandbox]: http://restframework.herokuapp.com/
|
[sandbox]: http://restframework.herokuapp.com/
|
||||||
[heroku]: http://www.heroku.com/
|
[heroku]: http://www.heroku.com/
|
||||||
|
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
||||||
|
|
||||||
[tomchristie]: https://github.com/tomchristie
|
[tomchristie]: https://github.com/tomchristie
|
||||||
[markotibold]: https://github.com/markotibold
|
[markotibold]: https://github.com/markotibold
|
||||||
|
@ -193,3 +195,5 @@ To contact the author directly:
|
||||||
[cmurtaugh]: https://github.com/cmurtaugh
|
[cmurtaugh]: https://github.com/cmurtaugh
|
||||||
[pilt]: https://github.com/pilt
|
[pilt]: https://github.com/pilt
|
||||||
[sunscrapers]: https://github.com/sunscrapers
|
[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.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
|
### 2.1.10
|
||||||
|
|
||||||
**Date**: 17th Dec 2012
|
**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:
|
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.
|
* 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.**
|
**Now go build awesome things.**
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
__version__ = '2.1.10'
|
__version__ = '2.1.12'
|
||||||
|
|
||||||
VERSION = __version__ # synonym
|
VERSION = __version__ # synonym
|
||||||
|
|
|
@ -5,6 +5,12 @@ versions of django/python, and compatibility wrappers around optional packages.
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
import django
|
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
|
# django-filter is optional
|
||||||
try:
|
try:
|
||||||
import django_filters
|
import django_filters
|
||||||
|
|
|
@ -390,6 +390,7 @@ class ManyRelatedMixin(object):
|
||||||
else:
|
else:
|
||||||
if value == ['']:
|
if value == ['']:
|
||||||
value = []
|
value = []
|
||||||
|
|
||||||
into[field_name] = [self.from_native(item) for item in value]
|
into[field_name] = [self.from_native(item) for item in value]
|
||||||
|
|
||||||
|
|
||||||
|
@ -801,7 +802,7 @@ class ChoiceField(WritableField):
|
||||||
if value == smart_unicode(k2):
|
if value == smart_unicode(k2):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if value == smart_unicode(k):
|
if value == smart_unicode(k) or value == k:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,10 @@ class UpdateModelMixin(object):
|
||||||
slug_field = self.get_slug_field()
|
slug_field = self.get_slug_field()
|
||||||
setattr(obj, slug_field, slug)
|
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):
|
class DestroyModelMixin(object):
|
||||||
"""
|
"""
|
||||||
|
@ -120,6 +124,6 @@ class DestroyModelMixin(object):
|
||||||
Should be mixed in with `SingleObjectBaseView`.
|
Should be mixed in with `SingleObjectBaseView`.
|
||||||
"""
|
"""
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
obj = self.get_object()
|
||||||
self.object.delete()
|
obj.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
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 import encoders
|
||||||
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
||||||
from rest_framework import VERSION, status
|
from rest_framework import VERSION, status
|
||||||
from rest_framework import serializers, parsers
|
from rest_framework import parsers
|
||||||
|
|
||||||
|
|
||||||
class BaseRenderer(object):
|
class BaseRenderer(object):
|
||||||
|
|
|
@ -188,6 +188,14 @@ class Request(object):
|
||||||
self._user, self._auth = self._authenticate()
|
self._user, self._auth = self._authenticate()
|
||||||
return self._auth
|
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):
|
def _load_data_and_files(self):
|
||||||
"""
|
"""
|
||||||
Parses the request content into self.DATA and self.FILES.
|
Parses the request content into self.DATA and self.FILES.
|
||||||
|
|
|
@ -160,6 +160,9 @@ class BaseSerializer(Field):
|
||||||
for key in self.opts.exclude:
|
for key in self.opts.exclude:
|
||||||
ret.pop(key, None)
|
ret.pop(key, None)
|
||||||
|
|
||||||
|
for key, field in ret.items():
|
||||||
|
field.initialize(parent=self, field_name=key)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
#####
|
#####
|
||||||
|
@ -174,13 +177,6 @@ class BaseSerializer(Field):
|
||||||
if parent.opts.depth:
|
if parent.opts.depth:
|
||||||
self.opts.depth = parent.opts.depth - 1
|
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.
|
# Methods to convert or revert from objects <--> primitive representations.
|
||||||
|
|
||||||
|
@ -507,25 +503,27 @@ class ModelSerializer(Serializer):
|
||||||
if instance is not None:
|
if instance is not None:
|
||||||
for key, val in attrs.items():
|
for key, val in attrs.items():
|
||||||
setattr(instance, key, val)
|
setattr(instance, key, val)
|
||||||
return instance
|
|
||||||
|
|
||||||
# Reverse relations
|
else:
|
||||||
for (obj, model) in self.opts.model._meta.get_all_related_m2m_objects_with_model():
|
# Reverse relations
|
||||||
field_name = obj.field.related_query_name()
|
for (obj, model) in self.opts.model._meta.get_all_related_m2m_objects_with_model():
|
||||||
if field_name in attrs:
|
field_name = obj.field.related_query_name()
|
||||||
self.m2m_data[field_name] = attrs.pop(field_name)
|
if field_name in attrs:
|
||||||
|
self.m2m_data[field_name] = attrs.pop(field_name)
|
||||||
|
|
||||||
# Forward relations
|
# Forward relations
|
||||||
for field in self.opts.model._meta.many_to_many:
|
for field in self.opts.model._meta.many_to_many:
|
||||||
if field.name in attrs:
|
if field.name in attrs:
|
||||||
self.m2m_data[field.name] = attrs.pop(field.name)
|
self.m2m_data[field.name] = attrs.pop(field.name)
|
||||||
|
|
||||||
|
instance = self.opts.model(**attrs)
|
||||||
|
|
||||||
instance = self.opts.model(**attrs)
|
|
||||||
try:
|
try:
|
||||||
instance.full_clean(exclude=self.get_validation_exclusions())
|
instance.full_clean(exclude=self.get_validation_exclusions())
|
||||||
except ValidationError, err:
|
except ValidationError, err:
|
||||||
self._errors = err.message_dict
|
self._errors = err.message_dict
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def save(self, save_m2m=True):
|
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.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.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 import permissions
|
||||||
|
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.authentication import TokenAuthentication
|
from rest_framework.authentication import TokenAuthentication
|
||||||
|
from rest_framework.compat import patterns
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.conf.urls.defaults import patterns, url
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from rest_framework.compat import patterns, url
|
||||||
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test.client import RequestFactory
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser
|
||||||
from rest_framework.authentication import BasicAuthentication
|
from rest_framework.authentication import BasicAuthentication
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils import simplejson as json
|
from django.utils import simplejson as json
|
||||||
|
@ -174,7 +175,7 @@ class TestInstanceView(TestCase):
|
||||||
content = {'text': 'foobar'}
|
content = {'text': 'foobar'}
|
||||||
request = factory.put('/1', json.dumps(content),
|
request = factory.put('/1', json.dumps(content),
|
||||||
content_type='application/json')
|
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.status_code, status.HTTP_200_OK)
|
||||||
self.assertEquals(response.data, {'id': 1, 'text': 'foobar'})
|
self.assertEquals(response.data, {'id': 1, 'text': 'foobar'})
|
||||||
updated = self.objects.get(id=1)
|
updated = self.objects.get(id=1)
|
||||||
|
@ -301,3 +302,36 @@ class TestCreateModelWithAutoNowAddField(TestCase):
|
||||||
self.assertEquals(response.status_code, status.HTTP_201_CREATED)
|
self.assertEquals(response.status_code, status.HTTP_201_CREATED)
|
||||||
created = self.objects.get(id=1)
|
created = self.objects.get(id=1)
|
||||||
self.assertEquals(created.content, 'foobar')
|
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.core.exceptions import PermissionDenied
|
||||||
from django.conf.urls.defaults import patterns, url
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.template import TemplateDoesNotExist, Template
|
from django.template import TemplateDoesNotExist, Template
|
||||||
import django.template.loader
|
import django.template.loader
|
||||||
|
from rest_framework.compat import patterns, url
|
||||||
from rest_framework.decorators import api_view, renderer_classes
|
from rest_framework.decorators import api_view, renderer_classes
|
||||||
from rest_framework.renderers import TemplateHTMLRenderer
|
from rest_framework.renderers import TemplateHTMLRenderer
|
||||||
from rest_framework.response import Response
|
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 import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils import simplejson as json
|
from django.utils import simplejson as json
|
||||||
from rest_framework import generics, status, serializers
|
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
|
from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = RequestFactory()
|
||||||
|
|
|
@ -52,6 +52,11 @@ class RESTFrameworkModel(models.Model):
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class HasPositiveIntegerAsChoice(RESTFrameworkModel):
|
||||||
|
some_choices = ((1, 'A'), (2, 'B'), (3, 'C'))
|
||||||
|
some_integer = models.PositiveIntegerField(choices=some_choices)
|
||||||
|
|
||||||
|
|
||||||
class Anchor(RESTFrameworkModel):
|
class Anchor(RESTFrameworkModel):
|
||||||
text = models.CharField(max_length=100, default='anchor')
|
text = models.CharField(max_length=100, default='anchor')
|
||||||
|
|
||||||
|
@ -161,7 +166,7 @@ class Photo(RESTFrameworkModel):
|
||||||
|
|
||||||
# Model for issue #324
|
# Model for issue #324
|
||||||
class BlankFieldModel(RESTFrameworkModel):
|
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
|
# 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 pickle
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf.urls.defaults import patterns, url, include
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
from rest_framework import status, permissions
|
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.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||||
|
@ -444,19 +443,19 @@ class CacheRenderTest(TestCase):
|
||||||
return
|
return
|
||||||
if state == None:
|
if state == None:
|
||||||
return
|
return
|
||||||
if isinstance(state,tuple):
|
if isinstance(state, tuple):
|
||||||
if not isinstance(state[0],dict):
|
if not isinstance(state[0], dict):
|
||||||
state=state[1]
|
state = state[1]
|
||||||
else:
|
else:
|
||||||
state=state[0].update(state[1])
|
state = state[0].update(state[1])
|
||||||
result = {}
|
result = {}
|
||||||
for i in state:
|
for i in state:
|
||||||
try:
|
try:
|
||||||
pickle.dumps(state[i],protocol=2)
|
pickle.dumps(state[i], protocol=2)
|
||||||
except pickle.PicklingError:
|
except pickle.PicklingError:
|
||||||
if not state[i] in seen:
|
if not state[i] in seen:
|
||||||
seen.append(state[i])
|
seen.append(state[i])
|
||||||
result[i] = cls._get_pickling_errors(state[i],seen)
|
result[i] = cls._get_pickling_errors(state[i], seen)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def http_resp(self, http_method, url):
|
def http_resp(self, http_method, url):
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
"""
|
"""
|
||||||
Tests for content parsing, and form-overloaded content parsing.
|
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.models import User
|
||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
from django.contrib.sessions.middleware import SessionMiddleware
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
|
from django.test.client import RequestFactory
|
||||||
from django.utils import simplejson as json
|
from django.utils import simplejson as json
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.authentication import SessionAuthentication
|
from rest_framework.authentication import SessionAuthentication
|
||||||
from django.test.client import RequestFactory
|
from rest_framework.compat import patterns
|
||||||
from rest_framework.parsers import (
|
from rest_framework.parsers import (
|
||||||
BaseParser,
|
BaseParser,
|
||||||
FormParser,
|
FormParser,
|
||||||
|
@ -304,3 +303,11 @@ class TestUserSetter(TestCase):
|
||||||
self.assertFalse(self.request.user.is_anonymous())
|
self.assertFalse(self.request.user.is_anonymous())
|
||||||
logout(self.request)
|
logout(self.request)
|
||||||
self.assertTrue(self.request.user.is_anonymous())
|
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 django.test import TestCase
|
||||||
|
from rest_framework.compat import patterns, url, include
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework import status
|
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 import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
from rest_framework.compat import patterns, url
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = RequestFactory()
|
||||||
|
|
|
@ -2,7 +2,7 @@ import datetime
|
||||||
import pickle
|
import pickle
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from rest_framework import serializers, fields
|
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,
|
BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
|
||||||
ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo)
|
ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo)
|
||||||
|
|
||||||
|
@ -69,6 +69,11 @@ class AlbumsSerializer(serializers.ModelSerializer):
|
||||||
model = Album
|
model = Album
|
||||||
fields = ['title'] # lists are also valid options
|
fields = ['title'] # lists are also valid options
|
||||||
|
|
||||||
|
class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = HasPositiveIntegerAsChoice
|
||||||
|
fields = ['some_integer']
|
||||||
|
|
||||||
|
|
||||||
class BasicTests(TestCase):
|
class BasicTests(TestCase):
|
||||||
def setUp(self):
|
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).']})
|
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):
|
class ModelValidationTests(TestCase):
|
||||||
def test_validate_unique(self):
|
def test_validate_unique(self):
|
||||||
"""
|
"""
|
||||||
|
@ -688,6 +699,10 @@ class BlankFieldTests(TestCase):
|
||||||
serializer = self.model_serializer_class(data=self.data)
|
serializer = self.model_serializer_class(data=self.data)
|
||||||
self.assertEquals(serializer.is_valid(), True)
|
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):
|
def test_create_not_blank_field(self):
|
||||||
"""
|
"""
|
||||||
Test to ensure blank data in a field not marked as blank=True
|
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
|
# This will raise RuntimeError if context doesn't get passed correctly to the nested Serializers
|
||||||
AlbumCollectionSerializer(album_collection, context={'context_item': 'album context'}).data
|
AlbumCollectionSerializer(album_collection, context={'context_item': 'album context'}).data
|
||||||
|
|
||||||
|
|
||||||
# Test for issue #467
|
# Test for issue #467
|
||||||
class FieldLabelTest(TestCase):
|
class FieldLabelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.test import TestCase
|
||||||
|
|
||||||
NO_SETTING = ('!', None)
|
NO_SETTING = ('!', None)
|
||||||
|
|
||||||
|
|
||||||
class TestSettingsManager(object):
|
class TestSettingsManager(object):
|
||||||
"""
|
"""
|
||||||
A class which can modify some Django settings temporarily for a
|
A class which can modify some Django settings temporarily for a
|
||||||
|
@ -19,7 +20,7 @@ class TestSettingsManager(object):
|
||||||
self._original_settings = {}
|
self._original_settings = {}
|
||||||
|
|
||||||
def set(self, **kwargs):
|
def set(self, **kwargs):
|
||||||
for k,v in kwargs.iteritems():
|
for k, v in kwargs.iteritems():
|
||||||
self._original_settings.setdefault(k, getattr(settings, k,
|
self._original_settings.setdefault(k, getattr(settings, k,
|
||||||
NO_SETTING))
|
NO_SETTING))
|
||||||
setattr(settings, k, v)
|
setattr(settings, k, v)
|
||||||
|
@ -31,7 +32,7 @@ class TestSettingsManager(object):
|
||||||
call_command('syncdb', verbosity=0)
|
call_command('syncdb', verbosity=0)
|
||||||
|
|
||||||
def revert(self):
|
def revert(self):
|
||||||
for k,v in self._original_settings.iteritems():
|
for k, v in self._original_settings.iteritems():
|
||||||
if v == NO_SETTING:
|
if v == NO_SETTING:
|
||||||
delattr(settings, k)
|
delattr(settings, k)
|
||||||
else:
|
else:
|
||||||
|
@ -57,6 +58,7 @@ class SettingsTestCase(TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.settings_manager.revert()
|
self.settings_manager.revert()
|
||||||
|
|
||||||
|
|
||||||
class TestModelsTestCase(SettingsTestCase):
|
class TestModelsTestCase(SettingsTestCase):
|
||||||
def setUp(self, *args, **kwargs):
|
def setUp(self, *args, **kwargs):
|
||||||
installed_apps = tuple(settings.INSTALLED_APPS) + ('rest_framework.tests',)
|
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'))
|
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'}
|
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]
|
[testenv:py2.7-django1.4]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
deps = django==1.4.1
|
deps = django==1.4.3
|
||||||
django-filter==0.5.4
|
django-filter==0.5.4
|
||||||
|
|
||||||
[testenv:py2.7-django1.3]
|
[testenv:py2.7-django1.3]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
deps = django==1.3.3
|
deps = django==1.3.5
|
||||||
django-filter==0.5.4
|
django-filter==0.5.4
|
||||||
|
|
||||||
[testenv:py2.6-django1.5]
|
[testenv:py2.6-django1.5]
|
||||||
|
@ -27,10 +27,10 @@ deps = https://github.com/django/django/zipball/master
|
||||||
|
|
||||||
[testenv:py2.6-django1.4]
|
[testenv:py2.6-django1.4]
|
||||||
basepython = python2.6
|
basepython = python2.6
|
||||||
deps = django==1.4.1
|
deps = django==1.4.3
|
||||||
django-filter==0.5.4
|
django-filter==0.5.4
|
||||||
|
|
||||||
[testenv:py2.6-django1.3]
|
[testenv:py2.6-django1.3]
|
||||||
basepython = python2.6
|
basepython = python2.6
|
||||||
deps = django==1.3.3
|
deps = django==1.3.5
|
||||||
django-filter==0.5.4
|
django-filter==0.5.4
|
||||||
|
|
Loading…
Reference in New Issue
Block a user