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、点赞点踩表

记录哪个用户给哪篇文章点了赞还是点了踩
	user						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)

根评论子评论的概念
	根评论就是直接评论当前发布的内容的
	子评论是评论别人的评论
		
	根评论与子评论是一对多的关系

数据库表创建

数据库选择:MySQL

新建一个bbs14数据库

django连接数据库设置

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bbs14',
        'USER':'root',
        'PASSWORD':'123456',
        'HOST':'127.0.0.1',
        'PORT':3306,
        'CHARSET':'utf8'
    }
}

在一个init文件中写入以下代码

import pymysql
pymysql.install_as_MySQLdb()

创建表:

继承使用Auth表,扩展使用要先在配置文件setting.py中加入以下代码

 AUTH_USER_MODEL = 'app01.UserInfo'
                   '应用名.表名'

先写普通字段,在写外键字段

数据库创建代码 models.py
# 用户表
class UserInfo(AbstractUser):
    phone = models.BigIntegerField(verbose_name='手机号码',null=True)
    avatar = models.FileField(verbose_name='头像',upload_to='avatar/',default='avatar/default.png')
    create_time = models.DateField(auto_now_add=True)

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

# 个人站点
class Blog(models.Model):
    site_name = models.CharField(verbose_name='站点名称',max_length=32)
    site_title = models.CharField(verbose_name='站点标题',max_length=32)
    # 简单模拟 存css或js的文件路径
    site_theme = models.CharField(verbose_name='站点样式',max_length=64)

# 文章分类
class Category(models.Model):
    name = models.CharField(verbose_name='文章分类',max_length=32)

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

# 文章标签
class Tag(models.Model):
    name = models.CharField(verbose_name='文章标签',max_length=32)

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


# 文章表
class Article(models.Model):
    title = models.CharField(verbose_name='文章标题',max_length=64)
    desc = models.CharField(verbose_name='文章简介',max_length=256)
    # 文章内容有很多,一般使用TextField
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateField(auto_now_add=True)

    # 数据库优化字段
    up_num = models.BigIntegerField(verbose_name='点赞数',default=0)
    down_num = models.BigIntegerField(verbose_name='点踩数',default=0)
    content_num = models.BigIntegerField(verbose_name='评论数',default=0)

    # 一对多站点表
    blog = models.ForeignKey(to='Blog',null=True)
    # 一对多文章分类
    category = models.ForeignKey(to='Category',null=True)
    tags = 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(verbose_name='是否点赞') # 存布尔值 0 1

**执行数据库迁移和记录命令**
python3 manage.py makemigrations 将操作记录记录到小本本上(migrations文件夹)

python3 manage.py migrate  将操作真正的同步到数据库中

注册功能

url配置路由

from app01 import views

url(r'^register',views.register)

在app01文件夹下,新建myforms.py文件夹

我们之前是直接在views.py中书写的forms组件代码,但是为了接耦合 ,应该将所有的forms组件代码单独写到一个地方

如果项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可
myforms.py
但是如果项目需要使用多个forms组件,那么可以创建一个文件夹在文件夹内根据forms组件功能的不同创建不同的py文件,例如:
myforms文件夹
regform.py
loginform.py
userform.py
orderform.py

