From 00b143297482143b85f819afa3b44d05d2e5da50 Mon Sep 17 00:00:00 2001 From: Tyson Clugg Date: Thu, 23 Jul 2015 11:07:20 +1000 Subject: [PATCH] Allow support for multiple meteor apps in a single django-ddp enabled project by reading METEOR_STAR_JSON as part of view init instead of app ready. --- dddp/server/apps.py | 171 ------------------------------------------ dddp/server/views.py | 175 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 168 insertions(+), 178 deletions(-) diff --git a/dddp/server/apps.py b/dddp/server/apps.py index 3065be9..4a742e9 100644 --- a/dddp/server/apps.py +++ b/dddp/server/apps.py @@ -1,37 +1,9 @@ """Django DDP Server app config.""" from __future__ import print_function, absolute_import, unicode_literals -import io import mimetypes -import os.path from django.apps import AppConfig -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured -from ejson import dumps, loads -import pybars - - -STAR_JSON_SETTING_NAME = 'METEOR_STAR_JSON' - - -def read(path, default=None, encoding='utf8'): - """Read encoded contents from specified path or return default.""" - if not path: - return default - try: - with io.open(path, mode='r', encoding=encoding) as contents: - return contents.read() - except IOError: - if default is not None: - return default - raise - - -def read_json(path): - """Read JSON encoded contents from specified path.""" - with open(path, mode='r') as json_file: - return loads(json_file.read()) class ServerConfig(AppConfig): @@ -41,149 +13,6 @@ class ServerConfig(AppConfig): name = 'dddp.server' verbose_name = 'Django DDP Meteor Web Server' - manifest = None - program_json = None - program_json_path = None - runtime_config = None - - star_json = None # top level layout - server_json = None # web server layout - web_browser_json = None # web.browser (client) layout - - url_map = None - internal_map = None - server_load_map = None - template_path = None # web server HTML template path - client_map = None # web.browser (client) URL to path map - html = '\nDDP App' - def ready(self): """Configure Django DDP server app.""" mimetypes.init() # read and process /etc/mime.types - self.url_map = {} - - try: - json_path = getattr(settings, STAR_JSON_SETTING_NAME) - except AttributeError: - raise ImproperlyConfigured( - '%s setting required by dddp.server.view.' % ( - STAR_JSON_SETTING_NAME, - ), - ) - - self.star_json = read_json(json_path) - star_format = self.star_json['format'] - if star_format != 'site-archive-pre1': - raise ValueError( - 'Unknown Meteor star format: %r' % star_format, - ) - programs = { - program['name']: program - for program in self.star_json['programs'] - } - - server_json_path = os.path.join( - os.path.dirname(json_path), - os.path.dirname(programs['server']['path']), - 'program.json', - ) - self.server_json = read_json(server_json_path) - server_format = self.server_json['format'] - if server_format != 'javascript-image-pre1': - raise ValueError( - 'Unknown Meteor server format: %r' % server_format, - ) - self.server_load_map = {} - for item in self.server_json['load']: - item['path_full'] = os.path.join( - os.path.dirname(server_json_path), - item['path'], - ) - self.server_load_map[item['path']] = item - self.url_map[item['path']] = ( - item['path_full'], 'text/javascript' - ) - try: - item['source_map_full'] = os.path.join( - os.path.dirname(server_json_path), - item['sourceMap'], - ) - self.url_map[item['sourceMap']] = ( - item['source_map_full'], 'text/plain' - ) - except KeyError: - pass - self.template_path = os.path.join( - os.path.dirname(server_json_path), - self.server_load_map[ - 'packages/boilerplate-generator.js' - ][ - 'assets' - ][ - 'boilerplate_web.browser.html' - ], - ) - - web_browser_json_path = os.path.join( - os.path.dirname(json_path), - programs['web.browser']['path'], - ) - self.web_browser_json = read_json(web_browser_json_path) - web_browser_format = self.web_browser_json['format'] - if web_browser_format != 'web-program-pre1': - raise ValueError( - 'Unknown Meteor web.browser format: %r' % ( - web_browser_format, - ), - ) - self.client_map = {} - self.internal_map = {} - for item in self.web_browser_json['manifest']: - item['path_full'] = os.path.join( - os.path.dirname(web_browser_json_path), - item['path'], - ) - if item['where'] == 'client': - if '?' in item['url']: - item['url'] = item['url'].split('?', 1)[0] - self.client_map[item['url']] = item - self.url_map[item['url']] = ( - item['path_full'], - mimetypes.guess_type( - item['path_full'], - )[0] or 'application/octet-stream', - ) - elif item['where'] == 'internal': - self.internal_map[item['type']] = item - - config = { - 'css': [ - {'url': item['path']} - for item in self.web_browser_json['manifest'] - if item['type'] == 'css' and item['where'] == 'client' - ], - 'js': [ - {'url': item['path']} - for item in self.web_browser_json['manifest'] - if item['type'] == 'js' and item['where'] == 'client' - ], - 'meteorRuntimeConfig': '"%s"' % ( - dumps(self.runtime_config) - ), - 'rootUrlPathPrefix': '/app', - 'bundledJsCssPrefix': '/app/', - 'inlineScriptsAllowed': False, - 'inline': None, - 'head': read( - self.internal_map.get('head', {}).get('path_full', None), - default=u'', - ), - 'body': read( - self.internal_map.get('body', {}).get('path_full', None), - default=u'', - ), - } - tmpl_raw = read(self.template_path, encoding='utf8') - compiler = pybars.Compiler() - tmpl = compiler.compile(tmpl_raw) - self.html = '\n%s' % tmpl(config) diff --git a/dddp/server/views.py b/dddp/server/views.py index 59e4575..9084fc5 100644 --- a/dddp/server/views.py +++ b/dddp/server/views.py @@ -1,13 +1,35 @@ """Django DDP Server views.""" from __future__ import print_function, absolute_import, unicode_literals -from ejson import dumps -from django.apps import apps + +import io +import mimetypes +import os.path + +from ejson import dumps, loads from django.conf import settings from django.http import HttpResponse from django.views.generic import View +import pybars -STAR_JSON_SETTING_NAME = 'METEOR_STAR_JSON' + +def read(path, default=None, encoding='utf8'): + """Read encoded contents from specified path or return default.""" + if not path: + return default + try: + with io.open(path, mode='r', encoding=encoding) as contents: + return contents.read() + except IOError: + if default is not None: + return default + raise + + +def read_json(path): + """Read JSON encoded contents from specified path.""" + with open(path, mode='r') as json_file: + return loads(json_file.read()) class MeteorView(View): @@ -16,13 +38,152 @@ class MeteorView(View): http_method_names = ['get', 'head'] - app = None + json_path = None runtime_config = None + manifest = None + program_json = None + program_json_path = None + runtime_config = None + + star_json = None # top level layout + + url_map = None + internal_map = None + server_load_map = None + template_path = None # web server HTML template path + client_map = None # web.browser (client) URL to path map + html = '\nDDP App' + + root_url_path_prefix = '' + bundled_js_css_prefix = '/' + def __init__(self, **kwargs): """Initialisation for Django DDP server view.""" + # super(...).__init__ assigns kwargs to instance. super(MeteorView, self).__init__(**kwargs) - self.app = apps.get_app_config('server') + + self.url_map = {} + + # process `star_json` + self.star_json = read_json(self.json_path) + star_format = self.star_json['format'] + if star_format != 'site-archive-pre1': + raise ValueError( + 'Unknown Meteor star format: %r' % star_format, + ) + programs = { + program['name']: program + for program in self.star_json['programs'] + } + + # process `bundle/programs/server/program.json` from build dir + server_json_path = os.path.join( + os.path.dirname(self.json_path), + os.path.dirname(programs['server']['path']), + 'program.json', + ) + server_json = read_json(server_json_path) + server_format = server_json['format'] + if server_format != 'javascript-image-pre1': + raise ValueError( + 'Unknown Meteor server format: %r' % server_format, + ) + self.server_load_map = {} + for item in server_json['load']: + item['path_full'] = os.path.join( + os.path.dirname(server_json_path), + item['path'], + ) + self.server_load_map[item['path']] = item + self.url_map[item['path']] = ( + item['path_full'], 'text/javascript' + ) + try: + item['source_map_full'] = os.path.join( + os.path.dirname(server_json_path), + item['sourceMap'], + ) + self.url_map[item['sourceMap']] = ( + item['source_map_full'], 'text/plain' + ) + except KeyError: + pass + self.template_path = os.path.join( + os.path.dirname(server_json_path), + self.server_load_map[ + 'packages/boilerplate-generator.js' + ][ + 'assets' + ][ + 'boilerplate_web.browser.html' + ], + ) + + # process `bundle/programs/web.browser/program.json` from build dir + web_browser_json_path = os.path.join( + os.path.dirname(self.json_path), + programs['web.browser']['path'], + ) + web_browser_json = read_json(web_browser_json_path) + web_browser_format = web_browser_json['format'] + if web_browser_format != 'web-program-pre1': + raise ValueError( + 'Unknown Meteor web.browser format: %r' % ( + web_browser_format, + ), + ) + self.client_map = {} + self.internal_map = {} + for item in web_browser_json['manifest']: + item['path_full'] = os.path.join( + os.path.dirname(web_browser_json_path), + item['path'], + ) + if item['where'] == 'client': + if '?' in item['url']: + item['url'] = item['url'].split('?', 1)[0] + self.client_map[item['url']] = item + self.url_map[item['url']] = ( + item['path_full'], + mimetypes.guess_type( + item['path_full'], + )[0] or 'application/octet-stream', + ) + elif item['where'] == 'internal': + self.internal_map[item['type']] = item + + config = { + 'css': [ + {'url': item['path']} + for item in web_browser_json['manifest'] + if item['type'] == 'css' and item['where'] == 'client' + ], + 'js': [ + {'url': item['path']} + for item in web_browser_json['manifest'] + if item['type'] == 'js' and item['where'] == 'client' + ], + 'meteorRuntimeConfig': '"%s"' % ( + dumps(self.runtime_config) + ), + 'rootUrlPathPrefix': self.root_url_path_prefix, + 'bundledJsCssPrefix': self.bundled_js_css_prefix, + 'inlineScriptsAllowed': False, + 'inline': None, + 'head': read( + self.internal_map.get('head', {}).get('path_full', None), + default=u'', + ), + 'body': read( + self.internal_map.get('body', {}).get('path_full', None), + default=u'', + ), + } + tmpl_raw = read(self.template_path, encoding='utf8') + compiler = pybars.Compiler() + tmpl = compiler.compile(tmpl_raw) + self.html = '\n%s' % tmpl(config) def get(self, request, path): """Return HTML (or other related content) for Meteor.""" @@ -46,7 +207,7 @@ class MeteorView(View): content_type='text/javascript', ) try: - file_path, content_type = self.app.url_map[path] + file_path, content_type = self.url_map[path] with open(file_path, 'r') as content: return HttpResponse( content.read(), @@ -54,5 +215,5 @@ class MeteorView(View): ) except KeyError: print(path) - return HttpResponse(self.app.html) + return HttpResponse(self.html) # raise Http404