23.7.2. 用Django REST framework实现豆瓣API应用

1.序列化器和视图

1.1 序列化

1. Serializer和ModelSerializer序列化选择

我们对Django REST framework的两种序列化方式做一个总结:Serializer和ModelSerializer两种序列化方式中,前者比较容易理解,适用于新手;后者则在商业项目中被使用的更多,在实际开发中建议大家多使用后者。

许多教材中都将Django REST framework的Serializer和ModelSerializer,与Django的Form和ModelForm做对比,虽然二者相似,在优劣选择上却是不同的。Form虽然没有ModelForm效率高,但是ModelForm的使用增加了项目的耦合度,不符合项目解耦原则,所以Form比ModelForm更优(除了字段量过大的情况);

而ModelSerializer有Serializer所有的优点,同时并没有比Serializer明显的不足之外,所以ModelSerializer比Serializer更优。

ModelSerializer与常规的Serializer相同,但提供了:

  • 自动推断需要序列化的字段及类型

  • 提供对字段数据的验证器的默认实现

  • 提供了修改数据需要用到的 .create().update() 方法的默认实现

另外我们还可以在 fileds 列表里挑选出需要的数据,以便减小数据的体积。

2.Serializer序列化方式

serializers.py

from rest_framework import serializers
from .models import UserProfile, Book


class BookSerializer(serializers.Serializer):
    title = serializers.CharField(required=True, max_length=100)
    isbn = serializers.CharField(required=True, max_length=100)
    author = serializers.CharField(required=True, max_length=100)
    publish = serializers.CharField(required=True, max_length=100)
    rate = serializers.FloatField(default=0)

views.py

class BookAPIView1(APIView):
    """
    使用Serializer
    """

    def get(self, request, format=None):
        APIKey = self.request.query_params.get("apikey", 0)
        developer = UserProfile.objects.filter(APIkey=APIKey).first()
        if developer:
            balance = developer.money
            if balance > 0:
                isbn = self.request.query_params.get("isbn", 0)
                books = Book.objects.filter(isbn=int(isbn))
                books_serializer = BookSerializer(books, many=True)
                developer.money -= 1
                developer.save()
                return Response(books_serializer.data)
            else:
                return Response("兄弟,又到了需要充钱的时候!好开心啊!")
        else:
            return Response("查无此人啊")

3.ModelSerializer序列化方式

serializers.py

class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        # 将整个表的所有字段都序列化
        # fields = "__all__"
        fields = ('title', 'isbn', 'author')    #指定序列化某些字段

views.py

class BookAPIView2(APIView):
    """
    使用ModelSerializer
    """

    def get(self, request, format=None):
        APIKey = self.request.query_params.get("apikey", 0)
        developer = UserProfile.objects.filter(APIkey=APIKey).first()
        if developer:
            balance = developer.money
            if balance > 0:
                isbn = self.request.query_params.get("isbn", 0)
                books = Book.objects.filter(isbn=int(isbn))
                books_serializer = BookModelSerializer(books, many=True)
                developer.money -= 1
                developer.save()
                return Response(books_serializer.data)
            else:
                return Response("兄弟,又到了需要充钱的时候!好开心啊!")
        else:
            return Response("查无此人啊")

4.HyperlinkedModelSerializer序列化方式

HyperlinkedModelSerializer 基本上与之前用的 ModelSerializer 差不多,区别是它自动提供了外键字段的超链接,并且默认不包含模型对象的 id 字段

HyperlinkedModelSerializerModelSerializer有以下区别:

  • 默认情况下不包括id字段。

  • 它包含一个url字段,使用HyperlinkedIdentityField

  • 关联关系使用HyperlinkedRelatedField,而不是PrimaryKeyRelatedField

参考文献:

https://q1mi.github.io/Django-REST-framework-documentation/api-guide/serializers_zh/#hyperlinkedmodelserializer

5.总结

ModelSerializer比Serializer是模型序列化的首选方案!

参考文献:

https://www.cnblogs.com/gengfenglog/p/14658470.html#_lab2_0_4

https://www.cuiliangblog.cn/detail/article/13

1.2 Django REST framework视图三层封装

1.2.1 mixins.ListModelMixin+GenericAPIView

使用mixins.ListModelMixin+generics.GenericAPIView对APIView进行一次封装,至少需要加一个get函数

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

views.py

