0.项目的通用流程

  1. 项目立项
  2. 需求分析
  3. 原型
  4. 前端
    1. 页面设计
    2. UI及交互实现
  5. 后端
    1. 架构设计
    2. 数据库设计
    3. 代码模板实现
    4. 单元测试
  6. 网站整合
  7. 功能及集成测试
  8. 网站发布           

1.BBS项目需求分析

  1. 需要哪些表
    1. UserInfo表
      1. username
      2. password
      3. avatar----头像  
    2. 文章表
      1. title
      2. publish_date
      3. desc----摘要
      4. author
      5. 详细内容 一对一关联,文章详情表
    3. 文章详情表
      1. info  
    4. 评论表
      1. user
      2. 评论时间
      3. 评论内容
      4. 评论和文章的关联关系(1个文章多个评论,1对多,写在多的那方)  
      5. 是谁的子评论,是不是回复别人的评论
    5. 标签
      1. 标签名
      2. 标签名和文章,多对多
    6. 分类
      1. 分类名
      2. 分类和文章的关联关系,多对多或一对多  
    7. 点赞
      1. 是赞还是踩
      2. 时间
      3. 谁点的 关联user
      4. 点的是哪个文章  

2.BBS的注册功能

  1. 基于Form表单和Ajax的注册    

form表单的作用:

  1. 生成HTML代码
  2. 验证
  3. 将验证的错误显示在页面上并保留原始的数据

创建项目和对应APP后

settings.py设置

编码格式

# coding=utf-8

静态文件所在位置

STATIC_URL = '/static/'# 静态文件夹的别名

# 静态文件夹的位置
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]

数据库连接

# 数据库相关的配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 连接的数据库类型
'HOST': '127.0.0.1', # 连接数据库的地址
'PORT': 3306, # 端口
'NAME': "bbs", # 数据库名称
'USER': 'root', # 用户
'PASSWORD': 'root' # 密码
}
} 

APP设置

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog.apps.BlogConfig',
]

模板设置,

因为是PyCharm创建的项目,自动设置好了

认证指定表

# 告诉Django项目用哪张表做认证
AUTH_USER_MODEL = 'blog.UserInfo'

static设置

放入必须的文件 https://pan.baidu.com/s/1EgrzvxIRGAJ_J3g77rL4rQ

__init__.py-----与settings.py同级的

# coding=utf-8
import pymysql
# 告诉Django用pymysql来代替默认的MySQLdb
pymysql.install_as_MySQLdb()

  

models.py

表结构,生成相应的迁移文件和执行迁移

