82、后台主页模块设计、Simpleui后台管理、轮播图接口、跨域问题详解、前台主页功能

436 阅读6分钟

后台主页模块设计

步骤

1.创建后台主页模块(一个模块一个app)
	python ../../manage.py startapp home   ps:路径切换至app里面

2.在models中写轮播图表
	-写一个基表BaseModel
  	表中有些字段,其它表也会有--->创建一个基表,以后其它表继承该表--->只用来做继承
  -写轮播图表
  
3.迁移
补充:DateTimeField的属性
1.auto_now_add:这个字段新增的时候,可以不传,会以当前时间存入
	这样写,配置文件中:USE_TZ = False写成true,和fasle的区别
2.auto_now:更新这条记录,会把当前时间存入
	update更新
  	直接:表.objects.filter().update()
		对象.属性=xx  --->对象.save() 
		default=datetime.datetime.now  也会设置当前时间,不要加括号

Utils/commcon_models.py

from django.db import models


class BaseModel(models.Model):
    create_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 django.db import models

# Create your models here.

from utils.common_models import BaseModel

class  Banner(BaseModel):
    # 1.需要的字段:id  img图片地址  上传时间  是否删除 是否显示 order
    # 2.上传时间  是否删除 是否显示 order-->很多地方用得到-->把公共字段抽取到某个基表中,以后要使用,直接继承基表,扩写自己的字段就可以了--->用过的AbstractUser就是这个原理
    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

迁移

python manage.py makemigrations
python manage.py migrate

Simpleui后台管理

1.安装:pip install django-simpleui
2.在配置文件中注册:simpleui
   INSTALLED_APPS = [
        'simpleui',
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        ...
    ]
3.创建超级用户:python manage.py createsuperuser
4.在admin注册表:
  	from django.contrib import admin
    from .models import Banner
    admin.register(Banner)
    
5.改后台语言,中文、时间
  LANGUAGE_CODE = 'zh-hans'
  TIME_ZONE = 'Asia/shanghai'
  USE_I18N = True
  USE_L10N = True
  USE_TZ = False
  
6.登陆,录入数据

ps:补充:
    正常在公司中,网站分主站和后台管理
    后台管理,主要是运营录入数据,使用simpleui

轮播图接口

路由分发

# 总路由
from django.urls import path,include
urlpatterns = [
path('api/v1/home/',include('home.urls')),
]

编写视图函数、序列化类、自定义配置文件--初步

from rest_framework import serializers
from .models import Banner
class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = ['id','title','image','link']
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from .models import Banner
from django.conf import settings
from .serializer import BannerSerializer
class BannerView(GenericViewSet,ListModelMixin):
    queryset = Banner.objects.filter(is_delete=False,is_show=True)[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer
    
ps:想定义自己的格式,所以自己写一个mixin
1.在settings下新建,common_settings.py
	BANNER_COUNT = 4
2.在settings的dev.py中导入:from .common_settings import *

封装自己的mixin

from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin, DestroyModelMixin,UpdateModelMixin
from utils.common_response import APIResponse


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


class CommonRetrieveModelMixinn(RetrieveModelMixin):
    def retrieve(self, request, *args, **kwargs):
        res = super().retrieve(request, *args, **kwargs)
        return APIResponse(data=res.data)

class CommonCreateModelMixin(CreateModelMixin):
    def create(self, request, *args, **kwargs):
        res = super().create(request, *args, **kwargs)
        return APIResponse(data=res.data)

class CommonDestroyModelMixin(DestroyModelMixin):
    def destroy(self, request, *args, **kwargs):
        res = super().destroy(request, *args, **kwargs)
        return APIResponse(msg='删除成功')

class CommonUpdateModelMixin(UpdateModelMixin):
    def update(self, request, *args, **kwargs):
        res = super().update(request, *args, **kwargs)
        return APIResponse(data=res.data)

视图函数更新

from rest_framework.viewsets import GenericViewSet
from .models import Banner
from django.conf import settings
from .serializer import BannerSerializer
from utils.common_mixin import CommonListModelMixin as ListModelMixin
class BannerView(GenericViewSet,ListModelMixin):
    queryset = Banner.objects.filter(is_delete=False,is_show=True)[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer

跨域问题

同源策略

1.同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现

2.请求的url地址,必须与浏览器上的url地址处于同域上,也就是【域名】,【端口】,【协议】相同.
	eg:我在本地上的域名是127.0.0.1:8000,请求另外一个域名:127.0.0.1:8001一段数据
浏览器上就会报错,这就是同源策略的保护,如果浏览器对javascript没有同源策略的保护,那么一些重要的机密网站将会很危险

3.请求发送,服务的执行,数据也正常返回,只是被浏览器拦截了,正因为同源策略的存在,咱们写前后端分离的项目,无法正常获取到数据

ps:浏览器的同源策略:浏览器不允许向不同域发请求[地址,端口,协议],请求发送成功了,服务端也响应了,但是被浏览器拦截了
	使用cors:跨域资源共享,解决这个问题----》再响应头中加入对应的响应头

解决跨域问题

1. jsonp 跨域(不了解)-->比如标签上的图片地址...
2. 跨域资源共享(CORS-->后端技术
3.Nginx代理

CORS:跨域资源共享

简介

1.CORS需要浏览器和服务器同时支持,所有浏览器都支持该功能,IE浏览器不能低于IE10。
		整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
		因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
    
2.只需要服务的处理即可:只需要在在响应头中加入固定的头就实现cors--->比如在响应头中加入Access-Control-Allow-Origin='*'---->get请求就没有跨域了---->但是put请求还会有

cors的请求分两种

1.cors的请求分两种:
  1.简单请求,浏览器直接发起
  2.非简单请求,浏览器先发送要给options预检请求,服务端允许,再发送真正的请求

2.什么是简单请求,什么是非简单请求
	如果属于下面,就是简单请求:
	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)

测试

def test(request):
    res = HttpResponse('ok')
    res['Access-Control-Allow-Origin']='http://localhost:8080'
    res['Access-Control-Allow-Headers']='token'
    return res
  
def test(request):
    res = HttpResponse('ok')
    if request.method == 'OPTIONS':
        print(11)
        res['Access-Control-Allow-Methods']='DELETE'
        res['Access-Control-Allow-Headers']='token'
    res['Access-Control-Allow-Origin'] = 'http://localhost:8080'
    return res

自定义中间件,解决跨域问题

1.自定义中间件解决跨域问题--->以后其它框架都是这个原理--->django上有人做了-->django-cors-headers

2.自定义中间件:
  from django.middleware.security import SecurityMiddleware
  from django.utils.deprecation import MiddlewareMixin
  class CommonMiddle(MiddlewareMixin):
      def process_response(self, request, response):
          if request.method == 'OPTIONS':
              response['Access-Control-Allow-Headers'] = 'token'
              response['Access-Control-Allow-Methods'] = 'DELETE,PUT'
          response['Access-Control-Allow-Origin'] = '*'
          return response
      
      
3.配置文件配置中间件:
      MIDDLEWARE = [
		...,
    'utils.common_middleware.CommonMiddle'
]

django-cors-headers

1.安装: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'
    )

django-cors-headers源码

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

前台主页功能

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 => {
            this.banner_list = res.data.data
        })
    }
}
</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>

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>

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>

HomeView.vue

<template>
    <div class="home">

        <Header></Header>
        <Banner></Banner>
        <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>

补充

1.表模型继承ModelMixin,所有表模型都有to_dict,序列化用的
2.以后拿到表模型对象:序列化后的数据=banner.to_dict(a=['id','name'])
参照开源项目:https://gitee.com/openspug/spug