class BookMixinView1(mixins.ListModelMixin, generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer

    def get(self, request, *args, **kwargs):  # 如果这里不加get函数,代表默认不支持get访问这个api,所以必须加上
        APIKey = self.request.query_params.get("apikey", 0)
        developer = UserProfile.objects.filter(APIkey=APIKey).first()
        if developer:
            balance = developer.money
            if balance > 0:
                isbn = self.request.query_params.get("isbn", 0)
                developer.money -= 1
                developer.save()
                self.queryset = Book.objects.filter(isbn=int(isbn))
                return self.list(request, *args, **kwargs)
            else:
                return Response("兄弟,又到了需要充钱的时候!好开心啊!")
        else:
            return Response("查无此人啊")

1.3 generics.ListAPIView

generics.ListAPIView相对于mixins.ListModelMixin+generics.GenericAPIView而言,所谓的封装,就是封装了一个get函数罢了。

views.py

class BookMixinView2(generics.ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer

    def get(self, request, *args, **kwargs):
        APIKey = self.request.query_params.get("apikey", 0)
        developer = UserProfile.objects.filter(APIkey=APIKey).first()
        if developer:
            balance = developer.money
            if balance > 0:
                isbn = self.request.query_params.get("isbn", 0)
                developer.money -= 1
                developer.save()
                self.queryset = Book.objects.filter(isbn=int(isbn))
                return self.list(request, *args, **kwargs)
            else:
                return Response("兄弟,又到了需要充钱的时候!好开心啊!")
        else:
            return Response("查无此人啊")

1.4 viewsets+Router

views.py

class IsDeveloper(BasePermission):
    message = '查无此人啊'

    def has_permission(self, request, view):
        APIKey = request.query_params.get("apikey", 0)
        developer = UserProfile.objects.filter(APIkey=APIKey).first()
        if developer:
            return True
        else:
            print(self.message)
            return False


class EnoughMoney(BasePermission):
    message = "兄弟,又到了需要充钱的时候!好开心啊!"

    def has_permission(self, request, view):
        APIKey = request.query_params.get("apikey", 0)
        developer = UserProfile.objects.filter(APIkey=APIKey).first()
        balance = developer.money
        if balance > 0:
            developer.money -= 1
            developer.save()
            return True
        else:
            return False


class BookModelViewSet(viewsets.ModelViewSet):
    authentication_classes = []
    permission_classes = [IsDeveloper, EnoughMoney]
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer

    def get_queryset(self):
        isbn = self.request.query_params.get("isbn", 0)
        books = Book.objects.filter(isbn=int(isbn))
        queryset = books
        return queryset

urls.py

from django.contrib import admin
from django.urls import path
from users.views import BookAPIView1, BookAPIView2, BookMixinView1, BookMixinView2, BookModelViewSet
from rest_framework.routers import DefaultRouter
from django.conf.urls import include

router = DefaultRouter()
router.register(r'apibook5', BookModelViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),
    # Serializer
    path('apibook1/', BookAPIView1.as_view(), name='book1'),
    # ModelSerializer
    path('apibook2/', BookAPIView2.as_view(), name='book2'),

    # 用mixins.ListModelMixin+GenericAPIView的方式实现视图封装
    path('apibook3/', BookMixinView1.as_view(), name='book3'),
    # 用generics.ListAPIView的方式实现视图封装
    path('apibook4/', BookMixinView2.as_view(), name='book4'),
    # 用viewsets+Router的方式实现视图封装
    path('', include(router.urls)),
]

使用Postman对API进行测试,用GET的方式访问:


http://127.0.0.1:8000/apibook1/?apikey=abcdefghigklmn&isbn=777777
http://127.0.0.1:8000/apibook2/?apikey=abcdefghigklmn&isbn=777777
http://127.0.0.1:8000/apibook3/?apikey=abcdefghigklmn&isbn=777777
http://127.0.0.1:8000/apibook4/?apikey=abcdefghigklmn&isbn=777777
http://127.0.0.1:8000/apibook5/?apikey=abcdefghigklmn&isbn=777777

1.2 视图

1.2.1 常规Django视图

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class JSONResponse(HttpResponse):
    """
    An HttpResponse that renders its content into JSON.
    """
    def __init__(self, data, **kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)

@csrf_exempt
def snippet_list(request):
    """
    列出所有的code snippet,或创建一个新的snippet。
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JSONResponse(serializer.data)
    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data, status=201)
        return JSONResponse(serializer.errors, status=400)


@csrf_exempt
def snippet_detail(request, pk):
    """
    获取,更新或删除一个 code snippet。
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)
    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JSONResponse(serializer.data)
    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data)
        return JSONResponse(serializer.errors, status=400)
    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

urls.py

from django.conf.urls import url
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]

1.2.2 APIview

1 基于函数的视图

views.py

from django.shortcuts import render

# Create your views here.
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Article
from .serializers import ArticleListSerializer


@api_view(['GET', 'POST'])
def article_list(request):
    if request.method == 'GET':
        articles = Article.objects.all()
        serializer = ArticleListSerializer(articles, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = ArticleListSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  • @api_view 装饰器允许视图接收 GETPOST 请求,以及提供如 405 Method Not Allowed 等默认实现,以便在不同的请求下进行正确的响应。

  • 返回了 Response ,该对象由 Django 原生响应体扩展而来,它可以根据内容协商来确定返回给客户端的正确内容类型。如果数据验证有误,还可以返回适当的状态码以表示当前的情况。

urls.py

from django.urls import path
from article import views

app_name = 'article'

urlpatterns = [
    path('', views.article_list, name='list'),
]

示例代码

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request, format=None):
    """
    列出所有的snippet,或创建一个新的snippet。
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


# 单个snippet对象相应的视图,并且用于获取,更新和删除这个snippet。
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk, format=None):
    """
    获取,更新或删除一个 code snippet。
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)
    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)
    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

urls.py

