第 12 章    按讚與留言

▸ 在 Article model 新增 likes 欄位

article/models.py
from django.db import models
from account.models import User


class Article(models.Model):
    ...
    pubDateTime = models.DateTimeField(auto_now_add=True)
    likes = models.ManyToManyField(User)
    ...

▸ 按讚函式

article/views.py
...
def articleSearch(request):
    ...


def articleLike(request, articleId):
    '''
    Add the user to the 'likes' field:
        1. Get the article; redirect to 404 if not found
        2. If the user does not exist in the "likes" field, add him/her
        3. Finally, call articleRead() function to render the article
    '''
    article = get_object_or_404(Article, id=articleId)
    if request.user not in article.likes.all():
        article.likes.add(request.user)
    return articleRead(request, articleId)

▸ 按讚 URL mapping

article/urls.py
...

urlpatterns = [
    ...
    path('articleSearch/', views.articleSearch, name='articleSearch'),
    path('articleLike/<int:articleId>/', views.articleLike, name='articleLike'),
]

▸ 按讚影像檔

like.png

▸ 顯示按讚人數

article/templates/article/article.html
  ...
  <p>發表時間:{{ article.pubDateTime|date:'Y-m-d H:i' }}</p>
  <div class="articleContent">{{ article.content|linebreaks|truncatechars_html:30 }}</div>
  <p class=like>
    <img id=like src="{% static 'main/img/like.png' %}" alt="Like"> {{ article.likes.count }}
  </p>
  {% for comment in comments %}
  ...

▸ 設定按讚樣式

article/static/article/css/article.css
...
.table-hover tr:hover td {
  background-color: #cdeaf0;
}

.like {
  font-weight: bold;
  color: #3e7bd1;
}

img#like {
  vertical-align: middle;
  width: 1.6em;
}

▸ 加上按讚連結

article/templates/article/articleRead.html
...
<div class="articleContent">{{ article.content|linebreaks }}</div>
<p class=like>
  <img id=like src="{% static 'main/img/like.png' %}" alt="Like"> {{ article.likes.count }}
  {% if user.is_authenticated %}
    <a href="{% url 'article:articleLike' article.id %}">讚</a>
  {% endif %}
</p>
{% for comment in comments %}
  ...

▸ 加上留言者欄位

article/models.py
...


