post.py
348 lines
| 9.7 KiB
| text/x-python
|
PythonLexer
neko259
|
r586 | from datetime import datetime, timedelta, date | |
neko259
|
r407 | from datetime import time as dtime | |
neko259
|
r639 | import logging | |
neko259
|
r386 | import re | |
neko259
|
r527 | ||
neko259
|
r410 | from django.core.cache import cache | |
neko259
|
r589 | from django.core.urlresolvers import reverse | |
neko259
|
r566 | from django.db import models, transaction | |
neko259
|
r692 | from django.template.loader import render_to_string | |
neko259
|
r384 | from django.utils import timezone | |
from markupfield.fields import MarkupField | |||
neko259
|
r723 | ||
neko259
|
r693 | from boards.models import PostImage | |
neko259
|
r692 | from boards.models.base import Viewable | |
neko259
|
r691 | from boards.models.thread import Thread | |
neko259
|
r384 | ||
neko259
|
r622 | ||
neko259
|
r410 | APP_LABEL_BOARDS = 'boards' | |
CACHE_KEY_PPD = 'ppd' | |||
neko259
|
r589 | CACHE_KEY_POST_URL = 'post_url' | |
neko259
|
r410 | ||
neko259
|
r408 | POSTS_PER_DAY_RANGE = range(7) | |
neko259
|
r384 | BAN_REASON_AUTO = 'Auto' | |
IMAGE_THUMB_SIZE = (200, 150) | |||
neko259
|
r612 | TITLE_MAX_LENGTH = 200 | |
neko259
|
r384 | ||
DEFAULT_MARKUP_TYPE = 'markdown' | |||
neko259
|
r702 | # TODO This should be removed | |
neko259
|
r384 | NO_IP = '0.0.0.0' | |
neko259
|
r702 | ||
# TODO Real user agent should be saved instead of this | |||
neko259
|
r384 | UNKNOWN_UA = '' | |
neko259
|
r702 | ||
neko259
|
r384 | SETTING_MODERATE = "moderate" | |
REGEX_REPLY = re.compile('>>(\d+)') | |||
neko259
|
r639 | logger = logging.getLogger(__name__) | |
neko259
|
r384 | ||
class PostManager(models.Manager): | |||
neko259
|
r728 | def create_post(self, title, text, image=None, thread=None, ip=NO_IP, | |
tags=None): | |||
neko259
|
r418 | """ | |
neko259
|
r622 | Creates new post | |
neko259
|
r418 | """ | |
neko259
|
r384 | posting_time = timezone.now() | |
neko259
|
r398 | if not thread: | |
thread = Thread.objects.create(bump_time=posting_time, | |||
last_edit_time=posting_time) | |||
neko259
|
r615 | new_thread = True | |
neko259
|
r398 | else: | |
thread.bump() | |||
thread.last_edit_time = posting_time | |||
thread.save() | |||
neko259
|
r615 | new_thread = False | |
neko259
|
r384 | ||
post = self.create(title=title, | |||
text=text, | |||
pub_time=posting_time, | |||
neko259
|
r398 | thread_new=thread, | |
neko259
|
r384 | poster_ip=ip, | |
neko259
|
r484 | poster_user_agent=UNKNOWN_UA, # TODO Get UA at | |
# last! | |||
neko259
|
r728 | last_edit_time=posting_time) | |
neko259
|
r384 | ||
neko259
|
r693 | if image: | |
post_image = PostImage.objects.create(image=image) | |||
post.images.add(post_image) | |||
logger.info('Created image #%d for post #%d' % (post_image.id, | |||
post.id)) | |||
neko259
|
r398 | thread.replies.add(post) | |
neko259
|
r384 | if tags: | |
linked_tags = [] | |||
for tag in tags: | |||
tag_linked_tags = tag.get_linked_tags() | |||
if len(tag_linked_tags) > 0: | |||
linked_tags.extend(tag_linked_tags) | |||
tags.extend(linked_tags) | |||
neko259
|
r398 | map(thread.add_tag, tags) | |
neko259
|
r384 | ||
neko259
|
r615 | if new_thread: | |
neko259
|
r716 | Thread.objects.process_oldest_threads() | |
neko259
|
r384 | self.connect_replies(post) | |
neko259
|
r639 | logger.info('Created post #%d' % post.id) | |
neko259
|
r384 | return post | |
def delete_post(self, post): | |||
neko259
|
r418 | """ | |
neko259
|
r622 | Deletes post and update or delete its thread | |
neko259
|
r418 | """ | |
neko259
|
r552 | ||
neko259
|
r639 | post_id = post.id | |
neko259
|
r619 | thread = post.get_thread() | |
neko259
|
r418 | ||
neko259
|
r552 | if post.is_opening(): | |
neko259
|
r718 | thread.delete() | |
neko259
|
r456 | else: | |
thread.last_edit_time = timezone.now() | |||
thread.save() | |||
neko259
|
r384 | ||
neko259
|
r470 | post.delete() | |
neko259
|
r384 | ||
neko259
|
r639 | logger.info('Deleted post #%d' % post_id) | |
neko259
|
r384 | def delete_posts_by_ip(self, ip): | |
neko259
|
r418 | """ | |
neko259
|
r622 | Deletes all posts of the author with same IP | |
neko259
|
r418 | """ | |
neko259
|
r384 | posts = self.filter(poster_ip=ip) | |
map(self.delete_post, posts) | |||
def connect_replies(self, post): | |||
neko259
|
r418 | """ | |
neko259
|
r622 | Connects replies to a post to show them as a reflink map | |
neko259
|
r418 | """ | |
neko259
|
r384 | ||
for reply_number in re.finditer(REGEX_REPLY, post.text.raw): | |||
post_id = reply_number.group(1) | |||
ref_post = self.filter(id=post_id) | |||
if ref_post.count() > 0: | |||
referenced_post = ref_post[0] | |||
referenced_post.referenced_posts.add(post) | |||
referenced_post.last_edit_time = post.pub_time | |||
neko259
|
r674 | referenced_post.build_refmap() | |
neko259
|
r715 | referenced_post.save(update_fields=['refmap', 'last_edit_time']) | |
neko259
|
r384 | ||
neko259
|
r619 | referenced_thread = referenced_post.get_thread() | |
neko259
|
r535 | referenced_thread.last_edit_time = post.pub_time | |
neko259
|
r715 | referenced_thread.save(update_fields=['last_edit_time']) | |
neko259
|
r535 | ||
neko259
|
r407 | def get_posts_per_day(self): | |
neko259
|
r418 | """ | |
neko259
|
r622 | Gets average count of posts per day for the last 7 days | |
neko259
|
r418 | """ | |
neko259
|
r407 | ||
neko259
|
r586 | today = date.today() | |
neko259
|
r424 | ppd = cache.get(CACHE_KEY_PPD + str(today)) | |
neko259
|
r410 | if ppd: | |
return ppd | |||
neko259
|
r408 | posts_per_days = [] | |
for i in POSTS_PER_DAY_RANGE: | |||
neko259
|
r413 | day_end = today - timedelta(i + 1) | |
day_start = today - timedelta(i + 2) | |||
neko259
|
r608 | day_time_start = timezone.make_aware(datetime.combine( | |
day_start, dtime()), timezone.get_current_timezone()) | |||
day_time_end = timezone.make_aware(datetime.combine( | |||
day_end, dtime()), timezone.get_current_timezone()) | |||
neko259
|
r408 | ||
neko259
|
r410 | posts_per_days.append(float(self.filter( | |
pub_time__lte=day_time_end, | |||
pub_time__gte=day_time_start).count())) | |||
neko259
|
r408 | ||
neko259
|
r410 | ppd = (sum(posts_per_day for posts_per_day in posts_per_days) / | |
len(posts_per_days)) | |||
neko259
|
r569 | cache.set(CACHE_KEY_PPD + str(today), ppd) | |
neko259
|
r410 | return ppd | |
neko259
|
r407 | ||
neko259
|
r384 | ||
neko259
|
r692 | class Post(models.Model, Viewable): | |
neko259
|
r384 | """A post is a message.""" | |
objects = PostManager() | |||
class Meta: | |||
neko259
|
r410 | app_label = APP_LABEL_BOARDS | |
neko259
|
r649 | ordering = ('id',) | |
neko259
|
r384 | ||
title = models.CharField(max_length=TITLE_MAX_LENGTH) | |||
pub_time = models.DateTimeField() | |||
text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE, | |||
escape_html=False) | |||
neko259
|
r693 | images = models.ManyToManyField(PostImage, null=True, blank=True, | |
related_name='ip+', db_index=True) | |||
neko259
|
r384 | ||
poster_ip = models.GenericIPAddressField() | |||
poster_user_agent = models.TextField() | |||
neko259
|
r661 | thread_new = models.ForeignKey('Thread', null=True, default=None, | |
neko259
|
r715 | db_index=True) | |
neko259
|
r384 | last_edit_time = models.DateTimeField() | |
referenced_posts = models.ManyToManyField('Post', symmetrical=False, | |||
null=True, | |||
neko259
|
r661 | blank=True, related_name='rfp+', | |
db_index=True) | |||
neko259
|
r674 | refmap = models.TextField(null=True, blank=True) | |
neko259
|
r384 | ||
def __unicode__(self): | |||
return '#' + str(self.id) + ' ' + self.title + ' (' + \ | |||
self.text.raw[:50] + ')' | |||
def get_title(self): | |||
neko259
|
r622 | """ | |
Gets original post title or part of its text. | |||
""" | |||
neko259
|
r384 | title = self.title | |
neko259
|
r618 | if not title: | |
neko259
|
r521 | title = self.text.rendered | |
neko259
|
r384 | ||
return title | |||
neko259
|
r674 | def build_refmap(self): | |
map_string = '' | |||
first = True | |||
for refpost in self.referenced_posts.all(): | |||
if not first: | |||
map_string += ', ' | |||
map_string += '<a href="%s">>>%s</a>' % (refpost.get_url(), refpost.id) | |||
first = False | |||
self.refmap = map_string | |||
neko259
|
r398 | def get_sorted_referenced_posts(self): | |
neko259
|
r674 | return self.refmap | |
neko259
|
r398 | ||
def is_referenced(self): | |||
neko259
|
r674 | return len(self.refmap) > 0 | |
neko259
|
r398 | ||
neko259
|
r400 | def is_opening(self): | |
neko259
|
r622 | """ | |
Checks if this is an opening post or just a reply. | |||
""" | |||
neko259
|
r620 | return self.get_thread().get_opening_post_id() == self.id | |
neko259
|
r400 | ||
neko259
|
r566 | @transaction.atomic | |
def add_tag(self, tag): | |||
edit_time = timezone.now() | |||
neko259
|
r619 | thread = self.get_thread() | |
neko259
|
r566 | thread.add_tag(tag) | |
self.last_edit_time = edit_time | |||
self.save() | |||
thread.last_edit_time = edit_time | |||
thread.save() | |||
@transaction.atomic | |||
def remove_tag(self, tag): | |||
edit_time = timezone.now() | |||
neko259
|
r619 | thread = self.get_thread() | |
neko259
|
r576 | thread.remove_tag(tag) | |
neko259
|
r566 | self.last_edit_time = edit_time | |
self.save() | |||
thread.last_edit_time = edit_time | |||
thread.save() | |||
neko259
|
r625 | def get_url(self, thread=None): | |
neko259
|
r589 | """ | |
neko259
|
r622 | Gets full url to the post. | |
neko259
|
r589 | """ | |
cache_key = CACHE_KEY_POST_URL + str(self.id) | |||
link = cache.get(cache_key) | |||
if not link: | |||
neko259
|
r625 | if not thread: | |
thread = self.get_thread() | |||
opening_id = thread.get_opening_post_id() | |||
neko259
|
r614 | ||
if self.id != opening_id: | |||
neko259
|
r608 | link = reverse('thread', kwargs={ | |
neko259
|
r614 | 'post_id': opening_id}) + '#' + str(self.id) | |
neko259
|
r589 | else: | |
link = reverse('thread', kwargs={'post_id': self.id}) | |||
cache.set(cache_key, link) | |||
return link | |||
neko259
|
r617 | def get_thread(self): | |
neko259
|
r622 | """ | |
Gets post's thread. | |||
""" | |||
neko259
|
r617 | return self.thread_new | |
neko259
|
r660 | def get_referenced_posts(self): | |
neko259
|
r692 | return self.referenced_posts.only('id', 'thread_new') | |
def get_text(self): | |||
return self.text | |||
def get_view(self, moderator=False, need_open_link=False, | |||
truncated=False, *args, **kwargs): | |||
if 'is_opening' in kwargs: | |||
is_opening = kwargs['is_opening'] | |||
else: | |||
is_opening = self.is_opening() | |||
if 'thread' in kwargs: | |||
thread = kwargs['thread'] | |||
else: | |||
thread = self.get_thread() | |||
if 'can_bump' in kwargs: | |||
can_bump = kwargs['can_bump'] | |||
else: | |||
can_bump = thread.can_bump() | |||
neko259
|
r724 | if is_opening: | |
opening_post_id = self.id | |||
else: | |||
opening_post_id = thread.get_opening_post_id() | |||
neko259
|
r692 | ||
return render_to_string('boards/post.html', { | |||
'post': self, | |||
'moderator': moderator, | |||
'is_opening': is_opening, | |||
'thread': thread, | |||
'bumpable': can_bump, | |||
'need_open_link': need_open_link, | |||
'truncated': truncated, | |||
'opening_post_id': opening_post_id, | |||
}) | |||
neko259
|
r693 | def get_first_image(self): | |
return self.images.earliest('id') | |||
neko259
|
r715 | def delete(self, using=None): | |
""" | |||
Delete all post images and the post itself. | |||
""" | |||
self.images.all().delete() | |||
super(Post, self).delete(using) |