import os import re from django.db import models from django.utils import timezone import time from neboard import settings from markupfield.fields import MarkupField import thumbs NO_PARENT = -1 NO_IP = '0.0.0.0' UNKNOWN_UA = '' def update_image_filename(instance, filename): """Get unique image filename""" path = 'images/' new_name = str(int(time.mktime(time.gmtime()))) + '_' + filename return os.path.join(path, new_name) class PostManager(models.Manager): def create_post(self, title, text, image=None, parent_id=NO_PARENT, ip=NO_IP, tags=None): post = self.create(title=title, text=text, pub_time=timezone.now(), parent=parent_id, image=image, poster_ip=ip, poster_user_agent=UNKNOWN_UA, last_edit_time=timezone.now()) if tags: for tag in tags: post.tags.add(tag) if parent_id != NO_PARENT: self._bump_thread(parent_id) else: self._delete_old_threads() return post def delete_post(self, post): children = self.filter(parent=post.id) for child in children: self.delete_post(child) post.delete() def delete_posts_by_ip(self, ip): posts = self.filter(poster_ip=ip) for post in posts: self.delete_post(post) def get_threads(self, tag=None): if tag: threads = self.filter(parent=NO_PARENT, tags=tag) else: threads = self.filter(parent=NO_PARENT) threads = list(threads.order_by('-last_edit_time')) return threads def get_thread(self, opening_post_id): opening_post = self.get(id=opening_post_id) if opening_post.parent == NO_PARENT: replies = self.filter(parent=opening_post_id) thread = [opening_post] thread.extend(replies) return thread def exists(self, post_id): posts = self.filter(id=post_id) return len(posts) > 0 def _delete_old_threads(self): """ Preserves maximum thread count. If there are too many threads, delete the old ones. """ # TODO Try to find a better way to get the active thread count. # 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 old_threads = threads[-num_threads_to_delete:] for thread in old_threads: self.delete_post(thread) def _bump_thread(self, thread_id): thread = self.get(id=thread_id) replies_count = len(self.get_thread(thread_id)) if replies_count <= settings.MAX_POSTS_PER_THREAD: thread.last_edit_time = timezone.now() thread.save() class TagManager(models.Manager): def get_not_empty_tags(self): all_tags = self.all().order_by('name') tags = [] for tag in all_tags: if not tag.is_empty(): tags.append(tag) return tags 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 """ objects = TagManager() name = models.CharField(max_length=100) # TODO Connect the tag to its posts to check the number of threads for # the tag. def __unicode__(self): return self.name def is_empty(self): return self.get_post_count() == 0 def get_post_count(self): posts_with_tag = Post.objects.get_threads(tag=self) return len(posts_with_tag) class Post(models.Model): """A post is a message.""" objects = PostManager() title = models.CharField(max_length=50) pub_time = models.DateTimeField() text = MarkupField(default_markup_type='markdown', escape_html=True) image = thumbs.ImageWithThumbsField(upload_to=update_image_filename, blank=True, sizes=((200, 150),)) poster_ip = models.IPAddressField() poster_user_agent = models.TextField() parent = models.BigIntegerField() tags = models.ManyToManyField(Tag) last_edit_time = models.DateTimeField() def __unicode__(self): return self.title + ' (' + self.text.raw + ')' def _get_replies(self): return Post.objects.filter(parent=self.id) def get_reply_count(self): return len(self._get_replies()) 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""" first = self.id == 1 # TODO Compile regexes id_str = str(self.id) pretty = re.match(r'^\d(0)+$', id_str) same_digits = re.match(r'^(.)\1+$', id_str) return first or pretty or same_digits class Admin(models.Model): """ Model for admin users """ name = models.CharField(max_length=100) password = models.CharField(max_length=100) def __unicode__(self): return self.name + '/' + '*' * len(self.password)