Django搭建我的博客:用django-notifications实现消息通知

凭借你勤奋的写做,拜读你文章的用户愈来愈多,他们的评论也分散在众多的文章之中。做为博主,读者的留言确定是要都看的;而读者给你留言,天然也但愿获得回复。css

怎么将未读的留言呈现给正确的用户呢?总不能用户本身去茫茫文章中寻找吧,那也太蠢了。给评论增长通知功能就是很流行的解决方案:好比微信朋友圈留言的通知、新浪微博留言的通知、以及各类社交平台的“小红点”。html

本篇将以django-notifications为基础,很是高效的搭建一个简易的通知系统。前端

发送通知

前面的步骤咱们已经很熟悉了。python

首先安装django-notificationsgit

(env) > pip install django-notifications-hq
复制代码

注册app:github

my_blog/settings.py

...
INSTALLED_APPS = [
    ...
    'notifications',
    ...
]
...
复制代码

在根路由中安装路径:django

my_blog/urls.py

...
import notifications.urls

urlpatterns = [
    ...
    path('inbox/notifications/', include(notifications.urls, namespace='notifications')),
    ...
]
...
复制代码

注意这里的notifications.urls没有像以前同样用字符串,是为了确保模块安装到正确的命名空间中。bash

数据迁移:服务器

(env) > python manage.py migrate
复制代码

app就安装好了。微信

接下来你就能够在项目的任何地方发送通知了!像这样:

from notifications.signals import notify

notify.send(actor, recipient, verb, target, action_object)
复制代码

其中的参数释义:

  • actor:发送通知的对象
  • recipient:接收通知的对象
  • verb:动词短语
  • target:连接到动做的对象*(可选)*
  • action_object:执行通知的对象(可选)

有点绕,举个栗子:杜赛 (actor)Django搭建我的博客 (target) 中对 (recipient) 发表了 (verb) 评论 (action_object)

由于咱们想要在用户发表评论的时候发送通知,所以修改一下发表评论的视图:

comments/views.py

...
from notifications.signals import notify
from django.contrib.auth.models import User

...
def post_comment(...):
    ...

    # 已有代码,建立新回复
    if comment_form.is_valid():
        ...

        # 已有代码,二级回复
        if parent_comment_id:
            ...

            # 新增代码,给其余用户发送通知
            if not parent_comment.user.is_superuser:
                notify.send(
                    request.user,
                    recipient=parent_comment.user,
                    verb='回复了你',
                    target=article,
                    action_object=new_comment,
                )

            return HttpResponse('200 OK')
        
        new_comment.save()

        # 新增代码,给管理员发送通知
        if not request.user.is_superuser:
            notify.send(
                    request.user,
                    recipient=User.objects.filter(is_superuser=1),
                    verb='回复了你',
                    target=article,
                    action_object=new_comment,
                )
            
        return redirect(article)
...
复制代码

2019/6/4 修正此代码。旧代码错误的将发送给管理员的notify放在了new_comment.save()的前面,致使action_object存储为NULL

增长了两条notify的语句,分别位于两个if语句中:

  • 第一个notify:用户之间能够互相评论,所以须要发送通知。if语句是为了防止管理员收到重复的通知。
  • 第二个notify:全部的评论都会给管理员(也就是博主)发送通知,除了管理员本身。

其余的代码没有变化,注意位置不要错就好了。你能够试着发送几条评论,而后打开SQLiteStudio,查看notifications_notification表中的数据变化。

有效代码实际上只有4行,咱们就完成了建立、发送通知的功能!相信你已经逐渐体会到运用第三方库带来的便利了。这就是站在了“巨人们”的肩膀上。

小红点

后台建立通知的逻辑已经写好了,可是若是不能在前端显示出来,那也没起到做用。

而前端显示消息通知比较流行的是**“小红点”,流行得都已经泛滥了,尽管不少软件其实根本就不须要。另外一种形式是消息徽章**,即一个红色方框中带有消息条目的计数。这两种方式都会用到博客页面中。

在位置的选择上,header是很合适的,由于它在博客的全部位置都会显示,很符合通知自己的定位。

所以修改header.html

templates/header.html

<!-- 引入notifications的模板标签 -->
{% load notifications_tags %}
{% notifications_unread as unread_count %}

...

<!-- 已有代码,用户下拉框 -->
<li class="nav-item dropdown">
    <a class="nav-link dropdown-toggle" ...>
        
        <!-- 新增代码,小红点 -->
        {% if unread_count %}
            <svg viewBox="0 0 8 8" width="8px" height="8px">
                <circle cx="4" cy="4" r="4" fill="#ff6b6b" ></circle>
            </svg>
        {% endif %}
        
        {{ user.username }}
    </a>
    <!-- 已有代码,下拉框中的连接 -->
    <div class="dropdown-menu" ...>
        
        <!-- 新增代码,通知计数 -->
        <a class="dropdown-item" href="#">通知
            {% if unread_count %}
            <span class="badge badge-danger">{{ unread_count }}</span>
            {% endif %}
        </a>
        
        ...
        <a ...>退出登陆</a>
    </div>
</li>

...
复制代码

django-notifications自带简易的模板标签,能够在前台模板中调用重要的通知相关的对象,在顶部引入就可使用了。好比unread_count是当前用户的未读通知的计数。

Bootstrap自带有徽章的样式,可是却没有小红点的样式(至少我没有找到),因此就只能用svg本身画了,好在也不难。

