BBS项目

BBS项目

数据表设计

1.用户表
    继承AbstractUser
        扩展
        phone 电话号码
        avatar  用户头像
        create_time  创建时间

    外键字段
        一对一个人站点表

2.个人站点表
    site_name 站点名称
    site_title 	 站点标题
    site_theme	站点样式

3.文章标签表
    name		标签名

    外键字段
   		一对多个人站点

4.文章分类表
    name		分类名

    外键字段
    	一对多个人站点

5.文章表
    title	文章标题
    desc	文章简介
    content	文章内容
    create_time 发布时间

    数据库字段设计优化(******)
    (虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率会很低)
    up_num					点赞数
    down_num				点踩数
    comment_num 			评论数

    外键字段
        一对多个人站点
        多对多文章标签
        一对多文章分类

		
	
6.点赞点踩表
    记录哪个用户给哪篇文章点了赞还是点了踩
    ForeignKey(to="User")				
    article					ForeignKey(to="Article")	
    is_up						BooleanField()

		
	
7.文章评论表
	记录哪个用户给哪篇文章写了哪些评论内容
    user						ForeignKey(to="User")				
    article					ForeignKey(to="Article")
    content					CharField()
    comment_time		DateField()
    # 自关联
    parent					ForeignKey(to="Comment",null=True)		
    # ORM专门提供的自关联写法	
    parent					ForeignKey(to="self",null=True)

		
根评论子评论的概念
	根评论就是直接评论当前发布的内容的
		
	子评论是评论别人的评论
		1.PHP是世界上最牛逼的语言
			1.1 python才是最牛逼的
				1.2 java才是
		
	根评论与子评论是一对多的关系

ORM源码(models.py)

from django.db import models
from django.contrib.auth.models import AbstractUser


# Create your models here.
class UserInfo(AbstractUser):
    phone = models.BigIntegerField(verbose_name='手机号', null=True)
    avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='头像')
    '''
    给avatar字段传文件对象  该文件会自动存储到avatar文件下  然后avatar字段只保存文件路径
    '''
    create_time = models.DateTimeField(auto_now_add=True)

    # 一对一站点表
    blog = models.OneToOneField(to='Blog', null=True)


class Blog(models.Model):
    site_name = models.CharField(max_length=32, verbose_name='站点名称')
    site_title = models.CharField(max_length=32, verbose_name='站点标题')
    site_theme = models.CharField(max_length=64, verbose_name='站点样式')


# 分类
class Categoery(models.Model):
    name = models.CharField(max_length=32, verbose_name='分类名')
    # 一对多站点表
    blog = models.ForeignKey(to='Blog', null=True)


class Tag(models.Model):
    name = models.CharField(max_length=32, verbose_name='标签名')
    # 一对多站点表
    blog = models.ForeignKey(to='Blog', null=True)


class Article(models.Model):
    title = models.CharField(max_length=64, verbose_name='文章标题')
    desc = models.CharField(max_length=255, verbose_name='文章简介')
    # 文章内容有很多 一般情况下都是使用TextField
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    # 数据库字段设计优化
    up_num = models.BigIntegerField(verbose_name='点赞数', default=0)
    down_num = models.BigIntegerField(verbose_name='点踩数', default=0)
    comment_num = models.BigIntegerField(verbose_name='评论数', default=0)

    # 一对多站点和分类
    blog = models.ForeignKey(to='Blog', null=True)
    categoery = models.ForeignKey(to='Categoery', null=True)

    # 多对多文件标签
    tag = models.ManyToManyField(to='Tag',
                                 through='Article2Tag',
                                 through_fields=('article', 'tag')
                                 )


# 半自动创建第三张表
class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')


class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    is_up = models.BooleanField()  # 传布尔值 存0/1


class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    content = models.CharField(max_length=255, verbose_name='评论内容')
    comment_time = models.DateField(auto_now_add=True, verbose_name='评论时间')
    # 自关联
    parent = models.ForeignKey(to='self', null=True, verbose_name='子评论')  # 可以为空,可以没有子评论

注册功能

forms组件源码(myforms.py)

# myforms.py  书写针对用户表的forms组件代码
from django import forms
from app01 import models


class MyRegForm(forms.Form):
    username = forms.CharField(label='用户名', min_length=3, max_length=8,
                               error_messages={
                                   'required': '用户名不能为空',
                                   'min_length': '用户名最少3位',
                                   'max_length': '用户名最大8位',
                               },
                               # 给标签添加bootstrap样式
                               widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
                               )
    password = forms.CharField(label='密码', min_length=3, max_length=8,
                               error_messages={
                                   'required': '密码不能为空',
                                   'min_length': '密码最少3位',
                                   'max_length': '密码最大8位',
                               },
                               widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                               )
    confirm_password = forms.CharField(label='确认密码', min_length=3, max_length=8,
                                       error_messages={
                                           'required': '确认密码不能为空',
                                           'min_length': '确认密码最少3位',
                                           'max_length': '确认密码最大8位',
                                       },
                                       widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                                       )

    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 'required': '邮箱不能为空',
                                 'invalid': '邮箱格式不正确',
                             },
                             widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
                             )

    # 钩子函数
    # 局部钩子: 校验用户名是否存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        # 去数据库中校验
        is_exist = models.UserInfo.objects.filter(username=username)
        if is_exist:
            # 提示信息
            self.add_error('username', '用户名已存在')

        return username

    # 全局钩子: 校验两次密码是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password', '两次密码不一致')
        return self.cleaned_data

