##// END OF EJS Templates
Added missing migration
Added missing migration

File last commit:

r1242:b72fba6e decentral
r1243:c8024468 decentral
Show More
__init__.py
401 lines | 12.5 KiB | text/x-python | PythonLexer
neko259
Refactored post export
r1156 import logging
import re
import uuid
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
neko259
Split post module into post and manager
r1234 from django.db import models
neko259
Fixed thread bumping
r1221 from django.db.models import TextField, QuerySet
neko259
Split post module into post and manager
r1234
neko259
Refactored post export
r1156 from django.template.loader import render_to_string
neko259
Split post module into post and manager
r1234
neko259
Refactored post export
r1156 from django.utils import timezone
neko259
Partly merged with default branch
r1157 from boards.mdx_neboard import Parser
neko259
Updated sync method for requesting and getting a post
r1177 from boards.models import KeyPair, GlobalId
neko259
Refactored post export
r1156 from boards import settings
from boards.models import PostImage
from boards.models.base import Viewable
from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
neko259
Split post module into post and manager
r1234 from boards.models.post.manager import PostManager
from boards.models.user import Notification
neko259
Refactored post export
r1156
neko259
Partly merged with default branch
r1157 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
WS_NOTIFICATION_TYPE = 'notification_type'
WS_CHANNEL_THREAD = "thread:"
neko259
Refactored post export
r1156
APP_LABEL_BOARDS = 'boards'
BAN_REASON_AUTO = 'Auto'
IMAGE_THUMB_SIZE = (200, 150)
TITLE_MAX_LENGTH = 200
REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
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_MODERATOR = 'moderator'
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',
'moderator',
'need_open_link',
'truncated',
'mode_tree',
)
neko259
Refactored post export
r1156 REFMAP_STR = '<a href="{}">&gt;&gt;{}</a>'
class Post(models.Model, Viewable):
"""A post is a message."""
objects = PostManager()
class Meta:
app_label = APP_LABEL_BOARDS
ordering = ('id',)
title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
pub_time = models.DateTimeField()
text = TextField(blank=True, null=True)
_text_rendered = TextField(blank=True, null=True, editable=False)
images = models.ManyToManyField(PostImage, null=True, blank=True,
related_name='ip+', db_index=True)
poster_ip = models.GenericIPAddressField()
# TODO This field can be removed cause UID is used for update now
last_edit_time = models.DateTimeField()
referenced_posts = models.ManyToManyField('Post', symmetrical=False,
null=True,
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)
threads = models.ManyToManyField('Thread', db_index=True)
thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
url = models.TextField()
uid = models.TextField(db_index=True)
neko259
Partly merged with default branch
r1157 # Global ID with author key. If the message was downloaded from another
# server, this indicates the server.
global_id = models.OneToOneField('GlobalId', null=True, blank=True)
neko259
Refactored post export
r1156 def __str__(self):
return 'P#{}/{}'.format(self.id, self.title)
neko259
Added tree mode for the thread
r1180 def get_referenced_posts(self):
neko259
Don't show replies from other threads in the tree mode
r1181 threads = self.get_threads().all()
neko259
Merged with defaul
r1241 return self.referenced_posts.filter(threads__in=threads) \
.order_by('pub_time').distinct().all()
neko259
Added tree mode for the thread
r1180
neko259
Refactored post export
r1156 def get_title(self) -> str:
"""
Gets original post title or part of its text.
"""
title = self.title
if not title:
title = self.get_text()
return title
def build_refmap(self) -> None:
"""
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
Use get_absolute_url instead of get_url for post, tag and thread
r1160 post_urls = [REFMAP_STR.format(refpost.get_absolute_url(), refpost.id)
neko259
Refactored post export
r1156 for refpost in self.referenced_posts.all()]
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.
"""
return self.get_thread().get_opening_post_id() == self.id
def get_absolute_url(self):
neko259
Fixed building reflink maps that were broken when post link generator was...
r1192 if self.url:
return self.url
else:
opening_id = self.get_thread().get_opening_post_id()
post_url = reverse('thread', kwargs={'post_id': opening_id})
if self.id != opening_id:
post_url += '#' + str(self.id)
return post_url
neko259
Refactored post export
r1156 def get_thread(self):
return self.thread
neko259
Fixed thread bumping
r1221 def get_threads(self) -> QuerySet:
neko259
Refactored post export
r1156 """
Gets post's thread.
"""
return self.threads
neko259
Added tree mode for the thread
r1180 def get_view(self, *args, **kwargs) -> str:
neko259
Refactored post export
r1156 """
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.
"""
thread = self.get_thread()
is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
if is_opening:
opening_post_id = self.id
else:
opening_post_id = thread.get_opening_post_id()
css_class = 'post'
if thread.archived:
css_class += ' archive_post'
elif not thread.can_bump():
css_class += ' dead_post'
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,
PARAMETER_IS_OPENING: is_opening,
PARAMETER_THREAD: thread,
PARAMETER_CSS_CLASS: css_class,
PARAMETER_OP_ID: opening_post_id,
})
neko259
Added tree mode for the thread
r1180 return render_to_string('boards/post.html', params)
neko259
Refactored post export
r1156 def get_search_view(self, *args, **kwargs):
neko259
Show thread link and title in the search results and notifications
r1172 return self.get_view(need_op_data=True, *args, **kwargs)
neko259
Refactored post export
r1156
def get_first_image(self) -> PostImage:
return self.images.earliest('id')
def delete(self, using=None):
"""
Deletes all post images and the post itself.
"""
for image in self.images.all():
image_refs_count = Post.objects.filter(images__in=[image]).count()
if image_refs_count == 1:
image.delete()
neko259
Partly merged with default branch
r1157 if self.global_id:
self.global_id.delete()
neko259
Refactored post export
r1156 thread = self.get_thread()
thread.last_edit_time = timezone.now()
thread.save()
super(Post, self).delete(using)
logging.getLogger('boards.post.delete').info(
'Deleted post {}'.format(self))
neko259
Partly merged with default branch
r1157 def set_global_id(self, key_pair=None):
"""
Sets global id based on the given key pair. If no key pair is given,
default one is used.
"""
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
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()
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]
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)
def notify_clients(self, recursive=True):
"""
Sends post HTML data to the thread web socket.
"""
if not settings.get_bool('External', 'WebsocketsEnabled'):
return
thread_ids = list()
for thread in self.get_threads().all():
thread_ids.append(thread.id)
thread.notify_clients()
if recursive:
for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
post_id = reply_number.group(1)
try:
ref_post = Post.objects.get(id=post_id)
if ref_post.get_threads().exclude(id__in=thread_ids).exists():
# If post is in this thread, its thread was already notified.
# Otherwise, notify its thread separately.
ref_post.notify_clients(recursive=False)
except ObjectDoesNotExist:
pass
def build_url(self):
neko259
Fixed building reflink maps that were broken when post link generator was...
r1192 self.url = self.get_absolute_url()
neko259
Refactored post export
r1156 self.save(update_fields=['url'])
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
self._text_rendered = Parser().parse(self.get_raw_text())
self.uid = str(uuid.uuid4())
if update_fields is not None and 'uid' not in update_fields:
update_fields += ['uid']
if self.id:
for thread in self.get_threads().all():
thread.last_edit_time = self.last_edit_time
neko259
Merged with default branch
r1227 thread.save(update_fields=['last_edit_time'])
neko259
Refactored post export
r1156
super().save(force_insert, force_update, using, update_fields)
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()):
absolute_post_id = str(Post.objects.get(id=post_id).global_id)
replacements[post_id] = absolute_post_id
text = self.get_raw_text()
for key in replacements:
text = text.replace('[post]{}[/post]'.format(key),
'[post]{}[/post]'.format(replacements[key]))
return text
neko259
Refactored post export
r1156 def get_absolute_id(self) -> str:
"""
If the post has many threads, shows its main thread OP id in the post
ID.
"""
if self.get_threads().count() > 1:
return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
else:
return str(self.id)
def connect_notifications(self):
for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
user_name = reply_number.group(1).lower()
Notification.objects.get_or_create(name=user_name, post=self)
def connect_replies(self):
"""
Connects replies to a post to show them as a reflink map
"""
for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
post_id = reply_number.group(1)
try:
referenced_post = Post.objects.get(id=post_id)
referenced_post.referenced_posts.add(self)
referenced_post.last_edit_time = self.pub_time
referenced_post.build_refmap()
referenced_post.save(update_fields=['refmap', 'last_edit_time'])
except ObjectDoesNotExist:
pass
def connect_threads(self, opening_posts):
for opening_post in opening_posts:
threads = opening_post.get_threads().all()
for thread in threads:
if thread.can_bump():
thread.update_bump_status()
thread.last_edit_time = self.last_edit_time
thread.save(update_fields=['last_edit_time', 'bumpable'])
neko259
Connect reply to the multi-thread OP only to this OP main thread, not all...
r1201 self.threads.add(opening_post.get_thread())