close

Python Django 學習紀錄:架設個人部落格(三) 

評論功能與用戶登錄

一.、實現評論功能的方式

 1.第三方社會化評論插件: 

臉書、推特、微博...等第三方登錄

※可能因為第三方服務器出問題而導致插件也出問題

 2.Django 評論庫: 

使用 django-comment 實現 

 3.自己寫代碼實現: 

可以依據開發者的需求,做更細部的調整

 

二.、創建評論模型

 1.模型建構

  1. 評論對象
  2. 評論內容
  3. 評論時間
  4. 評論者   

 2.創建應用模型: 

開啟虛擬環境,創建一個名為comment的應用:   

python manage.py startapp comment

開啟<models.py> 創建 comment 模型

這邊要導入 contenttypes 框架 和 User模型,並添加幾條資料模型:   

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
# Create your models here.

class Comment(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) #透過外鍵指向模型
    object_id = models.PositiveIntegerField() #紀錄對應模型的主鍵值
    content_object = GenericForeignKey('content_type', 'object_id') #集合前兩項成一個通用外鍵
    
    text = models.TextField()
    comment_time = models.DateTimeField(auto_now_add=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        ordering = ['-comment_time']

在<admin.py>中註冊:   

from django.contrib import admin
from .models import Comment
# Register your models here.

@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
    list_display = ('content_object', 'text', 'comment_time', 'user')

在<settings.py>的應用列表中添加 comment:   

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'ckeditor',
    'ckeditor_uploader',
    'blog',
    'read_statistic',
    'comment',
]

記得進行數據庫遷移 (makemigrations)與應用 (migrate)

3.設置<urls.py>路徑: 

在 comment 應用目錄創建<urls.py>文件, 用來指向提交評論的路徑:

    path('comment/',include('comment.urls')),

在 mysite 專案目錄中的<urls.py>文件, 添加 comment 應用的路徑:

from django.urls import path

4.初步撰寫提交評論方法: 

在comment 應用中,創建 <views.py>文件

並在其中撰寫提交評論並儲存的方法:   

from django.shortcuts import render, redirect
from .models import Comment
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
# Create your views here.

def update_comment(request):

    user = request.user #取得用戶信息
    text = request.POST.get('text','') #取得提交的評論,若無則提交空白
    content_type = request.POST.get('content_type','')
    object_id = int(request.POST.get('object_id',''))
    model_class = ContentType.objects.get(model=content_type).model_class() #取得模型中的class
    model_obj = model_class.objects.get(pk=object_id)
    
    comment = Comment()
    comment.user = user
    comment.text = text
    comment.content_object = model_obj
    comment.save()

    referer = request.META.get('HTTP_REFERER', reverse('update_comment'))
    return redirect(referer)



 5.初步建立前端評論區域: 

評論區域將添加在文章詳細頁面<blog_detail.html>中

以下為初步的評論區域顯示:   

        <div class="row">
            <div class="col-xs-10 col-xs-offset-1">
                <div class="comment-area">
                    <h3 class="comment-area_title">提交評論</h3>
                    {% if user.is_authenticated %}
                        {{ user.username }},歡迎評論
                        <form action="{% url 'update_comment' %}" method="POST">
                            {% csrf_token %}
                            <textarea id="comment_text" name="text"></textarea>
                            <input type="hidden" name="content_type" value="blog">
                            <input type="hidden" name="object_id" value="{{ blog.pk }}">
                            <input type="submit" value="評論">
                        </form>
                    {% else %}
                        您尚未登錄,登錄之後方可評論
                        <a href="{% url 'login' %}">登錄</a>
                    {% endif %}
                </div>
                <div style="margin-top: 2em;border:1px dashed;padding:2em;margin-bottom: 2em;">評論列表區域</div>
            </div>
        </div>

 

三.、使用Django Form設置用戶登錄

由於這部分細節較多,除了表單模型外還有登錄驗證的部分要同時處理

所以這裡先傳送基本的表單到前台展示,接著才加入驗證登錄的細節

 1.創建 <forms.py> 文件: 

在 mysite 專案目錄中,創建 <forms.py> 文件

導入 Django 的form 套件,並為用戶登錄設置一個表單:   

from django import forms

class LoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField()

2.設置<urls.py>路徑: 

在 mysite 專案目錄中的<urls.py>, 添加 login 路徑:

    path('login/', views.login, name='login'),