class Comment(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.CharField(max_length=128)
    ...

▸ 修改填充程式

populate/admin.py
...

def populate(): 
    print('Creating admin account ... ', end='')
    User.objects.all().delete()
    User.objects.create_superuser(username='admin', password='admin', email=None, fullName='管理者')
    print('done')

...
populate/users.py
...

def populate(): 
    print('Creating user accounts ... ', end='')
    User.objects.exclude(is_superuser=True).delete()
    for i in range(5):
        username = 'user' + str(i)
        User.objects.create_user(username=username, password=username, email=None, fullName=username)
    print('done')

...
populate/article.py
from populate import base
from account.models import User
from article.models import Article, Comment

...


def populate():
    ...
    
    admin = User.objects.get(is_superuser=True)
    for title in titles:
        ...
            Comment.objects.create(article=article, user=admin, content=comment)
    ...

▸ 在範本中加入留言者

article/templates/article/article.html
...
  <div class="commentDiv">
    <span class="commentAuthor">{{ comment.user.fullName }}</span>
    <span class="comment">{{ comment.content }}</span>
    ...
  </div>
...
article/templates/article/articleRead.html
...
  <div class="commentDiv">
    <span class="commentAuthor">{{ comment.user.fullName }}</span>
    <span class=comment>{{ comment.content }}</span>
    ...
  </div>
...

▸ 設定樣式

article/static/article/css/article.css
...

.commentDiv {
  margin-top: 1em;
}

.comment, .commentAuthor {
  font-size: 0.8em;
}

.commentAuthor {
  font-weight: bold;
  color: #186caf;
}

.commentTime {
  ...
}
...

▸ 新增留言函式

article/views.py
def articleLike(request, articleId):
    ...


def commentCreate(request, articleId):
    '''
    Create a comment for an article:
        1. Get the "comment" from the HTML form
        2. Store it to database
    '''
    if request.method == 'GET':
        return articleRead(request, articleId)

    # POST
    comment = request.POST.get('comment')
    if comment:
        comment = comment.strip()
    if not comment:
        return redirect('article:articleRead', articleId=articleId)

    article = get_object_or_404(Article, id=articleId)
    Comment.objects.create(article=article, user=request.user, content=comment)
    return redirect('article:articleRead', articleId=articleId)

▸ 新增留言 URL mapping

article/urls.py
...

urlpatterns = [
    ...
    path('articleLike/<int:articleId>/', views.articleLike, name='articleLike'),
    
    path('commentCreate/<int:articleId>/', views.commentCreate, name='commentCreate'),
]

▸ 新增留言範本

article/templates/article/articleRead.html
...
{% for comment in comments %}
  ...
{% endfor %}
{% if user.is_authenticated %}
  <br>
  <form method="post" action="{% url 'article:commentCreate' article.id %}">
    {% csrf_token %}
    <input type="text" name="comment"  placeholder="留言 ...">
    <input class="btn" type="submit" value="送出">
  </form>
  <br><br>
{% endif %}
{% endblock %}

▸ 修改留言函式

article/views.py
def commentCreate(request, articleId):
    ...


def commentUpdate(request, commentId):
    '''
    Update a comment:
        1. Get the comment to update and its article; redirect to 404 if not found
        2. If it is a 'GET' request, return
        3. If the comment's author is not the user, return
        4. If comment is empty, delete the comment, else update the comment
    '''
    commentToUpdate = get_object_or_404(Comment, id=commentId)
    article = get_object_or_404(Article, id=commentToUpdate.article.id)
    if request.method == 'GET':
        return articleRead(request, article.id)

    # POST
    if commentToUpdate.user != request.user:
        messages.error(request, '無修改權限')
        return redirect('article:articleRead', articleId=article.id)

    comment = request.POST.get('comment', '').strip()
    if not comment:
        commentToUpdate.delete()
    else:
        commentToUpdate.content = comment
        commentToUpdate.save()
    return redirect('article:articleRead', articleId=article.id)

▸ 修改留言 URL mapping

article/urls.py
...

urlpatterns = [
    ...
    
    path('commentCreate/<int:articleId>/', views.commentCreate, name='commentCreate'),
    path('commentUpdate/<int:commentId>/', views.commentUpdate, name='commentUpdate'),
]

▸ 修改留言範本

article/templates/article/articleRead.html
...
{% for comment in comments %}
  <div class=commentDiv>
    <span class="commentAuthor">{{ comment.user.fullName }}</span>
    {% if comment.user != user %}
      <span class="comment">{{ comment.content }}</span>
    {% else %}
      <form class="inlineBlock" method="post" action="{% url 'article:commentUpdate' comment.id %}">
        {% csrf_token %}
        <input type="text" name="comment" value="{{ comment.content }}">
        <input class="btn" type="submit" value="修改">
      </form>
    {% endif %}
    <br>
    <span class="commentTime">{{ comment.pubDateTime|date:'Y-m-d H:i'}}</span>
  </div>
{% endfor %}
...

▸ 刪除留言函式

article/views.py
def commentUpdate(request, commentId):
    ...


def commentDelete(request, commentId):
    '''
    Delete a comment:
        1. Get the comment to update and its article; redirect to 404 if not found
        2. If it is a 'GET' request, return
        3. If the comment's author is not the user, return
        4. Delete the comment
    '''
    comment = get_object_or_404(Comment, id=commentId)
    article = get_object_or_404(Article, id=comment.article.id)
    if request.method == 'GET':
        return articleRead(request, article.id)

    # POST
    if comment.user != request.user:
        messages.error(request, '無刪除權限')
        return redirect('article:articleRead', articleId=article.id)

    comment.delete()
    return redirect('article:articleRead', articleId=article.id)

▸ 刪除留言 URL mapping

article/urls.py
...

urlpatterns = [
    ...
    path('commentUpdate/<int:commentId>/', views.commentUpdate, name='commentUpdate'),
    path('commentDelete/<int:commentId>/', views.commentDelete, name='commentDelete'),
]

▸ 刪除留言範本

article/templates/article/articleRead.html
...
{% for comment in comments %}
  <div class=commentDiv>
    <span class="commentAuthor">{{ comment.user.fullName }}</span>
    {% if comment.user != user %}
      <span class="comment">{{ comment.content }}</span>
    {% else %}
      <form class="inlineBlock" method="post" action="{% url 'article:commentUpdate' comment.id %}">
        {% csrf_token %}
        <input type="text" name="comment" value="{{ comment.content }}">
        <input class="btn" type="submit" value="修改">
      </form>
      <form class="inlineBlock" method="post" action="{% url 'article:commentDelete' comment.id %}">
        {% csrf_token %}
        <input class="btn deleteConfirm" type="submit" value="刪除">
      </form>
    {% endif %}
    <br>
    <span class="commentTime">{{ comment.pubDateTime|date:'Y-m-d H:i'}}</span>
  </div>
{% endfor %}
...
    
{% endblock %}

{% block script %}
  <script src="{% static 'main/js/deleteConfirm.js' %}"></script>
{% endblock %}

▸ 本章完成專案:blog12.zip