views.py
563 lines
| 16.0 KiB
| text/x-python
|
PythonLexer
/ boards / views.py
neko259
|
r110 | import hashlib | |
neko259
|
r361 | import json | |
neko259
|
r199 | import string | |
neko259
|
r363 | import time | |
neko259
|
r370 | import calendar | |
neko259
|
r363 | ||
from datetime import datetime | |||
neko259
|
r221 | from django.core import serializers | |
neko259
|
r41 | from django.core.urlresolvers import reverse | |
neko259
|
r155 | from django.http import HttpResponseRedirect | |
neko259
|
r221 | from django.http.response import HttpResponse | |
Ilyas
|
r9 | from django.template import RequestContext | |
neko259
|
r79 | from django.shortcuts import render, redirect, get_object_or_404 | |
neko259
|
r121 | from django.utils import timezone | |
neko259
|
r335 | from django.db import transaction | |
neko259
|
r373 | import math | |
neko259
|
r76 | ||
Ilyas
|
r14 | from boards import forms | |
neko259
|
r20 | import boards | |
Ilyas
|
r78 | from boards import utils | |
from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \ | |||
neko259
|
r205 | ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm | |
Ilyas
|
r78 | ||
neko259
|
r312 | from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \ | |
REGEX_REPLY | |||
neko259
|
r103 | from boards import authors | |
neko259
|
r210 | from boards.utils import get_client_ip | |
neko259
|
r35 | import neboard | |
neko259
|
r312 | import re | |
neko259
|
r0 | ||
neko259
|
r340 | BAN_REASON_SPAM = 'Autoban: spam bot' | |
neko259
|
r22 | ||
neko259
|
r46 | def index(request, page=0): | |
neko259
|
r101 | context = _init_default_context(request) | |
Ilyas
|
r14 | ||
wnc_21
|
r95 | if utils.need_include_captcha(request): | |
threadFormClass = ThreadCaptchaForm | |||
kwargs = {'request': request} | |||
else: | |||
threadFormClass = ThreadForm | |||
kwargs = {} | |||
Ilyas
|
r78 | ||
Ilyas
|
r14 | if request.method == 'POST': | |
Ilyas
|
r78 | form = threadFormClass(request.POST, request.FILES, | |
wnc_21
|
r95 | error_class=PlainErrorList, **kwargs) | |
neko259
|
r153 | form.session = request.session | |
Ilyas
|
r78 | ||
neko259
|
r76 | if form.is_valid(): | |
return _new_post(request, form) | |||
neko259
|
r271 | if form.need_to_ban: | |
# Ban user because he is suspected to be a bot | |||
_ban_current_user(request) | |||
neko259
|
r20 | else: | |
wnc_21
|
r95 | form = threadFormClass(error_class=PlainErrorList, **kwargs) | |
neko259
|
r76 | ||
neko259
|
r163 | threads = [] | |
for thread in Post.objects.get_threads(page=int(page)): | |||
neko259
|
r318 | threads.append({ | |
'thread': thread, | |||
'bumpable': thread.can_bump(), | |||
'last_replies': thread.get_last_replies(), | |||
}) | |||
Ilyas
|
r18 | ||
neko259
|
r343 | # TODO Make this generic for tag and threads list pages | |
neko259
|
r76 | context['threads'] = None if len(threads) == 0 else threads | |
context['form'] = form | |||
neko259
|
r343 | ||
page_count = Post.objects.get_thread_page_count() | |||
context['pages'] = range(page_count) | |||
page = int(page) | |||
if page < page_count - 1: | |||
context['next_page'] = str(page + 1) | |||
if page > 0: | |||
context['prev_page'] = str(page - 1) | |||
Ilyas
|
r9 | ||
Ilyas
|
r84 | return render(request, 'boards/posting_general.html', | |
neko259
|
r76 | context) | |
Ilyas
|
r18 | ||
Ilyas
|
r14 | ||
neko259
|
r335 | @transaction.commit_on_success | |
neko259
|
r76 | def _new_post(request, form, thread_id=boards.models.NO_PARENT): | |
neko259
|
r4 | """Add a new post (in thread or as a reply).""" | |
neko259
|
r210 | ip = get_client_ip(request) | |
is_banned = Ban.objects.filter(ip=ip).exists() | |||
neko259
|
r116 | ||
if is_banned: | |||
return redirect(you_are_banned) | |||
neko259
|
r76 | data = form.cleaned_data | |
neko259
|
r22 | ||
neko259
|
r29 | title = data['title'] | |
text = data['text'] | |||
neko259
|
r0 | ||
neko259
|
r312 | text = _remove_invalid_links(text) | |
neko259
|
r29 | if 'image' in data.keys(): | |
image = data['image'] | |||
neko259
|
r22 | else: | |
image = None | |||
neko259
|
r24 | tags = [] | |
neko259
|
r41 | ||
new_thread = thread_id == boards.models.NO_PARENT | |||
if new_thread: | |||
neko259
|
r29 | tag_strings = data['tags'] | |
neko259
|
r0 | ||
neko259
|
r24 | if tag_strings: | |
neko259
|
r35 | tag_strings = tag_strings.split(' ') | |
neko259
|
r24 | for tag_name in tag_strings: | |
neko259
|
r199 | tag_name = string.lower(tag_name.strip()) | |
neko259
|
r24 | if len(tag_name) > 0: | |
tag, created = Tag.objects.get_or_create(name=tag_name) | |||
tags.append(tag) | |||
neko259
|
r6 | ||
neko259
|
r319 | linked_tags = tag.get_linked_tags() | |
neko259
|
r279 | if len(linked_tags) > 0: | |
tags.extend(linked_tags) | |||
neko259
|
r174 | op = None if thread_id == boards.models.NO_PARENT else \ | |
get_object_or_404(Post, id=thread_id) | |||
neko259
|
r22 | post = Post.objects.create_post(title=title, text=text, ip=ip, | |
neko259
|
r174 | thread=op, image=image, | |
neko259
|
r185 | tags=tags, user=_get_user(request)) | |
neko259
|
r4 | ||
neko259
|
r41 | thread_to_show = (post.id if new_thread else thread_id) | |
neko259
|
r5 | ||
neko259
|
r41 | if new_thread: | |
return redirect(thread, post_id=thread_to_show) | |||
else: | |||
neko259
|
r174 | return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) + | |
'#' + str(post.id)) | |||
neko259
|
r22 | ||
neko259
|
r5 | ||
neko259
|
r46 | def tag(request, tag_name, page=0): | |
neko259
|
r271 | """ | |
Get all tag threads. Threads are split in pages, so some page is | |||
requested. Default page is 0. | |||
""" | |||
neko259
|
r4 | ||
neko259
|
r79 | tag = get_object_or_404(Tag, name=tag_name) | |
neko259
|
r164 | threads = [] | |
for thread in Post.objects.get_threads(tag=tag, page=int(page)): | |||
neko259
|
r318 | threads.append({ | |
'thread': thread, | |||
'bumpable': thread.can_bump(), | |||
'last_replies': thread.get_last_replies(), | |||
}) | |||
neko259
|
r5 | ||
neko259
|
r24 | if request.method == 'POST': | |
neko259
|
r76 | form = ThreadForm(request.POST, request.FILES, | |
error_class=PlainErrorList) | |||
Pavel Ryapolov
|
r278 | form.session = request.session | |
neko259
|
r76 | if form.is_valid(): | |
return _new_post(request, form) | |||
neko259
|
r271 | if form.need_to_ban: | |
# Ban user because he is suspected to be a bot | |||
_ban_current_user(request) | |||
neko259
|
r24 | else: | |
neko259
|
r76 | form = forms.ThreadForm(initial={'tags': tag_name}, | |
error_class=PlainErrorList) | |||
neko259
|
r12 | ||
neko259
|
r101 | context = _init_default_context(request) | |
neko259
|
r76 | context['threads'] = None if len(threads) == 0 else threads | |
neko259
|
r179 | context['tag'] = tag | |
neko259
|
r343 | ||
page_count = Post.objects.get_thread_page_count(tag=tag) | |||
context['pages'] = range(page_count) | |||
page = int(page) | |||
if page < page_count - 1: | |||
context['next_page'] = str(page + 1) | |||
if page > 0: | |||
context['prev_page'] = str(page - 1) | |||
neko259
|
r4 | ||
neko259
|
r76 | context['form'] = form | |
Ilyas
|
r84 | return render(request, 'boards/posting_general.html', | |
neko259
|
r76 | context) | |
neko259
|
r22 | ||
neko259
|
r4 | ||
neko259
|
r24 | def thread(request, post_id): | |
neko259
|
r5 | """Get all thread posts""" | |
wnc_21
|
r95 | if utils.need_include_captcha(request): | |
postFormClass = PostCaptchaForm | |||
kwargs = {'request': request} | |||
else: | |||
postFormClass = PostForm | |||
kwargs = {} | |||
Ilyas
|
r78 | ||
neko259
|
r20 | if request.method == 'POST': | |
Ilyas
|
r78 | form = postFormClass(request.POST, request.FILES, | |
wnc_21
|
r95 | error_class=PlainErrorList, **kwargs) | |
neko259
|
r153 | form.session = request.session | |
neko259
|
r76 | if form.is_valid(): | |
return _new_post(request, form, post_id) | |||
neko259
|
r271 | if form.need_to_ban: | |
# Ban user because he is suspected to be a bot | |||
_ban_current_user(request) | |||
neko259
|
r20 | else: | |
wnc_21
|
r95 | form = postFormClass(error_class=PlainErrorList, **kwargs) | |
neko259
|
r12 | ||
neko259
|
r76 | posts = Post.objects.get_thread(post_id) | |
neko259
|
r5 | ||
neko259
|
r101 | context = _init_default_context(request) | |
neko259
|
r12 | ||
neko259
|
r76 | context['posts'] = posts | |
context['form'] = form | |||
neko259
|
r161 | context['bumpable'] = posts[0].can_bump() | |
neko259
|
r281 | if context['bumpable']: | |
context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len( | |||
posts) | |||
neko259
|
r319 | context['bumplimit_progress'] = str( | |
float(context['posts_left']) / | |||
neboard.settings.MAX_POSTS_PER_THREAD * 100) | |||
neko259
|
r370 | context["last_update"] = _datetime_to_epoch(posts[0].last_edit_time) | |
neko259
|
r76 | ||
Ilyas
|
r84 | return render(request, 'boards/thread.html', context) | |
Ilyas
|
r9 | ||
def login(request): | |||
neko259
|
r144 | """Log in with user id""" | |
Ilyas
|
r9 | ||
neko259
|
r144 | context = _init_default_context(request) | |
Ilyas
|
r13 | ||
neko259
|
r144 | if request.method == 'POST': | |
neko259
|
r147 | form = LoginForm(request.POST, request.FILES, | |
error_class=PlainErrorList) | |||
neko259
|
r211 | form.session = request.session | |
neko259
|
r144 | if form.is_valid(): | |
user = User.objects.get(user_id=form.cleaned_data['user_id']) | |||
request.session['user_id'] = user.id | |||
return redirect(index) | |||
Ilyas
|
r9 | ||
neko259
|
r144 | else: | |
form = LoginForm() | |||
neko259
|
r22 | ||
neko259
|
r144 | context['form'] = form | |
return render(request, 'boards/login.html', context) | |||
neko259
|
r35 | ||
def settings(request): | |||
neko259
|
r101 | """User's settings""" | |
neko259
|
r110 | context = _init_default_context(request) | |
neko259
|
r205 | user = _get_user(request) | |
is_moderator = user.is_moderator() | |||
neko259
|
r35 | ||
if request.method == 'POST': | |||
neko259
|
r370 | with transaction.commit_on_success(): | |
if is_moderator: | |||
form = ModeratorSettingsForm(request.POST, | |||
error_class=PlainErrorList) | |||
else: | |||
form = SettingsForm(request.POST, error_class=PlainErrorList) | |||
neko259
|
r205 | ||
neko259
|
r370 | if form.is_valid(): | |
selected_theme = form.cleaned_data['theme'] | |||
neko259
|
r110 | ||
neko259
|
r370 | user.save_setting('theme', selected_theme) | |
neko259
|
r35 | ||
neko259
|
r370 | if is_moderator: | |
moderate = form.cleaned_data['moderate'] | |||
user.save_setting(SETTING_MODERATE, moderate) | |||
neko259
|
r205 | ||
neko259
|
r370 | return redirect(settings) | |
neko259
|
r35 | else: | |
selected_theme = _get_theme(request) | |||
neko259
|
r205 | if is_moderator: | |
form = ModeratorSettingsForm(initial={'theme': selected_theme, | |||
'moderate': context['moderator']}, | |||
neko259
|
r319 | error_class=PlainErrorList) | |
neko259
|
r205 | else: | |
form = SettingsForm(initial={'theme': selected_theme}, | |||
error_class=PlainErrorList) | |||
context['form'] = form | |||
return render(request, 'boards/settings.html', context) | |||
neko259
|
r35 | ||
neko259
|
r57 | def all_tags(request): | |
neko259
|
r101 | """All tags list""" | |
context = _init_default_context(request) | |||
neko259
|
r57 | context['all_tags'] = Tag.objects.get_not_empty_tags() | |
Ilyas
|
r84 | return render(request, 'boards/tags.html', context) | |
neko259
|
r57 | ||
neko259
|
r98 | def jump_to_post(request, post_id): | |
neko259
|
r101 | """Determine thread in which the requested post is and open it's page""" | |
neko259
|
r98 | post = get_object_or_404(Post, id=post_id) | |
neko259
|
r174 | if not post.thread: | |
neko259
|
r319 | return redirect(thread, post_id=post.id) | |
neko259
|
r98 | else: | |
neko259
|
r174 | return redirect(reverse(thread, kwargs={'post_id': post.thread.id}) | |
neko259
|
r98 | + '#' + str(post.id)) | |
neko259
|
r103 | def authors(request): | |
neko259
|
r221 | """Show authors list""" | |
neko259
|
r103 | context = _init_default_context(request) | |
context['authors'] = boards.authors.authors | |||
return render(request, 'boards/authors.html', context) | |||
neko259
|
r335 | @transaction.commit_on_success | |
neko259
|
r112 | def delete(request, post_id): | |
neko259
|
r221 | """Delete post""" | |
neko259
|
r112 | user = _get_user(request) | |
post = get_object_or_404(Post, id=post_id) | |||
if user.is_moderator(): | |||
neko259
|
r147 | # TODO Show confirmation page before deletion | |
neko259
|
r112 | Post.objects.delete_post(post) | |
neko259
|
r370 | ||
neko259
|
r174 | if not post.thread: | |
neko259
|
r155 | return _redirect_to_next(request) | |
neko259
|
r112 | else: | |
neko259
|
r174 | return redirect(thread, post_id=post.thread.id) | |
neko259
|
r112 | ||
neko259
|
r145 | ||
neko259
|
r335 | @transaction.commit_on_success | |
neko259
|
r156 | def ban(request, post_id): | |
neko259
|
r221 | """Ban user""" | |
neko259
|
r156 | user = _get_user(request) | |
post = get_object_or_404(Post, id=post_id) | |||
if user.is_moderator(): | |||
# TODO Show confirmation page before ban | |||
neko259
|
r340 | ban, created = Ban.objects.get_or_create(ip=post.poster_ip) | |
if created: | |||
ban.reason = 'Banned for post ' + str(post_id) | |||
ban.save() | |||
neko259
|
r156 | ||
return _redirect_to_next(request) | |||
neko259
|
r116 | def you_are_banned(request): | |
neko259
|
r221 | """Show the page that notifies that user is banned""" | |
neko259
|
r116 | context = _init_default_context(request) | |
neko259
|
r340 | ||
ban = get_object_or_404(Ban, ip=utils.get_client_ip(request)) | |||
context['ban_reason'] = ban.reason | |||
neko259
|
r152 | return render(request, 'boards/staticpages/banned.html', context) | |
neko259
|
r116 | ||
neko259
|
r130 | def page_404(request): | |
neko259
|
r221 | """Show page 404 (not found error)""" | |
neko259
|
r130 | context = _init_default_context(request) | |
return render(request, 'boards/404.html', context) | |||
neko259
|
r335 | @transaction.commit_on_success | |
neko259
|
r145 | def tag_subscribe(request, tag_name): | |
neko259
|
r221 | """Add tag to favorites""" | |
neko259
|
r145 | user = _get_user(request) | |
tag = get_object_or_404(Tag, name=tag_name) | |||
if not tag in user.fav_tags.all(): | |||
neko259
|
r323 | user.add_tag(tag) | |
neko259
|
r145 | ||
neko259
|
r179 | return _redirect_to_next(request) | |
neko259
|
r145 | ||
neko259
|
r335 | @transaction.commit_on_success | |
neko259
|
r145 | def tag_unsubscribe(request, tag_name): | |
neko259
|
r221 | """Remove tag from favorites""" | |
neko259
|
r145 | user = _get_user(request) | |
tag = get_object_or_404(Tag, name=tag_name) | |||
if tag in user.fav_tags.all(): | |||
neko259
|
r323 | user.remove_tag(tag) | |
neko259
|
r145 | ||
neko259
|
r179 | return _redirect_to_next(request) | |
neko259
|
r145 | ||
neko259
|
r152 | def static_page(request, name): | |
neko259
|
r221 | """Show a static page that needs only tags list and a CSS""" | |
neko259
|
r152 | context = _init_default_context(request) | |
return render(request, 'boards/staticpages/' + name + '.html', context) | |||
neko259
|
r222 | def api_get_post(request, post_id): | |
neko259
|
r221 | """ | |
neko259
|
r222 | Get the JSON of a post. This can be | |
neko259
|
r221 | used as and API for external clients. | |
""" | |||
post = get_object_or_404(Post, id=post_id) | |||
json = serializers.serialize("json", [post], fields=( | |||
"pub_time", "_text_rendered", "title", "text", "image", | |||
"image_width", "image_height", "replies", "tags" | |||
)) | |||
return HttpResponse(content=json) | |||
neko259
|
r363 | def api_get_threaddiff(request, thread_id, last_update_time): | |
neko259
|
r370 | """Get posts that were changed or added since time""" | |
neko259
|
r361 | thread = get_object_or_404(Post, id=thread_id) | |
neko259
|
r363 | ||
neko259
|
r373 | filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000, | |
neko259
|
r370 | timezone.get_current_timezone()) | |
neko259
|
r361 | ||
json_data = { | |||
neko259
|
r363 | 'added': [], | |
neko259
|
r364 | 'updated': [], | |
neko259
|
r370 | 'last_update': None, | |
neko259
|
r361 | } | |
neko259
|
r364 | added_posts = Post.objects.filter(thread=thread, pub_time__gt=filter_time) | |
updated_posts = Post.objects.filter(thread=thread, | |||
neko259
|
r370 | pub_time__lt=filter_time, | |
last_edit_time__gt=filter_time) | |||
neko259
|
r363 | for post in added_posts: | |
neko259
|
r361 | json_data['added'].append(get_post(request, post.id).content.strip()) | |
neko259
|
r364 | for post in updated_posts: | |
json_data['updated'].append(get_post(request, post.id).content.strip()) | |||
neko259
|
r370 | json_data['last_update'] = _datetime_to_epoch(thread.last_edit_time) | |
neko259
|
r361 | ||
return HttpResponse(content=json.dumps(json_data)) | |||
neko259
|
r222 | def get_post(request, post_id): | |
neko259
|
r271 | """Get the html of a post. Used for popups.""" | |
neko259
|
r222 | ||
post = get_object_or_404(Post, id=post_id) | |||
neko259
|
r361 | thread = post.thread | |
neko259
|
r222 | ||
context = RequestContext(request) | |||
context["post"] = post | |||
neko259
|
r361 | context["can_bump"] = thread.can_bump() | |
neko259
|
r368 | if "truncated" in request.GET: | |
context["truncated"] = True | |||
neko259
|
r222 | ||
return render(request, 'boards/post.html', context) | |||
neko259
|
r147 | def _get_theme(request, user=None): | |
neko259
|
r101 | """Get user's CSS theme""" | |
neko259
|
r147 | if not user: | |
user = _get_user(request) | |||
neko259
|
r110 | theme = user.get_setting('theme') | |
if not theme: | |||
theme = neboard.settings.DEFAULT_THEME | |||
return theme | |||
neko259
|
r70 | ||
neko259
|
r101 | def _init_default_context(request): | |
"""Create context with default values that are used in most views""" | |||
context = RequestContext(request) | |||
neko259
|
r147 | ||
user = _get_user(request) | |||
context['user'] = user | |||
neko259
|
r149 | context['tags'] = user.get_sorted_fav_tags() | |
neko259
|
r260 | ||
theme = _get_theme(request, user) | |||
context['theme'] = theme | |||
context['theme_css'] = 'css/' + theme + '/base_page.css' | |||
neko259
|
r205 | ||
neko259
|
r271 | # This shows the moderator panel | |
neko259
|
r205 | moderate = user.get_setting(SETTING_MODERATE) | |
if moderate == 'True': | |||
context['moderator'] = user.is_moderator() | |||
else: | |||
context['moderator'] = False | |||
neko259
|
r101 | ||
return context | |||
neko259
|
r110 | ||
def _get_user(request): | |||
neko259
|
r271 | """ | |
Get current user from the session. If the user does not exist, create | |||
a new one. | |||
""" | |||
neko259
|
r110 | ||
session = request.session | |||
neko259
|
r112 | if not 'user_id' in session: | |
neko259
|
r110 | request.session.save() | |
md5 = hashlib.md5() | |||
md5.update(session.session_key) | |||
new_id = md5.hexdigest() | |||
neko259
|
r141 | time_now = timezone.now() | |
neko259
|
r121 | user = User.objects.create(user_id=new_id, rank=RANK_USER, | |
neko259
|
r202 | registration_time=time_now) | |
neko259
|
r110 | ||
neko259
|
r112 | session['user_id'] = user.id | |
neko259
|
r110 | else: | |
neko259
|
r112 | user = User.objects.get(id=session['user_id']) | |
neko259
|
r196 | ||
neko259
|
r137 | return user | |
neko259
|
r155 | ||
def _redirect_to_next(request): | |||
neko259
|
r271 | """ | |
If a 'next' parameter was specified, redirect to the next page. This is | |||
used when the user is required to return to some page after the current | |||
view has finished its work. | |||
""" | |||
neko259
|
r156 | if 'next' in request.GET: | |
next_page = request.GET['next'] | |||
return HttpResponseRedirect(next_page) | |||
else: | |||
return redirect(index) | |||
neko259
|
r271 | ||
neko259
|
r340 | @transaction.commit_on_success | |
neko259
|
r271 | def _ban_current_user(request): | |
"""Add current user to the IP ban list""" | |||
ip = utils.get_client_ip(request) | |||
neko259
|
r340 | ban, created = Ban.objects.get_or_create(ip=ip) | |
if created: | |||
ban.can_read = False | |||
ban.reason = BAN_REASON_SPAM | |||
ban.save() | |||
neko259
|
r312 | ||
def _remove_invalid_links(text): | |||
""" | |||
Replace invalid links in posts so that they won't be parsed. | |||
Invalid links are links to non-existent posts | |||
""" | |||
for reply_number in re.finditer(REGEX_REPLY, text): | |||
neko259
|
r319 | post_id = reply_number.group(1) | |
post = Post.objects.filter(id=post_id) | |||
if not post.exists(): | |||
text = string.replace(text, '>>' + id, id) | |||
neko259
|
r312 | ||
return text | |||
neko259
|
r370 | ||
def _datetime_to_epoch(datetime): | |||
return int(time.mktime(timezone.localtime( | |||
datetime,timezone.get_current_timezone()).timetuple()) | |||
neko259
|
r373 | * 1000000 + datetime.microsecond) |