1

1. Django的设计模式

Django是基于pyton语言的一个比较全面的框架,采用了MVC设计模式,但是Django更关注于模型(Model)、模板(Template)和视图(Views),称为 MTV模式。各自职责如下:

层次职责
模型(Model)即数据存取层。处理与数据相关的所有事务: 如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。
视图(View)即表现层。处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。模型与模板的桥梁。
模板(Template)即业务逻辑层。存取模型及调取恰当模板的相关逻辑。

从以上表述可以看出Django 视图不处理用户输入,而仅仅决定要展现哪些数据给用户,而Django 模板 仅仅决定如何展现Django视图指定的数据。或者说, Django将MVC中的视图进一步分解为 Django视图 和 Django模板两个部分,分别决定 “展现哪些数据” 和 “如何展现”,使得Django的模板可以根据需要随时替换,而不仅仅限制于内置的模板。至于MVC控制器部分,由Django框架的URLconf来实现。URLconf机制是使用正则表达式匹配URL,然后调用合适的Python函数。URLconf对于URL的规则没有任何限制,你完全可以设计成任意的URL风格,不管是传统的,RESTful的,或者是另类的。框架把控制层给封装了,无非与数据交互这层都是数据库表的读,写,删除,更新的操作.在写程序的时候,只要调用相应的方法就行了,感觉很方便。程序员把控制层东西交给Django自动完成了。 只需要编写非常少的代码完成很多的事情。所以,它比MVC框架考虑的问题要深一步,因为我们程序员大都在写控制层的程序。现在这个工作交给了框架,仅需写很少的调用代码,大大提高了工作效率。

2. 前后端分离

在Django中实现前后端分离,通常意味着后端(使用Django)只提供API接口,而前端(可能是Web页面、移动应用或其他客户端)通过HTTP请求来获取和提交数据。前后端分离的思想,就是前端负责界面交互和美观,后端负责数据管理和数据输出。前端和后端的通信,完全基于 API 来处理。
首先,说明下格式问题。前端找后端要数据,后端给数据,前端拿数据,都是有特定格式的,这种格式是前后端两个规则好的。所以在这里,以 json 格式为例,json 也是前端编程语言 javascript 的对象结构。

3. 使用Django和Vue构建前后端分离的WEB应用

3.1 后端定义API

在app1.urls.py中定义4个API,分别实现提交注册表单、提交登录表单、访问主页,验证是否登录和刷新图片验证码功能。

#文件名:app1.urls.py
from django.urls import path
from app1 import views

urlpatterns = [
    path("register", views.RegisterResponse.as_view()),  # 提交注册表单
    path("login", views.LoginResponse.as_view(), name="app1-login"),  # 提交登录表单
    path("home", views.home, name="home"),  # 访问主页,验证是否登录
    path("get_image", views.VerificationCode.as_view()),  # 刷新图片验证码
]

3.2 后端实现视图函数

4个视图函数与3.1中的4个API一一对应。

#文件名:app1.views.py
import json
from django.http import HttpResponse, JsonResponse

from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.views import View

from .form import RegisterForm, LoginForm
from verification_code import load_image


class RegisterResponse(View):
    def post(self, request):
        data = json.loads(request.body)
        form = RegisterForm(data)
        if form.is_valid:
            User.objects.create_user(username=form.username, password=form.password)
            form.info = form.register_success
            form.status = 1
        else:
            form.info = "\n".join(form.error)
            form.status = -1
        return JsonResponse(form.response)


class LoginResponse(View):
    def post(self, request):
        data = json.loads(request.body)
        form = LoginForm(data)
        if form.code != request.session["code"]:
            form.info = form.authentication_failure
            form.status = -1
            return JsonResponse(form.response)
        if form.is_valid:
            user = authenticate(request, username=form.username, password=form.password)
            if user:
                login(request, user) if user.is_active else None
                form.info = form.login_success
                form.status = 1
            else:
                form.info = form.login_failure
                form.status = -1
        else:
            error = "\n".join(form.error)
            form.info = error
            form.status = -1
        return JsonResponse(form.response)


def home(request):
    if request.user.is_authenticated:
        return JsonResponse({"view": "home"})
    else:
        return JsonResponse({"view": "login"})
    

class VerificationCode(View):
    def get(self, request):
        code, data = load_image()
        if code and data:
            request.session["code"] = code
            return HttpResponse(data)
        else:
            return HttpResponse("")

视图函数中用到的用户名密码格式校验、随机验证码图片生成见app1.form.py和django_study.verification_code.__init__.py。

