api.py
331 lines
| 10.0 KiB
| text/x-python
|
PythonLexer
|
r450 | import json | ||
|
r639 | import logging | ||
|
r1117 | |||
|
r1441 | from django.core import serializers | ||
|
r450 | from django.db import transaction | ||
|
r1951 | from django.db.models import Q | ||
|
r1937 | from django.http import HttpResponse, HttpResponseBadRequest | ||
|
r1117 | from django.shortcuts import get_object_or_404 | ||
|
r1422 | from django.views.decorators.csrf import csrf_protect | ||
|
r1441 | from boards.abstracts.settingsmanager import get_settings_manager | ||
|
r1998 | from boards.forms import PostForm, PlainErrorList, ThreadForm | ||
|
r1441 | from boards.mdx_neboard import Parser | ||
|
r1946 | from boards.models import Post, Thread, Tag, TagAlias | ||
|
r1937 | from boards.models.attachment import AttachmentSticker | ||
|
r1414 | from boards.models.thread import STATUS_ARCHIVE | ||
|
r1441 | from boards.models.user import Notification | ||
|
r542 | from boards.utils import datetime_to_epoch | ||
|
r1117 | |||
|
r450 | __author__ = 'neko259' | ||
|
r501 | PARAMETER_TRUNCATED = 'truncated' | ||
PARAMETER_TAG = 'tag' | ||||
PARAMETER_OFFSET = 'offset' | ||||
|
r524 | PARAMETER_DIFF_TYPE = 'type' | ||
|
r1075 | PARAMETER_POST = 'post' | ||
|
r1076 | PARAMETER_UPDATED = 'updated' | ||
|
r1086 | PARAMETER_LAST_UPDATE = 'last_update' | ||
|
r1119 | PARAMETER_THREAD = 'thread' | ||
PARAMETER_UIDS = 'uids' | ||||
|
r1625 | PARAMETER_SUBSCRIBED = 'subscribed' | ||
|
r524 | |||
DIFF_TYPE_HTML = 'html' | ||||
DIFF_TYPE_JSON = 'json' | ||||
|
r501 | |||
|
r533 | STATUS_OK = 'ok' | ||
STATUS_ERROR = 'error' | ||||
|
r639 | logger = logging.getLogger(__name__) | ||
|
r450 | |||
@transaction.atomic | ||||
|
r1086 | def api_get_threaddiff(request): | ||
|
r627 | """ | ||
Gets posts that were changed or added since time | ||||
""" | ||||
|
r450 | |||
|
r1191 | thread_id = request.POST.get(PARAMETER_THREAD) | ||
|
r1425 | uids_str = request.POST.get(PARAMETER_UIDS) | ||
if not thread_id or not uids_str: | ||||
return HttpResponse(content='Invalid request.') | ||||
uids = uids_str.strip().split(' ') | ||||
|
r639 | |||
|
r1323 | opening_post = get_object_or_404(Post, id=thread_id) | ||
thread = opening_post.get_thread() | ||||
|
r450 | |||
json_data = { | ||||
|
r1076 | PARAMETER_UPDATED: [], | ||
|
r1119 | PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already? | ||
|
r504 | } | ||
|
r1704 | posts = Post.objects.filter(thread=thread).exclude(uid__in=uids) | ||
|
r524 | |||
|
r917 | diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML) | ||
|
r524 | |||
|
r1118 | for post in posts: | ||
|
r1340 | json_data[PARAMETER_UPDATED].append(post.get_post_data( | ||
format_type=diff_type, request=request)) | ||||
|
r1086 | json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time) | ||
|
r450 | |||
|
r1625 | settings_manager = get_settings_manager(request) | ||
json_data[PARAMETER_SUBSCRIBED] = str(settings_manager.thread_is_fav(opening_post)) | ||||
|
r1323 | # If the tag is favorite, update the counter | ||
settings_manager = get_settings_manager(request) | ||||
favorite = settings_manager.thread_is_fav(opening_post) | ||||
if favorite: | ||||
settings_manager.add_or_read_fav_thread(opening_post) | ||||
|
r450 | return HttpResponse(content=json.dumps(json_data)) | ||
|
r1422 | @csrf_protect | ||
|
r1998 | def api_add_post(request, opening_post_id=None): | ||
|
r533 | """ | ||
|
r627 | Adds a post and return the JSON response for it | ||
|
r533 | """ | ||
|
r1998 | if opening_post_id: | ||
opening_post = get_object_or_404(Post, id=opening_post_id) | ||||
else: | ||||
opening_post = None | ||||
|
r533 | |||
status = STATUS_OK | ||||
errors = [] | ||||
|
r1996 | post = None | ||
|
r533 | if request.method == 'POST': | ||
|
r1998 | if opening_post: | ||
form = PostForm(request.POST, request.FILES, error_class=PlainErrorList) | ||||
else: | ||||
form = ThreadForm(request.POST, request.FILES, error_class=PlainErrorList) | ||||
|
r533 | form.session = request.session | ||
|
r638 | if form.need_to_ban: | ||
# Ban user because he is suspected to be a bot | ||||
# _ban_current_user(request) | ||||
status = STATUS_ERROR | ||||
|
r533 | if form.is_valid(): | ||
|
r1997 | post = Post.objects.create_from_form(request, form, opening_post, | ||
html_response=False) | ||||
|
r640 | if not post: | ||
|
r638 | status = STATUS_ERROR | ||
|
r640 | else: | ||
logger.info('Added post #%d via api.' % post.id) | ||||
|
r533 | else: | ||
status = STATUS_ERROR | ||||
errors = form.as_json_errors() | ||||
|
r1996 | else: | ||
status = STATUS_ERROR | ||||
|
r533 | |||
response = { | ||||
'status': status, | ||||
'errors': errors, | ||||
} | ||||
|
r1996 | if post: | ||
response['post_id'] = post.id | ||||
|
r1998 | if not opening_post: | ||
# FIXME For now we include URL only for threads to navigate to them. | ||||
# This needs to become something universal, just not yet sure how. | ||||
response['url'] = post.get_absolute_url() | ||||
|
r1996 | |||
|
r533 | return HttpResponse(content=json.dumps(response)) | ||
|
r499 | |||
|
r491 | def get_post(request, post_id): | ||
|
r499 | """ | ||
|
r627 | Gets the html of a post. Used for popups. Post can be truncated if used | ||
|
r499 | in threads list with 'truncated' get parameter. | ||
""" | ||||
|
r491 | |||
post = get_object_or_404(Post, id=post_id) | ||||
|
r1110 | truncated = PARAMETER_TRUNCATED in request.GET | ||
|
r491 | |||
|
r1413 | return HttpResponse(content=post.get_view(truncated=truncated, need_op_data=True)) | ||
|
r499 | |||
def api_get_threads(request, count): | ||||
""" | ||||
|
r627 | Gets the JSON thread opening posts list. | ||
|
r499 | Parameters that can be used for filtering: | ||
tag, offset (from which thread to get results) | ||||
""" | ||||
|
r501 | if PARAMETER_TAG in request.GET: | ||
tag_name = request.GET[PARAMETER_TAG] | ||||
|
r499 | if tag_name is not None: | ||
tag = get_object_or_404(Tag, name=tag_name) | ||||
|
r1414 | threads = tag.get_threads().exclude(status=STATUS_ARCHIVE) | ||
|
r499 | else: | ||
|
r1414 | threads = Thread.objects.exclude(status=STATUS_ARCHIVE) | ||
|
r499 | |||
|
r501 | if PARAMETER_OFFSET in request.GET: | ||
offset = request.GET[PARAMETER_OFFSET] | ||||
|
r499 | offset = int(offset) if offset is not None else 0 | ||
else: | ||||
offset = 0 | ||||
threads = threads.order_by('-bump_time') | ||||
threads = threads[offset:offset + int(count)] | ||||
opening_posts = [] | ||||
for thread in threads: | ||||
opening_post = thread.get_opening_post() | ||||
|
r524 | # TODO Add tags, replies and images count | ||
|
r1340 | post_data = opening_post.get_post_data(include_last_update=True) | ||
|
r1414 | post_data['status'] = thread.get_status() | ||
|
r976 | |||
opening_posts.append(post_data) | ||||
|
r499 | |||
return HttpResponse(content=json.dumps(opening_posts)) | ||||
|
r500 | |||
|
r502 | # TODO Test this | ||
|
r500 | def api_get_tags(request): | ||
""" | ||||
|
r627 | Gets all tags or user tags. | ||
|
r500 | """ | ||
# TODO Get favorite tags for the given user ID | ||||
|
r1874 | tags = TagAlias.objects.all() | ||
|
r1115 | |||
term = request.GET.get('term') | ||||
if term is not None: | ||||
tags = tags.filter(name__contains=term) | ||||
tag_names = [tag.name for tag in tags] | ||||
|
r500 | |||
return HttpResponse(content=json.dumps(tag_names)) | ||||
|
r502 | |||
|
r1702 | def api_get_stickers(request): | ||
|
r1937 | term = request.GET.get('term') | ||
if not term: | ||||
return HttpResponseBadRequest() | ||||
|
r1951 | global_stickers = AttachmentSticker.objects.filter(Q(name__icontains=term) | Q(stickerpack__name__icontains=term)) | ||
|
r1940 | local_stickers = [sticker for sticker in get_settings_manager(request).get_stickers() if term in sticker.name] | ||
stickers = list(global_stickers) + local_stickers | ||||
|
r1702 | |||
|
r1937 | image_dict = [{'thumb': sticker.attachment.get_thumb_url(), | ||
|
r1951 | 'alias': str(sticker)} | ||
|
r1937 | for sticker in stickers] | ||
|
r1702 | |||
return HttpResponse(content=json.dumps(image_dict)) | ||||
|
r502 | # TODO The result can be cached by the thread last update time | ||
# TODO Test this | ||||
def api_get_thread_posts(request, opening_post_id): | ||||
""" | ||||
|
r627 | Gets the JSON array of thread posts | ||
|
r502 | """ | ||
opening_post = get_object_or_404(Post, id=opening_post_id) | ||||
|
r627 | thread = opening_post.get_thread() | ||
|
r502 | posts = thread.get_replies() | ||
|
r525 | json_data = { | ||
'posts': [], | ||||
'last_update': None, | ||||
} | ||||
|
r502 | json_post_list = [] | ||
for post in posts: | ||||
|
r1340 | json_post_list.append(post.get_post_data()) | ||
|
r542 | json_data['last_update'] = datetime_to_epoch(thread.last_edit_time) | ||
|
r525 | json_data['posts'] = json_post_list | ||
|
r524 | |||
|
r525 | return HttpResponse(content=json.dumps(json_data)) | ||
|
r524 | |||
|
r994 | def api_get_notifications(request, username): | ||
last_notification_id_str = request.GET.get('last', None) | ||||
last_id = int(last_notification_id_str) if last_notification_id_str is not None else None | ||||
|
r1445 | posts = Notification.objects.get_notification_posts(usernames=[username], | ||
|
r1117 | last=last_id) | ||
|
r994 | |||
json_post_list = [] | ||||
for post in posts: | ||||
|
r1340 | json_post_list.append(post.get_post_data()) | ||
|
r994 | return HttpResponse(content=json.dumps(json_post_list)) | ||
|
r564 | def api_get_post(request, post_id): | ||
""" | ||||
|
r627 | Gets the JSON of a post. This can be | ||
|
r564 | 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) | ||||
|
r1217 | def api_get_preview(request): | ||
raw_text = request.POST['raw_text'] | ||||
parser = Parser() | ||||
return HttpResponse(content=parser.parse(parser.preparse(raw_text))) | ||||
|
r1340 | |||
def api_get_new_posts(request): | ||||
""" | ||||
Gets favorite threads and unread posts count. | ||||
""" | ||||
posts = list() | ||||
include_posts = 'include_posts' in request.GET | ||||
settings_manager = get_settings_manager(request) | ||||
fav_threads = settings_manager.get_fav_threads() | ||||
fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\ | ||||
.order_by('-pub_time').prefetch_related('thread') | ||||
|
r1345 | ops = [{'op': op, 'last_id': fav_threads[str(op.id)]} for op in fav_thread_ops] | ||
if include_posts: | ||||
new_post_threads = Thread.objects.get_new_posts(ops) | ||||
|
r1346 | if new_post_threads: | ||
thread_ids = {thread.id: thread for thread in new_post_threads} | ||||
else: | ||||
thread_ids = dict() | ||||
|
r1345 | |||
for op in fav_thread_ops: | ||||
fav_thread_dict = dict() | ||||
|
r1344 | |||
|
r1346 | op_thread = op.get_thread() | ||
if op_thread.id in thread_ids: | ||||
thread = thread_ids[op_thread.id] | ||||
|
r1345 | new_post_count = thread.new_post_count | ||
|
r1346 | fav_thread_dict['newest_post_link'] = thread.get_replies()\ | ||
.filter(id__gt=fav_threads[str(op.id)])\ | ||||
|
r1365 | .first().get_absolute_url(thread=thread) | ||
|
r1345 | else: | ||
new_post_count = 0 | ||||
fav_thread_dict['new_post_count'] = new_post_count | ||||
|
r1344 | |||
|
r1345 | fav_thread_dict['id'] = op.id | ||
|
r1340 | |||
|
r1343 | fav_thread_dict['post_url'] = op.get_link_view() | ||
fav_thread_dict['title'] = op.title | ||||
|
r1340 | |||
|
r1345 | posts.append(fav_thread_dict) | ||
else: | ||||
fav_thread_dict = dict() | ||||
fav_thread_dict['new_post_count'] = \ | ||||
Thread.objects.get_new_post_count(ops) | ||||
|
r1340 | posts.append(fav_thread_dict) | ||
return HttpResponse(content=json.dumps(posts)) | ||||