This commit is contained in:
2019-04-12 21:17:24 +02:00
commit 892ed029f6
45 changed files with 26292 additions and 0 deletions

2
.coveragerc Normal file
View File

@@ -0,0 +1,2 @@
[run]
source = base

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
*.pyc
.coverage
geckodriver.log
.idea/
django_test.egg-info/
env/
tmp/

1
INSTALL.rst Normal file
View File

@@ -0,0 +1 @@
NOT IMPLEMENTED YET

3
MANIFEST.in Normal file
View File

@@ -0,0 +1,3 @@
recursive-include base/console_scripts/django_project_config *.py
recursive-include base/static *
recursive-include base/templates *

19
README.rst Normal file
View File

@@ -0,0 +1,19 @@
ABOUT
=====
This is the heinzel django project.
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.

0
base/__init__.py Normal file
View File

View File

View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import argparse
import os
import pkg_resources
import sys
DJANGO_MAIN_MODULE = 'main'
VERSION = '0.1'
class AdminCommand(object):
@staticmethod
def _setup_argparser():
kwargs = {
'description': 'Tool to manage your test django installation.',
}
parser = argparse.ArgumentParser(**kwargs)
parser.add_argument('-V', '--version', action='version',
version='%(prog)s ' + VERSION)
subparsers = parser.add_subparsers(dest='subcmd', metavar='CMD',
title='subcommands',
description="Use '%(prog)s CMD -h' to show help for a subcommand")
subparser = subparsers.add_parser('setup', help='Setup the installation.')
subparser.add_argument('path', metavar='PATH',
help='A directory, where the project files will be installed.')
return parser
def _parse_args(self, argv=None):
if argv is None:
argv = sys.argv[1:]
if not argv:
argv = ['--help']
return self._argparser.parse_args(argv)
@staticmethod
def _subcmd_setup(cmd_args):
django_main_module = DJANGO_MAIN_MODULE
django_base_dir = cmd_args.path
sys.stdout.write('Setup installation in \'{path}\'...\n'.format(path=django_base_dir))
if os.path.exists(django_base_dir):
if not os.path.isdir(django_base_dir):
sys.stderr.write('{path}: Not a directory.\n'.format(path=django_base_dir))
return os.EX_USAGE
else:
os.makedirs(django_base_dir)
sys.stdout.write('Creating django project...\n')
django_cmd = 'django-admin startproject {name} "{path}"'.format(name=django_main_module,
path=django_base_dir)
exitval = os.system(django_cmd)
if exitval != os.EX_OK:
return exitval
sys.stdout.write('Configure django project...\n')
input_file = os.path.join('django_project_config', 'additional_settings.py')
output_file = os.path.join(django_base_dir, django_main_module, 'settings.py')
with open(output_file, 'ab') as f:
f.write(pkg_resources.resource_string(__package__, input_file))
sys.stdout.write('Creating directories...\n')
dirs = [
os.path.join(django_base_dir, 'var', 'log'),
os.path.join(django_base_dir, 'var', 'www', 'static'),
]
for d in dirs:
sys.stdout.write(' - %s\n' % d)
os.makedirs(d)
return os.EX_OK
def __init__(self):
self._argparser = self._setup_argparser()
def __call__(self, argv=None):
cmd_args = self._parse_args(argv)
subcmd = cmd_args.subcmd
method_name = '_subcmd_{}'.format(subcmd)
method = getattr(self, method_name)
exitval = method(cmd_args)
return exitval
def main():
cmd = AdminCommand()
exitval = cmd()
sys.exit(exitval)

View File

