mirror of
https://github.com/zulip/zulip.git
synced 2025-10-24 00:23:49 +00:00
Zulip's previous model for managing static asset files via Django pipeline had some broken behavior around upgrades. In particular, it was for some reason storing the information as to which static files should be used in a memcached cache that was shared between different deployments of Zulip. This means that during the upgrade process, some clients might be served a version of the static assets that does not correspond to the server they were connected to. We've replaced that model with using ManifestStaticFilesStorage, which instead allows each Zulip deployment directory to have its own complete copy of the mapping of files to static assets, as it should be. We have to do a little bit of hackery with the staticfiles.json path to make this work, basically because Django expects staticfiles.json to be under STATIC_ROOT (aka the path nginx is serving to users), but doing that doesn't really make sense for Zulip, since that directory is shared between different deployments.
100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
from __future__ import absolute_import
|
|
|
|
import os
|
|
import shutil
|
|
from typing import List, Any, Tuple
|
|
|
|
from django.conf import settings
|
|
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
|
from pipeline.storage import PipelineMixin
|
|
|
|
class AddHeaderMixin(object):
|
|
def post_process(self, paths, dry_run=False, **kwargs):
|
|
# type: (Dict[str, Tuple[ZulipStorage, str]], bool, **Any) -> List[Tuple[str, str, bool]]
|
|
if dry_run:
|
|
return []
|
|
|
|
with open(settings.STATIC_HEADER_FILE, 'rb') as header_file:
|
|
header = header_file.read().decode(settings.FILE_CHARSET)
|
|
|
|
# A dictionary of path to tuples of (old_path, new_path,
|
|
# processed). The return value of this method is the values
|
|
# of this dictionary
|
|
ret_dict = {}
|
|
|
|
for name in paths:
|
|
storage, path = paths[name]
|
|
|
|
if not path.startswith('min/') or not path.endswith('.css'):
|
|
ret_dict[path] = (path, path, False)
|
|
continue
|
|
|
|
# Prepend the header
|
|
with storage.open(path, 'rb') as orig_file:
|
|
orig_contents = orig_file.read().decode(settings.FILE_CHARSET)
|
|
|
|
storage.delete(path)
|
|
|
|
with storage.open(path, 'w') as new_file:
|
|
new_file.write(header + orig_contents)
|
|
|
|
ret_dict[path] = (path, path, True)
|
|
|
|
super_class = super(AddHeaderMixin, self) # type: ignore # https://github.com/JukkaL/mypy/issues/857
|
|
if hasattr(super_class, 'post_process'):
|
|
super_ret = super_class.post_process(paths, dry_run, **kwargs)
|
|
else:
|
|
super_ret = []
|
|
|
|
# Merge super class's return value with ours
|
|
for val in super_ret:
|
|
old_path, new_path, processed = val
|
|
if processed:
|
|
ret_dict[old_path] = val
|
|
|
|
return list(ret_dict.values())
|
|
|
|
|
|
class RemoveUnminifiedFilesMixin(object):
|
|
def post_process(self, paths, dry_run=False, **kwargs):
|
|
# type: (Dict[str, Tuple[ZulipStorage, str]], bool, **Any) -> List[Tuple[str, str, bool]]
|
|
if dry_run:
|
|
return []
|
|
|
|
root = settings.STATIC_ROOT
|
|
to_remove = ['templates', 'styles', 'js']
|
|
|
|
for tree in to_remove:
|
|
shutil.rmtree(os.path.join(root, tree))
|
|
|
|
is_valid = lambda p: all([not p.startswith(k) for k in to_remove])
|
|
|
|
paths = {k: v for k, v in paths.items() if is_valid(k)}
|
|
super_class = super(RemoveUnminifiedFilesMixin, self) # type: ignore # https://github.com/JukkaL/mypy/issues/857
|
|
if hasattr(super_class, 'post_process'):
|
|
return super_class.post_process(paths, dry_run, **kwargs)
|
|
|
|
return []
|
|
|
|
if settings.PRODUCTION:
|
|
# This is a hack to use staticfiles.json from within the
|
|
# deployment, rather than a directory under STATIC_ROOT. By doing
|
|
# so, we can use a different copy of staticfiles.json for each
|
|
# deployment, which ensures that we always use the correct static
|
|
# assets for each deployment.
|
|
ManifestStaticFilesStorage.manifest_name = os.path.join(settings.DEPLOY_ROOT,
|
|
"staticfiles.json")
|
|
orig_path = ManifestStaticFilesStorage.path
|
|
|
|
def path(self, name):
|
|
# type: (Any, str) -> str
|
|
if name == ManifestStaticFilesStorage.manifest_name:
|
|
return name
|
|
return orig_path(self, name)
|
|
ManifestStaticFilesStorage.path = path
|
|
|
|
class ZulipStorage(PipelineMixin,
|
|
AddHeaderMixin, RemoveUnminifiedFilesMixin,
|
|
ManifestStaticFilesStorage):
|
|
pass
|