最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

bbs项目(部分讲解)

来源:博客园

项目开发基本流程

  1. 需求分析
  2. 架构设计
  3. 分组开发
  4. 提交测试
  5. 交付上线

项目分析流程

仿BBS项目:

  • 仿造博客园项目
    • 核心:文章的增删改查
  • 技术栈 DjangoMySQL
  • 功能
    • 注册 (forms校验,页面渲染,上传头像)
    • 登录 (自定义图片验证码)
    • 首页:文章展示、侧边栏过滤(分类,标签,时间)
    • 文章详情:点赞点踩、评论(父评论和子评论)
    • 后台管理:当前用户文章展示(文章增删改查)
    • 发布文章
  • 项目版本信息:python3.8django2.2.2mysql:5.7jquery2.xbootstrap3

项目表设计及关联

创建数据库bbs

create database bbs

表分析

一共需要创建七张表

  1. 用户表(基于auth模块的user表扩写)
  2. 个人站点表(跟用户表一对一关系)
  3. 分类表(和个人站点表一对多、和文章表一对多)
  4. 标签表(和个人站点表一对多、和文章表多对多)
  5. 点赞点踩表(和用户表一对多、和文章表一对多)
  6. 评论表(和用户表一对多,和文章表一对多)
  7. 文章表(和个人站点表一对多)

前期准备

创建项目

1.安装django 2.2.2版本


(资料图)

pip3 install django==2.2.2

2.使用pycharm创建django项目

配置setting.py

TEMPLATES = {"DIRS":[os.path.joi(BASE_DIR, "templates")]}

配置语言环境

LANGUAGE = "zh-hans"  # 语言汉化TIME-ZONE = "Asia/Shanghai"  # 时区使用上海时区USE_I18N = TrueUSE_L10N = TrueUSE_TZ = False

配置数据库

DATABASES = {"default": {"ENGINE":"django.db.backends.mysql","NAME": "bbs","HOST": "127.0.0.1","PORT": 3306,"USER": "root","PASSWORD":"password"}}

在models中写表模型

from django.db import models# Create your models here.from django.contrib.auth.models import AbstractUserclass UserInfo(AbstractUser):  # 继承AbstractUser表 只用写auth表中没有的字段    phone = models.CharField(max_length=32, null=True, verbose_name="用户手机号")    # upload_to是文件保存在什么路径    icon = models.FileField(upload_to="icon/", default="icon/default.png", null=True, verbose_name="用户头像")    # 用户表和博客表一对一    blog = models.OneToOneField(to="Blog", on_delete=models.CASCADE, null=True)class Blog(models.Model):    title = models.CharField(max_length=32, null=True, verbose_name="主标题")    site_title = models.CharField(max_length=32, null=True, verbose_name="副标题")    site_style = models.CharField(max_length=64, null=True, verbose_name="站点样式")class Tag(models.Model):    name = models.CharField(max_length=32, verbose_name="标签名", null=True)    # 标签和博客是一对多 一个博客有多个标签    blog = models.ForeignKey(to="Blog", on_delete=models.CASCADE)class Classify(models.Model):    name = models.CharField(max_length=32, verbose_name="分类名")    # 分类和博客是一对多关系 一个博客有多个分类    blog = models.ForeignKey(to="Blog", on_delete=models.CASCADE)class Article(models.Model):    title = models.CharField(max_length=32, verbose_name="文章标题")    desc = models.CharField(max_length=255, verbose_name="文章摘要")    content = models.TextField(verbose_name="文章内容")    create_time = models.DateTimeField(auto_now_add=True)  # 第一次创建时自动添加时间    # 文章和分类表是一对多 一个分类有多篇文章    classify = models.ForeignKey(to="Classify", on_delete=models.CASCADE)    # 文章和标签是多对多关系 自动创建第三张表    tag = models.ManyToManyField(to="Tag")    # 文章和博客是一对多关系 一个博客对应多篇文章    blog = models.ForeignKey(to="Blog", on_delete=models.CASCADE)class UpAndDown(models.Model):    create_time = models.DateTimeField(auto_now_add=True, verbose_name="点赞点踩时间")    # 和用户表是一对多关系 一个用户可以有多条点赞点踩记录    user = models.ForeignKey(to="UserInfo", on_delete=models.CASCADE)    # 和文章也是一对多    article = models.ForeignKey(to="Article", on_delete=models.CASCADE)    # 1代表点赞 0代表点踩    is_up = models.BooleanField(verbose_name="是否点赞")class Comment(models.Model):    content = models.CharField(max_length=64, verbose_name="评论内容")    create_time = models.DateTimeField(auto_now_add=True, verbose_name="评论时间")    user = models.ForeignKey(to="UserInfo", on_delete=models.CASCADE, null=True)    article = models.ForeignKey(to="Article", on_delete=models.CASCADE, null=True)    # 自关联字段 只能存已有评论的主键值    parent = models.ForeignKey("self", on_delete=models.CASCADE, null=True)    # 自关联的其他方式    # parent = models.ForeignKey(to="Comment", on_delete=models.CASCADE)    # parent = models.IntegerField(null=Ture)

