##// END OF EJS Templates
Allow linked tags to have cyclic dependancies
Pavel Ryapolov -
r286:ad629f57 default
parent child Browse files
Show More
@@ -1,352 +1,354 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 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 = threads.count()
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 linked = models.ForeignKey('Tag', null=True, blank=True)
172 172
173 173 def __unicode__(self):
174 174 return self.name
175 175
176 176 def is_empty(self):
177 177 return self.get_post_count() == 0
178 178
179 179 def get_post_count(self):
180 180 return self.threads.count()
181 181
182 182 def get_popularity(self):
183 183 posts_with_tag = Post.objects.get_threads(tag=self)
184 184 reply_count = 0
185 185 for post in posts_with_tag:
186 186 reply_count += post.get_reply_count()
187 187 reply_count += OPENING_POST_POPULARITY_WEIGHT
188 188
189 189 return reply_count
190 190
191 def get_linked_tags(self):
192 linked_tags = []
193
191 def get_linked_tags(self, tag_list=[]):
192 """
193 Returns the list of tags linked to current. The list can be got
194 through returned value or tag_list parameter
195 """
196
194 197 linked_tag = self.linked
195 if linked_tag:
196 linked_tags.append(linked_tag)
198
199 if linked_tag and not (linked_tag in tag_list):
200 tag_list.append(linked_tag)
197 201
198 far_tags = linked_tag.get_linked_tags()
199 if len(far_tags) > 0:
200 linked_tags.extend(far_tags)
202 linked_tag.get_linked_tags(tag_list)
201 203
202 return linked_tags
204 return tag_list
203 205
204 206
205 207 class Post(models.Model):
206 208 """A post is a message."""
207 209
208 210 objects = PostManager()
209 211
210 212 def _update_image_filename(self, filename):
211 213 """Get unique image filename"""
212 214
213 215 path = IMAGES_DIRECTORY
214 216 new_name = str(int(time.mktime(time.gmtime())))
215 217 new_name += str(int(random() * 1000))
216 218 new_name += FILE_EXTENSION_DELIMITER
217 219 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
218 220
219 221 return os.path.join(path, new_name)
220 222
221 223 title = models.CharField(max_length=TITLE_MAX_LENGTH)
222 224 pub_time = models.DateTimeField()
223 225 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
224 226 escape_html=False)
225 227
226 228 image_width = models.IntegerField(default=0)
227 229 image_height = models.IntegerField(default=0)
228 230
229 231 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
230 232 blank=True, sizes=(IMAGE_THUMB_SIZE,),
231 233 width_field='image_width',
232 234 height_field='image_height')
233 235
234 236 poster_ip = models.GenericIPAddressField()
235 237 poster_user_agent = models.TextField()
236 238
237 239 thread = models.ForeignKey('Post', null=True, default=None)
238 240 tags = models.ManyToManyField(Tag)
239 241 last_edit_time = models.DateTimeField()
240 242 bump_time = models.DateTimeField()
241 243 user = models.ForeignKey('User', null=True, default=None)
242 244
243 245 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
244 246 blank=True, related_name='re+')
245 247
246 248 def __unicode__(self):
247 249 return '#' + str(self.id) + ' ' + self.title + ' (' + \
248 250 self.text.raw[:50] + ')'
249 251
250 252 def get_title(self):
251 253 title = self.title
252 254 if len(title) == 0:
253 255 title = self.text.raw[:20]
254 256
255 257 return title
256 258
257 259 def get_reply_count(self):
258 260 return self.replies.count()
259 261
260 262 def get_images_count(self):
261 263 images_count = 1 if self.image else 0
262 264 images_count += self.replies.filter(image_width__gt=0).count()
263 265
264 266 return images_count
265 267
266 268 def can_bump(self):
267 269 """Check if the thread can be bumped by replying"""
268 270
269 271 post_count = self.get_reply_count() + 1
270 272
271 273 return post_count <= settings.MAX_POSTS_PER_THREAD
272 274
273 275 def bump(self):
274 276 """Bump (move to up) thread"""
275 277
276 278 if self.can_bump():
277 279 self.bump_time = timezone.now()
278 280
279 281 def get_last_replies(self):
280 282 if settings.LAST_REPLIES_COUNT > 0:
281 283 reply_count = self.get_reply_count()
282 284
283 285 if reply_count > 0:
284 286 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
285 287 reply_count)
286 288 last_replies = self.replies.all().order_by('pub_time')[reply_count -
287 289 reply_count_to_show:]
288 290
289 291 return last_replies
290 292
291 293
292 294 class User(models.Model):
293 295
294 296 user_id = models.CharField(max_length=50)
295 297 rank = models.IntegerField()
296 298
297 299 registration_time = models.DateTimeField()
298 300
299 301 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
300 302 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
301 303 blank=True)
302 304
303 305 def save_setting(self, name, value):
304 306 setting, created = Setting.objects.get_or_create(name=name, user=self)
305 307 setting.value = str(value)
306 308 setting.save()
307 309
308 310 return setting
309 311
310 312 def get_setting(self, name):
311 313 if Setting.objects.filter(name=name, user=self).exists():
312 314 setting = Setting.objects.get(name=name, user=self)
313 315 setting_value = setting.value
314 316 else:
315 317 setting_value = None
316 318
317 319 return setting_value
318 320
319 321 def is_moderator(self):
320 322 return RANK_MODERATOR >= self.rank
321 323
322 324 def get_sorted_fav_tags(self):
323 325 tags = self.fav_tags.annotate(Count('threads'))\
324 326 .filter(threads__count__gt=0).order_by('name')
325 327
326 328 return tags
327 329
328 330 def get_post_count(self):
329 331 return Post.objects.filter(user=self).count()
330 332
331 333 def __unicode__(self):
332 334 return self.user_id + '(' + str(self.rank) + ')'
333 335
334 336 def get_last_access_time(self):
335 337 posts = Post.objects.filter(user=self)
336 338 if posts.count() > 0:
337 339 return posts.latest('pub_time').pub_time
338 340
339 341
340 342 class Setting(models.Model):
341 343
342 344 name = models.CharField(max_length=50)
343 345 value = models.CharField(max_length=50)
344 346 user = models.ForeignKey(User)
345 347
346 348
347 349 class Ban(models.Model):
348 350
349 351 ip = models.GenericIPAddressField()
350 352
351 353 def __unicode__(self):
352 354 return self.ip
General Comments 0
You need to be logged in to leave comments. Login now