# coding=utf-8
from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser):
""" 用户信息表"""
nid = models.AutoField(primary_key=True)
phone = models.CharField(max_length=11, null=True, unique=True)
# 传入的文件都上传到avatars文件夹下
avatar = models.FileField(upload_to="avatars/", default="avatars/default.png", verbose_name="头像")
create_time = models.DateTimeField(auto_now_add=True) blog = models.OneToOneField(to="Blog", to_field="nid", null=True) def __str__(self):
return self.username class Blog(models.Model):
"""
博客信息
"""
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=64) # 个人博客标题
site = models.CharField(max_length=32, unique=True) # 个人博客后缀
theme = models.CharField(max_length=32) # 博客主题 def __str__(self):
return self.title class Category(models.Model):
"""
个人博客文章分类
"""
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=32) # 分类标题
blog = models.ForeignKey(to="Blog", to_field="nid") # 外键关联博客,一个博客站点可以有多个分类 def __str__(self):
return self.title class Tag(models.Model):
"""
标签
"""
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=32) # 标签名
blog = models.ForeignKey(to="Blog", to_field="nid") # 所属博客 def __str__(self):
return self.title class Article(models.Model):
"""
文章
"""
nid = models.AutoField(primary_key=True)
title = models.CharField(max_length=50) # 文章标题
desc = models.CharField(max_length=255) # 文章描述
create_time = models.DateTimeField() # 创建时间 category = models.ForeignKey(to="Category", to_field="nid", null=True)
user = models.ForeignKey(to="UserInfo", to_field="nid")
tags = models.ManyToManyField( # 中介模型
to="Tag",
through="Article2Tag",
through_fields=("article", "tag"), # 注意顺序!!!
) def __str__(self):
return self.title class ArticleDetail(models.Model):
"""
文章详情表
"""
nid = models.AutoField(primary_key=True)
content = models.TextField()
article = models.OneToOneField(to="Article", to_field="nid") class Article2Tag(models.Model):
"""
文章和标签的多对多关系表
"""
nid = models.AutoField(primary_key=True)
article = models.ForeignKey(to="Article", to_field="nid")
tag = models.ForeignKey(to="Tag", to_field="nid") class Meta:
unique_together = (("article", "tag"),) class ArticleUpDown(models.Model):
"""
点赞表
"""
nid = models.AutoField(primary_key=True)
user = models.ForeignKey(to="UserInfo", null=True)
article = models.ForeignKey(to="Article", null=True)
is_up = models.BooleanField(default=True) class Meta:
unique_together = (("article", "user"),) class Comment(models.Model):
"""
评论表
"""
nid = models.AutoField(primary_key=True)
article = models.ForeignKey(to="Article", to_field="nid")
user = models.ForeignKey(to="UserInfo", to_field="nid")
content = models.CharField(max_length=255) # 评论内容
create_time = models.DateTimeField(auto_now_add=True)
parent_comment = models.ForeignKey("self", null=True) def __str__(self):
return self.content

  生成迁移文件

python manage.py makemigrations

  执行迁移

python manage.py migrate

  

forms.py

# coding=utf-8
"""bbs用到的form类""" from django import forms
# 从from组件导入widgets属性
from django.forms import widgets
# 导入错误类
from django.core.exceptions import ValidationError # 定义一个注册的from类 class RegForm(forms.Form):
username = forms.CharField(
max_length=16,
label="用户名",
# 错误提示
error_messages={
"max_length": "用户名最长16位",
"required": "用户名不能为空",
},
# 设定input框的样式
widget=forms.widgets.TextInput(
attrs={"class": "form-control"}
)
) password = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput(
attrs={"class": "form-control"},
# 输入密码不消失
render_value=True,
),
# 错误提示
error_messages={
"min_length": "密码最少6位",
"required": "密码不能为空",
}
) re_password = forms.CharField(
min_length=6,
label="确认密码",
widget=forms.widgets.PasswordInput(
attrs={"class": "form-control"},
# 输入密码不消失
render_value=True,
),
# 错误提示
error_messages={
"min_length": "密码最少6位",
"required": "密码不能为空",
}
) email = forms.EmailField(
label="邮箱",
widget=forms.widgets.EmailInput(
attrs={"class": "form-control"}
),
error_messages={
'invalid': "邮箱格式不正确",
"required": "邮箱不能为空",
}
) # 重写全局钩子函数,对确认密码做校验
def clean(self):
password = self.cleaned_data.get("password")
re_password = self.cleaned_data.get("re_password")
if re_password and re_password != password:
self.add_error("re_password", ValidationError("两次密码不一致"))
# 没错误直接返回
else:
return self.cleaned_data '''
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
'''

  

urls.py

# coding=utf-8

from django.conf.urls import url
from django.contrib import admin
from blog import views
urlpatterns = [
url(r'^admin/', admin.site.urls), # 注册
url(r'^reg/', views.register),
url(r'^index/', views.index), ]

  

views.py

# coding=utf-8
from django.shortcuts import render, redirect, HttpResponse
# 使用forms内的文件生成HTML
from blog import forms, models from django.http import JsonResponse # Create your views here. # 注册的视图函数
def register(request):
# from表单提交
if request.method == "POST":
# 定义一个字典做Ajax提交
ret = {"status": 0, "msg": "", } # 接收提交过来的数据,只有正常的键值对,没有图像
form_obj = forms.RegForm(request.POST)
print(request.POST)
# 帮我做校验,先校验内置的,在校验clean_开头的规则,整个循环走完,在调用clean()方法
if form_obj.is_valid():
# 校验成功,去数据库创建一个新的用户,models继承AbstractUser,使用creat_user
# 多一个键值对,re_password
form_obj.cleaned_data.pop('re_password')
# 自己取文件、图片的数据
avatar_img = request.FILES.get("avatar")
models.UserInfo.objects.create_user(avatar=avatar_img, **form_obj.cleaned_data)
# 注册成功 跳转页面
ret["msg"] = "/index/"
return JsonResponse(ret)
# return HttpResponse("注册成功") else:
print(form_obj.errors)
# 有错误将status给个值
ret["status"] = 1
# 错误封装到msg里面
ret["msg"] = form_obj.errors
return JsonResponse(ret)
# return render(request, "register.html", {"form_obj": form_obj})
# return HttpResponse("信息有误,注册失败")
# 生成from对象
form_obj = forms.RegForm()
return render(request, "register.html", {"form_obj": form_obj}) def index(request):
return HttpResponse("这里是index页面")

  

