Django REST framework+Vue 打造生鮮超市(四)

目錄html

生鮮超市(一)    生鮮超市(二)    生鮮超市(三)   前端

生鮮超市(四)    生鮮超市(五)    生鮮超市(六)   vue

生鮮超市(七)    生鮮超市(八)    生鮮超市(九)   git

生鮮超市(十)    生鮮超市(十一)    生鮮超市(十二)    生鮮超市(十三)   github

代碼下載正則表達式

githubdjango

教程json

學習自慕課網-前端vue結合後端DjangoFramework的在線生鮮超市 後端

5、商品列表頁

5.1.django的view實現商品列表頁

(1)goods/view_base.pyapi

在goods文件夾下面新建view_base.py,爲了區分django和django rest framework的view

利用Django的view實現返回json數據

# goods/view_base.py

from django.views.generic import View
from goods.models import Goods

class GoodsListView(View):
    def get(self,request):
        #經過django的view實現商品列表頁
        json_list = []
        #獲取全部商品
        goods = Goods.objects.all()
        for good in goods:
            json_dict = {}
            #獲取商品的每一個字段,鍵值對形式
            json_dict['name'] = good.name
            json_dict['category'] = good.category.name
            json_dict['market_price'] = good.market_price
            json_list.append(json_dict)

        from django.http import HttpResponse
        import json
        #返回json,必定要指定類型content_type='application/json'
        return HttpResponse(json.dumps(json_list),content_type='application/json')

(2)MxShop/urls.py

from goods.view_base import GoodsListView

urlpatterns = [
   #商品列表頁
    path('goods/',GoodsListView.as_view(),name='goods-list')
]

訪問http://127.0.0.1:8000/goods/  能夠獲取商品列表信息的json數據

 

5.2.django的serializer序列化model

(1)model_to_dict

當字段比較多時,一個字段一個字段的提取很麻煩,能夠用model_to_dict,將model整個轉化爲dict

# goods/view_base.py

from django.views.generic import View
from goods.models import Goods

class GoodsListView(View):
    def get(self,request):
        #經過django的view實現商品列表頁
        json_list = []
        #獲取全部商品
        goods = Goods.objects.all()
        # for good in goods:
        #     json_dict = {}
        #     #獲取商品的每一個字段,鍵值對形式
        #     json_dict['name'] = good.name
        #     json_dict['category'] = good.category.name
        #     json_dict['market_price'] = good.market_price
        #     json_list.append(json_dict)

        from django.forms.models import model_to_dict
        for good in goods:
            json_dict = model_to_dict(good)
            json_list.append(json_dict)

        from django.http import HttpResponse
        import json
        #返回json,必定要指定類型content_type='application/json'
        return HttpResponse(json.dumps(json_list),content_type='application/json')

可是這樣有個問題,就是ImageFieldFile 和add_time字段不能序列化

 

 如何才能將全部字段序列化呢?就要用到django的serializers

(2)django serializer的用法

# goods/view_base.py

from django.views.generic import View
from goods.models import Goods

class GoodsListView(View):
    def get(self,request):
        #經過django的view實現商品列表頁
        json_list = []
        #獲取全部商品
        goods = Goods.objects.all()
        # for good in goods:
        #     json_dict = {}
        #     #獲取商品的每一個字段,鍵值對形式
        #     json_dict['name'] = good.name
        #     json_dict['category'] = good.category.name
        #     json_dict['market_price'] = good.market_price
        #     json_list.append(json_dict)

        import json
        from django.core import serializers
        from django.http import JsonResponse

        json_data = serializers.serialize('json',goods)
        json_data = json.loads(json_data)
        #In order to allow non-dict objects to be serialized set the safe parameter to False.
        return JsonResponse(json_data,safe=False)

 

django的serializer雖然能夠很簡單實現序列化,可是有幾個缺點

  • 字段序列化定死的,要想重組的話很是麻煩
  • 從上面截圖能夠看出來,images保存的是一個相對路徑,咱們還須要補全路徑,而這些drf均可以幫助咱們作到

