luffy后端
1创建Banner数据库
由于此数据库中的时间,删除等可以共用,所以我们可以给他单独写一个表模型,最后使它不要在建数据库的时候给建到数据库里面,此段代码可共用,接下来我们继承BaseModels
from django.db import models class BaseModels(models.Model): created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间') is_delete = models.BooleanField(default=False, verbose_name='是否删除') is_show = models.BooleanField(default=True, verbose_name='是否上架') orders = models.IntegerField(verbose_name='优先级') class Meta: abstract = True
from utils.models import BaseModels from django.db import models class Banner(BaseModels): title = models.CharField(max_length=16, unique=True, verbose_name='名称') image = models.ImageField(upload_to='banner', verbose_name='图片') link = models.CharField(max_length=64, verbose_name='跳转链接') info = models.TextField(verbose_name='详情') class Meta: db_table = 'luffy_banner' verbose_name_plural = '轮播图表' def __str__(self): return self.title
此时我们Banner表就建好了,迁移表
在迁移前要注册app
python manage.py makemigrations
python manage.py migrate
2写Banner接口
通过继承GenericViewSet,ListModelMixin,我们可以很快的将接口写出来,但是如果我们要返回指定格式的数据的话,我们必须从写ListModelMixin中的list方法,把list的数据传给另一个变量,这样我们方便按函数的指定参数传值,由于这个list方法我们也可能多处复用,所以我们又可以给他做一个封装。
from rest_framework.mixins import ListModelMixin from .response import APIResponse class BannerListView(ListModelMixin): def list(self, request, *args, **kwargs): res = super().list(request, *args, **kwargs) return APIResponse(data=res.data)
from luffyapi.utils.listviews import BannerListView from .models import Banner from .serializer import BannerSerializers # Create your views here. from rest_framework.viewsets import GenericViewSet from django.conf import settings class BannerView(GenericViewSet,BannerListView): queryset=Banner.objects.all().filter(is_delete=False,is_show=True).order_by('orders')[:settings.AREA] serializer_class=BannerSerializers
3 编写多方式手机号登录接口,验证手机号是否存在接口
from rest_framework.decorators import action from django.shortcuts import render from rest_framework.viewsets import ViewSet from .serializer import UserSerializer,UserCodeSerializer from utils.response import APIResponse from .models import UserInfo from rest_framework.exceptions import APIException from txy_sms import get_code,txy_sms_phone from django.core.cache import cache import re class UserLoginView(ViewSet): @action(methods=['POST'],detail=False) def login(self,request): res = UserSerializer(data=request.data) res.is_valid(raise_exception=True) token=res.context.get('token') icon=res.context.get('icon') username=res.context.get('username') return APIResponse(token=token,icon=icon,username=username) @action(methods=['GET'],detail=False) def code(self,request): mobile= request.query_params.get('mobile') if re.match(r'^1[3-9][0-9]{9}$', mobile): code=get_code() cache.set('code_%s'%mobile,code) res = txy_sms_phone(mobile,code) if res: return APIResponse(msg='验证码发送成功') else: return APIResponse(msg='验证码发送失败',code=101) else: return APIResponse(code=102,msg='手机号不合法') @action(methods=['POST'],detail=False) def login_code(self,request): res = UserCodeSerializer(data=request.data) res.is_valid(raise_exception=True) token=res.context.get('token') icon=res.context.get('icon') username=res.context.get('username') return APIResponse(token=token,icon=icon,username=username) class UserView(ViewSet): @action(methods=['GET'],detail=False) def mobile(self,request): try: # print(request.query_params.get('mobile')) mobile=request.query_params.get('mobile') UserInfo.objects.get(mobile=mobile) return APIResponse(msg='手机号已存在') except Exception as e: raise APIException('手机号不存在')
from rest_framework import serializers from django.contrib.auth import authenticate from rest_framework.exceptions import APIException from rest_framework.exceptions import ValidationError from django.contrib.auth.hashers import make_password from django.core.cache import cache from .models import UserInfo import re from utils.get_jwt import get_jwt class UserSerializer(serializers.ModelSerializer): username = serializers.CharField() class Meta: model = UserInfo fields = ['username', 'password'] def _get_user(self, request): username = request.get('username') password = request.get('password') if re.match(r'^1[3-9][0-9]{9}$', username): print(request.get('username')) print(request.get('password')) # user = UserInfo.objects.all().filter(mobile=username).first() user = UserInfo.objects.all().filter(mobile=username).first() user = authenticate(username=user.username, password=password) elif re.match(r'^.+@.+$', username): user = UserInfo.objects.all().filter(email=username).first() user = authenticate(username=user.username, password=password) else: user = authenticate(username=username, password=password) if user: return user else: raise ValidationError('用户名或密码错误') def validate(self, request): user = self._get_user(request) token = get_jwt(user) self.context['token'] = token self.context['icon'] = 'http://127.0.0.1:8000/media/' + str(user.icon) self.context['username'] = user.username return request class UserCodeSerializer(serializers.ModelSerializer): mobile = serializers.CharField() code = serializers.CharField() class Meta: model = UserInfo fields = ['mobile', 'code'] def _get_user(self, request): # print(code) mobile = request.get('mobile') code = request.get('code') if re.match(r'^1[3-9][0-9]{9}$', mobile): # user = UserInfo.objects.all().filter(mobile=username).first() user = UserInfo.objects.all().filter(mobile=mobile).first() else: raise APIException('手机号错误') if user and code == cache.get('code_%s' % mobile): cache.set('code_%s' % mobile, '') return user else: raise ValidationError('验证码错误') def validate(self, request): user = self._get_user(request) token = get_jwt(user) self.context['token'] = token self.context['icon'] = 'http://127.0.0.1:8000/media/' + str(user.icon) self.context['username'] = user.username return request
from rest_framework_jwt.settings import api_settings from rest_framework.exceptions import ValidationError jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER def get_jwt(user): try: payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token except Exception as e: raise ValidationError(str(e))
4 注册接口
@action(methods=['POST'], detail=False) def register(self, request): res = RegisterSerializer(data=request.data) res.is_valid(raise_exception=True) res.save() return APIResponse(msg='注册成功')
class RegisterSerializer(serializers.ModelSerializer): code = serializers.CharField() class Meta: model = UserInfo fields = ['mobile', 'code', 'password'] def validate(self, attrs): code = attrs.get('code') mobile=attrs.get('mobile') if not(code == cache.get('code_%s' % mobile)): raise APIException('验证码错误') attrs['username'] = mobile attrs.pop('code') return attrs def create(self, validated_data): user = UserInfo.objects.create_user(**validated_data) return user
5 课程分类接口
class CourseCategorySerializer(serializers.ModelSerializer): class Meta: model = CourseCategory fields = ['id', 'name']
class CourseCategoryView(GenericViewSet, CommonListModelMixin): queryset = CourseCategory.objects.all().filter(is_delete=False, is_show=True).order_by('orders') serializer_class = CourseCategorySerializer
5.1 所有课程接口
class TeacherSerializer(serializers.ModelSerializer): class Meta: model = Teacher # fields = '__all__' fields = ['id','name','role','title','signature','image','brief'] class CourseSerializer(serializers.ModelSerializer): # 课程列表和课程详情都用这个序列化类,序列化的字段多了好几个 # 方式三:子序列化:通过老师的序列化类来实现序列化 teacher=TeacherSerializer() class Meta: model = Course # fields = ['id', 'name'] # 要序列化更多字段 fields = [ 'id', 'name', 'course_img', 'price', # 课程价格 'students', # 学生数量 'pub_sections', # 发布了多少课时 'sections', # 总课时数量 'brief', # 课程介绍 'attachment_path', # 课程课件地址 'period', # 建议学习周期 'course_type_name', # 课程类型名字,表中没有这个字段,表模型中重写方法 'level_name', # 级别名字 'status_name', # 状态名字 # 'teacher_detail', # 老师 {name:xx,title:xxx} 'teacher', 'section_list', # 课时 最多4个课时 [{},{},{},{}] ] # 要序列化更多字段:图片,有多少课时,更新了多少,学生数量,价格。。。 teacher字段:{name:xx,title:asdf} 章节下的课时,最多四个课时 # 指定序列化的字段:三种方式 # 方式一:在表模型中写 # 方式二:序列化类中写 # teacher = serializers.SerializerMethodField() # def get_teacher(self, obj): # return {'name': obj.teacher.name, 'title': obj.teacher.title, 'image': str(obj.teacher.image)} # 方式三:子序列化:通过老师的序列化类来实现序列化
class CourseView(GenericViewSet, CommonListModelMixin,RetrieveModelMixin): queryset = Course.objects.all().filter(is_delete=False, is_show=True).order_by('orders') serializer_class = CourseSerializer pagination_class =PageNumberPagination filter_backends=[OrderingFilter,DjangoFilterBackend] ordering_fields=['orders','price','students'] # 加过滤:按课程分类过滤-- django-filter实现 filterset_fields=['course_category']
class Course(BaseModel): """课程""" course_type = ( (0, '付费'), (1, 'VIP专享'), (2, '学位课程') ) level_choices = ( (0, '初级'), (1, '中级'), (2, '高级'), ) status_choices = ( (0, '上线'), (1, '下线'), (2, '预上线'), ) name = models.CharField(max_length=128, verbose_name="课程名称") # blank 后台管理录入可以为空,null存到数据库字段可以为空 course_img = models.ImageField(upload_to="courses", max_length=255, verbose_name="封面图片", blank=True, null=True) # 付费类型 course_type = models.SmallIntegerField(choices=course_type, default=0, verbose_name="付费类型") # TextField 大文本, 存html brief = models.TextField(max_length=2048, verbose_name="详情介绍", null=True, blank=True) # 难度等级 level = models.SmallIntegerField(choices=level_choices, default=0, verbose_name="难度等级") # 发布日期 pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True) # 建议学习周期 period = models.IntegerField(verbose_name="建议学习周期(day)", default=7) # 课件路径 attachment_path = models.FileField(upload_to="attachment", max_length=128, verbose_name="课件路径", blank=True, null=True) status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="课程状态") # 优化字段 students = models.IntegerField(verbose_name="学习人数", default=0) # 课程一边录,一边传 sections = models.IntegerField(verbose_name="总课时数量", default=0) pub_sections = models.IntegerField(verbose_name="课时更新数量", default=0) price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0) # 关联字段 # ForeignKey的on_delete的选择: # models.CASCADE 级联删除,用的少,除非真的要删除 # models.SET_NULL 关联字段设为空 null=True # models.SET_DEFAULT 关联字段设为默认值 defalut='asfasd' # models.DO_NOTHING 什么都不做, 不用强外键关联 # models.SET() 放一个函数内存地址,关联字段删除时,执行这个函数 # 外键关联的好处和坏处 # -好处在于 插入修改数据,有校验,能够保证数据不会错乱,不会出现脏数据 # -坏处在于 有校验,速度慢,数量越大,越慢,咱们可以通过程序控制不加入脏数据 # 公司内部为了效率,一般不建立外键关联,关系在 ,只是没有那条线了 # 在django中不建立外键关联,只是不创建外键,关联关系还在【关联查询】,也是使用ForeignKey,只是加一个参数,加了之后,没有约束,但你们关系还在 teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name="授课老师", db_constraint=False) course_category = models.ForeignKey("CourseCategory", on_delete=models.SET_NULL, db_constraint=False, null=True, blank=True, verbose_name="课程分类") class Meta: db_table = "luffy_course" verbose_name = "课程" verbose_name_plural = "课程" def __str__(self): return "%s" % self.name # 序列化的字段方法 def course_type_name(self): return self.get_course_type_display() def level_name(self): return self.get_level_display() def status_name(self): return self.get_status_display() # 序列化teacher的方法 # def teacher_detail(self): # return {'name': self.teacher.name} def section_list(self): section_list = [] # 先根据课程取出所有章节,循环每个章节,取出所有课时,拼接到列表中,当列表长度等于四,就结束 # 取到所有章节 原来是 表名小写_set.all() course_chapter_list = self.coursechapters.all() for chapter in course_chapter_list: # 拿到该章节下所有课时 原来是 表名小写_set.all() course_section_list = chapter.coursesections.all() for section in course_section_list: section_list.append({'id': section.id, 'name': section.name, 'section_link': section.section_link, 'duration': section.duration, # 视频时长 'free_trail': section.free_trail, }) if len(section_list) >= 4: return section_list return section_list
class CommonPageNumberPagination(PageNumberPagination): page_size = 2 page_query_param = 'page' page_size_query_param = 'size' max_page_size = 5
5.2 课程详情页接口
class CourseChapterView(GenericViewSet, CommonListModelMixin): queryset = CourseChapter.objects.all().filter(is_delete=False, is_show=True).order_by('orders') serializer_class = CourseChapterSerializer filter_backends = [DjangoFilterBackend] filterset_fields = ['course']
class CourseSectionSerializer(serializers.ModelSerializer): class Meta: model = CourseSection fields = '__all__' class CourseChapterSerializer(serializers.ModelSerializer): # 三种:序列化类中写,表模型中写,子序列化 # 一个章节下,有很多课时,课时是多条,子序列化的时候,一定不要忘了many=True coursesections = CourseSectionSerializer(many=True) class Meta: model = CourseChapter fields = ['id', 'chapter', 'name', 'summary', 'coursesections']
6 搜索后台接口
# es:分布式的全文检索引擎,实现像百度搜索的样子 class CourseSearchView(GenericViewSet, ListModelMixin): queryset = Course.objects.all().filter(is_delete=False, is_show=True).order_by('orders') serializer_class = CourseSerializer pagination_class =PageNumberPagination filter_backends=[SearchFilter] # 加过滤:按课程分类过滤-- django-filter实现 search_fields=['name'] # 目前只能搜实战课 :轻课,实战课,免费课 # def list(self, request, *args, **kwargs): # res=super().list( request, *args, **kwargs) # # res.data 是搜出来的实战课课程 # # #搜轻课 # words=request.query_params.get('search') # # 去轻课表搜 # # 去免费课表搜 # # #{code:100,msg:成功,actual_list:[{},{},{}],free_list:[{},{},{}],light_list:[{},{},{}]} # # return APIResponse(code=100,msg='成功',actual_list=res.data,free_list='',light_list='')
7 生成订单接口
from django.shortcuts import render # Create your views here. from .models import Order # 自动生成路由,新增功能:订单表存数据 from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import CreateModelMixin from .serializer import OrderSerializer from utils.response import APIResponse from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated class OrderView(GenericViewSet): queryset = Order.objects.all() serializer_class = OrderSerializer # 登录后才能访问 authentication_classes = [JSONWebTokenAuthentication] permission_classes = [IsAuthenticated] # 这个新增接口,既要新增,又要返回支付链接,冲写create方法 def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data,context={'request':request}) serializer.is_valid(raise_exception=True) serializer.save() pay_url = serializer.context.get('pay_url') return APIResponse(pay_url=pay_url)
from rest_framework import serializers from .models import Order, OrderDetail from course.models import Course from rest_framework.exceptions import APIException from django.conf import settings class OrderSerializer(serializers.ModelSerializer): # courses不是order表的字段,一定要重写 # 前端传入的是 courses:[1,3,4]----->转换成课程对象[课程对象1,课程对象3,课程对象4] courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True) class Meta: model = Order fields = ['courses', 'total_amount', 'subject', 'pay_type'] # 只用来做数据校验和反序列化 def _check_price(self, attrs): total_amount = attrs.get('total_amount') # 循环所有课程,取出价格累加得到总价格 real_total = 0 for course in attrs.get('courses'): real_total += course.price if not total_amount == real_total: # 不正常,抛异常 raise APIException('价格不一致') else: return real_total def _get_trade_no(self): import uuid return str(uuid.uuid4()) def _get_user(self): # 当前登录用户,request.user request = self.context.get('request') return request.user # 必须通过了登录认证,才有当前登录用户 def _get_pay_url(self, total_amount, subject, trade_no): from libs.alipay_common import GATEWAY, alipay pay_str = alipay.api_alipay_trade_page_pay( out_trade_no=trade_no, total_amount=float(total_amount), subject=subject, return_url=settings.RETURN_URL, # get回调地址 notify_url=settings.NOTIFY_URL # post回调地址 ) return GATEWAY + pay_str def _before_create(self, pay_url, attrs, user, trade_no): self.context['pay_url'] = pay_url attrs['user'] = user attrs['out_trade_no'] = trade_no # 要不要courses剔除? attrs:{courses:[对象1,对象2],total_amount:11,subject:xx,pay_type:1,user:user,out_trade_no:3333} def validate(self, attrs): # 1 校验价格:计算一下总价格和后端算出来的总价格是否一致 total_amount = self._check_price(attrs) # 2)生成订单号:唯一的, trade_no = self._get_trade_no() # 3)支付用户:request.user user = self._get_user() # 4)支付链接生成,放入到self.context中 pay_url = self._get_pay_url(total_amount, attrs.get('subject'), trade_no) # 5)入库(两个表)的信息准备:重写create方法 self._before_create(pay_url, attrs, user, trade_no) return attrs def create(self, validated_data): # {courses:[对象1,对象2],total_amount:11,subject:xx,pay_type:1,user:user,out_trade_no:3333} courses = validated_data.pop('courses') order = Order.objects.create(**validated_data) # 存订单详情表 for course in courses: OrderDetail.objects.create(order=order, course=course, price=course.price, real_price=course.price) return order
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现