Merge master

This commit is contained in:
Tom Christie 2013-03-12 18:49:38 +00:00
commit e8db504a98
7 changed files with 32 additions and 13 deletions

View File

@ -90,12 +90,17 @@ This permission is suitable if you want to your API to allow read permissions to
## DjangoModelPermissions ## DjangoModelPermissions
This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. When applied to a view that has a `.model` property, authorization will only be granted if the user has the relevant model permissions assigned. This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. When applied to a view that has a `.model` property, authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned.
* `POST` requests require the user to have the `add` permission on the model. * `POST` requests require the user to have the `add` permission on the model.
* `PUT` and `PATCH` requests require the user to have the `change` permission on the model. * `PUT` and `PATCH` requests require the user to have the `change` permission on the model.
* `DELETE` requests require the user to have the `delete` permission on the model. * `DELETE` requests require the user to have the `delete` permission on the model.
If you want to use `DjangoModelPermissions` but also allow unauthenticated users to have read permission, override the class and set the `authenticated_users_only` property to `False`. For example:
class HasModelPermissionsOrReadOnly(DjangoModelPermissions):
authenticated_users_only = False
The default behaviour can also be overridden to support custom model permissions. For example, you might want to include a `view` model permission for `GET` requests. The default behaviour can also be overridden to support custom model permissions. For example, you might want to include a `view` model permission for `GET` requests.
To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property. Refer to the source code for details. To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property. Refer to the source code for details.

View File

@ -109,6 +109,7 @@ The following people have helped make REST framework great.
* Wiliam Souza - [waa] * Wiliam Souza - [waa]
* Jonas Braun - [iekadou] * Jonas Braun - [iekadou]
* Ian Dash - [bitmonkey] * Ian Dash - [bitmonkey]
* Bouke Haarsma - [bouke]
* Pierre Dulac - [dulaccc] * Pierre Dulac - [dulaccc]
Many thanks to everyone who's contributed to the project. Many thanks to everyone who's contributed to the project.
@ -253,4 +254,5 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
[waa]: https://github.com/wiliamsouza [waa]: https://github.com/wiliamsouza
[iekadou]: https://github.com/iekadou [iekadou]: https://github.com/iekadou
[bitmonkey]: https://github.com/bitmonkey [bitmonkey]: https://github.com/bitmonkey
[bouke]: https://github.com/bouke
[dulaccc]: https://github.com/dulaccc [dulaccc]: https://github.com/dulaccc

View File

@ -44,6 +44,8 @@ You can determine your currently installed version using `pip freeze`:
* Filtering backends are now applied to the querysets for object lookups as well as lists. (Eg you can use a filtering backend to control which objects should 404) * Filtering backends are now applied to the querysets for object lookups as well as lists. (Eg you can use a filtering backend to control which objects should 404)
* Deal with error data nicely when deserializing lists of objects. * Deal with error data nicely when deserializing lists of objects.
* Extra override hook to configure `DjangoModelPermissions` for unauthenticated users.
* Bugfix: Fix pk relationship bug for some types of 1-to-1 relations.
* Bugfix: Workaround for Django bug causing case where `Authtoken` could be registered for cascade delete from `User` even if not installed. * Bugfix: Workaround for Django bug causing case where `Authtoken` could be registered for cascade delete from `User` even if not installed.
### 2.2.3 ### 2.2.3

View File

@ -104,6 +104,8 @@ class DjangoModelPermissions(BasePermission):
'DELETE': ['%(app_label)s.delete_%(model_name)s'], 'DELETE': ['%(app_label)s.delete_%(model_name)s'],
} }
authenticated_users_only = True
def get_required_permissions(self, method, model_cls): def get_required_permissions(self, method, model_cls):
""" """
Given a model and an HTTP method, return the list of permission Given a model and an HTTP method, return the list of permission
@ -117,13 +119,18 @@ class DjangoModelPermissions(BasePermission):
def has_permission(self, request, view): def has_permission(self, request, view):
model_cls = getattr(view, 'model', None) model_cls = getattr(view, 'model', None)
if not model_cls: queryset = getattr(view, 'queryset', None)
return True
if model_cls is None and queryset is not None:
model_cls = queryset.model
assert model_cls, ('Cannot apply DjangoModelPermissions on a view that'
' does not have `.model` or `.queryset` property.')
perms = self.get_required_permissions(request.method, model_cls) perms = self.get_required_permissions(request.method, model_cls)
if (request.user and if (request.user and
request.user.is_authenticated() and (request.user.is_authenticated() or not self.authenticated_users_only) and
request.user.has_perms(perms)): request.user.has_perms(perms)):
return True return True
return False return False

View File

@ -235,7 +235,6 @@ class PrimaryKeyRelatedField(RelatedField):
pk = getattr(obj, self.source or field_name).pk pk = getattr(obj, self.source or field_name).pk
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
return self.to_native(obj.pk)
# Forward relationship # Forward relationship
return self.to_native(pk) return self.to_native(pk)

View File

@ -391,11 +391,17 @@ class BaseSerializer(Field):
return self._data return self._data
def save_object(self, obj):
obj.save()
def save(self): def save(self):
""" """
Save the deserialized object and return it. Save the deserialized object and return it.
""" """
self.object.save() if isinstance(self.object, list):
[self.save_object(item) for item in self.object]
else:
self.save_object(self.object)
return self.object return self.object
@ -612,11 +618,11 @@ class ModelSerializer(Serializer):
if instance: if instance:
return self.full_clean(instance) return self.full_clean(instance)
def save(self): def save_object(self, obj):
""" """
Save the deserialized object and return it. Save the deserialized object and return it.
""" """
self.object.save() obj.save()
if getattr(self, 'm2m_data', None): if getattr(self, 'm2m_data', None):
for accessor_name, object_list in self.m2m_data.items(): for accessor_name, object_list in self.m2m_data.items():
@ -628,8 +634,6 @@ class ModelSerializer(Serializer):
setattr(self.object, accessor_name, object_list) setattr(self.object, accessor_name, object_list)
self.related_data = {} self.related_data = {}
return self.object
class HyperlinkedModelSerializerOptions(ModelSerializerOptions): class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
""" """

View File

@ -407,14 +407,14 @@ class PKNullableOneToOneTests(TestCase):
target.save() target.save()
new_target = OneToOneTarget(name='target-2') new_target = OneToOneTarget(name='target-2')
new_target.save() new_target.save()
source = NullableOneToOneSource(name='source-1', target=target) source = NullableOneToOneSource(name='source-1', target=new_target)
source.save() source.save()
def test_reverse_foreign_key_retrieve_with_null(self): def test_reverse_foreign_key_retrieve_with_null(self):
queryset = OneToOneTarget.objects.all() queryset = OneToOneTarget.objects.all()
serializer = NullableOneToOneTargetSerializer(queryset, many=True) serializer = NullableOneToOneTargetSerializer(queryset, many=True)
expected = [ expected = [
{'id': 1, 'name': 'target-1', 'nullable_source': 1}, {'id': 1, 'name': 'target-1', 'nullable_source': None},
{'id': 2, 'name': 'target-2', 'nullable_source': None}, {'id': 2, 'name': 'target-2', 'nullable_source': 1},
] ]
self.assertEqual(serializer.data, expected) self.assertEqual(serializer.data, expected)