1. 项目开发流程
1.1 需求分析
架构师 + 产品经理 + 开发者组长
在跟客户谈需求之前, 会大致先了解客户的需求, 然后先设计一套比较好写方案.
与客户沟通交流中引导客户往设计好方案上面靠, 形成一个初步的方案.
1.2 项目设计
架构师:
1. 编程语言选择
2. 框架选择
3. 数据库选择
3.1 主库:MySQL,postgreSQL,...
3.2 缓存数据库:redis、mongodb、memcache...
4. 功能划分
将整个项目划分成几个功能模块
5. 组长开会
给每个组分发任务
6. 项目报价
技术这块需要多少人,多少天(一个程序员一天1500~2000计算(大致))
产品经理公司层面, 公司财务签字确认, 公司老板签字确认 产品经理去跟客户沟通
后续需要加功能 继续加钱
1.3 分组开发
组长找组员开会,安排各自功能模块
组员在架构师设计好的框架里面填写代码,
在写代码的时候,需要自己先测试是否有bug,
如果是一些显而易见的bug, 没有避免而是直接交给了测试部门测出来...
1.4 测试
测试部门测试提交的代码
压力测试
1.5上线
1.交给对方的运维人员
2.直接上线到公司的服务器上 收取维护费用
3.其他...
2. 环境准备
前端 HTML + CSS + JavaScript
后端 Python 3.6
架构 Django 1.11.11
数据库 Mysql 5.6.47
2.1 新建Django项目

2.2 解决路径问题
项目名目录下的settings 第58行.
'DIRS': [BASE_DIR, 'templates']
2.3 建立bbs库
由于django自带的sqlite数据库对日期不敏感,所以我们换成MySQL.
使用Navicat创建bbs库.
数据库的名称 bbs 字符集 utf8mb4
CREATE DATABASE `bbs` CHARACTER SET 'utf8mb4';

2.4 Django连接MySQL
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1',
'POST': 3306,
'USER': 'root',
'PASSWORD': 123,
'NAME': 'bbs',
'CHARSET': 'UTF8'
}
}
在app01 应用下 __init__.py 中配置 pymysql 模块连接数据库.
import pymysql
pymysql.install_as_MySQLdb()
2.5 开放静态文件
0. 在项目目录下创建 static 静态文件目录.
1. 在static目录下 创建 js 目录, 将jQuery文件复制到 js 目录中.
2. 复制bootstarp框架文件到 static 目录中.
3. 去项目名文件下settings.py 中设置开放静态文件.

STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'STATIC')
]
2.6 访问测试
0. 启动项目
1. 在浏览器中输入: 127.0.0.1:8000

3. 表设计
一个项目中最重要的不是业务逻辑的书写
而是前期的表设计,只要将表设计好了,后续的功能书写才会一帆风顺.
在app01 下的 models.py 使用ORM模块, 创建映射表的类.
先写普通的字段, 在写外键字段.
3.1用户表 User

用户表: 记录用户的信息
继承AbstractUser 使用Auth模块
扩展字段:
phone 用户电话号码 IntegerField 数值类型 大整数 用4位来表示
avatar 用户头像 FileField 文件类型
create_time 用户创建时间 DateField 日期 年月日
外键字段:
用户表一对一个人站点表, 外键字段建在查询评论多的一方.
blog 外键绑定个人博客表的id
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone = models.IntegerField(null=True, verbose_name='手机号')
avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='头像')
create_time = models.DateField(auto_now_add=True, verbose_name='用户创建时间')
blog = models.OneToOneField(to='Blog', null=True, verbose_name='关联博客表id')
AUTH_USER_MODEL = 'app01.UserInfo'
avatar 字段存放的文件路径 avatar/xxx.pnh
upload_to='avatar/', upload_to 参数 文本保存的位置,
default='avatar/default.png' 用户不上床头像,使用 default 参数设置的默认头像.
在项目下创建avatar目录, 找一个图片设置为 default.png 默认头像.