终端执行数据库迁移命令

python38 manage.py makemigretionspython38 manage.py migrater

注册功能

注册forms编写

在根目录下创建blog_forms.py文件

from django import formsfrom django.forms import widgetsfrom blog.models import UserInfofrom django.core.exceptions import ValidationError  # 合法性错误class User(forms.Form):    # 用户名 密码 确认密码 邮箱    username = forms.CharField(max_length=8, min_length=3, label="用户名", required=True,                               error_messages={"max_length": "用户名最多只能输入8位",                                               "min_length": "用户名最少输入3位",                                               "required": "用户名必须填"                                               },                               # 添加bootstr样式                               widget=widgets.TextInput(attrs={"class": "form-control"})                               )    password = forms.CharField(max_length=16, min_length=8, required=True, label="密码",                               error_messages={                                   "max_length": "密码最长16位",                                   "min_length": "密码最短8位",                                   "required": "密码不能为空",                               },                               widget=widgets.PasswordInput(attrs={"class": "form-control"})                               )    re_password = forms.CharField(max_length=16, min_length=8, required=True, label="密码",                                  error_messages={                                      "max_length": "密码最长16位",                                      "min_length": "密码最短8位",                                      "required": "密码不能为空",                                  },                                  widget=widgets.PasswordInput(attrs={"class": "form-control"})                                  )    email = forms.EmailField(label="邮箱地址", widget=widgets.EmailInput(attrs={"class": "form-control"}))    # 局部钩子 校验用户名是否存在    def clean_username(self):        name = self.cleaned_data.get("username")        if UserInfo.objects.filter(username=name).first():            # 用户已存在            raise ValidationError("用户名已存在")  # 校验错误抛出异常        else:            return name    # 局部钩子 校验用户名是否存在    # def clean_username(self):    #     username = self.cleaned_data.get("username")    #     try:    #         UserInfo.objects.get(username=username)    #         print(UserInfo.objects.get(username=username), type(UserInfo.objects.get(username=username)))    #         raise ValidationError("用户名已存在")    #     except Exception:    #         return username    # 全局钩子 校验两次输入密码是否一致    def clean(self):        pwd = self.cleaned_data.get("password")        re_pwd = self.cleaned_data.get("re_password")        if pwd != re_pwd:            raise ValidationError("两次密码不一致")  # 主动抛出合法性错误        else:            return self.cleaned_data

路由配置

在项目同名文件夹下的urls.py中配置路由

from django.contrib import adminfrom django.urls import pathfrom blog import viewsurlpatterns = [    path("admin/", admin.site.urls),    path("register/", views.register),]

编写视图函数

views.py

