零基础入门python40:商品、分类 API 与筛选 零基础入门python40商品、分类 API 与筛选一、上一篇课后练习讲解订单项小计应使用SerializerMethodField或模型 property 计算price * quantity不能让客户端提交小计。它是由事实字段推导出的只读结果。上一篇课后练习完整答案上一篇练习的要求已落实到下面完整文件先运行项目测试再用 curl 对照状态码和数据库持久化结果答案要点ProductSerializer 校验价格并声明只读 idModel/Serializer/Service 三层职责分开关联输出用 select_related/prefetch_related。文件shop/serializers.py完整参考答案文件完整文件shop/serializers.pyfromrest_frameworkimportserializersfrom.modelsimportProductclassProductSerializer(serializers.ModelSerializer):classMeta:modelProduct fields(id,name,price,stock,category)read_only_fields(id,)defvalidate_price(self,value):ifvalue0:raiseserializers.ValidationError(price_invalid)returnvalue完整参考答案文件本篇对应的交付源码完整文件django-shop/shop/serializers.pyfromrest_frameworkimportserializersfrom.modelsimportCategory,Order,OrderItem,ProductclassCategorySerializer(serializers.ModelSerializer):classMeta:modelCategory;fields(id,name,slug)classProductSerializer(serializers.ModelSerializer):category_nameserializers.CharField(sourcecategory.name,read_onlyTrue)classMeta:modelProduct;fields(id,category,category_name,name,slug,price,stock)classOrderItemSerializer(serializers.ModelSerializer):classMeta:modelOrderItem;fields(product,product_name,price,quantity)classOrderSerializer(serializers.ModelSerializer):itemsOrderItemSerializer(manyTrue,read_onlyTrue)classMeta:modelOrder;fields(id,status,total_amount,created_at,items)classCheckoutItemSerializer(serializers.Serializer):product_idserializers.IntegerField(min_value1)quantityserializers.IntegerField(min_value1,max_value99)classCheckoutSerializer(serializers.Serializer):itemsCheckoutItemSerializer(manyTrue,allow_emptyFalse)验收命令python -m pytest -qDjango 项目使用 python manage.py test。预期测试通过若失败先检查迁移、配置和事务回滚。二、本篇完成什么使用 ReadOnlyModelViewSet 和 Router 创建分类、商品列表/详情 API支持分类和关键词筛选。三、ViewSet查询classProductViewSet(viewsets.ReadOnlyModelViewSet):serializer_classProductSerializerdefget_queryset(self):queryProduct.objects.filter(is_activeTrue).select_related(category)ifcategory:self.request.query_params.get(category):queryquery.filter(category_idcategory)ifkeyword:self.request.query_params.get(q):queryquery.filter(name__icontainskeyword)returnquery.order_by(-created_at)公开 API 只返回上架商品Admin 可看到全部商品。select_related避免序列化category_name时产生 N1。四、验收请求/api/products/?category1qPython确认只返回匹配商品下架商品不可见。课后练习增加价格范围筛选并限制非法负数。项目增量商品分类 API 和筛选列表接口用 query_params 组合分类、价格区间和关键词过滤所有值由 Serializer 或 FilterSet 校验分页由 DRF 统一处理。querysetProduct.objects.filter(is_activeTrue).select_related(category)ifcategory:querysetqueryset.filter(category__slugcategory)ifkeyword:querysetqueryset.filter(name__icontainskeyword)验收无结果、超大 page_size 和不存在分类课后练习增加排序白名单下一篇实现下单 API。五、上一篇练习讲解排序必须白名单排序参数不能直接传给order_by(request.query_params[ordering])否则用户可以让接口按不存在字段报错甚至在原生 SQL 中造成注入。白名单做法ORDERING{price:price,-price:-price,newest:-created_at}orderingORDERING.get(request.query_params.get(ordering,newest),-created_at)querysetProduct.objects.filter(is_activeTrue).order_by(ordering)未知排序可以选择回退默认值也可以返回 400课程接口选择回退并在日志记录一次 warning避免移动端旧版本因为新增排序字段而中断。六、筛选、分页和查询计划classProductListView(generics.ListAPIView):serializer_classProductSerializer pagination_classPageNumberPaginationdefget_queryset(self):paramsself.request.query_params qsProduct.objects.filter(is_activeTrue).select_related(category)ifslug:params.get(category):qsqs.filter(category__slugslug)ifq:params.get(q):qsqs.filter(name__icontainsq)if(low:params.get(min_price))isnotNone:qsqs.filter(price__gteDecimal(low))if(high:params.get(max_price))isnotNone:qsqs.filter(price__lteDecimal(high))returnqs.order_by(ORDERING.get(params.get(ordering),-created_at))价格参数应由 FilterSet 或 Serializer 校验示例省略的Decimal(low)需要捕获InvalidOperation否则非法输入会 500。select_related让列表显示分类名时只有一次查询分页器限制page_size防止一次返回过多数据。七、验收输出与故障排查curlhttp://127.0.0.1:8000/api/products/?categorybooksqPythonordering-pricepage_size2响应应包含count/next/previous/results下架商品不应出现在results。若page_size100000仍返回全部检查分页器是否设置max_page_size若分类筛选无效确认 URL 使用 slug 而不是数字 id若 SQL 数量随结果条数增长检查是否漏掉select_related。八、本篇练习增加不存在分类返回空列表、非法价格返回 400、排序白名单和分页上限测试。下一篇会把购物车交给下单 service并处理库存不足与幂等键。九、筛选接口的产品约定明确空结果返回 200 还是 404列表通常返回 200 空数组分类不存在不应泄露数据库异常价格上下界都包含若min_price max_price返回 400。把这些约定写进 OpenAPI 描述和测试前端就能可靠地组合搜索条件而不是根据一次偶然响应猜规则。本篇结束完整模块文件下面是交付项目中真实存在的完整文件 django-shop/shop/views.py。它覆盖本篇新增逻辑以及前文已经完成的依赖代码复制单个函数会丢失上下文因此这里提供整份文件。fromrest_frameworkimportgenerics,permissions,status,viewsetsfromrest_framework.responseimportResponsefrom.modelsimportCategory,Order,Productfrom.serializersimportCategorySerializer,CheckoutSerializer,OrderSerializer,ProductSerializerfrom.servicesimportcreate_orderclassCategoryViewSet(viewsets.ReadOnlyModelViewSet):querysetCategory.objects.order_by(name)serializer_classCategorySerializerclassProductViewSet(viewsets.ReadOnlyModelViewSet):serializer_classProductSerializerdefget_queryset(self):queryProduct.objects.filter(is_activeTrue).select_related(category)ifcategory:self.request.query_params.get(category):queryquery.filter(category_idcategory)ifkeyword:self.request.query_params.get(q):queryquery.filter(name__icontainskeyword)returnquery.order_by(-created_at)classCheckoutView(generics.GenericAPIView):permission_classes(permissions.IsAuthenticated,)serializer_classCheckoutSerializerdefpost(self,request):serializerself.get_serializer(datarequest.data)serializer.is_valid(raise_exceptionTrue)ordercreate_order(request.user,serializer.validated_data[items])returnResponse(OrderSerializer(order).data,status201)classMyOrderViewSet(viewsets.ReadOnlyModelViewSet):permission_classes(permissions.IsAuthenticated,)serializer_classOrderSerializerdefget_queryset(self):returnOrder.objects.filter(userself.request.user).prefetch_related(items).order_by(-id)classCartView(generics.GenericAPIView):defget(self,request):returnResponse(request.session.get(cart,{}))defpost(self,request):productgenerics.get_object_or_404(Product,idrequest.data.get(product_id),is_activeTrue)quantityint(request.data.get(quantity,1))ifquantity1orquantityproduct.stock:returnResponse({message:数量必须大于0且不能超过库存},statusstatus.HTTP_400_BAD_REQUEST)cartrequest.session.setdefault(cart,{})cart[str(product.id)]quantity request.session.modifiedTruereturnResponse(cart,statusstatus.HTTP_201_CREATED)defdelete(self,request):cartrequest.session.get(cart,{})cart.pop(str(request.data.get(product_id)),None)request.session.modifiedTruereturnResponse(cart)

