Просмотр исходного кода

remove pre-refactor threads list implementation, tests for unsupported threads list types in api

Rafał Pitoń 9 лет назад
Родитель
Сommit
cc9c33a2fe

+ 0 - 137
misago/threads/api/threadendpoints/lists.py

@@ -1,137 +0,0 @@
-from django.http import Http404
-from rest_framework.response import Response
-
-from misago.acl import add_acl
-from misago.categories.models import Category
-from misago.categories.permissions import allow_see_category, allow_browse_category
-from misago.core.shortcuts import get_int_or_404, paginate, pagination_dict
-from misago.readtracker import threadstracker
-
-from misago.threads.mixins.threadslist import ThreadsListMixin
-from misago.threads.permissions.privatethreads import allow_use_private_threads
-from misago.threads.serializers import ThreadListSerializer
-from misago.threads.subscriptions import make_subscription_aware
-from misago.threads.utils import add_categories_to_threads
-
-
-LIST_TYPES = (
-    'all',
-    'my',
-    'new',
-    'unread',
-    'subscribed',
-    'unapproved',
-)
-
-
-class BaseListEndpoint(ThreadsListMixin):
-    serialize_subcategories = True
-
-    def __call__(self, request):
-        try:
-            page_no = int(request.query_params.get('page', 0))
-        except (ValueError, TypeError):
-            raise Http404()
-
-        list_type = request.query_params.get('list') or 'all'
-        if list_type not in LIST_TYPES:
-            raise Http404()
-
-        categories = self.get_categories(request)
-        category = self.get_category(request, categories)
-
-        self.allow_see_list(request, category, list_type)
-        subcategories = self.get_subcategories(category, categories)
-
-        base_queryset = self.get_base_queryset(request, categories, list_type)
-
-        threads_categories = [category] + subcategories
-        threads_queryset = self.get_threads_queryset(category, base_queryset, threads_categories)
-
-        page = paginate(threads_queryset, page_no, 24, 6, allow_explicit_first_page=True)
-        response_dict = pagination_dict(page, include_page_range=False)
-
-        if page.number > 1:
-            threads = list(page.object_list)
-        else:
-            pinned_threads = self.get_pinned_threads(category, base_queryset, threads_categories)
-            threads = list(pinned_threads) + list(page.object_list)
-
-        if list_type in ('new', 'unread'):
-            # we already know all threads on list are unread
-            threadstracker.make_unread(threads)
-        else:
-            threadstracker.make_threads_read_aware(request.user, threads)
-
-        add_categories_to_threads(category, categories, threads)
-
-        if self.serialize_subcategories:
-            response_dict['subcategories'] = []
-            visible_subcategories = self.get_visible_subcategories(threads, threads_categories)
-            for subcategory in subcategories:
-                if subcategory in visible_subcategories:
-                    response_dict['subcategories'].append(subcategory.pk)
-
-        add_acl(request.user, threads)
-        make_subscription_aware(request.user, threads)
-
-        response_dict['results'] = ThreadListSerializer(threads, many=True).data
-        return Response(response_dict)
-
-
-class ThreadsListEndpoint(BaseListEndpoint):
-    def get_category(self, request, categories):
-        if request.query_params.get('category'):
-            category_id = get_int_or_404(request.query_params['category'])
-
-            for category in categories:
-                if category.pk == category_id:
-                    if category.level:
-                        allow_see_category(request.user, category)
-                        allow_browse_category(request.user, category)
-
-                        return category
-                    else:
-                        raise Http404() # disallow root category access
-            raise Http404()
-        return categories[0]
-
-    def get_pinned_threads(self, category, queryset, threads_categories):
-        if category.level:
-            return list(queryset.filter(weight=2)) + list(queryset.filter(
-                weight=1,
-                category__in=threads_categories
-            ))
-        else:
-            return queryset.filter(weight=2)
-
-    def get_threads_queryset(self, category, queryset, threads_categories):
-        if category.level:
-            return queryset.filter(
-                weight=0,
-                category__in=threads_categories,
-            )
-        else:
-            return queryset.filter(
-                weight__lt=2,
-                category__in=threads_categories,
-            )
-
-threads_list_endpoint = ThreadsListEndpoint()
-
-
-class PrivateThreadsListEndpoint(BaseListEndpoint):
-    def get_category(self, request, categories):
-        allow_use_private_threads(request.user)
-        return Category.objects.private_threads()
-
-    def get_subcategories(self, category, categories):
-        return []
-
-    def get_pinned_threads(self, category, queryset, threads_categories):
-        return []
-
-    def get_threads_queryset(self, category, queryset, threads_categories):
-        return queryset.filter(category__in=threads_categories)
-
-private_threads_list_endpoint = PrivateThreadsListEndpoint()

