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. 测试
略
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。