models.py
278 lines
| 8.0 KiB
| text/x-python
|
PythonLexer
/ boards / models.py
neko259
|
r29 | import os | |
neko259
|
r50 | from random import random | |
neko259
|
r31 | import re | |
neko259
|
r0 | from django.db import models | |
from django.utils import timezone | |||
neko259
|
r29 | import time | |
neko259
|
r46 | import math | |
neko259
|
r52 | import markdown | |
from markdown.inlinepatterns import Pattern | |||
from markdown.util import etree | |||
neko259
|
r4 | ||
neko259
|
r22 | from neboard import settings | |
neko259
|
r39 | from markupfield.fields import MarkupField | |
neko259
|
r22 | ||
import thumbs | |||
neko259
|
r4 | NO_PARENT = -1 | |
NO_IP = '0.0.0.0' | |||
UNKNOWN_UA = '' | |||
neko259
|
r22 | ||
neko259
|
r0 | ||
neko259
|
r2 | class PostManager(models.Manager): | |
neko259
|
r46 | ALL_PAGES = -1 | |
neko259
|
r22 | def create_post(self, title, text, image=None, parent_id=NO_PARENT, | |
neko259
|
r24 | ip=NO_IP, tags=None): | |
neko259
|
r22 | post = self.create(title=title, | |
text=text, | |||
pub_time=timezone.now(), | |||
parent=parent_id, | |||
image=image, | |||
poster_ip=ip, | |||
neko259
|
r25 | poster_user_agent=UNKNOWN_UA, | |
last_edit_time=timezone.now()) | |||
neko259
|
r0 | ||
neko259
|
r24 | if tags: | |
for tag in tags: | |||
post.tags.add(tag) | |||
neko259
|
r25 | if parent_id != NO_PARENT: | |
neko259
|
r38 | self._bump_thread(parent_id) | |
neko259
|
r28 | else: | |
self._delete_old_threads() | |||
neko259
|
r25 | ||
neko259
|
r1 | return post | |
neko259
|
r0 | ||
def delete_post(self, post): | |||
neko259
|
r22 | children = self.filter(parent=post.id) | |
neko259
|
r3 | for child in children: | |
self.delete_post(child) | |||
neko259
|
r2 | post.delete() | |
neko259
|
r0 | def delete_posts_by_ip(self, ip): | |
neko259
|
r22 | posts = self.filter(poster_ip=ip) | |
neko259
|
r1 | for post in posts: | |
self.delete_post(post) | |||
neko259
|
r0 | ||
neko259
|
r46 | def get_threads(self, tag=None, page=ALL_PAGES): | |
neko259
|
r27 | if tag: | |
threads = self.filter(parent=NO_PARENT, tags=tag) | |||
else: | |||
neko259
|
r22 | threads = self.filter(parent=NO_PARENT) | |
neko259
|
r58 | threads = threads.order_by('-last_edit_time') | |
neko259
|
r5 | ||
neko259
|
r46 | if page != self.ALL_PAGES: | |
thread_count = len(threads) | |||
if page < self.get_thread_page_count(tag=tag): | |||
start_thread = page * settings.THREADS_PER_PAGE | |||
end_thread = min(start_thread + settings.THREADS_PER_PAGE, | |||
thread_count) | |||
threads = threads[start_thread:end_thread] | |||
neko259
|
r5 | return threads | |
def get_thread(self, opening_post_id): | |||
neko259
|
r22 | opening_post = self.get(id=opening_post_id) | |
neko259
|
r33 | ||
if opening_post.parent == NO_PARENT: | |||
replies = self.filter(parent=opening_post_id) | |||
neko259
|
r5 | ||
neko259
|
r33 | thread = [opening_post] | |
thread.extend(replies) | |||
neko259
|
r17 | ||
neko259
|
r33 | return thread | |
neko259
|
r5 | ||
neko259
|
r8 | def exists(self, post_id): | |
neko259
|
r22 | posts = self.filter(id=post_id) | |
neko259
|
r8 | ||
neko259
|
r58 | return posts.count() > 0 | |
neko259
|
r8 | ||
neko259
|
r46 | def get_thread_page_count(self, tag=None): | |
if tag: | |||
threads = self.filter(parent=NO_PARENT, tags=tag) | |||
else: | |||
threads = self.filter(parent=NO_PARENT) | |||
neko259
|
r58 | return int(math.ceil(threads.count() / float( | |
settings.THREADS_PER_PAGE))) | |||
neko259
|
r46 | ||
neko259
|
r28 | def _delete_old_threads(self): | |
""" | |||
Preserves maximum thread count. If there are too many threads, | |||
delete the old ones. | |||
""" | |||
# TODO Move old threads to the archive instead of deleting them. | |||
# Maybe make some 'old' field in the model to indicate the thread | |||
# must not be shown and be able for replying. | |||
threads = self.get_threads() | |||
thread_count = len(threads) | |||
if thread_count > settings.MAX_THREAD_COUNT: | |||
num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT | |||
neko259
|
r58 | old_threads = threads[thread_count - num_threads_to_delete:] | |
neko259
|
r28 | ||
for thread in old_threads: | |||
self.delete_post(thread) | |||
neko259
|
r38 | def _bump_thread(self, thread_id): | |
thread = self.get(id=thread_id) | |||
neko259
|
r46 | if thread.can_bump(): | |
neko259
|
r38 | thread.last_edit_time = timezone.now() | |
thread.save() | |||
neko259
|
r5 | ||
neko259
|
r33 | class TagManager(models.Manager): | |
def get_not_empty_tags(self): | |||
neko259
|
r34 | all_tags = self.all().order_by('name') | |
neko259
|
r33 | tags = [] | |
for tag in all_tags: | |||
if not tag.is_empty(): | |||
tags.append(tag) | |||
return tags | |||
neko259
|
r57 | def get_popular_tags(self): | |
all_tags = self.get_not_empty_tags() | |||
sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(), | |||
reverse=True) | |||
return sorted_tags[:settings.POPULAR_TAGS] | |||
neko259
|
r33 | ||
neko259
|
r4 | class Tag(models.Model): | |
""" | |||
A tag is a text node assigned to the post. The tag serves as a board | |||
section. There can be multiple tags for each message | |||
""" | |||
neko259
|
r58 | OPENING_POST_WEIGHT = 5 | |
neko259
|
r33 | objects = TagManager() | |
neko259
|
r22 | name = models.CharField(max_length=100) | |
neko259
|
r28 | # TODO Connect the tag to its posts to check the number of threads for | |
# the tag. | |||
neko259
|
r4 | ||
neko259
|
r31 | def __unicode__(self): | |
return self.name | |||
neko259
|
r32 | def is_empty(self): | |
neko259
|
r33 | return self.get_post_count() == 0 | |
def get_post_count(self): | |||
neko259
|
r32 | posts_with_tag = Post.objects.get_threads(tag=self) | |
neko259
|
r58 | return posts_with_tag.count() | |
neko259
|
r32 | ||
neko259
|
r57 | def get_popularity(self): | |
posts_with_tag = Post.objects.get_threads(tag=self) | |||
reply_count = 0 | |||
for post in posts_with_tag: | |||
reply_count += post.get_reply_count() | |||
neko259
|
r58 | reply_count += self.OPENING_POST_WEIGHT | |
neko259
|
r57 | ||
return reply_count | |||
neko259
|
r8 | ||
neko259
|
r2 | class Post(models.Model): | |
neko259
|
r4 | """A post is a message.""" | |
neko259
|
r0 | ||
neko259
|
r50 | IMAGES_DIRECTORY = 'images/' | |
FILE_EXTENSION_DELIMITER = '.' | |||
neko259
|
r2 | objects = PostManager() | |
neko259
|
r0 | ||
neko259
|
r50 | def _update_image_filename(self, filename): | |
"""Get unique image filename""" | |||
path = self.IMAGES_DIRECTORY | |||
new_name = str(int(time.mktime(time.gmtime()))) | |||
new_name += str(int(random() * 1000)) | |||
new_name += self.FILE_EXTENSION_DELIMITER | |||
new_name += filename.split(self.FILE_EXTENSION_DELIMITER)[-1:][0] | |||
return os.path.join(path, new_name) | |||
neko259
|
r29 | title = models.CharField(max_length=50) | |
neko259
|
r2 | pub_time = models.DateTimeField() | |
neko259
|
r39 | text = MarkupField(default_markup_type='markdown', escape_html=True) | |
neko259
|
r50 | image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename, | |
neko259
|
r22 | blank=True, sizes=((200, 150),)) | |
neko259
|
r2 | poster_ip = models.IPAddressField() | |
poster_user_agent = models.TextField() | |||
parent = models.BigIntegerField() | |||
neko259
|
r7 | tags = models.ManyToManyField(Tag) | |
neko259
|
r25 | last_edit_time = models.DateTimeField() | |
neko259
|
r0 | ||
neko259
|
r46 | regex_pretty = re.compile(r'^\d(0)+$') | |
regex_same = re.compile(r'^(.)\1+$') | |||
neko259
|
r0 | def __unicode__(self): | |
neko259
|
r40 | return self.title + ' (' + self.text.raw + ')' | |
Ilyas
|
r9 | ||
neko259
|
r30 | def _get_replies(self): | |
return Post.objects.filter(parent=self.id) | |||
def get_reply_count(self): | |||
neko259
|
r58 | return self._get_replies().count() | |
neko259
|
r30 | ||
def get_images_count(self): | |||
images_count = 1 if self.image else 0 | |||
for reply in self._get_replies(): | |||
if reply.image: | |||
images_count += 1 | |||
return images_count | |||
def get_gets_count(self): | |||
gets_count = 1 if self.is_get() else 0 | |||
for reply in self._get_replies(): | |||
if reply.is_get(): | |||
gets_count += 1 | |||
return gets_count | |||
def is_get(self): | |||
"""If the post has pretty id (1, 1000, 77777), than it is called GET""" | |||
neko259
|
r39 | first = self.id == 1 | |
neko259
|
r30 | ||
neko259
|
r46 | id_str = str(self.id) | |
pretty = self.regex_pretty.match(id_str) | |||
same_digits = self.regex_same.match(id_str) | |||
return first or pretty or same_digits | |||
neko259
|
r31 | ||
neko259
|
r46 | def can_bump(self): | |
"""Check if the thread can be bumped by replying""" | |||
replies_count = len(Post.objects.get_thread(self.id)) | |||
return replies_count <= settings.MAX_POSTS_PER_THREAD | |||
neko259
|
r31 | ||
neko259
|
r59 | def get_last_replies(self): | |
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) | |||
last_replies = self._get_replies()[reply_count | |||
- reply_count_to_show:] | |||
return last_replies | |||
Ilyas
|
r9 | ||
neko259
|
r11 | class Admin(models.Model): | |
Ilyas
|
r9 | """ | |
Model for admin users | |||
""" | |||
neko259
|
r22 | name = models.CharField(max_length=100) | |
password = models.CharField(max_length=100) | |||
Ilyas
|
r9 | ||
def __unicode__(self): | |||
neko259
|
r52 | return self.name + '/' + '*' * len(self.password) |