视图,代码:
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__
方法相关错误。解决方案:
__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 ""
前端展示当前文集的文章列表,代码:
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 设置发布样式
- 移动文章
- 随笔
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
切换文集以后,重新获取新的文集对应的文章列表。
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 设置发布样式
- 移动文章
- 随笔
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
显示当前文章的操作菜单【扩展】
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 设置发布样式
- 移动文章
- {{collection.name}}
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
作业:完善当前文集下的文章列表的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,代码:
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 设置发布样式
- 移动文章
- {{collection.name}}
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
默认情况下,我们的文章属于未发布状态下的,这时候除了作者本人和后台管理员,其他人看不到的。
服务端提供切换文章发布状态的功能。、
视图代码:
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()),
]
前端代码:
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 取消发布
- 设置发布样式
- 移动文章
- {{collection.name}}
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
首先修复文章菜单中的文集列表,在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)
路由与上面发布文章状态切换接口一致。客户端实现切换文集代码效果:
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 取消发布
- 设置发布样式
- 移动文章
- {{collection.name}}
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
原理:使用celery完成定时任务!
步骤:
1. 当用户点选了定时发布, 页面中弹出一个选择时间的窗口。
2. 当用户设置完成发布时间以后,点击“确认”以后,把这个时间和文章id发送到服务端。
3. 服务端中文章模型的pub_date记录这个定时发布时间。
4. 在celery中创建一个定时任务,在每个固定时间段,检查文章表中,对应时间段的pub_date把对应的文章进行发布。
前端增加一个选择时间的弹窗,代码:
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 取消发布
- 设置发布样式
- 移动文章
- {{collection.name}}
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
定时发文
选择定时发布的时间:
本文章将于{{timeformat(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请求,代码:
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 取消发布
- 设置发布样式
- 移动文章
- {{collection.name}}
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
定时发文
选择定时发布的时间:
本文章将于{{timeformat(pub_date)}}发布。
当用户取消发布时,我们应该把定时发布也取消掉,也就是说把当前文章的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)
客户端状态在取消发布以后调整显示状态,代码:
回首页
新建文集
-
-
修改文集
-
删除文集
{{collection.name}}
设置
遇到问题
新建文章
-
- 直接发布
- 定时发布
- 发布为付费文章
- 取消发布
- 设置发布样式
- 移动文章
- {{collection.name}}
- 历史版本
- 删除文章
- 设置禁止转载
{{article.name}}
{{article.content|truncate}}
字数:{{article.content.length}}
在下方新建文章
定时发文
选择定时发布的时间:
本文章将于{{timeformat(pub_date)}}发布。
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
注意,使用的时候,如果有时区必须先配置好系统时区。