Django的一些魔改

介绍

Django和Django REST Framework都是功能很强大的框架,为我们的开发工作提供了极大的便利.但在某些特定需求下,难免存在一些限制和不便之处,为此我们需要进行一些自定义修改和拓展(魔改).

目录

  • 介绍
  • 目录
  • Django
    • Remove default Table
    • Remove is_staff
      • 重写AdminSite
      • property
    • Remove unused User field
    • Exception Handle
      • BackEnd
        • DRF中错误响应的结构
        • ValidationError的使用
        • Custom exception handling
      • FrontEnd
  • Django REST framework
    • Pagination unlimited
  • Django Admin
    • Display JSONField
    • Override UserAdmin

Django

Remove default Table

Django默认会在数据库中创建9张表,然而我们一般只使用Django作为后端接口来为前端提供服务,可能用不到这些默认生成的表.因此,为了节(技)省(术)资(洁)源(癖),我们可以禁用一些用不到的功能来避免在数据库中创建这些表.
Admin管理站点依赖的表:

  • django_seesion
  • django_admin_log
  • django_content_type

如果既不需要使用Admin管理站点功能,也不需要使用session做会话保持,还需从settings.py文件配置的MIDDLEWARE中删除django.contrib.sessions.middleware.SessionMiddleware,以及从INSTALLED_APPS中删除django.contrib.sessions.
User中的groupsuser_permissions设置为None可以阻止Django创建user_groups,user_user_permissions两张表

user.py
from django.contrib.auth.models import AbstractUserclass User(AbstractUser):...groups = []user_permissions = []...

⚠️在公司生产环境中,如果不使用migrate命令,可以手动删除以下四张表:

  • auth_group
  • auth_permission
  • auth_group_permissions
  • django_migrations

Remove is_staff

在Django中,默认使用is_staff字段来控制用户能否登录管理站点.但在实际应用中,我们可能希望只使用is_superuser一个字段来同时控制用户的管理员权限和登录管理站点的权限,有以下两种实现方式.

重写AdminSite

重写AdminSite中判断权限的逻辑,将其修改为判断is_superuser.参考覆盖默认的管理站点 | Django 管理站点 | Django 文档.

myproject/admin.py
from django.contrib import admin
from django.core.handlers.wsgi import WSGIRequestclass MyAdminSite(admin.AdminSite):def has_permission(self, request: WSGIRequest) -> bool:return request.user.is_active and request.user.is_superuser

property

除了重写AdminSite类,还可以借助property来实现.

user.py
from django.contrib.auth.models import AbstractUserclass User(AbstractUser):...@propertydef is_staff(self):return self.is_superuser

这种方式会有副作用,它会从Django的用户模型中移除is_staff字段.由于Django默认的UserAdmin还会展示is_staff字段,所以在Admin站点中访问用户页面时会报错,解决方案可参考下一节Remove unused User field.

此外,Django默认的用户Manager类中的create_usercreate_superuser方法内部会调用_create_user方法给is_staff字段设置默认值并写入数据库.但实际上数据库中并不存在is_staff字段,从而导致报错.因此,我们还需重写UserManager中的_create_user方法.

models/user.py
from django.contrib.auth.models import UserManager as BaseUserManagerclass UserManager(BaseUserManager, SoftDeleteManagerMixin):def _create_user(self, username, email, password, **extra_fields):extra_fields.pop("is_staff")return super()._create_user(username, email, password, **extra_fields)class User(AbstractUser):objects = UserManager()...

Update 2023.09.03

遇到了玄学错误...
还是老老实实用"重写AdminSite"吧....

当通过将User中的user_permissions设置为None的方式来移除Django默认创建的表时.
如果一个已经登录过AdminSite站点的用户,从超级用户变为普通用户后,没有清理Cookie就去访问Admin站点会报错(预期效果如下图).原因是此时用户是is_authenticated的,从而绕过了has_permission的检查.对于普通用户,AdminSite类中的get_app_list方法内部调用的has_module_permission会尝试访问user_user_permissions从而导致报错;对于超级用户,has_module_permission则会直接返回为True.
可以通过重写AdminSite的get_app_list方法来解决.
python class MyAdminSite(admin.AdminSite): ... def get_app_list(self, request: WSGIRequest): if request.user.is_superuser is False: return [] return super().get_app_list(request)
{% asset_img admin.png %}