以上寫了這麼多隻是爲了引入django rest framework和簡單介紹django的序列化用法,下面就是重點講解django rest framework了

 

5.3.APIview方式實現商品列表頁

(1)安裝

  • pip install coreapi                         drf的文檔支持
  • pip install django-guardian           drf對象級別的權限支持

(2)配置def文檔的url

MxShop/urls.py

from rest_framework.documentation import include_docs_urls

urlpatterns = [
    #drf文檔,title自定義
    path('docs',include_docs_urls(title='仙劍奇俠傳')),
]

 (3)配置rest_framework

settings.py中添加

INSTALLED_APPS = [
    'rest_framework',
]

MxShop/urls.py

urlpatterns = [
    path('api-auth/',include('rest_framework.urls')),
]

 

(4)goods文件夾下面新建serializers.py

用drf的序列化實現商品列表頁展現,代碼以下:

# goods/serializers.py

from rest_framework import serializers


class GoodsSerializer(serializers.Serializer):
    name = serializers.CharField(required=True,max_length=100)
    click_num = serializers.IntegerField(default=0)
    goods_front_image = serializers.ImageField()

(5)goods/views.py

# googd/views.py

from rest_framework.views import APIView
from goods.serializers import GoodsSerializer
from .models import Goods
from rest_framework.response import Response


class GoodsListView(APIView):
    '''
    商品列表
    '''
    def get(self,request,format=None):
        goods = Goods.objects.all()
        goods_serialzer = GoodsSerializer(goods,many=True)
        return Response(goods_serialzer.data)

 

 

5.4.drf的Modelserializer實現商品列表頁

上面是用Serializer實現的,須要本身手動添加字段,若是用Modelserializer,會更加的方便,直接用__all__就能夠所有序列化

# goods/serializers.py

from rest_framework import serializers
from .models import Goods

#Serializer實現商品列表頁
# class GoodsSerializer(serializers.Serializer):
#     name = serializers.CharField(required=True,max_length=100)
#     click_num = serializers.IntegerField(default=0)
#     goods_front_image = serializers.ImageField()

#ModelSerializer實現商品列表頁
class GoodsSerializer(serializers.ModelSerializer):
    class Meta:
        model = Goods
        fields = '__all__'

 

 category只顯示分類的id,Serialzer還能夠嵌套使用,覆蓋外鍵字段

# goods/serializers.py

from rest_framework import serializers
from .models import Goods,GoodsCategory

#Serializer實現商品列表頁
# class GoodsSerializer(serializers.Serializer):
#     name = serializers.CharField(required=True,max_length=100)
#     click_num = serializers.IntegerField(default=0)
#     goods_front_image = serializers.ImageField()


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = GoodsCategory
        fields = "__all__"


#ModelSerializer實現商品列表頁
class GoodsSerializer(serializers.ModelSerializer):
    #覆蓋外鍵字段
    category = CategorySerializer()
    class Meta:
        model = Goods
        fields = '__all__'

 

5.5.GenericView實現商品列表頁

(1)mixins和generic一塊兒用用

GenericAPIView繼承APIView,封裝了不少方法,比APIView功能更強大

