环境说明
Ubuntu20.04
Python3.8
Django3.2.8
djangorestframework-simplejwt 5.2.0
django-cors-headers 3.13.0基本命令
# 生成数据库命令 python3 manage.py makemigrations python3 manage.py migrate # 启动命令 python3 manage.py runserver 0.0.0.0:8190 # 新建app命令 python3 manage.py startapp api # 新建项目命令 django-admin startproject jw
项目路径
. ├── api │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_user_phone_no.py │ │ ├── 0003_user_state.py │ │ ├── __init__.py │ │ └── __pycache__ │ │ ├── 0001_initial.cpython-38.pyc │ │ ├── 0002_alter_user_phone_no.cpython-38.pyc │ │ ├── 0002_alter_user_username.cpython-38.pyc │ │ ├── 0003_alter_user_username.cpython-38.pyc │ │ ├── 0003_user_state.cpython-38.pyc │ │ └── __init__.cpython-38.pyc │ ├── models.py │ ├── __pycache__ │ │ ├── admin.cpython-38.pyc │ │ ├── apps.cpython-38.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── models.cpython-38.pyc │ │ ├── serializers.cpython-38.pyc │ │ ├── urls.cpython-38.pyc │ │ └── views.cpython-38.pyc │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── log │ ├── product_collect.log │ ├── product_err.log │ └── product_info.log ├── manage.py ├── product │ ├── asgi.py │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── urls.cpython-38.pyc │ │ └── wsgi.cpython-38.pyc │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── utils ├── error.py ├── exception.py ├── __init__.py ├── pagination.py ├── __pycache__ │ ├── error.cpython-38.pyc │ ├── exception.cpython-38.pyc │ ├── __init__.cpython-38.pyc │ ├── pagination.cpython-38.pyc │ └── renderer.cpython-38.pyc ├── renderer.py └── rendererresponse.py
## setting.py
主要涉及到模块的加载及配置
```python
from pathlib import Path
import os
from datetime import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_ROOT = BASE_DIR / 'staticfiles'
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-tqz+9yqxcfh(z_e&lxh*5uzj)^phfgdi6(qfg$78xso2xfx-5a'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
AUTH_USER_MODEL = 'api.User'
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1), # 设置token有效时间
'REFRESH_TOKEN_LIFETIME': timedelta(days=2), # 刷新token有效时间
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': True, # 设置为True会在用户登录时,更新user表中的last_login字段
'ALGORITHM': 'HS256', # 加密算法
'SIGNING_KEY': SECRET_KEY, # 签名密钥
'VERIFYING_KEY': None, # 验证密钥,用于验证生成令牌的内容
'AUDIENCE': None, # 设置为None时,此字段将从token中排除,并且不会进行验证
'ISSUER': None, # 设置为None时,此字段将从token中排除,并且不会进行验证
'JWK_URL': None, # 设置为None时,此字段将从token中排除,并且在验证期间不使用
'LEEWAY': 0, # 用来给到期时间留一些余地
'AUTH_HEADER_TYPES': ('Bearer',), # 认证的标签头,类似jwt token中的jwt
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', # 身份验证的授权标头名称
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id', # 生成token中声明将用于存储用户标识符
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type', # 用于存储token类型的声明名称
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'JTI_CLAIM': 'jti', # 用于存储令牌的唯一标识符的声明名称
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
# Application definition
INSTALLED_APPS = [
'django.contrib.admin', # 站台管理系统
'django.contrib.auth', # 认证系统
'django.contrib.contenttypes', # content type框架
'django.contrib.sessions', # session框架
'django.contrib.messages', # message框架
'django.contrib.staticfiles', # 静态文件管理框架
'rest_framework', # restful插件
'versatileimagefield', # 图片插件
'django_filters', # 过滤插件
'api', # 接口app
'corsheaders', # 跨域插件
'rest_framework_simplejwt.token_blacklist' # 认证插件
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', # 用来解决跨域问题
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'product.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# 图像尺寸配置
VERSATILEIMAGEFIELD_RENDITION_KEY_SETS = {
'product_headshot': [
('full_size', 'url'),
('thumbnail', 'thumbnail__100x100'),
('medium_square_crop', 'crop__400x400'),
('small_square_crop', 'crop__50x50')
]
}
WSGI_APPLICATION = 'product.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'Liuyang123456.',
'NAME': 'product_center'
}
}
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
# 全局配置异常模块
'EXCEPTION_HANDLER': 'utils.exception.custom_exception_handler',
# 修改默认返回JSON的renderer的类
'DEFAULT_RENDERER_CLASSES': (
'utils.renderer.CustomRenderer',
),
}
# 日志配置
BASE_LOG_DIR = os.path.join(BASE_DIR, "log")
LOGGING = {
'version': 1,
'disable_existing_loggers': False, # 禁用已经存在的logger实例
# 日志文件的格式
'formatters': {
'standard': {'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
'[%(levelname)s][%(message)s]'},
'simple': {'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'},
'collect': {'format': '%(message)s'}
},
# 过滤器
'filters': {
'require_debug_true': {'()': 'django.utils.log.RequireDebugTrue',},
},
# 处理器
'handlers': {
# 在终端打印
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志
'class': 'logging.StreamHandler', #
'formatter': 'simple'
},
# 默认的
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "product_info.log"), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 2, # 最多备份几个
'formatter': 'standard',
'encoding': 'utf-8',
},
# 专门用来记错误日志
'error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "product_err.log"), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 3,
'formatter': 'standard',
'encoding': 'utf-8',
},
# 专门定义一个收集特定信息的日志
'collect': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "product_collect.log"),
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 3,
'formatter': 'collect',
'encoding': 'utf-8',
}
},
'loggers': {
# 默认的logger应用如下配置
'': {
'handlers': ['default', 'console', 'error'], # 上线之后可以把console移除
'level': 'DEBUG',
'propagate': True, # 向不向更高级别的logger传递
},
# 名为 collect的logger还单独处理
'collect': {
'handlers': ['console', 'collect'],
'level': 'INFO',
}
},
}
第三方类
# error.py
from rest_framework.exceptions import APIException
class ParamsException(APIException):
"""
serializers自定义错误响应
"""
def __init__(self, error):
self.detail = error
# exception.py
# 自定义异常处理
from rest_framework.views import exception_handler
from rest_framework.views import Response
from rest_framework import status
def custom_exception_handler(exc, context):
# 先调用REST framework默认的异常处理方法获得标准错误响应对象
response = exception_handler(exc, context)
# 如果response响应对象为空,则设置message这个key的值,并将状态码设为500
# 如果response响应对象不为空,则则设置message这个key的值,并将使用其本身的状态码
if response is None:
return Response({
'message': '服务器错误:{exc}'.format(exc=exc)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
else:
return Response({
'message': '{exc}'.format(exc=exc),
}, status=response.status_code, exception=True)
# pagination.py
from rest_framework.pagination import PageNumberPagination
class MyPagination(PageNumberPagination):
page_size = 2
page_size_query_param = 'size'
max_page_size = 30
# renderer.py
# 自定义返回处理
# 导入控制返回的JSON格式的类
from rest_framework.renderers import JSONRenderer
class CustomRenderer(JSONRenderer):
# 重构render方法
def render(self, data, accepted_media_type=None, renderer_context=None):
if renderer_context:
# print(renderer_context)
# print(renderer_context["response"].status_code)
# 响应的信息,成功和错误的都是这个
# 成功和异常响应的信息,异常信息在前面自定义异常处理中已经处理为{'message': 'error'}这种格式
# 如果返回的data为字典
if isinstance(data, dict):
# 响应信息中有message和code这两个key,则获取响应信息中的message和code,并且将原本data中的这两个key删除,放在自定义响应信息里
# 响应信息中没有则将msg内容改为请求成功 code改为请求的状态码
msg = data.pop('message', '请求成功')
code = data.pop('code', renderer_context["response"].status_code)
# 如果不是字典则将msg内容改为请求成功 code改为请求的状态码
else:
msg = '请求成功'
code = renderer_context["response"].status_code
# 自定义返回的格式
ret = {
'msg': msg,
'code': code,
'data': data,
}
# 返回JSON数据
return super().render(ret, accepted_media_type, renderer_context)
else:
return super().render(data, accepted_media_type, renderer_context)
项目urls引入到下一级
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
]
下一级urls文件
from django.urls import path
from . import views
from rest_framework_simplejwt.views import TokenRefreshView
urlpatterns = [
path('login/', views.Login.as_view(), name='token_obtain_pair'), # 登录
path('login/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # 登录续约
path('getUser/', views.UserView.as_view(), name='get_user'), # 获取个人信息
path('register/', views.RegisterView.as_view(), name='auth_register'), # 注册
path('changePassword', views.ChangePasswordView.as_view(), name='auth_change_password'), # 修改用户密码
path('update_profile/<int:id>/', views.UpdateProfileView.as_view(), name='auth_update_profile'), # 修改个人信息
path('getUserAll/', views.GetUserAll.as_view(), name='get_user_all'), # 获取所有用户表 支持分页
path('deleteUser/', views.DeleteUser.as_view(), name='delete_user'), # 删除用户,软删除
path('resetPassword/', views.ResetPassword.as_view(), name='reset_password'), # 超级管理员重置密码
]
views文件
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from .serializers import MyTokenObtainPairSerializer, UserSerializer, RegisterSerializer, UpdateUserSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import generics
from api.models import User
from utils.error import ParamsException
from utils.pagination import MyPagination
class Login(TokenObtainPairView):
permission_classes = [AllowAny]
serializer_class = MyTokenObtainPairSerializer
class UserView(APIView):
permission_classes = (IsAuthenticated,)
@staticmethod
def get(request):
user = request.user
serializer = UserSerializer(user)
return Response(data=serializer.data, status=status.HTTP_200_OK)
class RegisterView(generics.CreateAPIView):
permission_classes = (IsAdminUser,)
serializer_class = RegisterSerializer
class ChangePasswordView(APIView):
permission_classes = (IsAuthenticated,)
@staticmethod
def put(request):
data = request.data
if not request.user.check_password(data['old_password']):
raise ParamsException('旧密码不对')
if data['password'] != data['confirm_password']:
raise ParamsException('密码不一致')
request.user.set_password(data['password'])
request.user.save()
return Response({"message": "修改成功,请重新登录"})
class UpdateProfileView(generics.UpdateAPIView):
queryset = User.objects.all()
permission_classes = (IsAuthenticated,)
lookup_field = 'id'
serializer_class = UpdateUserSerializer
class GetUserAll(APIView):
permission_classes = (IsAdminUser,)
def get(self, request):
data_list = User.objects.filter(state=1).order_by('id')
# 实例化分页器
page_obj = MyPagination()
# 调用分页方法分页
page_query = page_obj.paginate_queryset(data_list, request, view=self)
# 序列化
ser = UserSerializer(page_query, many=True)
return page_obj.get_paginated_response(ser.data)
class DeleteUser(APIView):
permission_classes = (IsAdminUser,)
@staticmethod
def put(request):
data = request.data
User.objects.filter(id=data["id"]).update(state=0)
return Response({"message": "删除用户成功"})
class ResetPassword(APIView):
permission_classes = (IsAdminUser,)
@staticmethod
def put(request):
data = request.data
user = User.objects.get(id=data["id"])
user.set_password(data['password'])
user.save()
return Response({"message": "重置密码成功"})
model文件
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import BaseUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, username, password, **extra_fields):
if not username:
raise ValueError('必须设置用户名!')
user = self.model(username=username, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(username, password, **extra_fields)
class User(AbstractUser):
# 定义角色
class Role(models.TextChoices):
GENERAL = "普通用户"
ADMIN = "管理员"
SUPER = "超级管理员"
id = models.AutoField(db_column='id', primary_key=True)
is_active = models.BooleanField(db_column='is_active', default=True)
role = models.CharField(db_column='role', max_length=20, choices=Role.choices, null=True)
username = models.CharField(db_column='username', max_length=20, unique=True, blank=True, null=True)
phone_no = models.BigIntegerField(db_column='phone_no', blank=True, null=True)
state = models.BooleanField(db_column='state', default=True)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []
class Meta:
db_table = 'user'
objects = CustomUserManager()
serializers.py文件
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from api.models import User
from rest_framework import serializers, validators
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
username_field = User.USERNAME_FIELD
default_error_messages = {
'no_active_account': '用户名密码错误或账号未激活!'
}
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password', 'phone_no', 'last_login', 'role', 'is_superuser',
'is_active', 'date_joined']
extra_kwargs = {'password': {'write_only': True}}
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password', 'phone_no', 'last_login', 'role', 'is_superuser',
'is_active', 'date_joined', 'first_name']
extra_kwargs = {
'password': {'write_only': True},
'username': {'validators': [validators.UniqueValidator(
queryset=User.objects.all(),
message='名称重复')]},
}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
class UpdateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('is_superuser', 'id', 'username', 'is_active', 'last_name', 'phone_no', 'role')
extra_kwargs = {
'last_name': {'required': True},
}
def validate_username(self, value):
user = self.context['request'].user
if User.objects.exclude(id=user.id).filter(username=value).exists():
raise serializers.ValidationError("该用户名已被使用")
return value
def update(self, instance, validated_data):
new_data = {}
for key in validated_data.keys():
if validated_data[key] is not None and validated_data[key] != "":
new_data[key] = validated_data[key]
instance = super().update(instance, new_data)
instance.save()
return instance
版权说明
本文地址:http://www.liuyangdeboke.cn/?post=28
未标注转载均为本站远程,转载请注明文章出处:
发表评论