项目搭建
后台项目创建和目录调整
# 第三步,调整目录 #### 调整配置文件 -把配置文件移动到setting文件夹下了,改名为dev.py,又新建了一个pro.py -dev.py:开发阶段用的配置 -pro.py:上线阶段用的配置 -manage.py 中指向的配置文件,改成咱么修改后路径 -控制台:python manage.py runserver 或者点绿色箭头就可以启动项目了 ### 调整app的路径,以后把所有app都放到luffy_api下的apps文件夹---》整洁 -切换到apps路径下,执行创建app的命令 -python ../../manage.py startapp user -在dev.py中注册app,运行报错,报错原因是找不到user这么模块 -原来直接写app名字不报错--》原因是app就在项目根路径下(模块的查找)--》由于项目的根路径在环境变量中,app就在根路径下,它能直接找到 -现在的问题是apps路径不在环境变量中,它就找不到 -把apps的路径加入到环境变量中--》要在项目的启动时加--》启动入口是配置文件 -到配置文件中:写入 sys.path.append(os.path.join(BASE_DIR,'apps')) -以后再INSTALLED_APPS中只需要写app的名字即可 #### 新建 logs文件夹,luffy_api/lib文件夹,luffy_api/utils文件夹,script文件夹 ### 测试阶段运行没问题,项目上线,使用uwsgi上线---》运行wsgi.py-->修改如下 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.setting.pro') ### 国际化 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False ## 把 小luffy_api也就是BASE_DIR 也加入到环境变量 sys.path.append(BASE_DIR) 导入模块的时候,只要从环境变量的路径开始导就可以了,从小luffy_api路径开始导入即可 但是pycharm爆红,但是没有错,点右键,把该路径(在环境变量中的),做成source root即可 以后再从这个路径下导包,不会报错了 ### 注意:以后导入包 -尽量用最短路径导入,如果从长路径导入--》路径经过的py文件都会去执行--》可能会导致循环导入的问题 -我个人推荐用相对导入 # from apps.user import models from . import models 推荐用这个 -py文件中有相对导入,这个py文件不能作为脚本运行 -django项目中,由于没有右键运行的脚本,所以都可以用相对导入 ### 项目目录结构 """ ├── luffy_api ├── logs/ # 项目运行时/开发时日志目录 - 包 ├── luffy_api/ # 项目主应用,开发时的代码保存 - 包 ├── apps/ # 开发者的代码保存目录,以模块[子应用]为目录保存 - 包 ├── libs/ # 第三方类库的保存目录[第三方组件、模块] - 包 ├── setting/ # 配置目录 - 包 ├── dev.py # 项目开发时的本地配置 └── prod.py # 项目上线时的运行配置 ├── urls.py # 总路由 └── utils/ # 多个模块[子应用]的公共函数类库[自己开发的组件] ├── manage.py # 脚本文件 └── scripts/ # 保存项目运营时的脚本文件 - 文件夹 """
后台配置
配置日志
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(module)s %(lineno)d %(message)s' }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { # 实际开发建议使用WARNING 'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { # 实际开发建议使用ERROR 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', # 日志位置,日志文件名,日志保存目录必须手动创建,注:这里的文件路径要注意BASE_DIR代表的是小luffyapi 'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"), # 日志文件的最大值,这里我们设置300M 'maxBytes': 300 * 1024 * 1024, # 日志文件的数量,设置最大日志数量为10 'backupCount': 10, # 日志格式:详细格式 'formatter': 'verbose', # 文件内容编码 'encoding': 'utf-8' }, }, # 日志对象 'loggers': { 'django': { 'handlers': ['console', 'file'], 'propagate': True, # 是否让日志信息继续冒泡给其他的日志处理系统 }, } } ### 在utils下简历logging.py import logging # 创造一个logger对象,使用的是配置文件中的django这个 logger = logging.getLogger('django') ## 以后使用,导入直接用--》打印到控制台和记录到文件中 from utils.logging import logger logger.info("我执行了一下")
处理全局异常
## 全局异常捕获 from rest_framework.views import exception_handler # 默认没有配置,出了异常会走它 from rest_framework.response import Response from utils.logging import logger def common_exception_handler(exc, context): res = exception_handler(exc, context) if res: res = Response(data={'code': 998, 'msg': res.data.get('detail', '服务器异常,请联系系统管理员')}) else: res = Response(data={'code': 999, 'msg': str(exc)}) request = context.get('request') view = context.get('view') logger.error('错误原因:%s,错误视图类:%s,请求地址:%s,请求方式:%s' % (str(exc), str(view), request.path, request.method)) return res
配置文件中配置
REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'utils.exception.common_exception_handler' # 再出异常,会执行这个函数 }
二次封装Response
创建一个py文件,继承Response
from rest_framework.response import Response class ApiResponse(Response): def __init__(self, status=100, msg='成功', http_status=None, template_name=None, headers=None, exception=False, content_type=None, **kwargs): data = { 'status': status, 'msg': msg } if kwargs: data.update(kwargs) super().__init__(data=data, status=http_status, template_name=template_name, headers=headers, exception=exception, content_type=content_type)
后台数据库配置
# 使用mysql---》创建一个库(手动)--》库名:luffy # 项目配置文件中,连接这个数据库 ### 创建数据库,并配置 -create database luffy default charset=utf8; ### 给数据库创建一个lqz用户,它只能操作luffy库---》万一你的lqz用户密码泄露了--》 # 查看用户 # 5.7之前版本 select user,host,password from mysql.user; # 5.7往后的版本 select user,host,authentication_string from mysql.user; ## 创建用户 # 授权账号命令:grant 权限(create, update) on 库.表 to '账号'@'host' identified by '密码' grant all privileges on luffy.* to 'lqz'@'%' identified by 'Luffy123?'; grant all privileges on luffy.* to 'lqz'@'localhost' identified by 'Luffy123?'; grant all privileges on luffy.* to 'xk'@'%' identified by 'Luffy123?'; grant all privileges on luffy.* to 'xk'@'localhost' identified by 'Luffy123?'; # mysql -h 127.0.0.1 -P 3306 -uroot -p # mysql -uroot -p 有区别,如果在本地连接,使用这个,速度会快 # 刷新权限 flush privileges; ## 项目配置文件加入 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'luffy', # 数据库名字 'USER': 'lqz', # 用户名 'PASSWORD': 'Luffy123?', 'HOST': 'localhost', 'PORT': 3306 } ### django操作mysql -模式使用MysqlDB来操作---》MysqlDB在python3.x以后不存在了 -使用pymysql替换---》django2.0.7版本及以上,如果使用pymysql替换,需要改django源码 ### 关于pymysql和mysqlclient的选择 # 这两句话,只要执行即可,放在那里都行---》只要django执行,所有py文件中顶格写的代码都会执行 # 作用是?猴子补丁,动态替换 --->python一切皆对象,可以动态替换对象 # 如果该源码,后期只要使用django,都要改它的源码 # 所以咱们换另一个操作mysql的模块,mysqlclient---》MysqlDB的3版本--》有可能装不上--》win上看人品,实在装不上用whl文件装 linux上有不同解决方案 # http://www.liuqingzheng.top/python/%E5%85%B6%E4%BB%96/01-%E5%90%84%E4%B8%BB%E6%B5%81Linux%E7%B3%BB%E7%BB%9F%E8%A7%A3%E5%86%B3pip%E5%AE%89%E8%A3%85mysqlclient%E6%8A%A5%E9%94%99/ # import pymysql # pymysql.install_as_MySQLdb() #### 使用mysqlclient不需要写两句话,不用改源码
user模块user表设计
# 用户板块---》做成app python ../../manage.py startapp user # 创建用户表,基于auth的user表扩写 ### 注意:在写好这个之前,不要先迁移数据,如果迁移了数据库,这个就不行了 ### 如果你已经迁移了,删除数据库,删除所有的migrations文件,包含你自己的app,和auth和admin这两个app ## models.py from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): mobile = models.CharField(max_length=11, unique=True) # 唯一,长度11 # 需要pillow包的支持 ImageField继承自FileField icon = models.ImageField(upload_to='icon', default='icon/default.png') class Meta: db_table = 'luffy_user' verbose_name = '用户表' verbose_name_plural = verbose_name def __str__(self): return self.username # 配置文件---》注册表 INSTALLED_APPS = [ # ... 'user', ] # 自定义User表 AUTH_USER_MODEL = 'user.User' ## 配置media MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 安装pillow ,迁移 pip install pillow python manage.py makemigrations python manage.py migrate
前台创建及配置
# 创建项目 vue create luffycity # 使用pycharm打开 # 删除一些东西 ### APP.vue"app">## HomeView.vue class="home">## router下的index.js const routes = [ { path: '/', name: 'home', component: HomeView }, ] ### elementui ,bootstrap,jquery,axios配置 # axios cnpm install axios -S ## main.js import axios from 'axios' Vue.prototype.$axios = axios; #elementui cnpm install element-ui -S ## main.js import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); # bootstrap和jq cnpm install jquery -S cnpm install bootstrap@3 -S ## vue.config.js module.exports = { configureWebpack: { plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "window.jQuery": "jquery", "window.$": "jquery", Popper: ["popper.js", "default"] }) ] } }; ## main.js import 'bootstrap' import 'bootstrap/dist/css/bootstrap.min.css' ### 全局css样式配置 ##assets/css/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 // 把自己定义的global.css 引入 import './assets/css/global.css' ## 配置文件配置 # assets/js/settings.js export default { base_url: "http://127.0.0.1:8000" } # main.js // 把自己定义的global.css 引入 import './assets/css/global.css' // 导入自定义配置 import settings from './assets/js/settings' Vue.prototype.$settings = settings;首页
前台主页
<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"> <el-card :body-style="{ padding: '0px' }" class="course_card"> <img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h1g0zd133mj20l20a875i.jpg" class="image"> <div style="padding: 14px;"> <span>推荐的课程span> <div class="bottom clearfix"> <time class="time">价格:100元time> <el-button type="text" class="button">查看详情el-button> div> div> el-card> el-col> el-row> div> <img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h1g112oiclj224l0u0jxl.jpg" alt="" height="500px" width="100%"> <Footer>Footer> div> template> <script> import Footer from "@/components/Footer"; import Header from "@/components/Header"; import Banner from "@/components/Banner"; export default { name: 'HomeView', data() { return {} }, 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 { margin-left: 20px; margin-right: 20px; } .course_card { margin: 50px; } style>
Banner.vue
<template> <div class="banner"> <el-carousel :interval="5000" arrow="always" 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>
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"> <div> <span>登录span> <span class="line">|span> <span>注册span> div> 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; } 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号-1p> 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>
后台主页轮播图接口
基表设计
# utils/model.py/BaseModel
from django.db import models
# 5个公共字段
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 # 表示它是虚拟的,不在数据库中生成表,它只用来做继承
Banner表
from django.db import models
# Create your models here.
# 轮播图接口---》轮播图表
from utils.model import BaseModel
class Banner(BaseModel):
# 顺序,插入时间, 是否显示,是否删除。。。----》后期写课程的表也会用到这些字段--->仿AbstractUser,写一个基表,以后继承这个表
# 继承过来,只需要写自有字段即可:title,image,info,link
title = models.CharField(max_length=16, unique=True, verbose_name='名称')
image = models.ImageField(upload_to='banner', verbose_name='图片')
# 写接口---》app---》前端配合一个接口---》实现打开app,就有广告图片---》点击广告图片调整到app内部或者使用浏览器打开
# 一打开app,先打开的页面是什么,写app的人写的---》整一张大图充满全屏即可--》配合一个接口,返回一张大图
# app打开广告接口---》{code:100,msg:成功,img:{img:127.0.0.1/img/1.png,link:'www.baidu.com',type:2}}
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
迁移数据库,创建超级用户
# python manage.py makemigrations ---》如果没有变化,是app没注册
# python manage.py migrate
# python manage.py createsuperuser --->创建个用户
引入simpleui
# 下载 pip install django-simpleui # 注册app INSTALLED_APPS = [ 'simpleui', ... ] # 在admin中写 from django.contrib import admin from .models import Banner @admin.register(Banner) class BannerAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'link','is_show', 'is_delete') # 增加自定义按钮 actions = ['make_copy'] def make_copy(self, request, queryset): # 选中一些数据,点击 【自定义按钮】 触发方法执行,传入你选中 queryset # 保存,删除 print(queryset) make_copy.short_description = '自定义按钮'
home路由
from django.urls import path, include from rest_framework.routers import SimpleRouter from .views import BannerView router = SimpleRouter() router.register('banner', BannerView, 'banner') urlpatterns = [ path('', include(router.urls)), ]
总路由
urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/home/', include('home.urls')), # http://127.0.0.1:8000/api/v1/home/banner/ ]
视图类
from .models import Banner from .serializer import BannerSerializer from utils.response import APIResponse from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin class BannerView(GenericViewSet,ListModelMixin): # 获取所有接口-list,自动生成路由 queryset = Banner.objects.filter(is_delete=False,is_show=True).order_by('orders') serializer_class =BannerSerializer def list(self, request, *args, **kwargs): # 重写list res=super().list(request, *args, **kwargs) return APIResponse(result=res.data)
序列化类
from rest_framework import serializers from .models import Banner class BannerSerializer(serializers.ModelSerializer): class Meta: model = Banner fields = ['title', 'image', 'link', 'orders']
跨域问题解决
# 现在写好了后端接口,前端加载数据---》加载不过来,报错,--》报跨域的错误 # 同源策略 ---》浏览器的规定 -请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同,否则,加载回来的数据就会禁止 -前端:http://127.0.0.1:8080 -后端:http://127.0.0.1:8000 -这俩属于不同源,协议,地址一样,但是端口不一样,所以请求成功,但是到了浏览器被禁止掉了,因为浏览器的同源策略 -前后端分离,就会遇到这个问题,解决这个问题 # jsonp:出现了跨域问题---》有的东西不出跨域问题---》img,script,link--》回调 # https://www.zhihu.com/question/19966531 # 通过CORS(跨域资源共享) CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能 实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信 ----只需要在响应头中指定,允许跨域即可---- # cors有两类请求 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request) # 只要同时满足以下两大条件,就属于简单请求,否则就是非简单请求 1-请求方法是以下三种方法之一: HEAD GET POST 2-HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain 问:post,josn格式是什么请求? 非简单 # 简单请求和非简单请求的区别 简单请求:一次请求,直接发真正的请求,如果允许,数据拿回来,如果不允许,浏览器拦截 非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。非简单请求发两次,第一次是OPTIONS请求,如果允许跨域,再发真正的请求 # 解决跨域-->分成简单和非简单请求处理 -简单请求再响应头中加入:"Access-Control-Allow-Origin":"*" -非简单,咱们要加判断,如果是OPTIONS请求,在响应头中加入允许 # 自行解决跨域---》django中写个中间件,处理跨域--->配置到配置文件中 from django.utils.deprecation import MiddlewareMixin class CorsMiddleWare(MiddlewareMixin): def process_response(self,request,response): if request.method=="OPTIONS": #可以加* response["Access-Control-Allow-Headers"]="Content-Type" response["Access-Control-Allow-Origin"] = "*" return response # 经常遇到的东西,一定会有第三方解决方案---》我们使用第三方解决 -第一步:下载:pip install django-cors-headers -第二步:app中注册 INSTALLED_APPS = ( ... 'corsheaders', ... ) -第三步:中间件注册 MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10 ... 'corsheaders.middleware.CorsMiddleware', ... ] -第四步:配置文件配置
# 允许简单请求,所有地址 相当于CORS_ORIGIN_ALLOW_ALL="*"
CORS_ALLOW_ALL_ORIGINS = True
# 运行的请求
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'POST',
'PUT',
)
# 允许的请求头
CORS_ALLOW_HEADERS = (
'accept-encoding',
'authorization', # jwt
'content-type', # json
'origin',
'user-agent',
'Pragma',
)
前端后互通
Banner.vue
class="banner">"5000" arrow="always" height="400px"> for="item in banner_list"> "item.image" alt="">
后端自定义配置
# 在setting文件夹下新建 user_settings.py # 用户自己的配置,单独放到另一个py文件中 BANNER_COUNT=3 # 在dev.py中导入 # 导入用户自定义的配置 from .user_settings import *