form组件代码
# 书写针对用户表的myforms组件代码
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位'
                               },
                               # 添加样式bootstrap
                               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位'
                               },
                               # 添加样式bootstrap
                               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_exists = models.UserInfo.objects.filter(username=username)
        if is_exists:
            self.add_error('username','用户名已存在')
        return username

    # 全局钩子 校验密码是否一致
    def clean_password(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

**配置静态文件**
STATICFILES_DIRS={
    os.path.join(BASE_DIR,'static')
}
register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    {% load static %}
    <style>
    </style>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">注册</h1>
            <form id="myform">  <!--这里不用form表单提交数据 只是单纯的用一下form标签而已-->
                {% 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"></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请求     我们发送的数据中即包含普通的键值也包含文件
        let formDataObj = new FormData();
        // 1.添加普通的键值对
        // {#console.log($('#myform').serializeArray())  // [{},{},{},{},{}]  只包含普通键值对#}
        $.each($('#myform').serializeArray(),function (index,obj) {
            //{#console.log(index,obj)#}  // obj = {}
            formDataObj.append(obj.name,obj.value)
        });
        // 2.添加文件数据
        formDataObj.append('avatar',$('#myfile')[0].files[0]);

        // 3.发送ajax请求
        $.ajax({
            url:"",
            type:'post',
            data:formDataObj,

            // 需要指定两个关键性的参数
            contentType:false,
            processData:false,

            success:function (args) {
                if (args.code==1000){
                    // 跳转到登陆页面
                    window.location.href = args.url
                }else{
                    // 如何将对应的错误提示展示到对应的input框下面
                    // forms组件渲染的标签的id值都是 id_字段名
                    $.each(args.msg,function (index,obj) {
                        // {#console.log(index,obj)  //  username        ["用户名不能为空"]#}
                        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>
register的视图代码
# 注册页面
def register(request):
    form_obj = MyRegForm()
    if request.method == 'POST':
        back_dic = {"code": 1000, 'msg': ''}
        # 校验数据是否合法
        form_obj = MyRegForm(request.POST)
        # 判断数据是否合法
        if form_obj.is_valid():
            # print(form_obj.cleaned_data)  # {'username': 'xh', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
            clean_data = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量
            # 将字典里面的confirm_password键值对删除
            clean_data.pop('confirm_password')  # {'username': 'xh', 'password': '123', 'email': '123@qq.com'}
            # 用户头像
            file_obj = request.FILES.get('avatar')
            """针对用户头像一定要判断是否传值 不能直接添加到字典里面去"""
            if file_obj:
                clean_data['avatar'] = file_obj
            # 直接操作数据库保存数据
            models.UserInfo.objects.create_user(**clean_data)
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 2000
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request,'register.html',locals())

登录功能

login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    {% load static %}
<body>
<div class="container-fluid">
    <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="370" height="35" id="id_img">
                    </div>
                </div>

            </div>
            <input type="button" class="btn btn-success" value="登录" id="id_commit">
            <span style="color: red" id="error"></span>
        </div>
    </div>
</div>
<script>
    $("#id_img").click(function () {
        // 1 先获取标签之前的src
        let oldVal = $(this).attr('src');
        $(this).attr('src',oldVal += '?')
    })

    // 点击按钮发送ajax请求
    $("#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>
login视图代码
# 登录页面
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')
        # 1 先校验验证码是否正确                  统一转大写或者小写再比较
        if request.session.get('code').upper() == code.upper():
            # 2 校验用户名和密码是否正确
            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')


# 图片验证码
import random
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',(430,35),get_random())
    img_draw = ImageDraw.Draw(img_obj)
    img_font = ImageFont.truetype('static1/font/111.ttf',30)
    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_upper,random_lower,random_int])

        img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
        code += tmp
    print(code)
    request.session['code'] = code
    io_obj = BytesIO()
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())

关于图片验证码

图片验证码代码
"""
图片相关的模块
    pip3 install pillow
"""
from PIL import Image,ImageDraw,ImageFont
"""
Image:生成图片
ImageDraw:能够在图片上乱涂乱画
ImageFont:控制字体样式
"""
from io import BytesIO,StringIO
"""
内存管理器模块
BytesIO:临时帮你存储数据 返回的时候数据是二进制
StringIO:临时帮你存储数据 返回的时候数据是字符串
"""
import random
def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):
    # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端
    # with open(r'static/img/111.jpg','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推导步骤2:利用pillow模块动态产生图片
    # img_obj = Image.new('RGB',(430,35),'green')
    # img_obj = Image.new('RGB',(430,35),get_random())
    # # 先将图片对象保存起来
    # with open('xxx.png','wb') as f:
    #     img_obj.save(f,'png')
    # # 再将图片对象读取出来
    # with open('xxx.png','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推导步骤3:文件存储繁琐IO操作效率低  借助于内存管理器模块
    # img_obj = Image.new('RGB', (430, 35), get_random())
    # io_obj = BytesIO()  # 生成一个内存管理器对象  你可以看成是文件句柄
    # img_obj.save(io_obj,'png')
    # return HttpResponse(io_obj.getvalue())  # 从内存管理器中读取二进制的图片数据返回给前端


    # 最终步骤4:写图片验证码
    img_obj = Image.new('RGB', (430, 35), get_random())
    img_draw = ImageDraw.Draw(img_obj)  # 产生一个画笔对象
    img_font = ImageFont.truetype('static/font/222.ttf',30)  # 字体样式 大小

    # 随机验证码  五位数的随机验证码  数字 小写字母 大写字母
    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_lower,random_upper,random_int])
        # 将产生的随机字符串写入到图片上
        """
        为什么一个个写而不是生成好了之后再写
        因为一个个写能够控制每个字体的间隙 而生成好之后再写的话
        间隙就没法控制了
        """
        img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
        # 拼接随机字符串
        code += tmp
    print(code)
    # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到
    request.session['code'] = code
    io_obj = BytesIO()
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())

首页

home.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">BBS</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
        <li><a href="#">文章</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></li>
          </ul>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
                <li><a href="#">{{ request.user.username }}</a></li>
                <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作 <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
            <li><a href="/set/avatar/">修改头像</a></li>
            <li><a href="/backend/">后台管理</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="{% url 'logout' %}">退出登陆</a></li>
          </ul>

            <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
              <div class="modal-dialog modal-lg" role="document">
                <div class="modal-content">
                    <h1 class="text-center">修改密码</h1>
                  <div class="row">
                      <div class="col-md-8 col-md-offset-2">
                          <div class="form-group">
                              <label for="">用户名</label>
                              <input type="text" disabled value="{{ request.user.username }}" class="form-control">
                          </div>
                          <div class="form-group">
                              <label for="">原密码</label>
                              <input type="password" id="id_old_password" class="form-control">
                          </div>
                          <div class="form-group">
                              <label for="">新密码</label>
                              <input type="password" id="id_new_password" class="form-control">
                          </div>
                          <div class="form-group">
                              <label for="">确认密码</label>
                              <input type="password" id="id_confirm_password" class="form-control">
                          </div>
                          <div class="modal-footer">
                          <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                          <button class="btn btn-primary" id="id_edit">修改</button>
                              <span style="color: red" id="password_error"></span>
                          </div>
                          <br>
                          <br>
                      </div>
                  </div>
                </div>
              </div>
            </div>
        </li>
          {% else %}
                <li><a href="{% url 'reg' %}">注册</a></li>
                <li><a href="{% url 'login' %}">登陆</a></li>
          {% endif %}
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="col-md-2">
        <div class="panel panel-primary">
          <div class="panel-heading">
            <h3 class="panel-title">广告</h3>
          </div>
          <div class="panel-body">
            事成之后,上海别墅一套外加现金500万
          </div>
        </div>
        <div class="panel panel-danger">
          <div class="panel-heading">
            <h3 class="panel-title">小红书</h3>
          </div>
          <div class="panel-body">
            抓紧联系:00000000
          </div>
        </div>
        <div class="panel panel-info">
          <div class="panel-heading">
            <h3 class="panel-title"></h3>
          </div>
          <div class="panel-body">
            你想要的,这里都有
          </div>
        </div>
    </div>
    <div class="col-md-8">
        <ul class="media-list">
            {% for article_obj in article_queryset %}
                <li class="media">
                    <h4 class="media-heading"><a href="/{{ article_obj.blog.userinfo.username }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></h4>
                    <div class="media-left">
                      <a href="#">
                        <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80">
                      </a>
                    </div>
                    <div class="media-body">
                        {{ article_obj.desc }}
                    </div>
{#                Newbe36524 发布于 2020-06-11 09:04 评论(0)阅读(23)#}
                    <br>
                <div>
                    <span><a href="/{{  article_obj.blog.userinfo.username}}/">{{ article_obj.blog.userinfo.username }}&nbsp;&nbsp;</a></span>
                    <span>发布于&nbsp;&nbsp;</span>
                    <span>{{ article_obj.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
                    <span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }})&nbsp;&nbsp;</span>
                    <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }})</span>
                </div>
                  </li>
                <hr>
            {% endfor %}

        </ul>
    </div>
    <div class="col-md-2">
        <div class="panel panel-primary">
          <div class="panel-heading">
            <h3 class="panel-title">重金求子</h3>
          </div>
          <div class="panel-body">
            事成之后,上海别墅一套外加现金500万
          </div>
        </div>
        <div class="panel panel-danger">
          <div class="panel-heading">
            <h3 class="panel-title">千万大奖</h3>
          </div>
          <div class="panel-body">
            抓紧联系:00000000
          </div>
        </div>
        <div class="panel panel-info">
          <div class="panel-heading">
            <h3 class="panel-title">小红书</h3>
          </div>
          <div class="panel-body">
            你想要的这里都有
          </div>
        </div>
    </div>
