昨日回顾
# 1 后端项目目录调整---》 正常: 项目名: -项目名同名文件夹 -配置文件:settings.py -总路由:urls.py -wsgi.py -asgi.py -user -home -order -templates -static -media -manage.py 调整后: 项目名: -logs -项目名同名文件夹(改名) -apps -user -home -order -libs -utils -配置文件夹: dev.py pro.py -总路由:urls.py -wsgi.py -asgi.py -media -scripts -manage.py # 无论调整成什么样,项目想运行 -1 注册app,要从环境变量开始导起 -可以把apps所在路径加入到环境变量---》配置文件中 ---》sys.path -2 项目运行,先执行配置文件 -manag.py 运行项目---》配置文件路径要对应 - wsgi.py&asgi.py 上线运行---》配置文件路径要对应 -3 把某些路径加入环境变量后---》以后导入模块会有多种选择 -建议在app内部---》用相对导入 -在外部,用最短路径导入---》用长的可能会报app没注册的错误 # 在pycharm中导入模块爆红 -把被加入到环境变量的路径--->编程sorce root # 以后运行项目,点绿色箭头不允许 -先试命令行是否可行---》命令行不行---》项目有问题--》解决问题 -多半是 配置文件路径问题,配置文件中有错误 -如果命令行能运行---》删除重建 -修改django配置--》setting中 # 2 数据库配置 -新建一个数据库用户--》以后咱们项目,就用这个用户操作,而不是root用户 -创建用户并授权命令:5.7 grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?'; grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?'; -项目配置文件:修改数据库链接--->mac 上配置环境变量 -user = os.environ.get('MS_USER', 'luffy') -pwd = os.environ.get('MS_PWD', 'Luffy123?') # 3 扩写auth的usr表 -继承AbstractUser -配置文件配置 -pillow模块 -sql审核 # 4 封装logger -1 配置文件中配置 -2 写一个common_logger import logging logger = logging.getLogger('django') -3 以后导入使用即可 -4 以后不要用print输出---》使用logger.info 输出 # 5 封装全局异常 -配置文件中注册 -全局异常处理好了--——》后端接口--》无论如何访问,都会返回固定格式 {code:数字,msg:xx}--->前端:axios拦截器
今日内容
二次封装Response模块
# 响应---》drf提供的Response---》前端想接收到的格式 {code:xx,msg:xx} # 写一个响应类,实现 后端返回 前端收到 APIResponse(tokne='asdfa.asdfas.asdf')---->{code:100,msg:成功,token:asdfa.asdfas.asdf} APIResponse(code=101,msg='用户不存在') ---->{code:101,msg:用户不存在} APIResponse(results=[{},{}])---->{code:100,msg:成功,results:[{},{}]} APIResponse(msg='创建成功')---->{code:100,msg:创建成功} APIResponse(token=sd.11.22,icon='用户头像')---->{code:100,msg:创建成功,token:sd.11.22,icon:'用户头像'} APIResponse(msg='创建成功',headers={'xx':xxx})---->{code:100,msg:创建成功} # 开始封装
封装
from rest_framework.response import Response class APIResponse(Response): def __init__(self, code=100, msg='成功', status=None, headers=None, **kwargs): data = {'code': code, 'msg': msg} if kwargs: # kwargs={token:xx,icon:zz} data.update(kwargs) # super().__init__()---等同于 -->Response(data=data) super().__init__(data=data, headers=headers, status=status)
使用
from utils.common_response import APIResponse class TestResponseView(APIView): def get(self, request): # return APIResponse(token='ss.ee.ss',icon='/media/icon/default.png') # return APIResponse(msg='创建成功') # return APIResponse(msg='用户不存在',code=101) # return APIResponse(results=[{},{}]) return APIResponse(headers={'xx': 'yy'}) # raise APIException(detail='用户名或密码错误') # {code:999,msg:用户名或密码错误}
————————————
# admin 注释掉,重新启动
-url中
-注册app
-迁移
# 国际化
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
————————
drf--5个视图扩展类--封装
from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin, UpdateModelMixin, \ RetrieveModelMixin from .common_response import APIResponse class CommonListModelMixin(ListModelMixin): def list(self, request, *args, **kwargs): # Response 的对象---》res.data res = super().list(request, *args, **kwargs) return APIResponse(results=res.data) # {code:100,msg:成功,results:[{},{},{}]} class CommonCreateModelMixin(CreateModelMixin): def create(self, request, *args, **kwargs): res = super().create(request, *args, **kwargs) return APIResponse(msg='新增成功', result=res.data) # {code:100,msg:新增成功,result:{}} class CommonDestroyModelMixin(DestroyModelMixin): def destroy(self, request, *args, **kwargs): super().destroy(request, *args, **kwargs) return APIResponse(msg='删除成功') # {code:100,msg:删除成功} class CommonUpdateModelMixin(UpdateModelMixin): def update(self, request, *args, **kwargs): super().update(request, *args, **kwargs) return APIResponse(msg='修改成功') # {code:100,msg:修改成功} class CommonRetrieveModelMixin(RetrieveModelMixin): def retrieve(self, request, *args, **kwargs): res = super().retrieve(request, *args, **kwargs) return APIResponse(result=res.data) # {code:100,msg:成功,result:{}}
开启media访问
# 1 新增用户---》先配置media--》以后头像都传到 meida/icon 内 createsuperuser 创建用户 # 2 步骤 1 配置文件 # media配置 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = 'media/' 2 路由: from django.views.static import serve from django.conf import settings path('media/<path:path>', serve, kwargs={'document_root': settings.MEDIA_ROOT}),
前端项目创建
# vue2 写
# vue create luffy_city
# 使用pycharm打开--》运行
# 删除东西
前端配置
全局样式
# 默认标签,都有自己的样式---》实际前端开发,都不用他们这些样式
# 所有前端在写项目开始,都会把默认样式去除掉
global.css
/* 声明全局样式和项目的初始化样式 */ body, h1, h2, h3, h4, h5, h6, p, table, tr, td, ul, li, a, form, input, select, option, textarea { margin: 0; padding: 0; font-size: 15px; } a { text-decoration: none; color: #333; } ul { list-style: none; } table { border-collapse: collapse; /* 合并边框 */ }
main.js引入
// 在这导入即可--》全局样式生效
import '@/assets/css/global.css'
前端项目配置文件
# asesst文件夹下新建js文件夹,settings.js export default { BASE_URL: 'http://127.0.0.1:8000/api/v1/' } # 在main.js中注册 import settings from "@/assets/js/settings"; Vue.prototype.$settings = settings # 以后再任意组件中 this.$settings.BASE_URL # 拿到基地址
axios
# 1 安装 cnpm install -S axios # 2 在main.js中注册 import axios from "axios"; Vue.prototype.$axios = axios # 3 以后再任意组件中直接使用 this.$axios.get(this.$settings.BASE_URL+'user/user/loign/')
使用elementui
# 1 安装 cnpm install element-ui -S # 2 main.js 配置 import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
操作cookie
# 安装:前端项目目录下的终端 cnpm install vue-cookies # 配置:main.js import cookies from 'vue-cookies' Vue.prototype.$cookies = cookies; # 以后任意组件直接使用 this.$cookies操作即可
使用bootstrap
# cnpm remove bootstrap@4 # 卸载 #1 安装 cnpm install bootstrap@5 #2 配置 main.js import 'bootstrap/dist/css/bootstrap.min.css' #3 在组件中使用 <button class="btn btn-danger">点我看---</button>
后台主页功能
轮播图表
# 首页 -轮播图接口 -推荐课程接口---》写到课程才能写 # 创建app,创建轮播图表 ####utils/common_model.py from django.db import models class BaseModel(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 # 这样写了,这张表是个虚拟的,不会在数据库创建,只用来继承 #####home/models.py from utils.common_model import BaseModel # 通过写一个BaseModel 实现,以后如果其他表中有对应字段,直接继承即可 class Banner(BaseModel): # 排序,上传时间,是否显示,是否删除(软删除--》通过字段控制--》数据还在) # 轮播图名字, 路径,点击跳转的页面,轮播图描述 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='详情')
轮播图接口
###### 序列化类##### from rest_framework import serializers from .models import Banner class BannerSerializer(serializers.ModelSerializer): class Meta: model = Banner fields = ['id', 'title', 'image', 'link'] #### 视图类########## from .models import Banner from rest_framework.viewsets import GenericViewSet from utils.mixins import CommonListModelMixin from .serializer import BannerSerializer from django.conf import settings # 查询所有轮播图 class BannerView(GenericViewSet, CommonListModelMixin): # qs对象可以切片----》 limit 2 queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT] serializer_class = BannerSerializer #### 路由########## from rest_framework.routers import SimpleRouter from .views import BannerView router = SimpleRouter() router.register('banner', BannerView, 'banner') urlpatterns = [ ] urlpatterns += router.urls
自定义配置
#1 以后咱们会有自定义的配置,统一都写在 #common_settings.py中 BANNER_COUNT=3 #2 只需要在配置文件中导入 # 导入自定义配置---》统一都会给 django的的settings from .common_settings import * #3 以后用, from django.conf import settings queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
前端主页功能
# 头部组件Header
# 尾部组件:Footer
# 轮播图组件:Banner
# 首页:HomeView
HomeView.vue
<template> <div class="home"> <Header></Header> <Banner></Banner> <div class="course"> <el-row> <el-col :span="6" v-for="(o, index) in 8" :key="o" class="course_detail"> <el-card :body-style="{ padding: '0px' }"> <img src="http://photo.liuqingzheng.top/2023%2002%2022%2021%2057%2011%20/image-20230222215707795.png" class="image"> <div style="padding: 14px;"> <span>推荐课程</span> <div class="bottom clearfix"> <time class="time">价格:999</time> <el-button type="text" class="button">查看详情</el-button> </div> </div> </el-card> </el-col> </el-row> </div> <img src="http://photo.liuqingzheng.top/2023%2003%2001%2016%2010%2034%20/1.png" alt="" width="100%" height="500px"> <Footer></Footer> </div> </template> <script> import Footer from '@/components/Footer' import Header from '@/components/Header' import Banner from "@/components/Banner"; export default { name: 'HomeView', components: { Footer, Header, Banner } } </script> <style scoped> .time { font-size: 13px; color: #999; } .bottom { margin-top: 13px; line-height: 12px; } .button { padding: 0; float: right; } .image { width: 100%; display: block; } .clearfix:before, .clearfix:after { display: table; content: ""; } .clearfix:after { clear: both } .course_detail { padding: 50px; } </style>
Header.vue
<template> <div class="header"> <div class="slogan"> <p>老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</p> </div> <div class="nav"> <ul class="left-part"> <li class="logo"> <router-link to="/"> <img src="../assets/img/head-logo.svg" alt=""> </router-link> </li> <li class="ele"> <span @click="goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课</span> </li> <li class="ele"> <span @click="goPage('/actual-course')" :class="{active: url_path === '/actual-course'}">实战课</span> </li> <li class="ele"> <span @click="goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课</span> </li> </ul> <div class="right-part"> <span>登录</span> <span class="line">|</span> <span>注册</span> </div> </div> </div> </template> <script> export default { name: "Header", data() { return { url_path: sessionStorage.url_path || '/', } }, methods: { goPage(url_path) { if (this.url_path !== url_path) { this.$router.push(url_path); } sessionStorage.url_path = url_path; }, }, created() { sessionStorage.url_path = this.$route.path; this.url_path = this.$route.path; }, } </script> <style scoped> .header { background-color: white; box-shadow: 0 0 5px 0 #aaa; } .header:after { content: ""; display: block; clear: both; } .slogan { background-color: #eee; height: 40px; } .slogan p { width: 1200px; margin: 0 auto; color: #aaa; font-size: 13px; line-height: 40px; } .nav { background-color: white; user-select: none; width: 1200px; margin: 0 auto; } .nav ul { padding: 15px 0; float: left; } .nav ul:after { clear: both; content: ''; display: block; } .nav ul li { float: left; } .logo { margin-right: 20px; } .ele { margin: 0 20px; } .ele span { display: block; font: 15px/36px '微软雅黑'; border-bottom: 2px solid transparent; cursor: pointer; } .ele span:hover { border-bottom-color: orange; } .ele span.active { color: orange; border-bottom-color: orange; } .right-part { float: right; } .right-part .line { margin: 0 10px; } .right-part span { line-height: 68px; cursor: pointer; } .search { float: right; position: relative; margin-top: 22px; margin-right: 10px; } .search input, .search button { border: none; outline: none; background-color: white; } .search input { border-bottom: 1px solid #eeeeee; } .search input:focus { border-bottom-color: orange; } .search input:focus + button { color: orange; } .search .tips { position: absolute; bottom: 3px; left: 0; } .search .tips span { border-radius: 11px; background-color: #eee; line-height: 22px; display: inline-block; padding: 0 7px; margin-right: 3px; cursor: pointer; color: #aaa; font-size: 14px; } .search .tips span:hover { color: orange; } </style>
Footer.vue
<template> <div class="footer"> <ul> <li>关于我们</li> <li>联系我们</li> <li>商务合作</li> <li>帮助中心</li> <li>意见反馈</li> <li>新手指南</li> </ul> <p>Copyright © luffycity.com版权所有 | 京ICP备17072161号-1</p> </div> </template> <script> export default { name: "Footer" } </script> <style scoped> .footer { width: 100%; height: 128px; background: #25292e; color: #fff; } .footer ul { margin: 0 auto 16px; padding-top: 38px; width: 810px; } .footer ul li { float: left; width: 112px; margin: 0 10px; text-align: center; font-size: 14px; } .footer ul::after { content: ""; display: block; clear: both; } .footer p { text-align: center; font-size: 12px; } </style>
Banner.vue
<template> <div class="banner"> <el-carousel height="400px"> <el-carousel-item v-for="item in 4" :key="item"> <img src="../assets/img/banner1.png" alt=""> </el-carousel-item> </el-carousel> </div> </template> <script> export default { name: "Banner" } </script> <style scoped> .el-carousel__item { height: 400px; min-width: 1200px; } .el-carousel__item img { height: 400px; margin-left: calc(50% - 1920px / 2); } </style>
前后端打通
<template> <div class="banner"> <el-carousel height="400px"> <el-carousel-item v-for="banner in bannerList" :key="banner.id"> <div v-if="banner.link.startsWith('http:')"> <a :href="banner.link"><img :src="banner.image" alt=""></a> </div> <div v-else> <router-link :to="banner.link"><img :src="banner.image" alt=""></router-link> </div> </el-carousel-item> </el-carousel> </div> </template> <script> export default { name: "Banner", data() { return { bannerList: [] } }, created() { this.$axios.get(this.$settings.BASE_URL + 'home/banner/').then(res => { if (res.data.code == 100) { this.bannerList = res.data.results } else { this.$message.error(res.data.msg); } }).catch(res => { this.$message.error('系统异常,请稍后再试'); }) } } </script> <style scoped> .el-carousel__item { height: 400px; min-width: 1200px; } .el-carousel__item img { height: 400px; margin-left: calc(50% - 1920px / 2); } </style>
跨域问题详解
# 同源策略(Same origin policy)是一种约定,它规定了 请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同,如果不一致,请求会发送成功,后端会正常响应,但是浏览器会拦截 # 浏览器对非同源请求返回的结果做了拦截 # 只要做前后端分离,就会出跨域 # 解决跨域问题 -CORS :跨域资源共享 咱们讲的方式 向响应头中加数据,允许跨域 - 后端代码处理 - nginx代理 -JSONP :利用有的标签没有跨域问题 script img -websocket:长链接,不存在跨域 -前端代理:开发阶段用,上线不用 # cors如何解决跨域 -需要服务端支持---》就是服务端再响应头中加数据即可 # CORS基本流程 CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request) 简单请求,只发送一次: 非简单请求发送两次,第一次是OPTIONS预检请求,第二次是真正的请求 # 什么是简单,什么是非简单 #请求方法是以下三种方法之一: HEAD GET POST # HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain # 请求头中带了 token ,所有请求都是非简单 ### 解决跨域---统一写个中间件--》处理所有跨域 from django.utils.deprecation import MiddlewareMixin class CorsMiddleWare(MiddlewareMixin): def process_response(self,request,response): if request.method=="OPTIONS": #可以加* response["Access-Control-Allow-Headers"]="*" res['Access-Control-Allow-Methods'] = '*' response["Access-Control-Allow-Origin"] = "*" return response # 第三方解决方案 # 1、使用pip安装 pip install django-cors-headers #2、添加到setting的app中 INSTALLED_APPS = ( ... 'corsheaders', ... ) #3、添加中间件 MIDDLEWARE = [ ... 'corsheaders.middleware.CorsMiddleware', ... ] 4、setting下面添加下面的配置 CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', ) CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', 'Pragma', 'token' )
标签:__,code,settings,APIResponse,---,03days,luffy,msg,import From: https://www.cnblogs.com/wzh366/p/17980092