路飞学城 之 基础配置

一、项目需求

1、五大需求如下

# 线上销售课程的
	-商城
    -知识付费类
    
# 需求
	-首页功能
    	-轮播图接口
    	-推荐课程接口(没写)
        
    -用户功能
    	-用户名密码登录
        -手机号验证码登录
        -发送手机验证码
        -验证手机号是否注册过
        -注册接口
        
   -课程列表功能
		-课程列表接口
    	-排序,过滤,分页

   -课程详情
		-课程详情接口
    	-视频播放功能
        -视频托管(第三方,自己平台)

   -下单功能
		-支付宝支付:生成支付链接,付款,回调修改订单状态
   		-购买成功功能

2、环境配置

项目创建

# 1 使用命令创建luffy项目
# 2 创建虚拟环境
	mkvirtualenv -p python38 luffy
# 3 安装django
	pip install django==3.1.12
# 3 命令创建项目
	django-admin startproject luffy_api
# 4 pycharm创建	
luffy后台项目创建,目录调整
├── luffy_api
	├── logs/				# 项目运行时/开发时日志目录 - 包
    ├── manage.py			# 脚本文件
    ├── luffy_api/      	# 项目主应用,开发时的代码保存 - 包
     	├── apps/      		# 开发者的代码保存目录,以模块[子应用]为目录保存 - 包
        ├── libs/      		# 第三方类库的保存目录[第三方组件、模块] - 包
    	├── settings/  		# 配置目录 - 包
			├── dev.py   	# 项目开发时的本地配置
			└── prod.py  	# 项目上线时的运行配置
		├── urls.py    		# 总路由
		└── utils/     		# 多个模块[子应用]的公共函数类库[自己开发的组件]
    └── scripts/       		# 保存项目运营时的脚本文件 - 文件夹

# 1 运行报错
-django项目运行,要先加载settings.py(dev.py)
-运行时,执行的是 python manage.py runserver
# 2 解决运行报错
- 修改manage.py 中 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
-命令行中运行,肯定不会报错

-pycharm运行,可能会报错
-删除之前的django-server,再创建一个,它会自动关联

# 3 创建app
python manage.py startapp home # 在哪执行,app就创建在哪里
切到apps目录下,创建app即可
python ../../manage.py startapp home

# 4 注册app
-在INSTALLED_APPS 直接写app的名字,会报错,报模块找不到的错误---》
# No module named 'home'
# 1 模块就是没有
# 2 不在环境变量中
# 3 自己写了一个,跟它同名
-只需要把apps路径加入到环境变量即可
sys.path.insert(0,str(BASE_DIR))
# 把apps文件夹加入环境变量,以后注册app,直接写名字即可
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))

# 5 wsgi.py,asgi.py 配置文件也要改---》后面上线才用到
# 6 验证现在配置文件用的是dev.py(在dev文件中测试)
# from django.conf import settings
# print(settings) # luffy_api.settings.dev

补充:

导出项目依赖 pip freeze

# pip freeze   > requirement.txt

# cat requirement.txt 
asgiref==3.7.2
cffi==1.16.0
cryptography==41.0.4
Django==3.2.12
django-cors-headers==4.2.0
django-simpleui==2023.8.28
djangorestframework==3.14.0
Pillow==10.0.1
pycparser==2.21
PyMySQL==1.1.0
pytz==2023.3.post1
sqlparse==0.4.4
typing_extensions==4.8.0

二、封装logger、封装全局异常、封装Response、后台数据库创建

1、封装logger

# django 默认使用 python原生的日志模块
    -以后不要再用print输出了,都用日志输出
    -print输出,上线也会有输出,如果用日志,日志有级别,上线后把级别调高,你开发阶段的输出就不再打印了

