Add JavaScript manager.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2018-04-05 03:29:28 -04:00
parent 94bdea3c69
commit b7b1a87f23
8 changed files with 197 additions and 1 deletions

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ static_collected/
/mayan/media/static/ /mayan/media/static/
/venv/ /venv/
/whoosh_index/ /whoosh_index/
node_modules/

View File

@@ -133,7 +133,9 @@
of specifying their default path. of specifying their default path.
- Unify checkbox selection code for list items and table items. - Unify checkbox selection code for list items and table items.
- Add smart checkbox manager. - Add smart checkbox manager.
- Update Chart.js version.
- Improve line chart appearance. Fix mouse hover label issue.
- Add JavaScript dependency manager.
2.7.3 (2017-09-11) 2.7.3 (2017-09-11)
================== ==================

View File

@@ -310,6 +310,22 @@ This will select the first, the last and all items in between. To deselect multi
items the same procedure is used. This code was donated by the Paperattor items the same procedure is used. This code was donated by the Paperattor
project (www.paperattor.com). project (www.paperattor.com).
Add JavaScript dependency manager
---------------------------------
An internal utility to install and upgrade the JavaScript dependencies was added.
This depency manager allows for the easier maintenace of the JavaScript libraries
used through the project.
Previously JavaScript libraries we downloaded and installed by manually. These
libraries were them checked into the Git repository. Finally to enable them
the correspoding imports were added to the base templates in the apppeance app.
This new manager is the first step to start resolving these issues. The manager
allows apps to specify their own dependencies. These dependecies are then
downloaded when the project is installed or upgraded. As such they are not
part of the repository and lower the file size of the project.
Other changes worth mentioning Other changes worth mentioning
------------------------------ ------------------------------
- Add Makefile target to check the format of the README.rst file. - Add Makefile target to check the format of the README.rst file.
@@ -408,6 +424,9 @@ Other changes worth mentioning
- Sort permission namespaces and permissions in the role permission views. - Sort permission namespaces and permissions in the role permission views.
- Invert the columns in the ACL detail view. - Invert the columns in the ACL detail view.
- Remove the data filters feature. - Remove the data filters feature.
- Update Chart.js version.
- Improve line chart appearance. Fix issue with mouse over labels next other chart margin.
Removals Removals
-------- --------

View File

@@ -14,3 +14,11 @@ class NotLatestVersion(BaseCommonException):
""" """
def __init__(self, upstream_version): def __init__(self, upstream_version):
self.upstream_version = upstream_version self.upstream_version = upstream_version
class NPMException(BaseCommonException):
"""Base exception for the NPM registry client"""
class NPMPackgeIntegrityError(NPMException):
"""Hash mismatch exception"""

View File

