##// END OF EJS Templates
Serialize timestamp before passing to tag. Fail if the passed timestamp is invalid and cannot be parsed
Serialize timestamp before passing to tag. Fail if the passed timestamp is invalid and cannot be parsed

File last commit:

r2067:bbee31e4 default
r2122:edce8526 default
Show More
__init__.py
367 lines | 11.3 KiB | text/x-python | PythonLexer
neko259
Refactored post export
r1156 import uuid
neko259
Adapt to django-2.0
r1986
neko259
Moderator can see poster IPs in the post view
r1638 import hashlib
import re
neko259
Adapt to django-2.0
r1986 from django.db import models
from django.db.models import TextField
from django.template.defaultfilters import truncatewords, striptags
from django.template.loader import render_to_string
from django.urls import reverse
neko259
Refactored post export
r1156
neko259
Unify thread and post creation into one method inside post manager, that can be called from almost anywhere (one step closer to ajax thread creation)
r1997 from boards.abstracts.constants import REGEX_REPLY
neko259
Added ability to reset tripcode
r1296 from boards.abstracts.tripcode import Tripcode
neko259
Store images as regular attachments instead of separate model
r1590 from boards.models import Attachment, KeyPair, GlobalId
from boards.models.attachment import FILE_TYPES_IMAGE
neko259
Refactored post export
r1156 from boards.models.base import Viewable
from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
neko259
Do not show IP colored lines and feed selectors when there is no saved IP
r1678 from boards.models.post.manager import PostManager, NO_IP
neko259
Don't show absolute post id, it slows down loading
r1533 from boards.utils import datetime_to_epoch
neko259
Refactored post export
r1156
neko259
Added ability to hide a post
r1366 CSS_CLS_HIDDEN_POST = 'hidden_post'
CSS_CLS_DEAD_POST = 'dead_post'
CSS_CLS_ARCHIVE_POST = 'archive_post'
CSS_CLS_POST = 'post'
neko259
Added ability to create monochrome threads
r1434 CSS_CLS_MONOCHROME = 'monochrome'
neko259
Added ability to hide a post
r1366
neko259
Moved template name and class delimiter for post to constants
r2029 CSS_CLASS_DELIMITER = ' '
neko259
Unified getting post title or text part if there is no title
r1358 TITLE_MAX_WORDS = 10
neko259
Refactored post export
r1156 APP_LABEL_BOARDS = 'boards'
BAN_REASON_AUTO = 'Auto'
TITLE_MAX_LENGTH = 200
neko259
Partly merged with default branch
r1157 REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]')
REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
neko259
Refactored post export
r1156 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
PARAMETER_TRUNCATED = 'truncated'
PARAMETER_TAG = 'tag'
PARAMETER_OFFSET = 'offset'
PARAMETER_DIFF_TYPE = 'type'
PARAMETER_CSS_CLASS = 'css_class'
PARAMETER_THREAD = 'thread'
PARAMETER_IS_OPENING = 'is_opening'
PARAMETER_POST = 'post'
PARAMETER_OP_ID = 'opening_post_id'
PARAMETER_NEED_OPEN_LINK = 'need_open_link'
PARAMETER_REPLY_LINK = 'reply_link'
neko259
Add link to thread in the post feed
r1166 PARAMETER_NEED_OP_DATA = 'need_op_data'
neko259
Refactored post export
r1156
neko259
Added tree mode for the thread
r1180 POST_VIEW_PARAMS = (
'need_op_data',
'reply_link',
'need_open_link',
'truncated',
'mode_tree',
neko259
Use native django permission system in templates
r1388 'perms',
neko259
New fast recursion-free tree render engine
r1473 'tree_depth',
neko259
Added tree mode for the thread
r1180 )
neko259
Moved template name and class delimiter for post to constants
r2029 TEMPLATE_POST = 'boards/post.html'
neko259
Unified getting post title or text part if there is no title
r1358
neko259
Refactored post export
r1156 class Post(models.Model, Viewable):
"""A post is a message."""
objects = PostManager()
class Meta:
app_label = APP_LABEL_BOARDS
ordering = ('id',)
neko259
Removed null=True from some text fields
r1751 title = models.CharField(max_length=TITLE_MAX_LENGTH, blank=True, default='')
neko259
Added indexes on frequently used fields
r1667 pub_time = models.DateTimeField(db_index=True)
neko259
Removed null=True from some text fields
r1751 text = TextField(blank=True, default='')
neko259
Refactored post export
r1156 _text_rendered = TextField(blank=True, null=True, editable=False)
neko259
Removed 2 null=True attributes that raise system warnings
r2067 attachments = models.ManyToManyField(Attachment, blank=True,
neko259
Unified getting post title or text part if there is no title
r1358 related_name='attachment_posts')
neko259
Refactored post export
r1156
poster_ip = models.GenericIPAddressField()
neko259
Removed stale TODOs. Fixed image actions not getting links
r1601 # Used for cache and threads updating
neko259
Refactored post export
r1156 last_edit_time = models.DateTimeField()
referenced_posts = models.ManyToManyField('Post', symmetrical=False,
neko259
Added tree mode for the thread
r1180 blank=True, related_name='refposts',
neko259
Refactored post export
r1156 db_index=True)
refmap = models.TextField(null=True, blank=True)
neko259
Adapt to django-2.0
r1986 thread = models.ForeignKey('Thread', on_delete=models.CASCADE,
db_index=True, related_name='replies')
neko259
Refactored post export
r1156
url = models.TextField()
neko259
Do not index post's uid field as mysql does not support it
r1835 uid = models.TextField()
neko259
Refactored post export
r1156
neko259
Partly merged with default branch
r1157 # Global ID with author key. If the message was downloaded from another
# server, this indicates the server.
neko259
Download attached filed to the post during sync
r1511 global_id = models.OneToOneField(GlobalId, null=True, blank=True,
on_delete=models.CASCADE)
neko259
Partly merged with default branch
r1157
neko259
Allow to edit tag and post in the admin site
r1367 tripcode = models.CharField(max_length=50, blank=True, default='')
neko259
Added index on post.opening. Use it when getting OP of a thread
r1381 opening = models.BooleanField(db_index=True)
neko259
Added ability to hide a post
r1366 hidden = models.BooleanField(default=False)
neko259
Added tripcodes
r1293
neko259
Refactored post export
r1156 def __str__(self):
neko259
Unified getting post title or text part if there is no title
r1358 return 'P#{}/{}'.format(self.id, self.get_title())
neko259
Refactored post export
r1156
def get_title(self) -> str:
neko259
Unified getting post title or text part if there is no title
r1358 return self.title
neko259
Refactored post export
r1156
neko259
Unified getting post title or text part if there is no title
r1358 def get_title_or_text(self):
title = self.get_title()
neko259
Refactored post export
r1156 if not title:
neko259
Unified getting post title or text part if there is no title
r1358 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
neko259
Refactored post export
r1156
return title
neko259
Rebuild refmap after post from it was deleted
r1619 def build_refmap(self, excluded_ids=None) -> None:
neko259
Refactored post export
r1156 """
Builds a replies map string from replies list. This is a cache to stop
the server from recalculating the map on every post show.
"""
neko259
Rebuild refmap after post from it was deleted
r1619 replies = self.referenced_posts
if excluded_ids is not None:
replies = replies.exclude(id__in=excluded_ids)
else:
replies = replies.all()
post_urls = [refpost.get_link_view() for refpost in replies]
neko259
Refactored post export
r1156
self.refmap = ', '.join(post_urls)
def is_referenced(self) -> bool:
return self.refmap and len(self.refmap) > 0
def is_opening(self) -> bool:
"""
Checks if this is an opening post or just a reply.
"""
neko259
Save "opening post" flag with the post itself and don't count it every time. Speed up getting posts with attachments and images
r1337 return self.opening
neko259
Refactored post export
r1156
neko259
Show updated thread's link in favorite's new multipost
r1365 def get_absolute_url(self, thread=None):
neko259
Post URL loading optimizations
r1668 # Url is cached only for the "main" thread. When getting url
# for other threads, do it manually.
neko259
Fixed building post refmaps
r2047 return self.url or self._build_url()
neko259
Fixed building reflink maps that were broken when post link generator was...
r1192
neko259
Refactored post export
r1156 def get_thread(self):
return self.thread
neko259
Speed up getting new post count for favorites. Get favorites at page start, not only by JS
r1455 def get_thread_id(self):
return self.thread_id
neko259
Cache post's threads count, don't load all threads each time
r1531 def _get_cache_key(self):
return [datetime_to_epoch(self.last_edit_time)]
neko259
Use inclusion tag for post_view
r1648 def get_view_params(self, *args, **kwargs):
neko259
Added docstring
r1651 """
Gets the parameters required for viewing the post based on the arguments
given and the post itself.
"""
neko259
If thread is specified in the post template, do not load it again
r1670 thread = kwargs.get('thread') or self.get_thread()
neko259
Refactored post export
r1156
neko259
Added ability to hide a post
r1366 css_classes = [CSS_CLS_POST]
neko259
Thread status field instead of bumpable and archived fields (per BB-73)
r1414 if thread.is_archived():
neko259
Added ability to hide a post
r1366 css_classes.append(CSS_CLS_ARCHIVE_POST)
neko259
Refactored post export
r1156 elif not thread.can_bump():
neko259
Added ability to hide a post
r1366 css_classes.append(CSS_CLS_DEAD_POST)
if self.is_hidden():
css_classes.append(CSS_CLS_HIDDEN_POST)
neko259
Added ability to create monochrome threads
r1434 if thread.is_monochrome():
css_classes.append(CSS_CLS_MONOCHROME)
neko259
Refactored post export
r1156
neko259
Added tree mode for the thread
r1180 params = dict()
for param in POST_VIEW_PARAMS:
if param in kwargs:
params[param] = kwargs[param]
params.update({
neko259
Refactored post export
r1156 PARAMETER_POST: self,
neko259
Save "opening post" flag with the post itself and don't count it every time. Speed up getting posts with attachments and images
r1337 PARAMETER_IS_OPENING: self.is_opening(),
neko259
Refactored post export
r1156 PARAMETER_THREAD: thread,
neko259
Moved template name and class delimiter for post to constants
r2029 PARAMETER_CSS_CLASS: CSS_CLASS_DELIMITER.join(css_classes),
neko259
Refactored post export
r1156 })
neko259
Use inclusion tag for post_view
r1648 return params
def get_view(self, *args, **kwargs) -> str:
"""
Renders post's HTML view. Some of the post params can be passed over
kwargs for the means of caching (if we view the thread, some params
are same for every post and don't need to be computed over and over.
"""
neko259
Fixed issue with newly added posts appearance
r1650 params = self.get_view_params(*args, **kwargs)
neko259
Use inclusion tag for post_view
r1648
neko259
Moved template name and class delimiter for post to constants
r2029 return render_to_string(TEMPLATE_POST, params)
neko259
Added tree mode for the thread
r1180
neko259
Show all post images in gallery
r1755 def get_images(self) -> Attachment:
return self.attachments.filter(mimetype__in=FILE_TYPES_IMAGE)
neko259
Store images as regular attachments instead of separate model
r1590 def get_first_image(self) -> Attachment:
neko259
Quick fix for the threads list API
r1725 try:
neko259
Show all post images in gallery
r1755 return self.get_images().earliest('-id')
neko259
Quick fix for the threads list API
r1725 except Attachment.DoesNotExist:
neko259
Show all post images in gallery
r1755 return None
neko259
Refactored post export
r1156
neko259
Partly merged with default branch
r1157 def set_global_id(self, key_pair=None):
neko259
Refactored post export
r1156 """
neko259
Partly merged with default branch
r1157 Sets global id based on the given key pair. If no key pair is given,
default one is used.
neko259
Refactored post export
r1156 """
neko259
Partly merged with default branch
r1157 if key_pair:
key = key_pair
else:
try:
key = KeyPair.objects.get(primary=True)
except KeyPair.DoesNotExist:
# Do not update the global id because there is no key defined
return
global_id = GlobalId(key_type=key.key_type,
key=key.public_key,
neko259
Sync-import of a single post is working
r1229 local_id=self.id)
neko259
Partly merged with default branch
r1157 global_id.save()
self.global_id = global_id
self.save(update_fields=['global_id'])
neko259
Sync-import of a single post is working
r1229 def get_pub_time_str(self):
return str(self.pub_time)
neko259
Refactored post export
r1156
neko259
Partly merged with default branch
r1157 def get_replied_ids(self):
"""
Gets ID list of the posts that this post replies.
"""
raw_text = self.get_raw_text()
neko259
Delete attachments with posts
r1278
neko259
Partly merged with default branch
r1157 local_replied = REGEX_REPLY.findall(raw_text)
global_replied = []
for match in REGEX_GLOBAL_REPLY.findall(raw_text):
key_type = match[0]
key = match[1]
local_id = match[2]
neko259
Refactored post export
r1156
neko259
Partly merged with default branch
r1157 try:
global_id = GlobalId.objects.get(key_type=key_type,
neko259
Merged with defaul
r1241 key=key, local_id=local_id)
neko259
Partly merged with default branch
r1157 for post in Post.objects.filter(global_id=global_id).only('id'):
global_replied.append(post.id)
except GlobalId.DoesNotExist:
pass
return local_replied + global_replied
neko259
Refactored post export
r1156
def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
include_last_update=False) -> str:
"""
Gets post HTML or JSON data that can be rendered on a page or used by
API.
"""
return get_exporter(format_type).export(self, request,
include_last_update)
neko259
Always save post URL
r1669 def _build_url(self):
neko259
Fixed building post refmaps
r2047 if self.id:
opening = self.is_opening()
opening_id = self.id if opening else self.get_thread().get_opening_post_id()
url = reverse('thread', kwargs={'post_id': opening_id})
if not opening:
url += '#' + str(self.id)
neko259
Always save post URL
r1669
neko259
Fixed building post refmaps
r2047 return url
neko259
Refactored post export
r1156
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
neko259
Refactored post post-save triggers
r1440 new_post = self.id is None
neko259
Refactored post export
r1156 self.uid = str(uuid.uuid4())
if update_fields is not None and 'uid' not in update_fields:
update_fields += ['uid']
neko259
Refactored post post-save triggers
r1440 if not new_post:
neko259
Removed multitread posts 'feature'
r1704 thread = self.get_thread()
if thread:
neko259
Refactored post export
r1156 thread.last_edit_time = self.last_edit_time
neko259
Thread status field instead of bumpable and archived fields (per BB-73)
r1414 thread.save(update_fields=['last_edit_time', 'status'])
neko259
Refactored post export
r1156
super().save(force_insert, force_update, using, update_fields)
neko259
Always save post URL
r1669 if new_post:
neko259
Fixed building post refmaps
r2047 if not self.url:
self.url = self._build_url()
neko259
Always save post URL
r1669 super().save(update_fields=['url'])
neko259
Refactored post post-save triggers
r1440
neko259
Refactored post export
r1156 def get_text(self) -> str:
return self._text_rendered
def get_raw_text(self) -> str:
return self.text
neko259
Convert local post IDs to global when generating responses
r1228 def get_sync_text(self) -> str:
"""
Returns text applicable for sync. It has absolute post reflinks.
"""
replacements = dict()
for post_id in REGEX_REPLY.findall(self.get_raw_text()):
neko259
Fixed getting sync text of broken reflinks. Reply posts when quoting them
r1575 try:
absolute_post_id = str(Post.objects.get(id=post_id).global_id)
replacements[post_id] = absolute_post_id
except Post.DoesNotExist:
pass
neko259
Convert local post IDs to global when generating responses
r1228
neko259
Download attached filed to the post during sync
r1511 text = self.get_raw_text() or ''
neko259
Convert local post IDs to global when generating responses
r1228 for key in replacements:
text = text.replace('[post]{}[/post]'.format(key),
'[post]{}[/post]'.format(replacements[key]))
neko259
Convert \r\n and \r to \n in the post text used in sync
r1504 text = text.replace('\r\n', '\n').replace('\r', '\n')
neko259
Convert local post IDs to global when generating responses
r1228
return text
neko259
Added ability to reset tripcode
r1296 def get_tripcode(self):
if self.tripcode:
return Tripcode(self.tripcode)
neko259
Reflinks to OPs are bold now. Refactored reflinks to build using the same code. Refactored autoescaping
r1309
def get_link_view(self):
"""
Gets view of a reflink to the post.
"""
result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
neko259
Favorite threads with new posts counter
r1323 self.id)
neko259
Reflinks to OPs are bold now. Refactored reflinks to build using the same code. Refactored autoescaping
r1309 if self.is_opening():
result = '<b>{}</b>'.format(result)
return result
neko259
Added ability to hide a post
r1366
def is_hidden(self) -> bool:
return self.hidden
def set_hidden(self, hidden):
self.hidden = hidden
neko259
Added version to post content
r1569
def clear_cache(self):
neko259
Process updated posts from sync server
r1586 """
Clears sync data (content cache, signatures etc).
"""
neko259
Added version to post content
r1569 global_id = self.global_id
if global_id is not None and global_id.is_local()\
and global_id.content is not None:
neko259
Process updated posts from sync server
r1586 global_id.clear_cache()
neko259
Added tags list to post admin. Post's tags are its thread's tags for now
r1620
def get_tags(self):
return self.get_thread().get_tags()
neko259
Moderator can see poster IPs in the post view
r1638
def get_ip_color(self):
return hashlib.md5(self.poster_ip.encode()).hexdigest()[:6]
neko259
Do not show IP colored lines and feed selectors when there is no saved IP
r1678
def has_ip(self):
return self.poster_ip != NO_IP
neko259
Moved moderation panel completely to javascript, which will use one menu for all posts instead of a new one for each loaded
r2039 def has_global_id(self):
return self.global_id_id is not None