class GenericAPIView(views.APIView):
    """
    Base class for all other generic views.
    """
    # You'll need to either set these attributes,
    # or override `get_queryset()`/`get_serializer_class()`.
    # If you are overriding a view method, it is important that you call
    # `get_queryset()` instead of accessing the `queryset` property directly,
    # as `queryset` will get evaluated only once, and those results are cached
    # for all subsequent requests.
    queryset = None
    serializer_class = None

    # If you want to use object lookups other than pk, set 'lookup_field'.
    # For more complex lookup requirements override `get_object()`.
    lookup_field = 'pk'
    lookup_url_kwarg = None

    # The filter backend classes to use for queryset filtering
    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS

    # The style to use for queryset pagination.
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

    def get_queryset(self):
        """
        Get the list of items for this view.
        This must be an iterable, and may be a queryset.
        Defaults to using `self.queryset`.

        This method should always be used rather than accessing `self.queryset`
        directly, as `self.queryset` gets evaluated only once, and those results
        are cached for all subsequent requests.

        You may want to override this if you need to provide different
        querysets depending on the incoming request.

        (Eg. return a list of items that is specific to the user)
        """
        assert self.queryset is not None, (
            "'%s' should either include a `queryset` attribute, "
            "or override the `get_queryset()` method."
            % self.__class__.__name__
        )

        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()
        return queryset

    def get_object(self):
        """
        Returns the object the view is displaying.

        You may want to override this if you need to provide non-standard
        queryset lookups.  Eg if objects are referenced using multiple
        keyword arguments in the url conf.
        """
        queryset = self.filter_queryset(self.get_queryset())

        # Perform the lookup filtering.
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

        assert lookup_url_kwarg in self.kwargs, (
            'Expected view %s to be called with a URL keyword argument '
            'named "%s". Fix your URL conf, or set the `.lookup_field` '
            'attribute on the view correctly.' %
            (self.__class__.__name__, lookup_url_kwarg)
        )

        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        obj = get_object_or_404(queryset, **filter_kwargs)

        # May raise a permission denied
        self.check_object_permissions(self.request, obj)

        return obj

    def get_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_serializer_class()
        kwargs['context'] = self.get_serializer_context()
        return serializer_class(*args, **kwargs)

    def get_serializer_class(self):
        """
        Return the class to use for the serializer.
        Defaults to using `self.serializer_class`.

        You may want to override this if you need to provide different
        serializations depending on the incoming request.

        (Eg. admins get full serialization, others get basic serialization)
        """
        assert self.serializer_class is not None, (
            "'%s' should either include a `serializer_class` attribute, "
            "or override the `get_serializer_class()` method."
            % self.__class__.__name__
        )

        return self.serializer_class

    def get_serializer_context(self):
        """
        Extra context provided to the serializer class.
        """
        return {
            'request': self.request,
            'format': self.format_kwarg,
            'view': self
        }

    def filter_queryset(self, queryset):
        """
        Given a queryset, filter it with whichever filter backend is in use.

        You are unlikely to want to override this method, although you may need
        to call it either from a list view, or from a custom `get_object`
        method if you want to apply the configured filtering backend to the
        default queryset.
        """
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset

    @property
    def paginator(self):
        """
        The paginator instance associated with the view, or `None`.
        """
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                self._paginator = self.pagination_class()
        return self._paginator

    def paginate_queryset(self, queryset):
        """
        Return a single page of results, or `None` if pagination is disabled.
        """
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset, self.request, view=self)

    def get_paginated_response(self, data):
        """
        Return a paginated style `Response` object for the given output data.
        """
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)
GenericAPIView源碼
用的時候須要定義queryset和serializer_class
GenericAPIView裏面默認爲空
  • queryset = None
  • serializer_class = None

ListModelMixin裏面list方法幫咱們作好了分頁和序列化的工做,只要調用就行了

class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        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)
ListModelMixin源碼

實現以下:

from goods.serializers import GoodsSerializer
from .models import Goods
from rest_framework.response import Response
from rest_framework import mixins
from rest_framework import generics


class GoodsListView(mixins.ListModelMixin,generics.GenericAPIView):
    '商品列表頁'
    queryset = Goods.objects.all()
    serializer_class = GoodsSerializer

    def get(self,request,*args,**kwargs):
        return self.list(request,*args,**kwargs)

 

上面的代碼優化,能夠直接繼承ListAPIView,ListAPIView主要作了兩件事:

  • ListAPIView(mixins.ListModelMixin,GenericAPIView)        繼承了這兩個類
  • 寫好了get方法

 

 咱們要獲取商品列表頁的信息,只要寫三行代碼就能夠了

class GoodsListView(generics.ListAPIView):
    '商品列表頁'
    queryset = Goods.objects.all()
    serializer_class = GoodsSerializer

 

5.6.添加分頁功能

先看rest_framework/settings.py源碼,裏面能夠找到如何配置:好比認證、權限和分頁等等

