☰
零基础入门python35:Django用户登录与权限边界
2026/10/31 15:58:57 网站建设 项目流程

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

一、上一篇课后练习讲解

Admin 中创建时间应设置readonly_fields,订单金额不能开放编辑,因为订单是财务事实,修改应经过支付或退款服务并留下审计记录。

上一篇课后练习完整答案

上一篇练习的要求已落实到下面完整文件;先运行项目测试,再用 curl 对照状态码和数据库持久化结果
答案要点:ProductAdmin 配置搜索、筛选、关联预取和只读时间;Admin action 只下架,不开放订单金额编辑。

文件:shop/admin.py

完整参考答案文件

完整文件:shop/admin.py

fromdjango.contribimportadminfrom.modelsimportProduct@admin.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.py

fromdjango.contribimportadminfrom.modelsimportCategory,Order,OrderItem,Product@admin.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):model=OrderItem extra=0readonly_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 -q(Django 项目使用 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(user=request.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.httpimportJsonResponse@login_requireddefcheckout(request):ifrequest.method!="POST":returnJsonResponse({"error":"method_not_allowed"},status=405)# 购物车内容随后会交给 service.place_order,视图不直接扣库存。order=place_order(request.user,request.session.get("cart",{}))request.session["cart"]={}returnJsonResponse({"id":order.id,"total":str(order.total)},status=201)

login_required默认返回 302;纯 API 可以配置LoginRequiredMiddleware或自定义装饰器返回 401/403。视图只负责 HTTP,库存和事务放进 service,避免网页和 API 两套下单规则。

七、对象级权限的查询写法

deforder_detail(request,order_id):order=get_object_or_404(Order,id=order_id,user=request.user)returnJsonResponse(OrderSerializer(order).data)

把user=request.user放进get_object_or_404,用户 A 猜到 B 的 id 只会得到 404。管理员查看全部订单应使用独立 URL 和独立权限检查,而不是在普通详情接口中偷偷放宽查询。

八、会话安全和排错

登录后检查响应是否包含HttpOnly、SameSite=Laxcookie;生产环境开启 HTTPS 后再设置Secure=True。出现“登录后跳回登录页”,检查django.contrib.sessions、SessionMiddleware和AuthenticationMiddleware的顺序。AnonymousUser访问request.user.id会得到None,不要把它直接用于数据库写入。

九、本篇练习

写三个测试:未登录访问结账、普通用户访问他人订单、staff 访问管理员订单列表。下一篇会用 Session 保存购物车,并说明为什么价格必须重新从数据库读取。

十、认证故障排查清单

遇到 403 时按顺序检查:用户是否真的登录(request.user.is_authenticated)、SessionMiddleware 是否加载、CSRF token 是否随 POST 提交、接口是否要求 staff。不要为了让测试通过而全局关闭 CSRF;API 可以使用 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):queryset=Category.objects.order_by('name')serializer_class=CategorySerializerclassProductViewSet(viewsets.ReadOnlyModelViewSet):serializer_class=ProductSerializerdefget_queryset(self):query=Product.objects.filter(is_active=True).select_related('category')ifcategory:=self.request.query_params.get('category'):query=query.filter(category_id=category)ifkeyword:=self.request.query_params.get('q'):query=query.filter(name__icontains=keyword)returnquery.order_by('-created_at')classCheckoutView(generics.GenericAPIView):permission_classes=(permissions.IsAuthenticated,)serializer_class=CheckoutSerializerdefpost(self,request):serializer=self.get_serializer(data=request.data)serializer.is_valid(raise_exception=True)order=create_order(request.user,serializer.validated_data['items'])returnResponse(OrderSerializer(order).data,status=201)classMyOrderViewSet(viewsets.ReadOnlyModelViewSet):permission_classes=(permissions.IsAuthenticated,)serializer_class=OrderSerializerdefget_queryset(self):returnOrder.objects.filter(user=self.request.user).prefetch_related('items').order_by('-id')classCartView(generics.GenericAPIView):defget(self,request):returnResponse(request.session.get('cart',{}))defpost(self,request):product=generics.get_object_or_404(Product,id=request.data.get('product_id'),is_active=True)quantity=int(request.data.get('quantity',1))ifquantity<1orquantity>product.stock:returnResponse({'message':'数量必须大于0且不能超过库存'},status=status.HTTP_400_BAD_REQUEST)cart=request.session.setdefault('cart',{})cart[str(product.id)]=quantity request.session.modified=TruereturnResponse(cart,status=status.HTTP_201_CREATED)defdelete(self,request):cart=request.session.get('cart',{})cart.pop(str(request.data.get('product_id')),None)request.session.modified=TruereturnResponse(cart)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询