#文件名:app1.form.py
from django.contrib.auth.models import User
from string import ascii_lowercase, ascii_uppercase


class AbstractForm:
    def __init__(self, data):
        self.error = list()
        self.info = ""
        self.status = 0
        self.username = data.get("username") if "username" in data.keys() else ""
        self.password = data.get("password") if "password" in data.keys() else ""
        self.code = data.get("code") if "username" in data.keys() else ""  # 验证码
        self.username_valid = False
        self.password_valid = False
        # self.is_valid = self.username_valid and self.password_valid

    @property
    def is_valid(self):
        return self.username_valid and self.password_valid

    @property
    def response(self):
        return {"info": self.info, "status": self.status}


class RegisterForm(AbstractForm):
    def __init__(self, data: dict):
        super(RegisterForm, self).__init__(data)
        self.username_valid = self.username_strategy()
        self.password_valid = self.password_strategy()
        self.register_success = "注册成功"
        self.register_failure = "\n".join(self.error)

    def username_strategy(self):
        res = True
        query = User.objects.filter(username=self.username)
        if query:
            self.error.append("用户{}已存在".format(self.username))
            res = False
        if len(self.username) > 30:
            self.error.append("用户名长度超过30")
            res = False
        if len(self.username) < 6:
            self.error.append("用户名长度小于6")
            res = False
        if len(self.username) < 1:
            self.error.append("用户名不能为空")
            res = False
        return res

    def password_strategy(self):
        res = True
        upper_exists = False
        lower_exists = False
        for letter in self.password:
            if letter in ascii_uppercase:
                upper_exists = True
                break
        for letter in self.password:
            if letter in ascii_lowercase:
                lower_exists = True
                break
        if len(self.password) < 8:
            self.error.append("密码长度小于8")
            res = False
        if not upper_exists:
            self.error.append("密码中不包含大写字母")
            res = False
        if not lower_exists:
            self.error.append("密码中不包含小写字母")
            res = False
        return res


class LoginForm(AbstractForm):
    def __init__(self, data: dict):
        super(LoginForm, self).__init__(data)
        self.username_valid = self.username or None
        self.password_valid = self.password or None
        if not self.username:
            self.error.append("用户名不能为空")
        if not self.password:
            self.error.append("密码不能为空")
        self.login_success = "登录成功"
        self.login_failure = "\n".join(self.error)
        self.authentication_failure = "验证码错误"
#文件名:django_study.verification_code.__init__.py
import numpy as np
import cv2
import os
import base64


def randcolor(lower=0, upper=255):
    return np.random.randint(lower, upper), np.random.randint(lower, upper), np.random.randint(lower, upper)


def randchar():
    lower = chr(np.random.randint(97, 122))
    upper = chr(np.random.randint(65, 90))
    number = str(np.random.randint(0, 9))
    return np.random.choice([lower, upper, number])


def randpos(x0, x1, y0, y1):
    return np.random.randint(x0, x1), np.random.randint(y0, y1)


def load_image(image_width = 120, image_height = 30, num = 4, path = "./"):
    # 创建背景图片
    img = np.random.randint(181, 255, (image_height, image_width, 3), np.uint8)
    image_name = ""
    x_pos = 0
    y_pos = int(image_height / 2)
    for i in range(num):  # num:验证码字符个数
        ch = randchar()
        image_name += ch
        # 添加文字
        cv2.putText(
            img,
            ch,
            (np.random.randint(x_pos, x_pos + int(image_width / (num + 1))),
             np.random.randint(y_pos, y_pos + int(image_height / 2))),  # 坐标
            cv2.FONT_HERSHEY_SIMPLEX,
            0.8,  # 字号
            randcolor(0, 180),
            2,
            cv2.LINE_AA
        )
        # 添加直线作为干扰信息
        x_pos += int(image_width / (num + 1))
        img = cv2.line(
            img,
            randpos(0, image_width, 0, image_height),
            randpos(0, image_width, 0, image_height),
            randcolor(),
            np.random.randint(1, 2)
        )
    file = os.path.join(path, image_name + ".jpg")
    try:
        cv2.imwrite(file, img)
        if os.path.isfile(file):
            with open(file, "rb") as f:
                data = base64.b64encode(f.read()).decode('utf-8')
            os.unlink(file)
            return image_name, data  # 返回验证码字符串和base64编码的图像数据
        else:
            return None, None
    except:
        return None, None

3.3 前端路由定义

