ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

零基础入门python35:Django用户登录与权限边界

零基础入门python35:Django用户登录与权限边界 零基础入门python35Django用户登录与权限边界一、上一篇课后练习讲解Admin 中创建时间应设置readonly_fields订单金额不能开放编辑因为订单是财务事实修改应经过支付或退款服务并留下审计记录。上一篇课后练习完整答案上一篇练习的要求已落实到下面完整文件先运行项目测试再用 curl 对照状态码和数据库持久化结果答案要点ProductAdmin 配置搜索、筛选、关联预取和只读时间Admin action 只下架不开放订单金额编辑。文件shop/admin.py完整参考答案文件完整文件shop/admin.pyfromdjango.contribimportadminfrom.modelsimportProductadmin.register(Product)classProductAdmin(admin.ModelAdmin):list_display(name,category,price,stock,is_active)list_select_related(category,)search_fields(name,slug)list_filter(is_active,category)完整参考答案文件本篇对应的交付源码完整文件django-shop/shop/admin.pyfromdjango.contribimportadminfrom.modelsimportCategory,Order,OrderItem,Productadmin.register(Category)classCategoryAdmin(admin.ModelAdmin):list_display(name,slug)prepopulated_fields{slug:(name,)}admin.register(Product)classProductAdmin(admin.ModelAdmin):list_display(name,category,price,stock,is_active)list_filter(is_active,category)search_fields(name,)prepopulated_fields{slug:(name,)}classOrderItemInline(admin.TabularInline):modelOrderItem extra0readonly_fields(product,product_name,price,quantity)admin.register(Order)classOrderAdmin(admin.ModelAdmin):list_display(id,user,status,total_amount,created_at)list_filter(status,)inlines(OrderItemInline,)验收命令python -m pytest -qDjango 项目使用 python manage.py test。预期测试通过若失败先检查迁移、配置和事务回滚。二、本篇完成什么商品列表公开购物车和结算需要登录订单列表只返回当前用户的订单。认证解决“你是谁”权限解决“你能操作什么”。三、DRF权限配置classCheckoutView(generics.GenericAPIView):permission_classes(permissions.IsAuthenticated,)权限类是第一道门get_queryset()仍然必须过滤用户因为“已登录”不等于“能看所有订单”。不要用客户端传来的user_id做权限依据。四、验收未登录访问/api/checkout/返回 403登录后可以结算用户 A 的订单不会出现在用户 B 的/api/orders/。课后练习为订单详情增加对象级权限测试。项目增量Django 认证与权限注册使用 Django 密码哈希登录由 AuthenticationForm 校验商品后台权限和前台订单权限是两套规则不要把“已登录”当成“可以修改所有数据”。defrequire_login(request):ifnotrequest.user.is_authenticated:returnredirect(login)returnNone订单查询必须 filter(userrequest.user)详情页猜 id 也不能看到他人订单。验收未登录、普通用户和 staff 三种角色课后练习增加装饰器保护结账页面。五、上一篇练习讲解Admin action 也要有权限批量下架 action 应只修改is_active并使用request.user写审计日志不能把 action 做成“批量改价格”。测试用 staff 用户调用 action普通用户访问 Admin 应返回 302 到登录页或 403取决于项目设置。六、认证和授权是两件事认证回答“你是谁”授权回答“你能做什么”。Django 的request.user.is_authenticated只能证明登录不能让用户读取别人的订单。结账视图可以这样写fromdjango.contrib.auth.decoratorsimportlogin_requiredfromdjango.httpimportJsonResponselogin_requireddefcheckout(request):ifrequest.method!POST:returnJsonResponse({error:method_not_allowed},status405)# 购物车内容随后会交给 service.place_order视图不直接扣库存。orderplace_order(request.user,request.session.get(cart,{}))request.session[cart]{}returnJsonResponse({id:order.id,total:str(order.total)},status201)login_required默认返回 302纯 API 可以配置LoginRequiredMiddleware或自定义装饰器返回 401/403。视图只负责 HTTP库存和事务放进 service避免网页和 API 两套下单规则。七、对象级权限的查询写法deforder_detail(request,order_id):orderget_object_or_404(Order,idorder_id,userrequest.user)returnJsonResponse(OrderSerializer(order).data)把userrequest.user放进get_object_or_404用户 A 猜到 B 的 id 只会得到 404。管理员查看全部订单应使用独立 URL 和独立权限检查而不是在普通详情接口中偷偷放宽查询。八、会话安全和排错登录后检查响应是否包含HttpOnly、SameSiteLaxcookie生产环境开启 HTTPS 后再设置SecureTrue。出现“登录后跳回登录页”检查django.contrib.sessions、SessionMiddleware和AuthenticationMiddleware的顺序。AnonymousUser访问request.user.id会得到None不要把它直接用于数据库写入。九、本篇练习写三个测试未登录访问结账、普通用户访问他人订单、staff 访问管理员订单列表。下一篇会用 Session 保存购物车并说明为什么价格必须重新从数据库读取。十、认证故障排查清单遇到 403 时按顺序检查用户是否真的登录request.user.is_authenticated、SessionMiddleware 是否加载、CSRF token 是否随 POST 提交、接口是否要求 staff。不要为了让测试通过而全局关闭 CSRFAPI 可以使用 token 认证后台表单仍保留 CSRF。密码验证失败时确认使用set_password而非直接给password字段赋值后者会把明文写入数据库。可在测试中打印response.wsgi_request.user和client.cookies但生产日志不应输出 cookie。认证和授权边界固定后购物车、订单、管理员 action 都只依赖request.user不会再从请求体猜用户。十一、角色矩阵练习把角色写成矩阵更直观游客可读在售商品普通用户可管理自己的购物车和订单staff 可维护商品、查看全部订单超级用户可管理权限。为矩阵每一格至少写一个测试避免以后新增接口时把“已登录”误当成“管理员”。本篇结束完整模块文件下面是交付项目中真实存在的完整文件 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)
返回列表