Browse Source

fix #497: extensive data preloader

Rafał Pitoń 10 years ago
parent
commit
b0e072f2f8

+ 1 - 0
docs/developers/index.rst

@@ -44,6 +44,7 @@ Following references cover everything you want to know about writing your own ap
    markup
    notifications
    posting_process
+   preloading_data
    settings
    shortcuts
    template_tags

+ 41 - 0
docs/developers/preloading_data.rst

@@ -0,0 +1,41 @@
+=====================================
+Preloading Data for Ember.js Frontend
+=====================================
+
+When user visits Misago site for first time, his/hers HTTP request is handled by Django which outputs simple version of requested page. If user has JavaScript enabled in browser, full version of page is then ran by Ember.js.
+
+To keep this process as fast as possible, Misago already includes ("preloads") some of data within initial response. This data is assigned to global ``MisagoData`` object and accessed via ``MisagoPreloadStore`` helper.
+
+
+Preloading Custom Data
+----------------------
+
+Misago creates empty dict and makes it available as ``preloaded_ember_data`` attribute on ``HttpRequest`` object. This dict is converted into JSON when initial page is rendered by Django.
+
+This means that as long as initial page wasn't rendered yet, you can preload data in any place that has access to request object.
+
+
+Accessing Preloaded Data
+------------------------
+
+Misago provides utility object defined within ``misago/utils/preloadstore`` module that provides simple API for accessing keys defined in ``MisagoData``:
+
+
+    // some .js module that wants to access preloaded data
+    import MisagoPreloadStore from 'misago/utils/preloadstore';
+
+    // see if required key is defined
+    if (MisagoPreloadStore.has('thread')) {
+        // get key value from store
+        var preloadedThread = MisagoPreloadStore.get('thread');
+    }
+
+    // or use default if key isn't set
+    var somethingElse = MisagoPreloadStore.get('nonexistantKey', {'default': 'Value'})
+
+    // pop value so future code doesn't access it
+    var preloadedThread = MisagoPreloadStore.pop('thread');
+    console.log(MisagoPreloadStore.get('thread')); // prints "undefined" in console
+
+    // finally set values in store (useful for testing, but terrible for global state)
+    MisagoPreloadStore.get('fakeValue', {'mock': 'Value'})

+ 1 - 0
docs/index.rst

@@ -33,6 +33,7 @@ Table of Contents
    developers/markup
    developers/notifications
    developers/posting_process
+   developers/preloading_data
    developers/settings
    developers/shortcuts
    developers/template_tags

+ 5 - 1
misago/conf/defaults.py

@@ -32,7 +32,8 @@ MISAGO_BASE_DIR = os.path.dirname(os.path.dirname(__file__))
 
 
 # Default JS debug to false
-MISAGO_JS_DEBUG = False
+# This setting used exclusively by test runner and isn't part of public API
+_MISAGO_JS_DEBUG = False
 
 
 # Assets Pipeline
