diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 859e8d52a..784aa585e 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -195,9 +195,9 @@ For more details on using filter sets see the [django-filter documentation][djan ## SearchFilter -The `SearchFilterBackend` class supports simple single query parameter based searching, and is based on the [Django admin's search functionality][search-django-admin]. +The `SearchFilter` class supports simple single query parameter based searching, and is based on the [Django admin's search functionality][search-django-admin]. -The `SearchFilterBackend` class will only be applied if the view has a `search_fields` attribute set. The `search_fields` attribute should be a list of names of text type fields on the model, such as `CharField` or `TextField`. +The `SearchFilter` class will only be applied if the view has a `search_fields` attribute set. The `search_fields` attribute should be a list of names of text type fields on the model, such as `CharField` or `TextField`. class UserListView(generics.ListAPIView): queryset = User.objects.all() diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 4483f1707..b5dce5040 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -169,6 +169,7 @@ The following people have helped make REST framework great. * Edmond Wong - [edmondwong] * Ben Reilly - [bwreilly] * Tai Lee - [mrmachine] +* Markus Kaiserswerth - [mkai] Many thanks to everyone who's contributed to the project. @@ -374,3 +375,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [edmondwong]: https://github.com/edmondwong [bwreilly]: https://github.com/bwreilly [mrmachine]: https://github.com/mrmachine +[mkai]: https://github.com/mkai diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 30966a106..6ff97f37d 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -147,7 +147,7 @@ Similarly, we can control the format of the request that we send, using the `Con # POST using form data curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123" - {"id": 3, "title": "", "code": "123", "linenos": false, "language": "python", "style": "friendly"} + {"id": 3, "title": "", "code": "print 123", "linenos": false, "language": "python", "style": "friendly"} # POST using JSON curl -X POST http://127.0.0.1:8000/snippets/ -d '{"code": "print 456"}' -H "Content-Type: application/json" diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 06eec3c4e..80bb9abb4 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -85,7 +85,7 @@ Right, we'd better write some views then. Open `quickstart/views.py` and get ty queryset = Group.objects.all() serializer_class = GroupSerializer -Rather that write multiple views we're grouping together all the common behavior into classes called `ViewSets`. +Rather than write multiple views we're grouping together all the common behavior into classes called `ViewSets`. We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely organized as well as being very concise. diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b9d1dae6b..581e29fc7 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -80,6 +80,14 @@ except ImportError: Image = None +def get_model_name(model_cls): + try: + return model_cls._meta.model_name + except AttributeError: + # < 1.6 used module_name instead of model_name + return model_cls._meta.module_name + + def get_concrete_model(model_cls): try: return model_cls._meta.concrete_model diff --git a/rest_framework/filters.py b/rest_framework/filters.py index b8fe7f77e..e287a1683 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -4,7 +4,7 @@ returned by list views. """ from __future__ import unicode_literals from django.db import models -from rest_framework.compat import django_filters, six, guardian +from rest_framework.compat import django_filters, six, guardian, get_model_name from functools import reduce import operator @@ -158,7 +158,7 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend): model_cls = queryset.model kwargs = { 'app_label': model_cls._meta.app_label, - 'model_name': model_cls._meta.module_name + 'model_name': get_model_name(model_cls) } permission = self.perm_format % kwargs return guardian.shortcuts.get_objects_for_user(user, permission, queryset) diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 531847988..ab6655e7b 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -8,7 +8,8 @@ import warnings SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] from django.http import Http404 -from rest_framework.compat import oauth2_provider_scope, oauth2_constants +from rest_framework.compat import (get_model_name, oauth2_provider_scope, + oauth2_constants) class BasePermission(object): @@ -116,7 +117,7 @@ class DjangoModelPermissions(BasePermission): """ kwargs = { 'app_label': model_cls._meta.app_label, - 'model_name': model_cls._meta.module_name + 'model_name': get_model_name(model_cls) } return [perm % kwargs for perm in self.perms_map[method]] @@ -177,7 +178,7 @@ class DjangoObjectPermissions(DjangoModelPermissions): def get_required_object_permissions(self, method, model_cls): kwargs = { 'app_label': model_cls._meta.app_label, - 'model_name': model_cls._meta.module_name + 'model_name': get_model_name(model_cls) } return [perm % kwargs for perm in self.perms_map[method]] diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index a63c7f6c2..8d2e0feb8 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -518,6 +518,9 @@ class BaseSerializer(WritableField): """ Save the deserialized object and return it. """ + # Clear cached _data, which may be invalidated by `save()` + self._data = None + if isinstance(self.object, list): [self.save_object(item, **kwargs) for item in self.object] diff --git a/rest_framework/tests/test_permissions.py b/rest_framework/tests/test_permissions.py index d08124f48..6e3a63034 100644 --- a/rest_framework/tests/test_permissions.py +++ b/rest_framework/tests/test_permissions.py @@ -4,7 +4,7 @@ from django.db import models from django.test import TestCase from django.utils import unittest from rest_framework import generics, status, permissions, authentication, HTTP_HEADER_ENCODING -from rest_framework.compat import guardian +from rest_framework.compat import guardian, get_model_name from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.test import APIRequestFactory from rest_framework.tests.models import BasicModel @@ -202,7 +202,7 @@ class ObjectPermissionsIntegrationTests(TestCase): # give everyone model level permissions, as we are not testing those everyone = Group.objects.create(name='everyone') - model_name = BasicPermModel._meta.module_name + model_name = get_model_name(BasicPermModel) app_label = BasicPermModel._meta.app_label f = '{0}_{1}'.format perms = { diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index c24976603..8d246b010 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models from django.db.models.fields import BLANK_CHOICE_DASH @@ -136,6 +137,7 @@ class BasicTests(TestCase): 'Happy new year!', datetime.datetime(2012, 1, 1) ) + self.actionitem = ActionItem(title='Some to do item',) self.data = { 'email': 'tom@example.com', 'content': 'Happy new year!', @@ -264,6 +266,20 @@ class BasicTests(TestCase): """ self.assertRaises(AssertionError, PersonSerializerInvalidReadOnly, []) + def test_serializer_data_is_cleared_on_save(self): + """ + Check _data attribute is cleared on `save()` + + Regression test for #1116 + — id field is not populated if `data` is accessed prior to `save()` + """ + serializer = ActionItemSerializer(self.actionitem) + self.assertIsNone(serializer.data.get('id',None), 'New instance. `id` should not be set.') + serializer.save() + self.assertIsNotNone(serializer.data.get('id',None), 'Model is saved. `id` should be set.') + + + class DictStyleSerializer(serializers.Serializer): """