##// END OF EJS Templates
Split up tag module from post module
neko259 -
r385:72926030 default
parent child Browse files
Show More
@@ -0,0 +1,85 b''
1 from boards.models import Post
2
3 __author__ = 'neko259'
4
5
6 from django.db import models
7 from django.db.models import Count
8
9 TAG_FONT_MULTIPLIER = 0.1
10 MAX_TAG_FONT = 10
11 OPENING_POST_POPULARITY_WEIGHT = 2
12
13
14 class TagManager(models.Manager):
15
16 def get_not_empty_tags(self):
17 tags = self.annotate(Count('threads')) \
18 .filter(threads__count__gt=0).order_by('name')
19
20 return tags
21
22
23 class Tag(models.Model):
24 """
25 A tag is a text node assigned to the post. The tag serves as a board
26 section. There can be multiple tags for each message
27 """
28
29 objects = TagManager()
30
31 class Meta:
32 app_label = 'boards'
33
34 name = models.CharField(max_length=100)
35 threads = models.ManyToManyField('Post', null=True,
36 blank=True, related_name='tag+')
37 linked = models.ForeignKey('Tag', null=True, blank=True)
38
39 def __unicode__(self):
40 return self.name
41
42 def is_empty(self):
43 return self.get_post_count() == 0
44
45 def get_post_count(self):
46 return self.threads.count()
47
48 def get_popularity(self):
49 posts_with_tag = Post.objects.get_threads(tag=self)
50 reply_count = 0
51 for post in posts_with_tag:
52 reply_count += post.get_reply_count()
53 reply_count += OPENING_POST_POPULARITY_WEIGHT
54
55 return reply_count
56
57 def get_linked_tags(self):
58 tag_list = []
59 self.get_linked_tags_list(tag_list)
60
61 return tag_list
62
63 def get_linked_tags_list(self, tag_list=[]):
64 """
65 Returns the list of tags linked to current. The list can be got
66 through returned value or tag_list parameter
67 """
68
69 linked_tag = self.linked
70
71 if linked_tag and not (linked_tag in tag_list):
72 tag_list.append(linked_tag)
73
74 linked_tag.get_linked_tags_list(tag_list)
75
76 def get_font_value(self):
77 """Get tag font value to differ most popular tags in the list"""
78
79 post_count = self.get_post_count()
80 if post_count > MAX_TAG_FONT:
81 post_count = MAX_TAG_FONT
82
83 font_value = str(1 + (post_count - 1) * TAG_FONT_MULTIPLIER)
84
85 return font_value No newline at end of file
@@ -1,7 +1,7 b''
1 1 __author__ = 'neko259'
2 2
3 3 from boards.models.post import Post
4 from boards.models.post import Tag
4 from boards.models.tag import Tag
5 5 from boards.models.post import Ban
6 6 from boards.models.post import Setting
7 7 from boards.models.post import User
@@ -1,476 +1,397 b''
1 1 import os
2 2 from random import random
3 3 import time
4 4 import math
5 5 from django.core.cache import cache
6 6
7 7 from django.db import models
8 8 from django.db.models import Count
9 9 from django.http import Http404
10 10 from django.utils import timezone
11 11 from markupfield.fields import MarkupField
12 12 from boards import settings as board_settings
13 13
14 14 from neboard import settings
15 15 from boards import thumbs
16 16
17 17 import re
18 18
19 TAG_FONT_MULTIPLIER = 0.2
20
21 MAX_TAG_FONT = 4
22
23 19 BAN_REASON_MAX_LENGTH = 200
24 20
25 21 BAN_REASON_AUTO = 'Auto'
26 22
27 23 IMAGE_THUMB_SIZE = (200, 150)
28 24
29 25 TITLE_MAX_LENGTH = 50
30 26
31 27 DEFAULT_MARKUP_TYPE = 'markdown'
32 28
33 29 NO_PARENT = -1
34 30 NO_IP = '0.0.0.0'
35 31 UNKNOWN_UA = ''
36 32 ALL_PAGES = -1
37 OPENING_POST_POPULARITY_WEIGHT = 2
38 33 IMAGES_DIRECTORY = 'images/'
39 34 FILE_EXTENSION_DELIMITER = '.'
40 35
41 36 RANK_ADMIN = 0
42 37 RANK_MODERATOR = 10
43 38 RANK_USER = 100
44 39
45 40 SETTING_MODERATE = "moderate"
46 41
47 42 REGEX_REPLY = re.compile('>>(\d+)')
48 43
49 44
50 45 class PostManager(models.Manager):
51 46
52 47 def create_post(self, title, text, image=None, thread=None,
53 48 ip=NO_IP, tags=None, user=None):
54 49 posting_time = timezone.now()
55 50
56 51 post = self.create(title=title,
57 52 text=text,
58 53 pub_time=posting_time,
59 54 thread=thread,
60 55 image=image,
61 56 poster_ip=ip,
62 57 poster_user_agent=UNKNOWN_UA,
63 58 last_edit_time=posting_time,
64 59 bump_time=posting_time,
65 60 user=user)
66 61
67 62 if tags:
68 63 linked_tags = []
69 64 for tag in tags:
70 65 tag_linked_tags = tag.get_linked_tags()
71 66 if len(tag_linked_tags) > 0:
72 67 linked_tags.extend(tag_linked_tags)
73 68
74 69 tags.extend(linked_tags)
75 70 map(post.tags.add, tags)
76 71 for tag in tags:
77 72 tag.threads.add(post)
78 73
79 74 if thread:
80 75 thread.replies.add(post)
81 76 thread.bump()
82 77 thread.last_edit_time = posting_time
83 78 thread.save()
84 79
85 80 #cache_key = thread.get_cache_key()
86 81 #cache.delete(cache_key)
87 82
88 83 else:
89 84 self._delete_old_threads()
90 85
91 86 self.connect_replies(post)
92 87
93 88 return post
94 89
95 90 def delete_post(self, post):
96 91 if post.replies.count() > 0:
97 92 map(self.delete_post, post.replies.all())
98 93
99 94 # Update thread's last edit time (used as cache key)
100 95 thread = post.thread
101 96 if thread:
102 97 thread.last_edit_time = timezone.now()
103 98 thread.save()
104 99
105 100 #cache_key = thread.get_cache_key()
106 101 #cache.delete(cache_key)
107 102
108 103 post.delete()
109 104
110 105 def delete_posts_by_ip(self, ip):
111 106 posts = self.filter(poster_ip=ip)
112 107 map(self.delete_post, posts)
113 108
114 109 def get_threads(self, tag=None, page=ALL_PAGES,
115 110 order_by='-bump_time'):
116 111 if tag:
117 112 threads = tag.threads
118 113
119 114 if threads.count() == 0:
120 115 raise Http404
121 116 else:
122 117 threads = self.filter(thread=None)
123 118
124 119 threads = threads.order_by(order_by)
125 120
126 121 if page != ALL_PAGES:
127 122 thread_count = threads.count()
128 123
129 124 if page < self._get_page_count(thread_count):
130 125 start_thread = page * settings.THREADS_PER_PAGE
131 126 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
132 127 thread_count)
133 128 threads = threads[start_thread:end_thread]
134 129
135 130 return threads
136 131
137 132 def get_thread(self, opening_post_id):
138 133 try:
139 134 opening_post = self.get(id=opening_post_id, thread=None)
140 135 except Post.DoesNotExist:
141 136 raise Http404
142 137
143 138 #cache_key = opening_post.get_cache_key()
144 139 #thread = cache.get(cache_key)
145 140 #if thread:
146 141 # return thread
147 142
148 143 if opening_post.replies:
149 144 thread = [opening_post]
150 145 thread.extend(opening_post.replies.all().order_by('pub_time'))
151 146
152 147 #cache.set(cache_key, thread, board_settings.CACHE_TIMEOUT)
153 148
154 149 return thread
155 150
156 151 def exists(self, post_id):
157 152 posts = self.filter(id=post_id)
158 153
159 154 return posts.count() > 0
160 155
161 156 def get_thread_page_count(self, tag=None):
162 157 if tag:
163 158 threads = self.filter(thread=None, tags=tag)
164 159 else:
165 160 threads = self.filter(thread=None)
166 161
167 162 return self._get_page_count(threads.count())
168 163
169 164 def _delete_old_threads(self):
170 165 """
171 166 Preserves maximum thread count. If there are too many threads,
172 167 delete the old ones.
173 168 """
174 169
175 170 # TODO Move old threads to the archive instead of deleting them.
176 171 # Maybe make some 'old' field in the model to indicate the thread
177 172 # must not be shown and be able for replying.
178 173
179 174 threads = self.get_threads()
180 175 thread_count = threads.count()
181 176
182 177 if thread_count > settings.MAX_THREAD_COUNT:
183 178 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
184 179 old_threads = threads[thread_count - num_threads_to_delete:]
185 180
186 181 map(self.delete_post, old_threads)
187 182
188 183 def connect_replies(self, post):
189 184 """Connect replies to a post to show them as a refmap"""
190 185
191 186 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
192 187 post_id = reply_number.group(1)
193 188 ref_post = self.filter(id=post_id)
194 189 if ref_post.count() > 0:
195 190 referenced_post = ref_post[0]
196 191 referenced_post.referenced_posts.add(post)
197 192 referenced_post.last_edit_time = post.pub_time
198 193 referenced_post.save()
199 194
200 195 def _get_page_count(self, thread_count):
201 196 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
202 197
203 198
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 199 class Post(models.Model):
279 200 """A post is a message."""
280 201
281 202 objects = PostManager()
282 203
283 204 class Meta:
284 205 app_label = 'boards'
285 206
286 207 def _update_image_filename(self, filename):
287 208 """Get unique image filename"""
288 209
289 210 path = IMAGES_DIRECTORY
290 211 new_name = str(int(time.mktime(time.gmtime())))
291 212 new_name += str(int(random() * 1000))
292 213 new_name += FILE_EXTENSION_DELIMITER
293 214 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
294 215
295 216 return os.path.join(path, new_name)
296 217
297 218 title = models.CharField(max_length=TITLE_MAX_LENGTH)
298 219 pub_time = models.DateTimeField()
299 220 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
300 221 escape_html=False)
301 222
302 223 image_width = models.IntegerField(default=0)
303 224 image_height = models.IntegerField(default=0)
304 225
305 226 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
306 227 blank=True, sizes=(IMAGE_THUMB_SIZE,),
307 228 width_field='image_width',
308 229 height_field='image_height')
309 230
310 231 poster_ip = models.GenericIPAddressField()
311 232 poster_user_agent = models.TextField()
312 233
313 234 thread = models.ForeignKey('Post', null=True, default=None)
314 tags = models.ManyToManyField(Tag)
235 tags = models.ManyToManyField('Tag')
315 236 last_edit_time = models.DateTimeField()
316 237 bump_time = models.DateTimeField()
317 238 user = models.ForeignKey('User', null=True, default=None)
318 239
319 240 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
320 241 blank=True, related_name='re+')
321 242 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
322 243 null=True,
323 244 blank=True, related_name='rfp+')
324 245
325 246 def __unicode__(self):
326 247 return '#' + str(self.id) + ' ' + self.title + ' (' + \
327 248 self.text.raw[:50] + ')'
328 249
329 250 def get_title(self):
330 251 title = self.title
331 252 if len(title) == 0:
332 253 title = self.text.raw[:20]
333 254
334 255 return title
335 256
336 257 def get_reply_count(self):
337 258 return self.replies.count()
338 259
339 260 def get_images_count(self):
340 261 images_count = 1 if self.image else 0
341 262 images_count += self.replies.filter(image_width__gt=0).count()
342 263
343 264 return images_count
344 265
345 266 def can_bump(self):
346 267 """Check if the thread can be bumped by replying"""
347 268
348 269 post_count = self.get_reply_count()
349 270
350 271 return post_count <= settings.MAX_POSTS_PER_THREAD
351 272
352 273 def bump(self):
353 274 """Bump (move to up) thread"""
354 275
355 276 if self.can_bump():
356 277 self.bump_time = timezone.now()
357 278
358 279 def get_last_replies(self):
359 280 if settings.LAST_REPLIES_COUNT > 0:
360 281 reply_count = self.get_reply_count()
361 282
362 283 if reply_count > 0:
363 284 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
364 285 reply_count)
365 286 last_replies = self.replies.all().order_by('pub_time')[
366 287 reply_count - reply_count_to_show:]
367 288
368 289 return last_replies
369 290
370 291 def get_tags(self):
371 292 """Get a sorted tag list"""
372 293
373 294 return self.tags.order_by('name')
374 295
375 296 def get_cache_key(self):
376 297 return str(self.id) + str(self.last_edit_time.microsecond)
377 298
378 299 def get_sorted_referenced_posts(self):
379 300 return self.referenced_posts.order_by('id')
380 301
381 302 def is_referenced(self):
382 303 return self.referenced_posts.count() > 0
383 304
384 305
385 306 class User(models.Model):
386 307
387 308 class Meta:
388 309 app_label = 'boards'
389 310
390 311 user_id = models.CharField(max_length=50)
391 312 rank = models.IntegerField()
392 313
393 314 registration_time = models.DateTimeField()
394 315
395 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
316 fav_tags = models.ManyToManyField('Tag', null=True, blank=True)
396 317 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
397 318 blank=True)
398 319
399 320 def save_setting(self, name, value):
400 321 setting, created = Setting.objects.get_or_create(name=name, user=self)
401 322 setting.value = str(value)
402 323 setting.save()
403 324
404 325 return setting
405 326
406 327 def get_setting(self, name):
407 328 if Setting.objects.filter(name=name, user=self).exists():
408 329 setting = Setting.objects.get(name=name, user=self)
409 330 setting_value = setting.value
410 331 else:
411 332 setting_value = None
412 333
413 334 return setting_value
414 335
415 336 def is_moderator(self):
416 337 return RANK_MODERATOR >= self.rank
417 338
418 339 def get_sorted_fav_tags(self):
419 340 cache_key = self._get_tag_cache_key()
420 341 fav_tags = cache.get(cache_key)
421 342 if fav_tags:
422 343 return fav_tags
423 344
424 345 tags = self.fav_tags.annotate(Count('threads')) \
425 346 .filter(threads__count__gt=0).order_by('name')
426 347
427 348 if tags:
428 349 cache.set(cache_key, tags, board_settings.CACHE_TIMEOUT)
429 350
430 351 return tags
431 352
432 353 def get_post_count(self):
433 354 return Post.objects.filter(user=self).count()
434 355
435 356 def __unicode__(self):
436 357 return self.user_id + '(' + str(self.rank) + ')'
437 358
438 359 def get_last_access_time(self):
439 360 posts = Post.objects.filter(user=self)
440 361 if posts.count() > 0:
441 362 return posts.latest('pub_time').pub_time
442 363
443 364 def add_tag(self, tag):
444 365 self.fav_tags.add(tag)
445 366 cache.delete(self._get_tag_cache_key())
446 367
447 368 def remove_tag(self, tag):
448 369 self.fav_tags.remove(tag)
449 370 cache.delete(self._get_tag_cache_key())
450 371
451 372 def _get_tag_cache_key(self):
452 373 return self.user_id + '_tags'
453 374
454 375
455 376 class Setting(models.Model):
456 377
457 378 class Meta:
458 379 app_label = 'boards'
459 380
460 381 name = models.CharField(max_length=50)
461 382 value = models.CharField(max_length=50)
462 383 user = models.ForeignKey(User)
463 384
464 385
465 386 class Ban(models.Model):
466 387
467 388 class Meta:
468 389 app_label = 'boards'
469 390
470 391 ip = models.GenericIPAddressField()
471 392 reason = models.CharField(default=BAN_REASON_AUTO,
472 393 max_length=BAN_REASON_MAX_LENGTH)
473 394 can_read = models.BooleanField(default=True)
474 395
475 396 def __unicode__(self):
476 397 return self.ip
@@ -1,559 +1,559 b''
1 1 import hashlib
2 2 import json
3 3 import string
4 4 import time
5 5 from datetime import datetime
6 6 import re
7 7
8 8 from django.core import serializers
9 9 from django.core.urlresolvers import reverse
10 10 from django.http import HttpResponseRedirect
11 11 from django.http.response import HttpResponse
12 12 from django.template import RequestContext
13 13 from django.shortcuts import render, redirect, get_object_or_404
14 14 from django.utils import timezone
15 15 from django.db import transaction
16 16
17 17 from boards import forms
18 18 import boards
19 19 from boards import utils
20 20 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
21 21 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
22 from boards.models.post import Post, Tag, Ban, User, RANK_USER, \
23 SETTING_MODERATE, REGEX_REPLY
22 from boards.models import Post, Tag, Ban, User
23 from boards.models.post import RANK_USER, SETTING_MODERATE, REGEX_REPLY
24 24 from boards import authors
25 25 from boards.utils import get_client_ip
26 26 import neboard
27 27
28 28
29 29 BAN_REASON_SPAM = 'Autoban: spam bot'
30 30
31 31
32 32 def index(request, page=0):
33 33 context = _init_default_context(request)
34 34
35 35 if utils.need_include_captcha(request):
36 36 threadFormClass = ThreadCaptchaForm
37 37 kwargs = {'request': request}
38 38 else:
39 39 threadFormClass = ThreadForm
40 40 kwargs = {}
41 41
42 42 if request.method == 'POST':
43 43 form = threadFormClass(request.POST, request.FILES,
44 44 error_class=PlainErrorList, **kwargs)
45 45 form.session = request.session
46 46
47 47 if form.is_valid():
48 48 return _new_post(request, form)
49 49 if form.need_to_ban:
50 50 # Ban user because he is suspected to be a bot
51 51 _ban_current_user(request)
52 52 else:
53 53 form = threadFormClass(error_class=PlainErrorList, **kwargs)
54 54
55 55 threads = []
56 56 for thread in Post.objects.get_threads(page=int(page)):
57 57 threads.append({
58 58 'thread': thread,
59 59 'bumpable': thread.can_bump(),
60 60 'last_replies': thread.get_last_replies(),
61 61 })
62 62
63 63 # TODO Make this generic for tag and threads list pages
64 64 context['threads'] = None if len(threads) == 0 else threads
65 65 context['form'] = form
66 66
67 67 page_count = Post.objects.get_thread_page_count()
68 68 context['pages'] = range(page_count)
69 69 page = int(page)
70 70 if page < page_count - 1:
71 71 context['next_page'] = str(page + 1)
72 72 if page > 0:
73 73 context['prev_page'] = str(page - 1)
74 74
75 75 return render(request, 'boards/posting_general.html',
76 76 context)
77 77
78 78
79 79 @transaction.commit_on_success
80 80 def _new_post(request, form, opening_post=None):
81 81 """Add a new post (in thread or as a reply)."""
82 82
83 83 ip = get_client_ip(request)
84 84 is_banned = Ban.objects.filter(ip=ip).exists()
85 85
86 86 if is_banned:
87 87 return redirect(you_are_banned)
88 88
89 89 data = form.cleaned_data
90 90
91 91 title = data['title']
92 92 text = data['text']
93 93
94 94 text = _remove_invalid_links(text)
95 95
96 96 if 'image' in data.keys():
97 97 image = data['image']
98 98 else:
99 99 image = None
100 100
101 101 tags = []
102 102
103 103 if not opening_post:
104 104 tag_strings = data['tags']
105 105
106 106 if tag_strings:
107 107 tag_strings = tag_strings.split(' ')
108 108 for tag_name in tag_strings:
109 109 tag_name = string.lower(tag_name.strip())
110 110 if len(tag_name) > 0:
111 111 tag, created = Tag.objects.get_or_create(name=tag_name)
112 112 tags.append(tag)
113 113
114 114 post = Post.objects.create_post(title=title, text=text, ip=ip,
115 115 thread=opening_post, image=image,
116 116 tags=tags, user=_get_user(request))
117 117
118 118 thread_to_show = (opening_post.id if opening_post else post.id)
119 119
120 120 if opening_post:
121 121 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
122 122 '#' + str(post.id))
123 123 else:
124 124 return redirect(thread, post_id=thread_to_show)
125 125
126 126
127 127 def tag(request, tag_name, page=0):
128 128 """
129 129 Get all tag threads. Threads are split in pages, so some page is
130 130 requested. Default page is 0.
131 131 """
132 132
133 133 tag = get_object_or_404(Tag, name=tag_name)
134 134 threads = []
135 135 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
136 136 threads.append({
137 137 'thread': thread,
138 138 'bumpable': thread.can_bump(),
139 139 'last_replies': thread.get_last_replies(),
140 140 })
141 141
142 142 if request.method == 'POST':
143 143 form = ThreadForm(request.POST, request.FILES,
144 144 error_class=PlainErrorList)
145 145 form.session = request.session
146 146
147 147 if form.is_valid():
148 148 return _new_post(request, form)
149 149 if form.need_to_ban:
150 150 # Ban user because he is suspected to be a bot
151 151 _ban_current_user(request)
152 152 else:
153 153 form = forms.ThreadForm(initial={'tags': tag_name},
154 154 error_class=PlainErrorList)
155 155
156 156 context = _init_default_context(request)
157 157 context['threads'] = None if len(threads) == 0 else threads
158 158 context['tag'] = tag
159 159
160 160 page_count = Post.objects.get_thread_page_count(tag=tag)
161 161 context['pages'] = range(page_count)
162 162 page = int(page)
163 163 if page < page_count - 1:
164 164 context['next_page'] = str(page + 1)
165 165 if page > 0:
166 166 context['prev_page'] = str(page - 1)
167 167
168 168 context['form'] = form
169 169
170 170 return render(request, 'boards/posting_general.html',
171 171 context)
172 172
173 173
174 174 def thread(request, post_id):
175 175 """Get all thread posts"""
176 176
177 177 if utils.need_include_captcha(request):
178 178 postFormClass = PostCaptchaForm
179 179 kwargs = {'request': request}
180 180 else:
181 181 postFormClass = PostForm
182 182 kwargs = {}
183 183
184 184 if request.method == 'POST':
185 185 form = postFormClass(request.POST, request.FILES,
186 186 error_class=PlainErrorList, **kwargs)
187 187 form.session = request.session
188 188
189 189 opening_post = get_object_or_404(Post, id=post_id)
190 190 if form.is_valid():
191 191 return _new_post(request, form, opening_post)
192 192 if form.need_to_ban:
193 193 # Ban user because he is suspected to be a bot
194 194 _ban_current_user(request)
195 195 else:
196 196 form = postFormClass(error_class=PlainErrorList, **kwargs)
197 197
198 198 posts = Post.objects.get_thread(post_id)
199 199
200 200 context = _init_default_context(request)
201 201
202 202 context['posts'] = posts
203 203 context['form'] = form
204 204 context['bumpable'] = posts[0].can_bump()
205 205 if context['bumpable']:
206 206 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
207 207 posts)
208 208 context['bumplimit_progress'] = str(
209 209 float(context['posts_left']) /
210 210 neboard.settings.MAX_POSTS_PER_THREAD * 100)
211 211 context["last_update"] = _datetime_to_epoch(posts[0].last_edit_time)
212 212
213 213 return render(request, 'boards/thread.html', context)
214 214
215 215
216 216 def login(request):
217 217 """Log in with user id"""
218 218
219 219 context = _init_default_context(request)
220 220
221 221 if request.method == 'POST':
222 222 form = LoginForm(request.POST, request.FILES,
223 223 error_class=PlainErrorList)
224 224 form.session = request.session
225 225
226 226 if form.is_valid():
227 227 user = User.objects.get(user_id=form.cleaned_data['user_id'])
228 228 request.session['user_id'] = user.id
229 229 return redirect(index)
230 230
231 231 else:
232 232 form = LoginForm()
233 233
234 234 context['form'] = form
235 235
236 236 return render(request, 'boards/login.html', context)
237 237
238 238
239 239 def settings(request):
240 240 """User's settings"""
241 241
242 242 context = _init_default_context(request)
243 243 user = _get_user(request)
244 244 is_moderator = user.is_moderator()
245 245
246 246 if request.method == 'POST':
247 247 with transaction.commit_on_success():
248 248 if is_moderator:
249 249 form = ModeratorSettingsForm(request.POST,
250 250 error_class=PlainErrorList)
251 251 else:
252 252 form = SettingsForm(request.POST, error_class=PlainErrorList)
253 253
254 254 if form.is_valid():
255 255 selected_theme = form.cleaned_data['theme']
256 256
257 257 user.save_setting('theme', selected_theme)
258 258
259 259 if is_moderator:
260 260 moderate = form.cleaned_data['moderate']
261 261 user.save_setting(SETTING_MODERATE, moderate)
262 262
263 263 return redirect(settings)
264 264 else:
265 265 selected_theme = _get_theme(request)
266 266
267 267 if is_moderator:
268 268 form = ModeratorSettingsForm(initial={'theme': selected_theme,
269 269 'moderate': context['moderator']},
270 270 error_class=PlainErrorList)
271 271 else:
272 272 form = SettingsForm(initial={'theme': selected_theme},
273 273 error_class=PlainErrorList)
274 274
275 275 context['form'] = form
276 276
277 277 return render(request, 'boards/settings.html', context)
278 278
279 279
280 280 def all_tags(request):
281 281 """All tags list"""
282 282
283 283 context = _init_default_context(request)
284 284 context['all_tags'] = Tag.objects.get_not_empty_tags()
285 285
286 286 return render(request, 'boards/tags.html', context)
287 287
288 288
289 289 def jump_to_post(request, post_id):
290 290 """Determine thread in which the requested post is and open it's page"""
291 291
292 292 post = get_object_or_404(Post, id=post_id)
293 293
294 294 if not post.thread:
295 295 return redirect(thread, post_id=post.id)
296 296 else:
297 297 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
298 298 + '#' + str(post.id))
299 299
300 300
301 301 def authors(request):
302 302 """Show authors list"""
303 303
304 304 context = _init_default_context(request)
305 305 context['authors'] = boards.authors.authors
306 306
307 307 return render(request, 'boards/authors.html', context)
308 308
309 309
310 310 @transaction.commit_on_success
311 311 def delete(request, post_id):
312 312 """Delete post"""
313 313
314 314 user = _get_user(request)
315 315 post = get_object_or_404(Post, id=post_id)
316 316
317 317 if user.is_moderator():
318 318 # TODO Show confirmation page before deletion
319 319 Post.objects.delete_post(post)
320 320
321 321 if not post.thread:
322 322 return _redirect_to_next(request)
323 323 else:
324 324 return redirect(thread, post_id=post.thread.id)
325 325
326 326
327 327 @transaction.commit_on_success
328 328 def ban(request, post_id):
329 329 """Ban user"""
330 330
331 331 user = _get_user(request)
332 332 post = get_object_or_404(Post, id=post_id)
333 333
334 334 if user.is_moderator():
335 335 # TODO Show confirmation page before ban
336 336 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
337 337 if created:
338 338 ban.reason = 'Banned for post ' + str(post_id)
339 339 ban.save()
340 340
341 341 return _redirect_to_next(request)
342 342
343 343
344 344 def you_are_banned(request):
345 345 """Show the page that notifies that user is banned"""
346 346
347 347 context = _init_default_context(request)
348 348
349 349 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
350 350 context['ban_reason'] = ban.reason
351 351 return render(request, 'boards/staticpages/banned.html', context)
352 352
353 353
354 354 def page_404(request):
355 355 """Show page 404 (not found error)"""
356 356
357 357 context = _init_default_context(request)
358 358 return render(request, 'boards/404.html', context)
359 359
360 360
361 361 @transaction.commit_on_success
362 362 def tag_subscribe(request, tag_name):
363 363 """Add tag to favorites"""
364 364
365 365 user = _get_user(request)
366 366 tag = get_object_or_404(Tag, name=tag_name)
367 367
368 368 if not tag in user.fav_tags.all():
369 369 user.add_tag(tag)
370 370
371 371 return _redirect_to_next(request)
372 372
373 373
374 374 @transaction.commit_on_success
375 375 def tag_unsubscribe(request, tag_name):
376 376 """Remove tag from favorites"""
377 377
378 378 user = _get_user(request)
379 379 tag = get_object_or_404(Tag, name=tag_name)
380 380
381 381 if tag in user.fav_tags.all():
382 382 user.remove_tag(tag)
383 383
384 384 return _redirect_to_next(request)
385 385
386 386
387 387 def static_page(request, name):
388 388 """Show a static page that needs only tags list and a CSS"""
389 389
390 390 context = _init_default_context(request)
391 391 return render(request, 'boards/staticpages/' + name + '.html', context)
392 392
393 393
394 394 def api_get_post(request, post_id):
395 395 """
396 396 Get the JSON of a post. This can be
397 397 used as and API for external clients.
398 398 """
399 399
400 400 post = get_object_or_404(Post, id=post_id)
401 401
402 402 json = serializers.serialize("json", [post], fields=(
403 403 "pub_time", "_text_rendered", "title", "text", "image",
404 404 "image_width", "image_height", "replies", "tags"
405 405 ))
406 406
407 407 return HttpResponse(content=json)
408 408
409 409
410 410 def api_get_threaddiff(request, thread_id, last_update_time):
411 411 """Get posts that were changed or added since time"""
412 412
413 413 thread = get_object_or_404(Post, id=thread_id)
414 414
415 415 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
416 416 timezone.get_current_timezone())
417 417
418 418 json_data = {
419 419 'added': [],
420 420 'updated': [],
421 421 'last_update': None,
422 422 }
423 423 added_posts = Post.objects.filter(thread=thread,
424 424 pub_time__gt=filter_time)\
425 425 .order_by('pub_time')
426 426 updated_posts = Post.objects.filter(thread=thread,
427 427 pub_time__lte=filter_time,
428 428 last_edit_time__gt=filter_time)
429 429 for post in added_posts:
430 430 json_data['added'].append(get_post(request, post.id).content.strip())
431 431 for post in updated_posts:
432 432 json_data['updated'].append(get_post(request, post.id).content.strip())
433 433 json_data['last_update'] = _datetime_to_epoch(thread.last_edit_time)
434 434
435 435 return HttpResponse(content=json.dumps(json_data))
436 436
437 437
438 438 def get_post(request, post_id):
439 439 """Get the html of a post. Used for popups."""
440 440
441 441 post = get_object_or_404(Post, id=post_id)
442 442 thread = post.thread
443 443 if not thread:
444 444 thread = post
445 445
446 446 context = RequestContext(request)
447 447 context["post"] = post
448 448 context["can_bump"] = thread.can_bump()
449 449 if "truncated" in request.GET:
450 450 context["truncated"] = True
451 451
452 452 return render(request, 'boards/post.html', context)
453 453
454 454
455 455 def _get_theme(request, user=None):
456 456 """Get user's CSS theme"""
457 457
458 458 if not user:
459 459 user = _get_user(request)
460 460 theme = user.get_setting('theme')
461 461 if not theme:
462 462 theme = neboard.settings.DEFAULT_THEME
463 463
464 464 return theme
465 465
466 466
467 467 def _init_default_context(request):
468 468 """Create context with default values that are used in most views"""
469 469
470 470 context = RequestContext(request)
471 471
472 472 user = _get_user(request)
473 473 context['user'] = user
474 474 context['tags'] = user.get_sorted_fav_tags()
475 475
476 476 theme = _get_theme(request, user)
477 477 context['theme'] = theme
478 478 context['theme_css'] = 'css/' + theme + '/base_page.css'
479 479
480 480 # This shows the moderator panel
481 481 moderate = user.get_setting(SETTING_MODERATE)
482 482 if moderate == 'True':
483 483 context['moderator'] = user.is_moderator()
484 484 else:
485 485 context['moderator'] = False
486 486
487 487 return context
488 488
489 489
490 490 def _get_user(request):
491 491 """
492 492 Get current user from the session. If the user does not exist, create
493 493 a new one.
494 494 """
495 495
496 496 session = request.session
497 497 if not 'user_id' in session:
498 498 request.session.save()
499 499
500 500 md5 = hashlib.md5()
501 501 md5.update(session.session_key)
502 502 new_id = md5.hexdigest()
503 503
504 504 time_now = timezone.now()
505 505 user = User.objects.create(user_id=new_id, rank=RANK_USER,
506 506 registration_time=time_now)
507 507
508 508 session['user_id'] = user.id
509 509 else:
510 510 user = User.objects.get(id=session['user_id'])
511 511
512 512 return user
513 513
514 514
515 515 def _redirect_to_next(request):
516 516 """
517 517 If a 'next' parameter was specified, redirect to the next page. This is
518 518 used when the user is required to return to some page after the current
519 519 view has finished its work.
520 520 """
521 521
522 522 if 'next' in request.GET:
523 523 next_page = request.GET['next']
524 524 return HttpResponseRedirect(next_page)
525 525 else:
526 526 return redirect(index)
527 527
528 528
529 529 @transaction.commit_on_success
530 530 def _ban_current_user(request):
531 531 """Add current user to the IP ban list"""
532 532
533 533 ip = utils.get_client_ip(request)
534 534 ban, created = Ban.objects.get_or_create(ip=ip)
535 535 if created:
536 536 ban.can_read = False
537 537 ban.reason = BAN_REASON_SPAM
538 538 ban.save()
539 539
540 540
541 541 def _remove_invalid_links(text):
542 542 """
543 543 Replace invalid links in posts so that they won't be parsed.
544 544 Invalid links are links to non-existent posts
545 545 """
546 546
547 547 for reply_number in re.finditer(REGEX_REPLY, text):
548 548 post_id = reply_number.group(1)
549 549 post = Post.objects.filter(id=post_id)
550 550 if not post.exists():
551 551 text = string.replace(text, '>>' + id, id)
552 552
553 553 return text
554 554
555 555
556 556 def _datetime_to_epoch(datetime):
557 557 return int(time.mktime(timezone.localtime(
558 558 datetime,timezone.get_current_timezone()).timetuple())
559 559 * 1000000 + datetime.microsecond)
General Comments 0
You need to be logged in to leave comments. Login now