diff --git a/appStore/utils/customer_mixin.py b/appStore/utils/customer_mixin.py new file mode 100644 index 0000000000000000000000000000000000000000..32857db608603c6668fb9cae5946b57fccb145eb --- /dev/null +++ b/appStore/utils/customer_mixin.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +@author: wqz +@time: 8/7/19 4:29 PM +""" +from __future__ import unicode_literals + +from collections import OrderedDict +from functools import update_wrapper +from inspect import getmembers + +from django.urls import NoReverseMatch +from django.utils.decorators import classonlymethod +from django.views.decorators.csrf import csrf_exempt + +from rest_framework import generics, views +from rest_framework.reverse import reverse + +from appStore.utils.customer_mixin import CusRetrieveModelMixin, CusListModelMixin, CusCreateModelMixin, \ + CusUpdateModelMixin, \ + CusDestroyModelMixin + + +def _is_extra_action(attr): + return hasattr(attr, 'mapping') + + +class ViewSetMixin(object): + """ + This is the magic. + Overrides `.as_view()` so that it takes an `actions` keyword that performs + the binding of HTTP methods to actions on the Resource. + For example, to create a concrete view binding the 'GET' and 'POST' methods + to the 'list' and 'create' actions... + view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) + """ + + @classonlymethod + def as_view(cls, actions=None, **initkwargs): + """ + Because of the way class based views create a closure around the + instantiated view, we need to totally reimplement `.as_view`, + and slightly modify the view function that is created and returned. + """ + # The name and description initkwargs may be explicitly overridden for + # certain route confiugurations. eg, names of extra actions. + cls.name = None + cls.description = None + + # The suffix initkwarg is reserved for displaying the viewset type. + # This initkwarg should have no effect if the name is provided. + # eg. 'List' or 'Instance'. + cls.suffix = None + + # The detail initkwarg is reserved for introspecting the viewset type. + cls.detail = None + + # Setting a basename allows a view to reverse its action urls. This + # value is provided by the router through the initkwargs. + cls.basename = None + + # actions must not be empty + if not actions: + raise TypeError("The `actions` argument must be provided when " + "calling `.as_view()` on a ViewSet. For example " + "`.as_view({'get': 'list'})`") + + # sanitize keyword arguments + for key in initkwargs: + if key in cls.http_method_names: + raise TypeError("You tried to pass in the %s method name as a " + "keyword argument to %s(). Don't do that." + % (key, cls.__name__)) + if not hasattr(cls, key): + raise TypeError("%s() received an invalid keyword %r" % ( + cls.__name__, key)) + + # name and suffix are mutually exclusive + if 'name' in initkwargs and 'suffix' in initkwargs: + raise TypeError("%s() received both `name` and `suffix`, which are " + "mutually exclusive arguments." % (cls.__name__)) + + def view(request, *args, **kwargs): + self = cls(**initkwargs) + # We also store the mapping of request methods to actions, + # so that we can later set the action attribute. + # eg. `self.action = 'list'` on an incoming GET request. + self.action_map = actions + + # Bind methods to actions + # This is the bit that's different to a standard view + for method, action in actions.items(): + handler = getattr(self, action) + setattr(self, method, handler) + + if hasattr(self, 'get') and not hasattr(self, 'head'): + self.head = self.get + + self.request = request + self.args = args + self.kwargs = kwargs + + # And continue as usual + return self.dispatch(request, *args, **kwargs) + + # take name and docstring from class + update_wrapper(view, cls, updated=()) + + # and possible attributes set by decorators + # like csrf_exempt from dispatch + update_wrapper(view, cls.dispatch, assigned=()) + + # We need to set these on the view function, so that breadcrumb + # generation can pick out these bits of information from a + # resolved URL. + view.cls = cls + view.initkwargs = initkwargs + view.actions = actions + return csrf_exempt(view) + + def initialize_request(self, request, *args, **kwargs): + """ + Set the `.action` attribute on the view, depending on the request method. + """ + request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs) + method = request.method.lower() + if method == 'options': + # This is a special case as we always provide handling for the + # options method in the base `View` class. + # Unlike the other explicitly defined actions, 'metadata' is implicit. + self.action = 'metadata' + else: + self.action = self.action_map.get(method) + return request + + def reverse_action(self, url_name, *args, **kwargs): + """ + Reverse the action for the given `url_name`. + """ + url_name = '%s-%s' % (self.basename, url_name) + kwargs.setdefault('request', self.request) + + return reverse(url_name, *args, **kwargs) + + @classmethod + def get_extra_actions(cls): + """ + Get the methods that are marked as an extra ViewSet `@action`. + """ + return [method for _, method in getmembers(cls, _is_extra_action)] + + def get_extra_action_url_map(self): + """ + Build a map of {names: urls} for the extra actions. + This method will noop if `detail` was not provided as a view initkwarg. + """ + action_urls = OrderedDict() + + # exit early if `detail` has not been provided + if self.detail is None: + return action_urls + + # filter for the relevant extra actions + actions = [ + action for action in self.get_extra_actions() + if action.detail == self.detail + ] + + for action in actions: + try: + url_name = '%s-%s' % (self.basename, action.url_name) + url = reverse(url_name, self.args, self.kwargs, request=self.request) + view = self.__class__(**action.kwargs) + action_urls[view.get_view_name()] = url + except NoReverseMatch: + pass # URL requires additional arguments, ignore + + return action_urls + + +class ViewSet(ViewSetMixin, views.APIView): + """ + The base ViewSet class does not provide any actions by default. + """ + pass + + +class GenericViewSet(ViewSetMixin, generics.GenericAPIView): + """ + The GenericViewSet class does not provide any actions by default, + but does include the base set of generic view behavior, such as + the `get_object` and `get_queryset` methods. + """ + pass + + +class CusReadOnlyModelViewSet(CusRetrieveModelMixin, + CusListModelMixin, + GenericViewSet): + """ + 只读 列表和详情 + """ + pass + + +class CusReadOnlyListModelViewSet(CusListModelMixin, + GenericViewSet): + """ + 只读列表 + """ + pass + + +class CusModelViewSet(CusCreateModelMixin, + CusRetrieveModelMixin, + CusUpdateModelMixin, + CusDestroyModelMixin, + CusListModelMixin, + GenericViewSet): + """ + 都允许 + """ + pass + + +class CusUpdateModelViewSet(CusUpdateModelMixin, + GenericViewSet): + """ + 只允许更新 + """ + pass + + +class CusCreateModelViewSet(CusCreateModelMixin, + GenericViewSet): + """ + 只允许创建 + """ + pass + + +class CusNotDeViewSet(CusCreateModelMixin, + CusRetrieveModelMixin, + CusUpdateModelMixin, + CusListModelMixin, + GenericViewSet): + """ + 不允许删除 + """ + pass \ No newline at end of file