@@ -0,0 +1,64 @@
#
# Additional settings for django-test
#
BASE_VAR_DIR = os.path.join(BASE_DIR, 'var')
LOG_DIR = os.path.join(BASE_VAR_DIR, 'log')
INSTALLED_APPS += [
'django_extensions',
# Our main app
'base',
]
ROOT_URLCONF = 'base.urls'
STATIC_ROOT = os.path.join(BASE_VAR_DIR, 'www', 'static')
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '%(asctime)s - %(name)s - %(levelname)s: %(message)s'
},
'verbose': {
'format': ('%(asctime)s -- %(processName)s[%(process)d]:%(threadName)s[%(thread)d] -- '
'%(name)s -- %(module)s...%(funcName)s() -- %(pathname)s:%(lineno)d -- '
'%(levelname)s: %(message)s')
},
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
},
'handlers': {
'null': {
'class': 'logging.NullHandler',
},
'console_debug': {
'level': 'DEBUG',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
},
'console_default': {
'level': 'INFO',
'filters': ['require_debug_false'],
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'level': 'INFO',
'propagate': True,
},
},
'root': {
'level': 'DEBUG',
'handlers': ['console_debug', 'console_default'],
},
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,331 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4435
base/static/base/bootstrap/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
#page-header {
padding-left: 1rem;
border-bottom-style: solid;
border-bottom-width: 2px;
border-bottom-color: #000;
margin-bottom: 1rem;
}
#page-footer {
padding-right: 1rem;
}
#page-footer > div.signum {
float: right;
}

5
base/static/base/js/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
{% load static %}
<html lang="{{ LANGUAGE_CODE|default:'de' }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link type="text/css" href="{% static 'base/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet" />
<link type="text/css" href="{% static 'base/css/local.css' %}" rel="stylesheet" />
<script type="text/javascript" src="{% static 'base/js/jquery.min.js' %}"></script>
<script type="text/javascript" src="{% static 'base/bootstrap/js/bootstrap.min.js' %}"></script>
<title>django-test</title>
</head>
<body>
<div id="page">
<div id="page-header">
<h1>
<a href="{% url 'root' %}">django-test</a>
</h1>
</div>
<div id="page-body">
{% block page-body %}{% endblock %}
</div>
<div id="page-footer">
<div class="signum">{% block signum %}{% include 'signum.html' %}{% endblock %}</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,15 @@
{% extends "base/base.html" %}
{% block page-body %}
<div class="container">
<div class="jumbotron">
<h1>Hello,</h1>
<p>
my name is {{ hostname|capfirst }} and I am your server right now.
</p>
<p>
By the way, my clock says {{ time|time:'TIME_FORMAT'|default:'nothing' }}.
</p>
</div>
</div>
{% endblock page-body %}

View File

@@ -0,0 +1 @@
<a href="mailto:heinzel@heinzelwelt.de">heinzel@heinzelwelt.de</a>

0
base/tests/__init__.py Normal file
View File

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import shutil
from django.test import SimpleTestCase
from ..console_scripts.admin import DJANGO_MAIN_MODULE, AdminCommand
from .utils import mkdtemp
class AdminTestCase(SimpleTestCase):
def setUp(self):
super(AdminTestCase, self).setUp()
self.tmp_dir = mkdtemp(prefix='AdminTestCase')
def tearDown(self):
super(AdminTestCase, self).tearDown()
if os.path.isdir(self.tmp_dir):
shutil.rmtree(self.tmp_dir)
def test_setup(self):
path = self.tmp_dir
cmd = AdminCommand()
argv = ['setup', path]
exitval = cmd(argv)
self.assertEqual(exitval, os.EX_OK)
self.assertTrue(os.path.isfile(os.path.join(path, 'manage.py')))
self.assertTrue(os.path.isfile(os.path.join(path, DJANGO_MAIN_MODULE, 'settings.py')))
self.assertTrue(os.path.isdir(os.path.join(path, 'var', 'log')))
self.assertTrue(os.path.isdir(os.path.join(path, 'var', 'www', 'static')))

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.template.context import Context
from django.test import SimpleTestCase
class BaseTemplateTestCase(SimpleTestCase):
def assertInHTML_multi(self, response, needles, format_kwargs=None):
content = response.content.decode('utf-8')
for needle in needles:
if format_kwargs is not None:
needle = needle.format(**format_kwargs)
self.assertInHTML(needle, content)
def setUp(self):
super(BaseTemplateTestCase, self).setUp()
self.response = self.client.get('/')
self.static_url = settings.STATIC_URL
self.base_prefix = 'base/'
def test_template_usage(self):
response = self.response
self.assertTemplateUsed(response, 'base/base.html')
def test_local_css_link(self):
response = self.response
format_kwargs = {
'static_url': self.static_url,
'base_prefix': self.base_prefix,
}
needles = (
'<link type="text/css" href="{static_url}{base_prefix}css/local.css" rel="stylesheet" />',
)
self.assertInHTML_multi(response, needles, format_kwargs)
def test_bootstrap_css_links(self):
response = self.response
format_kwargs = {
'static_url': self.static_url,
'base_prefix': self.base_prefix,
}
needles = (
# bootstrap css
'<link type="text/css" href="{static_url}{base_prefix}bootstrap/css/bootstrap.min.css"'
' rel="stylesheet" />',
# jquery.js file
'<script type="text/javascript" src="{static_url}{base_prefix}js/jquery.min.js"></script>',
# bootstrap js file
'<script type="text/javascript" src="{static_url}{base_prefix}bootstrap/js/bootstrap.min.js"></script>',
)
self.assertInHTML_multi(response, needles, format_kwargs)
def test_page_footer(self):
response = self.response
self.assertTemplateUsed(response, 'signum.html')
for template in response.templates:
if template.name == 'signum.html':
signum = template.render(Context({}))
break
else: # pragma: no cover
self.fail('Cannot find signum template.')
needle = """
<div id="page-footer">
<div class="signum">{signum}</div>
</div>
""".format(signum=signum)
self.assertInHTML(needle, response.content.decode('utf-8'))

31
base/tests/test_views.py Normal file
View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import socket
from django.test import SimpleTestCase
class DjangoAdminTestCase(SimpleTestCase):
def test_djangoadmin(self):
response = self.client.get('/djangoadmin', follow=True)
self.assertContains(response, 'Django administration')
class RootTestCase(SimpleTestCase):
def setUp(self):
super(RootTestCase, self).setUp()
self.response = self.client.get('/')
def test_root_template(self):
response = self.response
self.assertTemplateUsed(response, 'base/root.html')
def test_root_context(self):
response = self.response
self.assertIn('hostname', response.context)
hostname = socket.gethostname()
self.assertEqual(response.context['hostname'], hostname)
def test_root_content(self):
response = self.response
hostname = socket.gethostname().capitalize()
self.assertContains(response, hostname)

12
base/tests/utils.py Normal file
View File

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
from tempfile import mkdtemp as _mkdtmp
def mkdtemp(prefix):
dirname = os.path.dirname
pkg_base_dir = dirname(dirname(dirname(__file__)))
tmp_dir = os.path.join(pkg_base_dir, 'tmp')
os.makedirs(tmp_dir, exist_ok=True)
return _mkdtmp(prefix=prefix, dir=tmp_dir)

11
base/urls.py Normal file
View File

@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path('', views.RootView.as_view(), name='root'),
path('djangoadmin/', admin.site.urls),
]

