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