Django搭建個人部落格:用django-notifications實現訊息通知

杜賽_dusai發表於2019-05-18

憑藉你勤奮的寫作,拜讀你文章的使用者越來越多,他們的評論也分散在眾多的文章之中。作為博主,讀者的留言肯定是要都看的;而讀者給你留言,自然也希望得到回覆。

怎麼將未讀的留言呈現給正確的使用者呢?總不能使用者自己去茫茫文章中尋找吧,那也太蠢了。給評論增加通知功能就是很流行的解決方案:比如微信朋友圈留言的通知、新浪微博留言的通知、以及各種社交平臺的“小紅點”。

本篇將以django-notifications為基礎,非常高效的搭建一個簡易的通知系統。

傳送通知

前面的步驟我們已經很熟悉了。

首先安裝django-notifications

(env) > pip install django-notifications-hq
複製程式碼

註冊app:

my_blog/settings.py

...
INSTALLED_APPS = [
    ...
    'notifications',
    ...
]
...
複製程式碼

在根路由中安裝路徑:

my_blog/urls.py

...
import notifications.urls

urlpatterns = [
    ...
    path('inbox/notifications/', include(notifications.urls, namespace='notifications')),
    ...
]
...
複製程式碼

注意這裡的notifications.urls沒有像之前一樣用字串,是為了確保模組安裝到正確的名稱空間中。

資料遷移:

(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是繪製向量圖形的標籤,這裡就不展開講了,感興趣請自行搜尋相關文章。

隨便評論幾條,重新整理頁面看一看:

Django搭建個人部落格:用django-notifications實現訊息通知

效果不錯。但是連結的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>
...
複製程式碼

這樣就完成了。

開啟伺服器,用一個普通賬號評論幾條,再登入管理員賬號並進入通知頁面:

Django搭建個人部落格:用django-notifications實現訊息通知

就能看到不錯的效果了。實現的效果是僅展示未讀通知,當然也可以在下邊展示已讀通知,方便使用者追溯。

總結

通知功能非常的重要,特別是在你的部落格成長壯大了之後。你還可以把它用在別的地方,比如每新發表一篇文章,就給你所有的“粉絲”推送一條通知,提醒他們可以來拜讀了。具體如何擴充套件運用,就靠你腦洞大開了。

**課後作業:**前面的程式碼中,如果使用者自己評論自己,同樣也會收到通知。這種通知並沒有必要,請修正它。

遇到困難,在教程示例程式碼找答案吧。


相關文章