@@ -119,6 +120,8 @@ INSTALLED_APPS = (
 MIDDLEWARE_CLASSES = (
     'misago.users.middleware.AvatarServerMiddleware',
     'misago.users.middleware.RealIPMiddleware',
+    'misago.core.middleware.preloademberdata.PreloadEmberDataMiddleware',
+    'misago.conf.middleware.PreloadConfigMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'corsheaders.middleware.CorsMiddleware',
     'django.middleware.common.CommonMiddleware',
@@ -144,6 +147,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     'django.core.context_processors.tz',
     'django.contrib.messages.context_processors.messages',
     'misago.core.context_processors.site_address',
+    'misago.core.context_processors.preloaded_ember_data',
     'misago.conf.context_processors.settings',
     'misago.users.context_processors.sites_links',
 )

+ 17 - 0
misago/conf/middleware.py

@@ -0,0 +1,17 @@
+from django.core.urlresolvers import reverse
+from misago.conf.gateway import settings, db_settings  # noqa
+
+
+class PreloadConfigMiddleware(object):
+    def process_request(self, request):
+        request.preloaded_ember_data.update({
+            'misagoSettings': db_settings.get_public_settings(),
+
+            'staticUrl': settings.STATIC_URL,
+            'mediaUrl': settings.MEDIA_URL,
+
+            'loginRedirectUrl': reverse('misago:index'),
+            'loginUrl': reverse('misago:login'),
+
+            'logoutUrl': reverse('misago:logout'),
+        })

+ 30 - 0
misago/conf/tests/test_preloadconfig_middleware.py

@@ -0,0 +1,30 @@
+from django.test import TestCase
+from misago.conf.middleware import PreloadConfigMiddleware
+
+
+class MockRequest(object):
+    def __init__(self):
+        self.preloaded_ember_data = {}
+
+
+class PreloadConfigMiddlewareTests(TestCase):
+    def test_middleware_preloads_configuration(self):
+        """Middleware sets keys in preloaded_ember_data dict on request"""
+        request = MockRequest()
+
+        PreloadConfigMiddleware().process_request(request)
+
+        MIDDLEWARE_KEYS = (
+            'misagoSettings',
+
+            'staticUrl',
+            'mediaUrl',
+
+            'loginRedirectUrl',
+            'loginUrl',
+
+            'logoutUrl',
+        )
+
+        for key in MIDDLEWARE_KEYS:
+            self.assertIn(key, request.preloaded_ember_data)

+ 4 - 0
misago/core/context_processors.py

@@ -14,3 +14,7 @@ def site_address(request):
         'SITE_HOST': request.get_host(),
         'SITE_ADDRESS': address_template % request.get_host()
     }
+
+
+def preloaded_ember_data(request):
+    return {'preloaded_ember_data': request.preloaded_ember_data}

+ 3 - 0
misago/core/middleware/preloademberdata.py

@@ -0,0 +1,3 @@
+class PreloadEmberDataMiddleware(object):
+    def process_request(self, request):
+        request.preloaded_ember_data = {}

+ 15 - 4
misago/core/tests/test_context_processors.py

@@ -17,9 +17,9 @@ class MockRequest(object):
 class SiteAddressTests(TestCase):
     def test_site_address_for_http(self):
         """Correct SITE_ADDRESS set for HTTP request"""