"""
Settings for REST framework are all namespaced in the REST_FRAMEWORK setting.
For example your project's `settings.py` file might look like this:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer',
    )
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    )
}

This module provides the `api_setting` object, that is used to access
REST framework settings, checking for user settings first, then falling
back to the defaults.
"""
from __future__ import unicode_literals

from importlib import import_module

from django.conf import settings
from django.test.signals import setting_changed
from django.utils import six

from rest_framework import ISO_8601

DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ),
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.AllowAny',
    ),
    'DEFAULT_THROTTLE_CLASSES': (),
    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
    'DEFAULT_VERSIONING_CLASS': None,

    # Generic view behavior
    'DEFAULT_PAGINATION_CLASS': None,
    'DEFAULT_FILTER_BACKENDS': (),

    # Schema
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema',

    # Throttling
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
    },
    'NUM_PROXIES': None,

    # Pagination
    'PAGE_SIZE': None,

    # Filtering
    'SEARCH_PARAM': 'search',
    'ORDERING_PARAM': 'ordering',

    # Versioning
    'DEFAULT_VERSION': None,
    'ALLOWED_VERSIONS': None,
    'VERSION_PARAM': 'version',

    # Authentication
    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
    'UNAUTHENTICATED_TOKEN': None,

    # View configuration
    'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
    'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',

    # Exception handling
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'NON_FIELD_ERRORS_KEY': 'non_field_errors',

    # Testing
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer'
    ),
    'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',

    # Hyperlink settings
    'URL_FORMAT_OVERRIDE': 'format',
    'FORMAT_SUFFIX_KWARG': 'format',
    'URL_FIELD_NAME': 'url',

    # Input and output formats
    'DATE_FORMAT': ISO_8601,
    'DATE_INPUT_FORMATS': (ISO_8601,),

    'DATETIME_FORMAT': ISO_8601,
    'DATETIME_INPUT_FORMATS': (ISO_8601,),

    'TIME_FORMAT': ISO_8601,
    'TIME_INPUT_FORMATS': (ISO_8601,),

    # Encoding
    'UNICODE_JSON': True,
    'COMPACT_JSON': True,
    'STRICT_JSON': True,
    'COERCE_DECIMAL_TO_STRING': True,
    'UPLOADED_FILES_USE_URL': True,

    # Browseable API
    'HTML_SELECT_CUTOFF': 1000,
    'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",

    # Schemas
    'SCHEMA_COERCE_PATH_PK': True,
    'SCHEMA_COERCE_METHOD_NAMES': {
        'retrieve': 'read',
        'destroy': 'delete'
    },
}


# List of settings that may be in string import notation.
IMPORT_STRINGS = (
    'DEFAULT_RENDERER_CLASSES',
    'DEFAULT_PARSER_CLASSES',
    'DEFAULT_AUTHENTICATION_CLASSES',
    'DEFAULT_PERMISSION_CLASSES',
    'DEFAULT_THROTTLE_CLASSES',
    'DEFAULT_CONTENT_NEGOTIATION_CLASS',
    'DEFAULT_METADATA_CLASS',
    'DEFAULT_VERSIONING_CLASS',
    'DEFAULT_PAGINATION_CLASS',
    'DEFAULT_FILTER_BACKENDS',
    'DEFAULT_SCHEMA_CLASS',
    'EXCEPTION_HANDLER',
    'TEST_REQUEST_RENDERER_CLASSES',
    'UNAUTHENTICATED_USER',
    'UNAUTHENTICATED_TOKEN',
    'VIEW_NAME_FUNCTION',
    'VIEW_DESCRIPTION_FUNCTION'
)


# List of settings that have been removed
REMOVED_SETTINGS = (
    "PAGINATE_BY", "PAGINATE_BY_PARAM", "MAX_PAGINATE_BY",
)


def perform_import(val, setting_name):
    """
    If the given setting is a string import notation,
    then perform the necessary import or imports.
    """
    if val is None:
        return None
    elif isinstance(val, six.string_types):
        return import_from_string(val, setting_name)
    elif isinstance(val, (list, tuple)):
        return [import_from_string(item, setting_name) for item in val]
    return val


