# P11 Django-DRF（ModelViewSet）、路由组件(DefaultRouter)和自定义函数

## ModelViewSet 是什么

ModelViewSet 是 Django REST framework 提供的一个视图集类，它封装了常见的模型操作方法。

它继承自`GenericViewSet`、`ListModelMixin`、`RetrieveModelMixin`、`CreateModelMixin`、`UpdateModelMixin`、`DestoryModelMixin`。

所以，在使用ModelViewSet定义API时，我们只需要套用模板即可。

> 视图集类不再实现get()、post()、put()、delete()方法，而是实现动作 **action**。 如 list()、retrieve()、create()、update()、destory()

| 知识点             | 请求     | url                        | 特点                                           |
| ------------------ | -------- | -------------------------- | ---------------------------------------------- |
| GenericViewSet     |          |                            | 提供一组通用的视图方法，方便实现特定功能       |
| ListModelMixin     | get      | 127.0.0.1:8000/path/       | 提供 list 方法，用于获取资源列表               |
| RetrieveModelMixin | get      | 127.0.0.1:8000/path/{1}/   | 提供 retrieve 方法，用于获取单个资源的详细信息 |
| CreateModelMixin   | post     | 127.0.0.1:8000/path/       | 提供 create 方法，用于创建资源                 |
| UpdateModelMixin   | put      | 127.0.0.1:8000/path/{1}/   | 提供 update 方法，用于更新资源                 |
| DestroyModelMixin  | detete   | 127.0.0.1:8000/path/{1}/   | 提供 destroy 方法，用于删除资源                |
| 自定义             | get/post | 127.0.0.1:8000/path/自定义 | 用户自定义方法/函数                            |

　　这些技术知识点可以配合使用，帮助我们快速构建出具有 CRUD 功能的 Web 应用，并且遵循了 Django 框架的惯例和最佳实践。它们的应用场景包括博客系统、电商平台、社交网络等各种类型的 Web 应用。通过使用这些技术知识点，我们能够提高开发效率，减少重复的代码编写工作，并且保证代码的一致性和可维护性。

## **如何使用 ModelViewSet**

假设定义了模型 `GoodsCategory` 和序列化器 `GoodsCategorySerilizer`

**models.py 文件中定义模型**

```Python
from django.db.models import *


# Create your models here.
## 产品分类表
class GoodsCategory(Model):
    """产品分类"""

    name = CharField(max_length=64, verbose_name='分类名称')
    remark = CharField(max_length=64, null=True, verbose_name='备注', blank=True)
```

**serializer.py 文件中定义****序列化****器**

```Python
from rest_framework.serializers import *
from .models import *


class GoodsCategorySerilizer(ModelSerializer):
    class Meta:
        model = GoodsCategory
        fields = '__all__'
```

### 创建 ModelViewSet

设置 `queryset` 属性为要查询的对象集合，并设置 `serializer_class` 属性为对应的序列化器类。

**views.py 中定义ModelViewSet**

```Python
from rest_framework.viewsets import ModelViewSet
from .models import *
from .serializer import *


class GoodsCategoryViewSet(ModelViewSet):
    # 指定查询集（用到的数据）
    queryset = GoodsCategory.objects.all()
    # 指定查询集用到的序列化容器
    serializer_class = GoodsCategorySerializer
```

这样我们就定义好了API视图集，这个`GoodsCategoryViewSet`将提供 `list`，`create`，`retrieve`，`update` 和 `destroy` 操作。

## Django-DRF 路由组件

### 路由组件是什么？

　　`DefaultRouter`是Django REST framework中提供的一个路由器类，用于自动生成URL路由。

　　路由器是将URL与视图函数或视图集关联起来的一种机制。Django REST framework的路由器通过简单的配置可以自动生成标准的URL路由，从而减少了手动编写URL路由的工作量。


## **urlpatterns 变量的语法**
urlpatterns 应该是url() 实例的一个Python 列表。

## DefaultRouter的使用方法

**urls.py**

```python
from django.contrib import admin
from django.urls import path
from apps.erp_test.views import *

from rest_framework import routers

router = routers.DefaultRouter()
router.register('GoodsCategory', GoodsCategoryViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('filtergoodscategory/', FilterGoodsCategory),
    path('insertgoodscategory/', InsertGoodsCategory),
    path('filtergoodscategoryapi/', FilterGoodsCategoryAPI.as_view()),
    path('getgoods/', GetGoods.as_view()),
]


urlpatterns += router.urls
```


　　使用`routers.DefaultRouter()`​创建了一个默认的路由器对象，并使用`router.register()`​方法注册了一个视图集，`GoodsCategoryViewSet`​。这样可以自动为这个视图集生成对应的URL路由，并将其添加到`urlpatterns`​中。

![image](assets/image-20230815164041-GoodsCategoryList.png)

### 设置路由

**urls.py 中配置urlpatterns，使其指向我们的****API****视图集**

```Python
from rest_framework import routers

# 创建DefaultRouter对象，用于生成路由
router = routers.DefaultRouter()
# 将视图集注册到路由器上，字符串里的是URL路径的前缀
router.register('GoodsCategory', GoodsCategoryViewSet)

urlpatterns = [
    path('', include(router.urls)),
]
```