from django.urls import path, include, re_path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    re_path(r'^snippets/$', views.snippet_list),
    re_path(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)
2 基于类的视图

传统 Django 中就有基于类的视图的存在,DRF 中自然也有。优点也都差不多,即实现功能的模块化继承、封装,减少重复代码。

views.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status

from django.http import Http404

from .models import Article
from .serializers import ArticleDetailSerializer

class ArticleView(APIView):
    """ 获取文章所有信息  查询全部、新增 """

    def get(self, request,format=None):
        books = Article.objects.all()
        ser = ArticleDetailSerializer(instance=books, many=True)
        return Response(ser.data)

    def post(self, request,format=None):
        ser = ArticleDetailSerializer(data=request.data)
        if ser.is_valid():
            # 直接保存,保存到哪个表里?需要重写save
            ser.save()
            return Response(ser.data)

        return Response(ser.errors)


class ArticleDetail(APIView):
    """文章详情视图    单查 、删、改 """

    def get_object(self, pk):
        """获取单个文章对象"""
        try:
            # pk 即主键,默认状态下就是 id
            return Article.objects.get(pk=pk)
        except:
            raise Http404

    def get(self, request, pk,format=None):
        article = self.get_object(pk)
        serializer = ArticleDetailSerializer(article)
        # 返回 Json 数据
        return Response(serializer.data)

    def put(self, request, pk,format=None):
        article = self.get_object(pk)
        serializer = ArticleDetailSerializer(article, data=request.data)
        # 验证提交的数据是否合法
        # 不合法则返回400
        if serializer.is_valid():
            # 序列化器将持有的数据反序列化后,
            # 保存到数据库中
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk,format=None):
        article = self.get_object(pk)
        article.delete()
        # 删除成功后返回204
        return Response(status=status.HTTP_204_NO_CONTENT)

urls.py

ffrom django.urls import path
from article import views
from rest_framework.urlpatterns import format_suffix_patterns

app_name = 'article'

urlpatterns = [
    # 单查 、删、改
    path('<int:pk>/', views.ArticleDetail.as_view(), name='detail'),

    # 查询全部、新增
    path('info/', views.ArticleView.as_view(), name='view'),
]

urlpatterns = format_suffix_patterns(urlpatterns)

1.2.3 通用视图

对数据的增删改查是几乎每个项目的通用操作,因此可以通过 DRF 提供的 Mixin 类直接集成对应的功能。

说白了就是对APIView进行了封装操作。

1.Mixin类

修改一下 ArticleDetail 视图:

views.py

from .models import Article
from .serializers import ArticleDetailSerializer
from rest_framework import mixins
from rest_framework import generics


class ArticleList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleDetailSerializer
    """
     列出所有文章和新增文章
    """

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

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


class ArticleDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    """文章详情视图、 单查 、删、改  """
    queryset = Article.objects.all()
    serializer_class = ArticleDetailSerializer

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

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
2.使用通用的基于类的视图

使用 Mixin 已经足够简单了,但我们还可以让它更简单:

from .models import Article
from .serializers import ArticleDetailSerializer
from rest_framework import mixins
from rest_framework import generics


class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
    """ 单查 、删、改 """
    queryset = Article.objects.all()
    serializer_class = ArticleDetailSerializer

# 发送请求试试,功能和最开头那个继承 APIView 的视图是完全相同的。

class ArticleList(generics.ListCreateAPIView):
    """ 查询全部、增加"""
    queryset = Article.objects.all()
    serializer_class = ArticleListSerializer

除了上述介绍的以外,框架还提供 ListModelMixinCreateModelMixin 等混入类或通用视图,覆盖了基础的增删改查需求。

1.2.4 视图集

大部分对接口的操作,都是在增删改查的基础上衍生出来的。既然这样,视图集就将这些通用操作集成在一起了。

1. ViewSet
  • view.py

from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from quickstart.models import BookInfo
from quickstart.serializers import BookInfoSerializer
from rest_framework import viewsets


class BookInfoViewSet(viewsets.ViewSet):
    """
    获取所有图书和单个图书信息
    """
    def list(self, request):
        queryset = BookInfo.objects.all()
        serializer = BookInfoSerializer(queryset, many=True)
        return Response(serializer.data)
    def retrieve(self, request, pk=None):
        queryset = BookInfo.objects.all()
        book = get_object_or_404(queryset, pk=pk)
        serializer = BookInfoSerializer(book)
        return Response(serializer.data)
  • urls.py

from django.urls import path
from quickstart import views

urlpatterns = [
    path('books/', views.BookInfoViewSet.as_view({'get': 'list'})),
    path('books/<int:pk>', views.BookInfoViewSet.as_view({'get': 'retrieve'}))
]
2.ModelViewSet

这次我们使用了ModelViewSet类来获取完整的默认读写操作。

示例1

view.py

from article.models import Article
from article.serializers import ArticleSerializer
from article.permissions import IsAdminUserOrReadOnly
from rest_framework import viewsets


class ArticleViewSet(viewsets.ModelViewSet):
    """ 全部查看、新增、删、改、查"""
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAdminUserOrReadOnly]

    def perform_create(self, serializer):
        """
        重写perform_create()
        提供了视图集无法自行推断的用户外键字段。
        """
        serializer.save(author=self.request.user)