# 可以使用第三  logru  公司里可能会用
    
    
# django中集成日志
	-1 复制日志配置到dev.py中
    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',  # 控制台只显示DEBUG以上,就是Info开始
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            # 实际开发建议使用ERROR
            'level': 'INFO', #文件中只显示INFO以上,从WARNING
            '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, # 是否让日志信息继续冒泡给其他的日志处理系统
        },
    }
}
    -2 在utils下新建 common_logger.py  
        import logging
        logger = logging.getLogger('django')

    -3 在想使用日志的位置,导入直接使用即可,日志有级别,控制台和文件中打印的日志级别是不一样的
        from utils.common_logger import logger
        class LoggerView(APIView):
            def get(self, request):
                # 以后不要再用print输出了,都用日志输出
                logger.info('info级别')
                logger.warn('warn级别')
                logger.warning('warning级别')
                logger.error('error级别')
                logger.critical('critical级别')
                logger.debug('debug级别')
                return Response('看到我了')

2、封装全局异常

##### 1 写一个函数(只要走到这,程序出异常了--》记录日志--》越详细越好)
from rest_framework.views import exception_handler
from rest_framework.response import Response
from utils.common_logger import logger
def common_exception_handler(exc, context):
    res = exception_handler(exc, context)
    if res:  # 有值:drf的异常,处理了,格式不是咱们想要的
        err = res.data.get('detail') or res.data or '未知错误,请联系系统管理员'
        response = Response({'code': 888, 'msg': '请求异常-drf:%s' % err})
    else:  # 其他异常,没有处理,自己处理格式
        response = Response({'code': 999, 'msg': '请求异常-其他:%s' % str(exc)})
    # 记录日志,越详细越好,  请求错误:请求地址是:%s,请求方式是:%s,请求用户ip地址是:%s,错误是:%s,执行的视图函数是:%s
    request = context.get('request')
    path = request.get_full_path()
    method = request.method
    ip = request.META.get('REMOTE_ADDR')
    user_id = request.user.pk or '未登录用户'
    err = str(exc)
    view = str(context.get('view'))
    logger.error(
        '请求错误:请求地址是:%s,请求方式是:%s,请求用户ip地址是:%s,用户id是:%s,错误是:%s,执行的视图函数是:%s' % (
        path, method, ip, user_id, err, view))
    return response

##### 2 在配置文件中配置一下
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'utils.common_excepitons.common_exception_handler',
}

补充:查找drf的配置,照着写

 3、封装Response

# 之前使用drf的Response,我们需要自己构造返回字典
	return Response(data={code:100,msg:成功,result:[{},{}]})
	return Response(data={code:100,msg:成功,token:asdasd,username:lqz})
# 我们封装 APIResponse,以后使用,效果如下
	return APIResponse()---》{code:100,msg:成功}
	return APIResponse(result=[{},{}])
	return APIResponse(token=afasfd,username=lqz)

### 代码
from rest_framework.response import Response

# APIResponse()
# APIResponse(result=[{},{}])
# APIResponse(token=afasfd,username=lqz)
# APIResponse(token=afasfd,username=lqz,status=201,headers={xx:'xx'})
class APIResponse(Response):
    def __init__(self, code=100, msg='成功', status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None, **kwargs):
        data = {'code': code, 'msg': msg}
        if kwargs:
            data.update(kwargs)
        super().__init__(data=data, status=status, headers=headers, template_name=template_name, exception=exception,
                         content_type=content_type)
        # Response(data=data, status=status, headers=headers)

4、后台数据库创建

# 使用mysql作为数据库
	-mysql在win上安装步骤:https://zhuanlan.zhihu.com/p/571585588

# 1 创建数据库--root用户创建---》后续使用代码操作数据库,不使用root用户,新建一个用户 luffy
	-如果使用root用户,一旦密码泄露,所有库都不安全了
    -如果新建一个luffy用户,只授予luffy库的权限,即便泄露了密码,只是这个库不安全了
    
# 2 创建路飞用户,授予了luffy库所有表的所有权限
	-查看用户:select user,host,authentication_string from mysql.user;
    -创建用户:
    grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?';
	grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?';
    flush privileges;
  
