It is nice now.
All checks were successful
buildbot/tox Build done.

This commit is contained in:
2019-11-16 18:50:53 +01:00
parent 18613d3600
commit f1f0c585b9
21 changed files with 750 additions and 408 deletions

View File

@@ -61,7 +61,7 @@ class CreatePythonEnvironment(MyCommand):
setup(
name='django-deploy',
version='0.1.dev0',
version='0.2.dev0',
description='Helper to deploy django apps.',
url='https://dev.heinzelwerk.de/git/python/django-deploy',
maintainer='Jens Kleineheismann',

View File

@@ -1,2 +1,2 @@
from .config import get_installed_apps, get_urlpatterns
from .hooks import get_installed_apps, get_urlpatterns
from .main import main

View File

@@ -1,16 +1,85 @@
import errno
import importlib
import json
import os
import sys
from .base_types import OrderedDict
from .config import DJANGO_SETTINGS_DIR
from .exceptions import DjangoDeployError, FatalError
from .exceptions import DjangoDeployError
class DjangoProjectHooksConfig(OrderedDict):
def load(self, path=None):
if path is None:
path = self._json_file
if os.path.exists(path):
with open(path, 'r') as f:
items = json.load(f)
for item in items:
app_name = item.pop('APP')
self[app_name] = item
else:
self._clear()
def write(self, path=None):
if path is None:
path = self._json_file
with open(path, 'w') as f:
items = []
for key in self:
item = self[key]
item['APP'] = key
items.append(item)
json.dump(items, f, indent=4)
def __init__(self, project_dir=None, settings_dir=None):
assert (project_dir or settings_dir), 'DeployedAppsConfig(): ' \
'Either keyword argument project_dir or settings_dir' \
' must be set.'
assert (not (project_dir and settings_dir)), 'DeployedAppsConfig(): ' \
'Keyword arguments project_dir and settings_dir' \
' are mutually exclusive.'
super(DjangoProjectHooksConfig, self).__init__()
if project_dir is not None:
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
self._json_file = os.path.join(settings_dir, 'django_deploy.json')
self.load()
class DjangoProject(object):
def __init__(self, project_dir):
self._project_dir = project_dir
_hooks_config_name = 'django_deploy_hooks'
def create(self):
@staticmethod
def _append_to_pythonfile(path, text):
py2_cache = path + 'c'
with open(path, 'a') as f:
f.write(text)
if os.path.isfile(py2_cache):
os.unlink(py2_cache) # pragma: no cover
def _get_hooks_config_from_app(self, module_path, hooks_config_name=None):
if hooks_config_name is None:
hooks_config_name = self._hooks_config_name
try:
app_obj = importlib.import_module(module_path)
except ImportError as e:
raise DjangoDeployError(e, code=errno.ENOPKG)
try:
hooks_config = getattr(app_obj, hooks_config_name)
except AttributeError:
try:
hooks_config = importlib.import_module('{}.{}'.format(module_path, hooks_config_name))
except ImportError as e:
raise DjangoDeployError('In {}: {}'.format(module_path, e), code=errno.ENOENT)
return hooks_config
def create(self, install_hooks=True):
project_dir = self._project_dir
if not os.path.exists(project_dir):
@@ -25,13 +94,74 @@ class DjangoProject(object):
sys.stdout.write('Installing django files to {path}\n'.format(path=project_dir))
exitval = os.system(cmd)
if exitval != os.EX_OK: # pragma: no cover
raise FatalError(exitval=exitval)
raise DjangoDeployError(exitval=exitval)
if install_hooks:
self.install_hooks()
def install_hooks(self):
pass
project_dir = self._project_dir
def add_app(self, module):
pass
if not os.path.exists(project_dir):
raise DjangoDeployError('No such project directory: {}'.format(project_dir),
code=errno.ENOENT)
def mount_app(self, module, route):
pass
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
settings_file = os.path.join(settings_dir, 'settings.py')
urlconf_file = os.path.join(settings_dir, 'urls.py')
json_file = os.path.join(settings_dir, 'django_deploy.json')
if not os.path.exists(settings_dir):
raise DjangoDeployError('Existing project directory'
' does not contain django project: {}'.format(project_dir),
code=errno.EISDIR)
if os.path.exists(json_file):
raise DjangoDeployError('file already exists: {}'.format(json_file), code=errno.EEXIST)
config = DjangoProjectHooksConfig(project_dir=project_dir)
config.write()
text = '\n'
text += '# django-deploy\n'
text += 'from django_deploy import get_installed_apps\n'
text += 'INSTALLED_APPS = get_installed_apps(__file__, INSTALLED_APPS)\n'
text += '\n'
self._append_to_pythonfile(settings_file, text)
text = '\n'
text += '# django-deploy\n'
text += 'from django_deploy import get_urlpatterns\n'
text += 'urlpatterns = get_urlpatterns(__file__, urlpatterns)\n'
text += '\n'
self._append_to_pythonfile(urlconf_file, text)
def install_app(self, module_path, hooks_config_name=None):
hooks_config = self._get_hooks_config_from_app(module_path, hooks_config_name)
installed_apps = getattr(hooks_config, 'INSTALLED_APPS', [])
config = DjangoProjectHooksConfig(project_dir=self._project_dir)
if module_path not in config:
config[module_path] = {}
config[module_path]['INSTALLED_APPS'] = installed_apps
config.write()
def mount_app(self, module_path, route, hooks_config_name=None):
self.install_app(module_path, hooks_config_name=hooks_config_name)
hooks_config = self._get_hooks_config_from_app(module_path, hooks_config_name)
urlconf = getattr(hooks_config, 'ROOT_URLCONF', None)
if urlconf is None:
raise DjangoDeployError('django deploy hooks from {} has no ROOT_URLCONF'.format(module_path),
code=errno.ENOLINK)
config = DjangoProjectHooksConfig(project_dir=self._project_dir)
if module_path not in config: # pragma: no cover
config[module_path] = {}
config[module_path]['MOUNT'] = [route, urlconf]
config.write()
def __init__(self, project_dir):
self._project_dir = project_dir

View File

@@ -0,0 +1,29 @@
class OrderedDict(object):
def _clear(self):
self._items = {}
self._order = []
def __init__(self):
self._items = {}
self._order = []
def __contains__(self, key):
return key in self._items
def __getitem__(self, key):
return self._items[key]
def __setitem__(self, key, value):
self._items[key] = value
if key not in self._order:
self._order.append(key)
def __delitem__(self, key):
del self._items[key]
self._order.remove(key)
def __iter__(self):
return iter(self._order)
def __len__(self):
return len(self._items)

View File

@@ -1,102 +1 @@
try:
from collections.abc import MutableMapping
except ImportError: # pragma: no cover
from collections import MutableMapping
import json
import os
from django.conf.urls import url, include
DJANGO_SETTINGS_DIR = 'main'
class _BaseDict(MutableMapping):
def __init__(self, *args, **kwargs):
super(_BaseDict, self).__init__(*args, **kwargs)
self._store = dict()
self.update(dict(*args, **kwargs))
def __getitem__(self, key):
return self._store[self.__keytransform__(key)]
def __setitem__(self, key, value):
self._store[self.__keytransform__(key)] = value
def __delitem__(self, key):
del self._store[self.__keytransform__(key)]
def __iter__(self):
return iter(self._store)
def __len__(self):
return len(self._store)
@staticmethod
def __keytransform__(key):
return key
class DeployedAppsConfig(_BaseDict): # pylint: disable=too-many-ancestors
def load(self, path=None):
if path is None:
path = self._json_file
if os.path.exists(path):
with open(path, 'r') as f:
self._store = json.load(f)
else:
self._store = dict()
def write(self, path=None):
if path is None:
path = self._json_file
with open(path, 'w') as f:
json.dump(self._store, f, indent=4)
def __init__(self, project_dir=None, settings_dir=None):
assert (project_dir or settings_dir), 'DeployedAppsConfig(): ' \
'Either keyword argument project_dir or settings_dir' \
' must be set.'
assert (not (project_dir and settings_dir)), 'DeployedAppsConfig(): ' \
'Keyword arguments project_dir and settings_dir' \
' are mutually exclusive.'
super(DeployedAppsConfig, self).__init__()
if project_dir is not None:
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
self._json_file = os.path.join(settings_dir, 'django_deploy.json')
self.load()
def get_installed_apps(file_path, installed_apps):
settings_dir = os.path.dirname(os.path.abspath(file_path))
config = DeployedAppsConfig(settings_dir=settings_dir)
app_list = installed_apps[:]
for wanting_app in config:
wanted_apps = config[wanting_app].get('INSTALLED_APPS', [])
for wanted_app in wanted_apps:
if wanted_app not in app_list:
app_list.append(wanted_app)
return app_list
def get_urlpatterns(file_path, urlpatterns):
settings_dir = os.path.dirname(os.path.abspath(file_path))
config = DeployedAppsConfig(settings_dir=settings_dir)
urls_list = urlpatterns[:]
patterns = []
for url_obj in urls_list:
# Django 1 vs Django 2
if url_obj.__class__.__name__.startswith('Regex'): # pragma: no cover
patterns.append(url_obj.regex.pattern)
else: # pragma: no cover
patterns.append(str(url_obj.pattern))
for wanting_app in config:
wanted_urls = config[wanting_app].get('urlpatterns', [])
for wanted_url in wanted_urls:
pattern = wanted_url['pattern']
if pattern in patterns:
continue
if wanted_url['type'] == 'include':
url_obj = url(pattern, include(wanted_url['module']))
urls_list.append(url_obj)
patterns.append(pattern)
return urls_list

View File

@@ -0,0 +1,2 @@
# INSTALLED_APPS = ['django_deploy']
# ROOT_URLCONF = '.urls'

View File

@@ -1,4 +0,0 @@
# ADD_INSTALLED_APPS = ['django_deploy']
# ADD_URLPATTERNS = [
# {'type': 'include', 'pattern': '', 'module': 'django_deploy.urls'},
# ]

View File

@@ -1,7 +1,8 @@
class DjangoDeployError(Exception):
def __init__(self, message=None, code=None):
def __init__(self, message=None, code=None, exitval=None):
self._message = message
self._code = code
self._exitval = exitval
super(DjangoDeployError, self).__init__(message)
@property
@@ -12,12 +13,6 @@ class DjangoDeployError(Exception):
def code(self):
return self._code
class FatalError(DjangoDeployError):
def __init__(self, message=None, code=None, exitval=None):
self._exitval = exitval
super(FatalError, self).__init__(message=message, code=code)
@property
def exitval(self):
return self._exitval

View File

@@ -0,0 +1,31 @@
import os
from django.conf.urls import url, include
from .api import DjangoProjectHooksConfig
def get_installed_apps(file_path, installed_apps):
settings_dir = os.path.dirname(os.path.abspath(file_path))
config = DjangoProjectHooksConfig(settings_dir=settings_dir)
app_list = installed_apps[:]
for wanting_app in config:
wanted_apps = config[wanting_app].get('INSTALLED_APPS', [])
for wanted_app in wanted_apps:
if wanted_app not in app_list:
app_list.append(wanted_app)
return app_list
def get_urlpatterns(file_path, urlpatterns):
settings_dir = os.path.dirname(os.path.abspath(file_path))
config = DjangoProjectHooksConfig(settings_dir=settings_dir)
urls_list = urlpatterns[:]
for app in config:
if 'MOUNT' in config[app]:
route, urlconf_module = config[app]['MOUNT']
pattern = '^{}/'.format(route)
if urlconf_module.startswith('.'):
urlconf_module = '{}{}'.format(app, urlconf_module)
url_obj = url(pattern, include(urlconf_module))
urls_list.append(url_obj)
return urls_list

View File

@@ -1,11 +1,10 @@
import argparse
import importlib
import errno
import os
import sys
from .api import DjangoProject
from .config import DJANGO_SETTINGS_DIR, DeployedAppsConfig
from .exceptions import DjangoDeployError, FatalError
from .exceptions import DjangoDeployError
from .version import VERSION
@@ -15,18 +14,29 @@ class Program(object): # pylint: disable=too-few-public-methods
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('-e', '--enable',
action='store_true', dest='enable',
help='Enable django_deploy in an existing django project directory')
parser.add_argument('--no-install-hooks',
action='store_true', dest='no_install_hooks',
help='Do not install django deploy hooks while creating django project directory')
parser.add_argument('--install-hooks',
action='store_true', dest='install_hooks',
help='Install django deploy hooks into existing django project directory')
parser.add_argument('-a', '--install-app',
action='append', dest='install_apps', metavar='MODULE',
help='Merge settings from django app MODULE into django project settings'
'. Can be used multiple times')
parser.add_argument('-m', '--mount-app',
nargs=2,
action='append', dest='mount_apps', metavar=('MODULE', 'ROUTE'),
help='Merge settings from django app MODULE into django project settings'
' and add the app root urlconf to django root urlconf as ROUTE'
'. Can be used multiple times')
parser.add_argument('project_dir', metavar='PATH',
help='The directory, where the django project is or will be installed.')
@@ -42,107 +52,64 @@ class Program(object): # pylint: disable=too-few-public-methods
return self._argparser.parse_args(argv)
@staticmethod
def _append_to_pythonfile(path, text):
py2_cache = path + 'c'
with open(path, 'a') as f:
f.write(text)
if os.path.isfile(py2_cache):
os.unlink(py2_cache) # pragma: no cover
def _append_to_settings(self, project_dir, code, comment):
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
settings_file = os.path.join(settings_dir, 'settings.py')
text = '\n' + comment + '\n' + code + '\n'
return self._append_to_pythonfile(settings_file, text)
def _append_to_urlconf(self, project_dir, code, comment):
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
settings_file = os.path.join(settings_dir, 'urls.py')
text = '\n' + comment + '\n' + code + '\n'
return self._append_to_pythonfile(settings_file, text)
@staticmethod
def _install_django_files(project_dir):
def _create_django_project(project_dir, install_hooks=True):
project = DjangoProject(project_dir)
project.create()
project.create(install_hooks=install_hooks)
@staticmethod
def _install_django_deploy_files(project_dir, overwrite=False):
json_file = os.path.join(project_dir, DJANGO_SETTINGS_DIR, 'django_deploy.json')
if os.path.exists(json_file) and not overwrite:
sys.stderr.write('file already exists: {}\n'.format(json_file))
raise FatalError(exitval=os.EX_NOPERM)
config = DeployedAppsConfig(project_dir=project_dir)
config.write()
def _enable_django_deploy(self, project_dir):
settings_code = ''
settings_code += 'from django_deploy import get_installed_apps\n'
settings_code += 'INSTALLED_APPS = get_installed_apps(__file__, INSTALLED_APPS)\n'
settings_comment = '# django-deploy'
self._append_to_settings(project_dir, settings_code, settings_comment)
urlconf_code = ''
urlconf_code += 'from django_deploy import get_urlpatterns\n'
urlconf_code += 'urlpatterns = get_urlpatterns(__file__, urlpatterns)\n'
urlconf_comment = '# django-deploy'
self._append_to_urlconf(project_dir, urlconf_code, urlconf_comment)
def _install_hooks(project_dir):
project = DjangoProject(project_dir)
project.install_hooks()
@staticmethod
def _add_installed_apps_from_app(project_dir, app):
settings_from_app = importlib.import_module('{}.django_settings'.format(app))
wanted_apps = getattr(settings_from_app, 'ADD_INSTALLED_APPS', [])
config = DeployedAppsConfig(project_dir=project_dir)
if app not in config:
config[app] = {}
config[app]['INSTALLED_APPS'] = wanted_apps
config.write()
def _install_app(project_dir, module_path):
project = DjangoProject(project_dir)
project.install_app(module_path)
@staticmethod
def _add_urlpatterns_from_app(project_dir, app):
settings_from_app = importlib.import_module('{}.django_settings'.format(app))
wanted_urls = getattr(settings_from_app, 'ADD_URLPATTERNS', [])
config = DeployedAppsConfig(project_dir=project_dir)
if app not in config: # pragma: no cover
config[app] = {}
config[app]['urlpatterns'] = wanted_urls
config.write()
def _merge_app(self, project_dir, app):
self._add_installed_apps_from_app(project_dir, app)
self._add_urlpatterns_from_app(project_dir, app)
def _mount_app(project_dir, module_path, route):
project = DjangoProject(project_dir)
project.mount_app(module_path, route)
def __init__(self):
self._argparser = argparse.ArgumentParser()
self._setup_argparser(self._argparser)
def __call__(self, *args, **kwargs):
def __call__(self, *args, **kwargs): # pylint: disable=too-many-branches
argv = kwargs.get('argv', None)
cmd_args = self._parse_args(argv)
exitval = os.EX_OK
exceptions = []
try:
if cmd_args.create:
self._install_django_files(cmd_args.project_dir)
if cmd_args.create or cmd_args.enable:
self._install_django_deploy_files(cmd_args.project_dir)
self._enable_django_deploy(cmd_args.project_dir)
if cmd_args.merge_apps:
for app in cmd_args.merge_apps:
self._merge_app(cmd_args.project_dir, app)
except FatalError as e:
if e.message:
sys.stderr.write('{}\n'.format(e.message))
exitval = e.exitval
self._create_django_project(cmd_args.project_dir, install_hooks=not cmd_args.no_install_hooks)
if cmd_args.install_hooks:
self._install_hooks(cmd_args.project_dir)
if cmd_args.install_apps:
for app in cmd_args.install_apps:
try:
self._install_app(cmd_args.project_dir, app)
except DjangoDeployError as e:
if e.code in (errno.ENOPKG, errno.ENOENT):
exceptions.append(e)
continue
raise # pragma: no cover
if cmd_args.mount_apps:
for app, route in cmd_args.mount_apps:
try:
self._mount_app(cmd_args.project_dir, app, route)
except DjangoDeployError as e:
if e.code in (errno.ENOPKG, errno.ENOENT):
exceptions.append(e)
continue
raise # pragma: no cover
except DjangoDeployError as e:
exceptions.append(e)
for e in exceptions:
if e.message:
sys.stderr.write('{}\n'.format(e.message))
elif e.code:
elif e.code: # pragma: no cover
sys.stderr.write('{}\n'.format(os.strerror(e.code)))
exitval = os.EX_SOFTWARE
exitval = e.exitval or os.EX_SOFTWARE
return exitval

View File

@@ -0,0 +1,137 @@
import importlib
import os
import sys
import unittest
import mock
import pytest
from ..config import DJANGO_SETTINGS_DIR
class DjangoDeployTestCase(unittest.TestCase):
@pytest.fixture(autouse=True)
def tmpdir(self, tmpdir): # pylint: disable=method-hidden
self.tmpdir = tmpdir
@staticmethod
def _import_from_dir(module_name, directory):
sys.path.insert(0, directory)
module = importlib.import_module(module_name)
if sys.version_info.major == 2: # pragma: no cover
reload(module) # pylint: disable=undefined-variable
else: # pragma: no cover
importlib.reload(module) # pylint: disable=no-member
sys.path.pop(0)
return module
def get_django_settings(self, project_dir):
module_name = 'settings'
directory = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
return self._import_from_dir(module_name, directory)
def get_django_root_urlconf(self, project_dir):
module_name = 'urls'
directory = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
sys.modules['django.contrib'] = mock.MagicMock(name='django.contrib')
return self._import_from_dir(module_name, directory)
def assert_django_project(self, project_dir):
self.assertTrue(os.path.isdir(project_dir), 'no directory: {}'.format(project_dir))
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
self.assertTrue(os.path.isdir(settings_dir), 'no directory: {}'.format(settings_dir))
settings_file = os.path.join(settings_dir, 'settings.py')
self.assertTrue(os.path.isfile(settings_file), 'no file: {}'.format(settings_file))
manage_script = os.path.join(project_dir, 'manage.py')
self.assertTrue(os.path.isfile(manage_script), 'no file: {}'.format(manage_script))
def assert_django_deploy_hooks(self, project_dir):
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
settings_file = os.path.join(settings_dir, 'settings.py')
urlconf_file = os.path.join(settings_dir, 'urls.py')
django_deploy_json = os.path.join(settings_dir, 'django_deploy.json')
self.assertTrue(os.path.isfile(django_deploy_json), 'no file: {}'.format(django_deploy_json))
with open(settings_file, 'r') as f:
needle = 'from django_deploy import get_installed_apps'
haystack = f.read()
self.assertIn(needle, haystack)
with open(urlconf_file, 'r') as f:
needle = 'from django_deploy import get_urlpatterns'
haystack = f.read()
self.assertIn(needle, haystack)
def assert_no_django_deploy_hooks(self, project_dir):
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
settings_file = os.path.join(settings_dir, 'settings.py')
urlconf_file = os.path.join(settings_dir, 'urls.py')
django_deploy_json = os.path.join(settings_dir, 'django_deploy.json')
self.assertFalse(os.path.isfile(django_deploy_json))
with open(settings_file, 'r') as f:
needle = 'from django_deploy import get_installed_apps'
haystack = f.read()
self.assertNotIn(needle, haystack)
with open(urlconf_file, 'r') as f:
needle = 'from django_deploy import get_urlpatterns'
haystack = f.read()
self.assertNotIn(needle, haystack)
def assert_installed_apps(self, project_dir, apps, default_apps=None):
settings = self.get_django_settings(project_dir)
if default_apps is None:
expected_apps = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
else: # pragma: no cover
expected_apps = default_apps
expected_apps += apps
self.assertListEqual(expected_apps, settings.INSTALLED_APPS)
def assert_urlpatterns(self, project_dir, patterns):
root_urlconf = self.get_django_root_urlconf(project_dir)
# Django 2 vs Django 1
if hasattr(root_urlconf, 'path'): # pragma: no cover
expected_urlpatterns = [
('URLPattern', 'admin/', 'django.contrib.admin.site.urls'),
]
else: # pragma: no cover
expected_urlpatterns = [
('URLPattern', '^admin/', 'django.contrib.admin.site.urls'),
]
expected_urlpatterns += patterns
real_urlpatterns = root_urlconf.urlpatterns
self.assertEqual(len(expected_urlpatterns), len(real_urlpatterns))
for i, expected in enumerate(expected_urlpatterns):
real = real_urlpatterns[i]
real_class_name = real.__class__.__name__
self.assertTrue(real_class_name.endswith(expected[0]))
# Django 2 vs. Django 1
if real_class_name == 'URLPattern': # pragma: no cover
self.assertEqual(expected[1], str(real.pattern))
self.assertTrue(real.callback.startswith("<Mock name='{}' id=".format(expected[2])))
elif real_class_name == 'RegexURLPattern': # pragma: no cover
self.assertEqual(expected[1], real.regex.pattern)
self.assertTrue(real.callback.startswith("<Mock name='{}' id=".format(expected[2])))
elif real_class_name == 'URLResolver': # pragma: no cover
self.assertEqual(expected[1], str(real.pattern))
self.assertEqual(expected[2], real.urlconf_name.__name__)
elif real_class_name == 'RegexURLResolver': # pragma: no cover
self.assertEqual(expected[1], real.regex.pattern)
self.assertEqual(expected[2], real.urlconf_name.__name__)
else: # pragma: no cover
self.fail('Unknown urlpattern class: {}'.format(real_class_name))

View File

@@ -0,0 +1 @@
from . import django_deploy_hooks

View File

@@ -0,0 +1,2 @@
INSTALLED_APPS = ['django_deploy.tests.fake_app1']
ROOT_URLCONF = '.urls'

View File

@@ -1,5 +0,0 @@
ADD_INSTALLED_APPS = ['django_deploy.tests.fake_app1']
ADD_URLPATTERNS = [
{'type': 'include', 'pattern': '^fake1/', 'module': 'django_deploy.tests.fake_app1.urls'},
{'type': 'invalid', 'pattern': '^invalid/'},
]

View File

@@ -0,0 +1,2 @@
INSTALLED_APPS = ['django_deploy.tests.fake_app1', 'django_deploy.tests.fake_app2']
ROOT_URLCONF = 'django_deploy.tests.fake_app2.urls'

View File

@@ -1,5 +0,0 @@
ADD_INSTALLED_APPS = ['django_deploy.tests.fake_app1', 'django_deploy.tests.fake_app2']
ADD_URLPATTERNS = [
{'type': 'include', 'pattern': '^fake1/', 'module': 'django_deploy.tests.fake_app1.urls'},
{'type': 'include', 'pattern': '^fake2/', 'module': 'django_deploy.tests.fake_app2.urls'},
]

View File

@@ -1,27 +1,204 @@
import errno
import os
import unittest
import pytest
from ..api import DjangoProject
from ..config import DJANGO_SETTINGS_DIR
from ..api import DjangoProjectHooksConfig, DjangoProject
from ..exceptions import DjangoDeployError
from .base import DjangoDeployTestCase
class DjangoProjectTestCase(unittest.TestCase):
@pytest.fixture(autouse=True)
def tmpdir(self, tmpdir): # pylint: disable=method-hidden
self.tmpdir = tmpdir
class DjangoProjectHooksConfigTestCase(DjangoDeployTestCase):
def test_init(self):
with self.assertRaises(AssertionError):
DjangoProjectHooksConfig()
with self.assertRaises(AssertionError):
DjangoProjectHooksConfig(project_dir='.', settings_dir='.')
def _assert_django_project(self, project_dir):
self.assertTrue(os.path.isdir(project_dir), 'no directory: {}'.format(project_dir))
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
self.assertTrue(os.path.isdir(settings_dir), 'no directory: {}'.format(settings_dir))
settings_file = os.path.join(settings_dir, 'settings.py')
self.assertTrue(os.path.isfile(settings_file), 'no file: {}'.format(settings_file))
manage_script = os.path.join(project_dir, 'manage.py')
self.assertTrue(os.path.isfile(manage_script), 'no file: {}'.format(manage_script))
class DjangoProjectTestCase(DjangoDeployTestCase):
def setUp(self):
self._tmp_dir = str(self.tmpdir)
def test_init(self):
with self.assertRaises(TypeError):
DjangoProject() # pylint: disable=no-value-for-parameter
# existing project dir
DjangoProject(self._tmp_dir)
# not existing project dir
project_dir = os.path.join(self._tmp_dir, 'new')
DjangoProject(project_dir)
# project dir is an existing file
os.mknod(project_dir)
DjangoProject(project_dir)
def test_create(self):
project_dir = os.path.join(str(self.tmpdir), 'django')
# the parent of the to-be-created project dir shall also not exist.
project_dir = os.path.join(self._tmp_dir, 'new', 'sub', 'sub')
project = DjangoProject(project_dir)
project.create()
self._assert_django_project(project_dir)
self.assert_django_project(project_dir)
self.assert_django_deploy_hooks(project_dir)
def test_create_in_empty_dir(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
self.assert_django_project(project_dir)
self.assert_django_deploy_hooks(project_dir)
def test_create_in_project_dir(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
project = DjangoProject(project_dir)
try:
project.create()
except DjangoDeployError as e:
self.assertEqual(errno.EEXIST, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')
def test_create_without_installing_hooks(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create(install_hooks=False)
self.assert_django_project(project_dir)
self.assert_no_django_deploy_hooks(project_dir)
try:
project.create(install_hooks=False)
except DjangoDeployError as e:
self.assertEqual(errno.EEXIST, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')
try:
project.create()
except DjangoDeployError as e:
self.assertEqual(errno.EEXIST, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')
def test_install_hooks(self):
project_dir = os.path.join(self._tmp_dir, 'new')
project = DjangoProject(project_dir)
try:
project.install_hooks()
except DjangoDeployError as e:
self.assertEqual(errno.ENOENT, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')
def test_install_hooks_in_empty_dir(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
try:
project.install_hooks()
except DjangoDeployError as e:
self.assertEqual(errno.EISDIR, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')
def test_install_hooks_in_project_dir(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create(install_hooks=False)
self.assert_django_project(project_dir)
project.install_hooks()
self.assert_django_deploy_hooks(project_dir)
def test_install_hooks_after_create(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
try:
project.install_hooks()
except DjangoDeployError as e:
self.assertEqual(errno.EEXIST, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')
def test_install_app(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
project.install_app('django_deploy.tests.fake_app1')
installed_apps = [
'django_deploy.tests.fake_app1',
]
self.assert_installed_apps(project_dir, installed_apps)
def test_install_apps(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
project.install_app('django_deploy.tests.fake_app1')
project.install_app('django_deploy.tests.fake_app1')
project.install_app('django_deploy.tests.fake_app2')
installed_apps = [
'django_deploy.tests.fake_app1',
'django_deploy.tests.fake_app2',
]
self.assert_installed_apps(project_dir, installed_apps)
def test_install_nonexisting_app(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
try:
project.install_app('django_deploy.tests.fake_app0')
except DjangoDeployError as e:
self.assertEqual(errno.ENOPKG, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')
def test_install_nonconforming_app(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
try:
project.install_app('django_deploy.tests.fake_app1', hooks_config_name='non_existing_submodule')
except DjangoDeployError as e:
self.assertEqual(errno.ENOENT, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')
def test_mount_app(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
project.mount_app('django_deploy.tests.fake_app1', '')
expected_urlpatterns = [
('URLResolver', '^/', 'django_deploy.tests.fake_app1.urls'),
]
self.assert_urlpatterns(project_dir, expected_urlpatterns)
def test_mount_apps(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
project.mount_app('django_deploy.tests.fake_app1', 'app1')
project.mount_app('django_deploy.tests.fake_app1', 'app1')
project.mount_app('django_deploy.tests.fake_app2', 'app2')
expected_urlpatterns = [
('URLResolver', '^app1/', 'django_deploy.tests.fake_app1.urls'),
('URLResolver', '^app2/', 'django_deploy.tests.fake_app2.urls'),
]
self.assert_urlpatterns(project_dir, expected_urlpatterns)
def test_mount_unmountable_app(self):
project_dir = self._tmp_dir
project = DjangoProject(project_dir)
project.create()
try:
project.mount_app('django_deploy', '/')
except DjangoDeployError as e:
self.assertEqual(errno.ENOLINK, e.code)
else: # pragma: no cover
self.fail('DjangoDeployError not raised')

View File

@@ -0,0 +1,40 @@
import unittest
from ..base_types import OrderedDict
class OrderedDictTestCase(unittest.TestCase):
@staticmethod
def _create_filled_object(items):
obj = OrderedDict()
for key, value in items:
obj[key] = value
return obj
def setUp(self):
self._test_data = [
('a', 'AA'),
('c', 'CC'),
('b', 'BB'),
]
self._obj = self._create_filled_object(self._test_data)
def test_contain(self):
self.assertIn('a', self._obj)
self.assertIn('b', self._obj)
self.assertIn('c', self._obj)
self.assertNotIn('d', self._obj)
def test_len(self):
self.assertEqual(3, len(self._obj))
def test_del(self):
del self._obj['c']
self.assertEqual(2, len(self._obj))
def test_iter(self):
items = []
for key in self._obj:
items.append((key, self._obj[key]))
self.assertListEqual(self._test_data, items)

View File

@@ -1,11 +1,10 @@
import unittest
from django.conf.urls import url, include
from ..config import get_installed_apps, get_urlpatterns, DeployedAppsConfig
from ..hooks import get_installed_apps, get_urlpatterns
class FunctionsTestCase(unittest.TestCase):
class HooksTestCase(unittest.TestCase):
def test_get_installed_apps(self):
test_data_sets = [
(__file__, ['fake_app1', 'fake_app2']),
@@ -29,26 +28,3 @@ class FunctionsTestCase(unittest.TestCase):
for file_path, urlpatterns in test_data_sets:
result = get_urlpatterns(file_path, urlpatterns)
self.assertEqual(urlpatterns, result)
class DeployedAppsConfigTestCase(unittest.TestCase):
def test_init_parameters(self):
with self.assertRaises(AssertionError):
DeployedAppsConfig()
with self.assertRaises(AssertionError):
DeployedAppsConfig(project_dir='.', settings_dir='.')
def test_len(self):
c = DeployedAppsConfig(settings_dir='.')
c['a'] = 'a'
c['b'] = 'b'
c['c'] = 'c'
self.assertEqual(3, len(c))
def test_del(self):
c = DeployedAppsConfig(settings_dir='.')
c['a'] = 'a'
c['b'] = 'b'
c['c'] = 'c'
del c['b']
self.assertEqual(2, len(c))

View File

@@ -1,173 +1,141 @@
import importlib
import os
import sys
import unittest
import mock
import pytest
from ..config import DJANGO_SETTINGS_DIR
from ..program import Program
from .base import DjangoDeployTestCase
class ProgramTestCase(unittest.TestCase):
@pytest.fixture(autouse=True)
def tmpdir(self, tmpdir): # pylint: disable=method-hidden
self.tmpdir = tmpdir
def _assert_django_project(self, project_dir):
self.assertTrue(os.path.isdir(project_dir), 'no directory: {}'.format(project_dir))
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
self.assertTrue(os.path.isdir(settings_dir), 'no directory: {}'.format(settings_dir))
settings_file = os.path.join(settings_dir, 'settings.py')
self.assertTrue(os.path.isfile(settings_file), 'no file: {}'.format(settings_file))
manage_script = os.path.join(project_dir, 'manage.py')
self.assertTrue(os.path.isfile(manage_script), 'no file: {}'.format(manage_script))
def _assert_django_deploy_project(self, project_dir):
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
settings_file = os.path.join(settings_dir, 'settings.py')
urlconf_file = os.path.join(settings_dir, 'urls.py')
django_deploy_json = os.path.join(settings_dir, 'django_deploy.json')
self.assertTrue(os.path.isfile(django_deploy_json), 'no file: {}'.format(django_deploy_json))
with open(settings_file, 'r') as f:
needle = 'from django_deploy import get_installed_apps'
haystack = f.read()
self.assertIn(needle, haystack)
with open(urlconf_file, 'r') as f:
needle = 'from django_deploy import get_urlpatterns'
haystack = f.read()
self.assertIn(needle, haystack)
class ProgramTestCase(DjangoDeployTestCase):
def setUp(self):
self._program = Program()
self._project_dir = os.path.join(str(self.tmpdir), 'django')
self._program(argv=['--create', self._project_dir])
self._tmp_dir = str(self.tmpdir)
def test_create_new_dir(self):
project_dir = os.path.join(str(self.tmpdir), 'new_dir')
def test_create(self):
project_dir = os.path.join(self._tmp_dir, 'new')
exitval = self._program(argv=['--create', project_dir])
self.assertEqual(os.EX_OK, exitval)
self.assert_django_project(project_dir)
self.assert_django_deploy_hooks(project_dir)
def test_create_in_empty_dir(self):
project_dir = self._tmp_dir
exitval = self._program(argv=['-c', project_dir])
self.assertEqual(os.EX_OK, exitval, 'program() does not return os.EX_OK')
self._assert_django_project(project_dir)
self._assert_django_deploy_project(project_dir)
self.assertEqual(os.EX_OK, exitval)
self.assert_django_project(project_dir)
self.assert_django_deploy_hooks(project_dir)
def test_create_existing_empty_dir(self):
project_dir = os.path.join(str(self.tmpdir), 'empty_dir')
os.makedirs(project_dir)
def test_create_in_project_dir(self):
project_dir = self._tmp_dir
self._program(argv=['-c', project_dir])
exitval = self._program(argv=['-c', project_dir])
self.assertNotEqual(os.EX_OK, exitval)
self.assertEqual(os.EX_OK, exitval, 'program() does not return os.EX_OK')
self._assert_django_project(project_dir)
self._assert_django_deploy_project(project_dir)
def test_create_without_hooks(self):
project_dir = self._tmp_dir
exitval = self._program(argv=['-c', '--no-install-hooks', project_dir])
def test_create_existing_project_dir(self):
exitval = self._program(argv=['-c', self._project_dir])
self.assertEqual(os.EX_SOFTWARE, exitval, 'program() does not return os.EX_SOFTWARE'
' when project directory is not empty')
self.assertEqual(os.EX_OK, exitval)
self.assert_django_project(project_dir)
self.assert_no_django_deploy_hooks(project_dir)
def test_enable_django_deploy(self):
project_dir = os.path.join(str(self.tmpdir), 'pure_django')
os.makedirs(project_dir)
def test_install_hooks_without_project_dir(self):
project_dir = os.path.join(self._tmp_dir, 'new')
exitval = self._program(argv=['--install-hooks', project_dir])
self.assertNotEqual(os.EX_OK, exitval)
project_dir = os.path.join(self._tmp_dir)
exitval = self._program(argv=['--install-hooks', project_dir])
self.assertNotEqual(os.EX_OK, exitval)
def test_install_hooks_in_pure_django(self):
project_dir = self._tmp_dir
cmd = 'django-admin startproject {name} {path}'.format(name=DJANGO_SETTINGS_DIR,
path=project_dir)
os.system(cmd)
self._program(argv=['--enable', project_dir])
self._assert_django_deploy_project(project_dir)
exitval = self._program(argv=['--install-hooks', project_dir])
self.assertEqual(os.EX_OK, exitval)
self.assert_django_project(project_dir)
self.assert_django_deploy_hooks(project_dir)
def test_enable_django_deploy_twice(self):
exitval = self._program(argv=['-e', self._project_dir])
self.assertEqual(os.EX_NOPERM, exitval, 'program() does not return os.EX_NOPERM'
' when django_deploy is already enabled')
def test_merge_installed_apps(self):
project_dir = self._project_dir
def test_install_hooks_twice(self):
project_dir = self._tmp_dir
self._program(argv=['-c', project_dir])
exitval = self._program(argv=['--install-hooks', project_dir])
self.assertEqual(os.EX_SOFTWARE, exitval)
def test_install_apps(self):
project_dir = self._tmp_dir
argv = [
'-a', 'django_deploy',
'-a', 'django_deploy.tests.fake_app1',
'-c',
'--install-app', 'django_deploy.tests.fake_app1',
'--install-app', 'django_deploy.tests.fake_app1',
'-a', 'django_deploy.tests.fake_app2',
project_dir]
self._program(argv=argv)
project_dir
]
exitval = self._program(argv=argv)
self.assertEqual(os.EX_OK, exitval)
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
sys.path.insert(0, settings_dir)
settings = importlib.import_module('settings')
if sys.version_info.major == 2: # pragma: no cover
reload(settings) # pylint: disable=undefined-variable
else: # pragma: no cover
importlib.reload(settings) # pylint: disable=no-member
sys.path.pop(0)
expected_installed_apps = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
installed_apps = [
'django_deploy.tests.fake_app1',
'django_deploy.tests.fake_app2',
]
self.assertListEqual(expected_installed_apps, settings.INSTALLED_APPS)
def test_merge_root_urlconf(self):
project_dir = self._project_dir
self.assert_installed_apps(project_dir, installed_apps)
def test_install_apps_with_error(self):
project_dir = self._tmp_dir
argv = [
'-a', 'django_deploy',
'-c',
'-a', 'django_deploy.tests.fake_app1',
'-a', 'django_deploy.tests.fake_app0',
'-a', 'django_deploy.tests.fake_app2',
project_dir]
self._program(argv=argv)
project_dir
]
exitval = self._program(argv=argv)
self.assertNotEqual(os.EX_OK, exitval)
sys.modules['django.contrib'] = mock.MagicMock(name='django.contrib')
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
sys.path.insert(0, settings_dir)
root_urlconf = importlib.import_module('urls')
if sys.version_info.major == 2: # pragma: no cover
reload(root_urlconf) # pylint: disable=undefined-variable
else: # pragma: no cover
importlib.reload(root_urlconf) # pylint: disable=no-member
sys.path.pop(0)
# Django 2 vs Django 1
if hasattr(root_urlconf, 'path'): # pragma: no cover
expected_urlpatterns = [
('URLPattern', 'admin/', 'django.contrib.admin.site.urls'),
]
else: # pragma: no cover
expected_urlpatterns = [
('URLPattern', '^admin/', 'django.contrib.admin.site.urls'),
]
expected_urlpatterns += [
('URLResolver', '^fake1/', 'django_deploy.tests.fake_app1.urls'),
('URLResolver', '^fake2/', 'django_deploy.tests.fake_app2.urls'),
installed_apps = [
'django_deploy.tests.fake_app1',
'django_deploy.tests.fake_app2',
]
real_urlpatterns = root_urlconf.urlpatterns
self.assertEqual(len(expected_urlpatterns), len(real_urlpatterns))
self.assert_installed_apps(project_dir, installed_apps)
for i, expected in enumerate(expected_urlpatterns):
real = real_urlpatterns[i]
real_class_name = real.__class__.__name__
self.assertTrue(real_class_name.endswith(expected[0]))
# Django 2 vs. Django 1
if real_class_name == 'URLPattern': # pragma: no cover
self.assertEqual(expected[1], str(real.pattern))
self.assertTrue(real.callback.startswith("<Mock name='{}' id=".format(expected[2])))
elif real_class_name == 'RegexURLPattern': # pragma: no cover
self.assertEqual(expected[1], real.regex.pattern)
self.assertTrue(real.callback.startswith("<Mock name='{}' id=".format(expected[2])))
elif real_class_name == 'URLResolver': # pragma: no cover
self.assertEqual(expected[1], str(real.pattern))
self.assertEqual(expected[2], real.urlconf_name.__name__)
elif real_class_name == 'RegexURLResolver': # pragma: no cover
self.assertEqual(expected[1], real.regex.pattern)
self.assertEqual(expected[2], real.urlconf_name.__name__)
else: # pragma: no cover
self.fail('Unknown urlpattern class: {}'.format(real_class_name))
def test_mount_apps(self):
project_dir = self._tmp_dir
argv = [
'-c',
'--mount-app', 'django_deploy.tests.fake_app1', 'app1',
'--mount-app', 'django_deploy.tests.fake_app1', 'app1',
'-m', 'django_deploy.tests.fake_app2', 'app2',
project_dir
]
exitval = self._program(argv=argv)
self.assertEqual(os.EX_OK, exitval)
urlpatterns = [
('URLResolver', '^app1/', 'django_deploy.tests.fake_app1.urls'),
('URLResolver', '^app2/', 'django_deploy.tests.fake_app2.urls'),
]
self.assert_urlpatterns(project_dir, urlpatterns)
def test_mount_apps_with_errors(self):
project_dir = self._tmp_dir
self._program(argv=['-c', project_dir])
argv = [
'-m', 'django_deploy.tests.fake_app1', 'app1',
'-m', 'django_deploy.tests.fake_app0', 'app0',
'-m', 'django_deploy.tests.fake_app2', 'app2',
project_dir
]
exitval = self._program(argv=argv)
self.assertNotEqual(os.EX_OK, exitval)
patterns = [
('URLResolver', '^app1/', 'django_deploy.tests.fake_app1.urls'),
('URLResolver', '^app2/', 'django_deploy.tests.fake_app2.urls'),
]
self.assert_urlpatterns(project_dir, patterns)

View File

@@ -1 +1 @@
VERSION = '0.1.dev0'
VERSION = '0.2.dev0'