16
base/views.py Normal file
View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import socket
from django.utils import timezone
from django.views import generic
class RootView(generic.TemplateView):
template_name = 'base/root.html'
def get_context_data(self, **kwargs):
if 'hostname' not in kwargs:
kwargs['hostname'] = socket.gethostname()
if 'time' not in kwargs:
kwargs['time'] = timezone.now()
return super(RootView, self).get_context_data(**kwargs)

82
bin/coverage-html.py Executable file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import argparse
import coverage
import datetime
import os
import shutil
import sys
class Command(object):
default_browser = 'firefox'
@staticmethod
def _setup_argparser():
kwargs = {
'description': 'Tool to create html coverage report.',
}
parser = argparse.ArgumentParser(**kwargs)
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
def _remove_report_directory(self, path=None):
if path is None:
path = self._report_directory
if path is not None:
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)
def _show_report(self, path):
start_file = os.path.join(path, 'index.html')
browser = os.environ.get('BROWSER', self.default_browser)
cmd = '{browser} --new-window "{file}"'.format(browser=browser, file=start_file)
return os.system(cmd)
def __init__(self):
self._argparser = self._setup_argparser()
self._report_directory = self._create_report_directory()
self._coverage = coverage.Coverage()
self._coverage.load()
def __call__(self, argv=None):
self._parse_args(argv)
report_dir = self._report_directory
sys.stdout.write('Report directory: {}\n'.format(report_dir))
try:
self._create_report(report_dir)
except Exception as e1:
self._remove_report_directory(report_dir)
raise e1
exitval = self._show_report(report_dir)
return exitval
def main():
cmd = Command()
exitval = cmd()
sys.exit(exitval)
if __name__ == '__main__':
main()

117
setup.py Normal file
View File

@@ -0,0 +1,117 @@
#!/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 SetupPythonEnvironment(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-django) '
elif python_ver == 3:
path = os.path.join('env', 'python3')
symlink_path = os.path.join('env', 'python')
venv_module = 'venv'
prompt = 'py3-django'
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 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)
class SetupDjangoEnvironment(MyCommand):
description = 'create a typical installation for developing'
@staticmethod
def run():
# python_bin = sys.executable if sys.executable else 'python'
django_project_path = 'env/django'
# mgmt_script = os.path.join(django_project_path, 'manage.py')
sys.stdout.write('Install distribution in development mode...\n')
cmd = 'pip install -e .'
os.system(cmd)
sys.stdout.write('Setup django project in {}...\n'.format(django_project_path))
cmd = 'django-test-admin setup {}'.format(django_project_path)
os.system(cmd)
# sys.stdout.write('Make database migrations...\n')
# cmd = '{bin} {mgmt} makemigrations'.format(bin=python_bin, mgmt=mgmt_script)
# os.system(cmd)
# sys.stdout.write('Create database...\n')
# cmd = '{bin} {mgmt} migrate'.format(bin=python_bin, mgmt=mgmt_script)
# os.system(cmd)
# sys.stdout.write('Create superuser \'root\'...\n')
# cmd = ('{bin} {mgmt} createsuperuser'
# ' --username root').format(bin=python_bin, mgmt=mgmt_script)
# os.system(cmd)
setup(
name='django-test',
version='1.0',
description='An example django based web application.',
url='https://heinzelwelt.de',
maintainer='Jens Kleineheismann',
maintainer_email='heinzel@heinzelwelt.de',
cmdclass={
'mkpyenv': SetupPythonEnvironment,
'mkdjangoenv': SetupDjangoEnvironment,
},
packages=find_packages(),
include_package_data=True,
test_suite='tests.test_suite',
entry_points={
'console_scripts': [
'django-test-admin = base.console_scripts.admin:main',
],
},
install_requires=[
'coverage',
'django',
'django-extensions',
],
)

2
tests/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from .test_suite import TestSuite
test_suite = TestSuite()

71
tests/test_suite.py Normal file
View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime
import django
import os
import shutil
import sys
from django.test.utils import get_runner
from base.console_scripts.admin import DJANGO_MAIN_MODULE
from base.tests.utils import mkdtemp
class DjangoEnvironment(object):
@staticmethod
def _install_djangoproject(path):
cmd = 'django-test-admin setup "{}"'.format(path)
os.system(cmd)
def __init__(self, path=None, remove_after=True):
self.path = path
self._remove_after = remove_after
self._original_sys_path = None
self._modified_sys_path = None
def __enter__(self):
if self.path is None:
prefix = 'testrun-{datetime}-'.format(
datetime=datetime.datetime.now().strftime('%Y%m%d-%H%M')
)
self.path = mkdtemp(prefix=prefix)
self._install_djangoproject(self.path)
self._original_sys_path = sys.path
sys.path.append(self.path)
self._modified_sys_path = sys.path
os.environ['DJANGO_SETTINGS_MODULE'] = '{}.settings'.format(DJANGO_MAIN_MODULE)
django.setup()
from django.conf import settings
self.settings = settings
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
if self._remove_after:
shutil.rmtree(self.path)
class TestSuite(object):
@staticmethod
def run():
tests = ['base']
test_tags = None
exclude_test_tags = None
with DjangoEnvironment() as env:
test_runner_class = get_runner(env.settings)
test_runner = test_runner_class(tags=test_tags, exclude_tags=exclude_test_tags)
failures = test_runner.run_tests(tests)
return bool(failures)
def __call__(self):
sys.exit(self.run())