Remove unused User field

Django默认的用户模型中提供了一些附加字段,如first_namelast_name等.如果想移除这些不需要的字段,可以在Model中将它们设置为None,将这些字段从数据库中删除.

from django.contrib.auth.models import AbstractUserclass User(AbstractUser):...first_name = Nonelast_name = None...

由于Django默认的UserAdmin可能会尝试展示已被移除的字段,导致报错,因此我们还需要自定义UserAdmin来覆盖原有逻辑,如list_displayfieldsets等.完整例子见UserAdmin

admin/user.py
from django.contrib.auth.admin import UserAdmin as DjangoUserAdminclass UserAdmin(DjangoUserAdmin):...

Exception Handle

BackEnd

DRF中错误响应的结构

首先,我们先了解一下DRF中错误响应的结构:

  • 对于大多数异常,DRF会返回一个形如{"detail": "Method 'DELETE' not allowed."}的结构,一定包含detail键.

  • 对于ValidationError,会返回一个以字段名称作为key,错误信息数组为value的结构;不属于某个特定字段的异常会使用setting中NON_FIELD_ERRORS_KEY的值(默认值为non_field_errors·)作为key.

ValidationError Response
{"field1": ["Error message 1","Error message 2"],"field2": ["Error message 3"],...
}
{"non_field_errors": ["Error message",],
}
ValidationError的使用

ValidationError(detail, code=None)必须传入detail参数,detail可以是list或dict,也可以是嵌套结构.

我们可以通过serializers中的validate_<field_name>方法对特定的某个字段进行验证,raise异常时detail参数可以为str/list,DRF最终会将其转换为{'<filed_name>': ['xxxx', 'yyyy']}的结构.

  • ValidationError('Invalid <filed_name>.') -> {'<filed_name>': ['Invalid <filed_name>.']}
  • ValidationError(['Invalid msg 1','Invalid msg 2']) -> {'<filed_name>': ['Invalid msg 1', 'Invalid msg 2']}

也可以在validate方法中对多个字段进行验证,此时raise异常时detail参数可以为str/list/dict.

  • ValidationError({'title': 'Invalid title'}) -> {'title': ['Invalid title']}
  • ValidationError({'title': ['Invalid title','Invalid title 2']}) -> {'title': ['Invalid title','Invalid title 2']}
  • ValidationError('Error message1') -> {'non_field_errors': ['Error message1']}
  • ValidationError(['Error message1','Error message2']) -> {'non_field_errors': ['Error message1', 'Error message2']}
Custom exception handling

有时候我们不得不在serializers的create/update中方法中raise ValidationError,此时DRF返回的结果为[“Error in create”],与上
述结构不符.

class TestSerializer(serializers.Serializer):...def create(self, validated_data):....raise serializers.ValidationError('Error in create.')

因此,我们还需要自定义异常处理来处理这种情况,以此保证错误响应结构的统一.
以及对于非预期的异常进行统一处理,将错误信息存储于detail字段中,并返回500 Internal Server Error.

lib.rest_framework.exception_handler.py
from django.conf import settings
from rest_framework import exceptions, serializers, status
from rest_framework.response import Response
from rest_framework.views import exception_handlerdef custom_exception_handler(exc, context):if isinstance(exc, exceptions.ValidationError):exc = exceptions.ValidationError(detail=serializers.as_serializer_error(exc))response = exception_handler(exc, context)if response is None:# request = context["request"] # Logging or other thingsif settings.DEBUG is True:return Response({"detail": str(exc)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)return response

FrontEnd

前端的错误响应处理则主要分为三种类型:

  • 400 Bad Request
    • 优先从detail字段获取异常信息
    • detail字段为空时,用NON_FIELD_ERRORS_KEY(这里设置为errors)的值作为异常信息
    • 否则,遍历错误响应数据,逐行展示每个字段的错误信息
  • 401 Unauthorized
    • 登录接口的请求直接reject
    • 非登录接口的GET请求,提示用户重新登录
    • 非登录接口的非GET请求,提示用户选择"直接重新登录"/“在新窗口登录”,防止用户填写的表单数据丢失
  • 其他 -> 直接依据HTTP状态码弹窗提示即可
