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

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)