前端源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1">

    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">注册</h1>
            <form id="myform">
                {% csrf_token %}
                {% for form in form_obj %}
                    <div class="form-group">
                        <label for="{{ form.auto_id }}">{{ form.label }}</label>
                        {{ form }}
                        <span style="color:red" class="pull-right">{{ form.errors.0 }}</span>
                    </div>
                {% endfor %}
                <div class="form-group">
                    <label for="myfile">头像
                        {% load static %}
                        <img src="{% static 'img/default.png' %}" id="myimg" alt="" width="100" style="margin-left: 10px">
                    </label>
                    <input type="file" id="myfile" name="avatar" style="display:none">
                </div>

                <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">

            </form>
        </div>
    </div>
</div>
<script>
    $('#myfile').change(function () {
        // 文件阅读器对象
        // 1.先生成一个文件阅读器对象
        let myFileReaderObj = new FileReader();
        // 2.获取用户上传的文件
        let fileObj = $(this)[0].files[0];
        // 3.将文件对象交给阅读器对象读取
        myFileReaderObj.readAsDataURL(fileObj)  // 异步操作 IO操作
        // 4.利用文件阅读器见文件展示到前端页面  修改src属性
        // 等待文件阅读器加载完毕之后在执行
        myFileReaderObj.onload = function () {
            $('#myimg').attr('src',myFileReaderObj.result)
        }
    })

    $('#id_commit').click(function () {
        // 发送ajax请求  发送的数据中既包含普通的键值又包含文件 需要用到FormData对象
        let formDataObj = new FormData();
        // 1. 添加普通键值对
        $.each($('#myform').serializeArray(),function (index,obj) {
            // $('#myform').serializeArray() 获取form标签内所有普通键值对数据
            formDataObj.append(obj.name, obj.value)
        })
        // 2. 添加文件数据
        formDataObj.append('avatar',$('#myfile')[0].files[0])
        // 3. 发送ajax请求
        $.ajax({
            url:'',
            type:'post',
            data: formDataObj,
            // formdata对象需要指定两个关键参数
            contentType:false,
            processData:false,

            success:function (args) {
                if(args.code==1000){
                    // 跳转到登录页面
                    window.location.href = args.url
                }else{
                    $.each(args.msg, function (index,obj) {
                        let targetId = '#id_' + index;
                        $(targetId).next().text(obj[0]).parent().addClass('has-error')
                    })
                }
            }

        })
    })

    // 给所有的input框绑定获取焦点事件
    $('input').focus(function () {
        // 将input下面的span标签和input外面的div标签修改内容及属性
        $(this).next().text('').parent().removeClass('has-error')
    })

</script>

</body>
</html>

登录功能

图片验证码补充知识:

# 图片相关的模块  pip install pillow
from PIL import Image,ImageDraw,ImageFont
'''
Image : 生成图片
ImageDraw: 能够在图片上乱涂乱画
ImageFont: 控制字体样式
'''
from io import BytestIO,StringIO

'''
内存管理器模块
BytesIO: 临时存储数据 返回的数据是二进制类型
StringIO: 临时存储数据 返回的数据是字符串类型
'''

前端源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1">

    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    {% load static %}
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">登录</h1>
            <div class="form-group">
                <label for="username">用户名:</label>
                <input type="text" name="username" id="username" class="form-control">
            </div>
            <div class="form-group">
                <label for="password">密码:</label>
                <input type="password" name="password" id="password" class="form-control">
            </div>
            <div class="form-group">
                <label for="">验证码:</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" name="code" id="id_code" class="form-control">
                    </div>
                    <div class="col-md-6">
                        <img src="/get_code/" alt="" width="360" height="35" id="code_img">
                    </div>
                </div>
            </div>
            <input type="button" class="btn btn-success form-control" value="登录" id="id_commit">
            <span style="color: red" id="error"></span>
        </div>
    </div>
</div>

<script>
    $('#code_img').click(function () {
        // 1 先获取标签之前的src
        let oldVal = $(this).attr('src');
        $(this).attr('src',oldVal += '?');
    })

    $('#id_commit').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{
                'username':$('#username').val(),
                'password':$('#password').val(),
                'code':$('#id_code').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}'
            },
            success:function (args){
                if(args.code==1000){
                    window.location.href = args.url
                }else{
                    $('#error').text(args.msg)
                }
            }
        })
    })

</script>
</body>
</html>

后端源码

# 随机生成一组rgb颜色数据
def get_random():
    return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)

