我有一个由博客,文章和评论应用程序组成的django项目。评论模型与文章应用程序中的文章模型和博客应用程序中的条目具有通用关系。我希望两个模型(条目和文章)显示评论数。为此,我重写了save()
,update()
和delete()
方法(最后一个由信号处理),这些方法调用处理更新的特定任务。
现在输入代码。
comments/models.py
:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from model_utils.models import TimeStampedModel
from comments.tasks import update_comment_count_on_related_writing
class CommentManager(models.query.QuerySet):
def update(self, **kwargs):
comments_for_previous_writings = dict()
for comment in self.all().prefetch_related('writing_object'):
comments_for_previous_writings[comment] = {
'writing_pk': comment.writing_id,
'writing_type_pk': comment.writing_type.pk
}
rows = super().update(**kwargs)
for comment in self.all():
update_comment_count_on_related_writing.delay(
comment.pk,
comments_for_previous_writings[comment]['writing_pk'],
comments_for_previous_writings[comment]['writing_type_pk']
)
return rows
class Comment(TimeStampedModel):
body = models.TextField()
__writing_type_choices = (
models.Q(app_label='article', model='article') | models.Q(app_label='blog', model='entry'))
writing_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to=__writing_type_choices)
writing_id = models.PositiveIntegerField()
writing_object = GenericForeignKey('writing_type', 'writing_id')
objects = CommentManager.as_manager()
def __str__(self):
return '({}) {} - {}...'.format(self.writing_type, self.writing_object, self.body[:15])
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
old_writing = None
old_writing_pk = None
old_writing_type_pk = None
created = not self.id
if not created:
# Trick used to avoid changing old_writing* references after update in order to allow introducing changes
# to previously assigned writing.
comment_before_update = Comment.objects.get(pk=self.pk)
old_writing = comment_before_update.writing_object
old_writing_type_pk = comment_before_update.writing_type.pk
old_writing_pk = old_writing.pk
super().save(force_insert=False, force_update=False, using=None, update_fields=None)
if created or old_writing != self.writing_object:
update_comment_count_on_related_writing.delay(self.pk, old_writing_pk, old_writing_type_pk)
comments/signals.py
:
from django.db.models.signals import post_delete
from django.dispatch import receiver
from .models import Comment
from comments.tasks import update_comment_count_on_related_writing
@receiver(post_delete, sender=Comment)
def update_writings_comments_count(instance: Comment, **_):
update_comment_count_on_related_writing.delay(
old_writing_pk=instance.writing_id,
old_writing_type_pk=instance.writing_type.pk
)
article/models.py
:
from django.contrib.contenttypes.fields import GenericRelation
from comments.models import Comment
from common.models import WritingBase
class Article(WritingBase):
comments = GenericRelation(
Comment, related_query_name='articles', object_id_field='writing_id', content_type_field='writing_type')
blog/models.py
:
from django.contrib.contenttypes.fields import GenericRelation
from comments.models import Comment
from common.models import WritingBase
class Entry(WritingBase):
comments = GenericRelation(
Comment, related_query_name='entries', object_id_field='writing_id', content_type_field='writing_type')
class Meta:
verbose_name_plural = 'Entries'
处理更新的任务-comments/tasks.py
:
from django.contrib.contenttypes.models import ContentType
from wiktor.celery import app
@app.task
def update_comment_count_on_related_writing(updated_comment_pk=None, old_writing_pk=None, old_writing_type_pk=None):
from comments.models import Comment
if updated_comment_pk is not None:
updated_comment = Comment.objects.get(pk=updated_comment_pk)
comments_count = Comment.objects.filter(
writing_type=updated_comment.writing_type, writing_id=updated_comment.writing_id
).count()
updated_comment.writing_object._meta.model.objects.filter(
id=updated_comment.writing_object.id
).update(comments_count=comments_count)
if old_writing_pk is not None:
old_writing_class = ContentType.objects.get(pk=old_writing_type_pk).model_class()
comments_count = Comment.objects.filter(
writing_type__pk=old_writing_type_pk, writing_id=old_writing_pk).count()
old_writing_class.objects.filter(pk=old_writing_pk).update(
comments_count=comments_count)
配置-wiktor/celery.py
:
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wiktor.settings')
app = Celery('wiktor')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
以及负责芹菜的部分设置:
CELERY_BROKER_URL = 'amqp://guest:guest@rabbitmq//'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_RESULT_BACKEND = 'amqp://guest:guest@rabbitmq//'
CELERY_TASK_SERIALIZER = 'json'
CELERY_TASK_DEFAULT_QUEUE = 'wiktor_queue'
由于一切都可以在docker中运行,因此用于主要服务的dockerfile可以在Django中运行:
FROM python:3.5.2
ENV PYTHONUNBUFFERED 1
RUN apt-get update && apt-get install -y gettext
RUN mkdir /code
WORKDIR /code
ADD requirements/base.txt requirements/dev.txt /code/
RUN pip install -r base.txt -r dev.txt
ADD . /code/
RUN celery -A wiktor worker -D
WORKDIR /code
和docker-compose配置(名为dev.yml
):
version: '3'
services:
rabbitmq:
image: rabbitmq:3.7-management-alpine
ports:
- "15672:15672"
elasticsearch:
image: elasticsearch:2.4.6-alpine
postgres:
image: postgres
django:
build:
context: .
dockerfile: ./compose/django/Dockerfile
command: python manage.py runserver 0.0.0.0:8000
ports:
- "8000:8000"
volumes:
- .:/code
depends_on:
- postgres
- elasticsearch
- rabbitmq
现在,代码部分结束了,让我们了解它当前的工作方式。
我从docker-compose -f dev.yml up --build
开始项目。在管理面板中,我看到“评论”已分配给特定条目
与此评论相关的条目有2条评论:
我将“注释”中的writing type
字段从“条目”更改为“文章”,单击“保存”并继续编辑并刷新“条目”编辑页面。评论数仍为2。
从日志来看,rabbitmq似乎没有收到此任务:
rabbitmq_1 | 2018-07-11 08:53:51.822 [info] <0.640.0> accepting AMQP connection <0.640.0> (172.18.0.5:56766 -> 172.18.0.2:5672)
rabbitmq_1 | 2018-07-11 08:53:51.825 [info] <0.640.0> connection <0.640.0> (172.18.0.5:56766 -> 172.18.0.2:5672): user 'guest' authenticated and granted access to vhost '/'
django_1 | [11/Jul/2018 08:53:51] "POST /admin/comments/comment/1/change/ HTTP/1.1" 302 0
django_1 | [11/Jul/2018 08:53:51] "GET /admin/comments/comment/1/change/ HTTP/1.1" 200 5756
django_1 | [11/Jul/2018 08:53:51] "GET /admin/jsi18n/ HTTP/1.1" 200 3185
django_1 | [11/Jul/2018 08:53:51] "GET /static/admin/img/icon-yes.svg HTTP/1.1" 304 0
django_1 | [11/Jul/2018 08:54:34] "GET /admin/blog/entry/1/change/ HTTP/1.1" 200 5928
django_1 | [11/Jul/2018 08:54:34] "GET /admin/jsi18n/ HTTP/1.1" 200 3185
django_1 | [11/Jul/2018 08:55:30] "POST /admin/comments/comment/1/change/ HTTP/1.1" 302 0
django_1 | [11/Jul/2018 08:55:30] "GET /admin/comments/comment/1/change/ HTTP/1.1" 200 5748
我还通过删除对delay
的调用来确保自己的逻辑没有问题,只有芹菜可以了,
update_comment_count_on_related_writing.delay(
comment.pk,
comments_for_previous_writings[comment]['writing_pk'],
comments_for_previous_writings[comment]['writing_type_pk']
)
成为
update_comment_count_on_related_writing(
comment.pk,
comments_for_previous_writings[comment]['writing_pk'],
comments_for_previous_writings[comment]['writing_type_pk']
)
在这种情况下,保存“评论”和刷新页面后,我看到comment_count得到了更新。
现在终于出现了一个问题:我该怎么做才能使comment_count自动更新以反映实际的评论数?