mysql8:
  CREATE USER 'luffy'@'%' IDENTIFIED BY 'Luffy123?';
	GRANT ALL PRIVILEGES ON luffy.* TO 'luffy'@'%';

  
# 3 以后,操作luffy库,都用luffy用户登录,就不用root用户了

# 4 项目中配置使用mysql数据库,使用luffy用户
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'luffy',
            'HOST': '127.0.0.1',
            'PORT': 3306,
            'USER': 'luffy',
            'PASSWORD': 'Luffy123?'
        }
    }
    
    
# 5 运行会报错,因为没有装myslqclient
	-解决方式一:直接安装myslqclient  ---》win平台看人品,mac基本装不上,linux需要单独处理
    -解决方式二:使用pymysql
    	-安装,在配置文件中加入:
        import pymysql
		pymysql.install_as_MySQLdb()
              
        
# 6 数据库的用户名,密码,都是直接写死在代码中的,如果咱们代码被泄露了,数据库也就被人看到了
user=os.environ.get('LUFFY_USER','luffy')
password=os.environ.get('LUFFY_PWD','Luffy123?')
print(user)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'luffy',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': user,
        'PASSWORD': password
    }
}

三、luffy前端配置

1、安装axios、cookie、elementui,全局配置使用

# 1 跟后端交互:axios
	cnpm install -S axios
	以后想发送ajax请求,必须导入,使用
    可以把axios放到vue实例中,以后任意组件中  this.$axios.get()
	main.js中加入
    import axios from 'axios'
	Vue.prototype.$axios = axios;

# 2 操作cookie: vue-cookies
	cnpm install -S vue-cookies
    import cookies from 'vue-cookies'
    Vue.prototype.$cookies = cookies;
# 3 ui库:elementui
    cnpm install -S element-ui
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';  // 全局都会有这个css的样式
    Vue.use(ElementUI);

  
# 4 去掉 标签的默认样式
    1 编写全局css
        assets/css/global.css
    2 main.js中导入
    import '@/assets/css/global.css'

# 5 全局配置文件
	# 写一个settings.js
    export  default {
        BASE_URL:'http://127.0.0.1:8000/'
    }
   	# main.js中
    import settings from "@/assets/js/settings";
	Vue.prototype.$setting = settings

去除标签的默认样式

CSS Tools: Reset CSS (meyerweb.com)

2、三个组件

Banner.vue 轮播图组件 

<template>
  <div class="banner">
    <el-carousel height="400px">
      <el-carousel-item v-for="item in banner_list" :key="item.id">
        <div v-if="item.link.indexOf('http')==0">
          <a :href="item.link"><img :src="item.image" alt=""></a>
        </div>
        <div v-else>
          <router-link :to="item.link"><img :src="item.image" alt=""></router-link>
        </div>


      </el-carousel-item>
    </el-carousel>
  </div>
</template>

<script>
export default {
  name: "Banner",
  data() {
    return {
      banner_list: []
    }
  },
  created() {
    this.$axios.get(this.$settings.BASE_URL + 'home/banner/').then(res => {
      console.log(res.data)
      if (res.data.code == 100) {
        this.banner_list = res.data.result
      } else {
        this.$message({
          showClose: true,
          message: res.data.msg,
          type: 'error'
        });
      }
    })
  }
}

var a = 'ss'
var res = a.indexOf('ss')
console.log(res)
</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号-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>

3、前端首页

轮播图,ElmentUI 叫走马灯 组件 | Element

推荐课程 :卡片 组件 | Element

<template>
  <div class="home">
    <Header></Header>
    <Banner></Banner>
    <div>
      <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">价格:199</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 Header from "@/components/Header.vue";
import Footer from "@/components/Footer.vue";
import Banner from "@/components/Banner.vue";

