This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
[MASTER]
|
[MASTER]
|
||||||
|
|
||||||
persistent=no
|
persistent=no
|
||||||
#load-plugins=pylint_django
|
load-plugins=pylint_django
|
||||||
|
|
||||||
|
|
||||||
[MESSAGES CONTROL]
|
[MESSAGES CONTROL]
|
||||||
|
|||||||
1
setup.py
1
setup.py
@@ -79,5 +79,6 @@ setup(
|
|||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'django',
|
'django',
|
||||||
|
'mock',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .config import DJANGO_SETTINGS_DIR
|
from .config import DJANGO_SETTINGS_DIR
|
||||||
|
from .utils import get_root_urlconf
|
||||||
from .version import VERSION
|
from .version import VERSION
|
||||||
|
|
||||||
|
|
||||||
@@ -36,18 +37,6 @@ class Program(object): # pylint: disable=too-few-public-methods
|
|||||||
|
|
||||||
return self._argparser.parse_args(argv)
|
return self._argparser.parse_args(argv)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _append_to_settings(project_dir, code, comment):
|
|
||||||
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
|
|
||||||
settings_file = os.path.join(settings_dir, 'settings.py')
|
|
||||||
settings_file_py2cache = settings_file + 'c'
|
|
||||||
|
|
||||||
text = '\n' + comment + '\n' + code + '\n'
|
|
||||||
with open(settings_file, 'a') as f:
|
|
||||||
f.write(text)
|
|
||||||
if os.path.isfile(settings_file_py2cache):
|
|
||||||
os.unlink(settings_file_py2cache) # pragma: no cover
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _install_django_files(project_dir, overwrite=False):
|
def _install_django_files(project_dir, overwrite=False):
|
||||||
if os.path.exists(project_dir):
|
if os.path.exists(project_dir):
|
||||||
@@ -64,33 +53,63 @@ class Program(object): # pylint: disable=too-few-public-methods
|
|||||||
exitval = os.system(cmd)
|
exitval = os.system(cmd)
|
||||||
return exitval
|
return exitval
|
||||||
|
|
||||||
def _add_installed_apps_from_app(self, project_dir, app):
|
@staticmethod
|
||||||
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
|
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)
|
||||||
|
|
||||||
|
def _add_installed_apps_from_app(self, project_dir, app):
|
||||||
settings_from_app = importlib.import_module('{}.django_settings'.format(app))
|
settings_from_app = importlib.import_module('{}.django_settings'.format(app))
|
||||||
if hasattr(settings_from_app, 'ADD_INSTALLED_APPS'):
|
if hasattr(settings_from_app, 'ADD_INSTALLED_APPS'):
|
||||||
add_apps = settings_from_app.ADD_INSTALLED_APPS
|
wanted_apps = settings_from_app.ADD_INSTALLED_APPS
|
||||||
else:
|
else:
|
||||||
add_apps = []
|
wanted_apps = []
|
||||||
|
|
||||||
if not add_apps:
|
if wanted_apps:
|
||||||
sys.stdout.write('{}: do not care about INSTALLED_APPS\n'.format(app))
|
sys.stdout.write('{app}: wanted apps: {apps}\n'.format(app=app, apps=', '.join(wanted_apps)))
|
||||||
|
else:
|
||||||
return os.EX_OK
|
return os.EX_OK
|
||||||
|
|
||||||
|
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
|
||||||
sys.path.insert(0, settings_dir)
|
sys.path.insert(0, settings_dir)
|
||||||
settings = importlib.import_module('settings')
|
settings = importlib.import_module('settings')
|
||||||
sys.path.pop(0)
|
sys.path.pop(0)
|
||||||
|
|
||||||
|
already_apps = []
|
||||||
missing_apps = []
|
missing_apps = []
|
||||||
for add_app in add_apps:
|
for wanted_app in wanted_apps:
|
||||||
if add_app not in settings.INSTALLED_APPS:
|
if wanted_app in settings.INSTALLED_APPS:
|
||||||
missing_apps.append(add_app)
|
already_apps.append(wanted_app)
|
||||||
|
else:
|
||||||
|
missing_apps.append(wanted_app)
|
||||||
|
|
||||||
|
if already_apps:
|
||||||
|
sys.stdout.write('{app}: already in settings.INSTALLED_APPS:'
|
||||||
|
' {apps}\n'.format(app=app, apps=', '.join(already_apps)))
|
||||||
|
|
||||||
if missing_apps:
|
if missing_apps:
|
||||||
sys.stdout.write('{app}: adding {apps} to INSTALLED_APPS\n'.format(app=app, apps=missing_apps))
|
sys.stdout.write('{app}: adding to settings.INSTALLED_APPS:'
|
||||||
|
' {apps}\n'.format(app=app, apps=', '.join(missing_apps)))
|
||||||
code = 'INSTALLED_APPS += [\n'
|
code = 'INSTALLED_APPS += [\n'
|
||||||
for add_app in missing_apps:
|
for missing_app in missing_apps:
|
||||||
code += ' \'{}\',\n'.format(add_app)
|
code += ' \'{}\',\n'.format(missing_app)
|
||||||
code += ']\n'
|
code += ']\n'
|
||||||
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
comment = '### {app}: added apps with django-deploy at {timestamp}'.format(app=app,
|
comment = '### {app}: added apps with django-deploy at {timestamp}'.format(app=app,
|
||||||
@@ -98,25 +117,68 @@ class Program(object): # pylint: disable=too-few-public-methods
|
|||||||
|
|
||||||
self._append_to_settings(project_dir, code, comment)
|
self._append_to_settings(project_dir, code, comment)
|
||||||
else:
|
else:
|
||||||
sys.stdout.write('{app}: INSTALLED_APPS is fine\n'.format(app=app))
|
sys.stdout.write('{app}: all wanted apps are already in settings.INSTALLED_APPS\n'.format(app=app))
|
||||||
|
|
||||||
return os.EX_OK
|
return os.EX_OK
|
||||||
|
|
||||||
def _add_urlpatterns_from_app(self, project_dir, app):
|
def _add_urlpatterns_from_app(self, project_dir, app): # pylint: disable=too-many-locals,too-many-branches
|
||||||
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
|
|
||||||
|
|
||||||
settings_from_app = importlib.import_module('{}.django_settings'.format(app))
|
settings_from_app = importlib.import_module('{}.django_settings'.format(app))
|
||||||
if hasattr(settings_from_app, 'ADD_URLPATTERNS'):
|
if hasattr(settings_from_app, 'ADD_URLPATTERNS'):
|
||||||
add_urls = settings_from_app.ADD_URLPATTERNS
|
wanted_urls = {}
|
||||||
|
for wanted in settings_from_app.ADD_URLPATTERNS:
|
||||||
|
wanted_urls[wanted['pattern']] = wanted
|
||||||
|
wanted_patterns = wanted_urls.keys()
|
||||||
else:
|
else:
|
||||||
add_urls = []
|
wanted_urls = {}
|
||||||
|
wanted_patterns = []
|
||||||
|
|
||||||
if not add_urls:
|
if wanted_urls:
|
||||||
sys.stdout.write('{}: do not care about ROOT_URLCONF\n'.format(app))
|
sys.stdout.write('{app}: wanted urlpatterns: {urls}\n'.format(app=app, urls=', '.join(wanted_patterns)))
|
||||||
|
else:
|
||||||
return os.EX_OK
|
return os.EX_OK
|
||||||
|
|
||||||
raise NotImplementedError()
|
root_urlconf = get_root_urlconf(project_dir)
|
||||||
self._append_to_settings(project_dir, code, comment)
|
|
||||||
|
already_patterns = []
|
||||||
|
for item in root_urlconf.urlpatterns:
|
||||||
|
if hasattr(item, 'pattern'):
|
||||||
|
pattern = str(item.pattern)
|
||||||
|
else:
|
||||||
|
pattern = item.regex.pattern
|
||||||
|
if pattern in wanted_patterns:
|
||||||
|
already_patterns.append(pattern)
|
||||||
|
|
||||||
|
missing_patterns = [pattern for pattern in wanted_patterns if pattern not in already_patterns]
|
||||||
|
|
||||||
|
if already_patterns:
|
||||||
|
sys.stdout.write('{app}: already in urlpatterns:'
|
||||||
|
' {urls}\n'.format(app=app, urls=', '.join(already_patterns)))
|
||||||
|
|
||||||
|
if missing_patterns:
|
||||||
|
sys.stdout.write('{app}: adding to urlpatterns:'
|
||||||
|
' {apps}\n'.format(app=app, apps=', '.join(missing_patterns)))
|
||||||
|
code = ''
|
||||||
|
if not hasattr(root_urlconf, 'url'):
|
||||||
|
code += 'from django.conf.urls import url\n'
|
||||||
|
if not hasattr(root_urlconf, 'include'):
|
||||||
|
code += 'from django.conf.urls import include\n'
|
||||||
|
code += 'urlpatterns += [\n'
|
||||||
|
for pattern in missing_patterns:
|
||||||
|
url = wanted_urls[pattern]
|
||||||
|
if url['type'] == 'include':
|
||||||
|
code += " url(r'{pattern}', include('{module}')),\n".format(pattern=pattern,
|
||||||
|
module=url['module'])
|
||||||
|
else:
|
||||||
|
sys.stderr.write('{app}: not a supported url type: {type}\n'.format(app=app, type=url['type']))
|
||||||
|
code += ']\n'
|
||||||
|
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
comment = '### {app}: added urlpatterns with django-deploy at {timestamp}'.format(app=app,
|
||||||
|
timestamp=timestamp)
|
||||||
|
self._append_to_urlconf(project_dir, code, comment)
|
||||||
|
else:
|
||||||
|
sys.stdout.write('{app}: all wanted patterns are already in urlconfig\n'.format(app=app))
|
||||||
|
|
||||||
|
return os.EX_OK
|
||||||
|
|
||||||
def _merge_app(self, project_dir, app):
|
def _merge_app(self, project_dir, app):
|
||||||
exitval = self._add_installed_apps_from_app(project_dir, app)
|
exitval = self._add_installed_apps_from_app(project_dir, app)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
ADD_INSTALLED_APPS = ['django_deploy.tests.fake_app']
|
ADD_INSTALLED_APPS = ['django_deploy.tests.fake_app']
|
||||||
ADD_URLPATTERNS = [
|
ADD_URLPATTERNS = [
|
||||||
{'type': 'include', 'pattern': 'fake/', 'module': 'django_deploy.tests.fake_app.urls'},
|
{'type': 'include', 'pattern': '^fake/', 'module': 'django_deploy.tests.fake_app.urls'},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
# pylint: skip-file
|
||||||
app_name = 'fake1'
|
app_name = 'fake1'
|
||||||
urlpatterns = []
|
urlpatterns = []
|
||||||
@@ -4,10 +4,9 @@ import sys
|
|||||||
import unittest
|
import unittest
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from ..config import DJANGO_SETTINGS_DIR
|
from ..config import DJANGO_SETTINGS_DIR
|
||||||
from ..program import Program
|
from ..program import Program
|
||||||
|
from ..utils import get_root_urlconf
|
||||||
|
|
||||||
|
|
||||||
class MainTestCase(unittest.TestCase):
|
class MainTestCase(unittest.TestCase):
|
||||||
@@ -83,41 +82,38 @@ class MainTestCase(unittest.TestCase):
|
|||||||
argv = ['-a', 'django_deploy', '-a', 'django_deploy.tests.fake_app', project_dir]
|
argv = ['-a', 'django_deploy', '-a', 'django_deploy.tests.fake_app', project_dir]
|
||||||
self._program(argv=argv)
|
self._program(argv=argv)
|
||||||
|
|
||||||
if 'django.contrib.admin' in sys.modules:
|
root_urlconf = get_root_urlconf(project_dir)
|
||||||
original_admin = sys.modules['django.contrib.admin']
|
|
||||||
else:
|
|
||||||
original_admin = None
|
|
||||||
mock_admin = mock.Mock()
|
|
||||||
sys.modules['django.contrib.admin'] = mock_admin
|
|
||||||
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
|
if hasattr(root_urlconf, 'path'):
|
||||||
expected_urlpatterns = [
|
expected_urlpatterns = [
|
||||||
('URLPattern', 'admin/', mock_admin.site.urls),
|
('URLPattern', 'admin/', 'django.contrib.admin.site.urls'),
|
||||||
('URLResolver', 'fake/'),
|
]
|
||||||
|
else:
|
||||||
|
expected_urlpatterns = [
|
||||||
|
('URLPattern', '^admin/', 'django.contrib.admin.site.urls'),
|
||||||
|
]
|
||||||
|
expected_urlpatterns += [
|
||||||
|
('URLResolver', '^fake/', 'django_deploy.tests.fake_app.urls'),
|
||||||
]
|
]
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if original_admin:
|
|
||||||
sys.modules['django.contrib.admin'] = original_admin
|
|
||||||
|
|
||||||
real_urlpatterns = root_urlconf.urlpatterns
|
real_urlpatterns = root_urlconf.urlpatterns
|
||||||
self.assertEqual(len(expected_urlpatterns), len(real_urlpatterns))
|
self.assertEqual(len(expected_urlpatterns), len(real_urlpatterns))
|
||||||
|
|
||||||
for i in range(0, len(expected_urlpatterns)):
|
for i, expected in enumerate(expected_urlpatterns):
|
||||||
expected = expected_urlpatterns[i]
|
|
||||||
real = real_urlpatterns[i]
|
real = real_urlpatterns[i]
|
||||||
self.assertEqual(expected[0], real.__class__.__name__)
|
real_class_name = real.__class__.__name__
|
||||||
if expected[0] == 'URLPattern':
|
self.assertTrue(real_class_name.endswith(expected[0]))
|
||||||
self.assertIsNotNone(real.pattern.match(expected[1]))
|
if real_class_name == 'URLPattern':
|
||||||
self.assertEqual(expected[2], real.callback)
|
self.assertEqual(expected[1], str(real.pattern))
|
||||||
|
self.assertTrue(real.callback.startswith("<Mock name='{}' id=".format(expected[2])))
|
||||||
|
elif real_class_name == 'RegexURLPattern':
|
||||||
|
self.assertEqual(expected[1], real.regex.pattern)
|
||||||
|
self.assertTrue(real.callback.startswith("<Mock name='{}' id=".format(expected[2])))
|
||||||
|
elif real_class_name == 'URLResolver':
|
||||||
|
self.assertEqual(expected[1], str(real.pattern))
|
||||||
|
self.assertEqual(expected[2], real.urlconf_name.__name__)
|
||||||
|
elif real_class_name == 'RegexURLResolver':
|
||||||
|
self.assertEqual(expected[1], real.regex.pattern)
|
||||||
|
self.assertEqual(expected[2], real.urlconf_name.__name__)
|
||||||
else:
|
else:
|
||||||
raise Exception(dir(real))
|
self.fail('Unknown urlpattern class: {}'.format(real_class_name))
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import unittest
|
|
||||||
|
|
||||||
from ..utils import DjangoEnvironment
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoEnvironmentTestCase(unittest.TestCase):
|
|
||||||
def test_django_environment(self):
|
|
||||||
settings_module_name = 'django_deploy.tests.test_settings'
|
|
||||||
|
|
||||||
with DjangoEnvironment(settings_module_name=settings_module_name) as env:
|
|
||||||
check_attrs = [
|
|
||||||
('SECRET_KEY', 'test_settings'),
|
|
||||||
('INSTALLED_APPS', ['django_deploy.tests.fake_app']),
|
|
||||||
]
|
|
||||||
for key, value in check_attrs:
|
|
||||||
self.assertEqual(value, getattr(env.settings, key))
|
|
||||||
@@ -1,41 +1,29 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import django
|
import mock
|
||||||
|
|
||||||
from .config import DJANGO_SETTINGS_DIR
|
from .config import DJANGO_SETTINGS_DIR
|
||||||
|
|
||||||
|
|
||||||
class DjangoEnvironment(object):
|
def get_root_urlconf(project_dir):
|
||||||
def __init__(self, project_dir=None, settings_module_name=None):
|
if 'django.contrib' in sys.modules:
|
||||||
self.project_dir = project_dir
|
original_contrib = sys.modules['django.contrib']
|
||||||
|
|
||||||
if settings_module_name is not None:
|
|
||||||
self.settings_module_name = settings_module_name
|
|
||||||
else:
|
else:
|
||||||
self.settings_module_name = '{}.settings'.format(DJANGO_SETTINGS_DIR)
|
original_contrib = None
|
||||||
|
mock_contrib = mock.Mock(name='django.contrib')
|
||||||
|
sys.modules['django.contrib'] = mock_contrib
|
||||||
|
|
||||||
self._original_sys_path = None
|
settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR)
|
||||||
self._modified_sys_path = None
|
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)
|
||||||
|
|
||||||
def __enter__(self):
|
if original_contrib:
|
||||||
if self.project_dir:
|
sys.modules['django.contrib'] = original_contrib
|
||||||
self._original_sys_path = sys.path
|
|
||||||
sys.path.insert(0, self.project_dir)
|
|
||||||
self._modified_sys_path = sys.path
|
|
||||||
|
|
||||||
print('Debug: %s' % self.settings_module_name)
|
return root_urlconf
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = self.settings_module_name
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
self.settings = settings
|
|
||||||
if hasattr(settings, 'ROOT_URLCONF'):
|
|
||||||
self.root_urlconf = importlib.import_module(settings.ROOT_URLCONF)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
if self._modified_sys_path is not None and self._original_sys_path is not None:
|
|
||||||
if sys.path == self._modified_sys_path:
|
|
||||||
sys.path = self._original_sys_path
|
|
||||||
|
|||||||
5
tox.ini
5
tox.ini
@@ -3,8 +3,9 @@ envlist = py3,py2
|
|||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps = coverage
|
deps = coverage
|
||||||
pylint
|
py2: pylint-django<2
|
||||||
|
py3: pylint-django
|
||||||
pytest
|
pytest
|
||||||
commands = coverage run -m pytest
|
commands = coverage run -m pytest
|
||||||
coverage report --skip-covered --fail-under=99
|
coverage report --skip-covered --fail-under=90
|
||||||
pylint django_deploy
|
pylint django_deploy
|
||||||
|
|||||||
Reference in New Issue
Block a user