def clean_threads_for_merge(request): try: threads_ids = list(map(int, request.data.get('threads', []))) except (ValueError, TypeError): raise MergeError(_("One or more thread ids received were invalid.")) if len(threads_ids) < 2: raise MergeError( _("You have to select at least two threads to merge.")) elif len(threads_ids) > MERGE_LIMIT: message = ungettext( "No more than %(limit)s thread can be merged at single time.", "No more than %(limit)s threads can be merged at single time.", MERGE_LIMIT) raise MergeError(message % {'limit': MERGE_LIMIT}) threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) threads_queryset = Thread.objects.filter( id__in=threads_ids, category__tree_id=threads_tree_id, ).select_for_update().select_related('category').order_by('-id') threads = [] for thread in threads_queryset: add_acl(request.user, thread) if can_see_thread(request.user, thread): threads.append(thread) if len(threads) != len(threads_ids): raise MergeError(_("One or more threads to merge could not be found.")) return threads
def validate_threads(self, data): if len(data) > THREADS_LIMIT: message = ngettext( "No more than %(limit)s thread can be merged at single time.", "No more than %(limit)s threads can be merged at single time.", POSTS_LIMIT, ) raise ValidationError(message % {'limit': THREADS_LIMIT}) threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) threads_queryset = Thread.objects.filter( id__in=data, category__tree_id=threads_tree_id, ).select_related('category').order_by('-id') user = self.context['user'] threads = [] for thread in threads_queryset: add_acl(user, thread) if can_see_thread(user, thread): threads.append(thread) if len(threads) != len(data): raise ValidationError(_("One or more threads to merge could not be found.")) return threads
def test_delete_private_thread(self): """attempt to delete private thread fails""" private_thread = self.threads[0] private_thread.category = Category.objects.get( tree_id=trees_map.get_tree_id_for_root(PRIVATE_THREADS_ROOT), ) private_thread.save() private_thread.threadparticipant_set.create( user=self.user, is_owner=True, ) self.override_acl({ 'can_hide_own_threads': 2, 'can_hide_threads': 2, }) threads_ids = [p.id for p in self.threads] response = self.delete(self.api_link, threads_ids) self.assertEqual(response.status_code, 400) self.assertEqual(response.json(), { 'threads': ["One or more threads to delete could not be found."], }) Thread.objects.get(pk=private_thread.pk)
def test_delete_private_thread(self): """attempt to delete private thread fails""" private_thread = self.threads[0] private_thread.category = Category.objects.get( tree_id=trees_map.get_tree_id_for_root( PRIVATE_THREADS_ROOT_NAME), ) private_thread.save() private_thread.threadparticipant_set.create( user=self.user, is_owner=True, ) self.override_acl({ 'can_hide_own_threads': 2, 'can_hide_threads': 2, }) threads_ids = [p.id for p in self.threads] response = self.delete(self.api_link, threads_ids) self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), []) Thread.objects.get(pk=private_thread.pk) deleted_threads = [self.threads[1], self.threads[2]] for thread in deleted_threads: with self.assertRaises(Thread.DoesNotExist): Thread.objects.get(pk=thread.pk) category = Category.objects.get(pk=self.category.pk) self.assertNotIn(category.last_thread_id, threads_ids)
def clean_threads_for_merge(request): threads_ids = clean_ids_list( request.data.get('threads', []), _("One or more thread ids received were invalid."), ) if len(threads_ids) < 2: raise MergeError(_("You have to select at least two threads to merge.")) elif len(threads_ids) > MERGE_LIMIT: message = ungettext( "No more than %(limit)s thread can be merged at single time.", "No more than %(limit)s threads can be merged at single time.", MERGE_LIMIT, ) raise MergeError(message % {'limit': MERGE_LIMIT}) threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) threads_queryset = Thread.objects.filter( id__in=threads_ids, category__tree_id=threads_tree_id, ).select_related('category').order_by('-id') threads = [] for thread in threads_queryset: add_acl(request.user, thread) if can_see_thread(request.user, thread): threads.append(thread) if len(threads) != len(threads_ids): raise MergeError(_("One or more threads to merge could not be found.")) return threads
def setUp(self): super(StartThreadTests, self).setUp() threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) self.category = Category.objects.get(slug='first-category') self.api_link = reverse('misago:api:thread-list')
def test_delete_private_thread(self): """attempt to delete private thread fails""" private_thread = self.threads[0] private_thread.category = Category.objects.get( tree_id=trees_map.get_tree_id_for_root(PRIVATE_THREADS_ROOT_NAME), ) private_thread.save() private_thread.threadparticipant_set.create( user=self.user, is_owner=True, ) self.override_acl({ 'can_hide_own_threads': 2, 'can_hide_threads': 2, }) threads_ids = [p.id for p in self.threads] response = self.delete(self.api_link, threads_ids) self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), []) Thread.objects.get(pk=private_thread.pk) deleted_threads = [self.threads[1], self.threads[2]] for thread in deleted_threads: with self.assertRaises(Thread.DoesNotExist): Thread.objects.get(pk=thread.pk) category = Category.objects.get(pk=self.category.pk) self.assertNotIn(category.last_thread_id, threads_ids)
def test_get_categories_dict_from_db(self): """get_categories_dict_from_db returns dict with categories""" test_dict = Category.objects.get_categories_dict_from_db() for category in Category.objects.all(): threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) if category.tree_id == threads_tree_id: self.assertIn(category.id, test_dict) else: self.assertNotIn(category.id, test_dict)
def setUp(self): super(ThreadsApiTestCase, self).setUp() threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) self.root = Category.objects.get(tree_id=threads_tree_id, level=0) self.category = Category.objects.get(slug='first-category') self.thread = testutils.post_thread(category=self.category) self.api_link = self.thread.get_api_url()
def get_thread(self, request, pk, slug=None): thread = get_object_or_404( Thread.objects.select_related(*BASE_RELATIONS), pk=pk, category__tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME), ) allow_see_thread(request.user, thread) if slug: validate_slug(thread, slug) return thread
def get_target(self, kwargs): target = super(CategoryAdmin, self).get_target(kwargs) threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) target_is_special = bool(target.special_role) target_not_in_categories_tree = target.tree_id != threads_tree_id if target.pk and (target_is_special or target_not_in_categories_tree): raise Category.DoesNotExist() else: return target
def __init__(self, *args, **kwargs): self.base_level = kwargs.pop('base_level', 1) kwargs['level_indicator'] = kwargs.get('level_indicator', '- - ') threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) queryset = Category.objects.filter(tree_id=threads_tree_id) if not kwargs.pop('include_root', False): queryset = queryset.exclude(special_role="root_category") kwargs.setdefault('queryset', queryset) super(AdminCategoryFieldMixin, self).__init__(*args, **kwargs)
def thread_start_editor(request): if request.user.is_anonymous: raise PermissionDenied(_("You need to be signed in to start threads.")) # list of categories that allow or contain subcategories that allow new threads available = [] categories = [] queryset = Category.objects.filter( pk__in=request.user.acl['browseable_categories'], tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)).order_by( '-lft') for category in queryset: add_acl(request.user, category) post = False if can_start_thread(request.user, category): post = { 'close': bool(category.acl['can_close_threads']), 'hide': bool(category.acl['can_hide_threads']), 'pin': category.acl['can_pin_threads'] } available.append(category.pk) available.append(category.parent_id) elif category.pk in available: available.append(category.parent_id) categories.append({ 'id': category.pk, 'name': category.name, 'level': category.level - 1, 'post': post }) # list only categories that allow new threads, or contains subcategory that allows one cleaned_categories = [] for category in reversed(categories): if category['id'] in available: cleaned_categories.append(category) if not cleaned_categories: raise PermissionDenied( _("No categories that allow new threads are available to you at the moment." )) return Response(cleaned_categories)
def thread_start_editor(request): if request.user.is_anonymous: raise PermissionDenied(_("You need to be signed in to start threads.")) # list of categories that allow or contain subcategories that allow new threads available = [] categories = [] queryset = Category.objects.filter( pk__in=request.user.acl_cache['browseable_categories'], tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) ).order_by('-lft') for category in queryset: add_acl(request.user, category) post = False if can_start_thread(request.user, category): post = { 'close': bool(category.acl['can_close_threads']), 'hide': bool(category.acl['can_hide_threads']), 'pin': category.acl['can_pin_threads'], } available.append(category.pk) available.append(category.parent_id) elif category.pk in available: available.append(category.parent_id) categories.append({ 'id': category.pk, 'name': category.name, 'level': category.level - 1, 'post': post, }) # list only categories that allow new threads, or contains subcategory that allows one cleaned_categories = [] for category in reversed(categories): if category['id'] in available: cleaned_categories.append(category) if not cleaned_categories: raise PermissionDenied( _("No categories that allow new threads are available to you at the moment.") ) return Response(cleaned_categories)
def get_thread(self, request, pk, slug=None, select_for_update=False): if select_for_update: queryset = Thread.objects.select_for_update() else: queryset = Thread.objects.select_related(*BASE_RELATIONS) thread = get_object_or_404( queryset, pk=pk, category__tree_id=trees_map.get_tree_id_for_root( THREADS_ROOT_NAME)) allow_see_thread(request.user, thread) if slug: validate_slug(thread, slug) return thread
def get_thread(self, request, pk, slug=None): allow_use_private_threads(request.user) thread = get_object_or_404( Thread.objects.select_related(*BASE_RELATIONS), pk=pk, category__tree_id=trees_map.get_tree_id_for_root(PRIVATE_THREADS_ROOT_NAME), ) make_participants_aware(request.user, thread) allow_see_private_thread(request.user, thread) if slug: validate_slug(thread, slug) return thread
def validate_category(self, value): try: self.category_cache = Category.objects.get( pk=value, tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT)) can_see = can_see_category(self.user, self.category_cache) can_browse = can_browse_category(self.user, self.category_cache) if not (self.category_cache.level and can_see and can_browse): raise PermissionDenied(_("Selected category is invalid.")) allow_start_thread(self.user, self.category_cache) except PermissionDenied as e: raise serializers.ValidationError(e.args[0]) except Category.DoesNotExist: raise serializers.ValidationError( _("Selected category doesn't exist or you don't have permission to browse it." ))
def read_threads(user, pk): user.lock() category_id = get_int_or_404(pk) threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) category = get_object_or_404( Category, id=category_id, tree_id=threads_tree_id, ) if category.level: allow_see_category(user, category) allow_browse_category(user, category) read_category(user, category)
def read(self, request, pk): request.user.lock() category_id = get_int_or_404(pk) threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) category = get_object_or_404(Category, id=category_id, tree_id=threads_tree_id, ) if category.level: allow_see_category(request.user, category) allow_browse_category(request.user, category) read_category(request.user, category) return Response({'detail': 'ok'})
def get_valid_threads(self, threads_ids): user = self.context['user'] threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT) threads_queryset = Thread.objects.filter( id__in=threads_ids, category__tree_id=threads_tree_id, ).select_related('category').order_by('-id') invalid_threads = [] valid_threads = [] for thread in threads_queryset: add_acl(user, thread) if can_see_thread(user, thread): valid_threads.append(thread) try: allow_merge_thread(user, thread) except PermissionDenied as permission_error: invalid_threads.append({ 'id': thread.id, 'status': 403, 'detail': permission_error }) not_found_ids = set(threads_ids) - set([t.id for t in valid_threads]) for not_found_id in not_found_ids: invalid_threads.append({ 'id': not_found_id, 'status': 404, 'detail': _("Requested thread doesn't exist or you don't have permission to see it." ), }) if invalid_threads: invalid_threads.sort(key=lambda item: item['id']) raise ValidationError({'merge': invalid_threads}) return valid_threads
def all_categories(self, include_root=False): tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME) queryset = self.filter(tree_id=tree_id) if not include_root: queryset = queryset.filter(level__gt=0) return queryset.order_by('lft')