+ 0 - 148
misago/threads/mixins/threadslist.py

@@ -1,148 +0,0 @@
-from datetime import timedelta
-
-from django.conf import settings
-from django.core.exceptions import PermissionDenied
-from django.db.models import F, Q
-from django.utils import timezone
-from django.utils.translation import ugettext as _
-
-from misago.categories.models import Category
-
-from misago.threads.models import Thread
-from misago.threads.permissions import exclude_invisible_threads
-
-
-class ThreadsListMixin(object):
-    def allow_see_list(self, request, category, list_type):
-        if request.user.is_anonymous():
-            if list_type == 'my':
-                raise PermissionDenied(_("You have to sign in to see list of threads that you have started."))
-
-            if list_type == 'new':
-                raise PermissionDenied(_("You have to sign in to see list of threads you haven't read."))
-
-            if list_type == 'unread':
-                raise PermissionDenied(_("You have to sign in to see list of threads with new replies."))
-
-            if list_type == 'subscribed':
-                raise PermissionDenied(_("You have to sign in to see list of threads you are subscribing."))
-
-            if list_type == 'unapproved':
-                raise PermissionDenied(_("You have to sign in to see list of threads with unapproved posts."))
-        else:
-            if list_type == 'unapproved' and not request.user.acl['can_see_unapproved_content_lists']:
-                raise PermissionDenied(_("You don't have permission to see unapproved content lists."))
-
-    def get_categories(self, request):
-        return [Category.objects.root_category()] + list(
-            Category.objects.all_categories().filter(
-                id__in=request.user.acl['visible_categories']
-            ).select_related('parent'))
-
-    def get_subcategories(self, category, categories):
-        subcategories = []
-        for subcategory in categories:
-            if category.has_child(subcategory):
-                subcategories.append(subcategory)
-        return subcategories
-
-    def get_visible_subcategories(self, threads, threads_categories):
-        visible_subcategories = []
-        for thread in threads:
-            if (thread.top_category and thread.category in threads_categories and
-                    thread.top_category not in visible_subcategories):
-                visible_subcategories.append(thread.top_category)
-        return visible_subcategories
-
-    def get_base_queryset(self, request, categories, list_type):
-        # [:1] cos we are cutting off root caregory on forum threads list
-        # as it includes nedless extra condition to DB filter
-        if categories[0].special_role:
-            categories = categories[1:]
-        queryset = get_threads_queryset(request.user, categories, list_type)
-        return queryset.order_by('-last_post_id')
-
-
-def get_threads_queryset(user, categories, list_type):
-    queryset = exclude_invisible_threads(user, categories, Thread.objects)
-
-    if list_type == 'all':
-        return queryset
-    else:
-        return filter_threads_queryset(user, categories, list_type, queryset)
-
-
-def filter_threads_queryset(user, categories, list_type, queryset):
-    if list_type == 'my':
-        return queryset.filter(starter=user)
-    elif list_type == 'subscribed':
-        subscribed_threads = user.subscription_set.values('thread_id')
-        return queryset.filter(id__in=subscribed_threads)
-    elif list_type == 'unapproved':
-        return queryset.filter(has_unapproved_posts=True)
-    elif list_type in ('new', 'unread'):
-        return filter_read_threads_queryset(user, categories, list_type, queryset)
-    else:
-        return queryset
-
-
-def filter_read_threads_queryset(user, categories, list_type, queryset):
-    # grab cutoffs for categories
-    cutoff_date = timezone.now() - timedelta(
-        days=settings.MISAGO_READTRACKER_CUTOFF
-    )
-
-    if cutoff_date < user.joined_on:
-        cutoff_date = user.joined_on
-
-    categories_dict = {}
-    for record in user.categoryread_set.filter(category__in=categories):
-        if record.last_read_on > cutoff_date:
-            categories_dict[record.category_id] = record.last_read_on
-
-    if list_type == 'new':
-        # new threads have no entry in reads table
-        # AND were started after cutoff date
-        read_threads = user.threadread_set.filter(
-            category__in=categories
-        ).values('thread_id')
-
-        condition = Q(last_post_on__lte=cutoff_date)
-        condition = condition | Q(id__in=read_threads)
-
-        if categories_dict:
-            for category_id, category_cutoff in categories_dict.items():
-                condition = condition | Q(
-                    category_id=category_id,
-                    last_post_on__lte=category_cutoff,
-                )
-
-        return queryset.exclude(condition)
-    elif list_type == 'unread':
-        # unread threads were read in past but have new posts
-        # after cutoff date
-        read_threads = user.threadread_set.filter(
-            category__in=categories,
-            thread__last_post_on__gt=cutoff_date,
-            last_read_on__lt=F('thread__last_post_on')
-        ).values('thread_id')
-
-        queryset = queryset.filter(id__in=read_threads)
-
-        # unread threads have last reply after read/cutoff date
-        if categories_dict:
-            conditions = None
-
-            for category_id, category_cutoff in categories_dict.items():
-                condition = Q(
-                    category_id=category_id,
-                    last_post_on__lte=category_cutoff,
-                )
-                if conditions:
-                    conditions = conditions | condition
-                else:
-                    conditions = condition
-
-            return queryset.exclude(conditions)
-        else:
-            return queryset

