Merge commit '3b258d69c92e9d9293f7c5d1690f0ca434e677e3' into file_and_image_fields

This commit is contained in:
Marko Tibold 2012-11-15 22:48:22 +01:00
commit 403886b79b
17 changed files with 57 additions and 23 deletions

View File

@ -139,7 +139,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[twitter]: https://twitter.com/_tomchristie [twitter]: https://twitter.com/_tomchristie
[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]: topics/rest-framework-2-announcement.md [rest-framework-2-announcement]: http://django-rest-framework.org/topics/rest-framework-2-announcement.html
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[docs]: http://django-rest-framework.org/ [docs]: http://django-rest-framework.org/

View File

@ -97,6 +97,21 @@ If successfully authenticated, `TokenAuthentication` provides the following cred
**Note:** If you use `TokenAuthentication` in production you must ensure that your API is only available over `https` only. **Note:** If you use `TokenAuthentication` in production you must ensure that your API is only available over `https` only.
If you want every user to have an automatically generated Token, you can simply catch the User's `post_save` signal.
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
If you've already created some User`'s, you can run a script like this.
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
for user in User.objects.all():
Token.objects.get_or_create(user=user)
## OAuthAuthentication ## OAuthAuthentication
This policy uses the [OAuth 2.0][oauth] protocol to authenticate requests. OAuth is appropriate for server-server setups, such as when you want to allow a third-party service to access your API on a user's behalf. This policy uses the [OAuth 2.0][oauth] protocol to authenticate requests. OAuth is appropriate for server-server setups, such as when you want to allow a third-party service to access your API on a user's behalf.

View File

@ -59,6 +59,7 @@ The following people have helped make REST framework great.
* Toni Michel - [tonimichel] * Toni Michel - [tonimichel]
* Ben Konrath - [benkonrath] * Ben Konrath - [benkonrath]
* Marc Aymerich - [glic3rinu] * Marc Aymerich - [glic3rinu]
* Ludwig Kraatz - [ludwigkraatz]
Many thanks to everyone who's contributed to the project. Many thanks to everyone who's contributed to the project.
@ -153,3 +154,4 @@ To contact the author directly:
[tonimichel]: https://github.com/tonimichel [tonimichel]: https://github.com/tonimichel
[benkonrath]: https://github.com/benkonrath [benkonrath]: https://github.com/benkonrath
[glic3rinu]: https://github.com/glic3rinu [glic3rinu]: https://github.com/glic3rinu
[ludwigkraatz]: https://github.com/ludwigkraatz

View File

@ -1,6 +1,6 @@
""" """
The `compat` module provides support for backwards compatibility with older The `compat` module provides support for backwards compatibility with older
versions of django/python, and compatbility wrappers around optional packages. versions of django/python, and compatibility wrappers around optional packages.
""" """
# flake8: noqa # flake8: noqa
import django import django

View File

@ -17,7 +17,7 @@ def api_view(http_method_names):
) )
# Note, the above allows us to set the docstring. # Note, the above allows us to set the docstring.
# It is the equivelent of: # It is the equivalent of:
# #
# class WrappedAPIView(APIView): # class WrappedAPIView(APIView):
# pass # pass

View File

@ -323,7 +323,7 @@ class RelatedField(WritableField):
choices = property(_get_choices, _set_choices) choices = property(_get_choices, _set_choices)
### Regular serializier stuff... ### Regular serializer stuff...
def field_to_native(self, obj, field_name): def field_to_native(self, obj, field_name):
value = getattr(obj, self.source or field_name) value = getattr(obj, self.source or field_name)

View File

@ -91,11 +91,11 @@ class SingleObjectAPIView(SingleObjectMixin, GenericAPIView):
pk_url_kwarg = 'pk' # Not provided in Django 1.3 pk_url_kwarg = 'pk' # Not provided in Django 1.3
slug_url_kwarg = 'slug' # Not provided in Django 1.3 slug_url_kwarg = 'slug' # Not provided in Django 1.3
def get_object(self): def get_object(self, queryset=None):
""" """
Override default to add support for object-level permissions. Override default to add support for object-level permissions.
""" """
obj = super(SingleObjectAPIView, self).get_object() obj = super(SingleObjectAPIView, self).get_object(queryset)
if not self.has_permission(self.request, obj): if not self.has_permission(self.request, obj):
self.permission_denied(self.request) self.permission_denied(self.request)
return obj return obj

View File

@ -19,9 +19,16 @@ class CreateModelMixin(object):
if serializer.is_valid(): if serializer.is_valid():
self.pre_save(serializer.object) self.pre_save(serializer.object)
self.object = serializer.save() self.object = serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED) headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def get_success_headers(self, data):
if 'url' in data:
return {'Location': data.get('url')}
else:
return {}
def pre_save(self, obj): def pre_save(self, obj):
pass pass

