renran-文章模块02

显示当前文集的文章列表

视图,代码:

from rest_framework.generics import ListAPIView
from .serializers import ArticleModelSerializer
from .models import Article
from rest_framework.response import Response
from rest_framework import status
class ArticleOfCollectionAPIView(ListAPIView):
    """文集下的文章"""
    serializer_class = ArticleModelSerializer
    permission_classes = [IsAuthenticated]  # 必须是登陆用户才能访问过来

    def list(self, request,  *args, **kwargs):
        user = request.user
        collection_id = request.query_params.get("collection_id")
        try:
            ArticleCollection.objects.get(pk=collection_id)
        except ArticleCollection.DoesNotExist:
            return Response({"message":"错误的文集ID"}, status=status.HTTP_400_BAD_REQUEST)

        queryset = Article.objects.filter(user=user,collection_id=collection_id).order_by("orders", "-id")
        queryset = self.filter_queryset(queryset)
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

序列化器,代码:

from .models import Article
class ArticleModelSerializer(serializers.ModelSerializer):
    """文章模型序列化器"""
    class Meta:
        model = Article
        fields = ["id","name","content","html_content","collection","pub_date","is_public"]

路由代码:

from django.urls import path,re_path
from . import views
urlpatterns = [
    path("collection/", views.CollecionAPIView.as_view()),
    re_path("^collection/(?P\d+)/$", views.CollecionAPIView.as_view()),
    re_path("^collection/article/$", views.ArticleOfCollectionAPIView.as_view()),
]

现有的接口实现了以后,会发现没有文章,所以我们需要把文章相关模型注册到xadmin中,让我们可以在xadmin中添加测试文章数据。

article/adminx.py,代码:

import xadmin

from .models import ArticleCollection
class ArticleCollectionModelAdmin(object):
    """文集"""
    list_display = ["id","name"]
    list_editable = ["name"]
xadmin.site.register(ArticleCollection,ArticleCollectionModelAdmin)


from .models import Article
class ArticleModelAdmin(object):
    """文章"""
    list_display=["id","name"]
xadmin.site.register(Article, ArticleModelAdmin)

完成上面注册以后,修改文章子应用在xadmin中的名称显示,

article/apps.py,代码:

from django.apps import AppConfig


class ArticleConfig(AppConfig):
    name = 'article'
    verbose_name="文章管理"

article/__init__.py,代码:

default_app_config = "article.apps.ArticleConfig"

访问xadmin的时候报错,错误提示:__str__方法相关错误。解决方案:

  1. users/models.py下模型的__str__方法代码调整:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
    mobile = models.CharField(max_length=15, null=True, unique=True, help_text="手机号码",verbose_name="手机号码")
    wxchat = models.CharField(max_length=100, null=True, unique=True, help_text="微信账号", verbose_name="微信账号")
    qq_number = models.CharField(max_length=11, null=True, unique=True, help_text="QQ号", verbose_name="QQ号")
    alipay = models.CharField(max_length=100, null=True, unique=True, help_text="支付宝账号", verbose_name="支付宝账号")
    # 保存文件的子目录 ImageField和FileField字段类型内置了文件上传处理类
    avatar = models.ImageField(upload_to="avatar", null=True, default=None, verbose_name="头像")
    money  = models.DecimalField(max_digits=8, decimal_places=2,default=0, help_text="账户余额", verbose_name="账户余额")
    nickname = models.CharField(max_length=20, null=True, unique=True, help_text="用户昵称",verbose_name="用户昵称")

    class Meta:
        db_table = "rr_users"
        verbose_name = "用户信息"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.nickname if self.nickname else self.username

在utils/models.py,代码:

from django.db import models
class BaseModel(models.Model):
    """公共模型"""
    name = models.CharField(null=True, default="暂无", blank=True, max_length=150, verbose_name='名称')
    orders = models.IntegerField(default=0, verbose_name='显示顺序')
    is_show = models.BooleanField(default=False, verbose_name="是否上架")
    is_delete = models.BooleanField(default=False, verbose_name="逻辑删除")
    created_time = models.DateTimeField(null=True,blank=True, auto_now_add=True, verbose_name="添加时间")
    updated_time = models.DateTimeField(null=True,blank=True, auto_now=True, verbose_name="更新时间")

    class Meta:
        # 设置当前模型在数据迁移的时候不要为它创建表
        abstract = True

    def __str__(self):
        return self.name if self.name else ""

前端展示当前文集的文章列表,代码:



切换文集以后,重新获取新的文集对应的文章列表。



显示当前文章的操作菜单【扩展】






作业:完善当前文集下的文章列表的BUG

当前文集如果默认是0,那么在用户新建文件以后,显示的文章列表错误的。

所以,我们需要在用户创建文集以后,设置当前文章列表为空列表。

添加文章

视图代码:

from rest_framework.generics import ListAPIView
from .serializers import ArticleModelSerializer
from .models import Article
from rest_framework.response import Response
from rest_framework import status
class ArticleOfCollectionAPIView(ListAPIView, CreateAPIView):
    """文集下的文章"""
    serializer_class = ArticleModelSerializer
    permission_classes = [IsAuthenticated]  # 必须是登陆用户才能访问过来

    def list(self, request,  *args, **kwargs):
        user = request.user
        collection_id = request.query_params.get("collection_id")
        try:
            ArticleCollection.objects.get(pk=collection_id)
        except ArticleCollection.DoesNotExist:
            return Response({"message":"错误的文集ID"}, status=status.HTTP_400_BAD_REQUEST)

        queryset = Article.objects.filter(user=user,collection_id=collection_id).order_by("orders", "-id")
        queryset = self.filter_queryset(queryset)
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

序列化器,代码:

from .models import Article
class ArticleModelSerializer(serializers.ModelSerializer):
    """文章模型序列化器"""
    position = serializers.IntegerField(write_only=True, default=0, allow_null=True, label="添加文章的位置", help_text="在文章列表的前面插入添加则为0, 在文章列表的后面追加添加则为1")
    class Meta:
        model = Article
        fields = ["id","position","name","content","html_content","collection","pub_date","is_public"]
        read_only_fields = ["id","content","html_content","pub_date","is_public"]

    def validate(self, data):
        name = data.get("name")
        if len(name)<1:
            raise serializers.ValidationError("对不起,文章标题不能为空!")

        return data

    def create(self, validated_data):
        """添加文章"""
        try:
            article = Article.objects.create(
                name=validated_data.get("name"),
                user=self.context["request"].user,
                content="",
                html_content="",
                collection=validated_data.get("collection"),
                is_public=False,
                is_show=True,
                orders=0
            )
        except:
            raise serializers.ValidationError("对不起,文章添加失败!")

        if validated_data.get("position"):
            """
            如果用户设置在文章列表后面追加添加文章,则让文章的排序值跟着id来变化越来越大
            排序值越大的文章,越往后排列
            """
            article.orders = article.id
            article.save()

        return article

路由代码与上面的文集的文章列表共用一个路由。

客户端发送ajax,代码:



文章发布

默认情况下,我们的文章属于未发布状态下的,这时候除了作者本人和后台管理员,其他人看不到的。

  1. 在写文章页面,我们需要给用户区分哪些文章已经发布了,哪些文章没有发布,在前面的文章列表中我们已经完成了这个功能。
  2. 在写文章页面,提供一个api,用于修改发布状态。同时,需要提供发布后的文章页面。

切换文章的发布状态

服务端提供切换文章发布状态的功能。、

视图代码:

