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