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

fix #914: moved api-specific utils and features from misago.core to misago.api

Rafał Pitoń 7 лет назад
Родитель
Сommit
e5aa78dda3
38 измененных файлов с 143 добавлено и 153 удалено
  1. 1 0
      misago/api/__init__.py
  2. 7 0
      misago/api/apps.py
  3. 7 0
      misago/api/context_processors.py
  4. 20 0
      misago/api/exceptionhandler.py
  5. 0 0
      misago/api/middleware.py
  6. 0 0
      misago/api/patch.py
  7. 0 0
      misago/api/rest_permissions.py
  8. 0 0
      misago/api/router.py
  9. 0 0
      misago/api/serializers.py
  10. 0 0
      misago/api/tests/__init__.py
  11. 26 0
      misago/api/tests/test_context_processors.py
  12. 52 0
      misago/api/tests/test_exceptionhandler.py
  13. 1 1
      misago/api/tests/test_frontendcontext_middleware.py
  14. 1 1
      misago/api/tests/test_patch.py
  15. 1 1
      misago/api/tests/test_serializers.py
  16. 1 1
      misago/categories/serializers.py
  17. 1 1
      misago/categories/urls/api.py
  18. 0 9
      misago/core/context_processors.py
  19. 0 17
      misago/core/exceptionhandler.py
  20. 0 19
      misago/core/tests/test_context_processors.py
  21. 0 38
      misago/core/tests/test_exceptionhandlers.py
  22. 1 35
      misago/core/tests/test_utils.py
  23. 0 7
      misago/core/utils.py
  24. 6 5
      misago/project_template/project_name/settings.py
  25. 0 1
      misago/threads/api/postendpoints/delete.py
  26. 1 1
      misago/threads/api/postendpoints/patch_event.py
  27. 1 1
      misago/threads/api/postendpoints/patch_post.py
  28. 1 1
      misago/threads/api/threadendpoints/patch.py
  29. 1 1
      misago/threads/serializers/feed.py
  30. 1 1
      misago/threads/serializers/post.py
  31. 1 1
      misago/threads/serializers/thread.py
  32. 1 1
      misago/threads/urls/api.py
  33. 1 1
      misago/users/api/users.py
  34. 1 1
      misago/users/forms/auth.py
  35. 3 3
      misago/users/serializers/ban.py
  36. 1 1
      misago/users/serializers/user.py
  37. 4 3
      misago/users/tests/test_auth_api.py
  38. 1 1
      misago/users/urls/api.py

+ 1 - 0
misago/api/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'misago.api.apps.MisagoApiConfig'

+ 7 - 0
misago/api/apps.py

@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+
+
+class MisagoApiConfig(AppConfig):
+    name = 'misago.api'
+    label = 'misago_api'
+    verbose_name = "Misago Api"

+ 7 - 0
misago/api/context_processors.py

@@ -0,0 +1,7 @@
+def frontend_context(request):
+    if request.include_frontend_context:
+        return {
+            'frontend_context': request.frontend_context,
+        }
+    else:
+        return {}

+ 20 - 0
misago/api/exceptionhandler.py

@@ -0,0 +1,20 @@
+from rest_framework.views import exception_handler as rest_exception_handler
+
+from django.core.exceptions import PermissionDenied
+from django.utils import six
+
+from misago.core.exceptions import Banned
+
+
+def handle_api_exception(exception, context):
+    response = rest_exception_handler(exception, context)
+    if response:
+        if isinstance(exception, Banned):
+            response.data = exception.ban.get_serialized_message()
+        elif isinstance(exception, PermissionDenied) and exception.args:
+            response.data = {
+                'detail': six.text_type(exception),
+            }
+        return response
+    else:
+        return None

+ 0 - 0
misago/core/middleware/frontendcontext.py → misago/api/middleware.py


+ 0 - 0
misago/core/apipatch.py → misago/api/patch.py