相关新闻

最新新闻

android开发转到java后端开发--Lambda

android开发转到java后端开发--Lambda

Lambda 表达式(Lambda Expression)是 Java 8 中最为重要的里程碑特性。它的本质是一个匿名函数(即没有名称的方法),其核心价值在于将行为(逻辑)作为参数传递给方法,从而实现行为参数化(Behavior Parameterization),让代码表达更接近自然语言的声明式编程风格。 你可…

2026/9/1 16:02:08
DJI Neo 2 4K畅飞套装:入门无人机的技术取舍与实战指南

DJI Neo 2 4K畅飞套装:入门无人机的技术取舍与实战指南

入门无人机在不少人眼里是尴尬的存在:论画质比不过手机加稳定器,论参数比不过几千上万的旗舰机型,多翻几篇评测,就容易被“这台机器只适合新手练手”的观点劝退。但这两年的行情明显变了——以 4K 视频录制、掌上起降、AI 跟随、多…

2026/9/1 16:02:08
Substance Designer节点动画:从弹跳球到风吹草地的程序化驱动

Substance Designer节点动画:从弹跳球到风吹草地的程序化驱动

你有没有试过在 Substance Designer 里做动画?不是那种简单的参数变化,而是真正有逻辑、有节奏、能循环播放的动画。比如一个弹跳的小球,一个走时的时钟,一片随风摇曳的草地。很多人打开 Substance Designer,第一反应是…

2026/9/1 16:02:08
科目二直角转弯满分攻略:核心扣分规则与操作流程详解

科目二直角转弯满分攻略:核心扣分规则与操作流程详解

最近在驾校练车时,发现很多学员在科目二的直角转弯项目上,因为一些细节疏忽而意外丢分,非常可惜。这个项目本身操作并不复杂,但扣分点非常明确且严格,稍有不慎就会导致不合格。本文将系统梳理直角转弯的完整操作流程、…

2026/9/1 16:02:08
OpenCode与TRAE CN集成Superpowers技能包:完整配置与踩坑指南

OpenCode与TRAE CN集成Superpowers技能包:完整配置与踩坑指南

简介:面向需要在 OpenCode 与 TRAE CN 环境中落地 Superpowers 插件及 ui-ux-pro-max 技能的开发者,这份源码包提供了一套可直接参考的配置方案,帮助实现高质量、可运行代码的高效生成。资源共3个文件,包含HTML说明文档、inscode配…

2026/9/1 16:02:08
Zynq7000--AXI CDMA + BRAM 从PS侧DDR搬运数据记录

Zynq7000--AXI CDMA + BRAM 从PS侧DDR搬运数据记录

目录前言核心特性为什么需要 CDMA?1. CDMA1.1 原理对比:自己运 vs 快递代劳1.2 IP 配置与使用流程关键配置选项解析1. DMA 工作模式2. 4K 边界检查(Allow Unaligned Transfers 相关)3. 非对齐传输选项4. 传输数据宽度与突发长度5.…

2026/9/1 15:57:07