</div>
<script>
    $('#id_edit').click(function () {
        $.ajax({
            url:'/set_password/',
            type:'post',
            data:{
                'old_password':$('#id_old_password').val(),
                'new_password':$('#id_new_password').val(),
                'confirm_password':$('#id_confirm_password').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}'
            },
            success:function (args) {
                if (args.code == 1000){
                    window.location.reload()
                }else{
                    $("#password_error").text(args.msg)
                }
            }
        })
    })
</script>
</body>
</html>
修改密码、退出登录
修改密码与退出登录代码
def home(request):
    # 查询本网站所有的文章数据展示的前端页面 这里可以使用分页器做分页 但是我不做了 你们自己课下加
    article_queryset = models.Article.objects.all()
    return render(request,'home.html',locals())


@login_required
def set_password(request):
    if request.is_ajax():
        back_dic = {'code':1000,'msg':''}
        if request.method == 'POST':
            old_password = request.POST.get('old_password')
            new_password = request.POST.get('new_password')
            confirm_password = request.POST.get('confirm_password')
            is_right = request.user.check_password(old_password)
            if is_right:
                if new_password == confirm_password:
                    request.user.set_password(new_password)
                    request.user.save()
                    back_dic['msg'] = '修改成功'
                else:
                    back_dic['code'] = 1001
                    back_dic['msg'] = '两次密码不一致'
            else:
                back_dic['code'] = 1002
                back_dic['msg'] = '原密码错误'
        return JsonResponse(back_dic)


