INIT
This commit is contained in:
2
.coveragerc
Normal file
2
.coveragerc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[run]
|
||||||
|
source = django_deploy
|
||||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/.idea/
|
||||||
|
|
||||||
|
/env/
|
||||||
|
|
||||||
|
*.pyc
|
||||||
|
/.coverage
|
||||||
|
/.tox/
|
||||||
|
/geckodriver.log
|
||||||
|
|
||||||
|
/src/django_deploy.egg-info/
|
||||||
27
.pylintrc
Normal file
27
.pylintrc
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[MASTER]
|
||||||
|
|
||||||
|
persistent=no
|
||||||
|
#load-plugins=pylint_django
|
||||||
|
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
|
disable=missing-docstring,
|
||||||
|
missing-module-docstring,
|
||||||
|
missing-class-docstring,
|
||||||
|
missing-function-docstring,
|
||||||
|
useless-object-inheritance,
|
||||||
|
|
||||||
|
[BASIC]
|
||||||
|
|
||||||
|
good-names=_,
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
e,
|
||||||
|
i,
|
||||||
|
j,
|
||||||
|
k,
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
|
||||||
|
max-line-length=120
|
||||||
19
README.rst
Normal file
19
README.rst
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
ABOUT
|
||||||
|
=====
|
||||||
|
This is a helper to deploy django apps.
|
||||||
|
|
||||||
|
|
||||||
|
REQUIREMENTS
|
||||||
|
============
|
||||||
|
See INSTALL.rst
|
||||||
|
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
============
|
||||||
|
See INSTALL.rst
|
||||||
|
|
||||||
|
|
||||||
|
LICENCE
|
||||||
|
=======
|
||||||
|
Permission to use, copy, modify, and/or distribute this software
|
||||||
|
for any purpose with or without fee is hereby granted.
|
||||||
91
bin/coverage-html.py
Executable file
91
bin/coverage-html.py
Executable file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import coverage
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.common.exceptions import WebDriverException
|
||||||
|
|
||||||
|
|
||||||
|
class Command(object): # pylint: disable=too-few-public-methods
|
||||||
|
default_browser = 'firefox'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _setup_argparser():
|
||||||
|
kwargs = {
|
||||||
|
'description': 'Create a coverage report from a previous coverage run and show it within a browser window.',
|
||||||
|
}
|
||||||
|
parser = argparse.ArgumentParser(**kwargs)
|
||||||
|
parser.add_argument('-k', '--keep', action='store_true', dest='keep_report',
|
||||||
|
help='keep the report after closing the browser')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def _parse_args(self, argv=None):
|
||||||
|
if argv is None:
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
|
return self._argparser.parse_args(argv)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_report_directory(path=None):
|
||||||
|
if path is None:
|
||||||
|
timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M')
|
||||||
|
dirname = 'coverage-report-{}'.format(timestamp)
|
||||||
|
path = os.path.join('tmp', dirname)
|
||||||
|
os.makedirs(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _remove_report_directory(path):
|
||||||
|
if os.path.isdir(path):
|
||||||
|
sys.stdout.write('Removing report directory {}\n'.format(path))
|
||||||
|
shutil.rmtree(path)
|
||||||
|
|
||||||
|
def _create_report(self, path):
|
||||||
|
return self._coverage.html_report(directory=path, skip_covered=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _show_report(path):
|
||||||
|
browser = webdriver.Firefox()
|
||||||
|
start_file = os.path.abspath(os.path.join(path, 'index.html'))
|
||||||
|
browser.get('file://{}'.format(start_file))
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
try:
|
||||||
|
_ = browser.window_handles
|
||||||
|
except WebDriverException:
|
||||||
|
break
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._argparser = self._setup_argparser()
|
||||||
|
self._coverage = coverage.Coverage()
|
||||||
|
self._coverage.load()
|
||||||
|
|
||||||
|
def __call__(self, argv=None):
|
||||||
|
cmd_args = self._parse_args(argv)
|
||||||
|
report_dir = self._create_report_directory()
|
||||||
|
sys.stdout.write('Report directory: {}\n'.format(report_dir))
|
||||||
|
try:
|
||||||
|
self._create_report(report_dir)
|
||||||
|
self._show_report(report_dir)
|
||||||
|
finally:
|
||||||
|
if not cmd_args.keep_report:
|
||||||
|
self._remove_report_directory(report_dir)
|
||||||
|
|
||||||
|
return os.EX_OK
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
cmd = Command()
|
||||||
|
exitval = cmd()
|
||||||
|
sys.exit(exitval)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
10
bin/django-deploy.py
Executable file
10
bin/django-deploy.py
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'src'))
|
||||||
|
|
||||||
|
from django_deploy import main
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
83
setup.py
Normal file
83
setup.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
from setuptools import Command
|
||||||
|
|
||||||
|
|
||||||
|
class MyCommand(Command):
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePythonEnvironment(MyCommand):
|
||||||
|
description = 'create a (virtual) python environment'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run():
|
||||||
|
python_bin = sys.executable if sys.executable else 'python'
|
||||||
|
python_ver = sys.version_info.major
|
||||||
|
if python_ver == 2:
|
||||||
|
path = os.path.join('env', 'python2')
|
||||||
|
symlink_path = os.path.join('env', 'python')
|
||||||
|
venv_module = 'virtualenv'
|
||||||
|
prompt = '(py2) '
|
||||||
|
elif python_ver == 3:
|
||||||
|
path = os.path.join('env', 'python3')
|
||||||
|
symlink_path = os.path.join('env', 'python')
|
||||||
|
venv_module = 'venv'
|
||||||
|
prompt = 'py3'
|
||||||
|
else:
|
||||||
|
sys.stderr.write('Python {} is not supported.\n'.format(python_ver))
|
||||||
|
sys.exit(os.EX_USAGE)
|
||||||
|
|
||||||
|
sys.stdout.write('Creating new python environment in {path}\n'.format(path=path))
|
||||||
|
cmd = ('{bin} -m {venv_module}'
|
||||||
|
' --prompt="{prompt}"'
|
||||||
|
' {path}'.format(bin=python_bin, path=path,
|
||||||
|
venv_module=venv_module, prompt=prompt))
|
||||||
|
os.system(cmd)
|
||||||
|
|
||||||
|
if symlink_path and symlink_path != path and not os.path.exists(symlink_path):
|
||||||
|
symlink_dir = os.path.dirname(symlink_path)
|
||||||
|
relpath = os.path.relpath(path, symlink_dir)
|
||||||
|
os.symlink(relpath, symlink_path)
|
||||||
|
|
||||||
|
print('')
|
||||||
|
print('Depending on your operating system or command shell,')
|
||||||
|
print('you should activate the new environment for this shell session')
|
||||||
|
print('by running ONE of the following commands:')
|
||||||
|
print('- Windows: %s' % os.path.join(path, 'Scripts', 'activate'))
|
||||||
|
print('- C Shell: source %s/bin/activate.csh' % path)
|
||||||
|
print('- All others: source %s/bin/activate' % path)
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='django-deploy',
|
||||||
|
version='0.1.dev0',
|
||||||
|
description='Helper to deploy django apps.',
|
||||||
|
url='https://dev.heinzelwerk.de/git/python/django-deploy',
|
||||||
|
maintainer='Jens Kleineheismann',
|
||||||
|
maintainer_email='heinzel@farbemachtstark.de',
|
||||||
|
cmdclass={
|
||||||
|
'python': CreatePythonEnvironment,
|
||||||
|
},
|
||||||
|
packages=find_packages('src'),
|
||||||
|
package_dir={'': 'src'},
|
||||||
|
#include_package_data=True,
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'django-deploy.py = django_deploy:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
install_requires=[
|
||||||
|
'django',
|
||||||
|
],
|
||||||
|
)
|
||||||
1
src/django_deploy/__init__.py
Normal file
1
src/django_deploy/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .main import main
|
||||||
1
src/django_deploy/config.py
Normal file
1
src/django_deploy/config.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DJANGO_SETTINGS_MODULE_NAME = 'main'
|
||||||
1
src/django_deploy/django_settings.py
Normal file
1
src/django_deploy/django_settings.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# ADD_INSTALLED_APPS = ['django_deploy']
|
||||||
9
src/django_deploy/main.py
Normal file
9
src/django_deploy/main.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
from .program import Program
|
||||||
|
|
||||||
|
|
||||||
|
def main(*args, **kwargs):
|
||||||
|
program = Program()
|
||||||
|
exitval = program(*args, **kwargs)
|
||||||
|
sys.exit(exitval)
|
||||||
114
src/django_deploy/program.py
Normal file
114
src/django_deploy/program.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .config import DJANGO_SETTINGS_MODULE_NAME
|
||||||
|
from .version import VERSION
|
||||||
|
|
||||||
|
|
||||||
|
class Program(object): # pylint: disable=too-few-public-methods
|
||||||
|
@staticmethod
|
||||||
|
def _setup_argparser(parser):
|
||||||
|
parser.add_argument('-V', '--version', action='version',
|
||||||
|
version='%(prog)s ' + VERSION)
|
||||||
|
|
||||||
|
parser.add_argument('-a', '--app',
|
||||||
|
action='append', dest='merge_apps', metavar='MODULE',
|
||||||
|
help='Merge settings from the django app MODULE\n'
|
||||||
|
'Can be used multiple times')
|
||||||
|
|
||||||
|
parser.add_argument('-c', '--create',
|
||||||
|
action='store_true', dest='create',
|
||||||
|
help='Create the django project directory')
|
||||||
|
|
||||||
|
parser.add_argument('project_dir', metavar='PATH',
|
||||||
|
help='The directory, where the django project is or will be installed.')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def _parse_args(self, argv=None):
|
||||||
|
if argv is None:
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
if not argv:
|
||||||
|
argv = ()
|
||||||
|
|
||||||
|
return self._argparser.parse_args(argv)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _install_django_files(project_dir, overwrite=False):
|
||||||
|
if os.path.exists(project_dir):
|
||||||
|
if not overwrite:
|
||||||
|
sys.stderr.write('directory already exists: {}\n'.format(project_dir))
|
||||||
|
return os.EX_NOPERM
|
||||||
|
else:
|
||||||
|
os.makedirs(project_dir)
|
||||||
|
|
||||||
|
settings_module = DJANGO_SETTINGS_MODULE_NAME
|
||||||
|
cmd = 'django-admin startproject {name} {path}'.format(name=settings_module,
|
||||||
|
path=project_dir)
|
||||||
|
sys.stdout.write('Installing django files to {path}\n'.format(path=project_dir))
|
||||||
|
exitval = os.system(cmd)
|
||||||
|
return exitval
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_installed_apps_from_app(project_dir, app):
|
||||||
|
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_MODULE_NAME)
|
||||||
|
|
||||||
|
settings_from_app = importlib.import_module('{}.django_settings'.format(app))
|
||||||
|
if hasattr(settings_from_app, 'ADD_INSTALLED_APPS'):
|
||||||
|
add_apps = settings_from_app.ADD_INSTALLED_APPS
|
||||||
|
else:
|
||||||
|
add_apps = []
|
||||||
|
|
||||||
|
if not add_apps:
|
||||||
|
sys.stdout.write('{}: do not care about INSTALLED_APPS\n'.format(app))
|
||||||
|
return os.EX_OK
|
||||||
|
|
||||||
|
sys.path.insert(0, settings_dir)
|
||||||
|
settings = importlib.import_module('settings')
|
||||||
|
sys.path.pop(0)
|
||||||
|
|
||||||
|
missing_apps = []
|
||||||
|
for app in add_apps:
|
||||||
|
if app not in settings.INSTALLED_APPS:
|
||||||
|
missing_apps.append(app)
|
||||||
|
|
||||||
|
if missing_apps:
|
||||||
|
sys.stdout.write('{app}: adding {apps} to INSTALLED_APPS\n'.format(app=app, apps=missing_apps))
|
||||||
|
comment = '### {app}: added apps with django-deploy at {timestamp}'
|
||||||
|
code = 'INSTALLED_APPS += [\n'
|
||||||
|
for app in missing_apps:
|
||||||
|
code += ' \'{}\',\n'.format(app)
|
||||||
|
code += ']\n'
|
||||||
|
|
||||||
|
append_text = '\n' + comment + '\n' + code + '\n'
|
||||||
|
settings_file = os.path.join(settings_dir, 'settings.py')
|
||||||
|
with open(settings_file, 'a') as f:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
timestamp = now.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
f.write(append_text.format(app=app, timestamp=timestamp))
|
||||||
|
else:
|
||||||
|
sys.stdout.write('{app}: INSTALLED_APPS is fine\n'.format(app=app))
|
||||||
|
|
||||||
|
return os.EX_OK
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._argparser = argparse.ArgumentParser()
|
||||||
|
self._setup_argparser(self._argparser)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
argv = kwargs.get('argv', None)
|
||||||
|
cmd_args = self._parse_args(argv)
|
||||||
|
exitval = os.EX_OK
|
||||||
|
if cmd_args.create:
|
||||||
|
exitval = self._install_django_files(cmd_args.project_dir)
|
||||||
|
if exitval != os.EX_OK:
|
||||||
|
return exitval
|
||||||
|
if cmd_args.merge_apps:
|
||||||
|
for app in cmd_args.merge_apps:
|
||||||
|
exitval = self._add_installed_apps_from_app(cmd_args.project_dir, app)
|
||||||
|
if exitval != os.EX_OK:
|
||||||
|
return exitval
|
||||||
|
return exitval
|
||||||
0
src/django_deploy/tests/__init__.py
Normal file
0
src/django_deploy/tests/__init__.py
Normal file
1
src/django_deploy/tests/django_settings.py
Normal file
1
src/django_deploy/tests/django_settings.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ADD_INSTALLED_APPS = ['fake_app1', 'fake_app2']
|
||||||
37
src/django_deploy/tests/test_main.py
Normal file
37
src/django_deploy/tests/test_main.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .. import main
|
||||||
|
|
||||||
|
|
||||||
|
class MainTestCase(unittest.TestCase):
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def capsys(self, capsys): # pylint: disable=method-hidden
|
||||||
|
self.capsys = capsys
|
||||||
|
|
||||||
|
def test_main_without_arguments(self):
|
||||||
|
exitval = os.EX_SOFTWARE
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except SystemExit as e:
|
||||||
|
exitval = e.code
|
||||||
|
finally:
|
||||||
|
captured = self.capsys.readouterr()
|
||||||
|
|
||||||
|
is_usage = captured.err.startswith('usage:')
|
||||||
|
self.assertTrue(is_usage, 'main() does not produce usage text')
|
||||||
|
self.assertNotEqual(os.EX_OK, exitval)
|
||||||
|
|
||||||
|
def test_main_help(self):
|
||||||
|
exitval = os.EX_SOFTWARE
|
||||||
|
try:
|
||||||
|
main(argv=['-h'])
|
||||||
|
except SystemExit as e:
|
||||||
|
exitval = e.code
|
||||||
|
finally:
|
||||||
|
captured = self.capsys.readouterr()
|
||||||
|
|
||||||
|
is_usage = captured.out.startswith('usage:')
|
||||||
|
self.assertTrue(is_usage, 'main() does not produce usage text')
|
||||||
|
self.assertEqual(os.EX_OK, exitval)
|
||||||
69
src/django_deploy/tests/test_program.py
Normal file
69
src/django_deploy/tests/test_program.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..config import DJANGO_SETTINGS_MODULE_NAME
|
||||||
|
from ..program import Program
|
||||||
|
|
||||||
|
|
||||||
|
class MainTestCase(unittest.TestCase):
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def tmpdir(self, tmpdir): # pylint: disable=method-hidden
|
||||||
|
self.tmpdir = tmpdir
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self._program = Program()
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
tmpdir = self.tmpdir
|
||||||
|
project_dir = os.path.join(str(tmpdir), 'env', 'django')
|
||||||
|
|
||||||
|
exitval = self._program(argv=['-c', project_dir])
|
||||||
|
|
||||||
|
self.assertEqual(os.EX_OK, exitval, 'program() does not return os.EX_OK')
|
||||||
|
self.assertTrue(os.path.isdir(project_dir), 'project directory was not created')
|
||||||
|
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_MODULE_NAME)
|
||||||
|
self.assertTrue(os.path.isdir(settings_dir), 'settings directory was not created')
|
||||||
|
settings_file = os.path.join(settings_dir, 'settings.py')
|
||||||
|
self.assertTrue(os.path.isfile(settings_file), 'settings.py was not created')
|
||||||
|
manage_script = os.path.join(project_dir, 'manage.py')
|
||||||
|
self.assertTrue(os.path.isfile(manage_script), 'manage.py was not created')
|
||||||
|
|
||||||
|
def test_create_dont_overwrite(self):
|
||||||
|
tmpdir = self.tmpdir
|
||||||
|
project_dir = os.path.join(str(tmpdir), 'env', 'django')
|
||||||
|
|
||||||
|
self._program(argv=['--create', project_dir])
|
||||||
|
exitval = self._program(argv=['--create', project_dir])
|
||||||
|
|
||||||
|
self.assertEqual(os.EX_NOPERM, exitval, 'second call to program() does not exit with os.EX_NOPERM')
|
||||||
|
|
||||||
|
def test_merge_installed_apps(self):
|
||||||
|
tmpdir = self.tmpdir
|
||||||
|
project_dir = os.path.join(str(tmpdir), 'env', 'django')
|
||||||
|
|
||||||
|
expected_installed_apps = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'fake_app1',
|
||||||
|
'fake_app2',
|
||||||
|
]
|
||||||
|
|
||||||
|
argv = ['-c', project_dir]
|
||||||
|
self._program(argv=argv)
|
||||||
|
|
||||||
|
argv = ['-a', 'django_deploy', '-a', 'django_deploy.tests', project_dir]
|
||||||
|
self._program(argv=argv)
|
||||||
|
|
||||||
|
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_MODULE_NAME)
|
||||||
|
sys.path.insert(0, settings_dir)
|
||||||
|
settings = importlib.import_module('settings')
|
||||||
|
importlib.reload(settings)
|
||||||
|
sys.path.pop(0)
|
||||||
|
self.assertListEqual(expected_installed_apps, settings.INSTALLED_APPS)
|
||||||
1
src/django_deploy/version.py
Normal file
1
src/django_deploy/version.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VERSION = '0.1.dev0'
|
||||||
Reference in New Issue
Block a user