Django图书荐购云平台开发与实践 - 6留言板模块

留言板模块包括读者留言和管理员回复,屏蔽或删除留言。

  1. 读者留言按时间倒序排列。
  2. 管理员回复的留言在每条读者留言的下方。
  3. 管理员可以对评论进行回复,屏蔽或删除某条留言。

首先构造留言板的模型。

#comment 模块 models.py
from django.db import models
from user.models import MyUser

class Message(models.Model):
    msgid = models.AutoField('序号', primary_key=True)
    content = models.TextField('留言内容', )
    date = models.DateField('留言时间',)
    from_user = models.ForeignKey(MyUser, on_delete=models.CASCADE, verbose_name='留言者')
    msg_status= models.IntegerField('留言状态', default=1)
    parent_msgid =  models.ForeignKey('self', verbose_name="回复留言", on_delete=models.CASCADE, blank=True, null=True)
    def __str__(self):
        return self.content

    class Meta:
        verbose_name = '用户留言'
        verbose_name_plural = '用户留言'

上面的字段中,msg_status用来表示是否显示用户的留言,默认为1表示显示留言,parent_msgid外键关联自身的msgid,表示是对关联的留言的回复留言。其他的字段很好理解。模型建好以后执行数据迁移,创建好数据表。
然后到项目的urls.py中开启最后一道大门
下面就是编写comment应用的URL地址信息和视图函数,以及在templates下添加message.html模板文件

# comment 下的 urls.py
from django.urls import path
from . import views

#设置URL的地址信息
urlpatterns = [
    path('message/', views.messageView, name='message'),
]
#comment 下的 views.py 
from django.shortcuts import render
from .models import Message
from user.models import Library, MyUser
from datetime import datetime
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib.auth.decorators import login_required

@login_required(login_url='/user/login.html')
def messageView(request, page):
    #获取该用户所属馆的馆名和馆实例对象
    lib_name = request.user.lib_id
    library = Library.objects.get(lib_name=lib_name)
 
    #如果是POST请求,则说明用户在发表留言
    if request.method == 'POST':
        user_id = MyUser.objects.get(id=request.user.id)
        time = datetime.now()
        content = request.POST.get('content')
        Message.objects.create(content=content, date=time, from_user=user_id)
    
    #查询所在馆的留言记录倒序排列
    messages = Message.objects.select_related('from_user__lib_id').filter(from_user__lib_id__lib_id=library.lib_id).order_by('-msgid')

    #分页
    paginator = Paginator(messages, 5)
    try:
        pageInfo = paginator.page(page)
    except PageNotAnInteger:
        pageInfo = paginator.page(1)
    except EmptyPage:
        pageInfo = paginator.page(paginator.num_pages)

    return render(request, 'message.html', locals())

简单的讲解一下逻辑,接收到用户请求后,首先获取图书馆馆名和馆的实例对象,然后判断是不是POST请求,如果是则说明用户在发表留言,将留言相关信息保存到数据库,如果是GET请求,获取该用户所在图书馆的所有留言记录并且倒序排列,输出到模板文件message.html。最后看下模板文件。

