I'm building a simple API using django-tastypie. The idea is I have two resources:
TL;DR: I am unable to limit Note editing to a Note's creator while still allowing any user to comment on a Note.
I am using the following setup for Authentication:
class CreatedByEditAuthorization(Authorization):
def is_authorized(self, request, object=None, **kwargs):
return True
def apply_limits(self, request, object_list):
if request and request.method != 'GET' and hasattr(request, 'user'):
return object_list.filter(created_by=request.user)
return object_list
In short, a user is only authorized to edit objects for which they are equal to the created_by property (they can only edit the objects they created).
This is linked as follows:
class NoteResource(ModelResource):
comments = fields.ToManyField('myapp.api.resources.CommentResource', 'comments', null=True, blank=True)
created_by = fields.ToOneField('account.api.resources.UserResource', 'created_by')
def obj_create(self, bundle, request, **kwargs):
return super(HapResource, self).obj_create(bundle, request, created_by=request.user)
class Meta:
queryset = Note.objects.all()
allowed_methods = ['get', 'put', 'post']
authorization = CreatedByEditAuthorization()
so here, when an object is created, I automatically attach the current user to the created_by
attribute and link it to the proper Authorization.
A Comment
resource is simple and just has a ForeignKey
to a Note
resource.
The problem is this: if user A creates a Note and user B attempts to comment on that Note, tastypie sends (or simulates) a POST request to edit that Note. That attempt is rejected as user B did not create the Note, so creating the comment fails.
The question is this: Is there a way to either:
Thanks in advance for any insights.
Edit:
I have a big fat hack that can accomplish this. I'm fairly sure it's safe, but I'm not positive; I'll try constructing some queries to make sure. Instead of using fields.ForeignKey
in Comment
to relate to Note
, I create a custom field:
class SafeForeignKey(fields.ForeignKey):
def build_related_resource(self, value, request=None, related_obj=None, related_name=None):
temp = request.method
if isinstance(value, basestring):
request.method = 'GET'
ret = super(SafeForeignKey, self).build_related_resource(value, request, related_obj, related_name)
request.method = temp
return ret
Each time we try to construct this related resource, we mark the request as a GET
(since we expect it to be matched to a SELECT
query rather than an UPDATE
which matches to PUT
or POST
). This is really ugly and potentially unsafe if used incorrectly, and I'm hoping for a better solution.
Edit 2: From reading the tastypie source, as far as I can tell there is no way to filter authorization by the query that will actually get sent.
As per discussion on https://github.com/toastdriven/django-tastypie/issues/480#issuecomment-5561036:
The method that determines if a Resource
can be updated is can_update
. Therefore, to make this work in the "proper" way, you need to create a subclass of NoteResource
:
class SafeNoteResource(NoteResource):
def can_update(self):
return False
class Meta:
queryset = Note.objects.all()
allowed_methods = ['get']
authorization = Authorization()
# You MUST set this to the same resource_name as NoteResource
resource_name = 'note'
then let CommentResource
link to notes in the standard way: note = fields.ForeignKey(SafeNoteResource, 'note')
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With