##// END OF EJS Templates
Moved models to a separate module folder. Starting to split up models file
neko259 -
r384:d51739f7 default
parent child Browse files
Show More
@@ -0,0 +1,7 b''
1 __author__ = 'neko259'
2
3 from boards.models.post import Post
4 from boards.models.post import Tag
5 from boards.models.post import Ban
6 from boards.models.post import Setting
7 from boards.models.post import User
@@ -0,0 +1,476 b''
1 import os
2 from random import random
3 import time
4 import math
5 from django.core.cache import cache
6
7 from django.db import models
8 from django.db.models import Count
9 from django.http import Http404
10 from django.utils import timezone
11 from markupfield.fields import MarkupField
12 from boards import settings as board_settings
13
14 from neboard import settings
15 from boards import thumbs
16
17 import re
18
19 TAG_FONT_MULTIPLIER = 0.2
20
21 MAX_TAG_FONT = 4
22
23 BAN_REASON_MAX_LENGTH = 200
24
25 BAN_REASON_AUTO = 'Auto'
26
27 IMAGE_THUMB_SIZE = (200, 150)
28
29 TITLE_MAX_LENGTH = 50
30
31 DEFAULT_MARKUP_TYPE = 'markdown'
32
33 NO_PARENT = -1
34 NO_IP = '0.0.0.0'
35 UNKNOWN_UA = ''
36 ALL_PAGES = -1
37 OPENING_POST_POPULARITY_WEIGHT = 2
38 IMAGES_DIRECTORY = 'images/'
39 FILE_EXTENSION_DELIMITER = '.'
40
41 RANK_ADMIN = 0
42 RANK_MODERATOR = 10
43 RANK_USER = 100
44
45 SETTING_MODERATE = "moderate"
46
47 REGEX_REPLY = re.compile('>>(\d+)')
48
49
50 class PostManager(models.Manager):
51
52 def create_post(self, title, text, image=None, thread=None,
53 ip=NO_IP, tags=None, user=None):
54 posting_time = timezone.now()
55
56 post = self.create(title=title,
57 text=text,
58 pub_time=posting_time,
59 thread=thread,
60 image=image,
61 poster_ip=ip,
62 poster_user_agent=UNKNOWN_UA,
63 last_edit_time=posting_time,
64 bump_time=posting_time,
65 user=user)
66
67 if tags:
68 linked_tags = []
69 for tag in tags:
70 tag_linked_tags = tag.get_linked_tags()
71 if len(tag_linked_tags) > 0:
72 linked_tags.extend(tag_linked_tags)
73
74 tags.extend(linked_tags)
75 map(post.tags.add, tags)
76 for tag in tags:
77 tag.threads.add(post)
78
79 if thread:
80 thread.replies.add(post)
81 thread.bump()
82 thread.last_edit_time = posting_time
83 thread.save()
84
85 #cache_key = thread.get_cache_key()
86 #cache.delete(cache_key)
87
88 else:
89 self._delete_old_threads()
90
91 self.connect_replies(post)
92
93 return post
94
95 def delete_post(self, post):
96 if post.replies.count() > 0:
97 map(self.delete_post, post.replies.all())
98
99 # Update thread's last edit time (used as cache key)
100 thread = post.thread
101 if thread:
102 thread.last_edit_time = timezone.now()
103 thread.save()
104
105 #cache_key = thread.get_cache_key()
106 #cache.delete(cache_key)
107
108 post.delete()
109
110 def delete_posts_by_ip(self, ip):
111 posts = self.filter(poster_ip=ip)
112 map(self.delete_post, posts)
113
114 def get_threads(self, tag=None, page=ALL_PAGES,
115 order_by='-bump_time'):
116 if tag:
117 threads = tag.threads
118
119 if threads.count() == 0:
120 raise Http404
121 else:
122 threads = self.filter(thread=None)
123
124 threads = threads.order_by(order_by)
125
126 if page != ALL_PAGES:
127 thread_count = threads.count()
128
129 if page < self._get_page_count(thread_count):
130 start_thread = page * settings.THREADS_PER_PAGE
131 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
132 thread_count)
133 threads = threads[start_thread:end_thread]
134
135 return threads
136
137 def get_thread(self, opening_post_id):
138 try:
139 opening_post = self.get(id=opening_post_id, thread=None)
140 except Post.DoesNotExist:
141 raise Http404
142
143 #cache_key = opening_post.get_cache_key()
144 #thread = cache.get(cache_key)
145 #if thread:
146 # return thread
147
148 if opening_post.replies:
149 thread = [opening_post]
150 thread.extend(opening_post.replies.all().order_by('pub_time'))
151
152 #cache.set(cache_key, thread, board_settings.CACHE_TIMEOUT)
153
154 return thread
155
156 def exists(self, post_id):
157 posts = self.filter(id=post_id)
158
159 return posts.count() > 0
160
161 def get_thread_page_count(self, tag=None):
162 if tag:
163 threads = self.filter(thread=None, tags=tag)
164 else:
165 threads = self.filter(thread=None)
166
167 return self._get_page_count(threads.count())
168
169 def _delete_old_threads(self):
170 """
171 Preserves maximum thread count. If there are too many threads,
172 delete the old ones.
173 """
174
175 # TODO Move old threads to the archive instead of deleting them.
176 # Maybe make some 'old' field in the model to indicate the thread
177 # must not be shown and be able for replying.
178
179 threads = self.get_threads()
180 thread_count = threads.count()
181
182 if thread_count > settings.MAX_THREAD_COUNT:
183 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
184 old_threads = threads[thread_count - num_threads_to_delete:]
185
186 map(self.delete_post, old_threads)
187
188 def connect_replies(self, post):
189 """Connect replies to a post to show them as a refmap"""
190
191 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
192 post_id = reply_number.group(1)
193 ref_post = self.filter(id=post_id)
194 if ref_post.count() > 0:
195 referenced_post = ref_post[0]
196 referenced_post.referenced_posts.add(post)
197 referenced_post.last_edit_time = post.pub_time
198 referenced_post.save()
199
200 def _get_page_count(self, thread_count):
201 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
202
203
204 class TagManager(models.Manager):
205
206 def get_not_empty_tags(self):
207 tags = self.annotate(Count('threads')) \
208 .filter(threads__count__gt=0).order_by('name')
209
210 return tags
211
212
213 class Tag(models.Model):
214 """
215 A tag is a text node assigned to the post. The tag serves as a board
216 section. There can be multiple tags for each message
217 """
218
219 objects = TagManager()
220
221 class Meta:
222 app_label = 'boards'
223
224 name = models.CharField(max_length=100)
225 threads = models.ManyToManyField('Post', null=True,
226 blank=True, related_name='tag+')
227 linked = models.ForeignKey('Tag', null=True, blank=True)
228
229 def __unicode__(self):
230 return self.name
231
232 def is_empty(self):
233 return self.get_post_count() == 0
234
235 def get_post_count(self):
236 return self.threads.count()
237
238 def get_popularity(self):
239 posts_with_tag = Post.objects.get_threads(tag=self)
240 reply_count = 0
241 for post in posts_with_tag:
242 reply_count += post.get_reply_count()
243 reply_count += OPENING_POST_POPULARITY_WEIGHT
244
245 return reply_count
246
247 def get_linked_tags(self):
248 tag_list = []
249 self.get_linked_tags_list(tag_list)
250
251 return tag_list
252
253 def get_linked_tags_list(self, tag_list=[]):
254 """
255 Returns the list of tags linked to current. The list can be got
256 through returned value or tag_list parameter
257 """
258
259 linked_tag = self.linked
260
261 if linked_tag and not (linked_tag in tag_list):
262 tag_list.append(linked_tag)
263
264 linked_tag.get_linked_tags_list(tag_list)
265
266 def get_font_value(self):
267 """Get tag font value to differ most popular tags in the list"""
268
269 post_count = self.get_post_count()
270 if post_count > MAX_TAG_FONT:
271 post_count = MAX_TAG_FONT
272
273 font_value = str(1 + (post_count - 1) * TAG_FONT_MULTIPLIER)
274
275 return font_value
276
277
278 class Post(models.Model):
279 """A post is a message."""
280
281 objects = PostManager()
282
283 class Meta:
284 app_label = 'boards'
285
286 def _update_image_filename(self, filename):
287 """Get unique image filename"""
288
289 path = IMAGES_DIRECTORY
290 new_name = str(int(time.mktime(time.gmtime())))
291 new_name += str(int(random() * 1000))
292 new_name += FILE_EXTENSION_DELIMITER
293 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
294
295 return os.path.join(path, new_name)
296
297 title = models.CharField(max_length=TITLE_MAX_LENGTH)
298 pub_time = models.DateTimeField()
299 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
300 escape_html=False)
301
302 image_width = models.IntegerField(default=0)
303 image_height = models.IntegerField(default=0)
304
305 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
306 blank=True, sizes=(IMAGE_THUMB_SIZE,),
307 width_field='image_width',
308 height_field='image_height')
309
310 poster_ip = models.GenericIPAddressField()
311 poster_user_agent = models.TextField()
312
313 thread = models.ForeignKey('Post', null=True, default=None)
314 tags = models.ManyToManyField(Tag)
315 last_edit_time = models.DateTimeField()
316 bump_time = models.DateTimeField()
317 user = models.ForeignKey('User', null=True, default=None)
318
319 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
320 blank=True, related_name='re+')
321 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
322 null=True,
323 blank=True, related_name='rfp+')
324
325 def __unicode__(self):
326 return '#' + str(self.id) + ' ' + self.title + ' (' + \
327 self.text.raw[:50] + ')'
328
329 def get_title(self):
330 title = self.title
331 if len(title) == 0:
332 title = self.text.raw[:20]
333
334 return title
335
336 def get_reply_count(self):
337 return self.replies.count()
338
339 def get_images_count(self):
340 images_count = 1 if self.image else 0
341 images_count += self.replies.filter(image_width__gt=0).count()
342
343 return images_count
344
345 def can_bump(self):
346 """Check if the thread can be bumped by replying"""
347
348 post_count = self.get_reply_count()
349
350 return post_count <= settings.MAX_POSTS_PER_THREAD
351
352 def bump(self):
353 """Bump (move to up) thread"""
354
355 if self.can_bump():
356 self.bump_time = timezone.now()
357
358 def get_last_replies(self):
359 if settings.LAST_REPLIES_COUNT > 0:
360 reply_count = self.get_reply_count()
361
362 if reply_count > 0:
363 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
364 reply_count)
365 last_replies = self.replies.all().order_by('pub_time')[
366 reply_count - reply_count_to_show:]
367
368 return last_replies
369
370 def get_tags(self):
371 """Get a sorted tag list"""
372
373 return self.tags.order_by('name')
374
375 def get_cache_key(self):
376 return str(self.id) + str(self.last_edit_time.microsecond)
377
378 def get_sorted_referenced_posts(self):
379 return self.referenced_posts.order_by('id')
380
381 def is_referenced(self):
382 return self.referenced_posts.count() > 0
383
384
385 class User(models.Model):
386
387 class Meta:
388 app_label = 'boards'
389
390 user_id = models.CharField(max_length=50)
391 rank = models.IntegerField()
392
393 registration_time = models.DateTimeField()
394
395 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
396 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
397 blank=True)
398
399 def save_setting(self, name, value):
400 setting, created = Setting.objects.get_or_create(name=name, user=self)
401 setting.value = str(value)
402 setting.save()
403
404 return setting
405
406 def get_setting(self, name):
407 if Setting.objects.filter(name=name, user=self).exists():
408 setting = Setting.objects.get(name=name, user=self)
409 setting_value = setting.value
410 else:
411 setting_value = None
412
413 return setting_value
414
415 def is_moderator(self):
416 return RANK_MODERATOR >= self.rank
417
418 def get_sorted_fav_tags(self):
419 cache_key = self._get_tag_cache_key()
420 fav_tags = cache.get(cache_key)
421 if fav_tags:
422 return fav_tags
423
424 tags = self.fav_tags.annotate(Count('threads')) \
425 .filter(threads__count__gt=0).order_by('name')
426
427 if tags:
428 cache.set(cache_key, tags, board_settings.CACHE_TIMEOUT)
429
430 return tags
431
432 def get_post_count(self):
433 return Post.objects.filter(user=self).count()
434
435 def __unicode__(self):
436 return self.user_id + '(' + str(self.rank) + ')'
437
438 def get_last_access_time(self):
439 posts = Post.objects.filter(user=self)
440 if posts.count() > 0:
441 return posts.latest('pub_time').pub_time
442
443 def add_tag(self, tag):
444 self.fav_tags.add(tag)
445 cache.delete(self._get_tag_cache_key())
446
447 def remove_tag(self, tag):
448 self.fav_tags.remove(tag)
449 cache.delete(self._get_tag_cache_key())
450
451 def _get_tag_cache_key(self):
452 return self.user_id + '_tags'
453
454
455 class Setting(models.Model):
456
457 class Meta:
458 app_label = 'boards'
459
460 name = models.CharField(max_length=50)
461 value = models.CharField(max_length=50)
462 user = models.ForeignKey(User)
463
464
465 class Ban(models.Model):
466
467 class Meta:
468 app_label = 'boards'
469
470 ip = models.GenericIPAddressField()
471 reason = models.CharField(default=BAN_REASON_AUTO,
472 max_length=BAN_REASON_MAX_LENGTH)
473 can_read = models.BooleanField(default=True)
474
475 def __unicode__(self):
476 return self.ip
@@ -4,7 +4,7 b' from django import forms'
4 from django.forms.util import ErrorList
4 from django.forms.util import ErrorList
5 from django.utils.translation import ugettext_lazy as _
5 from django.utils.translation import ugettext_lazy as _
6 import time
6 import time
7 from boards.models import TITLE_MAX_LENGTH, User
7 from boards.models.post import TITLE_MAX_LENGTH, User
8 from neboard import settings
8 from neboard import settings
9 from boards import utils
9 from boards import utils
10 import boards.settings as board_settings
10 import boards.settings as board_settings
@@ -3,7 +3,7 b' import datetime'
3 from south.db import db
3 from south.db import db
4 from south.v2 import SchemaMigration
4 from south.v2 import SchemaMigration
5 from django.db import models
5 from django.db import models
6 from boards.models import Post, NO_PARENT
6 from boards.models.post import Post, NO_PARENT
7
7
8
8
9 class Migration(SchemaMigration):
9 class Migration(SchemaMigration):
@@ -2,9 +2,8 b' import hashlib'
2 import json
2 import json
3 import string
3 import string
4 import time
4 import time
5 import calendar
6
7 from datetime import datetime
5 from datetime import datetime
6 import re
8
7
9 from django.core import serializers
8 from django.core import serializers
10 from django.core.urlresolvers import reverse
9 from django.core.urlresolvers import reverse
@@ -14,20 +13,18 b' from django.template import RequestConte'
14 from django.shortcuts import render, redirect, get_object_or_404
13 from django.shortcuts import render, redirect, get_object_or_404
15 from django.utils import timezone
14 from django.utils import timezone
16 from django.db import transaction
15 from django.db import transaction
17 import math
18
16
19 from boards import forms
17 from boards import forms
20 import boards
18 import boards
21 from boards import utils
19 from boards import utils
22 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
20 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
23 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
21 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
24
22 from boards.models.post import Post, Tag, Ban, User, RANK_USER, \
25 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
23 SETTING_MODERATE, REGEX_REPLY
26 REGEX_REPLY
27 from boards import authors
24 from boards import authors
28 from boards.utils import get_client_ip
25 from boards.utils import get_client_ip
29 import neboard
26 import neboard
30 import re
27
31
28
32 BAN_REASON_SPAM = 'Autoban: spam bot'
29 BAN_REASON_SPAM = 'Autoban: spam bot'
33
30
@@ -80,7 +77,7 b' def index(request, page=0):'
80
77
81
78
82 @transaction.commit_on_success
79 @transaction.commit_on_success
83 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
80 def _new_post(request, form, opening_post=None):
84 """Add a new post (in thread or as a reply)."""
81 """Add a new post (in thread or as a reply)."""
85
82
86 ip = get_client_ip(request)
83 ip = get_client_ip(request)
@@ -103,8 +100,7 b' def _new_post(request, form, thread_id=b'
103
100
104 tags = []
101 tags = []
105
102
106 new_thread = thread_id == boards.models.NO_PARENT
103 if not opening_post:
107 if new_thread:
108 tag_strings = data['tags']
104 tag_strings = data['tags']
109
105
110 if tag_strings:
106 if tag_strings:
@@ -115,19 +111,17 b' def _new_post(request, form, thread_id=b'
115 tag, created = Tag.objects.get_or_create(name=tag_name)
111 tag, created = Tag.objects.get_or_create(name=tag_name)
116 tags.append(tag)
112 tags.append(tag)
117
113
118 op = None if thread_id == boards.models.NO_PARENT else \
119 get_object_or_404(Post, id=thread_id)
120 post = Post.objects.create_post(title=title, text=text, ip=ip,
114 post = Post.objects.create_post(title=title, text=text, ip=ip,
121 thread=op, image=image,
115 thread=opening_post, image=image,
122 tags=tags, user=_get_user(request))
116 tags=tags, user=_get_user(request))
123
117
124 thread_to_show = (post.id if new_thread else thread_id)
118 thread_to_show = (opening_post.id if opening_post else post.id)
125
119
126 if new_thread:
120 if opening_post:
127 return redirect(thread, post_id=thread_to_show)
128 else:
129 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
121 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
130 '#' + str(post.id))
122 '#' + str(post.id))
123 else:
124 return redirect(thread, post_id=thread_to_show)
131
125
132
126
133 def tag(request, tag_name, page=0):
127 def tag(request, tag_name, page=0):
@@ -192,8 +186,9 b' def thread(request, post_id):'
192 error_class=PlainErrorList, **kwargs)
186 error_class=PlainErrorList, **kwargs)
193 form.session = request.session
187 form.session = request.session
194
188
189 opening_post = get_object_or_404(Post, id=post_id)
195 if form.is_valid():
190 if form.is_valid():
196 return _new_post(request, form, post_id)
191 return _new_post(request, form, opening_post)
197 if form.need_to_ban:
192 if form.need_to_ban:
198 # Ban user because he is suspected to be a bot
193 # Ban user because he is suspected to be a bot
199 _ban_current_user(request)
194 _ban_current_user(request)
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now