##// END OF EJS Templates
Made post unicode string value shorter for the admin interface.
Pavel Ryapolov -
r105:86b80a17 default
parent child Browse files
Show More
@@ -1,289 +1,289 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import re
3 import re
4 import time
4 import time
5 import math
5 import math
6
6
7 from django.db import models
7 from django.db import models
8 from django.http import Http404
8 from django.http import Http404
9 from django.utils import timezone
9 from django.utils import timezone
10 from markupfield.fields import MarkupField
10 from markupfield.fields import MarkupField
11 from threading import Thread
11 from threading import Thread
12
12
13 from neboard import settings
13 from neboard import settings
14 import thumbs
14 import thumbs
15
15
16 IMAGE_THUMB_SIZE = (200, 150)
16 IMAGE_THUMB_SIZE = (200, 150)
17
17
18 TITLE_MAX_LENGTH = 50
18 TITLE_MAX_LENGTH = 50
19
19
20 DEFAULT_MARKUP_TYPE = 'markdown'
20 DEFAULT_MARKUP_TYPE = 'markdown'
21
21
22 NO_PARENT = -1
22 NO_PARENT = -1
23 NO_IP = '0.0.0.0'
23 NO_IP = '0.0.0.0'
24 UNKNOWN_UA = ''
24 UNKNOWN_UA = ''
25 ALL_PAGES = -1
25 ALL_PAGES = -1
26 OPENING_POST_POPULARITY_WEIGHT = 2
26 OPENING_POST_POPULARITY_WEIGHT = 2
27 IMAGES_DIRECTORY = 'images/'
27 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
28 FILE_EXTENSION_DELIMITER = '.'
29
29
30 REGEX_PRETTY = re.compile(r'^\d(0)+$')
30 REGEX_PRETTY = re.compile(r'^\d(0)+$')
31 REGEX_SAME = re.compile(r'^(.)\1+$')
31 REGEX_SAME = re.compile(r'^(.)\1+$')
32
32
33
33
34 class PostManager(models.Manager):
34 class PostManager(models.Manager):
35 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
35 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
36 ip=NO_IP, tags=None):
36 ip=NO_IP, tags=None):
37 post = self.create(title=title,
37 post = self.create(title=title,
38 text=text,
38 text=text,
39 pub_time=timezone.now(),
39 pub_time=timezone.now(),
40 parent=parent_id,
40 parent=parent_id,
41 image=image,
41 image=image,
42 poster_ip=ip,
42 poster_ip=ip,
43 poster_user_agent=UNKNOWN_UA,
43 poster_user_agent=UNKNOWN_UA,
44 last_edit_time=timezone.now())
44 last_edit_time=timezone.now())
45
45
46 if tags:
46 if tags:
47 map(post.tags.add, tags)
47 map(post.tags.add, tags)
48
48
49 if parent_id != NO_PARENT:
49 if parent_id != NO_PARENT:
50 self._bump_thread(parent_id)
50 self._bump_thread(parent_id)
51 else:
51 else:
52 self._delete_old_threads()
52 self._delete_old_threads()
53
53
54 return post
54 return post
55
55
56 def delete_post(self, post):
56 def delete_post(self, post):
57 children = self.filter(parent=post.id)
57 children = self.filter(parent=post.id)
58 for child in children:
58 for child in children:
59 self.delete_post(child)
59 self.delete_post(child)
60 post.delete()
60 post.delete()
61
61
62 def delete_posts_by_ip(self, ip):
62 def delete_posts_by_ip(self, ip):
63 posts = self.filter(poster_ip=ip)
63 posts = self.filter(poster_ip=ip)
64 for post in posts:
64 for post in posts:
65 self.delete_post(post)
65 self.delete_post(post)
66
66
67 def get_threads(self, tag=None, page=ALL_PAGES,
67 def get_threads(self, tag=None, page=ALL_PAGES,
68 order_by='-last_edit_time'):
68 order_by='-last_edit_time'):
69 if tag:
69 if tag:
70 threads = self.filter(parent=NO_PARENT, tags=tag)
70 threads = self.filter(parent=NO_PARENT, tags=tag)
71
71
72 # TODO Throw error 404 if no threads for tag found?
72 # TODO Throw error 404 if no threads for tag found?
73 else:
73 else:
74 threads = self.filter(parent=NO_PARENT)
74 threads = self.filter(parent=NO_PARENT)
75
75
76 threads = threads.order_by(order_by)
76 threads = threads.order_by(order_by)
77
77
78 if page != ALL_PAGES:
78 if page != ALL_PAGES:
79 thread_count = len(threads)
79 thread_count = len(threads)
80
80
81 if page < self.get_thread_page_count(tag=tag):
81 if page < self.get_thread_page_count(tag=tag):
82 start_thread = page * settings.THREADS_PER_PAGE
82 start_thread = page * settings.THREADS_PER_PAGE
83 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
83 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
84 thread_count)
84 thread_count)
85 threads = threads[start_thread:end_thread]
85 threads = threads[start_thread:end_thread]
86
86
87 return threads
87 return threads
88
88
89 def get_thread(self, opening_post_id):
89 def get_thread(self, opening_post_id):
90 try:
90 try:
91 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
91 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
92 except Post.DoesNotExist:
92 except Post.DoesNotExist:
93 raise Http404
93 raise Http404
94
94
95 if opening_post.parent == NO_PARENT:
95 if opening_post.parent == NO_PARENT:
96 replies = self.filter(parent=opening_post_id)
96 replies = self.filter(parent=opening_post_id)
97
97
98 thread = [opening_post]
98 thread = [opening_post]
99 thread.extend(replies)
99 thread.extend(replies)
100
100
101 return thread
101 return thread
102
102
103 def exists(self, post_id):
103 def exists(self, post_id):
104 posts = self.filter(id=post_id)
104 posts = self.filter(id=post_id)
105
105
106 return posts.count() > 0
106 return posts.count() > 0
107
107
108 def get_thread_page_count(self, tag=None):
108 def get_thread_page_count(self, tag=None):
109 if tag:
109 if tag:
110 threads = self.filter(parent=NO_PARENT, tags=tag)
110 threads = self.filter(parent=NO_PARENT, tags=tag)
111 else:
111 else:
112 threads = self.filter(parent=NO_PARENT)
112 threads = self.filter(parent=NO_PARENT)
113
113
114 return int(math.ceil(threads.count() / float(
114 return int(math.ceil(threads.count() / float(
115 settings.THREADS_PER_PAGE)))
115 settings.THREADS_PER_PAGE)))
116
116
117 def _delete_old_threads(self):
117 def _delete_old_threads(self):
118 """
118 """
119 Preserves maximum thread count. If there are too many threads,
119 Preserves maximum thread count. If there are too many threads,
120 delete the old ones.
120 delete the old ones.
121 """
121 """
122
122
123 # TODO Move old threads to the archive instead of deleting them.
123 # TODO Move old threads to the archive instead of deleting them.
124 # Maybe make some 'old' field in the model to indicate the thread
124 # Maybe make some 'old' field in the model to indicate the thread
125 # must not be shown and be able for replying.
125 # must not be shown and be able for replying.
126
126
127 threads = self.get_threads()
127 threads = self.get_threads()
128 thread_count = len(threads)
128 thread_count = len(threads)
129
129
130 if thread_count > settings.MAX_THREAD_COUNT:
130 if thread_count > settings.MAX_THREAD_COUNT:
131 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
131 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
132 old_threads = threads[thread_count - num_threads_to_delete:]
132 old_threads = threads[thread_count - num_threads_to_delete:]
133
133
134 for thread in old_threads:
134 for thread in old_threads:
135 self.delete_post(thread)
135 self.delete_post(thread)
136
136
137 def _bump_thread(self, thread_id):
137 def _bump_thread(self, thread_id):
138 thread = self.get(id=thread_id)
138 thread = self.get(id=thread_id)
139
139
140 if thread.can_bump():
140 if thread.can_bump():
141 thread.last_edit_time = timezone.now()
141 thread.last_edit_time = timezone.now()
142 thread.save()
142 thread.save()
143
143
144
144
145 class TagManager(models.Manager):
145 class TagManager(models.Manager):
146 def get_not_empty_tags(self):
146 def get_not_empty_tags(self):
147 all_tags = self.all().order_by('name')
147 all_tags = self.all().order_by('name')
148 tags = []
148 tags = []
149 for tag in all_tags:
149 for tag in all_tags:
150 if not tag.is_empty():
150 if not tag.is_empty():
151 tags.append(tag)
151 tags.append(tag)
152
152
153 return tags
153 return tags
154
154
155 def get_popular_tags(self):
155 def get_popular_tags(self):
156 all_tags = self.get_not_empty_tags()
156 all_tags = self.get_not_empty_tags()
157
157
158 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
158 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
159 reverse=True)
159 reverse=True)
160
160
161 return sorted_tags[:settings.POPULAR_TAGS]
161 return sorted_tags[:settings.POPULAR_TAGS]
162
162
163
163
164 class Tag(models.Model):
164 class Tag(models.Model):
165 """
165 """
166 A tag is a text node assigned to the post. The tag serves as a board
166 A tag is a text node assigned to the post. The tag serves as a board
167 section. There can be multiple tags for each message
167 section. There can be multiple tags for each message
168 """
168 """
169
169
170 objects = TagManager()
170 objects = TagManager()
171
171
172 name = models.CharField(max_length=100)
172 name = models.CharField(max_length=100)
173 # TODO Connect the tag to its posts to check the number of threads for
173 # TODO Connect the tag to its posts to check the number of threads for
174 # the tag.
174 # the tag.
175
175
176 def __unicode__(self):
176 def __unicode__(self):
177 return self.name
177 return self.name
178
178
179 def is_empty(self):
179 def is_empty(self):
180 return self.get_post_count() == 0
180 return self.get_post_count() == 0
181
181
182 def get_post_count(self):
182 def get_post_count(self):
183 posts_with_tag = Post.objects.get_threads(tag=self)
183 posts_with_tag = Post.objects.get_threads(tag=self)
184 return posts_with_tag.count()
184 return posts_with_tag.count()
185
185
186 def get_popularity(self):
186 def get_popularity(self):
187 posts_with_tag = Post.objects.get_threads(tag=self)
187 posts_with_tag = Post.objects.get_threads(tag=self)
188 reply_count = 0
188 reply_count = 0
189 for post in posts_with_tag:
189 for post in posts_with_tag:
190 reply_count += post.get_reply_count()
190 reply_count += post.get_reply_count()
191 reply_count += OPENING_POST_POPULARITY_WEIGHT
191 reply_count += OPENING_POST_POPULARITY_WEIGHT
192
192
193 return reply_count
193 return reply_count
194
194
195
195
196 class Post(models.Model):
196 class Post(models.Model):
197 """A post is a message."""
197 """A post is a message."""
198
198
199 objects = PostManager()
199 objects = PostManager()
200
200
201 def _update_image_filename(self, filename):
201 def _update_image_filename(self, filename):
202 """Get unique image filename"""
202 """Get unique image filename"""
203
203
204 path = IMAGES_DIRECTORY
204 path = IMAGES_DIRECTORY
205 new_name = str(int(time.mktime(time.gmtime())))
205 new_name = str(int(time.mktime(time.gmtime())))
206 new_name += str(int(random() * 1000))
206 new_name += str(int(random() * 1000))
207 new_name += FILE_EXTENSION_DELIMITER
207 new_name += FILE_EXTENSION_DELIMITER
208 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
208 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
209
209
210 return os.path.join(path, new_name)
210 return os.path.join(path, new_name)
211
211
212 title = models.CharField(max_length=TITLE_MAX_LENGTH)
212 title = models.CharField(max_length=TITLE_MAX_LENGTH)
213 pub_time = models.DateTimeField()
213 pub_time = models.DateTimeField()
214 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
214 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
215 escape_html=False)
215 escape_html=False)
216 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
216 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
217 blank=True, sizes=(IMAGE_THUMB_SIZE,))
217 blank=True, sizes=(IMAGE_THUMB_SIZE,))
218 poster_ip = models.IPAddressField()
218 poster_ip = models.IPAddressField()
219 poster_user_agent = models.TextField()
219 poster_user_agent = models.TextField()
220 parent = models.BigIntegerField()
220 parent = models.BigIntegerField()
221 tags = models.ManyToManyField(Tag)
221 tags = models.ManyToManyField(Tag)
222 last_edit_time = models.DateTimeField()
222 last_edit_time = models.DateTimeField()
223
223
224 def __unicode__(self):
224 def __unicode__(self):
225 return '#' + str(self.id) + ' ' + self.title + ' (' + self.text.raw + \
225 return '#' + str(self.id) + ' ' + self.title + ' (' + \
226 ')'
226 self.text.raw[:50] + ')'
227
227
228 def _get_replies(self):
228 def _get_replies(self):
229 return Post.objects.filter(parent=self.id)
229 return Post.objects.filter(parent=self.id)
230
230
231 def get_reply_count(self):
231 def get_reply_count(self):
232 return self._get_replies().count()
232 return self._get_replies().count()
233
233
234 def get_images_count(self):
234 def get_images_count(self):
235 images_count = 1 if self.image else 0
235 images_count = 1 if self.image else 0
236 for reply in self._get_replies():
236 for reply in self._get_replies():
237 if reply.image:
237 if reply.image:
238 images_count += 1
238 images_count += 1
239
239
240 return images_count
240 return images_count
241
241
242 def get_gets_count(self):
242 def get_gets_count(self):
243 gets_count = 1 if self.is_get() else 0
243 gets_count = 1 if self.is_get() else 0
244 for reply in self._get_replies():
244 for reply in self._get_replies():
245 if reply.is_get():
245 if reply.is_get():
246 gets_count += 1
246 gets_count += 1
247
247
248 return gets_count
248 return gets_count
249
249
250 def is_get(self):
250 def is_get(self):
251 """If the post has pretty id (1, 1000, 77777), than it is called GET"""
251 """If the post has pretty id (1, 1000, 77777), than it is called GET"""
252
252
253 first = self.id == 1
253 first = self.id == 1
254
254
255 id_str = str(self.id)
255 id_str = str(self.id)
256 pretty = REGEX_PRETTY.match(id_str)
256 pretty = REGEX_PRETTY.match(id_str)
257 same_digits = REGEX_SAME.match(id_str)
257 same_digits = REGEX_SAME.match(id_str)
258
258
259 return first or pretty or same_digits
259 return first or pretty or same_digits
260
260
261 def can_bump(self):
261 def can_bump(self):
262 """Check if the thread can be bumped by replying"""
262 """Check if the thread can be bumped by replying"""
263
263
264 replies_count = len(Post.objects.get_thread(self.id))
264 replies_count = len(Post.objects.get_thread(self.id))
265
265
266 return replies_count <= settings.MAX_POSTS_PER_THREAD
266 return replies_count <= settings.MAX_POSTS_PER_THREAD
267
267
268 def get_last_replies(self):
268 def get_last_replies(self):
269 if settings.LAST_REPLIES_COUNT > 0:
269 if settings.LAST_REPLIES_COUNT > 0:
270 reply_count = self.get_reply_count()
270 reply_count = self.get_reply_count()
271
271
272 if reply_count > 0:
272 if reply_count > 0:
273 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
273 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
274 reply_count)
274 reply_count)
275 last_replies = self._get_replies()[reply_count
275 last_replies = self._get_replies()[reply_count
276 - reply_count_to_show:]
276 - reply_count_to_show:]
277
277
278 return last_replies
278 return last_replies
279
279
280
280
281 class Admin(models.Model):
281 class Admin(models.Model):
282 """
282 """
283 Model for admin users
283 Model for admin users
284 """
284 """
285 name = models.CharField(max_length=100)
285 name = models.CharField(max_length=100)
286 password = models.CharField(max_length=100)
286 password = models.CharField(max_length=100)
287
287
288 def __unicode__(self):
288 def __unicode__(self):
289 return self.name + '/' + '*' * len(self.password)
289 return self.name + '/' + '*' * len(self.password)
General Comments 0
You need to be logged in to leave comments. Login now