export default {
  name: 'HomeView',
  components: {
    Header, Footer, 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>

四、跨域问题

1、前端请求的url地址必须与浏览器上的url地址相同(包括端口、域名、协议等),否则会发生跨域问题

#1 前后端打通---出现CORS policy 错误

#2 同源策略
	同源策略(Same origin policy)是一种约定,约定了请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同,如果违背了这个约定,浏览器就会报错
    请求正常发送成功,服务端也响应了,但是回来到浏览器的时候,报错,被浏览器的同源策略拦截了
    
    
#3 跨域问题出现的原因,源自于浏览器的同源策略

#4 只有前后端分离的web项目,才会出,才需要解决跨域问题


#5 CORS(跨域资源共享)---》是一个后端技术--》后端只需要在响应头中加入固定的响应头,前端就不禁止了

# 6 CORS请求分成两类(浏览器发送请求之前判断)
	-简单请求:
    	1 只发送一次请求,就是真正的请求
    -非简单请求
    	1 先发送一个options 预检请求,服务端如果写了cors,再发送真正的请求,如果没有写cors,浏览就不再发送真正的请求了
        

# 7 只要同时满足以下两大条件,就属于简单请求。
    # 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

2、自己解决跨域

定义一个中间件 common_middleware

from django.utils.deprecation import MiddlewareMixin
class CORSMiddleWare(MiddlewareMixin):
    def process_response(self, request, response):
        # 简单请求
        response['Access-Control-Allow-Origin'] = '*'  # 允许所有客户端
        # 非简单请求
        if request.method == 'OPTIONS':
            # res['Access-Control-Allow-Methods'] = 'DELETE,PUT'
            response['Access-Control-Allow-Methods'] = '*'
            response['Access-Control-Allow-Headers'] = '*'
        return response

settings 引入

MIDDLEWARE = [
     'utils.common_middleware.CORSMiddleWare'
]

3、第三方解决跨域

# 1 下载
pip install django-cors-headers
# 2 注册app
INSTALLED_APPS = (
	...
	'corsheaders',
	...
)
# 3 加中间件
MIDDLEWARE = [  
	...
	'corsheaders.middleware.CorsMiddleware',
	...
]
# 4 settings配置文件
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'
)

五、 后端数据库迁移

1、用户板块,用户表迁移

# 用户表,使用auth的user表,自己写
	-如果用的是auth的user表,必须在迁移之前,就定好,扩写的要写完
    
# 咱们用户表,用的就是基于auth的user表,扩写,咱们先写用户表
	1 创建用户app
    python ../../manage.py startapp user
    2 创建用户表,models.py中
        from django.contrib.auth.models import AbstractUser
        class User(AbstractUser):
            # 扩写,加入手机号,加入头像
            mobile = models.CharField(max_length=11, unique=True)
            # 需要pillow包的支持  pip install pillow
            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
            
     3 dev.py中注册
    # 注册user app
    	INSTALLED_APPS = [
                'user',
            ]
        AUTH_USER_MODEL='user.User'  # 应用名.表名
	 4 迁移命令 两条

六、后台主页功能

1、软件开发模式

# 1 瀑布开发模式
	-架构,数据库设计---》分任务开发(周期很长,可能半年)---》测试---》上线
    
# 2 敏捷开发(devops)
	-架构设计--》很多板块
    -开发某个版块,再设计相关板块的数据库   # 一周左右
    -开发某个板块
    -测试测试该板块
    -运维上线该板块
    
    -开发另一个版本

2、轮播图表

常用的字段写成公用的,使用继承

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  # 这个表,只用来继承,不会再数据库生成表


from utils.common_model import 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='详情')  # 也可以用详情表,宽高出处

    class Meta:
        db_table = 'luffy_banner'
        verbose_name_plural = '轮播图表'

    def __str__(self):
        return self.title

补充:这里的is_delete字段,软删除

MySQL 数据库中,硬删除(也称为物理删除)指的是直接从数据库表中删除数据行,硬删除操作可能导致数据库索引失效

3、轮播图接口

视图类

轮播图接口除了返回序列化后的id、image、link,还要返回状态码、msg,继承APIResponse重写一个list

from rest_framework.mixins import ListModelMixin
from utils.common_response import APIResponse