在该文件中，我们创建一个默认的Router，然后注册我们的`GoodsCategoryViewSet`。Router负责处理请求转到适当的视图。

这样，我们就可以通过API进行查询、创建、更新和删除操作。

### 请求的接口包括

| **url**                                | **请求** | **操作**                    |
| -------------------------------------- | -------- | --------------------------- |
| http://127.0.0.1:8000/GoodsCategory    | GET      | 获取所有对象                |
| http://127.0.0.1:8000/GoodsCategory/   | GET      | 获取所有对象                |
| http://127.0.0.1:8000/GoodsCategory/2  | GET      | 获取 id 为 2 的对象详细信息 |
| http://127.0.0.1:8000/GoodsCategory/2/ | GET      | 获取 id 为 2 的对象详细信息 |
| http://127.0.0.1:8000/GoodsCategory/   | POST     | 创建对象                    |
| http://127.0.0.1:8000/GoodsCategory/2/ | DELETE   | 删除 id 为 2 的对象         |
| http://127.0.0.1:8000/GoodsCategory/2/ | PUT      | 修改 id 为 2 的对象         |

### 通过 Postman 测试

首先启动server

```Python
python manager.py runserver
```

#### 获取所有对象

GET 请求

http://127.0.0.1:8000/GoodsCategory 或 http://127.0.0.1:8000/GoodsCategory/

![img](assets/get_request.png)

从测试的结果选择一个id进行获取

#### 获取指定 id 的对象

获取 id 为 13 的对象

GET 请求

http://127.0.0.1:8000/GoodsCategory/13 或 http://127.0.0.1:8000/GoodsCategory/13/

![img](assets/get_the_id_object.png)

如果尝试获取`GoodCategory`中不含有的 id，则返回`"未找到"`

![img](assets/try_to_get_an_object_with_no_id.png)

#### 创建对象

创建`name=水果`，`remark=水果`的对象

POST 请求

http://127.0.0.1:8000/GoodsCategory/

![img](assets/the_post_request_creates_the_object.png)

#### 删除对象

获取 id 为 15 的对象

DELETE 请求

http://127.0.0.1:8000/GoodsCategory/15/

![img](assets/delete_id=5_object.png)

删除 id 不存在的对象，则返回`"未找到"`

![img](assets/try_to_delete_no_id_object.png)

#### 修改对象

PUT 请求

http://127.0.0.1:8000/GoodsCategory/9/

修改 id 为 9 的对象

![img](assets/update_id=9_object.png)
## Django-DRF 自定义函数
### 自定义函数是什么

Django为视图提供了数个装饰器，用以支持相关的HTTP服务。django.views.decorators.http 包里的装饰器可以基于请求的方法来限制对视图的访问。若条件不满足会返回 django.http.HttpResponseNotAllowed。

`require_http_methods(request_method_list)[source]`


## 如何使用

**导入action**

`from rest_framework.decorators import action`


`@action` 是 Django REST framework 中的一个装饰器，用于将自定义函数转换为视图集的一个动作。`@action` 装饰器提供了一种定义自定义函数的方式，这些函数并不直接对应于标准的 CRUD 操作（Create-Read-Update-Delete），而是实现一些其他的自定义行为或业务逻辑。
 “ @action 装饰器”用于在 ViewSet 中创建自定义动作（custom action），为 ViewSet 提供了更灵活应用且 @action 只在ViewSet视图集中生效。视图集中附加action装饰器可接收两个参数：
 
 （1）methods: 声明该action对应的请求方式。
 
 （2）detail: True/False声明该action的路径是否是action对应的请求方式。


**views.py**

```python
class GoodsCategoryViewSet(ModelViewSet):
    # 指定查询集（用到的数据）
    queryset = GoodsCategory.objects.all()
    # 指定查询集用到的序列化容器
    serializer_class = GoodsCategorySerializer

    @action(detail=False, methods=['get'])
    def latest(self, request):
        latest_obj = GoodsCategory.objects.latest('id')
        print(latest_obj)
        return Response("helllo 你调用了自定义的函数")


    @action(detail=False, methods=['get','post'])
    def delete_example(self, request):
        name = request.data.get('name')
        # 删除名称为 'name' 的商品
        categories_to_delete = GoodsCategory.objects.filter(name=name)
        # 使用delete()方法删除对象
        deleted_count= categories_to_delete.delete()
        print(f"Deleted {deleted_count} categories.")      

    @action(detail=False, methods=['get','post'])
    def create_example(self, request):
        name = request.data.get('name')
            # 使用create()方法创建新的商品分类对象
        created_category = GoodsCategory.objects.create(name)
        print("Created category:", created_category)   
```

　　其中，`detail=False` 表示该动作不需要处理单个对象，而是处理整个集合；

　　被 `@action` 装饰的函数需要作为方法定义在视图集类中，并且在使用 `router.register()` 注册视图集时，需要指定 `basename` 参数，以确保该动作的 URL 能够正确映射到视图集。