+ 11 - 3
misago/threads/tests/test_threadslists.py

@@ -156,6 +156,14 @@ class ApiTests(ThreadsListTestCase):
         ))
         self.assertEqual(response.status_code, 200)
 
+    def test_invalid_list_type(self):
+        """api returns 404 for invalid list type"""
+        response = self.client.get('%s?category=%s&list=nope' % (
+            self.api_link,
+            self.root.pk,
+        ))
+        self.assertEqual(response.status_code, 404)
+
 
 class AllThreadsListTests(ThreadsListTestCase):
     def test_list_renders_empty(self):
@@ -176,7 +184,7 @@ class AllThreadsListTests(ThreadsListTestCase):
 
             self.access_all_categories()
 
-            response = self.client.get('%s?list=%s' % (self.api_link, url.strip('/')))
+            response = self.client.get('%s?list=%s' % (self.api_link, url.strip('/') or 'all'))
             self.assertEqual(response.status_code, 200)
 
             response_json = json_loads(response.content)
@@ -202,7 +210,7 @@ class AllThreadsListTests(ThreadsListTestCase):
             response = self.client.get('%s?category=%s&list=%s' % (
                 self.api_link,
                 self.category_b.pk,
-                url.strip('/'),
+                url.strip('/') or 'all',
             ))
             self.assertEqual(response.status_code, 200)
 
@@ -222,7 +230,7 @@ class AllThreadsListTests(ThreadsListTestCase):
             response = self.client.get('%s?category=%s&list=%s' % (
                 self.api_link,
                 self.category_b.pk,
-                url.strip('/'),
+                url.strip('/') or 'all',
             ))
             self.assertEqual(response.status_code, 403)
 

+ 0 - 194
misago/threads/views/lists.py