@@ -0,0 +1,150 @@
from __future__ import unicode_literals
import base64
import hashlib
import json
import os
import shutil
import sys
import tarfile
from furl import furl
import requests
from django.apps import apps
from .exceptions import NPMException, NPMPackgeIntegrityError
class NPMPackage(object):
def __init__(self, registry, name, version):
self.registry = registry
self.name = name
self.version = version
def _download(self):
with requests.get(self.metadata['dist']['tarball'], stream=True) as response:
with open(name=self.tar_file_path, mode='wb') as file_object:
file_object.write(response.content)
try:
upstream_algorithm_name, upstream_integrity_value = self.metadata['dist']['integrity'].split('-', 1)
except KeyError:
upstream_algorithm_name = 'sha1'
upstream_integrity_value = self.metadata['dist']['shasum']
algorithms = {
'sha1': lambda data: hashlib.sha1(data).hexdigest(),
'sha256': lambda data: base64.b64encode(hashlib.sha256(data).digest()),
'sha512': lambda data: base64.b64encode(hashlib.sha512(data).digest()),
}
try:
algorithm = algorithms[upstream_algorithm_name]
except KeyError:
raise NPMException('Unknown hash algorithm: {}'.format(upstream_algorithm_name))
with open(name=self.tar_file_path, mode='rb') as file_object:
integrity_value = algorithm(file_object.read())
if integrity_value != upstream_integrity_value:
os.unlink(self.tar_file_path)
raise NPMPackgeIntegrityError(
'Hash of downloaded package doesn\'t match online version.'
)
def _extract(self):
shutil.rmtree(
os.path.join(
self.registry.module_directory, self.name
), ignore_errors=True
)
with tarfile.open(name=self.tar_file_path, mode='r') as file_object:
file_object.extractall(path=self.registry.module_directory)
os.rename(
os.path.join(self.registry.module_directory, 'package'),
os.path.join(self.registry.module_directory, self.name)
)
def install(self):
print 'Installing package: {}@{}'.format(self.name, self.version)
self._download()
self._extract()
for name, version in self.metadata.get('dependencies', {}).items():
package = NPMPackage(registry=self.registry, name=name, version=version[1:])
package.install()
@property
def tar_filename(self):
if not hasattr(self, '_tar_filename'):
self._tar_filename = furl(self.metadata['dist']['tarball']).path.segments[-1]
return self._tar_filename
@property
def tar_file_path(self):
if not hasattr(self, '_tar_file_path'):
self._tar_file_path = os.path.join(self.registry.cache_path, self.tar_filename)
return self._tar_file_path
@property
def metadata(self):
if not hasattr(self, '_metadata'):
self._metadata = requests.get(url=self.get_url()).json()
return self._metadata
def get_url(self):
f = furl(self.registry.url)
f.path.segments = f.path.segments + [self.name, self.version]
return f.tostr()
class NPMRegistry(object):
DEFAULT_CACHE_PATH = '/tmp'
DEFAULT_REGISTRY_URL = 'http://registry.npmjs.com'
DEFAULT_MODULE_DIRECTORY = 'node_modules'
DEFAULT_PACKAGE_FILENAME = 'package.json'
DEFAULT_LOCK_FILENAME = 'package-lock.json'
def __init__(self, url=None, cache_path=None, module_directory=None, package_filename=None, lock_filename=None):
self.url = url or self.DEFAULT_REGISTRY_URL
self.cache_path = cache_path or self.DEFAULT_CACHE_PATH
self.module_directory = module_directory or self.DEFAULT_MODULE_DIRECTORY
self.package_file = package_filename or self.DEFAULT_PACKAGE_FILENAME
self.lock_filename = lock_filename or self.DEFAULT_LOCK_FILENAME
def _install_package(self, name, version):
package = NPMPackage(registry=self, name=name, version=version)
package.install()
def _read_package(self):
with open(self.package_file) as file_object:
self._package_data = json.loads(file_object.read())
def install(self, package=None):
if package:
name, version = package.split('@')
self._install_package(name=name, version=version)
else:
self._read_package()
for name, version in self._package_data['dependencies'].items():
self._install_package(name=name, version=version[1:])
class JSDependencyManager(object):
def install(self):
for app in apps.get_app_configs():
for root, dirs, files in os.walk(os.path.join(app.path, 'static')):
if 'package.json' in files and not any(map(lambda x: x in root, ['node_modules', 'packages', 'vendors'])):
print 'Installing JavaScript packages for app: {} - {}'.format(app.label, root)
npm_client = NPMRegistry(
module_directory=os.path.join(root, 'node_modules'),
package_filename=os.path.join(root, 'package.json')
)
npm_client.install()

View File

@@ -11,5 +11,6 @@ class Command(management.BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
management.call_command('createsettings', interactive=False) management.call_command('createsettings', interactive=False)
pre_initial_setup.send(sender=self) pre_initial_setup.send(sender=self)
management.call_command('installjavascript', interactive=False)
management.call_command('createautoadmin', interactive=False) management.call_command('createautoadmin', interactive=False)
post_initial_setup.send(sender=self) post_initial_setup.send(sender=self)

View File

@@ -0,0 +1,13 @@
from __future__ import unicode_literals
from django.core import management
from ...javascript import JSDependencyManager
class Command(management.BaseCommand):
help = 'Install JavaScript dependencies.'
def handle(self, *args, **options):
js_manager = JSDependencyManager()
js_manager.install()

View File

@@ -17,6 +17,8 @@ class Command(management.BaseCommand):
'Error during pre_upgrade signal: %s' % exception 'Error during pre_upgrade signal: %s' % exception
) )
management.call_command('installjavascript', interactive=False)
try: try:
perform_upgrade.send(sender=self) perform_upgrade.send(sender=self)
except Exception as exception: except Exception as exception: