import json import logging from django.core import serializers from django.db import transaction from django.db.models import Q from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import get_object_or_404 from django.views.decorators.csrf import csrf_protect from boards.abstracts.settingsmanager import get_settings_manager from boards.forms import PostForm, PlainErrorList, ThreadForm from boards.mdx_neboard import Parser from boards.models import Post, Thread, Tag, TagAlias from boards.models.attachment import AttachmentSticker from boards.models.thread import STATUS_ARCHIVE from boards.models.user import Notification from boards.utils import datetime_to_epoch __author__ = 'neko259' PARAMETER_TRUNCATED = 'truncated' PARAMETER_TAG = 'tag' PARAMETER_OFFSET = 'offset' PARAMETER_DIFF_TYPE = 'type' PARAMETER_POST = 'post' PARAMETER_UPDATED = 'updated' PARAMETER_LAST_UPDATE = 'last_update' PARAMETER_THREAD = 'thread' PARAMETER_UIDS = 'uids' PARAMETER_SUBSCRIBED = 'subscribed' DIFF_TYPE_HTML = 'html' DIFF_TYPE_JSON = 'json' STATUS_OK = 'ok' STATUS_ERROR = 'error' logger = logging.getLogger(__name__) @transaction.atomic def api_get_threaddiff(request): """ Gets posts that were changed or added since time """ thread_id = request.POST.get(PARAMETER_THREAD) 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(' ') opening_post = get_object_or_404(Post, id=thread_id) thread = opening_post.get_thread() json_data = { PARAMETER_UPDATED: [], PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already? } posts = Post.objects.filter(thread=thread).exclude(uid__in=uids) diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML) for post in posts: json_data[PARAMETER_UPDATED].append(post.get_post_data( format_type=diff_type, request=request)) json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time) settings_manager = get_settings_manager(request) json_data[PARAMETER_SUBSCRIBED] = str(settings_manager.thread_is_fav(opening_post)) # 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) return HttpResponse(content=json.dumps(json_data)) @csrf_protect def api_add_post(request, opening_post_id=None): """ Adds a post and return the JSON response for it """ if opening_post_id: opening_post = get_object_or_404(Post, id=opening_post_id) else: opening_post = None status = STATUS_OK errors = [] post = None if request.method == 'POST': if opening_post: form_class = PostForm else: form_class = ThreadForm form = form_class(request.POST, request.FILES, error_class=PlainErrorList, session=request.session) if form.need_to_ban: # Ban user because he is suspected to be a bot # _ban_current_user(request) status = STATUS_ERROR if form.is_valid(): post = Post.objects.create_from_form(request, form, opening_post, html_response=False) if not post: status = STATUS_ERROR else: logger.info('Added post #%d via api.' % post.id) else: status = STATUS_ERROR errors = form.as_json_errors() else: status = STATUS_ERROR response = { 'status': status, 'errors': errors, } if post: response['post_id'] = post.id 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() return HttpResponse(content=json.dumps(response)) def get_post(request, post_id): """ Gets the html of a post. Used for popups. Post can be truncated if used in threads list with 'truncated' get parameter. """ post = get_object_or_404(Post, id=post_id) truncated = PARAMETER_TRUNCATED in request.GET return HttpResponse(content=post.get_view(truncated=truncated, need_op_data=True)) def api_get_threads(request, count): """ Gets the JSON thread opening posts list. Parameters that can be used for filtering: tag, offset (from which thread to get results) """ if PARAMETER_TAG in request.GET: tag_name = request.GET[PARAMETER_TAG] if tag_name is not None: tag = get_object_or_404(Tag, name=tag_name) threads = tag.get_threads().exclude(status=STATUS_ARCHIVE) else: threads = Thread.objects.exclude(status=STATUS_ARCHIVE) if PARAMETER_OFFSET in request.GET: offset = request.GET[PARAMETER_OFFSET] 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() # TODO Add tags, replies and images count post_data = opening_post.get_post_data(include_last_update=True) post_data['status'] = thread.get_status() opening_posts.append(post_data) return HttpResponse(content=json.dumps(opening_posts)) # TODO Test this def api_get_tags(request): """ Gets all tags or user tags. """ # TODO Get favorite tags for the given user ID tags = TagAlias.objects.all() term = request.GET.get('term') if term is not None: tags = tags.filter(name__contains=term) tag_names = [tag.name for tag in tags] return HttpResponse(content=json.dumps(tag_names)) def api_get_stickers(request): term = request.GET.get('term') if not term: return HttpResponseBadRequest() global_stickers = AttachmentSticker.objects.filter(Q(name__icontains=term) | Q(stickerpack__name__icontains=term)) local_stickers = [sticker for sticker in get_settings_manager(request).get_stickers() if term in sticker.name] stickers = list(global_stickers) + local_stickers image_dict = [{'thumb': sticker.attachment.get_thumb_url(), 'alias': str(sticker)} for sticker in stickers] return HttpResponse(content=json.dumps(image_dict)) # TODO The result can be cached by the thread last update time # TODO Test this def api_get_thread_posts(request, opening_post_id): """ Gets the JSON array of thread posts """ opening_post = get_object_or_404(Post, id=opening_post_id) thread = opening_post.get_thread() posts = thread.get_replies() json_data = { 'posts': [], 'last_update': None, } json_post_list = [] for post in posts: json_post_list.append(post.get_post_data()) json_data['last_update'] = datetime_to_epoch(thread.last_edit_time) json_data['posts'] = json_post_list return HttpResponse(content=json.dumps(json_data)) 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 posts = Notification.objects.get_notification_posts(usernames=[username], last=last_id) json_post_list = [] for post in posts: json_post_list.append(post.get_post_data()) return HttpResponse(content=json.dumps(json_post_list)) def api_get_post(request, post_id): """ Gets the JSON of a post. This can be 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) def api_get_preview(request): raw_text = request.POST['raw_text'] parser = Parser() return HttpResponse(content=parser.parse(parser.preparse(raw_text))) 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) last_posts = settings_manager.get_last_posts() if include_posts: new_post_threads = Thread.objects.get_new_posts(last_posts) if new_post_threads: thread_ids = {thread.id: thread for thread in new_post_threads} else: thread_ids = dict() for post in last_posts: fav_thread_dict = dict() thread = post.get_thread() op = thread.get_opening_post() if thread.id in thread_ids: thread = thread_ids[thread.id] new_post_count = thread.new_post_count fav_thread_dict['newest_post_link'] = thread.get_replies()\ .filter(id__gt=post.id)\ .first().get_absolute_url(thread=thread) else: new_post_count = 0 fav_thread_dict['new_post_count'] = new_post_count fav_thread_dict['id'] = op.id fav_thread_dict['post_url'] = op.get_link_view() fav_thread_dict['title'] = op.title posts.append(fav_thread_dict) else: fav_thread_dict = dict() fav_thread_dict['new_post_count'] = \ Thread.objects.get_new_post_count(last_posts) posts.append(fav_thread_dict) return HttpResponse(content=json.dumps(posts))