From 4a6047607fad40e9d8d62c415d6ab24120e3a203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Tue, 14 Jun 2016 21:52:47 +0200 Subject: [PATCH] general: revamp static file serving to use external modules (#11355) --- wcs/ctl/collectstatic.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ wcs/root.py | 56 ++++++++++++++++++++++++++++++++------- 2 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 wcs/ctl/collectstatic.py diff --git a/wcs/ctl/collectstatic.py b/wcs/ctl/collectstatic.py new file mode 100644 index 0000000..666f77e --- /dev/null +++ b/wcs/ctl/collectstatic.py @@ -0,0 +1,68 @@ +# w.c.s. - web application for online forms +# Copyright (C) 2005-2016 Entr'ouvert +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +import os +import shutil + +from qommon.ctl import Command, make_option + + +class CmdCollectStatic(Command): + name = 'collectstatic' + + def __init__(self): + Command.__init__(self, [ + make_option('-c', '--clear', action='store_true', + dest='clear', default=False), + make_option('-l', '--link', action='store_true', + dest='link', default=False), + ]) + + def execute(self, base_options, sub_options, args): + import publisher + publisher.WcsPublisher.configure(self.config) + pub = publisher.WcsPublisher.create_publisher( + register_cron=False, register_tld_names=False) + return self.collecstatic(pub, + clear=sub_options.clear, link=sub_options.link) + + def collecstatic(self, pub, clear=False, link=False): + root_directory_class = pub.root_directory_class + static_dir = os.path.join(pub.app_dir, 'static') + if clear and os.path.exists(static_dir): + shutil.rmtree(static_dir) + if not os.path.exists(static_dir): + os.mkdir(static_dir) + for prefix in root_directory_class.static_directories: + for directory in root_directory_class.resolve_static_directories(prefix): + if not os.path.exists(directory): + continue + real_prefix = prefix.replace('_', '/') # xstatic hack + dst_base = os.path.join(static_dir, real_prefix) + for basedir, dirnames, filenames in os.walk(directory): + for filename in filenames: + dst_path = os.path.join(dst_base, basedir[len(directory)+1:]) + dst_filename = os.path.join(dst_path, filename) + if not os.path.exists(dst_path): + os.makedirs(dst_path) + if os.path.exists(dst_filename): + os.unlink(dst_filename) + if link: + os.symlink(os.path.join(basedir, filename), dst_filename) + else: + shutil.copy(os.path.join(basedir, filename), dst_filename) + +CmdCollectStatic.register() diff --git a/wcs/root.py b/wcs/root.py index 810175f..1ca99cd 100644 --- a/wcs/root.py +++ b/wcs/root.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see . +from importlib import import_module import json import os import re @@ -207,6 +208,17 @@ class RootDirectory(Directory): pages = qommon.pages.PagesDirectory() fargo = file_validation.FargoDirectory() + static_directories = { + 'css': ['web/css'], + 'images': ['web/images'], + 'themes': ['web/themes'], + 'qo': ['qommon'], + # maps /leaflet/ to the directory provided by the libjs-openlayers package + 'leaflet': ['/usr/share/javascript/leaflet'], + 'static': ['django:gadjo'], + 'static_xstatic': ['xstatic:jquery', 'xstatic:font_awesome'], + } + def tryauth(self): return forms.root.tryauth(get_publisher().get_root_url()) @@ -287,6 +299,14 @@ class RootDirectory(Directory): if not self.backoffice: self.backoffice = get_publisher().backoffice_directory_class() + if path[:2] == ['static', 'xstatic']: + # hack path so it's easier to lookup files from xstatic modules + # (the gadjo xstatic storage adds a "xstatic" prefix that is not on + # the filesystem, while StaticDirectory expects the filesystem + # layout to match exactly; so we merge the first two elements in a + # single one, so we get the real path in path[1:]) + path = ['static_xstatic'] + path[2:] + try: return Directory._q_traverse(self, path) except errors.TraversalError: @@ -294,16 +314,34 @@ class RootDirectory(Directory): return forms.root.RootDirectory()._q_traverse(path) - def _q_lookup(self, component): - if component in ('css','images'): - return StaticDirectory(os.path.join(get_publisher().data_dir, 'web', component), follow_symlinks = True) - if component == 'qo': - dirname = os.path.join(get_publisher().data_dir, 'qommon') - return StaticDirectory(dirname, follow_symlinks = True) + @classmethod + def resolve_static_directories(cls, prefix): + directories = cls.static_directories[prefix] + for directory in directories: + if directory[0] == '/': + yield directory + elif not ':' in directory: + yield os.path.join(get_publisher().data_dir, directory) + else: + directory_type, value = directory.split(':') + if directory_type == 'xstatic': + module = import_module('xstatic.pkg.%s' % value) + yield module.BASE_DIR + elif directory_type == 'django': + module = import_module(value) + yield os.path.join(os.path.dirname(module.__file__), 'static') + + def serve_statics(self, component): + for directory in self.resolve_static_directories(component): + try: + return StaticDirectory(directory, follow_symlinks=True) + except errors.TraversalError: + pass + raise errors.TraversalError() - # maps /leaflet/ to the directory provided by the libjs-openlayers package - if component == 'leaflet': - return StaticDirectory('/usr/share/javascript/leaflet') + def _q_lookup(self, component): + if component in self.static_directories: + return self.serve_statics(component) # is this a category ? try: -- 2.8.1