thread.py
219 lines
| 6.2 KiB
| text/x-python
|
PythonLexer
neko259
|
r691 | import logging | |
neko259
|
r1088 | from adjacent import Client | |
neko259
|
r957 | ||
neko259
|
r891 | from django.db.models import Count, Sum | |
neko259
|
r691 | from django.utils import timezone | |
from django.db import models | |||
neko259
|
r957 | ||
neko259
|
r716 | from boards import settings | |
neko259
|
r1027 | import boards | |
neko259
|
r957 | from boards.utils import cached_result | |
neko259
|
r958 | from boards.models.post import Post | |
neko259
|
r1083 | from boards.models.tag import Tag | |
neko259
|
r957 | ||
neko259
|
r691 | ||
__author__ = 'neko259' | |||
logger = logging.getLogger(__name__) | |||
neko259
|
r1088 | WS_NOTIFICATION_TYPE_NEW_POST = 'new_post' | |
WS_NOTIFICATION_TYPE = 'notification_type' | |||
WS_CHANNEL_THREAD = "thread:" | |||
neko259
|
r715 | class ThreadManager(models.Manager): | |
neko259
|
r716 | def process_oldest_threads(self): | |
neko259
|
r715 | """ | |
Preserves maximum thread count. If there are too many threads, | |||
neko259
|
r716 | archive or delete the old ones. | |
neko259
|
r715 | """ | |
threads = Thread.objects.filter(archived=False).order_by('-bump_time') | |||
thread_count = threads.count() | |||
if thread_count > settings.MAX_THREAD_COUNT: | |||
num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT | |||
old_threads = threads[thread_count - num_threads_to_delete:] | |||
for thread in old_threads: | |||
neko259
|
r716 | if settings.ARCHIVE_THREADS: | |
self._archive_thread(thread) | |||
else: | |||
neko259
|
r718 | thread.delete() | |
neko259
|
r716 | ||
logger.info('Processed %d old threads' % num_threads_to_delete) | |||
neko259
|
r715 | ||
neko259
|
r716 | def _archive_thread(self, thread): | |
thread.archived = True | |||
neko259
|
r863 | thread.bumpable = False | |
neko259
|
r716 | thread.last_edit_time = timezone.now() | |
neko259
|
r1029 | thread.update_posts_time() | |
neko259
|
r863 | thread.save(update_fields=['archived', 'last_edit_time', 'bumpable']) | |
neko259
|
r716 | ||
neko259
|
r715 | ||
neko259
|
r691 | class Thread(models.Model): | |
neko259
|
r715 | objects = ThreadManager() | |
neko259
|
r691 | ||
class Meta: | |||
app_label = 'boards' | |||
tags = models.ManyToManyField('Tag') | |||
neko259
|
r966 | bump_time = models.DateTimeField(db_index=True) | |
neko259
|
r691 | last_edit_time = models.DateTimeField() | |
archived = models.BooleanField(default=False) | |||
neko259
|
r863 | bumpable = models.BooleanField(default=True) | |
neko259
|
r1052 | max_posts = models.IntegerField(default=settings.MAX_POSTS_PER_THREAD) | |
neko259
|
r691 | ||
neko259
|
r1083 | def get_tags(self) -> list: | |
neko259
|
r691 | """ | |
Gets a sorted tag list. | |||
""" | |||
return self.tags.order_by('name') | |||
def bump(self): | |||
""" | |||
Bumps (moves to up) thread if possible. | |||
""" | |||
if self.can_bump(): | |||
neko259
|
r1029 | self.bump_time = self.last_edit_time | |
neko259
|
r691 | ||
neko259
|
r1046 | self.update_bump_status() | |
neko259
|
r885 | ||
neko259
|
r691 | logger.info('Bumped thread %d' % self.id) | |
neko259
|
r1083 | def has_post_limit(self) -> bool: | |
neko259
|
r1055 | return self.max_posts > 0 | |
neko259
|
r1046 | def update_bump_status(self): | |
neko259
|
r1055 | if self.has_post_limit() and self.get_reply_count() >= self.max_posts: | |
neko259
|
r1046 | self.bumpable = False | |
self.update_posts_time() | |||
neko259
|
r1083 | def get_reply_count(self) -> int: | |
neko259
|
r958 | return self.get_replies().count() | |
neko259
|
r691 | ||
neko259
|
r1083 | def get_images_count(self) -> int: | |
neko259
|
r958 | return self.get_replies().annotate(images_count=Count( | |
neko259
|
r1091 | 'images')).aggregate(Sum('images_count'))['images_count__sum'] | |
neko259
|
r691 | ||
neko259
|
r1083 | def can_bump(self) -> bool: | |
neko259
|
r691 | """ | |
Checks if the thread can be bumped by replying to it. | |||
""" | |||
neko259
|
r1026 | return self.bumpable and not self.archived | |
neko259
|
r691 | ||
neko259
|
r1083 | def get_last_replies(self) -> list: | |
neko259
|
r691 | """ | |
Gets several last replies, not including opening post | |||
""" | |||
if settings.LAST_REPLIES_COUNT > 0: | |||
reply_count = self.get_reply_count() | |||
if reply_count > 0: | |||
reply_count_to_show = min(settings.LAST_REPLIES_COUNT, | |||
reply_count - 1) | |||
neko259
|
r694 | replies = self.get_replies() | |
neko259
|
r987 | last_replies = replies[reply_count - reply_count_to_show:] | |
neko259
|
r691 | ||
return last_replies | |||
neko259
|
r1083 | def get_skipped_replies_count(self) -> int: | |
neko259
|
r691 | """ | |
Gets number of posts between opening post and last replies. | |||
""" | |||
reply_count = self.get_reply_count() | |||
last_replies_count = min(settings.LAST_REPLIES_COUNT, | |||
reply_count - 1) | |||
return reply_count - last_replies_count - 1 | |||
neko259
|
r1083 | def get_replies(self, view_fields_only=False) -> list: | |
neko259
|
r691 | """ | |
Gets sorted thread posts | |||
""" | |||
neko259
|
r979 | query = Post.objects.filter(threads__in=[self]) | |
neko259
|
r984 | query = query.order_by('pub_time').prefetch_related('images', 'thread', 'threads') | |
neko259
|
r691 | if view_fields_only: | |
neko259
|
r1079 | query = query.defer('poster_ip') | |
neko259
|
r691 | return query.all() | |
neko259
|
r1083 | def get_replies_with_images(self, view_fields_only=False) -> list: | |
neko259
|
r950 | """ | |
Gets replies that have at least one image attached | |||
""" | |||
neko259
|
r693 | return self.get_replies(view_fields_only).annotate(images_count=Count( | |
'images')).filter(images_count__gt=0) | |||
neko259
|
r1083 | def add_tag(self, tag: Tag): | |
neko259
|
r691 | """ | |
Connects thread to a tag and tag to a thread | |||
""" | |||
self.tags.add(tag) | |||
neko259
|
r1083 | def get_opening_post(self, only_id=False) -> Post: | |
neko259
|
r691 | """ | |
Gets the first post of the thread | |||
""" | |||
neko259
|
r958 | query = self.get_replies().order_by('pub_time') | |
neko259
|
r949 | if only_id: | |
query = query.only('id') | |||
opening_post = query.first() | |||
return opening_post | |||
neko259
|
r691 | ||
neko259
|
r957 | @cached_result | |
neko259
|
r1083 | def get_opening_post_id(self) -> int: | |
neko259
|
r691 | """ | |
Gets ID of the first thread post. | |||
""" | |||
neko259
|
r957 | return self.get_opening_post(only_id=True).id | |
neko259
|
r691 | ||
def get_pub_time(self): | |||
""" | |||
Gets opening post's pub time because thread does not have its own one. | |||
""" | |||
neko259
|
r949 | return self.get_opening_post().pub_time | |
neko259
|
r718 | ||
def delete(self, using=None): | |||
neko259
|
r950 | """ | |
Deletes thread with all replies. | |||
""" | |||
neko259
|
r959 | for reply in self.get_replies().all(): | |
neko259
|
r950 | reply.delete() | |
neko259
|
r718 | ||
neko259
|
r863 | super(Thread, self).delete(using) | |
neko259
|
r875 | ||
def __str__(self): | |||
neko259
|
r950 | return 'T#{}/{}'.format(self.id, self.get_opening_post_id()) | |
neko259
|
r1027 | ||
neko259
|
r1083 | def get_tag_url_list(self) -> list: | |
neko259
|
r1029 | return boards.models.Tag.objects.get_tag_url_list(self.get_tags()) | |
def update_posts_time(self): | |||
self.post_set.update(last_edit_time=self.last_edit_time) | |||
neko259
|
r1046 | for post in self.post_set.all(): | |
post.threads.update(last_edit_time=self.last_edit_time) | |||
neko259
|
r1088 | ||
neko259
|
r1091 | def notify_clients(self): | |
neko259
|
r1088 | if not settings.WEBSOCKETS_ENABLED: | |
return | |||
client = Client() | |||
channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id()) | |||
client.publish(channel_name, { | |||
WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST, | |||
}) | |||
client.send() |