{% extends "base.html" %}
{% load staticfiles %}
<link rel="stylesheet" href="{% static 'css/mycss.css' %}">
{# 隐藏顶部搜索框 #}
{% block search %}
{% endblock %}
{% block body %}
<div class="alert alert-success mx-auto">
    <span><h5>{{ lib_name }}-留言板</h5></span>
</div>

<ul class="list-group">
    {% for msg in pageInfo %}
        {% if msg.parent_msgid == NULL %}
        <div class="alert alert-info">
            <lable>由 <strong>{{ msg.from_user }}</strong>&nbsp;&nbsp;<small>{{ msg.date }} </small>留言</lable>
            <li class="list-group-item">{{ msg.content }}</li>
        </div>
        {% else %}
        <div class="alert alert-info">
            <lable>由 <strong>{{ msg.parent_msgid.from_user }}</strong>&nbsp;&nbsp;<small>{{ msg.parent_msgid.date }} </small>留言
            <li class="list-group-item">{{ msg.parent_msgid.content }}</li>
        </div>
        <div class="alert alert-warning">
            <div class="clearfix">
                <div class="float-right">
                    <lable>由 <strong>{{ msg.from_user }}</strong>&nbsp;&nbsp;<small>{{ msg.date }} </small>回复</lable>  
                </div>
            </div>
            <li class="list-group-item">{{ msg.content }}</li>   
        </div>

        {% endif %}
    {% endfor %}
</ul>

{# 分页 #}
<div class="center">
<ul class="pagination">
    {% if pageInfo.has_previous %}
    <li class="page-item"><a class="page-link" href="{% url 'message' pageInfo.previous_page_number %}">上一页</a></li>
    {% endif %}
    {% if pageInfo.has_next %}
    <li class="page-item"><a class="page-link" href="{% url 'message' pageInfo.next_page_number %}">下一页</a></li>
    {% endif %}
</ul>
</div>

<form  action="{% url 'message' 1 %}" method="POST">
    {% csrf_token %}
    <div class="form-group alert alert-info col-sm-12">
        <label for="comment">用户名:<strong>{{ user.username }}</strong></label>
        <textarea class="form-control" rows="5" name="content" id="comment" placeholder="请留言..." required></textarea>    
        <button type="submit" class="btn btn-primary">留言</button>
    </div>
</form>
{% endblock %}

实现效果如下:


image.png

然后就是做个留言板后台管理模块,在library应用里添加新的URL地址信息

    path('lib_msgadmin/<int:page>/', views.LibMsgAdminView, name='lib_msgadmin'),

编写留言板后台管理的视图函数LibMsgAdminView,别忘记头部导入相关模块和模型,这里只贴出了部分代码。

#登陆验证和身份验证
@login_required(login_url='/user/login.html')
@permission_required(perm='recommend.change_recommend')
#图书馆留言管理
def LibMsgAdminView(request, page):
    #获取该用户所属馆名
    lib_name = request.user.lib_id
    #获取该馆实例对象
    library = Library.objects.get(lib_name=lib_name)
    #如果是POST请求,则说明是管理员在回复用户留言
    if request.method == 'POST':
        #获取用户的实例对象
        myuser = MyUser.objects.get(id=request.user.id)
        #通过隐藏表单获取用户留言的msgid
        msgid=request.POST.get('msgid')
        #转为msg的实例对象
        msginstance = Message.objects.get(msgid=msgid)
        content = request.POST.get('content')
        time = datetime.now()

        Message.objects.create(content=content, date=time, from_user=myuser, parent_msgid=msginstance)

    #查询所在馆的留言记录倒序排列
    messages = Message.objects.select_related('from_user__lib_id').filter(from_user__lib_id__lib_id=library.lib_id).order_by('-msgid')

    #分页
    paginator = Paginator(messages, 5)
    try:
        pageInfo = paginator.page(page)
    except PageNotAnInteger:
        pageInfo = paginator.page(1)
    except EmptyPage:
        pageInfo = paginator.page(paginator.num_pages)

    return render(request, 'lib_msgadmin.html', locals())

后台管理的逻辑和前台差不多,主要差别就是回复留言的时候获取了隐藏表单中用户留言的msgid保存到数据表中的parent_id外键中。
模板文件lib_msgadmin.html如下:

{% extends "base.html" %}
{% load staticfiles %}
<link rel="stylesheet" href="{% static 'css/mycss.css' %}">
{# 隐藏顶部搜索框 #}
{% block search %}
{% endblock %}
{% block body %}
<div class="alert alert-success mx-auto">
    <span><h5>{{ lib_name }}-留言板管理</h5></span>
    <label><small>点击留言内容可回复留言</small></label>
</div>

<ul class="list-group">
    {% for msg in pageInfo %}
    {% if msg.parent_msgid == NULL %}
    <div class="alert alert-info">
        <lable>由 <strong>{{ msg.from_user }}</strong>&nbsp;&nbsp;<small>{{ msg.date }} </small>留言</lable>
        <li class="list-group-item" data-toggle="modal" data-target="#modal{{ forloop.counter }}">{{ msg.content }}</li>
    </div>
    <div class="modal fade" id="modal{{ forloop.counter }}">
        <div class="modal-dialog">
            <div class="modal-content">
                <!-- 模态框主体 -->
                <form  action="{% url 'lib_msgadmin' page %}" method="POST">
                    {% csrf_token %}
                    <input type="hidden" name="msgid" value="{{ msg.msgid }}">
                    <div class="form-group alert alert-info col-sm-12">
                        <label for="comment">回复用户:<strong>{{ msg.from_user }}</strong></label>
                        <textarea class="form-control" rows="5" name="content" id="comment" placeholder="回复留言..." required></textarea>    
                        <button type="submit" class="btn btn-primary">回复</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    {% else %}
    <div class="alert alert-info">
        <lable>由 <strong>{{ msg.parent_msgid.from_user }}</strong>&nbsp;&nbsp;<small>{{ msg.parent_msgid.date }} </small>留言
        <li class="list-group-item">{{ msg.parent_msgid.content }}</li>
    </div>
    <div class="alert alert-warning">
        <div class="clearfix">
            <div class="float-right">
                <lable>由 <strong>{{ msg.from_user }}</strong>&nbsp;&nbsp;<small>{{ msg.date }} </small>回复</lable>  
            </div>
        </div>
        <li class="list-group-item">{{ msg.content }}</li>   
    </div>
    {% endif %}
    {% endfor %}
</ul>

{# 分页 #}
<div class="center">
<ul class="pagination">
    {% if pageInfo.has_previous %}
    <li class="page-item"><a class="page-link" href="{% url 'lib_msgadmin' pageInfo.previous_page_number %}">上一页</a></li>
    {% endif %}
    {% if pageInfo.has_next %}
    <li class="page-item"><a class="page-link" href="{% url 'lib_msgadmin' pageInfo.next_page_number %}">下一页</a></li>
    {% endif %}
</ul>
</div>
{% endblock %}

回复留言


image.png

回复后的效果


image.png

留言板里的效果


image.png

如果需要对留言板有一个控制,比如能够屏蔽某些非法留言,就可以用到msg_status这个字段,数据表中默认是 1,我们把它该成非1的值就能实现屏蔽留言的功能。
首先到浏览展示的模板文件message.html中添加一个条件判断。

 {% if msg.msg_status == 1 %}

然后在留言板管理后台模板lib_msgadmin.html添加一个屏蔽留言的功能按钮

        {% if msg.msg_status == 1 %}
            <div class="float-right">
                <a href="{% url 'lib_msgadmin' page %}?msgid={{ msg.msgid }}"><button type="text" class="btn btn-secondary">屏蔽该留言</button></a>
            </div>
            {% else %}
            <div class="float-right">
                <button type="text" class="btn btn-secondary">已屏蔽</button>
            </div>
            {% endif %}

模板将浏览的msgid通过GET参数传递给视图函数处理。
视图函数中多做一个If判断,将GET获取到的msgid的msg_status设置为0或任意其他值即可实现留言的屏蔽。

 #如果通过GET获取到了msgid,说明是管理员用户要屏蔽该条留言
        if request.GET.get('msgid'):
            msgid = request.GET.get('msgid')
            #将留言状态设置为0
            Message.objects.filter(msgid=msgid).update(msg_status=0)

image.png

点击屏蔽按钮后,该条留言成为已屏蔽状态,进入留言板模板可以发现被屏蔽的留言不见了。


image.png

但是,我们发现留言被屏蔽了2条,现在第一页只有3条留言了,被屏蔽的留言虽然没有显示,但还是被从数据库中读取出来了,这种情况,我们只要在查询数据的时候再加 msg_status=1 这个条件,这样读取的就全是正常状态的留言了。

#comment 的 views.py
messages = Message.objects.select_related('from_user__lib_id').filter(from_user__lib_id__lib_id=library.lib_id,msg_status=1).order_by('-msgid')

现在再看留言板,第一页就是完整的5条留言了。


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容