def import_from_string(val, setting_name):
    """
    Attempt to import a class from a string representation.
    """
    try:
        # Nod to tastypie's use of importlib.
        module_path, class_name = val.rsplit('.', 1)
        module = import_module(module_path)
        return getattr(module, class_name)
    except (ImportError, AttributeError) as e:
        msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e)
        raise ImportError(msg)


class APISettings(object):
    """
    A settings object, that allows API settings to be accessed as properties.
    For example:

        from rest_framework.settings import api_settings
        print(api_settings.DEFAULT_RENDERER_CLASSES)

    Any setting with string import paths will be automatically resolved
    and return the class, rather than the string literal.
    """
    def __init__(self, user_settings=None, defaults=None, import_strings=None):
        if user_settings:
            self._user_settings = self.__check_user_settings(user_settings)
        self.defaults = defaults or DEFAULTS
        self.import_strings = import_strings or IMPORT_STRINGS
        self._cached_attrs = set()

    @property
    def user_settings(self):
        if not hasattr(self, '_user_settings'):
            self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
        return self._user_settings

    def __getattr__(self, attr):
        if attr not in self.defaults:
            raise AttributeError("Invalid API setting: '%s'" % attr)

        try:
            # Check if present in user settings
            val = self.user_settings[attr]
        except KeyError:
            # Fall back to defaults
            val = self.defaults[attr]

        # Coerce import strings into classes
        if attr in self.import_strings:
            val = perform_import(val, attr)

        # Cache the result
        self._cached_attrs.add(attr)
        setattr(self, attr, val)
        return val

    def __check_user_settings(self, user_settings):
        SETTINGS_DOC = "http://www.django-rest-framework.org/api-guide/settings/"
        for setting in REMOVED_SETTINGS:
            if setting in user_settings:
                raise RuntimeError("The '%s' setting has been removed. Please refer to '%s' for available settings." % (setting, SETTINGS_DOC))
        return user_settings

    def reload(self):
        for attr in self._cached_attrs:
            delattr(self, attr)
        self._cached_attrs.clear()
        if hasattr(self, '_user_settings'):
            delattr(self, '_user_settings')


api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)


def reload_api_settings(*args, **kwargs):
    setting = kwargs['setting']
    if setting == 'REST_FRAMEWORK':
        api_settings.reload()


setting_changed.connect(reload_api_settings)
rest_framework/settings源碼

 

 添加分頁功能,配置以下:

REST_FRAMEWORK = {
    #分頁
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    #每頁顯示的個數
    'PAGE_SIZE': 10,
}

 

自定也分頁功能

from rest_framework.pagination import PageNumberPagination

class GoodsPagination(PageNumberPagination):
    '''
    商品列表自定義分頁
    '''
    #默認每頁顯示的個數
    page_size = 10
    #能夠動態改變每頁顯示的個數
    page_size_query_param = 'page_size'
    #頁碼參數
    page_query_param = 'page'
    #最多能顯示多少頁
    max_page_size = 100


class GoodsListView(generics.ListAPIView):
    '商品列表頁'

    pagination_class = GoodsPagination    #分頁
    queryset = Goods.objects.all()
    serializer_class = GoodsSerializer

settings.py裏面就不用設置了

 

5.7.viewsets和router完成商品列表頁

主要用到viewsets中的GenericViewSet

再看下ViewSerMixin源碼

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 suffix initkwarg is reserved for displaying the viewset type.
        # 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))

        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.suffix = initkwargs.get('suffix', None)
        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)]
ViewSetMixin源碼

 

 ViewSets和Routers結合使用

 MxShop/yrls.py

from goods.views import GoodsListViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()

#配置goods的url
router.register(r'goods', GoodsListViewSet)

urlpatterns = [
    #商品列表頁
    re_path('^', include(router.urls)),
]

views.py

必須定義一個默認的排序方式

class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
    '商品列表頁'

    # 分頁
    pagination_class = GoodsPagination
    #這裏必需要定義一個默認的排序,不然會報錯
    queryset = Goods.objects.all().order_by('id')
    serializer_class = GoodsSerializer

 