+ 0 - 0
misago/core/rest_permissions.py → misago/api/rest_permissions.py


+ 0 - 0
misago/core/apirouter.py → misago/api/router.py


+ 0 - 0
misago/core/serializers.py → misago/api/serializers.py


+ 0 - 0
misago/api/tests/__init__.py


+ 26 - 0
misago/api/tests/test_context_processors.py

@@ -0,0 +1,26 @@
+from django.test import TestCase
+
+from misago.api import context_processors
+
+
+class MockRequest(object):
+    pass
+
+
+class FrontendContextTests(TestCase):
+    def test_frontend_context(self):
+        """frontend_context is available in templates"""
+        mock_request = MockRequest()
+        mock_request.include_frontend_context = True
+        mock_request.frontend_context = {'someValue': 'Something'}
+
+        self.assertEqual(
+            context_processors.frontend_context(mock_request), {
+                'frontend_context': {
+                    'someValue': 'Something',
+                },
+            }
+        )
+
+        mock_request.include_frontend_context = False
+        self.assertEqual(context_processors.frontend_context(mock_request), {})

+ 52 - 0
misago/api/tests/test_exceptionhandler.py

@@ -0,0 +1,52 @@
+from django.core import exceptions as django_exceptions
+from django.core.exceptions import PermissionDenied
+from django.http import Http404
+from django.test import TestCase
+
+from misago.api import exceptionhandler
+from misago.core.exceptions import Banned
+from misago.users.models import Ban
+
+
+INVALID_EXCEPTIONS = [
+    django_exceptions.ObjectDoesNotExist,
+    django_exceptions.ViewDoesNotExist,
+    TypeError,
+    ValueError,
+    KeyError,
+]
+
+
+class HandleAPIExceptionTests(TestCase):
+    def test_banned(self):
+        """banned exception is correctly handled"""
+        ban = Ban(user_message="This is test ban!")
+
+        response = exceptionhandler.handle_api_exception(Banned(ban), None)
+
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.data['detail']['html'], "<p>This is test ban!</p>")
+        self.assertIn('expires_on', response.data)
+
+    def test_permission_denied(self):
+        """permission denied exception is correctly handled"""
+        response = exceptionhandler.handle_api_exception(PermissionDenied(), None)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.data['detail'], "Permission denied.")
+
+    def test_permission_message_denied(self):
+        """permission denied with message is correctly handled"""
+        exception = PermissionDenied("You shall not pass!")
+        response = exceptionhandler.handle_api_exception(exception, None)
+        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.data['detail'], "You shall not pass!")
+
+    def test_unhandled_exception(self):
+        """our exception handler is not interrupting other exceptions"""
+        for exception in INVALID_EXCEPTIONS:
+            response = exceptionhandler.handle_api_exception(exception(), None)
+            self.assertIsNone(response)
+
+        response = exceptionhandler.handle_api_exception(Http404(), None)
+        self.assertEqual(response.status_code, 404)
+        self.assertEqual(response.data['detail'], "Not found.")

+ 1 - 1
misago/core/tests/test_frontendcontext_middleware.py → misago/api/tests/test_frontendcontext_middleware.py

@@ -1,6 +1,6 @@
 from django.test import TestCase
 
-from misago.core.middleware.frontendcontext import FrontendContextMiddleware
+from misago.api.middleware import FrontendContextMiddleware
 
 
 class MockRequest(object):

+ 1 - 1
misago/core/tests/test_apipatch.py → misago/api/tests/test_patch.py

@@ -2,7 +2,7 @@ from django.core.exceptions import PermissionDenied
 from django.http import Http404
 from django.test import TestCase
 
-from misago.core.apipatch import ApiPatch, InvalidAction
+from misago.api.patch import ApiPatch, InvalidAction
 
 
 class MockRequest(object):

+ 1 - 1
misago/core/tests/test_serializers.py → misago/api/tests/test_serializers.py