3.初步撰寫後端登錄方法: 

在 mysite 專案目錄中的<views.py>, 添加 login 方法:

from .forms import LoginForm #導入登錄表單
def login(request):
    login_form = LoginForm()
    context ={}
    context['login_form'] = login_form
    return render(request, "login.html", context)

 

 4.初步撰寫登錄前端頁面: 

在<blog_detail.html>的評論區域,添加進行登錄的連結:   

        <div class="row">
            <div class="col-xs-10 col-xs-offset-1">
                <div class="comment-area">
                    <h3 class="comment-area_title">提交評論</h3>
                    {% if user.is_authenticated %}
                        {{ user.username }},歡迎評論
                        <form action="{% url 'update_comment' %}" method="POST">
                            {% csrf_token %}
                            <textarea id="comment_text" name="text"></textarea>
                            <input type="hidden" name="content_type" value="blog">
                            <input type="hidden" name="object_id" value="{{ blog.pk }}">
                            <input type="submit" value="評論">
                        </form>
                    {% else %}
                        您尚未登錄,登錄之後方可評論
                        <a href="{% url 'login' %}">登錄</a>
                    {% endif %}
                </div>
                <div style="margin-top: 2em;border:1px dashed;padding:2em;margin-bottom: 2em;">評論列表區域</div>
            </div>
        </div>

在 mysite 的 templates 目錄創建一個<login.html>,顯示登錄頁面:      

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

{% block title %}
登錄頁面
{% endblock %}

{% block nav_blog_active %}active{% endblock %}

{% block content %}
    <form action="{% url 'login' %}" method="POST">
        {% csrf_token %}
        {{ login_form }}
        <input type="submit" value="登錄">
    </form>
{% endblock %}

 5.完善後端登錄方法

前面已經將登錄系統的雛形建立好了

接下來就是加入登錄驗證、傳送數據來完善 login 方法

這裡將登錄驗證的部分移到表單中進行,所以只進行驗證成功後的處理

註解寫在程式碼之中,以下為代碼:   

def login(request):

    if request.method == 'POST': #檢查是否有POST數據
        login_form = LoginForm(request.POST) #提交POST數據

        if login_form.is_valid(): #若表單有效
            user = login_form.cleaned_data['user']  #取得用戶資料
            auth.login(request, user) #進行登錄
            return redirect(request.GET.get('from',reverse('home'))) #跳轉回前一個頁面

    else: #若沒有POST數據
        login_form = LoginForm() #加載頁面

    context = {}
    context['login_form'] = login_form
    return render(request, "login.html", context)

 6.修改登錄後前端頁面

由於這裡將登錄後頁面改為跳轉至前一個頁面

所以要修改前端<blog_detail.html>與<login.html>模板

使用get的方法,使登錄後跳轉至登錄前頁面

<blog_detail.html>模板的url修改:   

        <div class="row">
            <div class="col-xs-10 col-xs-offset-1">
                <div class="comment-area">
                    <h3 class="comment-area_title">提交評論</h3>
                    {% if user.is_authenticated %}
                        {{ user.username }},歡迎評論
                        <form action="{% url 'update_comment' %}" method="POST">
                            {% csrf_token %}
                            <textarea id="comment_text" name="text"></textarea>
                            <input type="hidden" name="content_type" value="blog">
                            <input type="hidden" name="object_id" value="{{ blog.pk }}">
                            <input type="submit" value="評論">
                        </form>
                    {% else %}
                        您尚未登錄,登錄之後方可評論
                        <a href="{% url 'login' %}?from={{ request.get_full_path }}">登錄</a>
                    {% endif %}
                </div>
                <div style="margin-top: 2em;border:1px dashed;padding:2em;margin-bottom: 2em;">評論列表區域</div>
            </div>
        </div>

<loginl>模板則是將 action (傳送資料的目的地)移除,使登錄後能正確導向登錄前頁面:   

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

{% block title %}
登錄頁面
{% endblock %}

{% block nav_blog_active %}active{% endblock %}

{% block content %}
    <form action="" method="POST">
        {% csrf_token %}
        {{ login_form }}
        <input type="submit" value="登錄">
    </form>
{% endblock %}

 6.在表單中進行登錄驗證

在LoginForm表單類別中,添加一個驗證用戶資料的方法

