项目开发流程
需求分析: 引导客户朝着自己想要的方向提需求
架构设计: 框架, 语言, 数据库, 缓存数据库, 表设计, 拆分功能, 项目报价(参与开发的人员个数和开发天数)
分组开发: 架构师组长会议: 按模块功能拆分任务; 小组会议: 细分功能; 小组成员写代码, 提前测试下显而易见的bug
交付测试: 测试些不是显而易见的bug, 如果是显而易见的bug, 会扣绩效
运维上线: 1. 委托项目开发公司上线和维护, 定期支付维护费; 2. 交付给对方公司
表设计
先确定表名, 然后是表字段, 最后是表关系
用户表
- 使用auth_user表
- 手机号
- 头像
- 注册时间
外键字段: blog, 一对一个人站点表
个人站点表
- 站点名称
- 站点标题
- 站点样式
文章标签表
- 标签名
外键字段: blog, 多对一个人站点表
文章分类表
- 分类名
外键字段: blog, 多对一个人站点表
文章表
- 文章标题
- 文章摘要
- 文章内容
- 文章发布时间
外键字段1: blog, 多对一个人站点表
外键字段2: tag, 多对多文章标签表, 半自动创建
外键字段3: category, 多对一文章分类表
文章点赞点踩表
- user_id: 多对一用户表
- article_id: 多对一文章表
- is_upper: BooleanField
类似于用户和文章的关系表, 加一个is_upper字段
文章的评论表
- user_id: 多对一用户表
- article_id: 多对一文章表
- content: TextField
- create_time
自关联: parent, 和自己所在的表外键关联, 多对一, 根评论/子评论
'''
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bbs',
'USER': 'root',
'PASSWORD': 'Cql123456',
'HOST': '127.0.0.1',
'PORT': 3306,
'CHARSET': 'utf8'
}
}
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
AUTH_USER_MODEL = 'app01.UserInfo'
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class UserInfo(AbstractUser):
...
# 将用户上传的文件保存到avatar文件夹下, 数据库中对应的avatar字段保存文件路径
avatar = models.FileField(upload_to='avatar/', default='static/img/default.jpg')
...
class Blog(models.Model):
...
site_theme = models.CharField(max_length=64) # 存储用户自定义的css文件路径
...
class Article(models.Model):
...
# 数据库优化的三个普通字段
up_num = models.IntegerField(default=0)
down_num = models.IntegerField(default=0)
comment_num = models.IntegerField(default=0)
# 外键字段
...
tag = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag')) # 半自动创建多对多关系
# 文章和标签的关系表
class Article2Tag(models.Model):
article = models.ForeignKey(to='Article')
tag = models.ForeignKey(to='Tag')
...
class Comment(models.Model):
...
# 自关联字段
# parent = models.ForeignKey(to='Comment') # 方式一
parent = models.ForeignKey(to='self', null=True) # 方式二, 语义更明确
'''
注册页面搭建
更改文件创建的初始化模板: File-->Settings-->Editor-->File and Code Templates-->Files
'''
# app01-->myform.py
from django import forms
from app01 import models
class MyRegForm(forms.Form):
username = forms.CharField(
max_length=8,
min_length=3,
label='用户名',
error_messages={
'max_length': '用户名最长为8位',
'min_length': '用户名最短为3位',
'required': '用户名不能为空',
},
widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
)
password = forms.CharField(
...
)
confirm_password = forms.CharField(
...
)
email = forms.EmailField(
label='邮箱',
error_messages={
'invalid': '邮箱格式不正确',
'required': '邮箱不能为空',
},
widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
)
# 局部钩子函数校验用户名是否存在
def clean_username(self):
username = self.cleaned_data.get('username')
res = models.UserInfo.objects.filter(username=username) # app01_userinfo表中只有password字段为密文, 可以校验用户名是否已存在
if res:
self.add_error('username', '用户名已存在')
return username
# 全局钩子校验两次密码是否一致
def clean(self):
password = self.cleaned_data.get('password')
confirm_data = self.cleaned_data.get('confirm_password')
if not password == confirm_data:
self.add_error('confirm_password', '两次密码不一致')
return self.cleaned_data
# views.py
from django.shortcuts import render
from app01 import myform
def register(request):
form_obj = myform.MyRegForm()
return render(request, 'register.html', locals())
# register.html
...
<form action="" id="myform">
{% for form_field_obj in form_obj %}
<div class="form-group">
<label for="form_field_obj.auto_id">{{ form_field_obj.label }}</label>
# form_field_obj.auto_id获取forms组件渲染出的input框的id # label默认获取到的是类中字段名的首字母大写形式
{{ form_field_obj }}
<span class="errors pull-right" style="color: red"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="user_file">
头像
<img src="/static/img/default.jpg" alt="" width="100" style="margin-left: 10px;" id="img">
</label>
<input type="file" name="avatar" id="user_file" style="display: none">
</div>
<input type="button" class="btn btn-success pull-right" value="注册" id="submit">
</form>
...
<script>
$('#user_file').on(
'change', // change事件
function () {
let myFileReader = new FileReader(); // 文件阅读器对象, 专门读取文件并展示到页面上
let fileObj = $(this)[0].files[0]; // 获取用户上传的文件对象
myFileReader.readAsDataURL(fileObj); // 文件阅读器读取文件, 异步IO操作
myFileReader.onload = function () { // 等待文件阅读器读取完毕之后再执行函数体代码
$('#img').attr('src', myFileReader.result); // 将读取的结果替换到img标签的src属性中
}
}
)
</script>
'''
注册功能实现
'''
# views.py
def register(request):
form_obj = myform.MyRegForm()
if request.method == 'POST':
back_dic = {'code': 1000, 'msg': ''} # 响应ajax请求的信息字典
form_obj = myform.MyRegForm(request.POST) # forms组件校验ajax提交的POST请求中的数据
if form_obj.is_valid():
cleaned_data = form_obj.cleaned_data
cleaned_data.pop('confirm_password')
file_obj = request.FILES.get('avatar')
if file_obj:
cleaned_data['avatar'] = file_obj
models.UserInfo.objects.create_user(**cleaned_data)
back_dic['msg'] = '注册成功'
back_dic['url'] = '/login/' # 注册成功后跳到登录页面
else:
back_dic['code'] = 2000
back_dic['msg'] = form_obj.errors
return JsonResponse(back_dic) # 前端自动将字典转换成自定义对象
return render(request, 'register.html', locals())
# register.html
<script>
...
$('#submit').on(
'click',
function () {
let myFormData = new FormData();
$.each(
$('#my_form').serializeArray(), // form标签中的serializeArray方法将普通数据序列化成数组套键值对的形式
function (index, obj) { // index为索引, obj为字典对象
myFormData.append(obj.name, obj.value) // 添加普通价值对
}
);
myFormData.append('avatar', $('#user_file')[0].files[0]); // 手动添加文件数据
$.ajax({
url: '',
type: 'post',
data: myFormData,
processData: false, // 浏览器不处理数据
contentType: false, // 浏览器不使用任何编码
success: function (data) {
if (data.code == 1000) {
window.location.href = data.url // 前端控制页面跳转
}
else {
$.each(
data.msg,
function (index, obj) { // index为报错字段名, obj为数字形式的错误信息
let targetId = '#id_' + index; // 拼接报错信息对应input标签的id
$(targetId).next().text(obj[0]).parent().addClass('has-error') // 展示报错信息并使input框飘红
}
)
}
}
})
}
);
$('input').on(
'focus',
function () {
$(this).next().text('').parent().removeClass('has-error') // input框获取焦点时, 清除报错信息和飘红
}
)
</script>
'''
回顾添加内容
项目庞大, 需要用到多个forms组件, 将所有forms组件放到一个单独的文件夹下, 针对不同的forms组件开设不同的py文件:
文件夹: 1.py, 2.py, ...
img标签的src属性可以放的值:
- 图片的地址
- 图片的二进制数据
- 后端url, 页面渲染完毕后会自动朝该url发送get请求
window.onload = function () {}
fileReader.onload = function () {}, 解决异步文件读取的造成的问题