「路飞项目03」
1 后台主页模块设计
# 1 创建后台主页模块(一个模块一个app)
小luffy路径 python ../../manage.py startapp home
# 2 在models中写轮播图表
-写一个基表BaseModel
-写轮播图表
# 3 迁移,记得注册app
-大luffy路径 python manage.py makemigrations/migrate
-makemigrations用于创建数据库模型的迁移文件,而migrate用于将迁移文件应用于数据库本身,使数据库与模型匹配。
-makemigrations后有了迁移记录,migrate后有migrations表,要么全删要么不删,不要只删一半,否则记录错乱
########## BaseModel写在utils里作为公共的模型表 ##########
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 # 这个表模型只用来继承,不用来在数据库中生成表
########## Banner ##########
from django.db import models
from utils.common_models import BaseModel
class Banner(BaseModel):
# id img图片地址 上传时间 最后更新时间 是否删除 是否显示 order
# 把公共字段抽取到某个基表中,以后要使用,直接继承基表,扩写自己的字段就可以了--->用过的AbstractUser就是这个原理
title = models.CharField(max_length=16, unique=True, verbose_name='名称')
image = models.ImageField(upload_to='banner', verbose_name='图片')
# 点击图片,跳转到的路径
# 前端跳转的地址: 1.前端路由 2.完整的http链接
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
# 补充:
1.某个app,使用另一个app的表做关联,该如何用?先导入,然后a=models.ForeinKey(to=另一个表名)#不需要加引号,因为导入的是一个对象。
2.公司几乎不建物理外键,都是逻辑外键,好处增删查改速度快,不需要检索依赖,坏处是有可能进脏数据,有程序层面控制。
3.表中有些字段,其它表也会有---》创建一个基表,以后其它表继承该表--->只用来做继承
-class Meta:
abstract = True
4.字段类:DateTimeField的属性:
-auto_now_add:这个字段新增的时候,可以不传,会以当前时间存入 # 这样写配置文件中:USE_TZ写成True和Fasle的区别是8个小时
-auto_now:更新这条记录,会把当前时间存入
-update更新 # 时间不变
-对象.属性=xx ---》对象.save() # 时间改变
-default=datetime.datetime.now 也会设置当前时间,不要加括号。如果加了括号,程序走到这里就会执行,default就是程序执行时间,写死了,相当于字符串。不加括号就不会写死,到这儿就会执行。由此可得,这里可以放一个函数内存地址,每增一条记录就会执行一次函数。
5.每个app,要有自己的url--->总路由做分发
2 simpleui后台管理
# 安装: pip install django-simpleui,在配置文件里注册,放在最上面,记得加逗号。
# 创建一个超级用户: python manage.py createsuperuser
# 在admin中注册表模型: admin.sites.register(Banner)
# 录入数据
-MEDIA_URL = 'api/v1/media/' 以后所有的在media文件夹里的资源自动拼接media/
# 正常在公司中,网站分主站和后台管理。后台管理前后端分离django-vue-admin这种框架
# 后台管理,主要是运营录入数据,使用simpleui
3 轮播图接口
3.1 封装自己的mixin,返回自己的格式
from rest_framework.generics import ListAPIView, CreateAPIView, RetrieveAPIView, DestroyAPIView, UpdateAPIView
from .common_response import ApiResponse
class CommonListAPIView(ListAPIView):
def list(self, request, *args, **kwargs):
res = super().list(self, request, *args, **kwargs)
return ApiResponse(data=res.data)
class CommonCreateAPIView(CreateAPIView):
def create(self, request, *args, **kwargs):
res = super().create(self, request, *args, **kwargs)
return ApiResponse(data=res.data)
class CommonRetrieveAPIView(RetrieveAPIView):
def retrieve(self, request, *args, **kwargs):
res = super().retrieve(self, request, *args, **kwargs)
return ApiResponse(data=res.data)
class CommonDestroyAPIView(DestroyAPIView):
def destroy(self, request, *args, **kwargs):
res = super().destroy(self, request, *args, **kwargs)
return ApiResponse(data=res.data)
class CommonUpdateAPIView(UpdateAPIView):
def update(self, request, *args, **kwargs):
res = super().update(self, request, *args, **kwargs)
return ApiResponse(msg='删除成功')
# 补充
-super(CommonUpdateAPIView, self).update(request, *args, **kwargs)和super().update(request, *args, **kwargs)有区别吗
-在Python 3中,两者是等价的,因为使用不带参数的super()来自动检测方法所在的类和当前调用的方法。而在Python 2中,必须要在括号内指定类名和self参数,否则会抛出TypeError异常。无论在哪个版本中,这两个表达式的作用都是调用父类相应的方法,并将请求和参数传递给它。
3.2 轮播图接口
视图
# 查询所有 轮播图接口
from rest_framework.viewsets import ViewSetMixin
from utils.common_APIView import CommonListAPIView
from .serializer import BannerSerializer
from .models import Banner
class BannerView(GenericViewSet, ListModelMixin):
queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')
serializer_class = BannerSerializer
路由
# 总路由
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/home/', include('home.urls')),
# static 默认开启的,后期咱们会开启media文件夹,除此之外的其它文件夹,尽量不要开放,让外部访问
path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
]
# 分路由
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
from .views import BannerView
router.register('banner', BannerView, 'banner')
urlpatterns = [
]
urlpatterns += router.urls
序列化类
from rest_framework import serializers
from .models import Banner
class BannerSerializer(serializers.ModelSerializer):
class Meta:
model = Banner
fields = ['id', 'image', 'title', 'link']
3.3 自定义配置文件
# 通过配置文件控制显示多少条轮播图
# 使用步骤
1 在settings下新建:common_settings.py----->BANNER_COUNT=3
2 在dev.py中加入
from .common_settings import *
3 在轮播图接口上
class BannerView(ViewSetMixin, CommonListAPIView):
queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT] # 切片
serializer_class = BannerSerializer
# 以后使用配置文件:from django.conf import settings---》内部如何实现的---》通过反射
-settings是个对象,有内置配置,有自己的配置
-django所有的配置文件,必须大写---》本意其实是常量---》如果写成小写---》settings这个对象中就没有这个配置
'''
类的dict属性,返回类内部所有属性和⽅法对应的字典。
类的实例对象dict属性,返回实例属性和值组成的字典。
当有继承关系时,父类的dict 并不会影响子类的dict
内置的数据类型没有dict属性。
'''
class Person():
sex = None
age = 1
def __init__(self,name):
self.sex = '男'
self.age = 30
self.name = name
def test(self):
print ('a normal func.')
class Man(Person):
sex = None
age = 1
def __init__(self,name):
self.sex = '女'
self.age = 40
self.name = name
def test_B(self):
print ('func named test_B')
person = Person('Tom')
man = Man('Jerry')
print (Person.__dict__)
print (person.__dict__)
print (person.__dict__['name'])
print (Man.__dict__)
print (man.__dict__)
{'__module__': '__main__', 'sex': None, 'age': 1, '__init__': <function Person.__init__ at 0x000001CD23CB80D0>, 'test': <function Person.test at 0x000001CD23CB8160>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
{'sex': '男', 'age': 30, 'name': 'Tom'}
Tom
{'__module__': '__main__', 'sex': None, 'age': 1, '__init__': <function Man.__init__ at 0x000001CD23CB81F0>, 'test_B': <function Man.test_B at 0x000001CD23CB8280>, '__doc__': None}
{'sex': '女', 'age': 40, 'name': 'Jerry'}
4 跨域问题详解
# 以后只要前后端分离项目,都会出现跨域问题,咱们要解决
# 同源策略
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现
请求的url地址,必须与浏览器上的url地址处于同域上:也就是[域名],[端口],[协议]相同.
http://127.0.0.1:8080
ftf://127.0.0.1:8080
比如:我在本地上的域名是127.0.0.1:8000,请求另外一个域名:127.0.0.1:8001一段数据
浏览器上就会报错,个就是同源策略的保护,如果浏览器对javascript没有同源策略的保护,那么一些重要的机密网站将会很危险
请求发送,服务的执行,数据也正常返回,只是被浏览器拦截了
# 正因为同源策略的存在,咱们写前后端分离的项目,无法正常获取到数据
# 解决跨域问题:
1 jsonp 跨域(不了解)
2 跨域资源共享(CORS) 后端技术
3 Nginx代理
# CORS:跨域资源共享
CORS需要浏览器和服务器同时支持,所有浏览器都支持该功能
只需要服务的处理即可:只需要在在响应头中加入固定的头就实现cors
比如在响应头中加入Access-Control-Allow-Origin='*'--->get请求就没有跨域了--->但是put请求还会有
# cors的请求分两种
-简单请求:浏览器直接发起
-非简单请求:浏览器先发送要给options预检请求,服务端允许,再发送真正的请求
# 什么是简单请求,什么是非简单请求
# 如果属于下面,就是简单请求
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
# 使用cors解决跨域,就是再响应头中加入固定的一些东西,专门写个中间件
res['Access-Control-Allow-Headers'] = 'token'
res['Access-Control-Allow-Methods'] = 'DELETE'
res['Access-Control-Allow-Origin'] = 'http://192.168.1.252:8080'
##### 补充:######
前端访问的后端地址,一定要准确
4.0 自定义中间件,解决跨域问题
##### common_mideleware.py
from django.utils.deprecation import MiddlewareMixin
### 自定义中间件解决跨域问题---》以后其它框架都是这个原理---》django上有人做了
class CorsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
if request.method == 'OPTIONS':
response['Access-Control-Allow-Headers'] = 'token'
response['Access-Control-Allow-Methods'] = 'DELETE'
response['Access-Control-Allow-Origin'] = '*'
return response
### 配置文件配置中间件
MIDDLEWARE = [
'utils.common_mideleware.CorsMiddleware'
]
4.1 django-cors-headers
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'
)
4.2 django-cors-headers源码
# 核心代码再中间件的---》process_response 3.0.14 版本
class CorsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
if (
not conf.CORS_ALLOW_ALL_ORIGINS
and not self.origin_found_in_white_lists(origin, url)
and not self.check_signal(request)
):
return response
if conf.CORS_ALLOW_ALL_ORIGINS and not conf.CORS_ALLOW_CREDENTIALS:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
else:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
if request.method == "OPTIONS":
response[ACCESS_CONTROL_ALLOW_HEADERS] = ", ".join(conf.CORS_ALLOW_HEADERS)
response[ACCESS_CONTROL_ALLOW_METHODS] = ", ".join(conf.CORS_ALLOW_METHODS)
return response
5 前台主页功能
5.1 Banner.vue
<template>
<div>
<el-carousel height="400px">
<el-carousel-item v-for="item in banner_list" :key="item">
<!-- router-link只能跳内部,不能跳外部-->
<router-link :to="item.link" v-if="item.link.indexOf('http:')<0">
<img :src="item.image" :alt="item.title">
</router-link>
<a :href="item.link" v-else>
<img :src="item.image" :alt="item.title">
</a>
</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 => {
// 要先判断,有可能崩掉
if (res.data.code==200){this.banner_list = res.data.data}else{
this.$message.error(res.data.msg)}
// 如果状态码不是200就会走catch,如果不写的话页面会崩掉
// catch 可以捕捉上层错误,但是对下层错误是捕捉不到的。
}).catch(res=>{this.$message.error('系统错误请联系管理员')})
}
}
</script>
<style scoped>
.el-carousel__item h3 {
color: #475669;
font-size: 18px;
opacity: 0.75;
line-height: 300px;
margin: 0;
}
.el-carousel__item {
height: 400px;
min-width: 1200px;
}
.el-carousel__item img {
height: 400px;
margin-left: calc(50% - 1920px / 2);
}
</style>
5.2 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>
5.3 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>
5.4 HomeView.vue
<template>
<div class="home">
<Header></Header>
<Banner></Banner>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<Footer></Footer>
</div>
</template>
<script>
import Banner from "@/components/Banner";
import Footer from '@/components/Footer'
import Header from "@/components/Header";
export default {
name: 'HomeView',
components: {
Header, Footer, Banner
}
}
</script>
补充
# 有的公司没有用drf也需要序列化,如何做?表模型继承 ModelMixin 所有表模型都有to_dict方法,序列化用的
# 以后拿到表模型对象banner 序列化后的数据=banner.to_dict(a=['id','name'])
# 参照开源项目:https://gitee.com/openspug/spug