class CommonListModelMixin(ListModelMixin):
    def list(self, request, *args, **kwargs):
        res = super().list(request, *args, **kwargs)
        return APIResponse(result=res.data)

轮播图接口

from rest_framework.viewsets import ViewSetMixin, GenericViewSet
from . import models
from .serializer import BannerSerializer
from django.conf import settings
from utils.common_mixin import CommonListModelMixin

class BannerView(GenericViewSet, CommonListModelMixin):  # 自动生成路由
    queryset = models.Banner.objects.filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer

4、序列化类

from rest_framework import serializers
from .models import Banner


class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = ['id', 'image', 'link']

5、子路由  

from django.contrib import admin
from django.urls import path
from home import views  # pycharm报错,但实际上不报错 ,只需要把加入到环境变量的路径都做成source root即可
from rest_framework.routers import SimpleRouter
from .views import BannerView

router = SimpleRouter()
# 127.0.0.1:8080/api/v1/home/banner/--- get
router.register('banner', BannerView, 'banner')

urlpatterns = [

]
urlpatterns += router.urls

6、配置文件

# dev配置 导入用户配置
from .user_settings import *


### user_settings.py
# 用户自己配置,单独放到一个py文件中
BANNER_COUNT=3

# 后续可能还用别的配置

7、补充

# app 后端---》app一打开---》广告页---》可以滑动---》点击广告页---》一种浏览器打开页面跳转,一种情况打开一个他们app内的页面

# 原理就是轮播图接口

七、后台管理

# 任何项目,都会有后台管理
# 使用 django+vue ---》写---》
# django的admin做后台管理---》simpleui美化


# 1 下载simpleui---》app中注册
# 2 创建一个root用户,后台登录
# 3 后台登录,录入数据:admin中注册这个表
# 4 默认情况文件传到根路径下
	需要配置:以后都会放到meida文件夹下 MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    
# 5 接口中不带meida 路径的: 配置文件中
	MEDIA_URL = 'media/'   # 取出的文件地址,拼接上media这个目录
# 6 前端能访问图片
	  path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),

八、短信验证登录功能

1、登录注册页面分析

# 用户板块需要写的接口
1 用户名密码登录(多方式登录)
2 获取手机验证码接口
3 手机号+验证码登录
4 注册接口
5 验证手机号是否存在接口

2、验证手机号是否存在接口

user.view

from django.shortcuts import render

# Create your views here.
from django.utils.datastructures import MultiValueDictKeyError

from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from .models import User
from utils.common_response import APIResponse


class MobileView(ViewSet):
    # @action(methods=['GET'], detail=False)
    # def check_mobile(self, request, *args, **kwargs):
    #     # 取出前端传入手机号
    #     mobile = request.query_params.get('mobile')
    #     # 去数据中查询,是否存在即可
    #     user = User.objects.filter(mobile=mobile).first()
    #     if user:
    #         return APIResponse(msg='手机号存在')
    #     else:
    #         return APIResponse(code=101, msg='手机号不存在')
    @action(methods=['GET'], detail=False)
    def check_mobile(self, request, *args, **kwargs):
        try:
            # 取出前端传入手机号
            mobile = request.query_params['mobile']
            # 去数据中查询,是否存在即可
            User.objects.get(mobile=mobile)  # 有且只有一个才不报错,否则报错
        except MultiValueDictKeyError as e:
            raise Exception('您没有携带手机号')
        # except  ObjectDoesNotExis as e:
        #         #     raise Exception('手机号不存')
        except Exception as e:
            raise Exception('未知错误,请联系系统管理员')
        return APIResponse(msg='手机号存在')

开启子路由

from rest_framework.routers import SimpleRouter
from .views import MobileView

router = SimpleRouter()
router.register('mobile', MobileView, 'mobile')

urlpatterns = [

]
urlpatterns += router.urls

验证

127.0.0.1:8000/api/v1/user/mobile/check_mobile/?mobile=‘手机号’

  

 

posted @ 2023-10-07 19:25  凡人半睁眼  阅读(113)  评论(0编辑  收藏  举报