from django.shortcuts import renderfrom blog.blog_forms import Userdef register(request):    form_obj = User()    if request.method == "GET":  # 当请求为get时返回注册界面,并返回forms组件对象进行数据校验        return render(request, "register.html", {"form_obj": form_obj})

前端模板编写

register.html

需要先配置静态文件-在setting.py中STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] -把bootstrap和jquery导入模板中

注册功能

{% csrf_token %} {% for foo in form_obj %}
{{ foo }}
{% endfor %}

头像动态显示

<script>    // 头像动态显示    $("#id_file").change(function () {        // 将上传的头像展示到img标签内 修改img标签内的src参数        // 读出图片文件 借助于文件阅读器        let reader = new FileReader()        // 拿到文件对象        let file = $("#id_file")[0].files[0]        // 将文件对象读到文件阅读器中        reader.readAsDataURL(file)        // 文件加载完后修改img标签的src参数        reader.onload = function () {            // $("#id_img")[0].src=reader.result            $("#id_img").attr("src", reader.result)  # jquery对象方法        }    })</script>

发送ajax请求

// 发送ajax请求    $("#id_submit").click(function () {        let data = new FormData  // 可以传递文件数据        // 方式一:根据id获取标签数据添加至data中                // data.append("username", $("#id_username").val())        // data.append("password", $("#id_password").val())        // data.append("re_password", $("#id_re_password").val())        // data.append("email", $("#id_email").val())        // data.append("icon", $("#id_file")[0].files[0])        // data.append("csrfmiddlewaretoken", $("[name="csrfmiddlewaretoken"]").val())        // ...发送ajax请求        // 方式二:利用form组件批量处理        let data_arr = $("#id_form").serializeArray()  // 序列化数组        console.log(data_arr)  // 是一个数组套对象 对象中k是name v是value 自动添加csrf        // 使用for循环把数据添加到data对象中        $.each(data_arr, function (i, v) {            console.log("index:",i)            console.log("value:", v)            console.log("-----------------------")            data.append(v.name, v.value)        })        // 文件需要单独放入        data.append("icon", $("#id_file")[0].files[0])        // 使用ajax发送请求        $.ajax({            url: "/register/",            type: "post",            data: data,            processData: false,            contentType: false,            success: function (data) {}

打印结果

现在后端可以收到数据 继续写后端

views.py

def register(request):    form_obj = User()    if request.method == "GET":        return render(request, "register.html", {"form_obj": form_obj})    else:  # 当发送post请求        res = {"code": 100, "msg": "注册成功"}        forms_obj = User(data=request.POST)  # forms组件检验        if forms_obj.is_valid():  # 如果数据全部合法            register_data = forms_obj.cleaned_data  # 拿出所有的合法数据            register_data.pop("re_password")  # 弹出二次输入密码 因为用户表中不需要改字段            if request.FILES.get("icon"):  # 判断是否上传了图片文件                register_data["icon"] = request.FILES.get("icon")  # 上传了的话就添加进去            # 一定要用create_user 密码是密文 后面才可以使用auth模块的功能            UserInfo.objects.create_user(**register_data)  # 将register_data打散保存至数据库            return JsonResponse(res)  # 注册成功返回信息        else:  # 弱国数据不是全部合法            res["code"] = 101            res["msg"] = "注册失败"            res["errors"] = forms_obj.errors  # 返回错误信息            return JsonResponse(res)

前端ajax可以接受到后端返回的json字符串