@@ -2,8 +2,8 @@ from rest_framework import serializers
 
 from django.test import TestCase
 
+from misago.api.serializers import MutableFields
 from misago.categories.models import Category
-from misago.core.serializers import MutableFields
 from misago.threads import testutils
 from misago.threads.models import Thread
 

+ 1 - 1
misago/categories/serializers.py

@@ -2,7 +2,7 @@ from rest_framework import serializers
 
 from django.urls import reverse
 
-from misago.core.serializers import MutableFields
+from misago.api.serializers import MutableFields
 from misago.core.utils import format_plaintext_for_html
 
 from .models import Category

+ 1 - 1
misago/categories/urls/api.py

@@ -1,5 +1,5 @@
+from misago.api.router import MisagoApiRouter
 from misago.categories.api import CategoryViewSet
-from misago.core.apirouter import MisagoApiRouter
 
 
 router = MisagoApiRouter()

+ 0 - 9
misago/core/context_processors.py

@@ -25,12 +25,3 @@ def momentjs_locale(request):
     return {
         'MOMENTJS_LOCALE_URL': get_locale_url(get_language()),
     }
-
-
-def frontend_context(request):
-    if request.include_frontend_context:
-        return {
-            'frontend_context': request.frontend_context,
-        }
-    else:
-        return {}

+ 0 - 17
misago/core/exceptionhandler.py

@@ -1,5 +1,3 @@
-from rest_framework.views import exception_handler as rest_exception_handler
-
 from django.core.exceptions import PermissionDenied
 from django.http import Http404, HttpResponsePermanentRedirect, JsonResponse
 from django.urls import reverse
@@ -87,18 +85,3 @@ def get_exception_handler(exception):
 def handle_misago_exception(request, exception):
     handler = get_exception_handler(exception)
     return handler(request, exception)
-
-
-def handle_api_exception(exception, context):
-    response = rest_exception_handler(exception, context)
-    if response:
-        if isinstance(exception, Banned):
-            response.data['ban'] = exception.ban.get_serialized_message()
-        elif isinstance(exception, PermissionDenied):
-            try:
-                response.data['detail'] = exception.args[0]
-            except IndexError:
-                pass
-        return response
-    else:
-        return None

+ 0 - 19
misago/core/tests/test_context_processors.py

@@ -79,22 +79,3 @@ class SiteAddressTests(TestCase):
                 'SITE_PROTOCOL': 'https',
             }
         )
-
-
-class FrontendContextTests(TestCase):
-    def test_frontend_context(self):
-        """frontend_context is available in templates"""
-        mock_request = MockRequest(False, 'somewhere.com')
-        mock_request.include_frontend_context = True
-        mock_request.frontend_context = {'someValue': 'Something'}
-
-        self.assertEqual(
-            context_processors.frontend_context(mock_request), {
-                'frontend_context': {
-                    'someValue': 'Something',
-                },
-            }
-        )
-
-        mock_request.include_frontend_context = False
-        self.assertEqual(context_processors.frontend_context(mock_request), {})

+ 0 - 38
misago/core/tests/test_exceptionhandlers.py

@@ -1,11 +1,7 @@
 from django.core import exceptions as django_exceptions
-from django.core.exceptions import PermissionDenied
-from django.http import Http404
 from django.test import TestCase
 
 from misago.core import exceptionhandler