View File

@ -4,7 +4,7 @@ Renderers are used to serialize a response into specific media types.
They give us a generic way of being able to handle various media types They give us a generic way of being able to handle various media types
on the response, such as JSON encoded data or HTML output. on the response, such as JSON encoded data or HTML output.
REST framework also provides an HTML renderer the renders the browseable API. REST framework also provides an HTML renderer the renders the browsable API.
""" """
import copy import copy
import string import string

View File

@ -15,14 +15,17 @@ class Response(SimpleTemplateResponse):
Alters the init arguments slightly. Alters the init arguments slightly.
For example, drop 'template_name', and instead use 'data'. For example, drop 'template_name', and instead use 'data'.
Setting 'renderer' and 'media_type' will typically be defered, Setting 'renderer' and 'media_type' will typically be deferred,
For example being set automatically by the `APIView`. For example being set automatically by the `APIView`.
""" """
super(Response, self).__init__(None, status=status) super(Response, self).__init__(None, status=status)
self.data = data self.data = data
self.headers = headers and headers[:] or []
self.template_name = template_name self.template_name = template_name
self.exception = exception self.exception = exception
if headers:
for name,value in headers.iteritems():
self[name] = value
@property @property
def rendered_content(self): def rendered_content(self):

View File

@ -89,7 +89,7 @@ class BaseSerializer(Field):
pass pass
_options_class = SerializerOptions _options_class = SerializerOptions
_dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatability with unsorted implementations. _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations.
def __init__(self, instance=None, data=None, files=None, context=None, **kwargs): def __init__(self, instance=None, data=None, files=None, context=None, **kwargs):
super(BaseSerializer, self).__init__(**kwargs) super(BaseSerializer, self).__init__(**kwargs)
@ -165,7 +165,7 @@ class BaseSerializer(Field):
self.opts.depth = parent.opts.depth - 1 self.opts.depth = parent.opts.depth - 1
##### #####
# Methods to convert or revert from objects <--> primative representations. # Methods to convert or revert from objects <--> primitive representations.
def get_field_key(self, field_name): def get_field_key(self, field_name):
""" """
@ -246,7 +246,7 @@ class BaseSerializer(Field):
def to_native(self, obj): def to_native(self, obj):
""" """
Serialize objects -> primatives. Serialize objects -> primitives.
""" """
if hasattr(obj, '__iter__'): if hasattr(obj, '__iter__'):
return [self.convert_object(item) for item in obj] return [self.convert_object(item) for item in obj]
@ -254,7 +254,7 @@ class BaseSerializer(Field):
def from_native(self, data, files): def from_native(self, data, files):
""" """
Deserialize primatives -> objects. Deserialize primitives -> objects.
""" """
if hasattr(data, '__iter__') and not isinstance(data, dict): if hasattr(data, '__iter__') and not isinstance(data, dict):
# TODO: error data when deserializing lists # TODO: error data when deserializing lists
@ -336,7 +336,7 @@ class ModelSerializer(Serializer):
""" """
Return all the fields that should be serialized for the model. Return all the fields that should be serialized for the model.
""" """
# TODO: Modfiy this so that it's called on init, and drop # TODO: Modify this so that it's called on init, and drop
# serialize/obj/data arguments. # serialize/obj/data arguments.
# #
# We *could* provide a hook for dynamic fields, but # We *could* provide a hook for dynamic fields, but

View File

@ -152,7 +152,7 @@ class APISettings(object):
def validate_setting(self, attr, val): def validate_setting(self, attr, val):
if attr == 'FILTER_BACKEND' and val is not None: if attr == 'FILTER_BACKEND' and val is not None:
# Make sure we can initilize the class # Make sure we can initialize the class
val() val()
api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS) api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)

View File