若驗證失敗則會拋出錯誤訊息到前端顯示

驗證成功則將用錄資料回傳到 login 方法進行登錄

註解寫在程式碼之中,以下為代碼:   

from django import forms
from django.contrib import auth
from django.urls import reverse

class LoginForm(forms.Form):
    username = forms.CharField(label='用戶名', required=True) #label修改標籤文字,required設置是否接受空白內容
    password = forms.CharField(label='密碼',widget=forms.PasswordInput) #widget定義標籤內容,這裡將密碼改為密文

    def clean(self): #在forms中進行驗證
        username = self.cleaned_data['username']
        password = self.cleaned_data['password']

        user = auth.authenticate(username=username, password=password)
        if user is None: #若驗證失敗
            raise forms.ValidationError('用戶名或密碼不正確') #拋出錯誤訊息
        else: #若驗證成功
            self.cleaned_data['user'] = user #返回登錄資料
        return self.cleaned_data

 7.美化 Form 表單顯示的資料

在form可以添加修改標籤中的屬性

搭配 bootstrap 可以達到美化表單的效果

(1)在 LoginForm表單 添加個別屬性調整

以下為代碼:   

from django import forms
from django.contrib import auth
from django.urls import reverse

class LoginForm(forms.Form):
     #label修改標籤文字,required不寫則預設接受空白內容,attrs添加bootstrap屬性,placeholder設置預設文字
    username = forms.CharField(label='用戶名', 
                               widget=forms.TextInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入用戶名'}))
     #widget定義標籤內容,這裡將密碼改為密文,attrs添加bootstrap屬性,placeholder設置預設文字
    password = forms.CharField(label='密碼',
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入密碼'}))

    def clean(self): #在forms中進行驗證
        username = self.cleaned_data['username']
        password = self.cleaned_data['password']

        user = auth.authenticate(username=username, password=password)
        if user is None: #若驗證失敗
            raise forms.ValidationError('用戶名或密碼不正確') #拋出錯誤訊息
        else: #若驗證成功
            self.cleaned_data['user'] = user #返回登錄資料
        return self.cleaned_data

(2)在<login.html>進行bootstrap美化

這裡添加了容器、響應式設計、面板標題與內容框架

而為了把用戶名與密碼後面的冒號去除,所以使用for迴圈對表單又進行了過濾

以下為代碼:   

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

{% block title %}
登錄頁面
{% endblock %}

{% block nav_blog_active %}active{% endblock %}

{% block content %}
    <div class="container">
        <div class="row">
            <!-- 響應式 -->
            <div class="col-xs-12 col-md-4 col-md-offset-4">
                <!-- 添加面板 -->
                <div class="panel panel-default">
                    <!-- 面板標題 -->
                    <div class="panel-heading">登錄</div>
                    <div class="panel-body">
                        <!-- 表單 -->
                        <form action="" method="POST">
                            {% csrf_token %}
                            <!-- 進行表單細部調整 -->
                            {% for field in login_form %}
                                <!-- 移除冒號 -->
                                <label for="{{ field.id_for_label }}">{{field.label}}</label>
                                {{ field }}
                                <!-- 錯誤訊息紅字顯示 -->
                                <p class="text-danger">{{ field.errors.as_text }}</p>
                            {% endfor %}
                            <span class="pull-left text-danger">{{ login_form.non_field_errors }}</span>
                            <input type="submit" value="登錄" class="btn btn-primary pull-right">
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>


{% endblock %}

(3)範例展示

下圖為目前登入頁面的範例展示

17.JPG

四.、設置用戶註冊

已經實作了登錄功能,接下來就能如法炮製的快速實現註冊功能

 1.創建 註冊表單: 

在 mysite 專案目錄中的 <forms.py> 文件

為用戶註冊設置一個表單:   

class RegForm(forms.Form):
    username = forms.CharField(label='用戶名', required=False,
                               max_length=20, min_length=3,
                               widget=forms.TextInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入3-20位用戶名'}))
    email = forms.EmailField(label='信箱', required=False,
                             widget=forms.EmailInput(
                                          attrs={'class':'form-control', 'placeholder':'請輸信箱'}))

    password = forms.CharField(label='密碼',
                               max_length=12, min_length=6,
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請輸入6-12位密碼'}))
    password_again = forms.CharField(label='再次輸入密碼',
                               max_length=12, min_length=6,
                               widget=forms.PasswordInput(
                                            attrs={'class':'form-control', 'placeholder':'請再次輸入密碼'}))
    def clean_username(self):
        username = self.cleaned_data['username']
        if User.objects.filter(username=username).exists():
            raise forms.ValidationError('用戶名稱已存在')
        return username
    def clean_email(self):
        email = self.cleaned_data['email']
        if User.objects.filter(email=email).exists():
            raise forms.ValidationError('信箱已存在')
        return email
    def clean_password_again(self):
        password = self.cleaned_data['password']
        password_again = self.cleaned_data['password_again']
        if password != password_again: #比對密碼是否相同
            raise forms.ValidationError('兩次輸入密碼不一致')
        return password

2.設置<urls.py>路徑: 

在 mysite 專案目錄中的<urls.py>, 添加 register 路徑:

    path('register/', views.register, name='register'),

3.撰寫後端註冊方法: 

在 mysite 專案目錄中的<views.py>, 添加 register 方法:

def register(request):

    if request.method == 'POST':
        reg_form = RegForm(request.POST)
        if reg_form.is_valid():
            username = reg_form.cleaned_data['username']
            email = reg_form.cleaned_data['email']
            password = reg_form.cleaned_data['password']
            #創建用戶
            user = User() #創建物件
            user.username = username
            user.email = email
            user.set_password(password) #儲存密文密碼
            user.save() #將用戶資料儲存至資料庫
            #登錄用戶
            user = auth.authenticate(username=username, password=password)
            auth.login(request, user)
            return redirect(request.GET.get('from',reverse('home'))) 
    else:
        reg_form = RegForm()
    context = {}
    context['reg_form'] = reg_form
    return render(request, "register.html", context)

 4.撰寫註冊頁面: 

在<blog_detail.html>的評論區域,添加進行註冊的連結:   

                <div class="comment-area">
                    <h3 class="comment-area_title">提交評論</h3>
                    {% if user.is_authenticated %}
                        {{ user.username }},歡迎評論
                        <form action="{% url 'update_comment' %}" method="POST">
                            {% csrf_token %}
                            <textarea id="comment_text" name="text"></textarea>
                            <input type="hidden" name="content_type" value="blog">
                            <input type="hidden" name="object_id" value="{{ blog.pk }}">
                            <input type="submit" value="評論">
                        </form>
                    {% else %}
                        您尚未登錄,登錄之後方可評論
                        <a href="{% url 'login' %}?from={{ request.get_full_path }}">登錄</a>
                        <a href="{% url 'register' %}?from={{ request.get_full_path }}">註冊</a>
                    {% endif %}
                </div>

在 mysite 的 templates 目錄創建一個<register.html>,顯示登錄頁面:      

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

{% block title %}
註冊頁面
{% endblock %}

{% block nav_blog_active %}active{% endblock %}

{% block content %}
    <div class="container">
        <div class="row">
            <!-- 響應式 -->
            <div class="col-xs-12 col-md-4 col-md-offset-4">
                <!-- 添加面板 -->
                <div class="panel panel-default">
                    <!-- 面板標題 -->
                    <div class="panel-heading">註冊</div>
                    <div class="panel-body">
                        <!-- 表單 -->
                        <form action="" method="POST">
                            {% csrf_token %}
                            <!-- 進行表單細部調整 -->
                            {% for field in reg_form %}
                                <!-- 移除冒號 -->
                                <label for="{{ field.id_for_label }}">{{field.label}}</label>
                                {{ field }}
                                <!-- 錯誤訊息紅字顯示 -->
                                <p class="text-danger">{{ field.errors.as_text }}</p>
                            {% endfor %}
                            <span class="pull-left text-danger">{{ reg_form.non_field_errors }}</span>
                            <input type="submit" value="註冊" class="btn btn-primary pull-right">
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>


{% endblock %}

18.JPG

 

四.、評論表單化、富文本編輯

前面只建立了評論功能的雛型,接下來要使用 form 表單來完善評論功能

並結合表單提交與富文本編輯,讓評論的編輯器更加豐富

最後再採用ajax異步刷新的方式,使評論功能更貼近用戶需求

 1.創建評論表單: 

在 comment 應用目錄中創建 <forms.py>

並在其中添加一個 CommentForm 表單,用來傳送評論內容

其中因為需要再表單中驗證用戶,所以需自訂user用法,再由update_comment傳入user參數來使用

以下為代碼:   

from django import forms
from django.contrib.contenttypes.models import ContentType
from django.db.models import ObjectDoesNotExist

class CommentForm(forms.Form):
    #由於Comment方法的通用性,這裡也要沿用ContentType框架
    content_type = forms.CharField(widget=forms.HiddenInput)
    object_id = forms.IntegerField(widget=forms.HiddenInput)
    text = forms.CharField(widget=forms.Textarea)
    #修改類別,將user寫入方法
    def __init__(self, *args, **kwargs):
        if 'user' in kwargs:
            self.user = kwargs.pop('user')
        super(CommentForm, self).__init__(*args, **kwargs)

    def clean(self):
        #判斷用戶是否登錄
        if self.user.is_authenticated:
            self.cleaned_data['user'] = self.user
        else:
            raise forms.ValidationError('用戶尚未登錄')
        #評論對象驗證
        content_type = self.cleaned_data['content_type']
        object_id = self.cleaned_data['object_id']
        text = self.cleaned_data['text']

        try:
            model_class = ContentType.objects.get(model=content_type).model_class() #取得模型中的評論方法
            model_obj = model_class.objects.get(pk=object_id) #取得評論對應的編號
            self.cleaned_data['content_object'] = model_obj
        except ObjectDoesNotExist: #若對象不存在
            raise forms.ValidationError('評論對象不存在')

        return self.cleaned_data 

 2.撰寫後端評論方法: 

(1)修改 blog_detail方法

在 blog 應用<views.py>中的 blog_detail 方法中

添加傳遞後端評論資料的代碼

導入ContentTypes框架與 Comment應用的comment模型 

其中也使用到initail初始化,來輔助form表單傳送文章類型與編號:   

from comment.forms import CommentForm
from django.contrib.contenttypes.models import ContentType
from comment.models import Comment
def blog_detail(request, blog_pk): #導入 blog_pk 變數
    context={} #建立字典
    blog = get_object_or_404(Blog, pk=blog_pk)  #獲取blog_pk
    read_cookie_key = read_statistic_once_read(request, blog) #使用閱讀計數

    #資料庫文章日期已經為由新到舊排列(新文章日期大於舊文章日期)
    #獲取前一篇文章,取得大於該文章的日期中的最後一篇
    context['previous_blog'] = Blog.objects.filter(created_time__gt=blog.created_time).last()
    #獲取後一篇文章,取得小於該文章的日期中的第一篇
    context['next_blog'] = Blog.objects.filter(created_time__lt=blog.created_time).first()
    context['blog'] = blog

    #獲取評論
        #取得該評論對應的文章類型與編號
    blog_content_type = ContentType.objects.get_for_model(blog)
    comments = Comment.objects.filter(content_type=blog_content_type, object_id=blog.pk)
        #使用initial在運行時聲明表單字段的初始值,將文章類型與編號賦值
    data={}
    data['content_type']= blog_content_type.model
    data['object_id'] = blog_pk
    context['comment_form'] = CommentForm(initial=data)
    context['comments'] = comments

    response = render(request, 'blog/blog_detail.html', context) #將字典返回到'blog_detail.html'
    response.set_cookie(read_cookie_key, 'true') #閱讀cookie
    return response

(2)修改 update_comment方法

修改 comment應用<views.py>中的update_comment方法

將其改為接收form表單資料後,傳送資料至前端頁面:   

from django.shortcuts import render, redirect
from .models import Comment
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from .forms import CommentForm
# Create your views here.

def update_comment(request):

    referer = request.META.get('HTTP_REFERER', reverse('update_comment'))
    comment_form = CommentForm(request.POST,user=request.user)#傳送user參數

    if comment_form.is_valid():  #如果檢查通過
        comment = Comment()
        comment.user = comment_form.cleaned_data['user']
        comment.text = comment_form.cleaned_data['text']
        comment.content_object = comment_form.cleaned_data['content_object']
        comment.save()
        return redirect(referer)
    else:
        return redirect('home.html')

 3.撰寫前端評論頁面: 

在 <blog_detail.html>前端頁面改為使用表單提交評論

並增加評論列表的顯示,並用safe將html碼實現以因應富文本編輯:   

        <div class="row">
            <div class="col-xs-10 col-xs-offset-1">
                <div class="comment-area">
                    <h3 class="comment-area_title">提交評論</h3>
                    {% if user.is_authenticated %}
                        <form action="{% url 'update_comment' %}" method="POST" style="overflow:hidden">
                            {% csrf_token %}
                            <label>{{ user.username }},歡迎評論</label>
                            {% for field in comment_form %}
                                {{ field }}
                            {% endfor %}
                            <input type="submit" value="評論" class="btn btn-primary pull-right" style="float:right">
                        </form>
                    {% else %}
                        您尚未登錄,登錄之後方可評論
                        <a class="btn btn-primary" href="{% url 'login' %}?from={{ request.get_full_path }}">登錄</a>
                        <a class="btn btn-primary" href="{% url 'register' %}?from={{ request.get_full_path }}">註冊</a>
                    {% endif %}
                </div>
                <div class="comment-area">
                    <h3 class="comment-area_title">評論列表</h3>
                    {% for comment in comments %}
                        <div>
                        {{ comment.user }}
                        ({{ comment.comment_time|date:"Y-m-d H:n:s" }}):
                        {{comment.text |safe }}
                        </div>
                    {% empty %}
                        暫無評論
                    {% endfor %}
                </div>
            </div

4.富文本編輯評論: 

(1)後端導入功能

在 comment 應用的 <forms.py>頁面導入表單用的富文本編輯套件並使用在評論內容欄位

以下為代碼:   

from ckeditor.widgets import CKEditorWidget
    text = forms.CharField(widget=CKEditorWidget())

如果要自定義富文本編輯的功能,可以詳閱此文檔進行調整

以下為自定義後的代碼:   

from ckeditor.widgets import CKEditorWidget
    text = forms.CharField(widget=CKEditorWidget(config_name='comment_ckeditor'))

在<settings.py>中,進行自定義的設置:   

#配置ckeditor
CKEDITOR_CONFIGS = {
    'default':{},
    'comment_ckeditor': {
        'toolbar': 'custom',
        'toolbar_custom': [
            ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript'],
            ["TextColor", "BGColor", 'RemoveFormat'],
            ['NumberedList', 'BulletedList'],
            ['Link', 'Unlink'],
            ["Smiley", "SpecialChar", 'Blockquote'],
        ],
        'width': 'auto',
        'height': '180',
        'tabSpaces': 4,
        'removePlugins': 'elementspath',
        'resize_enabled': False,
    }
}

(2)前端導入js文件

在前端頁面也需導入js文件才可使用

在<blog_detail.html>預留的擴充模塊中加入js文件的位址:   

{% block header_extends %}
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
{% endblock %}  

(3)修改CSS樣式

檢視HTML網頁可以找到ckeditor的div標籤為"django-ckeditor-widget"

因此可以對此在<blog.css>進行樣式修改:   

div.django-ckeditor-widget{
    width:100%;
}

21.JPG

五.、評論 ajax異步刷新

一般提交資料會刷新頁面

ajax提交則是一種不刷新頁面的異步刷新方式

可以參考此中文文檔

 1.添加 ajax 的 js 代碼: 

在<base.html>下方增加一個js擴充模塊

必須加在導入的js文件後,以下為代碼:   

    <!-- 導入jquery和bootstrap(jquery必須在前) -->
    <script type="text/javascript" src="{% static 'jquery-3.3.1.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
    {% block script_extends %}{% endblock %}

2.為Html標籤添加id: 

使用ajax需要使用Html標籤的id屬性來查找對應的內容

所以在需用到ajax的部分添加id屬性,並添加一條顯示錯誤訊息的html代碼

以下為<blog_detail.html>評論區域所需修改的部分:   

        <div class="row">
            <div class="col-xs-10 col-xs-offset-1">
                <div class="comment-area">
                    <h3 class="comment-area_title">提交評論</h3>
                    {% if user.is_authenticated %}
                        <!-- 添加提交評論id -->
                        <form id="comment_form" action="{% url 'update_comment' %}" method="POST" style="overflow:hidden">
                            {% csrf_token %}
                            <label>{{ user.username }},歡迎評論</label>
                            {% for field in comment_form %}
                                {{ field }}
                            {% endfor %}
                            <!-- 添加錯誤訊息 -->
                            <span id="comment_error" class="text-danger pull-left"></span>
                            <input type="submit" value="評論" class="btn btn-primary pull-right">
                        </form>
                    {% else %}
                        您尚未登錄,登錄之後方可評論
                        <a class="btn btn-primary" href="{% url 'login' %}?from={{ request.get_full_path }}">登錄</a>
                        <a class="btn btn-primary" href="{% url 'register' %}?from={{ request.get_full_path }}">註冊</a>
                    {% endif %}
                </div>
                <div class="comment-area">
                    <h3 class="comment-area_title">評論列表</h3>
                    <!-- 添加評論列表id -->
                    <div id="comment_list">
                        {% for comment in comments %}
                            <div>
                            {{ comment.user }}
                            ({{ comment.comment_time|date:"Y-m-d H:i:s" }}):
                            {{comment.text |safe }}
                            </div>
                        {% empty %}
                            暫無評論
                        {% endfor %}
                    </div>
                </div>  
            </div>
        </div>

3.:修改提交評論方法 

因為提交對象要改為ajax,所以導入Json回傳的方法

並建立字典來將資料回傳到前端的ajax

由於ajax的時間戳不會根據django setting的TIME_ZONE做調整

因此這邊要導入datetime來調整時間

修改comment應用的<views.py>的update_comment方法:   

from django.http import JsonResponse
import datetime

def update_comment(request):

    referer = request.META.get('HTTP_REFERER', reverse('update_comment'))
    comment_form = CommentForm(request.POST,user=request.user)#傳送user參數

    data={}

    if comment_form.is_valid():  #如果檢查通過
        comment = Comment()
        comment.user = comment_form.cleaned_data['user']
        comment.text = comment_form.cleaned_data['text']
        comment.content_object = comment_form.cleaned_data['content_object']
        comment.save()
        #返回數據
        data['status'] = 'SUCCESS'
        data['username'] = comment.user.username
        data['comment_time'] = (comment.comment_time + datetime.timedelta(hours=8)).strftime("%Y-%m-%d %H:%M:%S")
        data['text'] = comment.text

    else:
        data['status'] = 'ERROR'
        #返回表單錯誤訊息
        data['message'] = list(comment_form.errors.values())[0]
    return JsonResponse(data)

4.:撰寫ajax 

將ajax撰寫在<blog_detail.html>的js擴充模塊

  1. 點擊評論
  2. 判斷是否為空內容
  3. 提交數據
  4. 清空編輯框
  5. 插入數據更新列表

其中因為富文本編輯的關係,需要更新數據到textarea

以下為代碼:   

{% block script_extends %}
    <script type="text/javascript">
        $("#comment_form").submit(function(){ //選擇(#為id)comment_form,submit為觸發提交事件,會回調一個function
            //判斷是否為空
            $("#comment_error").text('')
            if(CKEDITOR.instances['id_text'].document.getBody().getText().trim()==''){
                $('#comment_error').text('評論不能為空');
                return false;
            }
            //更新數據到textarea
            CKEDITOR.instances['id_text'].updateElement();
            //異步提交
            $.ajax({
                url: "{% url 'update_comment' %}", //提交資料的目的路徑
                type: 'POST',//提交資料的類型
                data: $(this).serialize(), //序列化表單值
                cache: false, //提交網址不須緩存,因此關閉緩存
                success: function(data){
                    console.log(data);
                    if(data['status'] == 'SUCCESS'){
                        //插入數據
                        var comment_html = '<div>' + data['username'] +
                                           '(' + data['comment_time'] + '):'
                                           + data['text'] + '</div>';
                        $("#comment_list").prepend(comment_html);
                        //清空編輯框內容
                        CKEDITOR.instances['id_text'].setData('');
                    }else{
                        //顯示錯誤訊息
                        $('#comment_error').text(data['message']);
                    }
                },
                error: function(xhr){
                    console.log(xhr);


                },
            });
            return false;
        });
    </script>
{% endblock %}

 

在表單text資料中,添加關於錯誤訊息的自訂內容

以下為代碼:   

    text = forms.CharField(widget=CKEditorWidget(config_name='comment_ckeditor'),
                           error_messages={'required':'評論內容不能為空'})
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 ivankao 的頭像
    ivankao

    IvanKao的部落格

    ivankao 發表在 痞客邦 留言(0) 人氣()