完成点赞功能:

文章点赞,留言点赞,评论点赞

在很多内容类型都会有点赞功能,所以将点赞做成一个模块来使用比较好

  • 新建app
python manage.py startapp likes
  • 注册app

settings.py

INSTALLED_APPS = [
    # ...
    'likes',
]
  • 构建模型

models

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.auth.models impoer User


class LikeCount(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()
    like_num = models.IntegerField(default=0)

解释:

LikeCount是用于记录目标对象,点赞的数量的表

class LikeRecord(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()
    
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    like_time = models.DateTimeField(auto_now_add=True)

解释:

LikeRecord 用于记录一篇文章或一条评论的点赞记录

user是谁在like_time哪个时间点赞了

建立完模型,同步数据库

python manage.py makemigrations
python manage.py migrate

模型建立完毕,突然不知道,该写什么,从哪里开始写

我们可以从实际需求出发,先完成前端需求

前端首先展示出点赞按钮,以及点赞的数量

UTOOLS1588315828125.png

当用户去点击的时候,这个时候就应该向服务器发送一个请求

那么,现在就来处理,这个点赞的请求

首先需要一个入口让前端请求,所以定制路由规则

在点赞模块下新建文件urls.py

from django.urls import path

urlpatterns = [
    path('', change_like, name="change_like")
]

创建视图函数change_like

def change_like(request):
    pass

现在再来理一理点赞的处理逻辑

  1. 判断用户是否登录
  2. 判断用户是否已经点赞过了
  3. 已经点赞过了,再次点赞则取消点赞
  4. 返回json数据,数据包含当前点赞数量

好的,走你,继续完善

views.py

def change_like(request):
    user = request.user
    if not user.is_authenticated:
        # 未登录...
        return ?
    # 获取内容类型,明白当前点赞内容是评论,文章还是其他什么的
    content_type = request.GET.get('content_type')
    # 获取点赞对象的id
    object_id = request.POST.get('object_id')
    try:
        # 从前端接收过来的是字符串,以此作为条件,取出该对象
        content_type = ContentType.objects.get(model=content_type)
        # 通过实例对象,取得模型类
        model_class = content_type.model_class()
        # 模型类以object_id为条件,即可取出该对象的数据
        model_class.objects.get(pk=object_id)
    except ObjectDoesNotExist:
        return ?
    

暂时先完成上方代码,先解释下

前端不可信 拿到的所有数据,重要的一定要进行校验其是否有效

我们这里接收,到 content_typeobject_id 数据库内万一没有呢?

可以使用try先去数据库里取,没有的话就会报错ObjectDoesNotExist

在前端,点击一次赞按钮,就是点赞,如果再次点赞那么就是取消点赞

是点赞,还是取消点赞? 这需要前端给我们消息

def change_like(request):
    
    # ...
    
    if request.GET.get('is_like') == 'true':
        # 点赞操作: 1.增加点赞数量,2.增加点赞记录
        # 取点赞记录,如果没有就创建
        like_record, created = LikeRecord.objects.get_or_create(content_type=content_type, object_id=object_id, user=user)
        if created:
            # created为真,就代表是新创建的记录
            like_count, created = LikeCount.objects.get_or_create(content_type=content_type, object_id=object_id)
            like_count.like_num += 1
            like_count.save()
            return ?
        else:
            # 已经点赞过了
            return ?
    else:
        # 取消点赞, 判断是否存在这个用户的点赞记录
        if LikeRecord.objects.filter(content_type=content_type, object_id=object_id, user=user).exists():
            # 删除点赞记录
            like_record = LikeRecord.objects.get(content_type=content_type, object_id=object_id, user=user)
            like_record.delete()
            # 点赞数目减一
            like_count, created = LikeCount.objects.get_or_create(content_type=content_type, object_id=object_id)
            if not created:
                like_count.like_num -= 1
                like_count.save()
                return ?

前端是异步请求,所以我们这里返回数据就不再是一个页面

返回给前端json数据包

给出两个函数,方便我们统一返回数据的格式

def success_response(like_num):
    data = {
        'status': 'SUCCESS',
        'like_num': like_num
    }
    return JsonResponse(data)

def error_response(code, message):
    data = {
        'status': 'ERROR',
        'code': code,
        'message': message
    }
    return JsonResponse(data)

最后完整的视图函数

from django.http import JsonResponse
from django.db.models import ObjectDoesNotExist
from django.contrib.contenttypes.models import ContentType
from .models import LikeCount, LikeRecord


def success_response(like_num):
    data = {}
    data['status'] = 'SUCCESS'
    data['like_num'] = like_num
    return JsonResponse(data)


def error_response(code, message):
    data = {}
    data['status'] = 'ERROR'
    data['code'] = code
    data['message'] = message
    return JsonResponse(data)


def change_like(request):
    user = request.user
    if not user.is_authenticated:
        return error_response(400, '没有登录')

    content_type = request.GET.get('content_type')
    object_id = request.GET.get('object_id')
    try:
        content_type = ContentType.objects.get(model=content_type)
        model_class = content_type.model_class()
        model_obj = model_class.objects.get(pk=object_id)
    except ObjectDoesNotExist:
        return error_response(401, '对象不存在')
    # print(request.GET.get('is_like'))
    if request.GET.get('is_like') == 'true':
        like_record, created = LikeRecord.objects.get_or_create(content_type=content_type, object_id=object_id, user=user)
        if created:
            # 新创建,未被创建过
            like_count, created = LikeCount.objects.get_or_create(content_type=content_type, object_id=object_id)
            like_count.like_num += 1
            like_count.save()
            return success_response(like_count.like_num)
        else:
            # 已点赞,不能重复点赞
            return error_response(402, '不能重复点赞')

    else:
        if LikeRecord.objects.filter(content_type=content_type, object_id=object_id, user=user).exists():
            # 删除点赞记录
            like_record = LikeRecord.objects.get(content_type=content_type, object_id=object_id, user=user)
            like_record.delete()
            # 点赞数目减一
            like_count, created = LikeCount.objects.get_or_create(content_type=content_type, object_id=object_id)
            if not created:
                like_count.like_num -= 1
                like_count.save()
                return success_response(like_count.like_num)

模板层

<div class="like" onclick="change_like(this, '{% get_content_type blog %}', {{ blog.pk }})">
    <span class="glyphicon glyphicon-thumbs-up"></span>
    <span class="like-num">0</span>
    <span>喜欢</span>
</div>

前端应该完成的工作:

  • 用户点赞后,显示新的点赞数量
  • 提交必要参数:内容类型,内容id,以及当前点赞状态

如果已经点赞,我们给这个标签加一个class active代表当前用户已经点赞过了,还可以定制点赞后展示的样式

那么如何知道该用户已经点赞了呢?

还是要来后端,写写逻辑

我们可以自定义标签,使用起来方便些

在点赞模块中创建文件夹templatetags,并在此文件夹下创建likes_tags.py

likes_tags.py

from django import template

register = template.Library()


@register.simple_tag
def get_like_num(obj):
    """返回当前内容的点赞数量"""
    content_type = ContentType.objects.get_for_model(obj)
    like_count, created = LikeCount.objects.get_or_create(
        content_type=content_type, object_id=obj.pk)
    return like_count.like_num


@register.simple_tag(takes_context=True)
def get_like_status(context, obj):
    """返回当前内容&当前用户的点赞状态"""
    content_type = ContentType.objects.get_for_model(obj)
    user = context['user']
    if not user.is_authenticated:
        return ''
    return 'active' if LikeRecord.objects.filter(
        content_type=content_type,
        object_id=obj.pk,
        user=user).exists() else ''

@register.simple_tag
def get_content_type(obj):
    """返回内容对象的类型"""
    return ContentType.objects.get_for_model(obj).model

@register.simple_tag(takes_context=True) 我们获取点赞状态需要传入用户实例

takes_context=True设置了这个参数,那么我们可以直接获取到模板层中所有的公共对象

模板层的公共对象默认有:

UTOOLS1588319394721.png

这就是,我们没有传入user,却可以在模板层直接使用user的原因

以及debug,request,都是这样的

完善了自定义模板标签

点赞模板就可以改写如下:

<div class="like" onclick="change_like(this, '{% get_content_type blog %}', {{ blog.pk }})">
    <span class="glyphicon glyphicon-thumbs-up {% get_like_status blog %}"></span>
    <span class="like-num">{% get_like_num blog %}</span>
    <span>喜欢</span>
</div>

模板层js

function change_like(obj, content_type, object_id) {
    // 检测有无active存在,不存在即是未点赞的状态
    var is_like = obj.getElementsByClassName('active').length === 0;
    $.ajax({
        url: "{% url 'change_like' %}",
        type: 'GET',
        data: {
            'content_type': content_type,
            'object_id': object_id,
            'is_like': is_like,
        },
        cache: false,
        success: function (data) {
            console.log(is_like);
            if (data['status'] === 'SUCCESS') {
                var element = $(obj.getElementsByClassName('glyphicon-thumbs-up'));
                if (is_like) {
                    element.addClass('active');
                } else {
                    element.removeClass('active');
                }
                var like_num = $(obj.getElementsByClassName('like-num'));
                like_num.text(data['like_num'])
            } else {
                if (data['code'] === 400) {
                    $('#login_modal').modal('show')
                } else {
                    alert(data['message']);
                }
            }
            console.log(data);
        },
        error: function (xhr) {
            console.log(xhr);
        }
    });}

效果:

UTOOLS1588319095807.gif

Last modification:May 1st, 2020 at 03:50 pm