from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDenied from django.db import transaction from django.db.models import F from django.http import Http404 from django.utils.translation import ugettext as _ from rest_framework import mixins, status, viewsets from rest_framework.decorators import detail_route, list_route from rest_framework.parsers import FormParser, JSONParser, MultiPartParser from rest_framework.response import Response from misago.acl import add_acl from misago.categories.models import Category from misago.core.cache import cache from misago.core.rest_permissions import IsAuthenticatedOrReadOnly from misago.core.shortcuts import get_int_or_404, get_object_or_404 from misago.threads.moderation.posts import hide_post from misago.threads.moderation.threads import hide_thread from ..bans import get_user_ban from ..forms.options import ForumOptionsForm from ..online.utils import get_user_status from ..permissions.delete import allow_delete_user from ..permissions.moderation import allow_moderate_avatar, allow_rename_user from ..permissions.profiles import allow_browse_users_list, allow_follow_user, allow_see_ban_details from ..serializers import BanDetailsSerializer, UserProfileSerializer, UserSerializer from ..viewmodels import UserPosts, UserThreads from .rest_permissions import BasePermission, UnbannedAnonOnly from .userendpoints.avatar import avatar_endpoint, moderate_avatar_endpoint from .userendpoints.changeemail import change_email_endpoint from .userendpoints.changepassword import change_password_endpoint from .userendpoints.create import create_endpoint from .userendpoints.list import list_endpoint from .userendpoints.signature import signature_endpoint from .userendpoints.username import moderate_username_endpoint, username_endpoint class UserViewSetPermission(BasePermission): def has_permission(self, request, view): if view.action == 'create': policy = UnbannedAnonOnly() else: policy = IsAuthenticatedOrReadOnly() return policy.has_permission(request, view) def allow_self_only(user, pk, message): if user.is_anonymous(): raise PermissionDenied(_("You have to sign in to perform this action.")) if user.pk != int(pk): raise PermissionDenied(message) class UserViewSet(viewsets.GenericViewSet): permission_classes = (UserViewSetPermission,) parser_classes=(FormParser, JSONParser, MultiPartParser) queryset = get_user_model().objects def get_queryset(self): relations = ('rank', 'online_tracker', 'ban_cache') return self.queryset.select_related(*relations) def get_user(self, request, pk): user = get_object_or_404(self.get_queryset(), pk=get_int_or_404(pk)) if not user.is_active and not request.user.is_staff: raise Http404() return user def list(self, request): allow_browse_users_list(request.user) return list_endpoint(request) def create(self, request): return create_endpoint(request) def retrieve(self, request, pk=None): profile = self.get_user(request, pk) add_acl(request.user, profile) profile.status = get_user_status(request.user, profile) serializer = UserProfileSerializer(profile, context={'user': request.user}) profile_json = serializer.data if not profile.is_active: profile_json['is_active'] = False return Response(profile_json) @detail_route(methods=['get', 'post']) def avatar(self, request, pk=None): get_int_or_404(pk) allow_self_only(request.user, pk, _("You can't change other users avatars.")) return avatar_endpoint(request) @detail_route(methods=['post']) def forum_options(self, request, pk=None): get_int_or_404(pk) allow_self_only(request.user, pk, _("You can't change other users options.")) form = ForumOptionsForm(request.data, instance=request.user) if form.is_valid(): form.save() return Response({ 'detail': _("Your forum options have been changed.") }) else: return Response(form.errors, status=status.HTTP_400_BAD_REQUEST) @detail_route(methods=['get', 'post']) def username(self, request, pk=None): get_int_or_404(pk) allow_self_only(request.user, pk, _("You can't change other users names.")) return username_endpoint(request) @detail_route(methods=['get', 'post']) def signature(self, request, pk=None): get_int_or_404(pk) allow_self_only(request.user, pk, _("You can't change other users signatures.")) return signature_endpoint(request) @detail_route(methods=['post']) def change_password(self, request, pk=None): get_int_or_404(pk) allow_self_only(request.user, pk, _("You can't change other users passwords.")) return change_password_endpoint(request) @detail_route(methods=['post']) def change_email(self, request, pk=None): get_int_or_404(pk) allow_self_only(request.user, pk, _("You can't change other users e-mail addresses.")) return change_email_endpoint(request) @detail_route(methods=['post']) def follow(self, request, pk=None): profile = self.get_user(request, pk) allow_follow_user(request.user, profile) profile_followers = profile.followers with transaction.atomic(): if request.user.is_following(profile): request.user.follows.remove(profile) followed = False profile_followers -= 1 profile.followers = F('followers') - 1 request.user.following = F('following') - 1 else: request.user.follows.add(profile) followed = True profile_followers += 1 profile.followers = F('followers') + 1 request.user.following = F('following') + 1 profile.save(update_fields=['followers']) request.user.save(update_fields=['following']) return Response({ 'is_followed': followed, 'followers': profile_followers }) @detail_route() def ban(self, request, pk=None): profile = self.get_user(request, pk) allow_see_ban_details(request.user, profile) ban = get_user_ban(profile) if ban: return Response(BanDetailsSerializer(ban).data) else: return Response({}) @detail_route(methods=['get', 'post']) def moderate_avatar(self, request, pk=None): profile = self.get_user(request, pk) allow_moderate_avatar(request.user, profile) return moderate_avatar_endpoint(request, profile) @detail_route(methods=['get', 'post']) def moderate_username(self, request, pk=None): profile = self.get_user(request, pk) allow_rename_user(request.user, profile) return moderate_username_endpoint(request, profile) @detail_route(methods=['get', 'post']) def delete(self, request, pk=None): profile = self.get_user(request, pk) allow_delete_user(request.user, profile) if request.method == 'POST': with transaction.atomic(): profile.lock() if request.data.get('with_content'): profile.delete_content() else: categories_to_sync = set() threads = profile.thread_set.select_related('category', 'first_post') for thread in threads.filter(is_hidden=False).iterator(): categories_to_sync.add(thread.category_id) hide_thread(request, thread) posts = profile.post_set.select_related('category', 'thread', 'thread__category') for post in posts.filter(is_hidden=False).iterator(): categories_to_sync.add(post.category_id) hide_post(request.user, post) post.thread.synchronize() post.thread.save() categories = Category.objects.filter(id__in=categories_to_sync) for category in categories.iterator(): category.synchronize() category.save() profile.delete() return Response({'detail': 'ok'}) @detail_route(methods=['get']) def threads(self, request, pk=None): profile = self.get_user(request, pk) page = get_int_or_404(request.query_params.get('page', 0)) if page == 1: page = 0 # api allows explicit first page feed = UserThreads(request, profile, page) return Response(feed.get_frontend_context()) @detail_route(methods=['get']) def posts(self, request, pk=None): profile = self.get_user(request, pk) page = get_int_or_404(request.query_params.get('page', 0)) if page == 1: page = 0 # api allows explicit first page feed = UserPosts(request, profile, page) return Response(feed.get_frontend_context())