@login_required
def logout(request):
    auth.logout(request)
    return redirect('/home/')

个人站点

准备继承模板

base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/media/css/{{ blog.site_theme }}/">
    {% block css %}

    {% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">{{ blog.site_title }}</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
        <li><a href="#">文章</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></li>
          </ul>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
                <li><a href="#">{{ request.user.username }}</a></li>
                <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作 <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
            <li><a href="/set/avatar/">修改头像</a></li>
            <li><a href="/backend/">后台管理</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="{% url 'logout' %}">退出登陆</a></li>
          </ul>

            <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
              <div class="modal-dialog modal-lg" role="document">
                <div class="modal-content">
                    <h1 class="text-center">修改密码</h1>
                  <div class="row">
                      <div class="col-md-8 col-md-offset-2">
                          <div class="form-group">
                              <label for="">用户名</label>
                              <input type="text" disabled value="{{ request.user.username }}" class="form-control">
                          </div>
                          <div class="form-group">
                              <label for="">原密码</label>
                              <input type="password" id="id_old_password" class="form-control">
                          </div>
                          <div class="form-group">
                              <label for="">新密码</label>
                              <input type="password" id="id_new_password" class="form-control">
                          </div>
                          <div class="form-group">
                              <label for="">确认密码</label>
                              <input type="password" id="id_confirm_password" class="form-control">
                          </div>
                          <div class="modal-footer">
                          <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                          <button class="btn btn-primary" id="id_edit">修改</button>
                              <span style="color: red" id="password_error"></span>
                          </div>
                          <br>
                          <br>
                      </div>
                  </div>
                </div>
              </div>
            </div>
        </li>
          {% else %}
                <li><a href="{% url 'reg' %}">注册</a></li>
                <li><a href="{% url 'login' %}">登陆</a></li>
          {% endif %}
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            {% load mytag %}
            {% left_menu username %}
        </div>
        <div class="col-md-9">
            {% block content %}

            {% endblock %}

        </div>
    </div>
</div>

{% block js %}

{% endblock %}
</body>
</html>
site.html
{% extends 'base.html' %}


{% block content %}
    <ul class="media-list">
            {% for article_obj in article_list %}
                <li class="media">
                    <h4 class="media-heading"><a href="/{{ username }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></h4>
                    <div class="media-left">
                      <a href="#">
                        <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80">
                      </a>
                    </div>
                    <div class="media-body">
                        {{ article_obj.desc }}
                    </div>
{#                posted @ 2015-10-20 01:02 武沛齐 阅读(68527) 评论(24) 推荐(58) 编辑)#}
                <div class="pull-right">
                    <span>posted&nbsp;&nbsp;</span>
                    <span>@&nbsp;&nbsp;</span>
                    <span>{{ article_obj.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
                    <span>{{ article_obj.blog.userinfo.username }}&nbsp;&nbsp;</span>
                    <span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }})&nbsp;&nbsp;</span>
                    <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }})</span>
                    <span><a href="#">编辑</a></span>
                </div>
                  </li>
                <hr>
            {% endfor %}

        </ul>
{% endblock %}
点击查看代码
def site(request,username,**kwargs):
    """
    :param kwargs: 如果该参数有值 也就意味着需要对article_list做额外的筛选操作
    """
    # 先校验当前用户名对应的个人站点是否存在
    user_obj = models.UserInfo.objects.filter(username=username).first()
    # 用户如果不存在应该返回一个404页面
    if not user_obj:
        return render(request,'errors.html')
    blog = user_obj.blog
    # 查询当前个人站点下的所有的文章
    article_list = models.Article.objects.filter(blog=blog)  # queryset对象 侧边栏的筛选其实就是对article_list再进一步筛选
    if kwargs:
        # print(kwargs)  # {'condition': 'tag', 'param': '1'}
        condition = kwargs.get('condition')
        param = kwargs.get('param')
        # 判断用户到底想按照哪个条件筛选数据
        if condition == 'category':
            article_list = article_list.filter(category_id=param)
        elif condition == 'tag':
            article_list = article_list.filter(tags__id=param)
        else:
            year,month = param.split('-')  # 2020-11  [2020,11]
            article_list = article_list.filter(create_time__year=year,create_time__month=month)
    # # 1 查询当前用户所有的分类及分类下的文章数
    # category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
    # # print(category_list)  # <QuerySet [('april的分类一', 2), ('april的分类二', 1), ('april的分类三', 1)]>
    #
    # # 2 查询当前用户所有的标签及标签下的文章数
    # tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
    # # print(tag_list)  # <QuerySet [('xh的标签一', 1), ('xh的标签二', 1), ('xh的标签三', 2)]>
    #
    # # 3 按照年月统计所有的文章
    # 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')
    # # print(date_list)
    return render(request,'site.html',locals())