3.2 个人博客表 Blog
个人博客/个人站点表: 记录每个用户个人网站, 访问时查询有没有该有户的个人站点存在.
每个人站点有自己的站点名称, 站点标题, 站点样式.
https://www.cnblogs.com/python

https://www.cnblogs.com/java

https://www.cnblogs.com/python_21

字段:
site_name 站点名称 CharField 字符串类型
site_title 站点标题 CharField 字符串类型
site_theme 站点样式 CharField 字符串类型
class Blog(models.Model):
site_name = models.CharField(max_length=32, verbose_name='站点名称')
site_title = models.CharField(max_length=32, verbose_name='站点标题')
site_theme = models.CharField(max_length=32, verbose_name='站点样式')
站点样式 字段存放css/js文件的路径, 简单的操作下样式的切换.
3.3 文章表 Article
文章表记录文字的内容.

字段:
文章的标题 title CharField 字符串类型
文章简介 desc CharField 字符串类型
文章内容 content TextField 文本字段适合存大量文本信息 不需要指定 max_length
文章发布时间 create_time DateField 日期 年月日
查询优化:
虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率低下,
直接在文章表中创建三个普通的字段, 点赞点踩, 评论表, 记录数据的时候同步文章表的这三个字段.
文章的点赞数 up_num BigIntegerField 极大整数值 用8位表示
文章的点踩数 down_num BigIntegerField 极大整数值 用8位表示
文字的评论数 comment_num BigIntegerField 极大整数值 用8位表示
* 访问数不做, 换为点踩.
外键字段:
站点表 一对多 文章表 外键 blog_id 绑定站点表的id
文章标签表 多对多 文章表 虚拟字段 tag 半自动建立第三张表
文章分类表 一对一 文章表 外键 sort_id 绑定分类表id

class Article(models.Model):
title = models.CharField(max_length=32, verbose_name='标题')
desc = models.CharField(max_length=256, verbose_name='简介')
content = models.TextField(verbose_name='文章内容')
create_time = models.DateField(auto_now_add=True, verbose_name='文章创建时间')
up_num = models.BigIntegerField(default=0, verbose_name='点赞数')
down_num = models.BigIntegerField(default=0, verbose_name='点踩数')
comment_num = models.BigIntegerField(default=0, verbose_name='评论数')
blog = models.ForeignKey(to='Blog', null=True, verbose_name='关联博客表id')
sort = models.OneToOneField(to='Sort', null=True, verbose_name='关联分类表id')
tag = models.ManyToManyField(to='Tag', through='ArticleToTag',
through_fields=('article', 'tag'))
class ArticleToTag(models.Model):
article = models.ForeignKey(to='Article')
tag = models.ForeignKey(to='Tag')
3.4 文章的标签 Tag
一个文章可以打上很多个标签.
字段:
标签的名字 name CharField 字符串类型
外键字段: (这个字段存在的原因是需要在个人博客表中,侧边栏可以通过标签去查找对应的文章)
个人站点表 一对多 文章标签表
blog_id 绑定博客表的id
class Tag(models.Model):
name = models.CharField(max_length=32, verbose_name='文章标签')
blog = models.ForeignKey(to='Blog', null=True, verbose_name='绑定博客表的id')
3.5 文章的分类 Sort
一个文章属于一个类型.(一个文章就一个类, 不搞多个.)
字段:
类的名字 name CharField 字符串类型
外键字段:(这个字段存在的原因是需要在个人站点中, 侧边栏可以通过分类去查找对应的文章)
个人站点表 一对多 文章分类表
blog_id 绑定博客表的id
class Sort(models.Model):
name = models.CharField(max_length=32, verbose_name='文章分类')
blog = models.ForeignKey(to='Blog', null=True, verbose_name='绑定博客表的id')
3.6 点赞点踩表 UpAndDown
文章点赞点踩表: 记录那个用户给哪篇文章点了赞还是点了踩
字段:
用户名 user
文章名 article
点赞/点踩 is_up

