Merge remote-tracking branch 'tom/master'

Conflicts:
	rest_framework/tests/serializer.py
This commit is contained in:
Michal Dvorak (cen38289) 2012-12-21 10:53:23 +01:00
commit 5ba2437f2d
30 changed files with 589 additions and 65 deletions

5
.gitignore vendored
View File

@ -10,5 +10,10 @@ dist/
*.egg-info/
MANIFEST
bin/
include/
lib/
local/
!.gitignore
!.travis.yml

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.**

View File

@ -1,3 +1,3 @@
__version__ = '2.1.10'
__version__ = '2.1.12'
VERSION = __version__ # synonym

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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.

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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

View 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)

View 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)

View File

@ -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, \

View File

@ -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')

View File

@ -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

View File

@ -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()

View File

@ -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):

View File

@ -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',)

View File

@ -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'}

View File

@ -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