request.js
import axios from 'axios';
import { MessageBox, Message } from 'element-ui'const service = axios.create({...
});service.interceptors.response.use((response) => {return response.data;},(error) => {let msg = '';const status = error.response.status;const method = error.response.config.method;const data = error.response.data;const { errors = [], detail = null } = data;if (status === 400) {if (detail !== null) {msg = detail;} else if (errors.length > 0) {msg = errors.join('<br />');} else if (typeof data === 'object') {msg = Object.entries(data).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join('<br />')}} else if (status === 401) {if (error.response.config.url !== '/account/login/') {if (method.toUpperCase() === 'GET') {MessageBox.alert('由于用户长时间未操作,请重新登录!', '错误提示', {type: 'warning',confirmButtonText: '重新登录',}).then(() => {// 重新登录}).catch(() => {// Close})} else {MessageBox.alert('登录状态已失效,您可在新窗口登录成功后返回当前页面','提示',{type: 'warning',distinguishCancelAndClose: true,confirmButtonText: '在新窗口登录',cancelButtonText: '直接重新登录',showCancelButton: true,}).then(() => {// 在新窗口登录window.open(window.location.href, '_blank')}).catch((action) => {if (action === 'cancel') {// 直接重新登录} else {// CloseMessage({message: '取消!',type: 'info',});}});}return Promise.reject(error);}return Promise.reject(error);} else if (status === 403) {msg = '你没有权限, 请联系管理员';} else if (status === 500) {msg = '服务器内部错误';} else if (status === 502 || status === 504) {msg = '服务器开小差了';} else {msg = `HTTP ${status}-错误${detail ? ':' + detail : ''}`;}Message({message: msg,type: 'error',duration: 5 * 1000,});return Promise.reject(error);}
);

Django REST framework

Pagination unlimited

在一些场景中(如下拉框选项),我们可能需要一次性从分页接口获取所有数据,通常的做法是前端传递一个非常非常大的page_size.借助DRF(Django Rest Framework)中的自定义分页类,我们可以更优雅的实现无限制分页,具体步骤如下:

  • 创建新的分页类,并在settings.py中配置
  • 后端通过特定的参数(如pagination_unlimited)来表示开启无限制分页,在需要开启的视图中声明为True
  • 前端传递一个特定的参数(如unlimited)来表示无限制的分页,以获取所有数据
  • Pagination - Django REST framework
Setting pagination class in settings.py
REST_FRAMEWORK = {..."DEFAULT_PAGINATION_CLASS": "lib.rest_framework.pagination.PageNumberPagination","PAGE_SIZE": 25...
}
lib/rest_framewor/pagination.py
from rest_framework import paginationclass PageNumberPagination(pagination.PageNumberPagination):page_size_query_param = "page_size"unlimited_query_param = "unlimited"unlimited_query_description = ("A boolean value to indicate whether return all results.")unlimited_view_attribute = "pagination_unlimited"def get_schema_operation_parameters(self, view):parameters = super().get_schema_operation_parameters(view)if getattr(view, self.unlimited_view_attribute, None) is True:parameters.append({"name": self.unlimited_query_param,"required": False,"in": "query","description": self.unlimited_query_description,"schema": {"type": "boolean",},})return parametersdef get_unlimited(self, request):unlimited = request.query_params.get(self.unlimited_query_param, None)if unlimited is None:return Falseif unlimited.lower() in ("1", "true"):return Trueelif unlimited.lower() in ("0", "false"):return Falsereturn Nonedef paginate_queryset(self, queryset, request, view=None):self.request = requestunlimited = self.get_unlimited(request)if (unlimited is Trueand getattr(view, self.unlimited_view_attribute, None) is True):page_size = queryset.count()paginator = self.django_paginator_class(queryset, page_size)self.page = paginator.page(1)return list(self.page)return super().paginate_queryset(queryset, request, view)
views.py
from rest_framework import viewsetsclass TestViewSet(viewsets.ModelViewSet):...pagination_unlimited = True...class TestViewSet(viewsets.ModelViewSet):...@propertydef pagination_unlimited(self):if self.action == "XXXX":return Truereturn False...

Django Admin

Display JSONField

在Django Admin中更友好的展示JSONField字段的值.

import jsonfrom django.contrib import admin
from django.utils.safestring import mark_safeclass TestAdmin(admin.ModelAdmin):...readonly_fields = ("pretty_config",)def pretty_config(self, obj):result = json.dumps(obj.config, indent=2, sort_keys=True, ensure_ascii=False)return mark_safe(f"<pre>{result}</pre>")x 

Override UserAdmin

基于django@517d3bb的UserAdmin源码,移除无关字段的UserAdmin如下.

admin/user.py
class UserAdmin(DjangoUserAdmin):fieldsets = ((None, {"fields": ("username", "password")}),(_("Personal info"),{"fields": (# "first_name",# "last_name","email",)},),(_("Permissions"),{"fields": ("is_active",# "is_staff","is_superuser",# "groups",# "user_permissions",),},),(_("Important dates"), {"fields": ("last_login", "date_joined")}),)list_display = ("username","email",# "first_name",# "last_name",# "is_staff",)search_fields = ("username",# "first_name",# "last_name","email",)filter_horizontal = (# "groups",# "user_permissions",)list_filter = (# "is_staff","is_superuser","is_active",# "groups",)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/50826.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3编程-import.meta.glob实现动态路由(菜单)

import.meta.glob 是vite提供的批量懒加载组件的方法 本地开发环境&#xff1a; const modules import.meta.glob(../views/**/*.vue)这段代码返回的modules是一个Map&#xff1a; key是vue文件的相对路径&#xff0c;值是一个函数&#xff0c;将函数打印出来&#xff0c;如…

Github个人网站搭建详细教程【Github+Jekyll模板】

文章目录 前言一、介绍1 Github Pages是什么2 静态网站生成工具3 Jekyll简介Jekyll 和 GitHub 的关系 4 Mac系统Jekyll的安装及使用安装Jekyll的简单使用 二、快速搭建第一个Github Pages网站三、静态网站模板——Chirpy1 个人定制 四、WordPress迁移到Github参考资料 前言 23…

智能电表怎么算电费的?

智能电表作为现代电力管理系统的核心组成部分&#xff0c;通过先进的计量技术和通信手段实现了电费计算的自动化与精准化。本文将详细介绍智能电表的工作原理以及如何基于这些数据计算电费。 一、智能电表的工作原理 -数据采集&#xff1a;智能电表内置传感器持续监测电流、电…

3DMAX科研绘图那些你不得不知道的插件

在3DMAX科研绘图中&#xff0c;有几个插件是不得不提的高效工具&#xff0c;它们能够显著提升科研绘图的效率和质量。以下是一些值得关注的插件&#xff1a; 1. DNAChain&#xff08;一键生成DNA链&#xff09; 功能描述&#xff1a;该插件允许用户沿着线条路径一键生成DNA链…

YOLOV8模型转TFJS 在Mac下遇到的版本的坑

1.目的&#xff1a;将训练好的yolov8模型转化成TFJS格式&#xff0c;用于在浏览器中通过tensorflow调用&#xff1b; 遇到问题&#xff1a; A KerasTensor cannot be used as input to a TensorFlow function. 本地环境&#xff1a; python :3.11 自动安装的版本为&#xf…

[Meachines] Lame smbd3.0-RCE

信息收集 IP AddressOpening Ports10.10.10.3TCP:21,22,139,445,3632 $ nmap -p- 10.10.10.3 --min-rate 1000 -sC -sV 21/tcp open ftp vsftpd 2.3.4 |_ftp-anon: Anonymous FTP login allowed (FTP code 230) | ftp-syst: | STAT: | FTP server status: | …

理解 HTTP 请求中 Query 和 Body 的异同

本文将深入探讨HTTP请求中的两个关键要素&#xff1a;查询参数&#xff08;Query&#xff09;和请求体&#xff08;Body&#xff09;。我们将阐明它们之间的差异&#xff0c;并讨论在何种情况下使用每一种。 HTTP 请求概述 HTTP 请求是客户端&#xff08;如浏览器&#xff09…

13 用户兴趣探索与开发 深度学习与强化学习

AI 技术在智能语音、图像识别、自然语言理解等领域&#xff0c;AI 都有大范围的落地。而应用得最早、最广泛的&#xff0c;还是 AI 在推荐领域的实践。 目前大部分主流 App 都集成了推荐系统&#xff0c;比如 58 同城 App 中推荐系统就不断通过对用户的兴趣的探索和开发&#…

计算机网络(Wrong Question)

一、计算机网络体系结构 1.1 计算机网络概述 D 注&#xff1a;计算机的三大主要功能是数据通信、资源共享、分布式处理。&#xff08;负载均衡、提高可靠性&#xff09; 注&#xff1a;几段链路就是几段流水。 C 注&#xff1a;记住一个基本计算公式&#xff1a;若n个分组&a…

Linux进程间通信(管道+共享内存)

进程间通信&#xff08;interprocess communication&#xff0c;简称 IPC&#xff09;指两个进程之间的通信。系统中的每一个进程都有各自的地址空间&#xff0c;并且相互独立、隔离&#xff0c;每个进程都处于自己的地址空间中。所以同一个进程的不同模块&#xff08;譬如不同…

matlab仿真 数字信号载波传输(下)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第七 章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; clear all M8; msg[1 4 3 0 7 5 2 6]; ts0.01; T1; %t0:ts:T; t0:ts:T-ts; %x0:ts:length(msg); x0:ts:length(msg)-ts; f…

《python语言程序设计》第6章10题使用isPrime函数 求小于10000的素数的个数

修改了一个地方&#xff0c;真的太棒了&#xff01; def isPrime(number):divisor 2while divisor < number / 2:if number % divisor 0:return Falsedivisor 1return Truedef printPrimeNumbers(numberOfPrimes):# 这个代码之前就没有用&#xff0c;作者写的目的是什么呢…

NC 最长回文子串

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 对于长度为n的…

【Unity渲染】后处理插件

使用 https://blog.csdn.net/qq_37524903/article/details/134467989 一些疑惑 1.Post-Process-Volume和Post-Process-Layer区别 Post-Process-Volume可以理解它可以有多个&#xff0c;每个代表一个空间的后处理&#xff0c;对应一个后处理文件Profile【类似material和shad…

6. 开发板烧录

1. 概述 采用恒玄的底板+2小板的开发板 2. 开发板资料 详见:<<BES AUDIO DEV BOARD USER MANUUAL_9v5.pdf>> 3. 硬件接线 供电:可以采用电池供电,也可以采用Type-c供电 烧录:采用Type-C口,实际上就是串口。(下图带黑色标志的)

【启明智显分享】基于国产Model3芯片的7寸触摸屏助力智慧医疗,电子床头屏提升护理交互

未来医院必然是以信息化为基础&#xff0c;以物联网为特征&#xff0c;以医疗为核心的服务型医院。病房作为医院的重要服务场所&#xff0c;成为智慧医院建设的重要一环。 为提高医护人员与患者的互动交流&#xff0c;给医疗注入智慧元素&#xff0c;让患者享受智能服务&#…

AJAX-Promise 详解

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 前言 一、Promise基本概念 1.1 定义 1.2 状态 1.3 构造函数 二、Promise基本用法 2.1 then() 2.2 ca…

keras的路透社数据训练对测试数据的概率总和计算问题

在使用keras内置数据路透社新闻分类的时候&#xff0c;使用训练的模型预测测试数据。然后发现对预测数据分类的概率总和不是1. pridiction model.predict(x_test)for i in range(0,46):print(np.sum(pridiction[i]))然而python深度学习这本书里面的是1.0 问题目前没有解决。…

掌握 Symfony 路由系统:配置与管理

掌握 Symfony 路由系统&#xff1a;配置与管理 Symfony 是一个非常流行的 PHP 框架&#xff0c;而路由系统是 Symfony 框架的核心组件之一。通过理解和掌握 Symfony 的路由系统&#xff0c;开发者可以更高效地配置和管理应用程序的 URL 结构&#xff0c;从而更好地控制应用程序…

醒醒,别睡了...讲《数据分析pandas库》了—/—<3>

直接上知识点 一、 1、新建数据框时建立索引 所有的数据框默认都已经使用从 0 开始的自然数索引&#xff0c;因此这里的"建立”索引指的是自定 df pd.DataFrame( {varl : 1.0, var2 :[1,2,3,4], var3 :[test,python,test,hello] , var4 : cons} , index [0,1,2,3]) …