$.ajax({     url: "/register/",     type: "post",     data: data,     processData: false,     contentType: false,     success: function (data) {         console.log(data)         if (data.code === 100) {             // 注册成功跳转至登录界面             location.href = "/login/"         } else {             // 在前端渲染出错误信息             console.log(data)             $.each(data.errors, function (k, v) {// for循环错误字典                 if (k === "__all__") {                     // 全局钩子错误 两次密码不一致                     $(".error").html(data.errors["__all__"][0])                 } else {                     // 其他错误找到相应的input框后的span标签渲染 父类标签加上has-error属性变红                     $("#id_" + k).next().html(v[0]).parent().addClass("has-error")                 }             })         }     } })

打印结果

此时的错误提示信息不会消失 需要绑定一个定时任务

// 定时任务 渲染的错误信息三秒后清除setTimeout(function () {    // 把所有的span标签的内容清除 父类中的属性has-error去除    $(".text-danger").html("").parent().removeClass("has-error")}, 3000)

校验用户是否存在

需求:当用户输入用户名后鼠标离开用户名框,校验用户名是否存在且不能刷新页面

前端

<script>// 后端ajax校验用户名是否存在    // 前端使用get请求传入用户名        // 绑定一个失去焦点事件    $("#id_username").blur(function () {        $.ajax({            url: "/check_name/?name=" + $("#id_username").val(),            type: "get",            success: function (data) {                if (data.code === 110) {// 当用户名存在 添加提示信息                    $("#id_username").next().html(data.msg)                }else {// 当用户不存在时清除提示信息                    $("#id_username").next().html("")                }            }        })    })</script>

后端

urls.py

path("check_name/", views.check_name),

views.py

def check_name(request):    # print(request.GET)    res = {"msg": "用户已存在", "code": 110}    name = request.GET.get("name")    obj = UserInfo.objects.filter(username=name).first()    if obj:        return JsonResponse(res)    else:        res["code"] = 100        res["msg"] = "用户不存在"        return JsonResponse(res)

登录功能

登陆界面搭建

注册成功跳转至/login/,创建login.html

        Title    <script src="/static/jQuery.js"></script>        <script src="/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>

登录功能

{% csrf_token %}
<script> // 点击验证码图片刷新验证码 $("#id_img").click(function () { let time = new Date().getTime() console.log(time) // 再次获取随机验证码图片 $("#id_img")[0].src = "/get_code/?t=" + time }) // 提交ajax $("#id_submit").click(function () { // 将form表单的input标签数据序列化成数组套对象 name value dataArray = $("#id_form").serializeArray() $.ajax({ url: "/login/", type: "post", data: dataArray, success: function (data) { console.log(data) if(data.code===100){ location.href = "/" }else { $(".error").html(data.msg) } } }) }) // 定时器任务 自动关闭错误提示信息 let test = function () { $(".error").html("") } // 可重复关闭 timer = setInterval(test, 2000)//60秒后关闭循环定时任务 setTimeout(function () { clearTimeout(timer) },60*1000) </script>

自定义图片验证码

验证码:字母数字共五位

views.py

from PIL import Image, ImageDraw, ImageFontfrom io import BytesIOimport randomdef get_code(request):# 1 生成一张图片 pillow模块img = Image.new("RGB", (350, 50), color=(255, 255, 255))# 2 生成一个画图对象 将img传入draw = ImageDraw.Draw(img)# 3 生成字体对象font = ImageFont.truetype(font="./static/font/1641263938811335.ttf", size=50)# 4 生成随机字符串ran_str = ""for i in range(5):ran_num = str(random.randint(0, 9))ran_upper = chr(random.randint(65,90))# 去除I和Lwhile (ran_upper == "L" or ran_upper == "I"):ran_upper = chr(random.randint(65,90))ran_lower = chr(random.randint(97, 122))# 去除i和lwhile (ran_lower == i or ran_lower == l):ran_lower = chr(random.randint(97, 122))res = random.choice([ran_num , ran_upper, ran_lower])# 将生成的随机字符画到图片中# fill=get_color 字体颜色也随机draw.text(xy=(10 + i * 60, 0), text=res, font=font, fill=get_color())# 5 画线    for i in range(10):        draw.line([(random.randint(0, 350), random.randint(0, 50)), (random.randint(0, 350), random.randint(0, 50))],                  fill=get_color())  # 起点和终点    # 6 画点    for i in range(100):        draw.point((random.randint(0, 350), random.randint(0, 50)), fill=get_color())# 7 将图片保存在内存中 BytesIo模块 并返回给前端byte_io = BytesIo()img.save(fp=byte_io, format="png")# 怎样校验前端传过来的验证码?# 可以存在session表中 前端访问返回给前端 前端再次访问携带session 后端取出data进行校验request.session["code"] = resreturn HttpResponse(byte_io.getvalue())def get_color():x, y = 0, 255return (random.randint(x, y), random.randint(x, y), random.randint(x, y))

上面是自定义的图片验证码,也可以使用第三方模块,比如gvcode模块

from gvcode import VFCode"""使用方法:vc = VFCode(        width=200,                       # 图片宽度        height=80,                       # 图片高度        fontsize=50,                     # 字体尺寸        font_color_values=[            "#ffffff",            "#000000",            "#3e3e3e",            "#ff1107",            "#1bff46",            "#ffbf13",            "#235aff"        ],                                # 字体颜色值        font_background_value="#ffffff",  # 背景颜色值        draw_dots=False,                  # 是否画干扰点        dots_width=1,                     # 干扰点宽度        draw_lines=True,                  # 是否画干扰线        lines_width=3,                    # 干扰线宽度        mask=False,                       # 是否使用磨砂效果        font="arial.ttf"                  # 字体 内置可选字体 arial.ttf calibri.ttf simsun.ttc    )    # 验证码类型    # 自定义验证码    # vc.generate("abcd")    # 数字验证码(默认5位)    # vc.generate_digit()    # vc.generate_digit(4)    # 字母验证码(默认5位)    # vc.generate_alpha()    # vc.generate_alpha(5)    # 数字字母混合验证码(默认5位)    # vc.generate_mix()    # vc.generate_mix(6)    # 数字加减验证码(默认加法)    vc.generate_op()    # 数字加减验证码(加法)    # vc.generate_op("+")    # 数字加减验证码(减法)    # vc.generate_op("-")    # 图片字节码    # print(vc.get_img_bytes())    # 图片base64编码    print(vc.get_img_base64())    # 保存图片    vc.save()"""def get_code(request):    vc = VFCode(width=350, height=50)    vc.generate_mix()    # vc.generate_op()    print(vc.get_img_base64()[0])    byte_io = BytesIO()    vc.save(byte_io, fm="png")    request.session["code"] = vc.get_img_base64()[0]    return HttpResponse(byte_io.getvalue())

登陆界面前端发送数据

login.html

<script>// 提交ajax$("#id_submit").click(function () {     let dataArray = $("#id_form").serializeArray()     console.log(dataArray)     $.ajax({         url: "/login/",         type: "post",         data: dataArray,         success: function (data) {             console.log(data)             if(data.code===100){              // 登陆成功 去首页                 location.href = "/"             }else {              // 登陆失败 显示错误信息                 $(".error").html(data.msg)             }         }     }) })// 计时器 关闭错误提示let test = function () {$(".error").html("")}// 循环执行timer = setInterval(test, 2000)//60秒后关闭循环定时任务setTimeout(function () {clearTimeout(timer)},60*1000)            </script>

登录后端

def login(request):    if request.method == "GET":        return render(request, "login.html")    res = {"code": 100, "msg": "登陆成功"}    code = request.POST.get("code")    # 校验验证码    if request.session.get("code").lower() == code.lower():        username = request.POST.get("username")        password = request.POST.get("password")                # 如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。        obj = authenticate(username=username, password=password)        if obj:            return JsonResponse(res)        res["code"] = 110        res["msg"] = "用户名或密码错误"        return JsonResponse(res)    res["code"] = "120"    res["msg"] = "验证码错误"    return JsonResponse(res)

关键词: 是否存在 定时任务 动态显示