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