由于使用了视图集,我们甚至连路由都不用自己设计了,使用框架提供的 Router 类就可以自动处理视图和 url 的连接。

urls.py

from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from article import views

router = DefaultRouter()
router.register(r'article', views.ArticleViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),

    # drf 自动注册路由
    path('api/', include(router.urls))

    # path('api/article/', include('article.urls', namespace='article')),
]

示例2

  • view.py

from quickstart.models import BookInfo
from quickstart.serializers import BookInfoSerializer
from rest_framework import viewsets


class BookInfoModelViewSet(viewsets.ModelViewSet):
    """
    获取所有图书和单个图书信息的增删改查
    """
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
  • urls.py

from django.urls import path
from quickstart import views

urlpatterns = [
    path('books/', views.BookInfoModelViewSet.as_view({'get': 'list', 'post': 'create'})),
    path('books/<int:pk>',
         views.BookInfoModelViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}))
]

访问多个序列化器

覆写 get_serializer_class() 方法可以根据条件而访问不同的序列化器:

class ArticleViewSet(viewsets.ModelViewSet):
    ...

    def get_serializer_class(self):
        if self.action == 'list':
            return SomeSerializer
        else:
            return AnotherSerializer

这次我们使用了ModelViewSet类来获取完整的默认读写操作。

请注意,我们还使用@detail_route装饰器创建一个名为highlight的自定义操作。这个装饰器可用于添加不符合标准create/update/delete样式的任何自定义路径。

默认情况下,使用@detail_route装饰器的自定义操作将响应GET请求。如果我们想要一个响应POST请求的动作,我们可以使用methods参数。

detail_route``**已经从****DRF 3.8中删除了。新版可以使用**``actions

from rest_framework.decorators import action

@action(detail=True, methods=['post'])
def set_password(self, request, pk=None):
   ....

示例3

class SnippetViewSet(viewsets.ModelViewSet):
    """
    此视图自动提供`list`,`create`,`retrieve`,`update`和`destroy`操作。
    另外我们还提供了一个额外的`highlight`操作。
    """
    from rest_framework.decorators import action
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)

    @action(renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


# 等效于如下
# SnippetList,SnippetDetail和SnippetHighlight视图类。等于ModelViewSet视图集。ModelViewSet封装了很多步骤
# class SnippetList(generics.ListCreateAPIView):
#     queryset = Snippet.objects.all()
#     serializer_class = SnippetSerializer
#     permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
#
#     def perform_create(self, serializer):
#         serializer.save(owner=self.request.user)
#
#
# class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
#     queryset = Snippet.objects.all()
#     serializer_class = SnippetSerializer
#     permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
#
#
# class SnippetHighlight(generics.GenericAPIView):
#     queryset = Snippet.objects.all()
#     renderer_classes = (renderers.StaticHTMLRenderer,)
#
#     def get(self, request, *args, **kwargs):
#         snippet = self.get_object()
#         return Response(snippet.highlighted)

明确地将ViewSets绑定到URL

请注意,我们是如何通过将http方法绑定到每个视图所需的操作,从每个ViewSet类创建多个视图的。

现在我们将资源绑定到具体的视图中,我们可以像往常一样在URL conf中注册视图。

from django.urls import path, include, re_path
from rest_framework.urlpatterns import format_suffix_patterns
# from snippets import views

from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers

snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
    'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
    'get': 'list'
})
user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