register.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>欢迎注册</title>
{# 方法3:使用bootstrap样式 #}
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>
<div class="container reg-form">
<div class="row">
<div class="col-md-6 col-md-offset-3">
{# 有文件类型就需要加enctype="multipart/form-data" novalidate不用H5浏览器帮你验证 #}
<form novalidate action="/reg/" method="post" class="form-horizontal" enctype="multipart/form-data">
{# 防域名伪造 #}
{% csrf_token %}
{# 方法1:标签 + input框 #}
{# {{ from_obj.username.label }}#}
{# {{ from_obj.username }}#} {# 方法2:按照forms写的顺序进行遍历 #}
{# {% for field in from_obj %}#}
{# froms中的每个字段标签+对象#}
{# {{ field.label }}#}
{# {{ field }} <br>#}
{# {% endfor %}#}
{# 方法3:bootstrap #}
<div class="form-group">
{# for属性为了聚焦,关联lable和input框 #}
<label for="{{ form_obj.username.id_for_label }}"
class="col-sm-2 control-label">{{ form_obj.username.label }}</label>
<div class="col-sm-8">
{{ form_obj.username }}
{#<input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}
{# 校验状态 #}
{# 错误提示 #}
<span id="helpBlock2" class="help-block">{{ form_obj.username.errors.0 }}</span>
</div>
</div> <div class="form-group">
{# for属性为了聚焦,关联lable和input框 #}
<label for="{{ form_obj.password.id_for_label }}"
class="col-sm-2 control-label">{{ form_obj.password.label }}</label>
<div class="col-sm-8">
{{ form_obj.password }}
{#<input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}
{# 校验状态 #}
<span id="" class="help-block">{{ form_obj.password.errors.0 }}</span>
</div>
</div> <div class="form-group">
{# for属性为了聚焦,关联lable和input框 #}
<label for="{{ form_obj.re_password.id_for_label }}"
class="col-sm-2 control-label">{{ form_obj.re_password.label }}</label>
<div class="col-sm-8">
{{ form_obj.re_password }}
{#<input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}
{# 校验状态 #}
<span id="helpBlock2" class="help-block">{{ form_obj.re_password.errors.0 }}</span>
</div>
</div> <div class="form-group">
{# for属性为了聚焦,关联lable和input框 #}
<label for="{{ form_obj.email.id_for_label }}"
class="col-sm-2 control-label">{{ form_obj.email.label }}</label>
<div class="col-sm-8">
{{ form_obj.email }}
{#<input type="email" class="form-control" id="inputEmail3" placeholder="Email">#}
{# 校验状态 #}
<span id="helpBlock2" class="help-block">{{ form_obj.email.errors.0 }}</span>
</div>
</div> <div class="form-group">
{# for属性为了聚焦,关联lable和input框 #}
<label for="" class="col-sm-2 control-label">头像</label>
<div class="col-sm-8">
<label for="id_avatar">
<img src="/static/img/default.png" id="avatar-img">
</label>
<input type="file" name="avatar" id="id_avatar" style="display: none"> {# 校验状态 #}
<span id="helpBlock2" class="help-block"></span>
</div>
</div> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" class="btn btn-success" id="reg-submit">注册</button>
</div>
</div>
</form>
</div>
</div>
</div> {#导入JS文件#}
<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script> <script>
// 找到头像的input标签绑定change事件
$("#id_avatar").change(function () {
// 1. 创建一个读取文件的对象
var fileReader = new FileReader();
// 取到当前选中的头像文件
// console.log(this.files[0]);
// 读取你选中的那个文件
fileReader.readAsDataURL(this.files[0]); // 读取文件是需要时间的
fileReader.onload = function () {
// 2. 等上一步读完文件之后才 把图片加载到img标签中
$("#avatar-img").attr("src", fileReader.result);
};
});
// AJAX提交注册数据
$('#reg-submit').click(function () {
//alert(123);
{#// 取得用户注册数据,向后端提交#}
//var username = $("#id_username").val();
//var password = $("#id_password").val();
//var re_password = $("#id_re_password").val();
//var email = $("#id_email").val();
// 文件类型必须使用FormData()对象
var formData = new FormData();
formData.append("username", $("#id_username").val());
formData.append("password", $("#id_password").val());
formData.append("re_password", $("#id_re_password").val());
formData.append("email", $("#id_email").val());
//提交图片数据
formData.append("avatar", $("#id_avatar")[0].files[0]);
formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val()); $.ajax({
url: "/reg/",
type: "post",
// AJAX传带文件必须设置,下面的两个参数
processData: false,
contentType: false,
data: formData,
//data: {
// 提交的数据
// username: username,
// password: password,
// re_password: re_password,
// email: email,
// csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
//},
success: function (data) {
if (data.status) {
//有错误展示错误
//console.log(data.msg);
//将错误信息填写到页面上,用each循环
$.each(data.msg, function (k, v) {
//console.log("id_"+ k,v[0]);
//找到id为id_+k的input标签,下的span标签,设置文本内容为v[0]
$("#id_" + k).next("span").text(v[0]);
//设置文本的样式为has-error
$("#id_" + k).parent().parent().addClass("has-error"); })
} else {
//没有错误,跳转指定页面
location.href = data.msg;
} }
}) }); // 将所有input框,绑定焦点事件,清空所有错误信息
$("form input").focus(function () {
// 清空内容
$(this).next().text("");
$(this).next().text("").parent().parent().removeClass("has-error") })
</script> </body>
</html>

 

....

3.API

# 根据爬虫数据库数据,生成models.py

python manage.py inspectdb

  

安装

pip install djangorestframework

  

 

<Django>博客项目的更多相关文章

  1. Django——博客项目

    博客项目 目前的目标是构建一个基于Django的前后端完整的博客系统,首先对项目流程整理如下: 1. 分析需求 1.1. 基于用户认证组件和Ajax实现登录验证 图形验证码核心代码: 模板: < ...

  2. django博客项目8:文章详情页

    首页展示的是所有文章的列表,当用户看到感兴趣的文章时,他点击文章的标题或者继续阅读的按钮,应该跳转到文章的详情页面来阅读文章的详细内容.现在让我们来开发博客的详情页面,有了前面的基础,开发流程都是一样 ...

  3. django博客项目5:博客首页视图(2)

    真正的 Django 博客首页视图 在此之前我们已经编写了 Blog 的首页视图,并且配置了 URL 和模板,让 Django 能够正确地处理 HTTP 请求并返回合适的 HTTP 响应.不过我们仅仅 ...

  4. django博客项目3:创建 Django 博客的数据库模型

    设计博客的数据库表结构 博客最主要的功能就是展示我们写的文章,它需要从某个地方获取博客文章数据才能把文章展示出来,通常来说这个地方就是数据库.我们把写好的文章永久地保存在数据库里,当用户访问我们的博客 ...

  5. django博客项目2.建立 Django 博客应用

    建立博客应用 我们已经建立了 Django 博客的项目工程,并且成功地运行了它.不过到目前为止这一切都还只是 Django 为我们创建的项目初始内容,Django 不可能为我们初始化生成博客代码,这些 ...

  6. django博客项目1.环境搭建

    安装 Python Windows 下安装 Python 非常简单,去 Python 官方网站找到 Python 3 的下载地址,根据你的系统选择 32 位或者 64 位的安装包,下载好后双击安装即可 ...

  7. 9.28 Django博客项目(一)

    2018-9-28 17:37:18 今天把博客项目 实现了注册和添加图片的功能! 放在了自己的github上面 源码! https://github.com/TrueNewBee/bbs_demo ...

  8. django博客项目6:Django Admin 后台发布文章

    在此之前我们完成了 Django 博客首页视图的编写,我们希望首页展示发布的博客文章列表,但是它却抱怨:暂时还没有发布的文章!如它所言,我们确实还没有发布任何文章,本节我们将使用 Django 自带的 ...

  9. django博客项目4:博客首页视图(1)

    Web 应用的交互过程其实就是 HTTP 请求与响应的过程.无论是在 PC 端还是移动端,我们通常使用浏览器来上网,上网流程大致来说是这样的: 我们打开浏览器,在地址栏输入想访问的网址,比如 http ...

随机推荐

  1. ElasticSearch Roaring bitmap 和跳表联合查询

    ElasticSearch Roaring map 先把所有数按65535划分, 划分方法就是求商和余数,商代表数字最终在哪一块,余数代表最终在块内的数字 比如 1, 65536, 65537, 13 ...

  2. 获取客户端IP地址-----以及--------线上开启redis扩展

    /** * 获取客户端IP地址 * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 * @return mixed */ function get_cl ...

  3. 二:unittest框架配合selenium之xpath定位

    刚开始学习selenium自动化测试时,犯了一个不该犯的错误,偷懒,使用火狐浏览器中的扩展FIREBUG,FIREPATH来辅助定位. 虽然用的定位方法大多数是使用XPATH方法,但是是工具定位出来的 ...

  4. vs2012+wdk8.0 搭建wdf驱动开发环境

    开发环境搭建: 系统:win7 x64 工具:vs2012 + WDK8.0 插件:wdfcoinstaller.msi (1)先安装vs2012,再安装wdk8.0,这样在打开vs2012时可以创建 ...

  5. HDU 6697 Closest Pair of Segments (计算几何 暴力)

    2019 杭电多校 10 1007 题目链接:HDU 6697 比赛链接:2019 Multi-University Training Contest 10 Problem Description T ...

  6. 爬虫抓取5大门户网站和电商数据day1:基础环境搭建

    最新想用爬虫实现抓取五大门户网站(搜狐.新浪.网易.腾讯.凤凰网)和电商数据(天猫,京东,聚美等), 今天第一天先搭建下环境和测试. 采用maven+xpath+ HttpClient+正则表达式. ...

  7. PS--工具类

    1.移动工具 快捷点Vctrl + 点击想要移动的图层.选中后,就可以移动了. 2.选取工具 快捷键M2.1选取后填充颜色:新建图层 选取 右键填充 添加前景色 2.2两块选区选择一个选区后,属性面板 ...

  8. android webview 输入法键盘遮挡输入框的问题

    新建一个工具类: /** * 解决webView键盘遮挡问题的类 * Created by zqy on 2016/11/14. */ public class KeyBoardListener { ...

  9. CentOS7.6编译安装Python-3.7.4

    安装步骤 1. 下载安装包.wget https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz<说明>如果报SSL/TSL错误,则加 ...

  10. vue实现京东动态楼层效果

    页面效果如下 <template> <div> <h1>首页</h1> <section class="floor-nav" ...