import json import logging from django.core import serializers from django.db import transaction from django.http import HttpResponse 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 from boards.mdx_neboard import Parser from boards.models import Post, Thread, Tag, Attachment, TagAlias from boards.models.thread import STATUS_ARCHIVE from boards.models.user import Notification from boards.utils import datetime_to_epoch from boards.views.thread import ThreadView from boards.models.attachment.viewers import FILE_TYPES_IMAGE __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): """ Adds a post and return the JSON response for it """ opening_post = get_object_or_404(Post, id=opening_post_id) logger.info('Adding post via api...') status = STATUS_OK errors = [] if request.method == 'POST': form = PostForm(request.POST, request.FILES, error_class=PlainErrorList) form.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 = ThreadView().new_post(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() response = { 'status': status, 'errors': errors, } 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): attachments = Attachment.objects.filter(mimetype__in=FILE_TYPES_IMAGE)\ .exclude(alias='').exclude(alias=None) term = request.GET.get('term') if term: attachments = attachments.filter(alias__contains=term) image_dict = [{'thumb': attachment.get_thumb_url(), 'alias': attachment.alias} for attachment in attachments] 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) 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') 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) if new_post_threads: thread_ids = {thread.id: thread for thread in new_post_threads} else: thread_ids = dict() for op in fav_thread_ops: fav_thread_dict = dict() op_thread = op.get_thread() if op_thread.id in thread_ids: thread = thread_ids[op_thread.id] new_post_count = thread.new_post_count fav_thread_dict['newest_post_link'] = thread.get_replies()\ .filter(id__gt=fav_threads[str(op.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(ops) posts.append(fav_thread_dict) return HttpResponse(content=json.dumps(posts))