urlpatterns = format_suffix_patterns([
    re_path(r'^$', api_root),
    re_path(r'^snippets/$', snippet_list, name='snippet-list'),
    re_path(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
    re_path(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
    re_path(r'^users/$', user_list, name='user-list'),
    re_path(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
])

urlpatterns += [
    re_path(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]

使用路由器

因为我们使用的是ViewSet类而不是View类,我们实际上不需要自己设计URL。将资源连接到视图和url的约定可以使用Router类自动处理。我们需要做的就是使用路由器注册相应的视图集,然后让它执行其余操作。

这是我们重写的urls.py文件。

from django.urls import path, include, re_path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
from rest_framework.routers import DefaultRouter

# 创建路由器并注册我们的视图。
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)
# API URL现在由路由器自动确定。
# 另外,我们还要包含可浏览的API的登录URL。
urlpatterns = [
    re_path(r'^', include(router.urls)),
    re_path(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
3.ReadOnlyModelViewSet

ReadOnlyModelViewSet类来自动提供默认的“只读”操作。

我们仍然像使用常规视图那样设置querysetserializer_class属性,但我们不再需要向两个不同的类提供相同的信息。

  • view.py

from quickstart.models import BookInfo
from quickstart.serializers import BookInfoSerializer
from rest_framework import viewsets


class BookInfoReadOnlyModelViewSet(viewsets.ReadOnlyModelViewSet):
    """
    获取所有图书和单个图书信息
    """
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
  • urls.py

from django.urls import path
from quickstart import views

urlpatterns = [
    path('books/', views.BookInfoReadOnlyModelViewSet.as_view({'get': 'list'})),
    path('books/<int:pk>', views.BookInfoReadOnlyModelViewSet.as_view({'get': 'retrieve'}))
]

总结

ViewSetMixin:           重写了as_view

ViewSet:                   继承ViewSetMixin和APIView

GenericViewSet:       继承ViewSetMixin, generics.GenericAPIView

ModelViewSet:        继承mixins.CreateModelMixin,mixins.RetrieveModelMixin,mixins.UpdateModelMixin,mixins.DestroyModelMixin,mixins.ListModelMixin,GenericViewSet


# ModelViewSet  封装提供了增删改查、单查

源码:
class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass



ReadOnlyModelViewSet:继承mixins.RetrieveModelMixin,mixins.ListModelMixin,GenericViewSet

参考文献:

1.3 小结

那么,到底该怎样选择视图封装呢?我们马上就将得到一个相对确切的答案。

首先,我们来剖析视图的封装层数。要知道,我们经常说到的Django REST framework的“三层视图封装”,并不是仅仅封装了三层,下面解剖一个viewsets.ModelViewSet看一下:

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):

class GenericAPIView(views.APIView):

可以看出,从APIView到views.ModelViewSet,mixins只是个过程,mixins存在的价值,更多的是为了帮助Django REST framework学习者,更加容易地理解视图封装的原理。

但事实上似乎并没有起到帮助作用。可以说,我们在今后的项目中,只需要优先在APIView和viewsets中选择即可。至于mixins就好像是斐波那契数列一样,几乎永远不会缺席于应聘Django REST framework技术岗位的笔试题中,但在实际项目中却很少能用得上。

APIView和viewsets

视图集最大程度地减少需要编写的代码量,并允许你专注于 API 提供的交互和表示形式,而不是 URL 的细节。但并不意味着用它总是比构建单独的视图更好。

原因就是它的抽象程度太高了。如果你对 DRF 框架的理解不深并且需要做某种定制化业务,可能让你一时间无从下手。

精简可读之间,你应该根据实际情况进行取舍。

APIView和viewsets应该怎样选择呢?Django REST framework的官方文档中也有介绍过二者的取舍问题,但帮助不大。viewsets虽然对APIView做了封装,如果出现代码结构更多,逻辑更麻烦,像这类情况,我们应该选择APIView。

如果仅仅做增删改查、CURD操作、无逻辑处理,直接使用视图集实现不需要多写一些重复代码。

总结一下:

  • 当视图要实现的功能中,存在数据运算、拼接的业务逻辑时,比如本章例子中,API成功访问一次,用户表中的money记录减少1,可以一律选择APIView的方式来写视图类。

  • 除此以外,优先使用viewsets的方式来写视图类,毕竟使用viewsets+Router在常规功能上效率极高。

2. 用Django设计大型电商的类别表

2.1 电商类别表的项目功能需求

(1)类别表必须包含多级类目,至少分为四级类目。

(2)类别表每一级类目数据都要有各自类目级别的标注,以便前端进行网页设计。

(3)类别表内的类别数据,必须可以灵活地进行增、减,并且不会因此而改变其上下层级的数据关系。

2.1.1 使用Vue.js在前端开发一个电商导航栏项目demo1

Vue.js就是一个前端框架。Vue.js的单页面应用模式对于前端技术的影响非常深远,包括微信小程序在内的很多前端原生语言,都是借鉴了此模式。

Vue.js是一个构建数据驱动的Web界面渐进式框架,与Angular和React并称为前端三大框架,而Vue.js框架是由华人尤雨溪所创造,开发文档更适合中国人阅读,而且尤雨溪也已经加盟阿里巴巴,所以Vue.js在国内也得到了阿里巴巴的推广,已经成为了国内最热门的前端框架之一。

Vue.js的知识非常简单,如果大家已经掌握了HTML+CSS+JavaScript语言,通过Vue.js的官方文档,只需要几个小时的学习,就可以轻松上手Vue.js框架了。Vue.js是实现多端并行中非常重要,也是非常基础的一个技术。

3. 搭建Vue.js前端开发环境

(1)下载Node.js。

Node.js官网地址为https://nodejs.org/en/

(2)安装Node.js。

(3)查看Node.js是否安装成功。

(4)安装淘宝镜像cnpm,可以让资源包下载得更快。

# 安装淘宝npm
npm install -g cnpm --registry=https://registry.npm.taobao.org

(5)安装Vue.js的脚手架工具。输入以下命令

# vue-cli 安装依赖包
cnpm install --g vue-cli

(6)创建项目。创建Vue.js项目,命名为vue02.然后连续按5次Enter键默认选项。

# vue init webpack-simple vue02

? Project name demo1
? Project description A Vue.js project
? Author
? License MIT
? Use sass? No

   vue-cli · Generated "demo1".

   To get started:

     cd demo1
     npm install
     npm run dev

项目新建完成后,切换到项目目录下,安装依赖:

[root@cicd-dev lib]# cd vue02D:\vue02>cnpm install axios --save
/
[root@cicd-dev demo1]# cnpm install

(7)运行初始项目。在项目目录demo1下,执行以下命令,结果如图所示:

[root@cicd-dev demo1]# npm run dev

> vue02@1.0.0 dev /usr/local/src/node-v10.9.0-linux-x64/lib/demo1
> cross-env NODE_ENV=development webpack-dev-server --open --hot

Project is running at http://localhost:8086/

4. 使用PyCharm新建后端演示项目

4.1 新建基础环境和数据

(1)新建Django项目并命名为demo2,同时新建App,命名为app01。

(2)在PyCharm中打开项目终端,安装相关依赖包:

这里使用豆瓣源加速安装依赖包

pip install Djangorestframework markdown Django-filter pillow Django-guardian coreapi -i "https://pypi.doubanio.com/simple/"

(3)在demo2/demo2/settings.py中注册rest_framework:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'rest_framework'
]

(3)demo2连接MySQL数据库,在settings中将数据库配置代码修改为:

将原本的数据库配置代码注释掉,然后填入新的配置代码,否则后面填入的配置代码是不起作用的。

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': BASE_DIR / 'db.sqlite3',
#     }
# }


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'demo2',
        'USER': 'root',
        'PASSWORD': 'OSChina@2020',
        'HOST': '127.0.0.1',
        # 第三方登录功能必须加上
        "OPTIONS": {"init_command": "SET default_storage_engine=INNODB;"}
    }
}

然后,安装PyMYSQL:

$ pip install PyMYSQL

在demo2/demo2/__init__.py中加入代码:

import pymysql

pymysql.install_as_MySQLdb()

(4)在demo2/app01/models.py中新建类别表:

from django.db import models
from datetime import datetime


# Create your models here.
class Type1(models.Model):
    """
    一级类目
    """
    name = models.CharField(max_length=10, default="", verbose_name="类目名")
    add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间')

    class Meta:
        verbose_name = '商品类别'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class Type2(models.Model):
    """
    二级类目
    """
    parent = models.ForeignKey(Type1, verbose_name="父级类别",
                               null=True, blank=True, on_delete=models.CASCADE)
    name = models.CharField(max_length=10, default="", verbose_name="类目名")
    add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间')

    class Meta:
        verbose_name = '商品类别2'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class Type3(models.Model):
    """
    三级类目
    """
    parent = models.ForeignKey(Type2, verbose_name="父级类别",
                               null=True, blank=True, on_delete=models.CASCADE)
    name = models.CharField(max_length=10, default="", verbose_name="类目名")
    add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间')

    class Meta:
        verbose_name = '商品类别3'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class Type4(models.Model):
    """
        四级类目
    """
    parent = models.ForeignKey(Type3, verbose_name="父级类别", null=True, blank=True, on_delete=models.CASCADE)
    name = models.CharField(max_length=10, default="", verbose_name="类目名")
    add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间')

    class Meta:
        verbose_name = '商品类别4'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

然后同第(2)步一样,打开项目终端,执行数据更新命令:

$ python manage.py makemigrations
$ python manage.py migrate

当数据表构建成功后,在项目目录中会生成一个db.sqlite3数据库文件。

(5)手动添加一些数据。我们不需要其他的数据库操作软件

没有使用MySQL数据库,而是直接使用了Django自带的sqlite3数据库,的确方便了许多。

但是sqlite3的局限性是只适合数据量级比较小的数据库服务,一旦涉及数据量比较庞大的项目,就要选择使用MySQL数据库或者其他数据库。

4.2 完善demo2的后台逻辑代码

(1)在app01目录下新建序列化模块serializers.py,新建4个类别表的序列化类:

from rest_framework import serializers  # 引入序列化模块
from .models import Type1, Type2, Type3, Type4  # 引入所有数据表类


class Type1ModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Type1
        fields = "__all__"


class Type2ModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Type2
        fields = "__all__"


class Type3ModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Type3
        fields = "__all__"


class Type4ModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Type4
        fields = "__all__"

(2)在app01/views.py中,编写访问4个类别表的视图逻辑代码:

#引入序列化类
from .serializers import Type1ModelSerializer,Type2ModelSerializer
from .serializers import Type3ModelSerializer,Type4ModelSerializer
#引入数据表
from .models import Type1,Type2,Type3,Type4
#引入rest_framework相关模块
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
# Create your views here.
class Type1View(APIView):
    """
    all Type1
    """
    renderer_classes = [JSONRenderer]
    def get(self, request, format=None):
        types=Type1.objects.all()
        types_serializer = Type1ModelSerializer(types, many=True)
        return Response(types_serializer.data)
class Type2View(APIView):
    """
    all Type2
    """
    renderer_classes = [JSONRenderer]
    def get(self, request, format=None):
        types=Type2.objects.all()
        types_serializer = Type2ModelSerializer(types, many=True)
        return Response(types_serializer.data)
class Type3View(APIView):
    """
    all Type3
    """
    renderer_classes = [JSONRenderer]
    def get(self, request, format=None):
        types=Type3.objects.all()
        types_serializer = Type3ModelSerializer(types, many=True)
        return Response(types_serializer.data)
class Type4View(APIView):
    """
    all Type4
    """
    renderer_classes = [JSONRenderer]
    def get(self, request, format=None):
        types=Type4.objects.all()
        types_serializer = Type4ModelSerializer(types, many=True)
        return Response(types_serializer.data)

注意: 这里用到了Django REST framework的选择器,大家可以根据自己的喜好,选择使用JSONRenderer模式还是BrowsableAPIRenderer.

renderer_classes = [JSONRenderer, BrowsableAPIRenderer] # 渲染器

(3)在demo2/urls.py中添加路由代码:

from django.contrib import admin
from django.urls import path

# 引入视图类
from app01.views import Type1View, Type2View, Type3View, Type4View

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/type1/', Type1View.as_view()),
    path('api/type2/', Type2View.as_view()),
    path('api/type3/', Type3View.as_view()),
    path('api/type4/', Type4View.as_view())
]

