如何复用带POST方法的Django视图开发DRF API接口
你现在手动构造WSGIRequest、直接实例化视图类调用post方法的写法完全不可用,不仅逻辑跑不通,后续维护成本极高。
- 你手动实例化
ShopModuleCategoryCreateView的操作,完全绕过了Django类视图的标准调度流程:正常请求走as_view()入口时,会自动完成实例初始化、绑定request/路由参数、执行Mixin里的前置逻辑(比如给self.shop、self.module、self.module_class赋值)、权限校验、请求预处理。你手动new出来的实例里这些属性全是初始化时的None,跑到post方法里取self.shop.pk的时候直接就抛空指针异常,连has_permission里的表单类初始化都过不了。 - 手动拼
QueryDict字符串、硬编码csrf token的做法非常脆弱:后续表单加字段、改校验规则,你都要手动改这段硬编码字符串,很容易漏参数,而且你没给请求绑定用户身份、会话上下文,权限校验环节直接就会被拦下来。 - 直接调用
post方法跳过了所有中间件、异常处理、请求解析逻辑,出了问题很难定位。
按优先级从高到低排:
1. 抽离核心业务逻辑(最优解,适配所有复用场景)
你这段post方法里的核心逻辑和网页端的渲染、跳转逻辑完全解耦,本质就是「校验参数→创建商品分类→绑定分类下的商品关联关系」,完全可以把这部分抽成独立的服务层函数,不管是原来的网页端视图、新写的移动端DRF接口、单元测试、甚至后续要加的批量导入、异步任务,都可以直接调用,完全不需要和Django的视图层、request对象绑定。
举个简单的实现例子:
# 可以单独放到services.py文件里,和视图逻辑解耦 def create_shop_category(shop, module, category_valid_data, product_bind_list): # 创建分类 category = Category.objects.create( name=category_valid_data['name'], order=category_valid_data['order'], module=module, shop_id=shop.pk, category_image=category_valid_data['category_image'], ) # 绑定分类下商品 for product_item in product_bind_list: try: product = Product.objects.get(pk=product_item['product'].split('/')[0]) quantity = int(product_item['quantity']) if product.unit else 1 CategoryProduct.objects.create( category=category, product=product, quantity=quantity ) except (ObjectDoesNotExist, KeyError): # 和你原有逻辑一致,跳过异常项 pass return category
改完之后原来的网页端视图只需要从request里取参数、校验表单,调用这个服务函数就行;DRF的移动端接口也只需要从request.data里取参数,做DRF自己的序列化校验,之后调用同一个服务函数,返回JSON格式响应即可,完全不需要复用视图层的request处理逻辑。
2. 单元测试场景用Django/DRF自带的测试客户端
如果你写这段代码是为了做单元测试,根本不需要自己构造请求对象,直接用框架自带的测试客户端发真实请求,走完整的请求链路,和用户实际访问的逻辑完全一致:
from django.test import TestCase, Client class CategoryCreateTest(TestCase): def setUp(self): self.client = Client() # 先登录拥有对应权限的测试用户 self.client.login(username='test_shop_operator', password='test_pwd_123') def test_create_category_success(self): post_params = { 'name': 'cat0', 'order': 3, 'category_image': 'test_image', 'form-TOTAL_FORMS': 2, 'form-INITIAL_FORMS': 0, 'form-MIN_NUM_FORMS': 0, 'form-MAX_NUM_FORMS': 1000, 'form-0-product': '6/unit', 'form-0-quantity': 123, 'form-1-product': '5/unit', 'form-1-quantity': 258, } # 替换成你实际的路由地址 resp = self.client.post('/shop/module/category/create/', post_params) # 校验返回和数据库结果 self.assertEqual(resp.status_code, 302) self.assertTrue(Category.objects.filter(name='cat0', order=3).exists())
如果是测DRF接口,换成rest_framework.test.APIClient即可,用法基本一致。
3. 临时调用视图用RequestFactory
如果确实需要在代码里临时调用现有视图,不要手动实例化视图类,用Django自带的RequestFactory构造请求,走as_view()的标准入口:
from django.test import RequestFactory factory = RequestFactory() request = factory.post('/target/url/', { 'name': 'cat0', 'order': 3, # 其他POST参数,直接传字典即可,不需要手动拼QueryDict }) # 给请求绑定用户,否则权限校验不通过 request.user = 你获取到的有权限的用户对象 # 传入路由需要的kwargs,走完整视图调度流程 resp = ShopModuleCategoryCreateView.as_view()(request, shop_pk=shop_id, module_class=module_class)
注意这种方式拿到的是网页端的redirect响应,你如果给移动端API用还要自己处理返回格式,远不如抽离服务层逻辑灵活。
优先选抽离服务层的方案,后续不管是改逻辑还是加新端,都不用重复写业务代码,也不会和Web端的表单、模板渲染逻辑耦合。
内容的提问来源于stack exchange,提问作者Gum