@ -8,12 +8,13 @@ factory = RequestFactory()
class BlogPostCommentSerializer(serializers.ModelSerializer): class BlogPostCommentSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='blogpostcomment-detail')
text = serializers.CharField() text = serializers.CharField()
blog_post_url = serializers.HyperlinkedRelatedField(source='blog_post', view_name='blogpost-detail') blog_post_url = serializers.HyperlinkedRelatedField(source='blog_post', view_name='blogpost-detail')
class Meta: class Meta:
model = BlogPostComment model = BlogPostComment
fields = ('text', 'blog_post_url') fields = ('text', 'blog_post_url', 'url')
class PhotoSerializer(serializers.Serializer): class PhotoSerializer(serializers.Serializer):
@ -53,6 +54,9 @@ class BlogPostCommentListCreate(generics.ListCreateAPIView):
model = BlogPostComment model = BlogPostComment
serializer_class = BlogPostCommentSerializer serializer_class = BlogPostCommentSerializer
class BlogPostCommentDetail(generics.RetrieveAPIView):
model = BlogPostComment
serializer_class = BlogPostCommentSerializer
class BlogPostDetail(generics.RetrieveAPIView): class BlogPostDetail(generics.RetrieveAPIView):
model = BlogPost model = BlogPost
@ -80,6 +84,7 @@ urlpatterns = patterns('',
url(r'^manytomany/(?P<pk>\d+)/$', ManyToManyDetail.as_view(), name='manytomanymodel-detail'), url(r'^manytomany/(?P<pk>\d+)/$', ManyToManyDetail.as_view(), name='manytomanymodel-detail'),
url(r'^posts/(?P<pk>\d+)/$', BlogPostDetail.as_view(), name='blogpost-detail'), url(r'^posts/(?P<pk>\d+)/$', BlogPostDetail.as_view(), name='blogpost-detail'),
url(r'^comments/$', BlogPostCommentListCreate.as_view(), name='blogpostcomment-list'), url(r'^comments/$', BlogPostCommentListCreate.as_view(), name='blogpostcomment-list'),
url(r'^comments/(?P<pk>\d+)/$', BlogPostCommentDetail.as_view(), name='blogpostcomment-detail'),
url(r'^albums/(?P<title>\w[\w-]*)/$', AlbumDetail.as_view(), name='album-detail'), url(r'^albums/(?P<title>\w[\w-]*)/$', AlbumDetail.as_view(), name='album-detail'),
url(r'^photos/$', PhotoListCreate.as_view(), name='photo-list'), url(r'^photos/$', PhotoListCreate.as_view(), name='photo-list'),
url(r'^optionalrelation/(?P<pk>\d+)/$', OptionalRelationDetail.as_view(), name='optionalrelationmodel-detail'), url(r'^optionalrelation/(?P<pk>\d+)/$', OptionalRelationDetail.as_view(), name='optionalrelationmodel-detail'),
@ -191,6 +196,7 @@ class TestCreateWithForeignKeys(TestCase):
request = factory.post('/comments/', data=data) request = factory.post('/comments/', data=data)
response = self.create_view(request).render() response = self.create_view(request).render()
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response['Location'], 'http://testserver/comments/1/')
self.assertEqual(self.post.blogpostcomment_set.count(), 1) self.assertEqual(self.post.blogpostcomment_set.count(), 1)
self.assertEqual(self.post.blogpostcomment_set.all()[0].text, 'A test comment') self.assertEqual(self.post.blogpostcomment_set.all()[0].text, 'A test comment')
@ -215,6 +221,7 @@ class TestCreateWithForeignKeysAndCustomSlug(TestCase):
request = factory.post('/photos/', data=data) request = factory.post('/photos/', data=data)
response = self.list_create_view(request).render() response = self.list_create_view(request).render()
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertNotIn('Location', response, msg='Location should only be included if there is a "url" field on the serializer')
self.assertEqual(self.post.photo_set.count(), 1) self.assertEqual(self.post.photo_set.count(), 1)
self.assertEqual(self.post.photo_set.all()[0].description, 'A test photo') self.assertEqual(self.post.photo_set.all()[0].description, 'A test photo')

View File

@ -106,7 +106,7 @@ class ThrottlingTests(TestCase):
if expect is not None: if expect is not None:
self.assertEquals(response['X-Throttle-Wait-Seconds'], expect) self.assertEquals(response['X-Throttle-Wait-Seconds'], expect)
else: else:
self.assertFalse('X-Throttle-Wait-Seconds' in response.headers) self.assertFalse('X-Throttle-Wait-Seconds' in response)
def test_seconds_fields(self): def test_seconds_fields(self):
""" """

View File

@ -4,7 +4,7 @@ from rest_framework.settings import api_settings
def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
""" """
Supplement existing urlpatterns with corrosponding patterns that also Supplement existing urlpatterns with corresponding patterns that also
include a '.format' suffix. Retains urlpattern ordering. include a '.format' suffix. Retains urlpattern ordering.
urlpatterns: urlpatterns:

View File

@ -1,7 +1,7 @@
""" """
Login and logout views for the browseable API. Login and logout views for the browsable API.
Add these to your root URLconf if you're using the browseable API and Add these to your root URLconf if you're using the browsable API and
your API requires authentication. your API requires authentication.
The urls must be namespaced as 'rest_framework', and you should make sure The urls must be namespaced as 'rest_framework', and you should make sure

View File

@ -140,7 +140,7 @@ class APIView(View):
def http_method_not_allowed(self, request, *args, **kwargs): def http_method_not_allowed(self, request, *args, **kwargs):
""" """
Called if `request.method` does not corrospond to a handler method. Called if `request.method` does not correspond to a handler method.
""" """
raise exceptions.MethodNotAllowed(request.method) raise exceptions.MethodNotAllowed(request.method)