1 有车型(CarModel),车厂(CarFactory),经销商(Distributor)三个表,一个车厂可以生产多种车型,一个经销商可以出售多种车型,一个车型可以有多个经销商出售
车型:车型名,车型出厂价,车厂id
车厂:车厂名,车厂地址,联系电话
经销商:经销商名,地址,联系电话
2 有用户表,基于django内置user表,扩展mobile字段
3 编写登陆接口,jwt方式返回token,
格式为{status:100,msg:登陆成功,token:safasdfa}
4 所有接口(除登录外),必须登录后才能访问
5 管理员登陆后可以增,删,单查,群查,改 车型,车厂,经销商(具备所有接口权限)
6 普通用户登陆可以查看车型,车厂,经销商单条,所有(只有查看权限)
7 所有查询所有接口带分页功能
8 查询所有车型接口,可以按车型名字精准过滤9 加分项:
用户注册接口
管理员有用户锁定,删除用户功能
注册应用:
INSTALLED_APPS = [ ... 'app01.apps.App01Config', 'rest_framework', 'rest_framework_simplejwt', ]
连接数据库:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'text19', 'HOST': '127.0.0.1', 'POST': '3306', 'USER': 'root', 'PASSWORD': 'JIAJIA', } }
语言配置:
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True
注意表名书写:
1、在models表中,表名是驼峰体 CarModel、CarFactory、Distributor
2、除表名驼峰体外,其余变量名用下划线形式 car_factory
3、函数内绝对不能出现下划线和驼峰体混用情况
内置user表扩展字段:
1、需把user表的扩写字段在model表中完成了才能迁移表格,不然需重建库
2、内置user表扩展表名可以直接命名
3、继承 AbstractUser ----> class User(AbstractUser)
4、需配置:
AUTH_USER_MODEL = 'app01.User'
迁移表格及创建超级用户命令:
python manage.py createsuperuser
python manage.py makemigrations
python manage.py migrate
from django.db import models from django.contrib.auth.models import AbstractUser # 车型(CarModel),车厂(CarFactory),经销商(Distributor) # 有用户表,基于django内置user表,扩展mobile字段 class User(AbstractUser): mobile = models.CharField(max_length=11) # 车型表 class CarModel(models.Model): name = models.CharField(max_length=32, verbose_name='车型名') price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='车型出厂价') car_factory = models.ForeignKey(to='CarFactory', on_delete=models.SET_NULL, null=True) distributor = models.ManyToManyField(to='Distributor') # 车厂表 class CarFactory(models.Model): name = models.CharField(max_length=32, verbose_name='车厂名') address = models.CharField(max_length=32, verbose_name='车厂地址') phone = models.CharField(max_length=32, verbose_name='车厂联系电话') # 经销商 class Distributor(models.Model): name = models.CharField(max_length=32, verbose_name='经销商名') address = models.CharField(max_length=32, verbose_name='经销商地址') phone = models.CharField(max_length=32, verbose_name='经销商联系电话')
# view.py # 编写登陆接口,jwt方式返回token from rest_framework.response import Response from rest_framework.generics import GenericAPIView from .serializer import LoginSerializer class UserView(GenericAPIView): authentication_classes = [] # 登录接口不用登录后再访问 serializer_class = LoginSerializer def post(self, request): ser = self.get_serializer(data=request.data) if ser.is_valid(): # 会执行字段自己的校验(没有),执行局部钩子(没有),执行全局钩子(写了:校验用户,签发token) return Response(ser.validated_data) else: return Response({'code': 101, 'msg': '用户名或密码错误'})
# serializer.py # jwt登录 from rest_framework import serializers from .models import User import re from rest_framework.exceptions import ValidationError from rest_framework_simplejwt.serializers import TokenObtainPairSerializer class LoginSerializer(serializers.Serializer): username = serializers.CharField() password = serializers.CharField() # 写全局钩子 def validate(self, attrs): # 校验用户,签发token username = attrs.get('username') password = attrs.get('password') # 2 使用正则判断用用户名是邮箱,手机号还是用户名,分别去查询当前用户 if re.match(r'^1[3-9][0-9]{9}$', username): user = User.objects.filter(mobile=username).first() elif re.match(r'^.+@.+$', username): user = User.objects.filter(email=username).first() else: user = User.objects.filter(username=username).first() if user and user.check_password(password): # 3 校验密码 # 4 签发token refresh = TokenObtainPairSerializer.get_token(user) data = {'code': 100, 'msg': '登录成功', 'token': str(refresh), 'access': str(refresh.access_token), } return data else: raise ValidationError('用户名或密码错误')
要求:
所有接口(除登录外),必须登录后才能访问
管理员登陆后可以增,删,单查,群查,改 车型,车厂,经销商(具备所有接口权限)
普通用户登陆可以查看车型,车厂,经销商单条,所有(只有查看权限)思路:
1、写五个接口最快捷的方式便是继承 ModelViewSet
2、关于登录认证这里全局配置是最好的
3、关于权限认证,有默认权限 IsAuthenticated 和自定义权限 SuperPermission
4、这里分为普通用户权限和管理员权限,分两大类
5、给管理员单独写个自定义权限类,在视图层相应配置
# 全局配置登录认证 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_simplejwt.authentication.JWTAuthentication'], # 认证组件 }
序列化类:
1、车型表有一对多车厂关系和多对多经销商关系
2、在模型model中返回自身+车厂信息+经销商信息
3、返回所以信息,分好序列化和反序列化
@property def car_factory_detail(self): return {'name': self.carfactory.name, 'addr': self.carfactory.addr} def distributor_detail(self): l = [] for item in self.distributor.all(): l.append({'name': item.name, 'addr': item.addr, 'telephone': item.telephone}) return l
# serliazer.py # 车型:CarModel from .models import CarModel,CarFactory,Distributor class CarModelSerializer(serializers.ModelSerializer): class Meta: model = CarModel fields = ['id', 'name', 'price', 'carfactory', 'distributor', 'car_factory_detail', 'distributor_detail'] extra_kwargs = { 'car_factory': {'write_only': True}, 'distributor': {'write_only': True}, } car_factory_detail = serializers.DictField(read_only=True) distributor_detail = serializers.ListField(read_only=True) # 车厂:CarFactory class CarFactorySerializer(serializers.ModelSerializer): class Meta: model = CarFactory fields = '__all__' # 经销商:Distributor class DistributorSerializer(serializers.ModelSerializer): class Meta: model = Distributor fields = '__all__'
# views.py from rest_framework.permissions import IsAuthenticated from .permission import SuperPermission from .pagination import CommonPageNumberPagination from .filters import CommonFilter # 车型:CarModel class CarModelView(GenericViewSet, ListAPIView, RetrieveModelMixin): permission_classes = [IsAuthenticated] # 用jwt自带得权限类验证登陆 queryset = CarModel.objects.all() serializer_class = CarModelSerializer filter_backends = [CommonFilter] # 自定义的过滤类 pagination_class = CommonPageNumberPagination # 分页 class CarModelDetailView(GenericViewSet, CreateAPIView, UpdateModelMixin, CreateModelMixin, DestroyModelMixin): permission_classes = [SuperPermission] queryset = CarModel.objects.all() serializer_class = CarModelSerializer # 车厂:CarFactory class CarFactoryView(GenericViewSet, ListAPIView, RetrieveModelMixin): permission_classes = [IsAuthenticated] # 用jwt自带得权限类验证登陆 queryset = CarFactory.objects.all() serializer_class = CarFactorySerializer filter_backends = [CommonFilter] # 自定义的过滤类 pagination_class = CommonPageNumberPagination # 分页 class CarFactoryDetailView(GenericViewSet, CreateAPIView, UpdateModelMixin, CreateModelMixin, DestroyModelMixin): permission_classes = [SuperPermission] queryset = CarFactory.objects.all() serializer_class = CarFactorySerializer # 经销商:Distributor class DistributorView(GenericViewSet, ListAPIView, RetrieveModelMixin): permission_classes = [IsAuthenticated] # 用jwt自带得权限类验证登陆 queryset = Distributor.objects.all() serializer_class = DistributorSerializer filter_backends = [CommonFilter] # 自定义的过滤类 pagination_class = CommonPageNumberPagination # 分页 class DistributorDetailView(GenericViewSet, CreateAPIView, UpdateModelMixin, CreateModelMixin, DestroyModelMixin): permission_classes = [SuperPermission] queryset = Distributor.objects.all() serializer_class = DistributorSerializer
# permission.py # 给管理员定义的权限类 from rest_framework.permissions import BasePermission # 只有超级管理员能访问,其他人都不能访问,给需要限制得视图加上即可 class SuperPermission(BasePermission): def has_permission(self, request, view): # 到这,正常应该登录完了,登录完了 request.user 当前登录用户 try: user_type = request.user.is_superuser if user_type == 1: return True else: self.message = '您不是管理员,您没有权限操作' return False except Exception as exc: self.message = '您没有登录,您没有权限操作' return False
自定义类注意:
1、自定义过滤、分页、排序、权限等等都可以直接套用我们自己的模板来写
2、自定义相应的需在视图层配上响应应用的地方
permission_classes = [IsAuthenticated] # 用jwt自带权限
permission_classes = [SuperPermission] # 自定义权限
filter_backends = [CommonFilter] # 自定义的过滤类
pagination_class = CommonPageNumberPagination # 分页
# pagination.py # 自定义分类 from rest_framework.pagination import PageNumberPagination class CommonPageNumberPagination(PageNumberPagination): page_size = 3 # 设置的默认会显示的数量 page_query_param = 'page' page_size_query_param = 'size' # &size=2 max_page_size = 10 # 最大显示数量 # http://127.0.0.1:7000/api/v1/books/?search=34&size=2
# filter.py # 自定义过滤 # 查询所有车型接口,可以按车型名字精准过滤 from rest_framework.filters import BaseFilterBackend class CommonFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, view): # queryset 之前所有的数据 Book.objects.all() # request 当次请求request if request.query_params.get('name'): queryset = queryset.filter(name__contains=request.query_params.get('name')) # if request.query_params.get('price'): # queryset = queryset.filter(price=request.query_params.get('price')) # if request.query_params.get('price__gt'): # queryset = queryset.filter(price__gt=request.query_params.get('price__gt')) return queryset
总路由:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/', include('app01.urls')), ]
分路由:
# app01\urls.py from django.urls import path from app01 import views from rest_framework.routers import SimpleRouter from .views import CarModelView, CarFactoryView, DistributorView, CarModelDetailView, CarFactoryDetailView, DistributorDetailView router = SimpleRouter() router.register('model', CarModelView, 'model') # 车型 router.register('models', CarModelDetailView, 'models') router.register('factory', CarFactoryView, 'factory') # 车厂 router.register('factorys', CarFactoryDetailView, 'factorys') router.register('people', DistributorView, 'people') # 经销商 router.register('peoples', DistributorDetailView, 'peoples') urlpatterns = [ path('car/', views.UserView.as_view()), ] urlpatterns += router.urls
全局异常处理作用:
postman中报错信息可以定制返回格式
# settings.py REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler' }
# exceptions.py # 全局异常处理 from rest_framework.views import exception_handler from rest_framework.response import Response def common_exception_handler(exc, context): res = exception_handler(exc, context) if res: # drf异常 msg = res.data.get('detail') or res.data or '系统异常,请联系系统管理员' return Response({'code': 999, 'msg': msg}) else: return Response({'code': 888, 'msg': '系统异常,请联系系统管理员"%s' % str(exc)})