「路飞项目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

posted @ 2023-06-19 20:11  雀雀飞了  阅读(10)  评论(0编辑  收藏  举报