5.前后端项目联合调试

现在,前端项目demo1和后端项目demo2都完成了,可以正式开始前后端联合调试的工作了。

(1)运行demo2项目。

(2)给vue02项目安装网络请求模块axios。在cmd窗口执行安装axios模块命令。

D:\vue02>cnpm install axios --save

(3)改写前端项目,在vue02/src/App.vue中,<style>样式标签内的代码不做改变,其他代码修改如下:

html

<template>
  <div id="app">
    <div class="all">
      <div class="one">
        <div class="onetype" v-for="(item,index) in one" :key="index">
          <b>{{one[index].name}}</b>
        </div>
      </div>
      <div class="twothreefour">
        <div class="two">
          <div class="twotype"
          v-for="(item,index) in two" :key="index"
           @mouseenter="open(index)">
            <b>{{two[index].name}}</b>
          </div>
        </div>
        <div class="threefour" v-if="flag"
           @mouseleave="close()">
          <div class="threefourtype" v-for="(item,index) in three1" :key="index">
            <span class="three">{{three1[index]}}</span>
            <span class="four" v-for="(item4,index4) in four1" :key="index4">
{{four1[index4]}}&nbsp;</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

js

<script>
import Axios from 'axios';

export default {
  name: 'app',
  data() {
    return {
      one: [],
      two: [],
      three: [],
      four: [],
      flag: false,
      three1: [],
      four1: []
    }
  },
  methods: {
    getData() {
      const api = 'http://127.0.0.1:8000/';
      var api1 = api + 'api/type1/';
      var api2 = api + 'api/type2/';
      var api3 = api + 'api/type3/';
      var api4 = api + 'api/type4/';
      var type1 = [];
      var type2 = [];
      var type3 = [];
      var type4 = [];
      Axios.get(api1)
          .then(function (response) {
            // console.log(response);
            for (var i = 0; i < response.data.length; i++) {
              // console.log(response.data[i])
              type1.push(response.data[i])
            }
            // console.log(type1)
          })
          .catch(function (error) {
            console.log(error);
          });
      this.one = type1;
      Axios.get(api2)
          .then(function (response) {
            // console.log(response);
            for (var i = 0; i < response.data.length; i++) {
              // console.log(response.data[i])
              type2.push(response.data[i])
            }
            // console.log(type2)
          })
          .catch(function (error) {
            console.log(error);
          });
      this.two = type2;
      Axios.get(api3)
          .then(function (response) {
            // console.log(response);
            for (var i = 0; i < response.data.length; i++) {
              // console.log(response.data[i])
              type3.push(response.data[i])
            }
            // console.log(type3)
          })
          .catch(function (error) {
            console.log(error);
          });
      this.three = type3;
      Axios.get(api4)
          .then(function (response) {
            // console.log(response);
            for (var i = 0; i < response.data.length; i++) {
              // console.log(response.data[i])
              type4.push(response.data[i])
            }
            // console.log(type4)
          })
          .catch(function (error) {
            console.log(error);
          });
      this.four = type4;
      // console.log(this.one)
      // console.log(this.two)
      // console.log(this.three)
      // console.log(this.four)
    },
    open(index) {
      // console.log(this.two[index].id)
      var temp = []
      for (var i = 0; i < this.three.length; i++) {
        if (this.three[i].parent === index) {
          temp.push(this.three[i].name)
        }
      }
      console.log(temp)
      this.three1 = temp;
      var temp4 = []
      for (var j = 0; j < this.four.length; j++) {
        temp4.push(this.four[j].name)
      }
      this.four1 = temp4
      this.flag = true
    },
    close() {
      this.flag = false
    }
  },
  mounted() {
    this.getData()
  },
}
</script>

