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