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