<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