##// END OF EJS Templates
Do not close the form when clicking 'reply' again on the same post
Do not close the form when clicking 'reply' again on the same post

File last commit:

r1760:641fa167 default
r1830:5502a653 default
Show More
thread.py
313 lines | 9.8 KiB | text/x-python | PythonLexer
neko259
Moved thread model to a separate module
r691 import logging
neko259
Archive threads by expired time from creation instead of thread limit
r1616 from datetime import timedelta
neko259
Remove websocket support. JS internal auto-update works fine enough
r1760 from django.db import models, transaction
neko259
Faster way of obtaining new post count
r1345 from django.db.models import Count, Sum, QuerySet, Q
neko259
Moved thread model to a separate module
r691 from django.utils import timezone
neko259
Started work on the method caching decorator (BB-57)
r957
neko259
Remove websocket support. JS internal auto-update works fine enough
r1760 import boards
from boards import settings
neko259
Don't show archived random images
r1416 from boards.models import STATUS_BUMPLIMIT, STATUS_ACTIVE, STATUS_ARCHIVE
neko259
Remove websocket support. JS internal auto-update works fine enough
r1760 from boards.models.attachment import FILE_TYPES_IMAGE
neko259
Removed unecessary connection of a thread to its replies, cause posts are...
r958 from boards.models.post import Post
neko259
Added type hints to some models and managers
r1083 from boards.models.tag import Tag
neko259
Remove websocket support. JS internal auto-update works fine enough
r1760 from boards.utils import cached_result, datetime_to_epoch
neko259
Started work on the method caching decorator (BB-57)
r957
neko259
Fixed last post link in new favorite posts api
r1346 FAV_THREAD_NO_UPDATES = -1
neko259
Moved thread model to a separate module
r691
__author__ = 'neko259'
logger = logging.getLogger(__name__)
neko259
Refactored websockets notifications and updating threads when updating posts
r1088 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
WS_NOTIFICATION_TYPE = 'notification_type'
WS_CHANNEL_THREAD = "thread:"
neko259
Added PoW instead of 30-second captcha
r1428 STATUS_CHOICES = (
(STATUS_ACTIVE, STATUS_ACTIVE),
(STATUS_BUMPLIMIT, STATUS_BUMPLIMIT),
(STATUS_ARCHIVE, STATUS_ARCHIVE),
)
neko259
Refactored websockets notifications and updating threads when updating posts
r1088
neko259
Code cleanup. Update only edited fields while performing thread archiving or post editing. Remove image when post is removed
r715 class ThreadManager(models.Manager):
neko259
Archive threads by expired time from creation instead of thread limit
r1616 def process_old_threads(self):
neko259
Code cleanup. Update only edited fields while performing thread archiving or post editing. Remove image when post is removed
r715 """
Preserves maximum thread count. If there are too many threads,
neko259
Moved imageboard settings to the boards settings module. Added setting to disable archive
r716 archive or delete the old ones.
neko259
Code cleanup. Update only edited fields while performing thread archiving or post editing. Remove image when post is removed
r715 """
neko259
Archive threads by expired time from creation instead of thread limit
r1616 old_time_delta = settings.get_int('Messages', 'ThreadArchiveDays')
old_time = timezone.now() - timedelta(days=old_time_delta)
old_ops = Post.objects.filter(opening=True, pub_time__lte=old_time).exclude(thread__status=STATUS_ARCHIVE)
neko259
Code cleanup. Update only edited fields while performing thread archiving or post editing. Remove image when post is removed
r715
neko259
Archive threads by expired time from creation instead of thread limit
r1616 for op in old_ops:
thread = op.get_thread()
if settings.get_bool('Storage', 'ArchiveThreads'):
self._archive_thread(thread)
else:
thread.delete()
logger.info('Processed old thread {}'.format(thread))
neko259
Code cleanup. Update only edited fields while performing thread archiving or post editing. Remove image when post is removed
r715
neko259
Moved imageboard settings to the boards settings module. Added setting to disable archive
r716 def _archive_thread(self, thread):
neko259
Thread status field instead of bumpable and archived fields (per BB-73)
r1414 thread.status = STATUS_ARCHIVE
neko259
Moved imageboard settings to the boards settings module. Added setting to disable archive
r716 thread.last_edit_time = timezone.now()
neko259
Update all posts in thread diff when the thread become not bumpable (BB-66)
r1029 thread.update_posts_time()
neko259
Thread status field instead of bumpable and archived fields (per BB-73)
r1414 thread.save(update_fields=['last_edit_time', 'status'])
neko259
Moved imageboard settings to the boards settings module. Added setting to disable archive
r716
neko259
Faster way of obtaining new post count
r1345 def get_new_posts(self, datas):
query = None
# TODO Use classes instead of dicts
for data in datas:
neko259
Fixed last post link in new favorite posts api
r1346 if data['last_id'] != FAV_THREAD_NO_UPDATES:
neko259
Speed up getting new post count for favorites. Get favorites at page start, not only by JS
r1455 q = (Q(id=data['op'].get_thread_id())
neko259
Fixed post count updating
r1705 & Q(replies__id__gt=data['last_id']))
neko259
Fixed last post link in new favorite posts api
r1346 if query is None:
query = q
else:
query = query | q
if query is not None:
return self.filter(query).annotate(
neko259
Fixed post count updating
r1705 new_post_count=Count('replies'))
neko259
Faster way of obtaining new post count
r1345
def get_new_post_count(self, datas):
neko259
Fixed reflink migration. Fixed new_post API when there are not favorite...
r1347 new_posts = self.get_new_posts(datas)
neko259
Fixed post count updating
r1705 return new_posts.aggregate(total_count=Count('replies'))\
neko259
Fixed reflink migration. Fixed new_post API when there are not favorite...
r1347 ['total_count'] if new_posts else 0
neko259
Faster way of obtaining new post count
r1345
neko259
Code cleanup. Update only edited fields while performing thread archiving or post editing. Remove image when post is removed
r715
neko259
Fixed thread max posts to not create new migration each time it changes in settings
r1136 def get_thread_max_posts():
neko259
Implemented ini settings parser
r1153 return settings.get_int('Messages', 'MaxPostsPerThread')
neko259
Fixed thread max posts to not create new migration each time it changes in settings
r1136
neko259
Moved thread model to a separate module
r691 class Thread(models.Model):
neko259
Code cleanup. Update only edited fields while performing thread archiving or post editing. Remove image when post is removed
r715 objects = ThreadManager()
neko259
Moved thread model to a separate module
r691
class Meta:
app_label = 'boards'
neko259
Show related tags in the tag page
r1269 tags = models.ManyToManyField('Tag', related_name='thread_tags')
neko259
Added index to thread bump time as sorts on it happen often
r966 bump_time = models.DateTimeField(db_index=True)
neko259
Moved thread model to a separate module
r691 last_edit_time = models.DateTimeField()
neko259
Fixed thread max posts to not create new migration each time it changes in settings
r1136 max_posts = models.IntegerField(default=get_thread_max_posts)
neko259
Added PoW instead of 30-second captcha
r1428 status = models.CharField(max_length=50, default=STATUS_ACTIVE,
neko259
Added indexes on frequently used fields
r1667 choices=STATUS_CHOICES, db_index=True)
neko259
Added ability to create monochrome threads
r1434 monochrome = models.BooleanField(default=False)
neko259
Moved thread model to a separate module
r691
neko259
Refactored type hints for thread model
r1186 def get_tags(self) -> QuerySet:
neko259
Moved thread model to a separate module
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
Update all posts in thread diff when the thread become not bumpable (BB-66)
r1029 self.bump_time = self.last_edit_time
neko259
Moved thread model to a separate module
r691
neko259
If we are replying a multi-thread post, update all its threads and their...
r1046 self.update_bump_status()
neko259
Bump thread only after adding post to it
r885
neko259
Moved thread model to a separate module
r691 logger.info('Bumped thread %d' % self.id)
neko259
Added type hints to some models and managers
r1083 def has_post_limit(self) -> bool:
neko259
Added ability to disable bump limit of a thread
r1055 return self.max_posts > 0
neko259
Fixed moving thread to bump limit. Fixed multipost in the not bumpable thread
r1134 def update_bump_status(self, exclude_posts=None):
neko259
Added ability to disable bump limit of a thread
r1055 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
neko259
Thread status field instead of bumpable and archived fields (per BB-73)
r1414 self.status = STATUS_BUMPLIMIT
neko259
Fixed moving thread to bump limit. Fixed multipost in the not bumpable thread
r1134 self.update_posts_time(exclude_posts=exclude_posts)
neko259
If we are replying a multi-thread post, update all its threads and their...
r1046
neko259
Added ability to cache method result with additional arguments. Cache thread...
r1106 def _get_cache_key(self):
return [datetime_to_epoch(self.last_edit_time)]
@cached_result(key_method=_get_cache_key)
neko259
Added type hints to some models and managers
r1083 def get_reply_count(self) -> int:
neko259
Removed unecessary connection of a thread to its replies, cause posts are...
r958 return self.get_replies().count()
neko259
Moved thread model to a separate module
r691
neko259
Added ability to cache method result with additional arguments. Cache thread...
r1106 @cached_result(key_method=_get_cache_key)
neko259
Added type hints to some models and managers
r1083 def get_images_count(self) -> int:
neko259
Store images as regular attachments instead of separate model
r1590 return self.get_replies().filter(
attachments__mimetype__in=FILE_TYPES_IMAGE)\
.annotate(images_count=Count(
neko259
Fixed issues with new images storage
r1591 'attachments')).aggregate(Sum('images_count'))['images_count__sum'] or 0
neko259
Moved thread model to a separate module
r691
neko259
Added type hints to some models and managers
r1083 def can_bump(self) -> bool:
neko259
Moved thread model to a separate module
r691 """
Checks if the thread can be bumped by replying to it.
"""
neko259
Thread status field instead of bumpable and archived fields (per BB-73)
r1414 return self.get_status() == STATUS_ACTIVE
neko259
Moved thread model to a separate module
r691
neko259
Refactored type hints for thread model
r1186 def get_last_replies(self) -> QuerySet:
neko259
Moved thread model to a separate module
r691 """
Gets several last replies, not including opening post
"""
neko259
Implemented ini settings parser
r1153 last_replies_count = settings.get_int('View', 'LastRepliesCount')
if last_replies_count > 0:
neko259
Moved thread model to a separate module
r691 reply_count = self.get_reply_count()
if reply_count > 0:
neko259
Implemented ini settings parser
r1153 reply_count_to_show = min(last_replies_count,
neko259
Moved thread model to a separate module
r691 reply_count - 1)
neko259
Optimized loading post images in post template
r694 replies = self.get_replies()
neko259
Backed out changeset 68e7bfc6c078
r987 last_replies = replies[reply_count - reply_count_to_show:]
neko259
Moved thread model to a separate module
r691
return last_replies
neko259
Added type hints to some models and managers
r1083 def get_skipped_replies_count(self) -> int:
neko259
Moved thread model to a separate module
r691 """
Gets number of posts between opening post and last replies.
"""
reply_count = self.get_reply_count()
neko259
Implemented ini settings parser
r1153 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
neko259
Moved thread model to a separate module
r691 reply_count - 1)
return reply_count - last_replies_count - 1
neko259
Load only viewable post fields in thread
r1614 # TODO Remove argument, it is not used
def get_replies(self, view_fields_only=True) -> QuerySet:
neko259
Moved thread model to a separate module
r691 """
Gets sorted thread posts
"""
neko259
Removed multitread posts 'feature'
r1704 query = self.replies.order_by('pub_time').prefetch_related(
'attachments')
neko259
Don't load all posts when only last are required
r1530 return query
neko259
Moved thread model to a separate module
r691
neko259
Load only viewable post fields in thread
r1614 def get_viewable_replies(self) -> QuerySet:
"""
Gets replies with only fields that are used for viewing.
"""
neko259
Include poster IP in thead loading because it is shown for moderator
r1671 return self.get_replies().defer('text', 'last_edit_time', 'version')
neko259
Load only viewable post fields in thread
r1614
neko259
Refactored type hints for thread model
r1186 def get_top_level_replies(self) -> QuerySet:
neko259
Added tree mode for the thread
r1180 return self.get_replies().exclude(refposts__threads__in=[self])
neko259
Refactored type hints for thread model
r1186 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
neko259
Delete replies properly when deleting a thread. Delete thread directly, not by...
r950 """
Gets replies that have at least one image attached
"""
neko259
Store images as regular attachments instead of separate model
r1590 return self.get_replies(view_fields_only).filter(
attachments__mimetype__in=FILE_TYPES_IMAGE).annotate(images_count=Count(
'attachments')).filter(images_count__gt=0)
neko259
Moved post image to a separate model. Each post (as of model) can contain multiple images now. The image shown to the user is got with get_first_image method
r693
neko259
Added type hints to some models and managers
r1083 def get_opening_post(self, only_id=False) -> Post:
neko259
Moved thread model to a separate module
r691 """
Gets the first post of the thread
"""
neko259
Added index on post.opening. Use it when getting OP of a thread
r1381 query = self.get_replies().filter(opening=True)
neko259
Backed out changeset a37f5ca1da43
r949 if only_id:
query = query.only('id')
opening_post = query.first()
return opening_post
neko259
Moved thread model to a separate module
r691
neko259
Added ability to cache method result with additional arguments. Cache thread...
r1106 @cached_result()
neko259
Added type hints to some models and managers
r1083 def get_opening_post_id(self) -> int:
neko259
Moved thread model to a separate module
r691 """
Gets ID of the first thread post.
"""
neko259
Started work on the method caching decorator (BB-57)
r957 return self.get_opening_post(only_id=True).id
neko259
Moved thread model to a separate module
r691
def get_pub_time(self):
"""
Gets opening post's pub time because thread does not have its own one.
"""
neko259
Backed out changeset a37f5ca1da43
r949 return self.get_opening_post().pub_time
neko259
Use own search form and view
r718
neko259
Show thread, post and tag names in admin with python3
r875 def __str__(self):
neko259
Post URL loading optimizations
r1668 return 'T#{}'.format(self.id)
neko259
Refactoring
r1027
neko259
Added type hints to some models and managers
r1083 def get_tag_url_list(self) -> list:
neko259
Update all posts in thread diff when the thread become not bumpable (BB-66)
r1029 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
neko259
Fixed moving thread to bump limit. Fixed multipost in the not bumpable thread
r1134 def update_posts_time(self, exclude_posts=None):
neko259
Fixed thread bumping
r1221 last_edit_time = self.last_edit_time
neko259
Fixed issues related to removal of multithread posts
r1708 for post in self.replies.all():
neko259
Fixed updating thread posts when reaching bumplimit. Updated logo
r1219 if exclude_posts is None or post not in exclude_posts:
neko259
Fixed moving thread to bump limit. Fixed multipost in the not bumpable thread
r1134 # Manual update is required because uids are generated on save
neko259
Fixed thread bumping
r1221 post.last_edit_time = last_edit_time
neko259
Fixed moving thread to bump limit. Fixed multipost in the not bumpable thread
r1134 post.save(update_fields=['last_edit_time'])
neko259
Added index on post. Update post uids when the thread reaches bumplimit
r1120
neko259
Added ability to show multiple banners. Added "view on site" link for the post...
r1149 def get_absolute_url(self):
return self.get_opening_post().get_absolute_url()
neko259
Show only sections in the image tags list in random images
r1257
def get_required_tags(self):
return self.get_tags().filter(required=True)
neko259
Favorite threads with new posts counter
r1323
neko259
Show threads' sections in the landing page
r1739 def get_sections_str(self):
return Tag.objects.get_tag_url_list(self.get_required_tags())
neko259
Favorite threads with new posts counter
r1323 def get_replies_newer(self, post_id):
return self.get_replies().filter(id__gt=post_id)
neko259
Don't check new posts in the archived threads
r1344 def is_archived(self):
neko259
Thread status field instead of bumpable and archived fields (per BB-73)
r1414 return self.get_status() == STATUS_ARCHIVE
def get_status(self):
return self.status
neko259
Added ability to create monochrome threads
r1434
def is_monochrome(self):
return self.monochrome
neko259
Update tag threads' tags when the tag's parent is changed
r1471
# If tags have parent, add them to the tag list
@transaction.atomic
def refresh_tags(self):
for tag in self.get_tags().all():
parents = tag.get_all_parents()
if len(parents) > 0:
self.tags.add(*parents)
neko259
New fast recursion-free tree render engine
r1473 def get_reply_tree(self):
replies = self.get_replies().prefetch_related('refposts')
tree = []
for reply in replies:
parents = reply.refposts.all()
neko259
One more tree fix
r1489
neko259
New fast recursion-free tree render engine
r1473 found_parent = False
neko259
One more tree fix
r1489 searching_for_index = False
neko259
New fast recursion-free tree render engine
r1473 if len(parents) > 0:
index = 0
neko259
One more tree fix
r1489 parent_depth = 0
indexes_to_insert = []
neko259
New fast recursion-free tree render engine
r1473 for depth, element in tree:
index += 1
neko259
One more tree fix
r1489
# If this element is next after parent on the same level,
# insert child before it
if searching_for_index and depth <= parent_depth:
indexes_to_insert.append((index - 1, parent_depth))
searching_for_index = False
neko259
New fast recursion-free tree render engine
r1473 if element in parents:
neko259
Fixed tree positions for siblings that have children under them
r1488 found_parent = True
neko259
One more tree fix
r1489 searching_for_index = True
parent_depth = depth
neko259
Fixed tree child positions
r1487
neko259
New fast recursion-free tree render engine
r1473 if not found_parent:
tree.append((0, reply))
neko259
One more tree fix
r1489 else:
if searching_for_index:
tree.append((parent_depth + 1, reply))
offset = 0
for last_index, parent_depth in indexes_to_insert:
tree.insert(last_index + offset, (parent_depth + 1, reply))
offset += 1
neko259
New fast recursion-free tree render engine
r1473
return tree