id user_id article_id is_up
1 1 1 1 用户1 给第一篇文章点了赞
2 2 1 1 点赞 统计 article的 is_up 为1 数量
3 3 1 1 点踩 统计 article的 is_up 为1 数量
class UpAndDown(models.Model):
user = models.ForeignKey(to='UserInfo', verbose_name='关联用户id')
article = models.ForeignKey(to='Article', verbose_name='关联文章id')
is_up = models.BooleanField(verbose_name='是否点赞')
3.7 文字评论表 Comment
文章的评论表: 记录那个用户给哪篇文字评论了什么内容
字段:
用户名 user
文章名 article
评论内容 content
评论时间 comment_time
根评论子评论的概念
根评论就是直接评论当前发布的内容的
子评论是评论别人的评论
1.PHP是世界上最牛逼的语言
1.1 PHP是世界上最牛逼的语言.py
1.1.1 java才是.py
1.2.1 go才是.py
根评论与子评论是一对多的关系
自关联
parent ForeignKey(to="Comment",null=True)
ORM专门提供的自关联写法
parent ForeignKey(to="self",null=True)
id user_id article_id parent_id
1 1 1 (可以不评论)
2 2 1 1 (代表这个评论是给这个表第一个内容的)

class Comment(models.Model):
user = models.ForeignKey(to='UserInfo', verbose_name='关联用户id')
article = models.ForeignKey(to='Article', verbose_name='关联文章id')
content = models.CharField(max_length=256, verbose_name='评价内容')
comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
parent = models.ForeignKey(to='self', null=True, verbose_name='自关联')
只要关联了就代表它是子评论, 否则就是根评论, null=True, 不写就只能是评论了

3.8数据库迁移
python manage.py makemigrations
python manage.py migrate