@@ -1,194 +0,0 @@
-from django.conf import settings
-from django.core.urlresolvers import reverse
-from django.http import Http404
-from django.shortcuts import render
-from django.views.generic import View
-from django.utils.translation import ugettext_lazy
-
-from misago.acl import add_acl
-from misago.categories.models import Category
-from misago.categories.permissions import allow_see_category, allow_browse_category
-from misago.categories.serializers import IndexCategorySerializer
-from misago.core.shortcuts import paginate, pagination_dict, validate_slug
-from misago.readtracker import threadstracker
-
-from misago.threads.mixins.threadslist import ThreadsListMixin
-from misago.threads.permissions.privatethreads import allow_use_private_threads
-from misago.threads.serializers import ThreadListSerializer
-from misago.threads.subscriptions import make_subscription_aware
-from misago.threads.utils import add_categories_to_threads
-
-
-LISTS_NAMES = {
-    'all': None,
-    'my': ugettext_lazy("Your threads"),
-    'new': ugettext_lazy("New threads"),
-    'unread': ugettext_lazy("Unread threads"),
-    'subscribed': ugettext_lazy("Subscribed threads"),
-    'unapproved': ugettext_lazy("Unapproved content"),
-}
-
-
-class BaseList(View):
-    template_name = 'misago/threadslist/threads.html'
-    preloaded_data_prefix = ''
-
-    def get(self, request, list_type=None, **kwargs):
-        try:
-            page = int(request.GET.get('page', 0))
-        except (ValueError, TypeError):
-            raise Http404()
-
-        categories = self.get_categories(request)
-        category = self.get_category(request, categories, **kwargs)
-
-        self.allow_see_list(request, category, list_type)
-        subcategories = self.get_subcategories(category, categories)
-
-        base_queryset = self.get_base_queryset(request, categories, list_type)
-
-        threads_categories = [category] + subcategories
-        threads_queryset = self.get_threads_queryset(base_queryset, threads_categories)
-
-        list_page = paginate(threads_queryset, page, 24, 6)
-        paginator = pagination_dict(list_page, include_page_range=False)
-
-        if list_page.number > 1:
-            threads = list(list_page.object_list)
-        else:
-            pinned_threads = self.get_pinned_threads(base_queryset, threads_categories)
-            threads = list(pinned_threads) + list(list_page.object_list)
-
-        if list_type in ('new', 'unread'):
-            # we already know all threads on list are unread
-            threadstracker.make_unread(threads)
-        else:
-            threadstracker.make_threads_read_aware(request.user, threads)
-
-        add_categories_to_threads(category, categories, threads)
-
-        category.subcategories = []
-        visible_subcategories = self.get_visible_subcategories(threads, threads_categories)
-        for subcategory in subcategories:
-            if subcategory in visible_subcategories:
-                category.subcategories.append(subcategory)
-
-        add_acl(request.user, threads)
-        make_subscription_aware(request.user, threads)
-
-        frontend_context_data = self.get_frontend_context(request, category, categories, threads, paginator)
-        request.frontend_context.update(frontend_context_data)
-
-        context_data = self.get_context_data(request, category, list_type, threads, paginator)
-        return render(request, self.template_name, context_data)
-
-    def get_pinned_threads(self, request, queryset):
-        return []
-
-    def get_threads_queryset(self, queryset, threads_categories):
-        return queryset.filter(category__in=threads_categories)
-
-    def get_frontend_context(self, request, category, categories, threads, paginator):
-        context = {
-            'THREADS': {
-                'results': ThreadListSerializer(threads, many=True).data,
-                'subcategories': [c.pk for c in category.subcategories]
-            },
-            'CATEGORIES': IndexCategorySerializer(categories, many=True).data
-        }
-
-        context['THREADS'].update(paginator)
-        return context
-
-    def get_context_data(self, request, category, list_type, threads, paginator):
-        return {
-            'category': category,
-
-            'list_name': LISTS_NAMES[list_type],
-            'list_type': list_type,
-
-            'threads': threads,
-            'paginator': paginator
-        }
-
-
-class CategoryThreadsList(BaseList, ThreadsListMixin):
-    template_name = 'misago/threadslist/category.html'
-    preloaded_data_prefix = 'CATEGORY_'
-
-    def get_category(self, request, categories, **kwargs):
-        for category in categories:
-            # pylint
-            if category.pk == int(kwargs['pk']):
-                if not category.level:
-                    raise Http404()
-
-                allow_see_category(request.user, category)
-                allow_browse_category(request.user, category)
-
-                validate_slug(category, kwargs['slug'])
-                return category
-        raise Http404()
-
-    def get_pinned_threads(self, queryset, threads_categories):
-        return list(queryset.filter(weight=2)) + list(queryset.filter(
-            weight=1,
-            category__in=threads_categories
-        ))
-
-    def get_threads_queryset(self, queryset, threads_categories):
-        return queryset.filter(
-            weight=0,
-            category__in=threads_categories,
-        )
-
-    def get_frontend_context(self, request, category, categories, *args):
-        context = super(CategoryThreadsList, self).get_frontend_context(request, category, categories, *args)
-        if categories[0].special_role:
-            context['CATEGORIES'][0]['special_role'] = True
-
-        context.update({
-            'THREADS_API': reverse('misago:api:thread-list'),
-            'MERGE_THREADS_API': reverse('misago:api:thread-merge'),
-        })
-
-        return context
-
-
-class ThreadsList(CategoryThreadsList):
-    template_name = 'misago/threadslist/threads.html'
-
-    def get_category(self, request, categories, **kwargs):
-        return categories[0]
-
-    def get_pinned_threads(self, queryset, threads_categories):
-        return queryset.filter(weight=2)
-
-    def get_threads_queryset(self, queryset, threads_categories):
-        return queryset.filter(
-            weight__lt=2,
-            category__in=threads_categories,
-        )
-
-    def get_context_data(self, *args):
-        context = super(ThreadsList, self).get_context_data(*args)
-        context['is_index'] = not settings.MISAGO_CATEGORIES_ON_INDEX
-        return context
-
-
-class PrivateThreadsList(BaseList):
-    template_name = 'misago/threadslist/private_threads.html'
-    preloaded_data_prefix = 'PRIVATE_'
-
-    def get_base_queryset(self, request, categories, list_type):
-        raise NotImplementedError('Private Threads List is not implemented yet!')
-
-    def get_categories(self, request):
-        return [Category.objects.private_threads()]
-
-    def get_category(self, request, **kwargs):
-        allow_use_private_threads(request.user)
-        return Category.objects.private_threads()
-
-    def get_subcategories(self, category, categories):
-        return []