一级、二级、三级类目遵循了从属关系,而第四级类目为了体现效果并没有通过筛选赋值。

(4)解决跨域问题。在后端Django项目demo2中安装相关模块:

pip install Django-cors-headers -i "https://pypi.doubanio.com/simple/"

然后在settings.py中的注册里配置如下:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'rest_framework',
    'corsheaders'
]

在settings.py中的MIDDLEWARE里设置如下:

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # 放到中间件顶部
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

在settings.py中新增配置项,即可解决本项目中的跨域问题。

CORS_ORIGIN_ALLOW_ALL = True

注意:在Python全栈开发的知识体系里,跨域问题和深浅拷贝,几乎是逢面试必考的两个笔试题。

不同的是,深浅拷贝在实际项目中很少用到,而跨域问题却几乎在每个项目中都有涉及,只是并非都能被察觉罢了。跨域问题是非常重要的一个知识点,关系到网络安全,甚至说跨域问题是Web安全中最重要的一环也不为过。

(5)重启前后端,即可看到效果

6. 使用Django的model实现类别表建立

6.1 四表合一

类别表本来就应该是一张表,我们来构建一张类别表,把上一节用传统建表方式建立的四张类别表合为一张。

(1)在demo2/app01/models.py中新建类Type。

class Type(models.Model):
    """
    商品类别
    """

    CATEGORY_TYPE=(
        (1,'一级类目'),
        (2,'二级类目'),
        (3,'三级类目'),
        (4,'四级类目')
    )

    name=models.CharField(default='',max_length=30,verbose_name='类别名',help_text='类别名')
    code=models.CharField(default='',max_length=30,verbose_name='类别code',help_text='类别code')
    desc=models.CharField(default='',max_length=30,verbose_name='类别描述',help_text='类别描述')
    category_Type=models.IntegerField(choices=CATEGORY_TYPE,verbose_name='类别描述',help_text='类别描述')
    parent_category=models.ForeignKey('self',null=True,blank=True,verbose_name='父类目录',help_text='父类别',related_name='sub_cat',on_delete=models.CASCADE)
    is_tab=models.BooleanField(default=False,verbose_name='是否导航',help_text='是否导航')

    class Meta:
        verbose_name='商品类别'
    verbose_name_plural=verbose_name
        def __str__(self):
            return self.name