svg是绘制矢量图形的标签,这里就不展开讲了,感兴趣请自行搜索相关文章。

随便评论几条,刷新页面看一看:

效果不错。可是连接的href是空的,接下来就处理。

未读与已读

既然是通知,那么确定可以分红**”未读的“”已读的“**两种。在适当的时候,未读通知又须要转换为已读的。如今咱们来开发功能集中处理它。

通知是一个独立的功能,之后有可能在任何地方用到,放到评论app中彷佛并不合适。

因此新建一个app:

(env) > python manage.py startapp notice
复制代码

注册:

my_blog/settings.py

...
INSTALLED_APPS = [
    ...
    'notice',
]
...
复制代码

根路由:

my_blog/urls.py

...
urlpatterns = [
    ...
    # notice
    path('notice/', include('notice.urls', namespace='notice')),
]
...
复制代码

接下来就是视图了。以前全部的视图都是用的视图函数,此次咱们更进一步,用类视图来完成。忘记什么是类视图的,回忆一下前面类的视图章节。

编写视图:

notice/views.py

from django.shortcuts import render, redirect
from django.views import View
from django.views.generic import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from article.models import ArticlePost


class CommentNoticeListView(LoginRequiredMixin, ListView):
    """通知列表"""
    # 上下文的名称
    context_object_name = 'notices'
    # 模板位置
    template_name = 'notice/list.html'
    # 登陆重定向
    login_url = '/userprofile/login/'

    # 未读通知的查询集
    def get_queryset(self):
        return self.request.user.notifications.unread()


class CommentNoticeUpdateView(View):
    """更新通知状态"""
    # 处理 get 请求
    def get(self, request):
        # 获取未读消息
        notice_id = request.GET.get('notice_id')
        # 更新单条通知
        if notice_id:
            article = ArticlePost.objects.get(id=request.GET.get('article_id'))
            request.user.notifications.get(id=notice_id).mark_as_read()
            return redirect(article)
        # 更新所有通知
        else:
            request.user.notifications.mark_all_as_read()
            return redirect('notice:list')
复制代码

视图共两个。

CommentNoticeListView:继承自ListView,用于展现全部的未读通知。get_queryset方法返回了传递给模板的上下文对象,unread()方法是django-notifications提供的,用于获取全部未读通知的集合。另外视图还继承了**“混入类”**LoginRequiredMixin,要求调用此视图必须先登陆。

CommentNoticeUpdateView:继承自View,得到了如get、post等基础的方法。mark_as_read()mark_all_as_read都是模块提供的方法,用于将未读通知转换为已读。if语句用来判断转换单条仍是全部未读通知。

重复:阅读有困难的同窗,请从新阅读类的视图章节,或者去官方文档查阅。

接下来就是新建urls.py了,写入:

notice/urls.py

from django.urls import path
from . import views

app_name = 'notice'

urlpatterns = [
    # 通知列表
    path('list/', views.CommentNoticeListView.as_view(), name='list'),
    # 更新通知状态
    path('update/', views.CommentNoticeUpdateView.as_view(), name='update'),
]
复制代码

path()的第二个参数只能接收函数,所以别忘了要调用类视图的as_view()方法。

**集中处理通知须要一个单独的页面。**新建templates/notice/list.html模板文件:

templates/notice/list.html

{% extends "base.html" %}
{% load staticfiles %}

{% block title %}
    通知
{% endblock title %}

{% block content %}
<div class="container">
    <div class="row mt-4 ml-4">
        <a href="{% url "notice:update" %}" class="btn btn-warning" role="button">清空全部通知</a>
    </div>
    <!-- 未读通知列表 -->
    <div class="row mt-2 ml-4">
        <ul class="list-group">
            {% for notice in notices %}
                <li class="list-group-item" id="notice_link">
                    <a href="{% url "notice:update" %}?article_id={{ notice.target.id }}&notice_id={{ notice.id }}" target="_blank" >
                    <span style="color: #5897fb;">
                        {{ notice.actor }}
                    </span><span style="color: #01a252;">{{ notice.target }}</span> {{ notice.verb }}。
                    </a>
                    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{{ notice.timestamp|date:"Y/m/d H:i" }}
                </li>
            {% endfor %}
        </ul>
    </div>
</div>

<style> #notice_link a:link { color: black; } #notice_link a:visited { color: lightgrey; } </style>
{% endblock content %}
复制代码

模板中主要提供了两个功能:

  • 点击button按钮清空全部未读通知
  • 点击单个通知,将其转换为已读通知,并前往此评论所在的文章

末尾<style>标签中的伪类选择器,做用是将已经点击过的通知字体颜色转换为浅灰色,优化用户体验。

最后就是补上入口

templates/header.html

...
<a ... href="{% url "notice:list" %}">通知...</a>
...
复制代码

这样就完成了。

打开服务器,用一个普通帐号评论几条,再登陆管理员帐号并进入通知页面:

就能看到不错的效果了。实现的效果是仅展现未读通知,固然也能够在下边展现已读通知,方便用户追溯。

总结

通知功能很是的重要,特别是在你的博客成长壮大了以后。你还能够把它用在别的地方,好比每新发表一篇文章,就给你全部的“粉丝”推送一条通知,提醒他们能够来拜读了。具体如何扩展运用,就靠你脑洞大开了。

**课后做业:**前面的代码中,若是用户本身评论本身,一样也会收到通知。这种通知并无必要,请修正它。

遇到困难,在教程示例代码找答案吧。


相关文章
相关标签/搜索