<Django>博客项目
0.项目的通用流程
- 项目立项
- 需求分析
- 原型
- 前端
- 页面设计
- UI及交互实现
- 后端
- 架构设计
- 数据库设计
- 代码模板实现
- 单元测试
- 网站整合
- 功能及集成测试
- 网站发布
1.BBS项目需求分析
- 需要哪些表
- UserInfo表
- username
- password
- avatar----头像
- 文章表
- title
- publish_date
- desc----摘要
- author
- 详细内容 一对一关联,文章详情表
- 文章详情表
- info
- 评论表
- user
- 评论时间
- 评论内容
- 评论和文章的关联关系(1个文章多个评论,1对多,写在多的那方)
- 是谁的子评论,是不是回复别人的评论
- 标签
- 标签名
- 标签名和文章,多对多
- 分类
- 分类名
- 分类和文章的关联关系,多对多或一对多
- 点赞
- 是赞还是踩
- 时间
- 谁点的 关联user
- 点的是哪个文章
- UserInfo表
2.BBS的注册功能
- 基于Form表单和Ajax的注册
form表单的作用:
- 生成HTML代码
- 验证
- 将验证的错误显示在页面上并保留原始的数据
创建项目和对应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>博客项目的更多相关文章
- Django——博客项目
博客项目 目前的目标是构建一个基于Django的前后端完整的博客系统,首先对项目流程整理如下: 1. 分析需求 1.1. 基于用户认证组件和Ajax实现登录验证 图形验证码核心代码: 模板: < ...
- django博客项目8:文章详情页
首页展示的是所有文章的列表,当用户看到感兴趣的文章时,他点击文章的标题或者继续阅读的按钮,应该跳转到文章的详情页面来阅读文章的详细内容.现在让我们来开发博客的详情页面,有了前面的基础,开发流程都是一样 ...
- django博客项目5:博客首页视图(2)
真正的 Django 博客首页视图 在此之前我们已经编写了 Blog 的首页视图,并且配置了 URL 和模板,让 Django 能够正确地处理 HTTP 请求并返回合适的 HTTP 响应.不过我们仅仅 ...
- django博客项目3:创建 Django 博客的数据库模型
设计博客的数据库表结构 博客最主要的功能就是展示我们写的文章,它需要从某个地方获取博客文章数据才能把文章展示出来,通常来说这个地方就是数据库.我们把写好的文章永久地保存在数据库里,当用户访问我们的博客 ...
- django博客项目2.建立 Django 博客应用
建立博客应用 我们已经建立了 Django 博客的项目工程,并且成功地运行了它.不过到目前为止这一切都还只是 Django 为我们创建的项目初始内容,Django 不可能为我们初始化生成博客代码,这些 ...
- django博客项目1.环境搭建
安装 Python Windows 下安装 Python 非常简单,去 Python 官方网站找到 Python 3 的下载地址,根据你的系统选择 32 位或者 64 位的安装包,下载好后双击安装即可 ...
- 9.28 Django博客项目(一)
2018-9-28 17:37:18 今天把博客项目 实现了注册和添加图片的功能! 放在了自己的github上面 源码! https://github.com/TrueNewBee/bbs_demo ...
- django博客项目6:Django Admin 后台发布文章
在此之前我们完成了 Django 博客首页视图的编写,我们希望首页展示发布的博客文章列表,但是它却抱怨:暂时还没有发布的文章!如它所言,我们确实还没有发布任何文章,本节我们将使用 Django 自带的 ...
- django博客项目4:博客首页视图(1)
Web 应用的交互过程其实就是 HTTP 请求与响应的过程.无论是在 PC 端还是移动端,我们通常使用浏览器来上网,上网流程大致来说是这样的: 我们打开浏览器,在地址栏输入想访问的网址,比如 http ...
随机推荐
- 软件测试工程师如何提高提BUG逼格
某个周四早上,沏好一杯茶,刚要坐到座位上,就听开发说,你们测试怎么提的Bug,给个截图能说明啥?截图上面显示的奔溃,如果是必现还好,如果不是必现,那么我们怎么去定位?至少给个日志吧?当时我的内心活动是 ...
- STL unique
1: template <class ForwardIterator> 2: ForwardIterator unique (ForwardIterator first, ForwardI ...
- 四(2)、springcloud之Ribbon负载均衡
2.Ribbon负载均衡 Ribbon在工作时分成两步第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server. 第二步再根据用户指定的策略,在从server取到的 ...
- 使用Python将字符串转换为格式化的日期时间字符串
我正在尝试将字符串“20091229050936”转换为“2009年12月29日(UTC)” >>>import time >>>s = time.strptime ...
- hdu6354 /// 线段树
题目大意: 给定n m x y z 长度为n的序列初始为0 接下来m个操作 l r v 将l r区间内比v小的数都变成v l r v由x y z和给定的函数生成 线段树维护区间 最大值 最小值 再加 ...
- Oracle之数据类型问题
做项目涉及到Oracle数据库中数据类型:字符串型的问题 我不太清楚varchar(32)到底代表着什么? 通过搜索了解到:oracle中有三种常用的类型:varchar2(byte),varchar ...
- Vue-cli开发笔记三----------引入外部插件
(一)绝对路径直接引入: (1)主入口页面index.html中头部script标签引入: <script type="text/javascript" src=" ...
- tf-idf 词条权重计算
在文本分类问题中,某些高频词一直出现,这样的词对区分文档的作用不大,例如: D1: 'Job was the chairman of Apple Inc.' D2: 'I like to use ...
- Codeforces Round #563 (Div. 2) E. Ehab and the Expected GCD Problem
https://codeforces.com/contest/1174/problem/E dp 好题 *(if 满足条件) 满足条件 *1 不满足条件 *0 ///这代码虽然写着方便,但是常数有点大 ...
- Spring Boot 2.X 实现文件上传(三)
使用 SpringBoot 项目完成单个.多个文件的上传处理,并将上传的文件保存到指定目录下. 代码演示案例 所有的 HTML 页面文件 index.html <!DOCTYPE html> ...