##// END OF EJS Templates
Fixed thread diff
neko259 -
r403:f86393d8 default
parent child Browse files
Show More
@@ -1,567 +1,565 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 22 from boards.models import Post, Tag, Ban, User
23 23 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
24 24 from boards.models.user import RANK_USER
25 25 from boards import authors
26 26 from boards.utils import get_client_ip
27 27 import neboard
28 28
29 29
30 30 BAN_REASON_SPAM = 'Autoban: spam bot'
31 31
32 32
33 33 def index(request, page=0):
34 34 context = _init_default_context(request)
35 35
36 36 if utils.need_include_captcha(request):
37 37 threadFormClass = ThreadCaptchaForm
38 38 kwargs = {'request': request}
39 39 else:
40 40 threadFormClass = ThreadForm
41 41 kwargs = {}
42 42
43 43 if request.method == 'POST':
44 44 form = threadFormClass(request.POST, request.FILES,
45 45 error_class=PlainErrorList, **kwargs)
46 46 form.session = request.session
47 47
48 48 if form.is_valid():
49 49 return _new_post(request, form)
50 50 if form.need_to_ban:
51 51 # Ban user because he is suspected to be a bot
52 52 _ban_current_user(request)
53 53 else:
54 54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
55 55
56 56 threads = []
57 57 for thread_to_show in Post.objects.get_threads(page=int(page)):
58 58 threads.append({
59 59 'thread': thread_to_show,
60 60 'op': thread_to_show.get_replies()[0],
61 61 'bumpable': thread_to_show.can_bump(),
62 62 'last_replies': thread_to_show.get_last_replies(),
63 63 })
64 64
65 65 # TODO Make this generic for tag and threads list pages
66 66 context['threads'] = None if len(threads) == 0 else threads
67 67 context['form'] = form
68 68
69 69 page_count = Post.objects.get_thread_page_count()
70 70 context['pages'] = range(page_count)
71 71 page = int(page)
72 72 if page < page_count - 1:
73 73 context['next_page'] = str(page + 1)
74 74 if page > 0:
75 75 context['prev_page'] = str(page - 1)
76 76
77 77 return render(request, 'boards/posting_general.html',
78 78 context)
79 79
80 80
81 81 @transaction.atomic
82 82 def _new_post(request, form, opening_post=None):
83 83 """Add a new post (in thread or as a reply)."""
84 84
85 85 ip = get_client_ip(request)
86 86 is_banned = Ban.objects.filter(ip=ip).exists()
87 87
88 88 if is_banned:
89 89 return redirect(you_are_banned)
90 90
91 91 data = form.cleaned_data
92 92
93 93 title = data['title']
94 94 text = data['text']
95 95
96 96 text = _remove_invalid_links(text)
97 97
98 98 if 'image' in data.keys():
99 99 image = data['image']
100 100 else:
101 101 image = None
102 102
103 103 tags = []
104 104
105 105 if not opening_post:
106 106 tag_strings = data['tags']
107 107
108 108 if tag_strings:
109 109 tag_strings = tag_strings.split(' ')
110 110 for tag_name in tag_strings:
111 111 tag_name = string.lower(tag_name.strip())
112 112 if len(tag_name) > 0:
113 113 tag, created = Tag.objects.get_or_create(name=tag_name)
114 114 tags.append(tag)
115 115 post_thread = None
116 116 else:
117 117 post_thread = opening_post.thread_new
118 118
119 119 post = Post.objects.create_post(title=title, text=text, ip=ip,
120 120 thread=post_thread, image=image,
121 121 tags=tags, user=_get_user(request))
122 122
123 123 thread_to_show = (opening_post.id if opening_post else post.id)
124 124
125 125 if opening_post:
126 126 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
127 127 '#' + str(post.id))
128 128 else:
129 129 return redirect(thread, post_id=thread_to_show)
130 130
131 131
132 132 def tag(request, tag_name, page=0):
133 133 """
134 134 Get all tag threads. Threads are split in pages, so some page is
135 135 requested. Default page is 0.
136 136 """
137 137
138 138 tag = get_object_or_404(Tag, name=tag_name)
139 139 threads = []
140 140 for thread_to_show in Post.objects.get_threads(page=int(page), tag=tag):
141 141 threads.append({
142 142 'thread': thread_to_show,
143 143 'op': thread_to_show.get_replies()[0],
144 144 'bumpable': thread_to_show.can_bump(),
145 145 'last_replies': thread_to_show.get_last_replies(),
146 146 })
147 147
148 148 if request.method == 'POST':
149 149 form = ThreadForm(request.POST, request.FILES,
150 150 error_class=PlainErrorList)
151 151 form.session = request.session
152 152
153 153 if form.is_valid():
154 154 return _new_post(request, form)
155 155 if form.need_to_ban:
156 156 # Ban user because he is suspected to be a bot
157 157 _ban_current_user(request)
158 158 else:
159 159 form = forms.ThreadForm(initial={'tags': tag_name},
160 160 error_class=PlainErrorList)
161 161
162 162 context = _init_default_context(request)
163 163 context['threads'] = None if len(threads) == 0 else threads
164 164 context['tag'] = tag
165 165
166 166 page_count = Post.objects.get_thread_page_count(tag=tag)
167 167 context['pages'] = range(page_count)
168 168 page = int(page)
169 169 if page < page_count - 1:
170 170 context['next_page'] = str(page + 1)
171 171 if page > 0:
172 172 context['prev_page'] = str(page - 1)
173 173
174 174 context['form'] = form
175 175
176 176 return render(request, 'boards/posting_general.html',
177 177 context)
178 178
179 179
180 180 def thread(request, post_id):
181 181 """Get all thread posts"""
182 182
183 183 if utils.need_include_captcha(request):
184 184 postFormClass = PostCaptchaForm
185 185 kwargs = {'request': request}
186 186 else:
187 187 postFormClass = PostForm
188 188 kwargs = {}
189 189
190 190 if request.method == 'POST':
191 191 form = postFormClass(request.POST, request.FILES,
192 192 error_class=PlainErrorList, **kwargs)
193 193 form.session = request.session
194 194
195 195 opening_post = get_object_or_404(Post, id=post_id)
196 196 if form.is_valid():
197 197 return _new_post(request, form, opening_post)
198 198 if form.need_to_ban:
199 199 # Ban user because he is suspected to be a bot
200 200 _ban_current_user(request)
201 201 else:
202 202 form = postFormClass(error_class=PlainErrorList, **kwargs)
203 203
204 204 thread_to_show = get_object_or_404(Post, id=post_id).thread_new
205 205
206 206 context = _init_default_context(request)
207 207
208 208 posts = thread_to_show.get_replies()
209 209 context['form'] = form
210 210 context['bumpable'] = thread_to_show.can_bump()
211 211 if context['bumpable']:
212 212 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts\
213 213 .count()
214 214 context['bumplimit_progress'] = str(
215 215 float(context['posts_left']) /
216 216 neboard.settings.MAX_POSTS_PER_THREAD * 100)
217 217 context["last_update"] = _datetime_to_epoch(thread_to_show.last_edit_time)
218 218 context["thread"] = thread_to_show
219 219
220 220 return render(request, 'boards/thread.html', context)
221 221
222 222
223 223 def login(request):
224 224 """Log in with user id"""
225 225
226 226 context = _init_default_context(request)
227 227
228 228 if request.method == 'POST':
229 229 form = LoginForm(request.POST, request.FILES,
230 230 error_class=PlainErrorList)
231 231 form.session = request.session
232 232
233 233 if form.is_valid():
234 234 user = User.objects.get(user_id=form.cleaned_data['user_id'])
235 235 request.session['user_id'] = user.id
236 236 return redirect(index)
237 237
238 238 else:
239 239 form = LoginForm()
240 240
241 241 context['form'] = form
242 242
243 243 return render(request, 'boards/login.html', context)
244 244
245 245
246 246 def settings(request):
247 247 """User's settings"""
248 248
249 249 context = _init_default_context(request)
250 250 user = _get_user(request)
251 251 is_moderator = user.is_moderator()
252 252
253 253 if request.method == 'POST':
254 254 with transaction.commit_on_success():
255 255 if is_moderator:
256 256 form = ModeratorSettingsForm(request.POST,
257 257 error_class=PlainErrorList)
258 258 else:
259 259 form = SettingsForm(request.POST, error_class=PlainErrorList)
260 260
261 261 if form.is_valid():
262 262 selected_theme = form.cleaned_data['theme']
263 263
264 264 user.save_setting('theme', selected_theme)
265 265
266 266 if is_moderator:
267 267 moderate = form.cleaned_data['moderate']
268 268 user.save_setting(SETTING_MODERATE, moderate)
269 269
270 270 return redirect(settings)
271 271 else:
272 272 selected_theme = _get_theme(request)
273 273
274 274 if is_moderator:
275 275 form = ModeratorSettingsForm(initial={'theme': selected_theme,
276 276 'moderate': context['moderator']},
277 277 error_class=PlainErrorList)
278 278 else:
279 279 form = SettingsForm(initial={'theme': selected_theme},
280 280 error_class=PlainErrorList)
281 281
282 282 context['form'] = form
283 283
284 284 return render(request, 'boards/settings.html', context)
285 285
286 286
287 287 def all_tags(request):
288 288 """All tags list"""
289 289
290 290 context = _init_default_context(request)
291 291 context['all_tags'] = Tag.objects.get_not_empty_tags()
292 292
293 293 return render(request, 'boards/tags.html', context)
294 294
295 295
296 296 def jump_to_post(request, post_id):
297 297 """Determine thread in which the requested post is and open it's page"""
298 298
299 299 post = get_object_or_404(Post, id=post_id)
300 300
301 301 if not post.thread:
302 302 return redirect(thread, post_id=post.id)
303 303 else:
304 304 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
305 305 + '#' + str(post.id))
306 306
307 307
308 308 def authors(request):
309 309 """Show authors list"""
310 310
311 311 context = _init_default_context(request)
312 312 context['authors'] = boards.authors.authors
313 313
314 314 return render(request, 'boards/authors.html', context)
315 315
316 316
317 317 @transaction.atomic
318 318 def delete(request, post_id):
319 319 """Delete post"""
320 320
321 321 user = _get_user(request)
322 322 post = get_object_or_404(Post, id=post_id)
323 323
324 324 if user.is_moderator():
325 325 # TODO Show confirmation page before deletion
326 326 Post.objects.delete_post(post)
327 327
328 328 if not post.thread:
329 329 return _redirect_to_next(request)
330 330 else:
331 331 return redirect(thread, post_id=post.thread.id)
332 332
333 333
334 334 @transaction.atomic
335 335 def ban(request, post_id):
336 336 """Ban user"""
337 337
338 338 user = _get_user(request)
339 339 post = get_object_or_404(Post, id=post_id)
340 340
341 341 if user.is_moderator():
342 342 # TODO Show confirmation page before ban
343 343 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
344 344 if created:
345 345 ban.reason = 'Banned for post ' + str(post_id)
346 346 ban.save()
347 347
348 348 return _redirect_to_next(request)
349 349
350 350
351 351 def you_are_banned(request):
352 352 """Show the page that notifies that user is banned"""
353 353
354 354 context = _init_default_context(request)
355 355
356 356 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
357 357 context['ban_reason'] = ban.reason
358 358 return render(request, 'boards/staticpages/banned.html', context)
359 359
360 360
361 361 def page_404(request):
362 362 """Show page 404 (not found error)"""
363 363
364 364 context = _init_default_context(request)
365 365 return render(request, 'boards/404.html', context)
366 366
367 367
368 368 @transaction.atomic
369 369 def tag_subscribe(request, tag_name):
370 370 """Add tag to favorites"""
371 371
372 372 user = _get_user(request)
373 373 tag = get_object_or_404(Tag, name=tag_name)
374 374
375 375 if not tag in user.fav_tags.all():
376 376 user.add_tag(tag)
377 377
378 378 return _redirect_to_next(request)
379 379
380 380
381 381 @transaction.atomic
382 382 def tag_unsubscribe(request, tag_name):
383 383 """Remove tag from favorites"""
384 384
385 385 user = _get_user(request)
386 386 tag = get_object_or_404(Tag, name=tag_name)
387 387
388 388 if tag in user.fav_tags.all():
389 389 user.remove_tag(tag)
390 390
391 391 return _redirect_to_next(request)
392 392
393 393
394 394 def static_page(request, name):
395 395 """Show a static page that needs only tags list and a CSS"""
396 396
397 397 context = _init_default_context(request)
398 398 return render(request, 'boards/staticpages/' + name + '.html', context)
399 399
400 400
401 401 def api_get_post(request, post_id):
402 402 """
403 403 Get the JSON of a post. This can be
404 404 used as and API for external clients.
405 405 """
406 406
407 407 post = get_object_or_404(Post, id=post_id)
408 408
409 409 json = serializers.serialize("json", [post], fields=(
410 410 "pub_time", "_text_rendered", "title", "text", "image",
411 411 "image_width", "image_height", "replies", "tags"
412 412 ))
413 413
414 414 return HttpResponse(content=json)
415 415
416 416
417 417 @transaction.atomic
418 418 def api_get_threaddiff(request, thread_id, last_update_time):
419 419 """Get posts that were changed or added since time"""
420 420
421 421 thread = get_object_or_404(Post, id=thread_id).thread_new
422 422
423 423 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
424 424 timezone.get_current_timezone())
425 425
426 426 json_data = {
427 427 'added': [],
428 428 'updated': [],
429 429 'last_update': None,
430 430 }
431 431 added_posts = Post.objects.filter(thread_new=thread,
432 432 pub_time__gt=filter_time)\
433 433 .order_by('pub_time')
434 434 updated_posts = Post.objects.filter(thread_new=thread,
435 435 pub_time__lte=filter_time,
436 436 last_edit_time__gt=filter_time)
437 437 for post in added_posts:
438 438 json_data['added'].append(get_post(request, post.id).content.strip())
439 439 for post in updated_posts:
440 440 json_data['updated'].append(get_post(request, post.id).content.strip())
441 441 json_data['last_update'] = _datetime_to_epoch(thread.last_edit_time)
442 442
443 443 return HttpResponse(content=json.dumps(json_data))
444 444
445 445
446 446 def get_post(request, post_id):
447 447 """Get the html of a post. Used for popups."""
448 448
449 449 post = get_object_or_404(Post, id=post_id)
450 thread = post.thread
451 if not thread:
452 thread = post
450 thread = post.thread_new
453 451
454 452 context = RequestContext(request)
455 453 context["post"] = post
456 454 context["can_bump"] = thread.can_bump()
457 455 if "truncated" in request.GET:
458 456 context["truncated"] = True
459 457
460 458 return render(request, 'boards/post.html', context)
461 459
462 460
463 461 def _get_theme(request, user=None):
464 462 """Get user's CSS theme"""
465 463
466 464 if not user:
467 465 user = _get_user(request)
468 466 theme = user.get_setting('theme')
469 467 if not theme:
470 468 theme = neboard.settings.DEFAULT_THEME
471 469
472 470 return theme
473 471
474 472
475 473 def _init_default_context(request):
476 474 """Create context with default values that are used in most views"""
477 475
478 476 context = RequestContext(request)
479 477
480 478 user = _get_user(request)
481 479 context['user'] = user
482 480 context['tags'] = user.get_sorted_fav_tags()
483 481
484 482 theme = _get_theme(request, user)
485 483 context['theme'] = theme
486 484 context['theme_css'] = 'css/' + theme + '/base_page.css'
487 485
488 486 # This shows the moderator panel
489 487 moderate = user.get_setting(SETTING_MODERATE)
490 488 if moderate == 'True':
491 489 context['moderator'] = user.is_moderator()
492 490 else:
493 491 context['moderator'] = False
494 492
495 493 return context
496 494
497 495
498 496 def _get_user(request):
499 497 """
500 498 Get current user from the session. If the user does not exist, create
501 499 a new one.
502 500 """
503 501
504 502 session = request.session
505 503 if not 'user_id' in session:
506 504 request.session.save()
507 505
508 506 md5 = hashlib.md5()
509 507 md5.update(session.session_key)
510 508 new_id = md5.hexdigest()
511 509
512 510 time_now = timezone.now()
513 511 user = User.objects.create(user_id=new_id, rank=RANK_USER,
514 512 registration_time=time_now)
515 513
516 514 session['user_id'] = user.id
517 515 else:
518 516 user = User.objects.get(id=session['user_id'])
519 517
520 518 return user
521 519
522 520
523 521 def _redirect_to_next(request):
524 522 """
525 523 If a 'next' parameter was specified, redirect to the next page. This is
526 524 used when the user is required to return to some page after the current
527 525 view has finished its work.
528 526 """
529 527
530 528 if 'next' in request.GET:
531 529 next_page = request.GET['next']
532 530 return HttpResponseRedirect(next_page)
533 531 else:
534 532 return redirect(index)
535 533
536 534
537 535 @transaction.atomic
538 536 def _ban_current_user(request):
539 537 """Add current user to the IP ban list"""
540 538
541 539 ip = utils.get_client_ip(request)
542 540 ban, created = Ban.objects.get_or_create(ip=ip)
543 541 if created:
544 542 ban.can_read = False
545 543 ban.reason = BAN_REASON_SPAM
546 544 ban.save()
547 545
548 546
549 547 def _remove_invalid_links(text):
550 548 """
551 549 Replace invalid links in posts so that they won't be parsed.
552 550 Invalid links are links to non-existent posts
553 551 """
554 552
555 553 for reply_number in re.finditer(REGEX_REPLY, text):
556 554 post_id = reply_number.group(1)
557 555 post = Post.objects.filter(id=post_id)
558 556 if not post.exists():
559 557 text = string.replace(text, '>>' + post_id, post_id)
560 558
561 559 return text
562 560
563 561
564 562 def _datetime_to_epoch(datetime):
565 563 return int(time.mktime(timezone.localtime(
566 564 datetime,timezone.get_current_timezone()).timetuple())
567 565 * 1000000 + datetime.microsecond)
General Comments 0
You need to be logged in to leave comments. Login now