//文件名:./src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    component: () => import('../views/home'),
    redirect: '/home',
    meta: {
      title: 'Django Study'
    }
  },
  {
    path: '/home',
    component: () => import('../views/home'),
    meta: {
      title: 'Django Study'
    }
  },
  {
    path: '/login',
    component: () => import('../views/login'),
    meta: {
      title: 'Login'
    }
  },
  {
    path: '/register',
    component: () => import('../views/register'),
    meta: {
      title: 'Register'
    }
  },
];

const router = new VueRouter({
  mode: 'history',
  routes
});

export default router

3.4 注册页面

<!--文件名:./src/view/register.vue-->
<template>
    <div>
        <div>
            <span>新用户</span>
            <label>
                <input v-model="username" type="text"/>
            </label>
        </div>
        <div>
            <span>请输入新密码</span>
            <label>
                <input v-model="password" type="text"/>
            </label>
        </div>
        <div>
            <span>请确认新密码</span>
            <label>
                <input v-model="password2" type="text"/>
            </label>
        </div>
        <div>
            <button @click="registerSubmit">提交</button>
            <span id="tologin" @click="toLogin">已有账户?</span>
        </div>
    </div>
</template>

<script>
    export default {
        name: "register",
        data() {
            return {
                username: "",
                password: "",
                password2: "",
                info: "",
                status: 0,
            }
        },
        methods: {
            registerSubmit () {
                if (this.password !== this.password2) {
                    console.log("密码不一致")
                }
                else {
                    this.$axios({
                        method: "POST",
                        url: "/api/app1/register",
                        data: {
                            username: this.username,
                            password: this.password,
                        }
                    }).then((res) => {
                        const data = res.data;
                        this.info = data.info;
                        this.status = data.status;
                        if (this.status === 1) {
                            alert("注册成功:" + this.username);
                        } else if (this.status === -1) {
                            alert("注册失败:" + this.info);
                        }
                        else {}
                    })
                }
            },
            toLogin () {
                this.$router.push("/login")
            }
        }
    }

</script>

<style scoped>
#tologin {
    color: #f00;
}
    #tologin:hover {
        cursor: pointer;
    }
</style>

3.5 登录页面

<!--文件名:./src/view/login.vue-->
<template>
    <div>
        <div>
            <span>用户名</span>
            <label>
                <input v-model="username" type="text"/>
            </label>
        </div>
        <div>
            <span>密码</span>
            <label>
                <input v-model="password" type="password"/>
            </label>
        </div>
        <div>
            <span>验证码</span>
            <label>
                <input v-model="verificationCode" type="text"/>
            </label>
            <img
                style="width: 120px; height: 30px"
                :src="'data:image/png;base64,' + imageCode"
                @click="getImage"
                alt=""
            >
        </div>
        <div>
            <button @click="loginSubmit">确认</button>
            <button @click="toRegister">注册</button>
        </div>
    </div>
</template>

<script>
    export default {
        name: "login",
        mounted() {
            this.getImage();
        },
        data() {
            return {
                username: "",
                password: "",
                verificationCode: "",  //验证码
                imageCode: "",         // 图片
            }
        },
        methods: {
            loginSubmit () {
                this.$axios({
                    method: "POST",
                    url: "/api/app1/login",
                    data: {
                        username: this.username,
                        password: this.password,
                        code: this.verificationCode,
                    }
                }).then((res) => {
                    const data = res.data;
                    this.info = data.info;
                    this.status = data.status;
                    if (this.status === 1) {
                        alert("登录成功:" + this.username);
                        this.$router.back();
                    } else if (this.status === -1) {
                        alert("登录失败:" + this.info);
                        this.getImage();
                    }
                    else {}
                })
            },
            getImage () {
                this.$axios({
                    method: "get",
                    url: "/api/app1/get_image",
                }).then(res => {
                    this.imageCode = res.data;
                    console.log(res.data);
                })
            },
            toRegister () {
                this.$router.push("/register")
            }
        }
    }
</script>

<style scoped>

</style>

3.6 主页

<!--文件名:./src/view/home.vue-->
<template>
  <div style="overflow-y: auto; overflow-x: auto; height: 100%">
    home
  </div>
</template>

<script>

  export default {
    name: "home",
    mounted() {
      this.$axios({
        method: "get",
        url: "/api/app1/home"
      }).then(res => {
        const view = res.data.view;
        console.log("/" + view)
        this.$router.push("/" + view);
      })
    },
    components: {
    },
    data() {
      return {

      }

    },
  }
</script>

<style scoped>
</style>

4. 测试


追忆
6 声望1 粉丝