-from misago.core.exceptions import Banned
-from misago.users.models import Ban
 
 
 INVALID_EXCEPTIONS = [
@@ -46,37 +42,3 @@ class GetExceptionHandlerTests(TestCase):
         for exception in INVALID_EXCEPTIONS:
             with self.assertRaises(ValueError):
                 exceptionhandler.get_exception_handler(exception())
-
-
-class HandleAPIExceptionTests(TestCase):
-    def test_banned(self):
-        """banned exception is correctly handled"""
-        ban = Ban(user_message="This is test ban!")
-
-        response = exceptionhandler.handle_api_exception(Banned(ban), None)
-
-        self.assertEqual(response.status_code, 403)
-        self.assertEqual(response.data['ban']['message']['html'], "<p>This is test ban!</p>")
-
-    def test_permission_denied(self):
-        """permission denied exception is correctly handled"""
-        response = exceptionhandler.handle_api_exception(PermissionDenied(), None)
-        self.assertEqual(response.status_code, 403)
-        self.assertEqual(response.data['detail'], "Permission denied.")
-
-    def test_permission_message_denied(self):
-        """permission denied with message is correctly handled"""
-        exception = PermissionDenied("You shall not pass!")
-        response = exceptionhandler.handle_api_exception(exception, None)
-        self.assertEqual(response.status_code, 403)
-        self.assertEqual(response.data['detail'], "You shall not pass!")
-
-    def test_unhandled_exception(self):
-        """our exception handler is not interrupting other exceptions"""
-        for exception in INVALID_EXCEPTIONS:
-            response = exceptionhandler.handle_api_exception(exception(), None)
-            self.assertIsNone(response)
-
-        response = exceptionhandler.handle_api_exception(Http404(), None)
-        self.assertEqual(response.status_code, 404)
-        self.assertEqual(response.data['detail'], "Not found.")

+ 1 - 35
misago/core/tests/test_utils.py

@@ -9,7 +9,7 @@ from django.utils import six
 
 from misago.core.utils import (
     clean_return_path, format_plaintext_for_html, is_referer_local, is_request_to_misago,
-    parse_iso8601_string, slugify, get_exception_message, clean_ids_list)
+    parse_iso8601_string, slugify, get_exception_message)
 
 
 VALID_PATHS = ("/", "/threads/", )
@@ -266,37 +266,3 @@ class GetExceptionMessageTests(TestCase):
 
         message = get_exception_message(default_message='Lorem Ipsum')
         self.assertEqual(message, 'Lorem Ipsum')
-
-
-class CleanIdsListTests(TestCase):
-    def test_valid_list(self):
-        """list of valid ids is cleaned"""
-        self.assertEqual(clean_ids_list(['1', 3, '42'], None), [1, 3, 42])
-
-    def test_empty_list(self):
-        """empty list passes validation"""
-        self.assertEqual(clean_ids_list([], None), [])
-
-    def test_string_list(self):
-        """string list passes validation"""
-        self.assertEqual(clean_ids_list('1234', None), [1, 2, 3, 4])
-
-    def test_message(self):
-        """utility uses passed message for exception"""
-        with self.assertRaisesMessage(PermissionDenied, "Test error message!"):
-            clean_ids_list(None, "Test error message!")
-
-    def test_invalid_inputs(self):
-        """utility raises exception for invalid inputs"""
-        INVALID_INPUTS = (
-            None,
-            'abc',
-            [None],
-            [1, 2, 'a', 4],
-            [1, None, 3],
-            {1: 2, 'a': 4},
-        )
-
-        for invalid_input in INVALID_INPUTS:
-            with self.assertRaisesMessage(PermissionDenied, "Test error message!"):
-                clean_ids_list(invalid_input, "Test error message!")

+ 0 - 7
misago/core/utils.py

@@ -147,10 +147,3 @@ def get_exception_message(exception=None, default_message=None):
         return exception.args[0]
     except IndexError:
         return default_message
-
-
-def clean_ids_list(ids_list, error_message):
-    try:
-        return list(map(int, ids_list))
-    except (ValueError, TypeError):
-        raise PermissionDenied(error_message)

+ 6 - 5
misago/project_template/project_name/settings.py

