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