(2)建表,执行数据更新命令如下:

python manage.py makemigrations
python manage.py migrate

6.2 数据导入

因为在上一节中建立的4个类别表已经手动加入了一些数据,我们没有必要再用手动加入的方式将这些记录加入新建的Type表里,而是选择使用一种更加方便的方式,即通过Postman将数据以post的方式,加入Type表内。

(1)在demo2/app01/serializers.py内增加Type表的序列化类

from .models import Type

class TypeModelSerializer(serializers.ModelSerializer):
    class Meta:
        model=Type
        fields="__all__"

(2)在demo2/app01/views.py内创建TypeView视图类:

from .models import Type
# 导入序列化类
from .serializers import TypeModelSerializer


class TypeView(APIView):
    """
    操作类别表
    """
    renderer_classes = [JSONRenderer]

    def get(self, request, format=None):
        types = Type.objects.all()
        types_serializer = TypeModelSerializer(types, many=True)
        return Response(types_serializer.data)


    def post(self, request):
        name = request.data.get('name')
        category_type = request.data.get('lei')
        parent_category_id = request.data.get('parent')
        type = Type()
        type.name = name
        type.category_type = category_type

        if parent_category_id:
            parent_category = Type.objects.filter(id=parent_category_id).first()
            type.parent_category = parent_category
        type.save()

        type_serializer = TypeModelSerializer(type)

        return Response(type_serializer.data)

注意: 这里用到了Django REST framework的选择器,大家可以根据自己的喜好,选择使用JSONRenderer模式还是BrowsableAPIRenderer.

renderer_classes = [JSONRenderer, BrowsableAPIRenderer] # //渲染器

全局使用

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer'],
}

(3)在demo2/demo2/urls.py内增加路由代码如下:

from app01.views import TypeView

urlpatterns = [
    #......
    path('api/type/',TypeView.as_view())

]

(4)运行demo2项目,使用Postman通过post加入数据记录,

省略

7.前后端项目联合调试

崭新的数据类别表构建完成了,下面将前端项目改造一下,对接调试我们的类别表,看看可以节省多少前端的工作量。

(1)在demo1/src/App.vue原来的基础上,只修改<script>标签内的代码:

<script>
import Axios from 'axios';
export default {
  name: 'app',
  data () {
    return {
      type:[],
      one:[],
      two:[],
      flag:false,
      three1:[],
      four1:[]
    }
  },
  methods: {
    getData(){
      const api='http://127.0.0.1:8000/api/type/';
      var _this=this

      Axios.get(api)
      .then(function (response) {
        _this.type=response.data;
        for(var i=0;i<_this.type.length;i++){
          if(_this.type[i].category_type===1){
            _this.one.push(_this.type[i])
          }
        }
        for(var j=0;j<_this.type.length;j++){
          if(_this.type[j].category_type===2){
            _this.two.push(_this.type[j])
          }
        }
      })
      .catch(function (error) {
      console.log(error);
      });

    },
    open(index){
      this.three1=[]
      this.four1=[]
      var parent=this.two[index].id
      for(var i=0;i<this.type.length;i++){
        if(this.type[i].parent_category===parent){
          this.three1.push(this.type[i].name)
        }
        if(this.type[i].category_type===4){
          this.four1.push(this.type[i].name)
        }
      }
      this.flag=true
    },
    close(){
      this.flag=false
    }
  },
  mounted() {
    this.getData()
  }
}
</script>

(2)运行前端demo1和后端demo2,查看效果图,如图3-32所示。我们用四分之一的代码量,完成了一个更加强大的类别表。