## BBS(仿造博客园项目) ### 项目开发基本流程 ```python 1.需求分析 2.架构设计 3.分组开发 4.提交测试 5.交付上线 ``` ### 项目流程 ```python 仿造博客园项目 核心:文章的增删改查 表分析 先确定表的数量 再确定表的基础字段 最后确定表的外键字段 1.用户表 2.个人站点表 3.文章表 4.文章分类表 5.文章标签表 6.点赞点踩表 7.文章评论表 基础字段分析 '''下列表字段设计仅供参考 你可以有更多的想法''' 用户表 替换auth_user表并扩展额外的字段 电话号码、头像、注册时间 个人站点表 站点名称(jason\lili\kevin) 站点标题(努力奋斗去他妹的) 站点样式(css文件) 文章表 文章标题 文章简介 文章内容 发布时间 文章分类表 分类名称 文章标签表 标签名称 点赞点踩表:记录哪个用户给哪篇文章点了推荐(赞)还是反对(踩) 用户字段(用户主键)>>>:外键字段 文章字段(文章主键)>>>:外键字段 点赞点踩 文章评论表:记录哪个用户给哪篇文章评论了什么内容 用户字段(用户主键)>>>:外键字段 文章字段(文章主键)>>>:外键字段 评论内容 评论时间 外键字段(自关联) """ id user_id article_id content parent_id 1 1 1 哈哈哈 null 2 2 1 哈你妹 1 3 3 1 讲文明 2 """ 评论点赞点踩表:记录哪个用户给那篇文章的哪条评论点赞或点踩 用户字段(用户主键)>>>:外键字段 文章字段(文章主键)>>>:外键字段 评论字段(评论主键)>>>:外键字段 点赞点踩 外键字段 用户表 用户与个人站点是一对一外键关系 个人站点表 文章表 文章表与个人站点表是一对多外键关系 文章表与文章分类表是一对多外键关系 文章表与文章标签表是多对多外键关系 ''' 数据库字段优化设计:我们想统计文章的评论数 点赞数 通过文章数据跨表查询到文章评论表中对应的数据统计即可 但是文章需要频繁的展示 每次都跨表查询的话效率极低 我们在文章表中再创建三个普通字段 之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三 个普通字段即可 ''' 文章评论数 文章点赞数 文章点踩数 文章分类表 文章分类与个人站点是一对多外键关系 文章标签表 文章标签与个人站点是一对多外键关系 ```  ### 注册功能 ```python 用户注册 1.渲染前端标签 2.校验用户数据 3.展示错误提示 ps:forms组件、modelform组件 单独开设py文件编写 解耦合!!! ``` ### 登录功能 ```python img标签的src属性 1.可以直接填写图片地址 2.还可以填写一个路由 会自动朝该路由发送get请求 如果结果是图片的二进制数据 那么自动渲染图片 pip install pillow -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com ``` ## 创建表相关代码 ```python from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): """用户表,进行扩展,自定义字段""" phone = models.CharField(max_length=32, verbose_name='手机号', null=True) avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='头像') register_time = models.DateTimeField(auto_now_add=True, verbose_name='注册时间') """外键字段""" """用户表与个人站点表一对一关系""" site = models.OneToOneField(to='Site', on_delete=models.CASCADE, null=True) class Site(models.Model): """个人站点表""" site_name = models.CharField(max_length=32, verbose_name='站点名称') site_title = models.CharField(max_length=32, verbose_name='站点标题') site_theme_css = models.TextField(verbose_name='站点css', null=True) site_theme_js = models.TextField(verbose_name='站点js', null=True) site_theme_html = models.TextField(verbose_name='站点html', null=True) site_publish_info = models.TextField(verbose_name='公告', null=True) class Article(models.Model): """文章表""" title = models.CharField(max_length=32, verbose_name='文章标题') summary = models.TextField(verbose_name='文章摘要') content = models.TextField(verbose_name='文章内容') publist_time = models.DateTimeField(auto_now_add=True, verbose_name='文章发布时间') """数据库字段优化""" """因为经常要统计下面字段数量,虽然跨表可以查询,但是浪费数据库资源,所有在进行数据增加或删除数据,对下面字段进行数据的修改 以达到,减少数据库资源消耗,而进行优化的目的 """ up_num = models.IntegerField(verbose_name='点赞数', default=0) down_num = models.IntegerField(verbose_name='点踩数', default=0) comment_num = models.IntegerField(verbose_name='评论数', default=0) """外键字段""" site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True) classify = models.ForeignKey(to='Classify', on_delete=models.CASCADE, null=True) """多对多字段,一个文章可以有很多标签,一个标签页可以有很多文章 采用半自动创建多对多 """ labels = models.ManyToManyField(to='Label', through='Article2Label', through_fields=('article', 'label')) class Article2Label(models.Model): """多对多手动第三张表,后期可以扩展字段""" article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True) label = models.ForeignKey(to='Label', on_delete=models.CASCADE, null=True) class Classify(models.Model): """文章分类表""" name = models.CharField(max_length=32, verbose_name='分类名称') site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True) class Label(models.Model): """文章标签表""" name = models.CharField(max_length=32, verbose_name='文章标签表') site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True) class UpAndDownArticle(models.Model): """文章点赞点踩表""" """记录:哪个用户给那篇文章点赞或点踩""" user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE) # 外键字段关联userinfo表主键 article = models.ForeignKey(to='Article', on_delete=models.CASCADE) # 外键字段 is_up = models.BooleanField(verbose_name='点赞点踩') # 写True False 存 1 0 class Comment(models.Model): """文章评论表""" """记录:哪个用户给那片文章评论的内容与时间""" user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE) article = models.ForeignKey(to='Article', on_delete=models.CASCADE) content = models.TextField(verbose_name='评论内容') comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间') """评论显示什么浏览器,以及评论者的ip""" user_agent = models.CharField(max_length=64, verbose_name='用户评论浏览器') user_ip = models.TextField(verbose_name='用户ip/ipv4/ipv6') """自关联字段""" """在有时侯,某些字段需要关联所在表的数据就需要使用到自关联字段 id user content parent 1 1 哈哈哈 null 2 2 不要 1 3 3 你怎么管这么多 2 """ parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True) """数据库字段优化""" up_num = models.IntegerField(verbose_name='评论被点赞数', default=0) down_num = models.IntegerField(verbose_name='评论被点踩', default=0) class UpAndDownComment(models.Model): """评论点赞点踩表""" """记录:哪个用户给那篇文章里的哪个评论点赞或点踩""" user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE) # 外键字段关联userinfo表主键 article = models.ForeignKey(to='Article', on_delete=models.CASCADE) # 外键字段 comment = models.ForeignKey(to='Comment', on_delete=models.CASCADE) is_up = models.BooleanField(verbose_name='点赞点踩') # 写True False 存 1 0 ``` ## 注册功能相关代码 ### 注册form组件 ```python from django import forms from django.forms import widgets from BBS import models class Register_from(forms.Form): """用户注册forms类""" # validators正则匹配校验用的 required内容为空用的 username = forms.CharField(max_length=16, min_length=6, label='用户名', error_messages={ 'max_length': '用户名最长为16位', 'min_length': '用户名最短为6位', 'required': '用户名不能为空' }, widget=widgets.TextInput(attrs={'class': 'form-control'}) ) password = forms.CharField(max_length=16, min_length=6, label='密码', error_messages={ 'max_length': '密码最长为16位', 'min_length': '密码最短为6位', 'required': '密码不能为空' }, widget=widgets.PasswordInput(attrs={'class': 'form-control'})) confirm_password = forms.CharField(max_length=16, min_length=6, label='确认密码', error_messages={ 'max_length': '密码最长为16位', 'min_length': '密码最短为6位', 'required': '密码不能为空' }, widget=widgets.PasswordInput(attrs={'class': 'form-control'})) email = forms.EmailField(label='邮箱', error_messages={ 'required': '邮箱不能为空' }, widget=widgets.EmailInput(attrs={'class': 'form-control'}) ) def clean_username(self): """局部钩子校验用户名是否已存在""" username = self.cleaned_data.get('username') user_obj = models.UserInfo.objects.filter(username=username) if user_obj: 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 ``` ## 注册视图类相关代码 项目使用模块 ```python from django.shortcuts import render, HttpResponse, redirect, reverse from django.http import JsonResponse # Create your views here. from django.views import View from BBS.myforms.register import Register_from from django.contrib import auth # 校验模块 from django.contrib.auth.decorators import login_required # 登录装饰器 from django.views.decorators.csrf import csrf_exempt, csrf_protect # 取消或者校验csrf from django.utils.decorators import method_decorator # 给视图类添加装饰器用的 from django.db.models import Q, F # 跟查询相关 ``` 后端代码 ```python class Register_class(View): """用户注册视图类""" def get(self, request): form_obj = Register_from() return render(request, 'bbs/registerPage.html', locals()) def post(self, request): re_dict = { 'code': 10000, 'msg': '' } print(request.POST, request.FILES) form_obj = Register_from(request.POST) if form_obj.is_valid(): clean_data = form_obj.cleaned_data clean_data.pop('confirm_password') file_obj = request.FILES.get('avatar') if file_obj: clean_data['avatar'] = file_obj models.UserInfo.objects.create_user(**clean_data) re_dict['url'] = reverse('login_url') else: re_dict['code'] = 10001 re_dict['msg'] = form_obj.errors return JsonResponse(re_dict) ``` html代码 ```python Title {% load static %} 用户注册 {% csrf_token %} {% for form in form_obj %} {{ form.label }} {{ form }} {% endfor %} 头像 ``` ## 登录功能 后端代码 ```python def login_func(request): """用户登录功能""" if request.method == 'POST': re_dict = { 'code': 10000, 'msg': '' } captcha: str = request.POST.get('captcha') username = request.POST.get('username') password = request.POST.get('password') if request.session.get('captcha').lower() == captcha.lower(): user_obj = auth.authenticate(request, username=username, password=password) if user_obj: auth.login(request, user_obj) re_dict['url'] = reverse('home_url') else: re_dict['code'] = 10001 re_dict['msg'] = { 'password': '用户名或密码错误' } else: re_dict['code'] = 10002 re_dict['msg'] = { 'captcha': '验证码错误', } if not username: re_dict['msg']['username'] = '用户名不能为空' if not password: re_dict['msg']['password'] = '密码不能为空' return JsonResponse(re_dict) return render(request, 'bbs/loginPage.html', locals()) ``` 前端代码 ```python Title {% load static %} 用户登录 {% csrf_token %} 用户名 密码 验证码 看不清?点击换一张 ``` ## 验证码功能代码 后端代码 ```python from PIL import Image, ImageFont, ImageDraw from io import BytesIO, StringIO # StringIO字符串内存管理对象 BytesIO字节内存管理对象 import random def get_random_color(): """获取随机rgb颜色""" return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) def captcha_func(request): """生成验证码存储到session中,返回验证码图片""" imgobj = Image.new('RGB', (260, 35), get_random_color()) # 生成一个背景图片 imgfont = ImageFont.truetype(r'static/font/云峰静龙行书.ttf', size=32) # 生成字体 imgdraw = ImageDraw.ImageDraw(imgobj) # 生成一个画笔对象 code = '' for num in range(5): """剩余5位数验证吗""" choice_big = chr(random.randint(65, 90)) choice_small = chr(random.randint(97, 122)) choice_int = str(random.randint(0, 9)) choice_code = random.choice([choice_big, choice_small, choice_int]) imgdraw.text((num * 40 + 40, -3), choice_code, get_random_color(), imgfont) # 在图像上写字 code += choice_code io_obj = BytesIO() # 生成字节内存管理对象 imgobj.save(io_obj, 'png') # 把图像保存到内存管理对象 request.session['captcha'] = code request.session.set_expiry(60 * 5) return HttpResponse(io_obj.getvalue()) ``` Last modification:February 9th, 2023 at 10:20 pm © 允许规范转载 Support 如果觉得我的文章对你有用,请随意赞赏 ×Close Appreciate the author Sweeping payments
Comment here is closed