4. 用户注册功能
4.1 路由层
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^register/', views.register)
]
4.2 forms组件
0. 在app01应用程序下创建 forms_module 目录
1. 在 forms_module 目录下创建 register_froms.py 文件
from django import forms
from app01 import models
class RegisterForms(forms.Form):
username = forms.CharField(
label='名称', min_length=3, max_length=8,
error_messages={
'required': '名称不能为空',
'min_length': '名称不能少于3位',
'max_length': '名称不能超过8位'},
widget=forms.widgets.TextInput(
attrs=(
{'class': 'form-control input-lg', 'style': 'border:none; background-color:transparent; color: #66AFE9;'})
)
)
password = forms.CharField(
label='密码', min_length=3, max_length=8,
error_messages={
'required': '密码不能为空',
'min_length': '密码不能少于3位',
'max_length': '密码不能超过8为'},
widget=forms.widgets.PasswordInput(
attrs=(
{'class': 'form-control input-lg', 'style': 'border:none; background-color:transparent; color: #ffaa00;'})
)
)
confirm_password = forms.CharField(
label='确认密码', min_length=3, max_length=8,
error_messages={
'required': '确认密码不能为空',
'min_length': '确认密码不能少于3位',
'max_length': '确认密码不能超过8位'},
widget=forms.widgets.PasswordInput(
attrs={'class': 'form-control input-lg',
'style': 'border:none; background-color:transparent; color: #ffaa00;'}
)
)
email = forms.EmailField(
label='邮箱',
error_messages={
'required': '邮箱不能为空',
'invalid': '邮箱格式不正确'},
widget=forms.widgets.EmailInput(
attrs=(
{'class': 'form-control input-lg', 'style': 'border:none; background-color:transparent; color: #ffaa00;'})
)
)
def clean_username(self):
username = self.cleaned_data.get('username')
is_exist = models.UserInfo.objects.filter(username=username)
if is_exist:
self.add_error('username', '名称已经存在')
return username
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if password != confirm_password:
self.add_error('confirm_password', '两次密码不一致')
return self.cleaned_data
4.3 视图层
from django.shortcuts import render, redirect, HttpResponse
from app01.forms_module.register_forms import RegisterForms
def register(request):
forms_obj = RegisterForms()
return render(request, 'register.html', locals())
4.4 模板层
在templates目录下创建 register.html
form表单 autocomplete="off" 浏览器不自动填充数据.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
{% load static %}
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
<style>
body {
background-image: url("{% static 'background/register.jpg'%}");
background-size: cover;
background-repeat: no-repeat;
color: #d4ff00;
font-size: 18px;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
-webkit-transition-delay: 111111s;
-webkit-transition: color 11111s ease-out, background-color 111111s ease-out;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1 class="text-center">注册</h1>
<form action="" autocomplete="off" id="form_table">
{% csrf_token %}
{% for input in forms_obj %}
<div class="form-group">
<label for="{{ input.auto_id }}">{{ input.label }}</label>
{{ input }}
<span style="color: red; " class="pull-right"></span>
</div>
{% endfor %}
<label for="my_avatar">头像
<img src="{% static 'img/default.png' %}" alt="" id='my_img' width="80px" style="margin-left:20px">
</label>
<input type="file" name="avatar" id="my_avatar" style="display: none">
<p>
<input type="button" id='btn1' value="注册" class="btn-primary btn-lg btn-block"
style="border: none; opacity:0.3; color: orange; font-size: 20px">
</p>
</form>
</div>
</div>
</div>
<script>
$('#my_avatar').change(function () {
let FileReaderObj = new FileReader()
let UpFileObj = $(this)[0].files[0];
FileReaderObj.readAsDataURL(UpFileObj)
FileReaderObj.onload = function () {
$('#my_img').attr('src', FileReaderObj.result)
console.log(FileReaderObj.result)
}
})
$('#btn1').on('click', function () {
let FormDataObj = new FormData()
let Text_Data = $('#form_table').serializeArray()
$.each(Text_Data, function (index, obj) {
FormDataObj.append(obj.name, obj.value)
})
FormDataObj.append('avatar', $('#my_avatar')[0].files[0])
$.ajax({
url: '',
type: 'post',
data: FormDataObj,
contentType: false,
processData: false,
success: function (args) {
if (args.code === 200) {
window.location.href = args.url
} else {
$.each(args.error, function (key, value) {
console.log(key, value)
let InputId = '#id_' + key
$(InputId).css('border', '')
$(InputId).next().text(value[0]).parent().addClass('has-error')
})
}
}
})
})
$('input').focus(function () {
$(this).next().text('').parent().removeClass('has-error')
$(this).css('border', 'none')
})
</script>
</body>
</html>
let Text_Data = $('#form_table').serializeArray()

each遍历列表获取到两个参数,第一个是index索引, 第二个是索引对应的值.
$.each(Text_Data, function (index, obj) {
console.log(index, obj)...

args.error

each遍历对象获取到两个参数,第一个是属性, 第二个是属性值
$.each(args.error, function (key, value) {
console.log(key, value) ...

forms组件 表单错误信息提示


4.5 业务逻辑
from django.shortcuts import render, redirect, HttpResponse
from app01.forms_module.register_forms import RegisterForms
from app01 import models
from django.http import JsonResponse
def register(request):
forms_obj = RegisterForms()
if request.is_ajax():
forms_obj = RegisterForms(request.POST)
if forms_obj.is_valid():
cleaned_data = forms_obj.cleaned_data
cleaned_data.pop('confirm_password')
avatar_obj = request.FILES.get('avatar')
if avatar_obj:
cleaned_data['avatar'] = avatar_obj
models.UserInfo.objects.create_user(**cleaned_data)
print('写入数据成功!')
back_dir = {'code': 200, 'url': '/login/'}
else:
back_dir = {'code': 400, 'error': forms_obj.errors}
return JsonResponse(back_dir)
return render(request, 'register.html', locals())