from django.contrib.auth import get_permission_codename from django.db.models.base import ModelBase from django.utils.functional import cached_property from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination from rest_framework.permissions import BasePermission from rest_framework.response import Response from rest_framework.routers import DefaultRouter from rest_framework.serializers import ModelSerializer from rest_framework.decorators import action as base_action class AlreadyRegistered(Exception): pass class NotRegistered(Exception): pass class ImproperlyConfigured(Exception): pass class AuthPermissionViewSetMixin: NOT_FOUND_PERMISSION_DEFAULT = False permission_map = dict() def get_permission_map(self): permission_map = { 'list': self._make_permission_key('view'), 'retrieve': self._make_permission_key('view'), 'create': self._make_permission_key('add'), 'update': self._make_permission_key('change'), 'delete': self._make_permission_key('delete'), } permission_map.update(self.permission_map) return permission_map @cached_property def _options(self): return self.get_queryset().model._meta def _make_permission_key(self, action): code_name = get_permission_codename(action, self._options) return "{0}.{1}".format(self._options.app_label, code_name) def _has_perm_action(self, action, request, obj=None): if not action: return False perm_map = self.get_permission_map() if hasattr(getattr(self, action), 'permission'): perm_map.update(**{action: getattr(self, action).permission}) if action not in perm_map: return self.NOT_FOUND_PERMISSION_DEFAULT perm_code = perm_map[action] if callable(perm_code): return perm_code(self, action, request, obj) if isinstance(perm_code, bool): return perm_code return request.user.has_perm( perm_code ) class IsStaffAccess(BasePermission): """ Allows access only to authenticated Trainee users. """ def has_permission(self, request, view): return bool(request.user and request.user.is_authenticated and request.user.is_staff) def has_object_permission(self, request, view, obj): """ Return `True` if permission is granted, `False` otherwise. """ return self.has_permission(request, view) class HasPermissionAccess(BasePermission): """ Allows access only to authenticated Trainee users. """ def has_permission(self, request, view): assert hasattr(view, 'get_permission_map'), """ Must be inherit from RestFulModelAdmin to use this permission """ return view._has_perm_action(view.action, request) def has_object_permission(self, request, view, obj): """ Return `True` if permission is granted, `False` otherwise. """ return view._has_perm_action(view.action, request, obj) class RestFulModelAdmin(AuthPermissionViewSetMixin, viewsets.ModelViewSet): queryset = None single_serializer_class = None permission_classes = (IsStaffAccess, HasPermissionAccess) @staticmethod def get_doc(): return 'asd' def get_urls(self): return [] def get_single_serializer_class(self): return self.single_serializer_class if self.single_serializer_class else self.get_serializer_class() def get_single_serializer(self, *args, **kwargs): """ Return the serializer instance that should be used for validating and deserializing input, and for serializing output. """ serializer_class = self.get_single_serializer_class() kwargs['context'] = self.get_serializer_context() return serializer_class(*args, **kwargs) def list(self, request, *args, **kwargs): """list all of objects""" queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) def create(self, request, **kwargs): """Create new object""" serializer = self.get_single_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def retrieve(self, request, pk=None, **kwargs): """Get object Details""" instance = self.get_object() serializer = self.get_single_serializer(instance) return Response(serializer.data) def update(self, request, pk=None, **kwargs): """Update object""" partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_single_serializer(instance, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to # forcibly invalidate the prefetch cache on the instance. instance._prefetched_objects_cache = {} return Response(serializer.data) def partial_update(self, request, pk=None, **kwargs): """Partial Update""" return super().partial_update(request, pk=pk, **kwargs) def destroy(self, request, pk=None, **kwargs): """Delete object""" instance = self.get_object() self.perform_destroy(instance) return Response(status=status.HTTP_204_NO_CONTENT) class BaseModelSerializer(ModelSerializer): class Meta: pass class RestFulAdminSite: def __init__(self, view_class=RestFulModelAdmin): self._registry = {} self._url_patterns = [] self.default_view_class = view_class def register_decorator(self, *model_or_iterable, **options): def wrapper(view_class): self.register(model_or_iterable, view_class, **options) return view_class return wrapper def register(self, model_or_iterable, view_class=None, **options): if not view_class: view_class = self.default_view_class if isinstance(model_or_iterable, ModelBase): model_or_iterable = [model_or_iterable] for model in model_or_iterable: if model._meta.abstract: raise ImproperlyConfigured( 'The model %s is abstract, so it cannot be registered with admin.' % model.__name__ ) if model in self._registry: raise AlreadyRegistered('The model %s is already registered' % model.__name__) options.update({ "__doc__": self.generate_docs(model) }) view_class = type("%sAdmin" % model.__name__, (view_class,), options) # self.set_docs(view_class, model) # Instantiate the admin class to save in the registry self._registry[model] = view_class def register_url_pattern(self, url_pattern): self._url_patterns.append(url_pattern) @classmethod def generate_docs(cls, model): return """ ### The APIs include: > `GET` {app}/{model} ===> list all `{verbose_name_plural}` page by page; > `POST` {app}/{model} ===> create a new `{verbose_name}` > `GET` {app}/{model}/123 ===> return the details of the `{verbose_name}` 123 > `PATCH` {app}/{model}/123 and `PUT` {app}/{model}/123 ==> update the `{verbose_name}` 123 > `DELETE` {app}/{model}/123 ===> delete the `{verbose_name}` 123 > `OPTIONS` {app}/{model} ===> show the supported verbs regarding endpoint `{app}/{model}` > `OPTIONS` {app}/{model}/123 ===> show the supported verbs regarding endpoint `{app}/{model}/123` """.format( app=model._meta.app_label, model=model._meta.model_name, verbose_name=model._meta.verbose_name, verbose_name_plural=model._meta.verbose_name_plural ) def unregister(self, model_or_iterable): """ Unregister the given model(s). If a model isn't already registered, raise NotRegistered. """ if isinstance(model_or_iterable, ModelBase): model_or_iterable = [model_or_iterable] for model in model_or_iterable: if model not in self._registry: raise NotRegistered('The model %s is not registered' % model.__name__) del self._registry[model] def is_registered(self, model): """ Check if a model class is registered with this `AdminSite`. """ return model in self._registry def get_model_basename(self, model): return None def get_model_url(self, model): return '%s/%s' % (model._meta.app_label, model._meta.model_name) def get_urls(self): router = DefaultRouter() view_sets = [] for model, view_set in self._registry.items(): if view_set.queryset is None: view_set.queryset = model.objects.all() if view_set.serializer_class is None: serializer_class = type("%sModelSerializer" % model.__name__, (ModelSerializer,), { "Meta": type("Meta", (object,), { "model": model, "fields": "__all__" }), }) view_set.serializer_class = serializer_class view_sets.append(view_set) router.register(self.get_model_url(model), view_set, self.get_model_basename(model)) return router.urls + self._url_patterns @property def urls(self): return self.get_urls(), 'django_restful_admin', 'django_restful_admin' site = RestFulAdminSite() def register(*model_or_iterable, **options): return site.register_decorator(*model_or_iterable, **options) def action(permission=None, methods=None, detail=None, url_path=None, url_name=None, **kwargs): def decorator(func): base_func = base_action(methods, detail, url_path, url_name, **kwargs)(func) base_func.permission = permission return base_func return decorator