文章详情

article_detail.html
{% extends  'base.html' %}

{% block css %}
    <style>
        #div_digg {
            float: right;
            margin-bottom: 10px;
            margin-right: 30px;
            font-size: 12px;
            width: 128px;
            text-align: center;
            margin-top: 10px;
        }

        .diggit {
            float: left;
            width: 46px;
            height: 52px;
            background: url('/static/img/upup.gif') no-repeat;
            text-align: center;
            cursor: pointer;
            margin-top: 2px;
            padding-top: 5px;
        }

        .buryit {
            float: right;
            margin-left: 20px;
            width: 46px;
            height: 52px;
            background: url('/static/img/downdown.gif') no-repeat;
            text-align: center;
            cursor: pointer;
            margin-top: 2px;
            padding-top: 5px;
        }

        .clear {
            clear: both;
        }
    </style>
{% endblock %}

{% block content %}
    <h1>{{ article_obj.title }}</h1>
    <div class="article_content">
        {{ article_obj.content|safe }}
    </div>
    {#    点赞点踩样式开始#}
    <div class="clearfix">
        <div id="div_digg">
            <div class="diggit action" >
                <span class="diggnum " id="digg_count">{{ article_obj.up_num }}</span>
            </div>
            <div class="buryit action">
                <span class="burynum " id="bury_count">{{ article_obj.down_num }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips" style="color: red">
            </div>
        </div>
    </div>
    {#    点赞点踩样式结束#}
{#    评论楼渲染开始#}
{#    #3楼 2020-05-14 14:11 代码一字狂#}
    <div>
    <ul class="list-group">
        {% for comment in comment_list %}
             <li class="list-group-item">
            <span>#{{ forloop.counter }}楼</span>
            <span>{{ comment.comment_time|date:'Y-m-d h:i:s' }}</span>
            <span>{{ comment.user.username }}</span>
            <span><a class="pull-right reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span>
            <div>
{#                判断当前评论是否是子评论 如果是需要渲染对应的评论人名#}
                {% if comment.parent_id %}
                    <p>@{{ comment.parent.user.username }}</p>
                {% endif %}
                    {{ comment.content }}
            </div>
            </li>
        {% endfor %}
</ul>

        
    </div>
{#    评论楼渲染结束#}
    {#   文章评论样式开始 #}
    {% if request.user.is_authenticated %}
        <div>
        <p><span class="glyphicon glyphicon-comment"></span>发表评论</p>
        <div>
            <textarea name="comment" id="id_comment" cols="60" rows="10" ></textarea>
        </div>
        <button class="btn btn-primary" id="id_submit">提交评论</button>
        <span style="color: red" id="errors"></span>
    </div>
        {% else %}
                <li><a href="{% url 'reg' %}">注册</a></li>
                <li><a href="{% url 'login' %}">登陆</a></li>
    {% endif %}
    {#   文章评论样式结束 #}
{% endblock %}

{% block js %}
    <script>
        //   给所有的action类绑定事件
        $('.action').click(function () {
            {#alert($(this).hasClass('diggit'))#}
            let isUp = $(this).hasClass('diggit');
            let $div = $(this);
            // 朝后端发送ajax请求
            $.ajax({
                url:'/up_or_down/',
                type:'post',
                data:{
                    'article_id':'{{ article_obj.pk }}',
                    'is_up':isUp,
                    'csrfmiddlewaretoken':'{{ csrf_token }}'
                },
                success:function (args) {
                        if(args.code == 1000){
                            $('#digg_tips').text(args.msg)
                            // 将前端的数字加一
                            // 先获取到之前的数字
                            let oldNum = $div.children().text();  // 文本 是字符类型
                            // 易错点
                            $div.children().text(Number(oldNum) + 1)  // 字符串拼接了 1+1 = 11  11 + 1 = 111
                        }else{
                            $('#digg_tips').html(args.msg)
                        }
                }
            })
        })
        // 设置一个全局的parentID字段
        let parentId = null;
        // 用户点击评论按钮朝后端发送ajax请求
        $('#id_submit').click(function () {
            // 获取用户评论的内容
            let conTent = $('#id_comment').val();
            // 判断当前评论是否是子评论 如果是 需要将我们之前手动渲染的@username去除
            if(parentId){
                // 找到\n对应的索引 然后利用切片 但是前片顾头不顾尾 所以索引+1
                let indexNum = conTent.indexOf('\n') + 1;
                conTent = conTent.slice(indexNum)  // 将indexNum之前的所有数据切除 只保留后面的部分
            }
            $.ajax({
                url:'/comment/',
                type:'post',
                data:{
                    'article_id':'{{ article_obj.pk }}',
                    'content':conTent,
                    // 如果parantId没有值 那么就是null 后端存储null没有任何关系
                    'parent_id':parentId,
                    'csrfmiddlewaretoken':'{{ csrf_token }}'
                },
                success:function (args) {
                    if(args.code ==1000){
                        $('#error').text(args.msg)

                        // 将评论框里面的内容清空
                        $('#id_comment').val('');

                        // 临时渲染评论楼
                        let userName = '{{ request.user.username }}';
                        let temp = `
                        <li class="list-group-item">

                            <span>${userName}</span>
                            <span><a href="#" class="pull-right">回复</a></span>
                            <div>
                                ${conTent}
                            </div>
                            </li>
                        `
                        // 将生成好的标签添加到ul标签内
                        $('.list-group').append(temp);
                        // 清空全局的parentId
                        parentId = null;
                    }
                }
            })
        })

    // 给回复按钮绑定点击事件
    $('.reply').click(function () {
        // 需要评论对应的评论人姓名   还需要评论的主键值
        // 获取用户名
        let commentUserName = $(this).attr('username');
        // 获取主键值 直接修改全局
        parentId = $(this).attr('comment_id');
        // 拼接信息塞给评论框
        $('#id_comment').val('@' + commentUserName + '\n').focus()
    })
    </script>
{% endblock %}
文章详情的视图代码
def article_detail(request,username,article_id):
    """
    应该需要校验username和article_id是否存在,但是我们这里先只完成正确的情况
    默认不会瞎搞
    :param request:
    :param username:
    :param article_id:
    :return:
    """
    user_obj = models.UserInfo.objects.filter(username=username).first()
    blog = user_obj.blog
    # 先获取文章对象
    article_obj = models.Article.objects.filter(pk=article_id,blog__userinfo__username=username).first()
    if not article_obj:
        return render(request,'errors.html')
    # 获取当前 文章所有的评论内容
    comment_list = models.Comment.objects.filter(article=article_obj)
    return render(request,'article_detail.html',locals())

import json
from django.db.models import F
def up_or_down(request):
    """
    1.校验用户是否登陆
    2.判断当前文章是否是当前用户自己写的(自己不能点自己的文章)
    3.当前用户是否已经给当前文章点过了
    4.操作数据库了
    :param request:
    :return:
    """
    if request.is_ajax():
        back_dic = {'code':1000,'msg':''}
        # 1 先判断当前用户是否登陆
        if request.user.is_authenticated():
            article_id = request.POST.get('article_id')
            is_up = request.POST.get('is_up')
            # print(is_up,type(is_up))  # true <class 'str'>
            is_up = json.loads(is_up)  # 记得转换
            # print(is_up, type(is_up))  # True <class 'bool'>
            # 2 判断当前文章是否是当前用户自己写的  根据文章id查询文章对象 根据文章对象查作者 根request.user比对
            article_obj = models.Article.objects.filter(pk=article_id).first()
            if not article_obj.blog.userinfo == request.user:
                # 3 校验当前用户是否已经点了      哪个地方记录了用户到底点没点
                is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)
                if not is_click:
                    # 4 操作数据库 记录数据      要同步操作普通字段
                    # 判断当前用户点了赞还是踩 从而决定给哪个字段加一
                    if is_up:
                        # 给点赞数加一
                        models.Article.objects.filter(pk=article_id).update(up_num = F('up_num') + 1)
                        back_dic['msg'] = '点赞成功'
                    else:
                        # 给点踩数加一
                        models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
                        back_dic['msg'] = '点踩成功'
                    # 操作点赞点踩表
                    models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)
                else:
                    back_dic['code'] = 1001
                    back_dic['msg'] = '你已经点过了,不能再点了'  # 这里你可以做的更加的详细 提示用户到底点了赞还是点了踩
            else:
                back_dic['code'] = 1002
                back_dic['msg'] = '你个臭不要脸的!'
        else:
            back_dic['code'] = 1003
            back_dic['msg'] = '请先<a href="/login/">登陆</a>'
        return JsonResponse(back_dic)


from django.db import transaction
def comment(request):
    # 自己也可以给自己的文章评论内容
    if request.is_ajax():
        back_dic = {'code': 1000, 'msg': ""}
        if request.method == 'POST':
            if request.user.is_authenticated():
                article_id = request.POST.get('article_id')
                content = request.POST.get("content")
                parent_id = request.POST.get('parent_id')
                # 直接操作评论表 存储数据      两张表
                with transaction.atomic():
                    models.Article.objects.filter(pk=article_id).update(comment_num = F('comment_num') + 1)
                    models.Comment.objects.create(user=request.user,article_id=article_id,content=content,parent_id=parent_id)
                back_dic['msg'] = '评论成功'
            else:
                back_dic['code'] = 1001
                back_dic['msg'] = '用户未登陆'
            return JsonResponse(back_dic)

from app01.utils.mypage import Pagination
@login_required
def backend(request):
    # 获取当前用户对象所有的文章展示到页面
    article_list = models.Article.objects.filter(blog=request.user.blog)

    page_obj = Pagination(current_page=request.GET.get('page',1),all_count=article_list.count())
    page_queryset = article_list[page_obj.start:page_obj.end]
    return render(request,'backend/backend.html',locals())


修改头像
save.avatar.html
{% extends 'base.html' %}


{% block content %}
    <h3 class="text-center">修改头像</h3>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <p>
            原头像:
            <img src="/media/{{ request.user.avatar }}" alt="">
        </p>
        <p>

            <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" >

        </p>
        <input type="submit" class="btn btn-info">
    </form>
{% endblock %}

{% block js %}
    <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)
        }
    })
    </script>
{% endblock %}
修改头像视图
@login_required
def set_avatar(request):
    if request.method == 'POST':
        file_obj = request.FILES.get('avatar')
        # models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=file_obj)  # 不会再自动加avatar前缀
        # 1.自己手动加前缀
        # 2.换一种更新方式
        user_obj = request.user
        user_obj.avatar = file_obj
        user_obj.save()
        return redirect('/home/')
    blog = request.user.blog
    username = request.user.username
    return render(request,'set_avatar.html',locals())

posted on 2022-08-30 11:16  AprilX  阅读(73)  评论(1)    收藏  举报