from django.core.exceptions import ObjectDoesNotExist from tastypie import fields from tastypie.authorization import DjangoAuthorization from tastypie.constants import ALL, ALL_WITH_RELATIONS from tastypie.exceptions import Unauthorized from tastypie.resources import ModelResource from django_images.models import Thumbnail from .models import Pin, Image from users.models import User def _is_pin_owner(obj_or_list, user): assert obj_or_list is not None if not isinstance(obj_or_list, (tuple, list)): obj_or_list = (obj_or_list,) results = tuple( obj.submitter == user for obj in obj_or_list if isinstance(obj, Pin) ) if len(results) <= 0: raise ValueError( "You should never check permission on %s with this function." % obj_or_list ) return all(results) def _is_authenticated_and_owner(object_list, bundle): if bundle.request.user.is_anonymous(): return object_list.none() return object_list.filter(submitter=bundle.request.user) class PinryAuthorization(DjangoAuthorization): """ Pinry-specific Authorization backend with object-level permission checking. """ def _is_obj_owner(self, object_list, bundle): klass = self.base_checks(bundle.request, bundle.obj.__class__) if klass is False: raise Unauthorized("You are not allowed to access that resource.") return _is_pin_owner(bundle.obj, bundle.request.user) def read_list(self, object_list, bundle): # This assumes a ``QuerySet`` from ``ModelResource``. return object_list def read_detail(self, object_list, bundle): """ User can always read detail of any Pin object. """ return True def create_detail(self, object_list, bundle): return self._is_obj_owner(object_list, bundle) def update_detail(self, object_list, bundle): return self._is_obj_owner(object_list, bundle) def delete_detail(self, object_list, bundle): return self._is_obj_owner(object_list, bundle) def update_list(self, object_list, bundle): return _is_authenticated_and_owner(object_list, bundle) def delete_list(self, object_list, bundle): return _is_authenticated_and_owner(object_list, bundle) class ImageAuthorization(DjangoAuthorization): """ Pinry-specific Authorization backend with object-level permission checking. """ def __init__(self): DjangoAuthorization.__init__(self) def read_list(self, object_list, bundle): return object_list def read_detail(self, object_list, bundle): """ User can always read detail of any Pin object. """ return True def create_detail(self, object_list, bundle): return bundle.request.user.is_authenticated() def update_detail(self, object_list, bundle): return bundle.request.user.is_authenticated() def delete_detail(self, object_list, bundle): return bundle.request.user.is_authenticated() def update_list(self, object_list, bundle): if not bundle.request.user.is_authenticated(): return object_list.none() return object_list def delete_list(self, object_list, bundle): if not bundle.request.user.is_authenticated(): return object_list.none() return object_list class UserResource(ModelResource): gravatar = fields.CharField(readonly=True) def dehydrate_gravatar(self, bundle): return bundle.obj.gravatar class Meta: list_allowed_methods = ['get'] filtering = { 'username': ALL } queryset = User.objects.all() resource_name = 'user' fields = ['username'] include_resource_uri = False def filter_generator_for(size): def wrapped_func(bundle, **kwargs): if hasattr(bundle.obj, '_prefetched_objects_cache') and 'thumbnail' in bundle.obj._prefetched_objects_cache: for thumbnail in bundle.obj._prefetched_objects_cache['thumbnail']: if thumbnail.size == size: return thumbnail raise ObjectDoesNotExist() else: return bundle.obj.get_by_size(size) return wrapped_func class ThumbnailResource(ModelResource): class Meta: list_allowed_methods = ['get'] fields = ['image', 'width', 'height'] queryset = Thumbnail.objects.all() resource_name = 'thumbnail' include_resource_uri = False class ImageResource(ModelResource): standard = fields.ToOneField( ThumbnailResource, full=True, attribute=lambda bundle: filter_generator_for('standard')(bundle), related_name='thumbnail', ) thumbnail = fields.ToOneField( ThumbnailResource, full=True, attribute=lambda bundle: filter_generator_for('thumbnail')(bundle), related_name='thumbnail', ) square = fields.ToOneField( ThumbnailResource, full=True, attribute=lambda bundle: filter_generator_for('square')(bundle), related_name='thumbnail', ) class Meta: fields = ['image', 'width', 'height'] include_resource_uri = False resource_name = 'image' queryset = Image.objects.all() authorization = ImageAuthorization() class PinResource(ModelResource): submitter = fields.ToOneField(UserResource, 'submitter', full=True) image = fields.ToOneField(ImageResource, 'image', full=True) tags = fields.ListField() def hydrate_image(self, bundle): url = bundle.data.get('url', None) if url: image = Image.objects.create_for_url( url, referer=bundle.data.get('referer', None), ) bundle.data['image'] = '/api/v1/image/{}/'.format(image.pk) return bundle def hydrate(self, bundle): """Run some early/generic processing Make sure that user is authorized to create Pins first, before we hydrate the Image resource, creating the Image object in process """ submitter = bundle.data.get('submitter', None) if not submitter: bundle.data['submitter'] = '/api/v1/user/{}/'.format(bundle.request.user.pk) else: if not '/api/v1/user/{}/'.format(bundle.request.user.pk) == submitter: raise Unauthorized("You are not authorized to create Pins for other users") return bundle def dehydrate_tags(self, bundle): return list(map(str, bundle.obj.tags.all())) def build_filters(self, filters=None, ignore_bad_filters=False): orm_filters = super(PinResource, self).build_filters(filters, ignore_bad_filters=ignore_bad_filters) if filters and 'tag' in filters: orm_filters['tags__name__in'] = filters['tag'].split(',') return orm_filters def save_m2m(self, bundle): tags = bundle.data.get('tags', None) if tags: bundle.obj.tags.set(*tags) return super(PinResource, self).save_m2m(bundle) class Meta: fields = ['id', 'url', 'origin', 'description', 'referer'] ordering = ['id'] filtering = { 'submitter': ALL_WITH_RELATIONS } queryset = Pin.objects.all().select_related('submitter', 'image'). \ prefetch_related('image__thumbnail_set', 'tags') resource_name = 'pin' include_resource_uri = False always_return_data = True authorization = PinryAuthorization()