# 生成图片验证码
def get_code(request):
    img_obj = Image.new('RGB', (360, 35), get_random())
    img_draw = ImageDraw.Draw(img_obj)  # 产生一个画笔对象
    img_font = ImageFont.truetype('static/font/222.ttf', 30)  # 字体样式路径和大小

    # 生成随机5位数的验证码
    code = ''
    for i in range(5):
        random_upper = chr(random.randint(65, 90))
        random_lower = chr(random.randint(97, 122))
        random_int = str(random.randint(0, 9))
        # 从上面三个随机选一个
        tmp = random.choice([random_int, random_upper, random_lower])

        img_draw.text((i*50+50, -2), tmp, get_random(), img_font)
        code += tmp

    # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到
    request.session['code'] = code
    io_obj = BytesIO()
    img_obj.save(io_obj, 'png')
    return HttpResponse(io_obj.getvalue())


# 登录
def login(request):
    if request.method == 'POST':
        back_dic = {'code':1000,'msg':''}
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        if request.session.get('code').upper() == code.upper():
            user_obj = auth.authenticate(request, username=username, password=password)

            if user_obj:
                auth.login(request,user_obj)
                back_dic['url'] = '/home/'
            else:
                back_dic['code'] = 2000
                back_dic['msg'] = '用户名或密码错误'
        else:
            back_dic['code'] = 3000
            back_dic['msg'] = '验证码错误'
        return JsonResponse(back_dic)

    return render(request, 'login.html')

零碎知识点

request.user.is_authenticated  判断是否有用户信息

phone = models.BigIntegerField(verbose_name='手机号', null=True, blank=True)
    null = True 数据库该字段可以为空
    blank = True admin后台管理该字段可以为空
    
class Meta:
    verbose_name_plural = '站点表'     # 用户admin后台管理显示表名
    
    
1 网站所使用的的静态文件默认放在static文件夹下
2 用户上传的静态文件也应单独放在某个文件夹下

media配置
	改配置可以让用户上传的所有文件都固定存放在某一个指定的文件夹下    
# 配置用户上传的文件存储位置
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')    # 暴露文件夹名
	会自动创建多级目录

如何开设后端指定文件夹资源
	首先去urls.py中书写固定代码
	from django.views.static import serve
	from BBS import settings
    # 暴露后端指定文件夹资源
  	url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})
    
# 全是每个用户都可以有自己的站点样式
	内部给每个人开设可以自定义css和js的文件接口并且用户自定义之后将用户的文件保存下来,在用户
	<link rel="stylesheet" href="/media/css/{{ blog.site_theme }}/">        

图片防盗链

# 如何避免别的网站直接通过本网站的url访问本网站资源

# 简单的防盗
	我可以做到请求来的时候先看看当前请求是从哪个网站过来的
  如果是本网站那么正常访问
  如果是其他网站直接拒绝
  	请求头里面有一个专门记录请求来自于哪个网址的参数
    	Referer: http://127.0.0.1:8000/xxx/

# 如何避免
	1.要么修改请求头referer
	2.直接写爬虫把对方网址的所有资源直接下载到我们自己的服务器上

Django官方提供生成年月源码

  • 通常用来按照月份进行分组
django官网提供的一个orm语法
	from django.db.models.functions import TruncMonth
-官方提供
			from django.db.models.functions import TruncMonth
			Sales.objects
			.annotate(month=TruncMonth('timestamp'))  # 将日期按照月份截取并添加到查询列表中
			.values('month')  # 通过month分组
			.annotate(c=Count('id'))  # 选择分组的计数
			.values('month', 'c')  # (可能是多余的,尚未测试)选择月份并计数
 


date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month', 'count_num')

时区问题报错

LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = True
          

路由分发的本质

针对bbs在绑定数据的时候一定要细心
	1.先去文章表里面绑定数据
	2.个人站点
	3.文章分类
	4.将用户和个人站点绑定关系
	5.文章与文章标签

扩展:admin路由分发的本质
		# 路由分发本质    include  可以无限制的嵌套N多层
		url(r'^index/',([],None,None))
		
        # url(r'^index/',([
        #         url(r'^index_1/',([
        #                 url(r'^index_1_1',index),
        #                 url(r'^index_1_2',index),
        #                 url(r'^index_1_3',index),
        #                           ],None,None)),
        #         url(r'^index_2/',index),
        #         url(r'^index_3/',index),
        #                 ],None,None)),
        
由于url方法的第一个参数是正则表达式,所以当路由特别多的时候,可能会出现呗顶替的情况,针对这种情况有两种解决办法
1. 修改正则表达式
2. 调整url方法的位置
子评论处理
// 找到\n对应的索引,然后利用切片 切片是顾头不顾尾 所以索引+1
    let indexNum = content.indexOf('\n') + 1;
    content = content.slice(indexNum)   // 将indexNum之前的数据切除 只保留后面的部分


XSS攻击
  	针对支持用户直接编写html代码的网址
    针对用户直接书写的script标签 我们需要处理
	1.注视标签内部的内容
	2.直接将script删除
(1)null
 
如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.
 
(1)blank
 
如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

go&Python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值