@@ -188,8 +188,9 @@ INSTALLED_APPS = [
     'rest_framework',
 
     # Misago apps
-    'misago.admin',
     'misago.acl',
+    'misago.admin',
+    'misago.api',
     'misago.core',
     'misago.conf',
     'misago.markup',
@@ -215,7 +216,7 @@ MIDDLEWARE = [
     'debug_toolbar.middleware.DebugToolbarMiddleware',
 
     'misago.users.middleware.RealIPMiddleware',
-    'misago.core.middleware.frontendcontext.FrontendContextMiddleware',
+    'misago.api.middleware.FrontendContextMiddleware',
 
     'django.middleware.security.SecurityMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
@@ -266,7 +267,7 @@ TEMPLATES = [
 
                 # Note: keep frontend_context processor last for previous processors
                 # to be able to expose data UI app via request.frontend_context
-                'misago.core.context_processors.frontend_context',
+                'misago.api.context_processors.frontend_context',
             ],
         },
     },
@@ -307,12 +308,12 @@ DEBUG_TOOLBAR_PANELS = [
 
 REST_FRAMEWORK = {
     'DEFAULT_PERMISSION_CLASSES': [
-        'misago.core.rest_permissions.IsAuthenticatedOrReadOnly',
+        'misago.api.rest_permissions.IsAuthenticatedOrReadOnly',
     ],
     'DEFAULT_RENDERER_CLASSES': [
         'rest_framework.renderers.JSONRenderer',
     ],
-    'EXCEPTION_HANDLER': 'misago.core.exceptionhandler.handle_api_exception',
+    'EXCEPTION_HANDLER': 'misago.api.exceptionhandler.handle_api_exception',
     'UNAUTHENTICATED_USER': 'misago.users.models.AnonymousUser',
     'URL_FORMAT_OVERRIDE': None,
 }

+ 0 - 1
misago/threads/api/postendpoints/delete.py

@@ -5,7 +5,6 @@ from django.utils.translation import ugettext as _
 from django.utils.translation import ungettext
 
 from misago.conf import settings
-from misago.core.utils import clean_ids_list
 from misago.threads.moderation import posts as moderation
 from misago.threads.permissions import allow_delete_event, allow_delete_post
 from misago.threads.permissions import exclude_invisible_posts

+ 1 - 1
misago/threads/api/postendpoints/patch_event.py

@@ -2,7 +2,7 @@ from django.core.exceptions import PermissionDenied
 from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
-from misago.core.apipatch import ApiPatch
+from misago.api.patch import ApiPatch
 from misago.threads.moderation import posts as moderation
 from misago.threads.permissions import allow_hide_event, allow_unhide_event
 

+ 1 - 1
misago/threads/api/postendpoints/patch_post.py

@@ -5,8 +5,8 @@ from django.core.exceptions import PermissionDenied
 from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
+from misago.api.patch import ApiPatch
 from misago.conf import settings
-from misago.core.apipatch import ApiPatch
 from misago.threads.models import PostLike
 from misago.threads.moderation import posts as moderation
 from misago.threads.permissions import (

+ 1 - 1
misago/threads/api/threadendpoints/patch.py

@@ -9,11 +9,11 @@ from django.utils import six
 from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
+from misago.api.patch import ApiPatch
 from misago.categories.models import Category
 from misago.categories.permissions import allow_browse_category, allow_see_category
 from misago.categories.serializers import CategorySerializer
 from misago.conf import settings
-from misago.core.apipatch import ApiPatch
 from misago.core.shortcuts import get_int_or_404
 from misago.threads.moderation import threads as moderation
 from misago.threads.participants import (

+ 1 - 1
misago/threads/serializers/feed.py

@@ -1,7 +1,7 @@
 from rest_framework import serializers
 
+from misago.api.serializers import MutableFields
 from misago.categories.serializers import CategorySerializer
-from misago.core.serializers import MutableFields
 from misago.threads.models import Post
 from misago.users.serializers import UserSerializer
 

+ 1 - 1
misago/threads/serializers/post.py

@@ -2,7 +2,7 @@ from rest_framework import serializers
 
 from django.urls import reverse
 
-from misago.core.serializers import MutableFields
+from misago.api.serializers import MutableFields
 from misago.threads.models import Post
 from misago.users.serializers import UserSerializer as BaseUserSerializer
 

+ 1 - 1
misago/threads/serializers/thread.py

@@ -2,8 +2,8 @@ from rest_framework import serializers
 
 from django.urls import reverse
 
+from misago.api.serializers import MutableFields
 from misago.categories.serializers import CategorySerializer
-from misago.core.serializers import MutableFields
 from misago.threads.models import Thread
 
 from .poll import PollSerializer

+ 1 - 1
misago/threads/urls/api.py

@@ -1,4 +1,4 @@
-from misago.core.apirouter import MisagoApiRouter
+from misago.api.router import MisagoApiRouter
 from misago.threads.api.attachments import AttachmentViewSet
 from misago.threads.api.threadpoll import ThreadPollViewSet
 from misago.threads.api.threadposts import PrivateThreadPostsViewSet, ThreadPostsViewSet

+ 1 - 1
misago/users/api/users.py

@@ -12,8 +12,8 @@ from django.shortcuts import get_object_or_404
 from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
+from misago.api.rest_permissions import IsAuthenticatedOrReadOnly
 from misago.categories.models import Category
-from misago.core.rest_permissions import IsAuthenticatedOrReadOnly
 from misago.core.shortcuts import get_int_or_404
 from misago.threads.moderation import hide_post, hide_thread
 from misago.users.bans import get_user_ban

+ 1 - 1
misago/users/forms/auth.py

@@ -37,7 +37,7 @@ class MisagoAuthMixin(object):
     def get_errors_dict(self):
         error = self.errors.as_data()['__all__'][0]
         if error.code == 'banned':
-            error.message = self.user_ban.ban.get_serialized_message()
+            return self.user_ban.ban.get_serialized_message()
         else:
             error.message = error.messages[0]
 

+ 3 - 3
misago/users/serializers/ban.py

@@ -23,16 +23,16 @@ def serialize_message(message):
 
 
 class BanMessageSerializer(serializers.ModelSerializer):
-    message = serializers.SerializerMethodField()
+    detail = serializers.SerializerMethodField()
 
     class Meta:
         model = Ban
         fields = [
-            'message',
+            'detail',
             'expires_on',
         ]
 
-    def get_message(self, obj):
+    def get_detail(self, obj):
         if obj.user_message:
             message = obj.user_message
         elif obj.check_type == Ban.IP:

+ 1 - 1
misago/users/serializers/user.py

@@ -3,7 +3,7 @@ from rest_framework import serializers
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
-from misago.core.serializers import MutableFields
+from misago.api.serializers import MutableFields
 
 from . import RankSerializer
 

+ 4 - 3
misago/users/tests/test_auth_api.py

@@ -113,11 +113,12 @@ class GatewayTests(TestCase):
         self.assertEqual(response.status_code, 400)
 
         response_json = response.json()
-        self.assertEqual(response_json['code'], 'banned')
-        self.assertEqual(response_json['detail']['message']['plain'], ban.user_message)
+
+        self.assertEqual(response_json['detail']['plain'], ban.user_message)
         self.assertEqual(
-            response_json['detail']['message']['html'], '<p>%s</p>' % ban.user_message
+            response_json['detail']['html'], '<p>%s</p>' % ban.user_message
         )
+        self.assertIn('expires_on', response_json)
 
         response = self.client.get('/api/auth/')
         self.assertEqual(response.status_code, 200)

+ 1 - 1
misago/users/urls/api.py

@@ -1,6 +1,6 @@
 from django.conf.urls import url
 
-from misago.core.apirouter import MisagoApiRouter
+from misago.api.router import MisagoApiRouter
 from misago.users.api import auth, captcha, mention
 from misago.users.api.ranks import RanksViewSet
 from misago.users.api.usernamechanges import UsernameChangesViewSet