from rest_framework.views import APIView
class ArticleAPIView(APIView):
    permission_classes = [IsAuthenticated]
    def patch(self,request,pk):
        """切换文章的发布状态"""
        try:
            article = Article.objects.get(user=request.user,pk=pk)
        except Article.DoesNotExist:
            return Response({"message":"当前文章不存在!"}, status=status.HTTP_400_BAD_REQUEST)

        article.is_public = not article.is_public
        article.save()

        return Response(status=status.HTTP_200_OK)

路由代码:

from django.urls import path,re_path
from . import views
urlpatterns = [
    path("collection/", views.CollecionAPIView.as_view()),
    re_path("^collection/(?P\d+)/$", views.CollecionAPIView.as_view()),
    path("collection/article/", views.ArticleOfCollectionAPIView.as_view()),
    re_path("^public/(?P\d+)/$", views.ArticleAPIView.as_view()),
]

前端代码:




移动文章

首先修复文章菜单中的文集列表,在css代码中这个位置放置以下代码:

._2_WAp ._2KzJx, ._2_WAp ._3x4X_ {
    position: absolute;
    right: 100%;
    top: 0;
    display: none;
}
._2_WAp:hover ._2KzJx, ._2_WAp:hover ._3x4X_ {
    display: block;
}


服务端提供修改文章的文集ID的接口,在上面切换文章发布状态的视图类中,增加代码:

from rest_framework.views import APIView
class ArticleAPIView(APIView):
    permission_classes = [IsAuthenticated]
    def patch(self,request,pk):
        """切换文章的发布状态"""
        try:
            article = Article.objects.get(user=request.user,pk=pk)
        except Article.DoesNotExist:
            return Response({"message":"当前文章不存在!"}, status=status.HTTP_400_BAD_REQUEST)

        article.is_public = not article.is_public
        article.save()

        return Response(status=status.HTTP_200_OK)

    def put(self,request,pk):
        """移动文章"""
        try:
            article = Article.objects.get(user=request.user, pk=pk)
        except Article.DoesNotExist:
            return Response({"message": "当前文章不存在!"}, status=status.HTTP_400_BAD_REQUEST)

        collection_id = request.data.get("collection_id")
        try:
            collection = ArticleCollection.objects.get(user=request.user, pk=collection_id)
        except ArticleCollection.DoesNotExist:
            return Response({"message": "当前文集不存在!"}, status=status.HTTP_400_BAD_REQUEST)

        article.collection = collection
        article.save()

        return Response(status=status.HTTP_200_OK)

路由与上面发布文章状态切换接口一致。客户端实现切换文集代码效果:




定时发布[扩展知识点]

原理:使用celery完成定时任务!
步骤:
1. 当用户点选了定时发布, 页面中弹出一个选择时间的窗口。
2. 当用户设置完成发布时间以后,点击“确认”以后,把这个时间和文章id发送到服务端。
3. 服务端中文章模型的pub_date记录这个定时发布时间。
4. 在celery中创建一个定时任务,在每个固定时间段,检查文章表中,对应时间段的pub_date把对应的文章进行发布。

前端增加一个选择时间的弹窗,代码:







服务端提供修改pub_date发布时间的api接口

配置文件中, 调整市区,代码:

# 必须设置为True,否则后面提交发布时间到服务端会报错!
USE_TZ = True

视图代码:

class ArticleIntervalAPIView(APIView):
    """定时发布文章"""
    permission_classes = [IsAuthenticated]
    def put(self,request,pk):
        try:
            article = Article.objects.get(pk=pk)
        except Article.DoesNotExist:
            return Response("对不起,当前文章不存在!", status=status.HTTP_400_BAD_REQUEST)

        pub_date = request.data.get("pub_date")
        article.pub_date = pub_date
        article.save()
        return Response("操作成功!")

路由,代码:

urlpatterns = [
	
    ....
    
    re_path("^interval/(?P\d+)/$", views.ArticleIntervalAPIView.as_view()),
]

客户端发送ajax请求,代码:




当用户取消发布时,我们应该把定时发布也取消掉,也就是说把当前文章的pub_date清空!

