【Django REST framework电商项目笔记】第05章 商品列表页功能开发(上)

Django的view实现商品列表页

建议通过基于 class 的 view 编码实现,面向对象思想
view 可以实现 json 返回
1、配置 url

# goods_list = GoodsListViewSet.as_view({
#     'get': list,
# })
urlpatterns = [
    # # 商品列表页
    # path('goods/', goods_list.as_view(), name="goods-list"),
]

2、goods中新建一个 view_base 来实现一个只通过 Django 实现的 json 返回

class GoodsListView(View):

    def get(self, request):
        """
        通过django的view实现商品列表页
        """

        # json_list = []
        goods = Goods.objects.all()[:10]
        # 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.core import serializers
        from django.http import JsonResponse
        import json

        json_data = serializers.serialize('json', goods)
        json_data = json.loads(json_data)

        return JsonResponse(json_data, safe=False)

可以使用 model_to_dict 将model 转换为字典,不用一个字段一个字段提取

    for good in goods:
       json_dict = model_to_dict(good)
       json_list.append(json_dict)

serializers 专门用于序列化,即把上面的 model_to_dict 进行了封装
如果将 addtime 加入会报错,json.dumps 无法自己完成。

TypeError: Object of type 'add_time' is not JSON serializable

推荐阅读:

序列化 - 廖雪峰的官方网站

Django 中要用 drf 原因:

drf 对相对路径完成补前缀工作,无需自己手动配置
Django 的字段序列化不利于重组
drf 生成开发文档,输入检测,接口测试
官方文档:强大的,灵活的API

安装

pip install coreapi
pip install django-guardian

引入 drf 开发文档 url 相关配置:

urlpatterns = [
	...
    # DRF自动文档, 方便前后端交互的文档
    url(r'docs/', include_docs_urls(title="DRF SHOP DOCS")),
    # DRF 后台登录 API 接口
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]

INSTALLED_APPS = (
    ...
    'rest_framework',
)

在drf的登录系统中user nonetype
注意检查这里需要返回的是username

    def __str__(self):
        return self.username

之所以可以登录退出,
是因为这里配置了一个url

官方例子简单的写一个class view:
将官方示例中的snippets用我们的Goods代替
http://www.django-rest-framework.org/tutorial/3-class-based-views/

class SnippetList(APIView):
    """
    List all snippets, or create a new snippet.
    """
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

Serializer方式实现序列化

可以自定义序列化的类。SnippetSerializer
modelform 和 form。modelform可以将字段直接转成html
现在drf里的Serializer是用来取代form开发的。modelfrom是针对html的,
Serializer是针对json的。
和之前的form一样。新建一个文件 serializers.py
官方例子SnippetSerializer

我们在用浏览器请求,drf 会帮你渲染成网页格式,这就是Web browsable API

使用浏览器请求会返回 html
get 的时候可以指明 json

接口的描述我们可以自行定义
因为我们是序列化的 Goods,所以我们的 Serializer 要和 goods model 中保持一致
我们在Serializer中加上 front image
这样我们返回的 json 中,image 字段全部加上了 media 前缀
前缀是通过我们 setting 的MEDIA_URL 来自动添加的

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

    def create(self, validated_data):
        return Goods.objects.create(**validated_data)

这里使用drf的modelSerializer实现列表

class GoodsSerializer(serializers.ModelSerializer):
    """
    list all goods
    """
    category = CategorySerializer()
    images = GoodsImageSerializer(many=True)
    class Meta:
        model = Goods
        fields = "__all__"

APIView 方式实现商品列表页

class GoodsListView(APIView):
    """
    商品列表页
    """
    def get(self, request, format=None):
        goods = Goods.objects.all()[:10]
        goods_serializer = GoodsSerializer(goods, many=True)
        return Response(goods_serializer.data)

    def post(self, request, format=None):
        # drf封装get/post/body的请求的数据
        serializer = GoodsSerializer(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)

GenericView 方式实现商品列表页和分页功能

使用更加上层的 view 封装的方法越多,但是用起来简单
使用 Using mixins 和 generic view
GenericAPIView 是在 APIView 的基础上加了 filter 分页等的一些封装
我们不用添加 CreateModelMixin 是因为我们的商品数据是后台添加的,前台不会提交
list函数是在mixin中的,做了分页以及序列化
查看源码路径: /lib/python3.6/site-packages/rest_framework/generics.py

