From 220c49a7252ace05e0fcd75ae308dc4309cbe222 Mon Sep 17 00:00:00 2001 From: winkidney Date: Tue, 19 Feb 2019 18:57:36 +0800 Subject: [PATCH] Feature: add basic drf-api for user/pin --- core/drf_api.py | 117 +++++++++++++++++++++++++++++++++++++++++ core/models.py | 26 +++++++++ core/permissions.py | 42 +++++++++++++++ core/urls.py | 1 + pinry/settings/base.py | 10 +++- pinry/urls.py | 8 +++ 6 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 core/drf_api.py create mode 100644 core/permissions.py diff --git a/core/drf_api.py b/core/drf_api.py new file mode 100644 index 0000000..96f74ff --- /dev/null +++ b/core/drf_api.py @@ -0,0 +1,117 @@ +from rest_framework import serializers, viewsets, routers +from taggit.models import Tag + +from core.models import Image, Pin +from core.permissions import IsOwnerOrReadOnly +from django_images.models import Thumbnail +from django.conf import settings +from users.models import User + + +class UserSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = User + fields = ( + 'username', + 'gravatar', + 'url', + ) + + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + + +class ThumbnailSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Thumbnail + fields = ( + "image", + "width", + "height", + ) + + +class ImageSerializer(serializers.ModelSerializer): + class Meta: + model = Image + fields = ( + "image", + "width", + "height", + "standard", + "thumbnail", + "square", + ) + + standard = ThumbnailSerializer(read_only=True) + thumbnail = ThumbnailSerializer(read_only=True) + square = ThumbnailSerializer(read_only=True) + + +class TagSerializer(serializers.ModelSerializer): + class Meta: + model = Tag + fields = ("name", ) + + +class PinSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Pin + fields = ( + settings.DRF_URL_FIELD_NAME, + "id", + "submitter", + "url", + "origin", + "description", + "referer", + "image", + "tags", + ) + + tags = serializers.SlugRelatedField( + many=True, + source="tag_list", + queryset=Tag.objects.all(), + slug_field="name", + ) + image = ImageSerializer(required=False) + + def create(self, validated_data): + image_file = validated_data.pop('image') + if validated_data['url']: + image = Image.objects.create_for_url( + validated_data['url'], + validated_data['referer'], + ) + else: + image = Image.objects.create(image=image_file['image']) + pin = Pin.objects.create(image=image, **validated_data) + tags = validated_data.pop('tag_list') + if tags: + pin.tags.set(*tags) + return pin + + def update(self, instance, validated_data): + tags = validated_data.pop('tag_list') + if tags: + instance.tags.set(*tags) + image_file = validated_data.pop('image', None) + if image_file: + image = Image.objects.create(image=image_file['image']) + instance.image = image + return super(PinSerializer, self).update(instance, validated_data) + + +class PinViewSet(viewsets.ModelViewSet): + queryset = Pin.objects.all() + serializer_class = PinSerializer + filter_fields = ('submitter__username', ) + permission_classes = [IsOwnerOrReadOnly("submitter"), ] + + +drf_router = routers.DefaultRouter() +drf_router.register(r'users', UserViewSet) +drf_router.register(r'pins', PinViewSet) diff --git a/core/models.py b/core/models.py index 766a598..59ee382 100644 --- a/core/models.py +++ b/core/models.py @@ -43,9 +43,32 @@ class ImageManager(models.Manager): class Image(BaseImage): objects = ImageManager() + class Sizes: + standard = "standard" + thumbnail = "thumbnail" + square = "square" + class Meta: proxy = True + @property + def standard(self): + return Thumbnail.objects.get( + original=self, size=self.Sizes.standard + ) + + @property + def thumbnail(self): + return Thumbnail.objects.get( + original=self, size=self.Sizes.thumbnail + ) + + @property + def square(self): + return Thumbnail.objects.get( + original=self, size=self.Sizes.square + ) + class Pin(models.Model): submitter = models.ForeignKey(User) @@ -57,6 +80,9 @@ class Pin(models.Model): published = models.DateTimeField(auto_now_add=True) tags = TaggableManager() + def tag_list(self): + return self.tags.all() + def __unicode__(self): return '%s - %s' % (self.submitter, self.published) diff --git a/core/permissions.py b/core/permissions.py new file mode 100644 index 0000000..0df1293 --- /dev/null +++ b/core/permissions.py @@ -0,0 +1,42 @@ +from rest_framework import permissions + + +class IsOwnerOrReadOnly(permissions.IsAuthenticatedOrReadOnly): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + def __init__(self, owner_field_name="owner"): + self.__owner_field_name = owner_field_name + + def __call__(self): + return self + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS: + return True + + return getattr(obj, self.__owner_field_name) == request.user + + +class OwnerOnly(permissions.IsAuthenticatedOrReadOnly): + + def has_permission(self, request, view): + return request.user.is_authenticated() + + def has_object_permission(self, request, view, obj): + return obj.owner == request.user + + +class SuperUserOnly(permissions.BasePermission): + """ + The request is authenticated as a user, or is a read-only request. + """ + + def has_permission(self, request, view): + return request.user.is_superuser + + def has_object_permission(self, request, view, obj): + return request.user.is_superuser diff --git a/core/urls.py b/core/urls.py index 1a45826..49ef287 100644 --- a/core/urls.py +++ b/core/urls.py @@ -3,6 +3,7 @@ from django.views.generic import TemplateView from tastypie.api import Api +from core.drf_api import drf_router from .api import ImageResource, ThumbnailResource, PinResource, UserResource from .views import CreateImage diff --git a/pinry/settings/base.py b/pinry/settings/base.py index 55def71..bcea051 100644 --- a/pinry/settings/base.py +++ b/pinry/settings/base.py @@ -15,6 +15,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', + 'django_filters', 'taggit', 'compressor', 'django_images', @@ -142,10 +143,17 @@ IS_TEST = False IMAGE_AUTO_DELETE = True # Rest Framework + +DRF_URL_FIELD_NAME = "resource_link" + REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' - ] + ], + 'DEFAULT_FILTER_BACKENDS': ( + 'django_filters.rest_framework.DjangoFilterBackend', + ), + 'URL_FIELD_NAME': DRF_URL_FIELD_NAME, } diff --git a/pinry/urls.py b/pinry/urls.py index 1311a18..e296fa7 100644 --- a/pinry/urls.py +++ b/pinry/urls.py @@ -4,10 +4,18 @@ from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.contrib import admin from django.views.static import serve +from core.drf_api import drf_router + + admin.autodiscover() urlpatterns = [ + # drf api + url(r'^drf_api/', include(drf_router.urls)), + url(r'^api-auth/', include('rest_framework.urls', namespace="rest_framework")), + + # old api and views url(r'^admin/', include(admin.site.urls)), url(r'', include('core.urls', namespace='core')), url(r'', include('users.urls', namespace='users')),