视图中切换文章发布状态视图接口代码进行调整,代码:

from rest_framework.views import APIView
class ArticleAPIView(APIView):
    permission_classes = [IsAuthenticated]
    def patch(self,request,pk):
        """切换文章的发布状态"""
        try:
            article = Article.objects.get(user=request.user,pk=pk)
        except Article.DoesNotExist:
            return Response({"message":"当前文章不存在!"}, status=status.HTTP_400_BAD_REQUEST)

        # 判断如果是取消发布,则取消定时发布
        # 1. 定时发布
        if article.is_public == False and article.pub_date is not None:
            # 原来定时发布的,现在取消
            article.pub_date = None
        elif article.is_public == False and article.pub_date is None:
            # 原来没有发布的,现在立即发布
            article.is_public = True
        elif article.is_public == True:
            # 原来发布的,现在取消
            article.is_public = False
        article.save()

        return Response(status=status.HTTP_200_OK)

客户端状态在取消发布以后调整显示状态,代码:




celery的定时任务,每分钟执行一次定时发布操作,让pub_date时间到了,则更新对应文章的发布状态。

在mycelery中创建article任务目录,在目录下创建任务文件tasks.py,编写异步任务:

from mycelery.main import app
from article.models import Article
from datetime import datetime
@app.task(name="interval_pub_article")
def interval_pub_article():
    """定时发布文章"""
    article_list = Article.objects.exclude(pub_date=None)
    for article in article_list:
        pub_date_timestamp = int(article.pub_date.timestamp())
        current_timestamp = int(datetime.now().timestamp() +8 * 60 * 60)
        if pub_date_timestamp <= current_timestamp:
            article.pub_date = None
            article.is_public = True
            article.save()

注册异步任务到main.py中。并重启celery,代码:

from celery import Celery
# 初始化celery对象
app = Celery("renran")

# 初始化django
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'renranapi.settings.dev')
import django
django.setup()

# 加载配置
app.config_from_object("mycelery.config")

# 注册异步任务
# 任务以包进行管理,每一个包必须里面包含了一个tasks.py文件
app.autodiscover_tasks(["mycelery.sms","mycelery.article"])

# 在终端下面运行celery,将来在线上服务器中,可以使用supervisor以守护进程的方式启动
# celery -A mycelery.main worker --loglevel=info

接下来,我们就可以使用celery的定时任务调度器,让celery定时执行异步任务。

Celery官方文档中关于定时任务使用的说明:

http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html

在mycelery中config.py配置异步任务定时执行,代码:

# 任务队列的链接地址
broker_url = 'redis://127.0.0.1:6379/15'
# 结果队列的链接地址
result_backend = 'redis://127.0.0.1:6379/14'

# 定时任务调度器相关配置
from .main import app
from celery.schedules import crontab
app.conf.beat_schedule = {
    # 定时任务列表
    'pub-article-every-one-minute': {
        'task': 'interval_pub_article', # 指定定时执行的的异步任务
        # 'schedule': crontab(),         # 时间间隔,一分钟
        'schedule': 30.0,               # 时间间隔,默认:秒
        # 'args': (16, 16)              # 如果任务有固定参数,则可以写在args
    },
}

# 和django框架同步时区
from django.conf import settings
app.conf.timezone = settings.TIME_ZONE

# 完成上面的配置以后,重启celery并在新的终端窗口执行命令,运行定时任务的调度器
# celery -A mycelery.main beat  # mycelery.main 是celery的主应用文件

接下来,我们就可以重启Celery并启用Celery的定时任务调度器

先在终端下,运行celery的定时任务程序,以下命令:

celery -A mycelery.main beat  # mycelery.main 是celery的主应用文件

然后再新建一个终端,运行以下命令,上面的命令必须先指定:

celery -A mycelery.main worker --loglevel=info

注意,使用的时候,如果有时区必须先配置好系统时区。

你可能感兴趣的:(web)