5.8.drf的APIView、GenericView、viewsets和router的原理分析

genericViewSet 是最高的一層

往下

GenericViewSet(viewsets)     ----drf

  GenericAPIView                  ---drf

    APIView                        ---drf

      View            ----django

這些view功能的不一樣,主要的是有mixin的存在

 

mixins總共有五種:

  CreateModelMixin

  ListModelMixin

  UpdateModelMixin

  RetrieveModelMixin

  DestoryModelMixin

"""
Basic building blocks for generic class based views.

We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
from __future__ import unicode_literals

from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings


class CreateModelMixin(object):
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        serializer = self.get_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 perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}


class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        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)


class RetrieveModelMixin(object):
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)


class UpdateModelMixin(object):
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_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 perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)


class DestroyModelMixin(object):
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()
mixins.py源碼

 

以ListModelMixin爲例:

若是不繼承ListModelMixin的話,就沒法將get和商品的列表關聯起來,另外還有其中的分頁等等,都沒法實現。

還有其它幾個mixin(增刪改查局部),這些功能都是mixin作的

 

 咱們通常都是用viewsets

ViewSet類與View類其實幾乎是相同的,但提供的是read或update這些操做,而不是get或put 等HTTP動做。同時,ViewSet爲咱們提供了默認的URL結構, 使得咱們能更專一於API自己。

 Router提供了一種簡單,快速,集成的方式來定義一系列的urls

 

 5.9.drf的request和response介紹

REST framework 的 Request 類擴展與標準的 HttpRequest,並作了相應的加強,好比更加靈活的請求解析(request parsing)和認證(request authentication)。

Request 解析

REST framwork 的 Request 對象提供了靈活的請求解析,容許你使用 JSON data 或 其餘 media types 像一般處理表單數據同樣處理請求。

.data

request.data 返回請求主題的解析內容。這跟標準的 request.POST 和 request.FILES 相似,而且還具備如下特色:

  • 包括全部解析的內容,文件(file) 和 非文件(non-file inputs)。
  • 支持解析 POST 之外的 HTTP method , 好比 PUT, PATCH
  • 更加靈活,不只僅支持表單數據,傳入一樣的 JSON 數據同樣能夠正確解析,而且不用作額外的處理(意思是前端無論提交的是表單數據,仍是 JSON 數據,.data 都可以正確解析)。

.data 具體操做,之後再說~

.query_params

request.query_params 等同於 request.GET,不過其名字更加容易理解。

爲了代碼更加清晰可讀,推薦使用 request.query_params ,而不是 Django 中的 request.GET,這樣那夠讓你的代碼更加明顯的體現出 ----- 任何 HTTP method 類型均可能包含查詢參數(query parameters),而不只僅只是 'GET' 請求。

.parser

APIView 類或者 @api_view 裝飾器將根據視圖上設置的 parser_classes 或 settings 文件中的 DEFAULT_PARSER_CLASSES 設置來確保此屬性(.parsers)自動設置爲 Parser 實例列表。

一般不須要關注該屬性......

若是你非要看看它裏面是什麼,能夠打印出來看看,大概長這樣:

[<rest_framework.parsers.JSONParser object at 0x7fa850202d68>, <rest_framework.parsers.FormParser object at 0x7fa850202be0>, <rest_framework.parsers.MultiPartParser object at 0x7fa850202860>]

包含三個解析器 JSONParserFormParserMultiPartParser

注意: 若是客戶端發送格式錯誤的內容,則訪問 request.data 可能會引起 ParseError 。默認狀況下, REST framework 的 APIView 類或者 @api_view 裝飾器將捕獲錯誤並返回 400 Bad Request 響應。 若是客戶端發送的請求內容沒法解析(不一樣于格式錯誤),則會引起 UnsupportedMediaType 異常,默認狀況下會被捕獲並返回 415 Unsupported Media Type 響應。

Responses

與基本的 HttpResponse 對象不一樣,TemplateResponse 對象保留了視圖提供的用於計算響應的上下文的詳細信息。直到須要時纔會計算最終的響應輸出,也就是在後面的響應過程當中進行計算。 — Django 文檔

REST framework 經過提供一個 Response 類來支持 HTTP 內容協商,該類容許你根據客戶端請求返回不一樣的表現形式(如: JSON ,HTML 等)。

Response 類的子類是 Django 的 SimpleTemplateResponseResponse 對象使用數據進行初始化,數據應由 Python 對象(native Python primitives)組成。而後 REST framework 使用標準的 HTTP 內容協商來肯定它應該如何渲染最終響應的內容。

固然,您也能夠不使用 Response 類,直接返回常規 HttpResponse 或 StreamingHttpResponse 對象。 使用 Response 類只是提供了一個更好的交互方式,它能夠返回多種格式。

除非因爲某種緣由須要大幅度定製 REST framework ,不然應該始終對返回 Response 對象的視圖使用 APIView 類或 @api_view 裝飾器。這樣作能夠確保視圖執行內容協商,並在視圖返回以前爲響應選擇適當的渲染器。

建立 response

Response()

與普通 HttpResponse 對象不一樣,您不會使用渲染的內容實例化 Response 對象。相反,您傳遞的是未渲染的數據,可能包含任何 Python 對象。

因爲 Response 類使用的渲染器不能處理複雜的數據類型(好比 Django 的模型實例),因此須要在建立 Response 對象以前將數據序列化爲基本的數據類型。

你可使用 REST framework 的 Serializer 類來執行序列化的操做,也能夠用本身的方式來序列化。

構造方法: Response(data, status=None, template_name=None, headers=None, content_type=None)

參數:

  • data: 響應的序列化數據。
  • status: 響應的狀態代碼。默認爲200。
  • template_name: 選擇 HTMLRenderer 時使用的模板名稱。
  • headers: 設置 HTTP header,字典類型。
  • content_type: 響應的內容類型,一般渲染器會根據內容協商的結果自動設置,但有些時候須要手動指定。

屬性

.data

尚未渲染,但已經序列化的響應數據。

.status_code

狀態碼

.content

將會返回的響應內容,必須先調用 .render() 方法,才能訪問 .content 。

.template_name

只有在 response 的渲染器是 HTMLRenderer 或其餘自定義模板渲染器時才須要提供。

.accepted_renderer

用於將會返回的響應內容的渲染器實例。

從視圖返回響應以前由 APIView 或 @api_view 自動設置。

.accepted_media_type

內容協商階段選擇的媒體類型。

從視圖返回響應以前由 APIView 或 @api_view 自動設置。

.renderer_context

將傳遞給渲染器的 .render() 方法的附加的上下文信息字典。

從視圖返回響應以前由 APIView 或 @api_view 自動設置。

標準 HttpResponse 屬性

Response 類擴展於 SimpleTemplateResponse,而且響應中也提供了全部經常使用的屬性和方法。例如,您能夠用標準方式在響應中設置 header:

response = Response()
response['Cache-Control'] = 'no-cache'

.render()

與其餘任何 TemplateResponse 同樣,調用此方法將響應的序列化數據呈現爲最終響應內容。響應內容將設置爲在 accepted_renderer 實例上調用 .render(data,accepted_media_type,renderer_context) 方法的結果。

一般不須要本身調用 .render() ,由於它是由 Django 處理的。

 

5.10.drf的過濾

drf的filter用法   http://www.django-rest-framework.org/api-guide/filtering/

(1)添加到app裏面

INSTALLED_APPS = [
     'django_filters',
]

(2)新建filter.py

自定義一個過濾器

# goods/filters.py

import django_filters

from .models import Goods


class GoodsFilter(django_filters.rest_framework.FilterSet):
    '''
    商品過濾的類
    '''
    #兩個參數,name是要過濾的字段,lookup是執行的行爲,‘小與等於本店價格’
    price_min = django_filters.NumberFilter(name="shop_price", lookup_expr='gte')
    price_max = django_filters.NumberFilter(name="shop_price", lookup_expr='lte')

    class Meta:
        model = Goods
        fields = ['price_min', 'price_max']

(3)views.py

 
 
from .filters import GoodsFilter
from django_filters.rest_framework import DjangoFilterBackend

class
GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): '商品列表頁' #這裏必需要定義一個默認的排序,不然會報錯 queryset = Goods.objects.all().order_by('id') # 分頁 pagination_class = GoodsPagination serializer_class = GoodsSerializer filter_backends = (DjangoFilterBackend,) # 設置filter的類爲咱們自定義的類 filter_class = GoodsFilter

 

 

5.11.drf的搜索和排序

 添加搜索功能

搜索的字段可使用正則表達式,更加的靈活

class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
    '商品列表頁'

    #這裏必需要定義一個默認的排序,不然會報錯
    queryset = Goods.objects.all().order_by('id')
    # 分頁
    pagination_class = GoodsPagination
    serializer_class = GoodsSerializer
    filter_backends = (DjangoFilterBackend,filters.SearchFilter)

    # 設置filter的類爲咱們自定義的類
    filter_class = GoodsFilter
    #搜索,=name表示精確搜索,也可使用各類正則表達式
    search_fields = ('=name','goods_brief')

 

 添加排序功能

class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
    '商品列表頁'

    #這裏必需要定義一個默認的排序,不然會報錯
    queryset = Goods.objects.all()
    # 分頁
    pagination_class = GoodsPagination
    #序列化
    serializer_class = GoodsSerializer
    filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter)

    # 設置filter的類爲咱們自定義的類
    #過濾
    filter_class = GoodsFilter
    #搜索,=name表示精確搜索,也可使用各類正則表達式
    search_fields = ('=name','goods_brief')
    #排序
    ordering_fields = ('sold_num', 'add_time')

 

 

全部代碼

# googd/views.py

from rest_framework.views import APIView
from goods.serializers import GoodsSerializer
from .models import Goods
from rest_framework.response import Response
from rest_framework import mixins
from rest_framework import generics
from rest_framework.pagination import PageNumberPagination
from rest_framework import viewsets
from .filters import GoodsFilter
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters


class GoodsPagination(PageNumberPagination):
    '''
    商品列表自定義分頁
    '''
    #默認每頁顯示的個數
    page_size = 10
    #能夠動態改變每頁顯示的個數
    page_size_query_param = 'page_size'
    #頁碼參數
    page_query_param = 'page'
    #最多能顯示多少頁
    max_page_size = 100


class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
    '商品列表頁'

    #這裏必需要定義一個默認的排序,不然會報錯
    queryset = Goods.objects.all()
    # 分頁
    pagination_class = GoodsPagination
    #序列化
    serializer_class = GoodsSerializer
    filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter)

    # 設置filter的類爲咱們自定義的類
    #過濾
    filter_class = GoodsFilter
    #搜索,=name表示精確搜索,也可使用各類正則表達式
    search_fields = ('=name','goods_brief')
    #排序
    ordering_fields = ('sold_num', 'add_time')
views.py
# MxShop/urls.py
__author__ = 'derek'


from django.urls import path,include,re_path
import xadmin
from django.views.static import serve
from MxShop.settings import MEDIA_ROOT
# from goods.view_base import GoodsListView

from rest_framework.documentation import include_docs_urls
from goods.views import GoodsListViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()

#配置goods的url
router.register(r'goods', GoodsListViewSet)

urlpatterns = [
    path('xadmin/', xadmin.site.urls),
    path('api-auth/',include('rest_framework.urls')),
    path('ueditor/',include('DjangoUeditor.urls' )),
    #文件
    path('media/<path:path>',serve,{'document_root':MEDIA_ROOT}),
    #drf文檔,title自定義
    path('docs',include_docs_urls(title='仙劍奇俠傳')),
    #商品列表頁
    re_path('^', include(router.urls)),
]
MxShop/urls.py

 

Django REST framework+Vue 打造生鮮超市(三)

Django REST framework+Vue 打造生鮮超市(二)

Django REST framework+Vue 打造生鮮超市(一)

相關文章
相關標籤/搜索