-        http_somewhere_com = MockRequest(False, 'somewhere.com')
+        mock_request = MockRequest(False, 'somewhere.com')
         self.assertEqual(
-            context_processors.site_address(http_somewhere_com),
+            context_processors.site_address(mock_request),
             {
                 'SITE_ADDRESS': 'http://somewhere.com',
                 'SITE_HOST': 'somewhere.com',
@@ -28,11 +28,22 @@ class SiteAddressTests(TestCase):
 
     def test_site_address_for_https(self):
         """Correct SITE_ADDRESS set for HTTPS request"""
-        https_somewhere_com = MockRequest(True, 'somewhere.com')
+        mock_request = MockRequest(True, 'somewhere.com')
         self.assertEqual(
-            context_processors.site_address(https_somewhere_com),
+            context_processors.site_address(mock_request),
             {
                 'SITE_ADDRESS': 'https://somewhere.com',
                 'SITE_HOST': 'somewhere.com',
                 'SITE_PROTOCOL': 'https',
             })
+
+
+class PreloadedDataTests(TestCase):
+    def test_preloaded_ember_data(self):
+        """preloaded_ember_data is available in templates"""
+        mock_request = MockRequest(False, 'somewhere.com')
+        mock_request.preloaded_ember_data = {'someValue': 'Something'}
+
+        self.assertEqual(
+            context_processors.preloaded_ember_data(mock_request),
+            {'preloaded_ember_data': {'someValue': 'Something'}})

+ 3 - 0
misago/core/tests/test_errorpages.py

@@ -45,6 +45,9 @@ class CustomErrorPagesTests(TestCase):
         self.misago_request = RequestFactory().get(reverse('misago:index'))
         self.site_request = RequestFactory().get(reverse('raise_403'))
 
+        self.misago_request.preloaded_ember_data = {}
+        self.site_request.preloaded_ember_data = {}
+
     def test_shared_403_decorator(self):
         """shared_403_decorator calls correct error handler"""
         response = self.client.get(reverse('raise_misago_403'))

+ 1 - 0
misago/core/tests/test_middleware_exceptionhandler.py → misago/core/tests/test_exceptionhandler_middleware.py

@@ -9,6 +9,7 @@ from misago.core.middleware.exceptionhandler import ExceptionHandlerMiddleware
 class ExceptionHandlerMiddlewareTests(TestCase):
     def setUp(self):
         self.request = RequestFactory().get(reverse('misago:index'))
+        self.request.preloaded_ember_data = {}
 
     def test_middleware_returns_response_for_supported_exception(self):
         """Middleware returns HttpResponse for supported exception"""

+ 15 - 0
misago/core/tests/test_preloademberdata_middleware.py

@@ -0,0 +1,15 @@
+from django.test import TestCase
+from misago.core.middleware.preloademberdata import PreloadEmberDataMiddleware
+
+
+class MockRequest(object):
+    pass
+
+
+class PreloadEmberDataMiddlewareTests(TestCase):
+    def test_middleware_sets_preloaded_dict(self):
+        """Middleware sets preloaded_ember_data dict on request"""
+        request = MockRequest()
+
+        PreloadEmberDataMiddleware().process_request(request)
+        self.assertEqual(request.preloaded_ember_data, {})

+ 9 - 2
misago/core/tests/test_views.py

@@ -17,5 +17,12 @@ class JavaScriptViewTests(TestCase):
 
     def test_preload_data_view_returns_200(self):
         """preload_data view has no show-stoppers"""
-        response = self.client.get('/misago-preload-data.js')
-        self.assertEqual(response.status_code, 200)
+        with self.settings(_MISAGO_JS_DEBUG=True):
+            response = self.client.get('/misago-preload-data.js')
+            self.assertEqual(response.status_code, 200)
+
+    def test_preload_data_view_returns_404_outside_debug(self):
+        """preload_data view returns 404 outside debug"""
+        with self.settings(_MISAGO_JS_DEBUG=False):
+            response = self.client.get('/misago-preload-data.js')
+            self.assertEqual(response.status_code, 404)

+ 2 - 2
misago/core/views.py

@@ -27,8 +27,8 @@ def javascript_catalog(request):
 
 @never_cache
 def preload_data(request):
-    if not settings.MISAGO_JS_DEBUG:
+    if not (settings.DEBUG or settings._MISAGO_JS_DEBUG):
         raise Http404()
 
-    return render(request, 'misago/preload_data.js',
+    return render(request, 'misago/preloaded_data.js',
                   content_type='application/javascript')

+ 8 - 2
misago/emberapp/app/utils/preloadstore.js

@@ -15,7 +15,6 @@ export default function() {
     },
 
     get: function(key, value) {
-
       if (this.has(key)) {
         return this.data[key];
       } else if (value !== undefined) {
@@ -23,12 +22,19 @@ export default function() {
       } else {
         return undefined;
       }
-
     },
 
     set: function(key, value) {
       this.data[key] = value;
       return value;
+    },
+
+    pop: function(key, value) {
+      var returnValue = this.get(key, value);
+      if (this.has(key)) {
+        delete this.data[key];
+      }
+      return returnValue;
     }
   };
 

+ 37 - 0
misago/emberapp/tests/unit/utils/preloadstore-test.js

@@ -21,9 +21,46 @@ test('get(key) method returns undefined for undefined key', function() {
   equal(PreloadStore.get('undefinedKey'), undefined);
 });
 
+test('get(key, default) method returns default value for undefined key', function() {
+  var key = 'undefinedKey';
+  var defaultValue = 'Default value';
+
+  equal(PreloadStore.get(key, defaultValue), defaultValue);
+  ok(!PreloadStore.has(key));
+});
+
+test('get(key, default) method returns value for defined key', function() {
+  var key = 'mediaUrl';
+
+  equal(PreloadStore.get(key, 'Default Value'), window.MisagoData.mediaUrl);
+  ok(PreloadStore.has(key));
+});
+
 test('set(key, value) method sets new value', function() {
   var key = 'testKey';
+
   var value = 'Lo Bob!';
   equal(PreloadStore.set(key, value), value);
   equal(PreloadStore.get(key), value);
+  ok(PreloadStore.has(key));
+});
+
+test('pop(key, default) method returns default undefined for key', function() {
+  var key = 'undefinedKey';
+  var defaultValue = 'Default value';
+
+  equal(PreloadStore.get(key, defaultValue), defaultValue);
+  ok(!PreloadStore.has(key));
+});
+
+test('pop(key, default) method returns and deletes value for key', function() {
+  var key = 'undefinedKey';
+  var realValue = 'valid value!';
+  var defaultValue = 'Default value';
+
+  PreloadStore.set(key, realValue);
+
+  equal(PreloadStore.pop(key, defaultValue), realValue);
+  equal(PreloadStore.pop(key, defaultValue), defaultValue);
+  ok(!PreloadStore.has(key));
 });

+ 16 - 11
misago/emberapp/vendor/testutils/misago-preload-data.js

@@ -1,23 +1,28 @@
 window.MisagoData = {
-  MISAGO_JS_DEBUG: true,
-  misagoSettings: {
+  "misagoSettings": {
+    "thread_title_length_max": 90,
+    "thread_title_length_min": 5,
+
     "account_activation": "none",
 
     "forum_name": "Misago",
-    "forum_index_title": "",
     "forum_footnote": "This site uses cookies to track and analyse traffic.",
+    "forum_index_title": "",
 
-    "thread_title_length_max": 90,
-    "thread_title_length_min": 5,
-
-    "terms_of_service_title": "",
     "terms_of_service_link": "",
+    "terms_of_service_title": "Regulamin serwisu",
     "terms_of_service": null,
 
-    "privacy_policy_title": "",
     "privacy_policy_link": "",
-    "privacy_policy": null
+    "privacy_policy_title": "Polityka prywatno\u015bci",
+    "privacy_policy": true
   },
-  staticUrl: "/",
-  mediaUrl: "/media/"
+
+  "mediaUrl": "/media/",
+  "staticUrl": "/static/",
+
+  "loginUrl": "/login/",
+  "loginRedirectUrl": "/",
+
+  "logoutUrl": "/logout/"
 };

+ 1 - 1
misago/templates/misago/base.html

@@ -37,7 +37,7 @@
 
     <script type="text/javascript" src="/django-i18n.js"></script>
     <script type="text/javascript">
-      {% include "misago/preload_data.js" %}
+      {% include "misago/preloaded_data.js" %}
     </script>
 
     <script type="text/javascript" src="{% static 'misago/js/vendor.js' %}"></script>

+ 0 - 7
misago/templates/misago/preload_data.js

@@ -1,7 +0,0 @@
-{% load misago_json %}
-window.MisagoData = {
-  MISAGO_JS_DEBUG: {{ MISAGO_JS_DEBUG|yesno:"true,false" }},
-  misagoSettings: {{ misago_settings.get_public_settings|as_json }},
-  staticUrl: "{{ STATIC_URL }}",
-  mediaUrl: "{{ MEDIA_URL }}"
-}

+ 2 - 0
misago/templates/misago/preloaded_data.js

@@ -0,0 +1,2 @@
+{% load misago_json %}
+window.MisagoData = {{ preloaded_ember_data|as_json }};