ListAPIView 获取列表
CreateAPIView 创建一个
Retrieve 获取某一条

所有关于restframework的配置要写在变量里
/lib/python3.6/site-packages/rest_framework/settings.py
这里可以设置默认分页配置:

    # Generic view behavior
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'DEFAULT_FILTER_BACKENDS': (),

也可以设置在自己项目的settings文件中,根据自己情况设置:

# rest_framework 相关设置
REST_FRAMEWORK = {
    # 分页设置
    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    # 'PAGE_SIZE': 5,
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    # 'PAGE_SIZE': 12,
}

goods view 分页实现:

class GoodsPagination(PageNumberPagination):
    """
    商品列表分页
    """
    page_size = 12
    page_size_query_param = 'page_size'
    page_query_param = 'page'
    max_page_size = 100

GoodsListView 中添加

    pagination_class = GoodsPagination

这样通过 url ,可以实现分页
localhost:8000/goods/?page=3&page_size=5

viewsets 和 router 完成商品列表页

快速查看 viewsets 源码

from rest_framework import viewsets

可以看到里面包含:

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

它的内部只是多继承了一个 ViewSetMixin
ViewSetMixin 中重写了 as_view 方法可以使我们注册 url 变得更加简单

	@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.
        """

在 view 的基础上配置了很多方法
为了让我们更好看清我们使用的是 viewset 的实现.
goods/views.py

class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):

改名字,配置我们的 url

goods_list = GoodsListViewSet.as_view({
    'get': 'list',
})

将get请求绑定到list之上,类似于之前的

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

这样我们就不用自己再去绑定了

配置 url 时就不用再加 as_view() 了

    # 商品列表页
    path('goods/', goods_list,name="goods-list"),

但是我们可以使用 router ,直接不用进行这个 get 与 list 的绑定,它自动完成

from rest_framework.routers import DefaultRouter
router = DefaultRouter()

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

url中配置

    # 全局router  url的配置
    url(r'^', include(router.urls)),

router 自动帮我们配置了 get 和 list,create 和 post 的绑定

view之间和router的原理解析

1、genericViewSet 是最高的一层

GenericViewSet(ViewSet) -drf
    GenericAPIView      -drf
        APIView         -drf
            View        -django

这些 view 之间的差异就引起了 drf 中另外一个核心点 mixin

mixin
    CreateModelMixin
    ListModelMixin
    updateModelMixin
    RetrieveModelMixin
    DestoryModelMixin

以 ListModelMixin 为例,如果我们不去继承这个 mixin 它里面的这些方法的话,就无法将get 和 list连接起来。无法连接,那么 list 中所作的所有功能都不能完成。比如其中的过滤,分页都需要自己配置。

RetrieveModelMixin 对具体的商品信息进行了获取,序列化

UpdateModelMixin 中对部分更新还是全部更新进行了判断

DestoryModelMixin 是用来连接 delete 方法,在 delete 时做一些必要操作,比如返回删除成功的状态码 204 等

注意:上述这些功能都是 mixin 完成的,而 generic view 并没有做。所以 drf 是通过两者的组合进行实现的。

GenericAPIView 继承于 views.APIView
在原本的 APIView 基础上添加了过滤,分页。如果不用 GenericAPIView 而用 APIView 这些事情都要我们自己实现。

配合着 genericview 加上 mixin 就能组合出更加强大

Viewset有哪些好处

generic 下的各种 view 也是继承了genericAPIView,配合各种 mixin 进行组合工作。

它将具体的单个 modelmixin ( 如:mixins.RetrieveModelMixin ,) 换成了 ViewSetMixin

之前我们需要写函数将 get 等与 retrieve 等进行绑定
ViewSetMixin 的好处就是: 不需要我们通过方法来绑定。但是我们还是需要绑定关系的。

在 url 配置的时候进行绑定。

def as_view(cls, actions=None, **initkwargs)

viewset 重写了as_view,接受参数,传递到对应的method 与 action进行绑定

goods_list = GoodsListViewSet.as_view({
    'get': 'list',
})

为视图绑定动作,依赖于具体的 request 请求方法。这些 action 在我们后期进行动态 Serializer 时有很大好处。

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

猜你喜欢

转载自blog.csdn.net/Yuyh131/article/details/82916906