##// END OF EJS Templates
Fixed threads with tags creating.
neko259 -
r184:efe23e07 default
parent child Browse files
Show More
@@ -1,323 +1,325 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.db.models import Count
8 from django.db.models import Count
9 from django.http import Http404
9 from django.http import Http404
10 from django.utils import timezone
10 from django.utils import timezone
11 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
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 RANK_ADMIN = 0
30 RANK_ADMIN = 0
31 RANK_MODERATOR = 10
31 RANK_MODERATOR = 10
32 RANK_USER = 100
32 RANK_USER = 100
33
33
34
34
35 class PostManager(models.Manager):
35 class PostManager(models.Manager):
36 def create_post(self, title, text, image=None, thread=None,
36 def create_post(self, title, text, image=None, thread=None,
37 ip=NO_IP, tags=None, user=None):
37 ip=NO_IP, tags=None, user=None):
38 post = self.create(title=title,
38 post = self.create(title=title,
39 text=text,
39 text=text,
40 pub_time=timezone.now(),
40 pub_time=timezone.now(),
41 thread=thread,
41 thread=thread,
42 image=image,
42 image=image,
43 poster_ip=ip,
43 poster_ip=ip,
44 poster_user_agent=UNKNOWN_UA,
44 poster_user_agent=UNKNOWN_UA,
45 last_edit_time=timezone.now(),
45 last_edit_time=timezone.now(),
46 user=user)
46 user=user)
47
47
48 if tags:
48 if tags:
49 map(post.tags.add, tags)
49 map(post.tags.add, tags)
50 for tag in tags:
51 tag.threads.add(post)
50
52
51 if thread:
53 if thread:
52 thread.replies.add(post)
54 thread.replies.add(post)
53 thread.bump()
55 thread.bump()
54 else:
56 else:
55 self._delete_old_threads()
57 self._delete_old_threads()
56
58
57 return post
59 return post
58
60
59 def delete_post(self, post):
61 def delete_post(self, post):
60 if post.replies.count() > 0:
62 if post.replies.count() > 0:
61 map(self.delete_post, post.replies.all())
63 map(self.delete_post, post.replies.all())
62 post.delete()
64 post.delete()
63
65
64 def delete_posts_by_ip(self, ip):
66 def delete_posts_by_ip(self, ip):
65 posts = self.filter(poster_ip=ip)
67 posts = self.filter(poster_ip=ip)
66 map(self.delete_post, posts)
68 map(self.delete_post, posts)
67
69
68 def get_threads(self, tag=None, page=ALL_PAGES,
70 def get_threads(self, tag=None, page=ALL_PAGES,
69 order_by='-last_edit_time'):
71 order_by='-last_edit_time'):
70 if tag:
72 if tag:
71 threads = tag.threads
73 threads = tag.threads
72
74
73 # TODO This needs to be uncommented when 'all tags' view won't
75 # TODO This needs to be uncommented when 'all tags' view won't
74 # use this method to get threads for tag
76 # use this method to get threads for tag
75
77
76 # if threads.count() == 0:
78 # if threads.count() == 0:
77 # raise Http404
79 # raise Http404
78 else:
80 else:
79 threads = self.filter(thread=None)
81 threads = self.filter(thread=None)
80
82
81 threads = threads.order_by(order_by)
83 threads = threads.order_by(order_by)
82
84
83 if page != ALL_PAGES:
85 if page != ALL_PAGES:
84 thread_count = threads.count()
86 thread_count = threads.count()
85
87
86 if page < self.get_thread_page_count(tag=tag):
88 if page < self.get_thread_page_count(tag=tag):
87 start_thread = page * settings.THREADS_PER_PAGE
89 start_thread = page * settings.THREADS_PER_PAGE
88 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
90 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
89 thread_count)
91 thread_count)
90 threads = threads[start_thread:end_thread]
92 threads = threads[start_thread:end_thread]
91
93
92 return threads
94 return threads
93
95
94 def get_thread(self, opening_post_id):
96 def get_thread(self, opening_post_id):
95 try:
97 try:
96 opening_post = self.get(id=opening_post_id, thread=None)
98 opening_post = self.get(id=opening_post_id, thread=None)
97 except Post.DoesNotExist:
99 except Post.DoesNotExist:
98 raise Http404
100 raise Http404
99
101
100 if opening_post.replies:
102 if opening_post.replies:
101 thread = [opening_post]
103 thread = [opening_post]
102 thread.extend(opening_post.replies.all())
104 thread.extend(opening_post.replies.all())
103
105
104 return thread
106 return thread
105
107
106 def exists(self, post_id):
108 def exists(self, post_id):
107 posts = self.filter(id=post_id)
109 posts = self.filter(id=post_id)
108
110
109 return posts.count() > 0
111 return posts.count() > 0
110
112
111 def get_thread_page_count(self, tag=None):
113 def get_thread_page_count(self, tag=None):
112 if tag:
114 if tag:
113 threads = self.filter(thread=None, tags=tag)
115 threads = self.filter(thread=None, tags=tag)
114 else:
116 else:
115 threads = self.filter(thread=None)
117 threads = self.filter(thread=None)
116
118
117 return int(math.ceil(threads.count() / float(
119 return int(math.ceil(threads.count() / float(
118 settings.THREADS_PER_PAGE)))
120 settings.THREADS_PER_PAGE)))
119
121
120 def _delete_old_threads(self):
122 def _delete_old_threads(self):
121 """
123 """
122 Preserves maximum thread count. If there are too many threads,
124 Preserves maximum thread count. If there are too many threads,
123 delete the old ones.
125 delete the old ones.
124 """
126 """
125
127
126 # TODO Move old threads to the archive instead of deleting them.
128 # TODO Move old threads to the archive instead of deleting them.
127 # Maybe make some 'old' field in the model to indicate the thread
129 # Maybe make some 'old' field in the model to indicate the thread
128 # must not be shown and be able for replying.
130 # must not be shown and be able for replying.
129
131
130 threads = self.get_threads()
132 threads = self.get_threads()
131 thread_count = len(threads)
133 thread_count = len(threads)
132
134
133 if thread_count > settings.MAX_THREAD_COUNT:
135 if thread_count > settings.MAX_THREAD_COUNT:
134 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
136 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
135 old_threads = threads[thread_count - num_threads_to_delete:]
137 old_threads = threads[thread_count - num_threads_to_delete:]
136
138
137 map(self.delete_post, old_threads)
139 map(self.delete_post, old_threads)
138
140
139
141
140 class TagManager(models.Manager):
142 class TagManager(models.Manager):
141
143
142 def get_not_empty_tags(self):
144 def get_not_empty_tags(self):
143 tags = self.annotate(Count('threads')) \
145 tags = self.annotate(Count('threads')) \
144 .filter(threads__count__gt=0).order_by('name')
146 .filter(threads__count__gt=0).order_by('name')
145
147
146 return tags
148 return tags
147
149
148
150
149 class Tag(models.Model):
151 class Tag(models.Model):
150 """
152 """
151 A tag is a text node assigned to the post. The tag serves as a board
153 A tag is a text node assigned to the post. The tag serves as a board
152 section. There can be multiple tags for each message
154 section. There can be multiple tags for each message
153 """
155 """
154
156
155 objects = TagManager()
157 objects = TagManager()
156
158
157 name = models.CharField(max_length=100)
159 name = models.CharField(max_length=100)
158 threads = models.ManyToManyField('Post', null=True,
160 threads = models.ManyToManyField('Post', null=True,
159 blank=True, related_name='tag+')
161 blank=True, related_name='tag+')
160
162
161 def __unicode__(self):
163 def __unicode__(self):
162 return self.name
164 return self.name
163
165
164 def is_empty(self):
166 def is_empty(self):
165 return self.get_post_count() == 0
167 return self.get_post_count() == 0
166
168
167 def get_post_count(self):
169 def get_post_count(self):
168 return self.threads.count()
170 return self.threads.count()
169
171
170 def get_popularity(self):
172 def get_popularity(self):
171 posts_with_tag = Post.objects.get_threads(tag=self)
173 posts_with_tag = Post.objects.get_threads(tag=self)
172 reply_count = 0
174 reply_count = 0
173 for post in posts_with_tag:
175 for post in posts_with_tag:
174 reply_count += post.get_reply_count()
176 reply_count += post.get_reply_count()
175 reply_count += OPENING_POST_POPULARITY_WEIGHT
177 reply_count += OPENING_POST_POPULARITY_WEIGHT
176
178
177 return reply_count
179 return reply_count
178
180
179
181
180 class Post(models.Model):
182 class Post(models.Model):
181 """A post is a message."""
183 """A post is a message."""
182
184
183 objects = PostManager()
185 objects = PostManager()
184
186
185 def _update_image_filename(self, filename):
187 def _update_image_filename(self, filename):
186 """Get unique image filename"""
188 """Get unique image filename"""
187
189
188 path = IMAGES_DIRECTORY
190 path = IMAGES_DIRECTORY
189 new_name = str(int(time.mktime(time.gmtime())))
191 new_name = str(int(time.mktime(time.gmtime())))
190 new_name += str(int(random() * 1000))
192 new_name += str(int(random() * 1000))
191 new_name += FILE_EXTENSION_DELIMITER
193 new_name += FILE_EXTENSION_DELIMITER
192 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
194 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
193
195
194 return os.path.join(path, new_name)
196 return os.path.join(path, new_name)
195
197
196 title = models.CharField(max_length=TITLE_MAX_LENGTH)
198 title = models.CharField(max_length=TITLE_MAX_LENGTH)
197 pub_time = models.DateTimeField()
199 pub_time = models.DateTimeField()
198 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
200 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
199 escape_html=False)
201 escape_html=False)
200
202
201 image_width = models.IntegerField(default=0)
203 image_width = models.IntegerField(default=0)
202 image_height = models.IntegerField(default=0)
204 image_height = models.IntegerField(default=0)
203
205
204 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
206 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
205 blank=True, sizes=(IMAGE_THUMB_SIZE,),
207 blank=True, sizes=(IMAGE_THUMB_SIZE,),
206 width_field='image_width',
208 width_field='image_width',
207 height_field='image_height')
209 height_field='image_height')
208
210
209 poster_ip = models.GenericIPAddressField()
211 poster_ip = models.GenericIPAddressField()
210 poster_user_agent = models.TextField()
212 poster_user_agent = models.TextField()
211
213
212 # TODO Remove this field after everything has been updated to 'thread'
214 # TODO Remove this field after everything has been updated to 'thread'
213 parent = models.BigIntegerField(default=NO_PARENT)
215 parent = models.BigIntegerField(default=NO_PARENT)
214
216
215 thread = models.ForeignKey('Post', null=True, default=None)
217 thread = models.ForeignKey('Post', null=True, default=None)
216 tags = models.ManyToManyField(Tag)
218 tags = models.ManyToManyField(Tag)
217 last_edit_time = models.DateTimeField()
219 last_edit_time = models.DateTimeField()
218 user = models.ForeignKey('User', null=True, default=None)
220 user = models.ForeignKey('User', null=True, default=None)
219
221
220 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
222 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
221 blank=True, related_name='re+')
223 blank=True, related_name='re+')
222
224
223 def __unicode__(self):
225 def __unicode__(self):
224 return '#' + str(self.id) + ' ' + self.title + ' (' + \
226 return '#' + str(self.id) + ' ' + self.title + ' (' + \
225 self.text.raw[:50] + ')'
227 self.text.raw[:50] + ')'
226
228
227 def get_title(self):
229 def get_title(self):
228 title = self.title
230 title = self.title
229 if len(title) == 0:
231 if len(title) == 0:
230 title = self.text.raw[:20]
232 title = self.text.raw[:20]
231
233
232 return title
234 return title
233
235
234 def get_reply_count(self):
236 def get_reply_count(self):
235 return self.replies.count()
237 return self.replies.count()
236
238
237 def get_images_count(self):
239 def get_images_count(self):
238 images_count = 1 if self.image else 0
240 images_count = 1 if self.image else 0
239 images_count += self.replies.filter(image_width__gt=0).count()
241 images_count += self.replies.filter(image_width__gt=0).count()
240
242
241 return images_count
243 return images_count
242
244
243 def can_bump(self):
245 def can_bump(self):
244 """Check if the thread can be bumped by replying"""
246 """Check if the thread can be bumped by replying"""
245
247
246 post_count = self.get_reply_count() + 1
248 post_count = self.get_reply_count() + 1
247
249
248 return post_count <= settings.MAX_POSTS_PER_THREAD
250 return post_count <= settings.MAX_POSTS_PER_THREAD
249
251
250 def bump(self):
252 def bump(self):
251 """Bump (move to up) thread"""
253 """Bump (move to up) thread"""
252
254
253 if self.can_bump():
255 if self.can_bump():
254 self.last_edit_time = timezone.now()
256 self.last_edit_time = timezone.now()
255 self.save()
257 self.save()
256
258
257 def get_last_replies(self):
259 def get_last_replies(self):
258 if settings.LAST_REPLIES_COUNT > 0:
260 if settings.LAST_REPLIES_COUNT > 0:
259 reply_count = self.get_reply_count()
261 reply_count = self.get_reply_count()
260
262
261 if reply_count > 0:
263 if reply_count > 0:
262 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
264 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
263 reply_count)
265 reply_count)
264 last_replies = self.replies.all()[reply_count -
266 last_replies = self.replies.all()[reply_count -
265 reply_count_to_show:]
267 reply_count_to_show:]
266
268
267 return last_replies
269 return last_replies
268
270
269
271
270 class User(models.Model):
272 class User(models.Model):
271
273
272 user_id = models.CharField(max_length=50)
274 user_id = models.CharField(max_length=50)
273 rank = models.IntegerField()
275 rank = models.IntegerField()
274
276
275 registration_time = models.DateTimeField()
277 registration_time = models.DateTimeField()
276 last_access_time = models.DateTimeField()
278 last_access_time = models.DateTimeField()
277
279
278 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
280 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
279 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
281 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
280 blank=True)
282 blank=True)
281
283
282 def save_setting(self, name, value):
284 def save_setting(self, name, value):
283 setting, created = Setting.objects.get_or_create(name=name, user=self)
285 setting, created = Setting.objects.get_or_create(name=name, user=self)
284 setting.value = value
286 setting.value = value
285 setting.save()
287 setting.save()
286
288
287 return setting
289 return setting
288
290
289 def get_setting(self, name):
291 def get_setting(self, name):
290 if Setting.objects.filter(name=name, user=self).exists():
292 if Setting.objects.filter(name=name, user=self).exists():
291 setting = Setting.objects.get(name=name, user=self)
293 setting = Setting.objects.get(name=name, user=self)
292 setting_value = setting.value
294 setting_value = setting.value
293 else:
295 else:
294 setting_value = None
296 setting_value = None
295
297
296 return setting_value
298 return setting_value
297
299
298 def is_moderator(self):
300 def is_moderator(self):
299 return RANK_MODERATOR >= self.rank
301 return RANK_MODERATOR >= self.rank
300
302
301 def get_sorted_fav_tags(self):
303 def get_sorted_fav_tags(self):
302 tags = self.fav_tags.annotate(Count('threads'))\
304 tags = self.fav_tags.annotate(Count('threads'))\
303 .filter(threads__count__gt=0).order_by('name')
305 .filter(threads__count__gt=0).order_by('name')
304
306
305 return tags
307 return tags
306
308
307 def __unicode__(self):
309 def __unicode__(self):
308 return self.user_id + '(' + str(self.rank) + ')'
310 return self.user_id + '(' + str(self.rank) + ')'
309
311
310
312
311 class Setting(models.Model):
313 class Setting(models.Model):
312
314
313 name = models.CharField(max_length=50)
315 name = models.CharField(max_length=50)
314 value = models.CharField(max_length=50)
316 value = models.CharField(max_length=50)
315 user = models.ForeignKey(User)
317 user = models.ForeignKey(User)
316
318
317
319
318 class Ban(models.Model):
320 class Ban(models.Model):
319
321
320 ip = models.GenericIPAddressField()
322 ip = models.GenericIPAddressField()
321
323
322 def __unicode__(self):
324 def __unicode__(self):
323 return self.ip
325 return self.ip
General Comments 0
You need to be logged in to leave comments. Login now