Merge pull request #1412 from ostcar/management
Rework of management command
This commit is contained in:
commit
0b29d13dc6
10
.gitignore
vendored
10
.gitignore
vendored
@ -7,16 +7,12 @@
|
||||
.virtualenv/*
|
||||
.venv/*
|
||||
|
||||
# Node modules
|
||||
# Javascript tools and libraries
|
||||
node_modules/*
|
||||
bower_components/*
|
||||
|
||||
# Development user data (settings, database, media, search index, static files)
|
||||
settings.py
|
||||
!tests/settings.py
|
||||
database.sqlite
|
||||
media/*
|
||||
whoosh_index/*
|
||||
bower_components/*
|
||||
development/*
|
||||
openslides/static/*
|
||||
|
||||
# Package building/IDE
|
||||
|
@ -8,6 +8,6 @@ install:
|
||||
- "node_modules/.bin/bower install"
|
||||
- "node_modules/.bin/gulp --production"
|
||||
script:
|
||||
- "DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py django test"
|
||||
- "DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test"
|
||||
- "coverage report -m --fail-under=80"
|
||||
- "flake8 --max-line-length=150 --statistics openslides tests"
|
||||
|
@ -11,7 +11,7 @@ def test(args=None):
|
||||
"""
|
||||
module = getattr(args, 'module', '')
|
||||
return call("DJANGO_SETTINGS_MODULE='tests.settings' coverage run "
|
||||
"./manage.py django test %s" % module)
|
||||
"./manage.py test %s" % module)
|
||||
|
||||
|
||||
@argument('--plain', action='store_true')
|
||||
@ -23,7 +23,6 @@ def coverage(args=None, plain=None):
|
||||
By default it creates a html report. With the argument --plain, it creates
|
||||
a plain report and fails under a certain amount of untested lines.
|
||||
"""
|
||||
test()
|
||||
if plain is None:
|
||||
plain = getattr(args, 'plain', False)
|
||||
if plain:
|
||||
|
@ -1,59 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from django.conf import ENVIRONMENT_VARIABLE
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
from openslides import get_version
|
||||
from openslides.utils.main import (
|
||||
detect_openslides_type,
|
||||
ensure_settings,
|
||||
get_browser_url,
|
||||
get_database_path_from_settings,
|
||||
get_default_settings_path,
|
||||
get_default_user_data_path,
|
||||
get_port,
|
||||
setup_django_settings_module,
|
||||
write_settings,
|
||||
UnknownCommand,
|
||||
ExceptionArgumentParser,
|
||||
get_development_settings_path,
|
||||
start_browser,
|
||||
translate_customizable_strings,
|
||||
write_settings)
|
||||
is_development)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entrance to OpenSlides.
|
||||
"""
|
||||
# Parse all command line args.
|
||||
args = parse_args()
|
||||
|
||||
# Setup settings path: Take it either from command line or get default path
|
||||
if hasattr(args, 'settings') and args.settings:
|
||||
settings = args.settings
|
||||
setup_django_settings_module(settings)
|
||||
parser = get_parser()
|
||||
try:
|
||||
known_args, unknown_args = parser.parse_known_args()
|
||||
except UnknownCommand:
|
||||
unknown_command = True
|
||||
else:
|
||||
if ENVIRONMENT_VARIABLE not in os.environ:
|
||||
openslides_type = detect_openslides_type()
|
||||
settings = get_default_settings_path(openslides_type)
|
||||
setup_django_settings_module(settings)
|
||||
unknown_command = False
|
||||
|
||||
if unknown_command:
|
||||
# Run a command, that is defined by the django management api
|
||||
development = is_development()
|
||||
setup_django_settings_module(development=development)
|
||||
execute_from_command_line(sys.argv)
|
||||
else:
|
||||
# The environment variable is set, so we do not need to process
|
||||
# anything more here.
|
||||
settings = None
|
||||
|
||||
# Process the subcommand's callback
|
||||
return args.callback(settings, args)
|
||||
# Run a command that is defined here
|
||||
# These are commands that can not rely on an existing settings
|
||||
known_args.callback(known_args)
|
||||
|
||||
|
||||
def parse_args():
|
||||
def get_parser():
|
||||
"""
|
||||
Parses all command line arguments. The subcommand 'django' links to
|
||||
Django's command-line utility.
|
||||
Parses all command line arguments.
|
||||
"""
|
||||
if len(sys.argv) == 1:
|
||||
if len(sys.argv) == 1 and not is_development():
|
||||
sys.argv.append('start')
|
||||
|
||||
# Init parser
|
||||
@ -64,7 +55,7 @@ def parse_args():
|
||||
'That means OpenSlides will setup default settings and '
|
||||
'database, start the tornado webserver, launch the '
|
||||
'default web browser and open the webinterface.')
|
||||
parser = argparse.ArgumentParser(description=description)
|
||||
parser = ExceptionArgumentParser(description=description)
|
||||
|
||||
# Add version argument
|
||||
parser.add_argument(
|
||||
@ -85,284 +76,88 @@ def parse_args():
|
||||
subcommand_start = subparsers.add_parser(
|
||||
'start',
|
||||
help='Setup settings and database, start tornado webserver, launch the '
|
||||
'default web browser and open the webinterface.')
|
||||
add_general_arguments(subcommand_start, ('settings', 'user_data_path', 'language', 'address', 'port'))
|
||||
'default web browser and open the webinterface. The environment '
|
||||
'variable DJANGO_SETTINGS_MODULE is ignored.')
|
||||
subcommand_start.add_argument(
|
||||
'--no-browser',
|
||||
action='store_true',
|
||||
help='Do not launch the default web browser.')
|
||||
subcommand_start.add_argument(
|
||||
'--settings_path',
|
||||
action='store',
|
||||
default=None,
|
||||
help='The used settings file. The file is created, if it does not exist.')
|
||||
subcommand_start.set_defaults(callback=start)
|
||||
|
||||
# Subcommand runserver
|
||||
subcommand_runserver = subparsers.add_parser(
|
||||
'runserver',
|
||||
help='Run OpenSlides using tornado webserver. The database tables must '
|
||||
'be created before. Use syncdb subcommand for this.')
|
||||
add_general_arguments(subcommand_runserver, ('settings', 'user_data_path', 'address', 'port'))
|
||||
subcommand_runserver.add_argument(
|
||||
'--start-browser',
|
||||
subcommand_start.add_argument(
|
||||
'--development',
|
||||
action='store_true',
|
||||
help='Launch the default web browser and open the webinterface.')
|
||||
subcommand_runserver.set_defaults(callback=runserver)
|
||||
help='Command for development purposes.')
|
||||
|
||||
# Subcommand syncdb
|
||||
subcommand_syncdb = subparsers.add_parser(
|
||||
'syncdb',
|
||||
help='Create or update database tables.')
|
||||
add_general_arguments(subcommand_syncdb, ('settings', 'user_data_path', 'language'))
|
||||
subcommand_syncdb.set_defaults(callback=syncdb)
|
||||
# Subcommand createsettings
|
||||
subcommand_createsettings = subparsers.add_parser(
|
||||
'createsettings',
|
||||
help='Create the settings file.')
|
||||
subcommand_createsettings.set_defaults(callback=createsettings)
|
||||
subcommand_createsettings.add_argument(
|
||||
'--settings_path',
|
||||
action='store',
|
||||
default=None,
|
||||
help='The used settings file. The file is created, even if it exists.')
|
||||
subcommand_createsettings.add_argument(
|
||||
'--development',
|
||||
action='store_true',
|
||||
help='Command for development purposes.')
|
||||
|
||||
# Subcommand createsuperuser
|
||||
subcommand_createsuperuser = subparsers.add_parser(
|
||||
'createsuperuser',
|
||||
help="Make sure the user 'admin' exists and uses 'admin' as password. "
|
||||
"The database tables must be created before. Use syncdb subcommand for this.")
|
||||
add_general_arguments(subcommand_createsuperuser, ('settings', 'user_data_path'))
|
||||
subcommand_createsuperuser.set_defaults(callback=createsuperuser)
|
||||
return parser
|
||||
|
||||
# Subcommand backupdb
|
||||
subcommand_backupdb = subparsers.add_parser(
|
||||
'backupdb',
|
||||
help='Store a backup copy of the SQLite3 database at the given path.')
|
||||
add_general_arguments(subcommand_backupdb, ('settings', 'user_data_path'))
|
||||
subcommand_backupdb.add_argument(
|
||||
'path',
|
||||
help='Path to the backup file. An existing file will be overwritten.')
|
||||
subcommand_backupdb.set_defaults(callback=backupdb)
|
||||
|
||||
# Subcommand deletedb
|
||||
subcommand_deletedb = subparsers.add_parser(
|
||||
'deletedb',
|
||||
help='Delete the SQLite3 database.')
|
||||
add_general_arguments(subcommand_deletedb, ('settings', 'user_data_path'))
|
||||
subcommand_deletedb.set_defaults(callback=deletedb)
|
||||
def start(args):
|
||||
"""
|
||||
Starts OpenSlides: Runs migrations and runs runserver.
|
||||
"""
|
||||
settings_path = args.settings_path
|
||||
development = is_development()
|
||||
|
||||
# Subcommand create-dev-settings
|
||||
subcommand_create_dev_settings = subparsers.add_parser(
|
||||
'create-dev-settings',
|
||||
help='Create a settings file at current working directory for development use.')
|
||||
subcommand_create_dev_settings.set_defaults(callback=create_dev_settings)
|
||||
|
||||
# Subcommand django
|
||||
subcommand_django_command_line_utility = subparsers.add_parser(
|
||||
'django',
|
||||
description="Link to Django's command-line utility. Type "
|
||||
"'%s django help' for more help on this." % parser.prog,
|
||||
help="Call Django's command-line utility.")
|
||||
subcommand_django_command_line_utility.set_defaults(
|
||||
callback=django_command_line_utility,
|
||||
django_args=['%s' % subcommand_django_command_line_utility.prog])
|
||||
|
||||
known_args, unknown_args = parser.parse_known_args()
|
||||
|
||||
if known_args.subcommand == 'django':
|
||||
if not unknown_args:
|
||||
unknown_args.append('help')
|
||||
known_args.django_args.extend(unknown_args)
|
||||
if settings_path is None:
|
||||
if development:
|
||||
settings_path = get_development_settings_path()
|
||||
else:
|
||||
if unknown_args:
|
||||
parser.error('Unknown arguments %s found.' % ' '.join(unknown_args))
|
||||
settings_path = get_default_settings_path()
|
||||
|
||||
return known_args
|
||||
# Write settings if it does not exists.
|
||||
if not os.path.isfile(settings_path):
|
||||
createsettings(args)
|
||||
|
||||
# Set the django setting module and run migrations
|
||||
# A manual given environment variable will be overwritten
|
||||
setup_django_settings_module(settings_path, development=development)
|
||||
|
||||
execute_from_command_line(['manage.py', 'migrate'])
|
||||
|
||||
if not args.no_browser:
|
||||
start_browser('http://0.0.0.0:8000')
|
||||
|
||||
# Start the webserver
|
||||
execute_from_command_line(['manage.py', 'runserver', '0.0.0.0:8000'])
|
||||
|
||||
|
||||
def add_general_arguments(subcommand, arguments):
|
||||
def createsettings(args):
|
||||
"""
|
||||
Adds the named arguments to the subcommand.
|
||||
Creates settings for OpenSlides.
|
||||
"""
|
||||
general_arguments = {}
|
||||
openslides_type = detect_openslides_type()
|
||||
|
||||
general_arguments['settings'] = (
|
||||
('-s', '--settings'),
|
||||
dict(help="Path to settings file. The file must be a python module. "
|
||||
"If if isn't provided, the %s environment variable will be "
|
||||
"used. If the environment variable isn't provided too, a "
|
||||
"default path according to the OpenSlides type will be "
|
||||
"used. At the moment it is %s" % (
|
||||
ENVIRONMENT_VARIABLE,
|
||||
get_default_settings_path(openslides_type))))
|
||||
general_arguments['user_data_path'] = (
|
||||
('-d', '--user-data-path'),
|
||||
dict(help='Path to the directory for user specific data files like SQLite3 '
|
||||
'database, uploaded media and search index. It is only used, '
|
||||
'when a new settings file is created. The given path is only '
|
||||
'written into the new settings file. Default according to the '
|
||||
'OpenSlides type is at the moment %s' % get_default_user_data_path(openslides_type)))
|
||||
general_arguments['language'] = (
|
||||
('-l', '--language'),
|
||||
dict(help='Language code. All customizable strings will be translated '
|
||||
'during database setup. See https://www.transifex.com/projects/p/openslides/ '
|
||||
'for supported languages.'))
|
||||
general_arguments['address'] = (
|
||||
('-a', '--address',),
|
||||
dict(default='0.0.0.0', help='IP address to listen on. Default is %(default)s.'))
|
||||
general_arguments['port'] = (
|
||||
('-p', '--port'),
|
||||
dict(type=int,
|
||||
default=80,
|
||||
help='Port to listen on. Default as admin or root is %(default)d, else 8000.'))
|
||||
|
||||
for argument in arguments:
|
||||
try:
|
||||
args, kwargs = general_arguments[argument]
|
||||
except KeyError:
|
||||
raise TypeError('The argument %s is not a valid general argument.' % argument)
|
||||
subcommand.add_argument(*args, **kwargs)
|
||||
|
||||
|
||||
def start(settings, args):
|
||||
"""
|
||||
Starts OpenSlides: Runs syncdb and runs runserver (tornado webserver).
|
||||
"""
|
||||
ensure_settings(settings, args)
|
||||
syncdb(settings, args)
|
||||
args.start_browser = not args.no_browser
|
||||
runserver(settings, args)
|
||||
|
||||
|
||||
def runserver(settings, args):
|
||||
"""
|
||||
Runs tornado webserver. Runs the function start_browser if the respective
|
||||
argument is given.
|
||||
"""
|
||||
ensure_settings(settings, args)
|
||||
port = get_port(address=args.address, port=args.port)
|
||||
if args.start_browser:
|
||||
browser_url = get_browser_url(address=args.address, port=port)
|
||||
start_browser(browser_url)
|
||||
|
||||
# Now the settings is available and the function can be imported.
|
||||
# TODO: only start tornado when it is used as wsgi server
|
||||
from openslides.utils.autoupdate import run_tornado
|
||||
run_tornado(args.address, port)
|
||||
|
||||
|
||||
def syncdb(settings, args):
|
||||
"""
|
||||
Run syncdb to create or update the database.
|
||||
"""
|
||||
ensure_settings(settings, args)
|
||||
db_file = get_database_path_from_settings()
|
||||
if db_file is not None:
|
||||
db_dir = os.path.dirname(db_file)
|
||||
if not os.path.exists(db_dir):
|
||||
os.makedirs(db_dir)
|
||||
if not os.path.exists(db_file):
|
||||
print('Clearing old search index...')
|
||||
execute_from_command_line(["", "clear_index", "--noinput"])
|
||||
execute_from_command_line(["", "syncdb", "--noinput"])
|
||||
if args.language:
|
||||
translate_customizable_strings(args.language)
|
||||
return 0
|
||||
|
||||
|
||||
def createsuperuser(settings, args):
|
||||
"""
|
||||
Creates or resets the admin user. Returns 0 to show success.
|
||||
"""
|
||||
ensure_settings(settings, args)
|
||||
# can't be imported in global scope as it already requires
|
||||
# the settings module during import
|
||||
from openslides.users.api import create_or_reset_admin_user
|
||||
if create_or_reset_admin_user():
|
||||
print('Admin user successfully created.')
|
||||
else:
|
||||
print('Admin user successfully reset.')
|
||||
return 0
|
||||
|
||||
|
||||
def backupdb(settings, args):
|
||||
"""
|
||||
Stores a backup copy of the SQlite3 database. Returns 0 on success, else 1.
|
||||
"""
|
||||
ensure_settings(settings, args)
|
||||
|
||||
from django.db import connection, transaction
|
||||
|
||||
@transaction.atomic
|
||||
def do_backup(src_path, dest_path):
|
||||
# perform a simple file-copy backup of the database
|
||||
# first we need a shared lock on the database, issuing a select()
|
||||
# will do this for us
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT count(*) from sqlite_master")
|
||||
# now copy the file
|
||||
try:
|
||||
shutil.copy(src_path, dest_path)
|
||||
except IOError:
|
||||
raise Exception("Database backup failed.")
|
||||
|
||||
database_path = get_database_path_from_settings()
|
||||
if database_path:
|
||||
do_backup(database_path, args.path)
|
||||
print('Database %s successfully stored at %s.' % (database_path, args.path))
|
||||
return_value = 0
|
||||
else:
|
||||
print('Error: Default database is not SQLite3. Only SQLite3 databases '
|
||||
'can currently be backuped.')
|
||||
return_value = 1
|
||||
return return_value
|
||||
|
||||
|
||||
def deletedb(settings, args):
|
||||
"""
|
||||
Deletes the sqlite3 database. Returns 0 on success, else 1.
|
||||
"""
|
||||
ensure_settings(settings, args)
|
||||
database_path = get_database_path_from_settings()
|
||||
if database_path and os.path.exists(database_path):
|
||||
os.remove(database_path)
|
||||
print('SQLite3 database file %s successfully deleted.' % database_path)
|
||||
execute_from_command_line(["", "clear_index", "--noinput"])
|
||||
print('Whoosh search index successfully cleared.')
|
||||
return_value = 0
|
||||
else:
|
||||
print('SQLite3 database file %s does not exist.' % database_path)
|
||||
return_value = 1
|
||||
return return_value
|
||||
|
||||
|
||||
def create_dev_settings(settings, args):
|
||||
"""
|
||||
Creates a settings file at the currect working directory for development use.
|
||||
"""
|
||||
settings = os.path.join(os.getcwd(), 'settings.py')
|
||||
if not os.path.exists(settings):
|
||||
settings_path = args.settings_path
|
||||
development = is_development()
|
||||
context = {}
|
||||
context['openslides_user_data_path'] = repr(os.getcwd())
|
||||
context['import_function'] = ''
|
||||
context['debug'] = 'True'
|
||||
write_settings(settings, **context)
|
||||
print('Settings file at %s successfully created.' % settings)
|
||||
return_value = 0
|
||||
else:
|
||||
print('Error: Settings file %s already exists.' % settings)
|
||||
return_value = 1
|
||||
return return_value
|
||||
|
||||
if development:
|
||||
if settings_path is None:
|
||||
settings_path = get_development_settings_path()
|
||||
context = {
|
||||
'openslides_user_data_path': repr(os.path.join(os.getcwd(), 'development')),
|
||||
'debug': 'True'}
|
||||
|
||||
def django_command_line_utility(settings, args):
|
||||
"""
|
||||
Runs Django's command line utility. Returns 0 on success, else 1.
|
||||
"""
|
||||
if 'runserver' in args.django_args:
|
||||
command = 'runserver'
|
||||
elif 'syncdb' in args.django_args:
|
||||
command = 'syncdb'
|
||||
elif 'createsuperuser' in args.django_args:
|
||||
command = 'createsuperuser'
|
||||
else:
|
||||
command = None
|
||||
if command:
|
||||
print("Error: The command '%s' is disabled in OpenSlides for use via Django's "
|
||||
"command line utility." % command)
|
||||
return_value = 1
|
||||
else:
|
||||
ensure_settings(settings, args)
|
||||
execute_from_command_line(args.django_args)
|
||||
return_value = 0
|
||||
return return_value
|
||||
settings_path = write_settings(settings_path, **context)
|
||||
print('Settings created at %s' % settings_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
42
openslides/core/management/commands/backupdb.py
Normal file
42
openslides/core/management/commands/backupdb.py
Normal file
@ -0,0 +1,42 @@
|
||||
from optparse import make_option # TODO: Use argpase in Django 1.8
|
||||
import shutil
|
||||
|
||||
from django.core.management.base import NoArgsCommand, CommandError
|
||||
from django.db import connection, transaction
|
||||
|
||||
from openslides.utils.main import get_database_path_from_settings
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
"""
|
||||
Commands to create or reset the adminuser
|
||||
"""
|
||||
option_list = NoArgsCommand.option_list + (
|
||||
make_option('--path', dest='path'),
|
||||
)
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
path = options.get('path')
|
||||
|
||||
@transaction.atomic
|
||||
def do_backup(src_path, dest_path):
|
||||
# perform a simple file-copy backup of the database
|
||||
# first we need a shared lock on the database, issuing a select()
|
||||
# will do this for us
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT count(*) from sqlite_master")
|
||||
# now copy the file
|
||||
try:
|
||||
shutil.copy(src_path, dest_path)
|
||||
except IOError:
|
||||
# TODO: use the IOError message as message for the user
|
||||
raise CommandError("Database backup failed.")
|
||||
|
||||
database_path = get_database_path_from_settings()
|
||||
if database_path:
|
||||
do_backup(database_path, path)
|
||||
self.stdout.write('Database %s successfully stored at %s.' % (database_path, path))
|
||||
else:
|
||||
raise CommandError(
|
||||
'Default database is not SQLite3. Only SQLite3 databases'
|
||||
'can currently be backuped.')
|
15
openslides/core/management/commands/createsuperuser.py
Normal file
15
openslides/core/management/commands/createsuperuser.py
Normal file
@ -0,0 +1,15 @@
|
||||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
from openslides.users.api import create_or_reset_admin_user
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
"""
|
||||
Commands to create or reset the adminuser
|
||||
"""
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
if create_or_reset_admin_user():
|
||||
self.stdout.write('Admin user successfully created.')
|
||||
else:
|
||||
self.stdout.write('Admin user successfully reset.')
|
29
openslides/core/management/commands/migrate.py
Normal file
29
openslides/core/management/commands/migrate.py
Normal file
@ -0,0 +1,29 @@
|
||||
import os
|
||||
|
||||
from django.core.management.commands.migrate import Command as _Command
|
||||
|
||||
from openslides.users.api import create_builtin_groups_and_admin
|
||||
|
||||
|
||||
class Command(_Command):
|
||||
"""
|
||||
Migration command that does the same like the django migration command but
|
||||
calles also creates the default groups
|
||||
"""
|
||||
# TODO: Try to get rid of this code. The problem are the ContentType
|
||||
# and Permission objects, which are created in the post_migrate signal, but
|
||||
# we need to things later.
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from django.conf import settings
|
||||
# Creates the folder for a sqlite database if necessary
|
||||
if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
|
||||
try:
|
||||
os.makedirs(settings.OPENSLIDES_USER_DATA_PATH)
|
||||
except (FileExistsError, AttributeError):
|
||||
# If the folder already exist or the settings OPENSLIDES_USER_DATA_PATH
|
||||
# is unknown, then do nothing
|
||||
pass
|
||||
|
||||
super().handle(*args, **options)
|
||||
create_builtin_groups_and_admin()
|
78
openslides/core/management/commands/runserver.py
Normal file
78
openslides/core/management/commands/runserver.py
Normal file
@ -0,0 +1,78 @@
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import socket
|
||||
import errno
|
||||
|
||||
from django.core.management.commands.runserver import Command as _Command
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils import translation
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from openslides.utils.autoupdate import run_tornado
|
||||
|
||||
|
||||
class Command(_Command):
|
||||
"""
|
||||
Runserver command from django core, but starts the tornado webserver.
|
||||
|
||||
Only the line to run tornado has changed from the django default
|
||||
implementation.
|
||||
"""
|
||||
# TODO: do not start tornado when the settings says so
|
||||
|
||||
def inner_run(self, *args, **options):
|
||||
from django.conf import settings
|
||||
# From the base class:
|
||||
self.stdout.write("Performing system checks...\n\n")
|
||||
self.validate(display_num_errors=True)
|
||||
|
||||
try:
|
||||
self.check_migrations()
|
||||
except ImproperlyConfigured:
|
||||
pass
|
||||
|
||||
now = datetime.now().strftime('%B %d, %Y - %X')
|
||||
|
||||
shutdown_message = options.get('shutdown_message', '')
|
||||
quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'
|
||||
|
||||
self.stdout.write((
|
||||
"%(started_at)s\n"
|
||||
"Django version %(version)s, using settings %(settings)r\n"
|
||||
"Starting development server at http://%(addr)s:%(port)s/\n"
|
||||
"Quit the server with %(quit_command)s.\n"
|
||||
) % {
|
||||
"started_at": now,
|
||||
"version": self.get_version(),
|
||||
"settings": settings.SETTINGS_MODULE,
|
||||
"addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
|
||||
"port": self.port,
|
||||
"quit_command": quit_command,
|
||||
})
|
||||
|
||||
translation.activate(settings.LANGUAGE_CODE)
|
||||
|
||||
try:
|
||||
handler = self.get_handler(*args, **options)
|
||||
run_tornado(
|
||||
self.addr,
|
||||
int(self.port),
|
||||
handler,
|
||||
ipv6=self.use_ipv6)
|
||||
except socket.error as e:
|
||||
# Use helpful error messages instead of ugly tracebacks.
|
||||
ERRORS = {
|
||||
errno.EACCES: "You don't have permission to access that port.",
|
||||
errno.EADDRINUSE: "That port is already in use.",
|
||||
errno.EADDRNOTAVAIL: "That IP address can't be assigned-to.",
|
||||
}
|
||||
try:
|
||||
error_text = ERRORS[e.errno]
|
||||
except KeyError:
|
||||
error_text = force_text(e)
|
||||
self.stderr.write("Error: %s" % error_text)
|
||||
sys.exit(0)
|
||||
except KeyboardInterrupt:
|
||||
if shutdown_message:
|
||||
self.stdout.write(shutdown_message)
|
||||
sys.exit(0)
|
@ -1,23 +0,0 @@
|
||||
from django.core.management.commands.syncdb import Command as _Command
|
||||
|
||||
from openslides.core.signals import post_database_setup
|
||||
|
||||
|
||||
class Command(_Command):
|
||||
"""
|
||||
Setup the database and sends the signal post_database_setup.
|
||||
"""
|
||||
def handle_noargs(self, *args, **kwargs):
|
||||
"""
|
||||
Calls Django's syncdb command but always in non-interactive mode. After
|
||||
this it sends our post_database_setup signal.
|
||||
"""
|
||||
interactive = kwargs.get('interactive', False)
|
||||
kwargs['interactive'] = False
|
||||
return_value = super(Command, self).handle_noargs(*args, **kwargs)
|
||||
post_database_setup.send(sender=self)
|
||||
|
||||
if interactive:
|
||||
print('Interactive mode (e. g. creating a superuser) is not possibile '
|
||||
'in OpenSlides. A superuser is automaticly created.')
|
||||
return return_value
|
@ -1,12 +1,9 @@
|
||||
from django import forms
|
||||
from django.dispatch import Signal
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
|
||||
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
||||
|
||||
post_database_setup = Signal()
|
||||
|
||||
|
||||
def setup_general_config(sender, **kwargs):
|
||||
"""
|
||||
|
@ -79,6 +79,7 @@ MIDDLEWARE_CLASSES = (
|
||||
ROOT_URLCONF = 'openslides.urls'
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'openslides.core',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
@ -90,7 +91,6 @@ INSTALLED_APPS = (
|
||||
'ckeditor',
|
||||
'rest_framework',
|
||||
'openslides.poll',
|
||||
'openslides.core',
|
||||
'openslides.account',
|
||||
'openslides.projector',
|
||||
'openslides.agenda',
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
from django.db.models.signals import post_migrate
|
||||
|
||||
|
||||
class MotionAppConfig(AppConfig):
|
||||
@ -12,13 +13,12 @@ class MotionAppConfig(AppConfig):
|
||||
|
||||
# Import all required stuff.
|
||||
from openslides.config.signals import config_signal
|
||||
from openslides.core.signals import post_database_setup
|
||||
from openslides.projector.api import register_slide_model
|
||||
from .signals import create_builtin_workflows, setup_motion_config
|
||||
|
||||
# Connect signals.
|
||||
config_signal.connect(setup_motion_config, dispatch_uid='setup_motion_config')
|
||||
post_database_setup.connect(create_builtin_workflows, dispatch_uid='motion_create_builtin_workflows')
|
||||
post_migrate.connect(create_builtin_workflows, dispatch_uid='motion_create_builtin_workflows')
|
||||
|
||||
# Register slides.
|
||||
Motion = self.get_model('Motion')
|
||||
|
@ -172,8 +172,11 @@ def create_builtin_workflows(sender, **kwargs):
|
||||
connected to the signal openslides.core.signals.post_database_setup
|
||||
during app loading.
|
||||
"""
|
||||
workflow_1, created = Workflow.objects.get_or_create(name=ugettext_noop('Simple Workflow'))
|
||||
if created:
|
||||
if Workflow.objects.exists():
|
||||
# If there is at least one workflow, then do nothing.
|
||||
return
|
||||
|
||||
workflow_1 = Workflow.objects.create(name=ugettext_noop('Simple Workflow'))
|
||||
state_1_1 = State.objects.create(name=ugettext_noop('submitted'),
|
||||
workflow=workflow_1,
|
||||
allow_create_poll=True,
|
||||
@ -192,8 +195,7 @@ def create_builtin_workflows(sender, **kwargs):
|
||||
workflow_1.first_state = state_1_1
|
||||
workflow_1.save()
|
||||
|
||||
workflow_2, created = Workflow.objects.get_or_create(name=ugettext_noop('Complex Workflow'))
|
||||
if created:
|
||||
workflow_2 = Workflow.objects.create(name=ugettext_noop('Complex Workflow'))
|
||||
state_2_1 = State.objects.create(name=ugettext_noop('published'),
|
||||
workflow=workflow_2,
|
||||
allow_support=True,
|
||||
|
@ -2,6 +2,7 @@ from random import choice
|
||||
|
||||
from django.contrib.auth.models import Permission, Group
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_noop
|
||||
|
||||
from .models import User
|
||||
|
||||
@ -49,6 +50,95 @@ def get_registered_group():
|
||||
return Group.objects.get(pk=2)
|
||||
|
||||
|
||||
def create_builtin_groups_and_admin():
|
||||
"""
|
||||
Creates the builtin groups: Anonymous, Registered, Delegates and Staff.
|
||||
|
||||
Creates the builtin user: admin.
|
||||
"""
|
||||
# Check whether the group pks 1 to 4 are free
|
||||
if Group.objects.filter(pk__in=range(1, 5)).exists():
|
||||
# Do completely nothing if there are already some of our groups in the database.
|
||||
return
|
||||
|
||||
# Anonymous (pk 1) and Registered (pk 2)
|
||||
ct_core = ContentType.objects.get(app_label='core', model='customslide')
|
||||
perm_11 = Permission.objects.get(content_type=ct_core, codename='can_see_projector')
|
||||
perm_12 = Permission.objects.get(content_type=ct_core, codename='can_see_dashboard')
|
||||
|
||||
ct_agenda = ContentType.objects.get(app_label='agenda', model='item')
|
||||
ct_speaker = ContentType.objects.get(app_label='agenda', model='speaker')
|
||||
perm_13 = Permission.objects.get(content_type=ct_agenda, codename='can_see_agenda')
|
||||
perm_14 = Permission.objects.get(content_type=ct_agenda, codename='can_see_orga_items')
|
||||
can_speak = Permission.objects.get(content_type=ct_speaker, codename='can_be_speaker')
|
||||
|
||||
ct_motion = ContentType.objects.get(app_label='motion', model='motion')
|
||||
perm_15 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion')
|
||||
|
||||
ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment')
|
||||
perm_16 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignment')
|
||||
|
||||
ct_users = ContentType.objects.get(app_label='users', model='user')
|
||||
perm_users_can_see_name = Permission.objects.get(content_type=ct_users, codename='can_see_name')
|
||||
perm_users_can_see_extra_data = Permission.objects.get(content_type=ct_users, codename='can_see_extra_data')
|
||||
|
||||
ct_mediafile = ContentType.objects.get(app_label='mediafile', model='mediafile')
|
||||
perm_18 = Permission.objects.get(content_type=ct_mediafile, codename='can_see')
|
||||
|
||||
base_permission_list = (
|
||||
perm_11,
|
||||
perm_12,
|
||||
perm_13,
|
||||
perm_14,
|
||||
perm_15,
|
||||
perm_16,
|
||||
perm_users_can_see_name,
|
||||
perm_users_can_see_extra_data,
|
||||
perm_18)
|
||||
|
||||
group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous'), pk=1)
|
||||
group_anonymous.permissions.add(*base_permission_list)
|
||||
group_registered = Group.objects.create(name=ugettext_noop('Registered'), pk=2)
|
||||
group_registered.permissions.add(can_speak, *base_permission_list)
|
||||
|
||||
# Delegates (pk 3)
|
||||
perm_31 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion')
|
||||
perm_32 = Permission.objects.get(content_type=ct_motion, codename='can_support_motion')
|
||||
perm_33 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_other')
|
||||
perm_34 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_self')
|
||||
perm_35 = Permission.objects.get(content_type=ct_mediafile, codename='can_upload')
|
||||
|
||||
group_delegates = Group.objects.create(name=ugettext_noop('Delegates'), pk=3)
|
||||
group_delegates.permissions.add(perm_31, perm_32, perm_33, perm_34, perm_35)
|
||||
|
||||
# Staff (pk 4)
|
||||
perm_41 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
|
||||
perm_42 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion')
|
||||
perm_43 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignment')
|
||||
perm_44 = Permission.objects.get(content_type=ct_users, codename='can_manage')
|
||||
perm_45 = Permission.objects.get(content_type=ct_core, codename='can_manage_projector')
|
||||
perm_46 = Permission.objects.get(content_type=ct_core, codename='can_use_chat')
|
||||
perm_47 = Permission.objects.get(content_type=ct_mediafile, codename='can_manage')
|
||||
|
||||
ct_config = ContentType.objects.get(app_label='config', model='configstore')
|
||||
perm_48 = Permission.objects.get(content_type=ct_config, codename='can_manage')
|
||||
|
||||
ct_tag = ContentType.objects.get(app_label='core', model='tag')
|
||||
can_manage_tags = Permission.objects.get(content_type=ct_tag, codename='can_manage_tags')
|
||||
|
||||
group_staff = Group.objects.create(name=ugettext_noop('Staff'), pk=4)
|
||||
# add delegate permissions (without can_support_motion)
|
||||
group_staff.permissions.add(perm_31, perm_33, perm_34, perm_35)
|
||||
# add staff permissions
|
||||
group_staff.permissions.add(perm_41, perm_42, perm_43, perm_44, perm_45, perm_46, perm_47, perm_48, can_manage_tags)
|
||||
# add can_see_name and can_see_extra_data permissions
|
||||
# TODO: Remove this redundancy after cleanup of the permission system.
|
||||
group_staff.permissions.add(perm_users_can_see_name, perm_users_can_see_extra_data)
|
||||
|
||||
# Admin user
|
||||
create_or_reset_admin_user()
|
||||
|
||||
|
||||
def create_or_reset_admin_user():
|
||||
group_staff = Group.objects.get(pk=4)
|
||||
try:
|
||||
|
@ -13,10 +13,9 @@ class UsersAppConfig(AppConfig):
|
||||
# Import all required stuff.
|
||||
from django.db.models.signals import post_save
|
||||
from openslides.config.signals import config_signal
|
||||
from openslides.core.signals import post_database_setup
|
||||
from openslides.projector.api import register_slide_model
|
||||
from openslides.utils.rest_api import router
|
||||
from .signals import create_builtin_groups_and_admin, setup_users_config, user_post_save
|
||||
from .signals import setup_users_config, user_post_save
|
||||
from .views import UserViewSet
|
||||
|
||||
# Load User model.
|
||||
@ -24,7 +23,6 @@ class UsersAppConfig(AppConfig):
|
||||
|
||||
# Connect signals.
|
||||
config_signal.connect(setup_users_config, dispatch_uid='setup_users_config')
|
||||
post_database_setup.connect(create_builtin_groups_and_admin, dispatch_uid='users_create_builtin_groups_and_admin')
|
||||
post_save.connect(user_post_save, sender=User, dispatch_uid='users_user_post_save')
|
||||
|
||||
# Register slides.
|
||||
|
@ -1,14 +1,9 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
|
||||
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
||||
|
||||
from .api import create_or_reset_admin_user
|
||||
from .models import Group
|
||||
|
||||
|
||||
def setup_users_config(sender, **kwargs):
|
||||
"""
|
||||
@ -104,100 +99,6 @@ def setup_users_config(sender, **kwargs):
|
||||
groups=(group_general, group_pdf))
|
||||
|
||||
|
||||
def create_builtin_groups_and_admin(sender, **kwargs):
|
||||
"""
|
||||
Receiver function to builtin groups and the admin user.
|
||||
|
||||
Creates the builtin groups: Anonymous, Registered, Delegates and Staff.
|
||||
|
||||
Creates the builtin user: admin.
|
||||
|
||||
It is connected to the signal
|
||||
openslides.core.signals.post_database_setup during app loading.
|
||||
"""
|
||||
# Check whether the group pks 1 to 4 are free
|
||||
if Group.objects.filter(pk__in=range(1, 5)).exists():
|
||||
# Do completely nothing if there are already some of our groups in the database.
|
||||
return
|
||||
|
||||
# Anonymous (pk 1) and Registered (pk 2)
|
||||
ct_core = ContentType.objects.get(app_label='core', model='customslide')
|
||||
perm_11 = Permission.objects.get(content_type=ct_core, codename='can_see_projector')
|
||||
perm_12 = Permission.objects.get(content_type=ct_core, codename='can_see_dashboard')
|
||||
|
||||
ct_agenda = ContentType.objects.get(app_label='agenda', model='item')
|
||||
ct_speaker = ContentType.objects.get(app_label='agenda', model='speaker')
|
||||
perm_13 = Permission.objects.get(content_type=ct_agenda, codename='can_see_agenda')
|
||||
perm_14 = Permission.objects.get(content_type=ct_agenda, codename='can_see_orga_items')
|
||||
can_speak = Permission.objects.get(content_type=ct_speaker, codename='can_be_speaker')
|
||||
|
||||
ct_motion = ContentType.objects.get(app_label='motion', model='motion')
|
||||
perm_15 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion')
|
||||
|
||||
ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment')
|
||||
perm_16 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignment')
|
||||
|
||||
ct_users = ContentType.objects.get(app_label='users', model='user')
|
||||
perm_users_can_see_name = Permission.objects.get(content_type=ct_users, codename='can_see_name')
|
||||
perm_users_can_see_extra_data = Permission.objects.get(content_type=ct_users, codename='can_see_extra_data')
|
||||
|
||||
ct_mediafile = ContentType.objects.get(app_label='mediafile', model='mediafile')
|
||||
perm_18 = Permission.objects.get(content_type=ct_mediafile, codename='can_see')
|
||||
|
||||
base_permission_list = (
|
||||
perm_11,
|
||||
perm_12,
|
||||
perm_13,
|
||||
perm_14,
|
||||
perm_15,
|
||||
perm_16,
|
||||
perm_users_can_see_name,
|
||||
perm_users_can_see_extra_data,
|
||||
perm_18)
|
||||
|
||||
group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous'), pk=1)
|
||||
group_anonymous.permissions.add(*base_permission_list)
|
||||
group_registered = Group.objects.create(name=ugettext_noop('Registered'), pk=2)
|
||||
group_registered.permissions.add(can_speak, *base_permission_list)
|
||||
|
||||
# Delegates (pk 3)
|
||||
perm_31 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion')
|
||||
perm_32 = Permission.objects.get(content_type=ct_motion, codename='can_support_motion')
|
||||
perm_33 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_other')
|
||||
perm_34 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_self')
|
||||
perm_35 = Permission.objects.get(content_type=ct_mediafile, codename='can_upload')
|
||||
|
||||
group_delegates = Group.objects.create(name=ugettext_noop('Delegates'), pk=3)
|
||||
group_delegates.permissions.add(perm_31, perm_32, perm_33, perm_34, perm_35)
|
||||
|
||||
# Staff (pk 4)
|
||||
perm_41 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
|
||||
perm_42 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion')
|
||||
perm_43 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignment')
|
||||
perm_44 = Permission.objects.get(content_type=ct_users, codename='can_manage')
|
||||
perm_45 = Permission.objects.get(content_type=ct_core, codename='can_manage_projector')
|
||||
perm_46 = Permission.objects.get(content_type=ct_core, codename='can_use_chat')
|
||||
perm_47 = Permission.objects.get(content_type=ct_mediafile, codename='can_manage')
|
||||
|
||||
ct_config = ContentType.objects.get(app_label='config', model='configstore')
|
||||
perm_48 = Permission.objects.get(content_type=ct_config, codename='can_manage')
|
||||
|
||||
ct_tag = ContentType.objects.get(app_label='core', model='tag')
|
||||
can_manage_tags = Permission.objects.get(content_type=ct_tag, codename='can_manage_tags')
|
||||
|
||||
group_staff = Group.objects.create(name=ugettext_noop('Staff'), pk=4)
|
||||
# add delegate permissions (without can_support_motion)
|
||||
group_staff.permissions.add(perm_31, perm_33, perm_34, perm_35)
|
||||
# add staff permissions
|
||||
group_staff.permissions.add(perm_41, perm_42, perm_43, perm_44, perm_45, perm_46, perm_47, perm_48, can_manage_tags)
|
||||
# add can_see_name and can_see_extra_data permissions
|
||||
# TODO: Remove this redundancy after cleanup of the permission system.
|
||||
group_staff.permissions.add(perm_users_can_see_name, perm_users_can_see_extra_data)
|
||||
|
||||
# Admin user
|
||||
create_or_reset_admin_user()
|
||||
|
||||
|
||||
def user_post_save(sender, instance, *args, **kwargs):
|
||||
"""
|
||||
Receiver function to add a new user to the registered group. It is
|
||||
|
@ -70,7 +70,7 @@ class OpenSlidesSockJSConnection(SockJSConnection):
|
||||
waiter.send(data)
|
||||
|
||||
|
||||
def run_tornado(addr, port):
|
||||
def run_tornado(addr, port, *args, **kwargs):
|
||||
"""
|
||||
Starts the tornado webserver as wsgi server for OpenSlides.
|
||||
|
||||
@ -79,14 +79,6 @@ def run_tornado(addr, port):
|
||||
# Don't try to read the command line args from openslides
|
||||
parse_command_line(args=[])
|
||||
|
||||
# Print listening address and port to command line
|
||||
if addr == '0.0.0.0':
|
||||
url_string = "the machine's local ip address"
|
||||
else:
|
||||
url_string = 'http://%s:%s' % (addr, port)
|
||||
# TODO: don't use print, use django logging
|
||||
print("Starting OpenSlides' tornado webserver listening to %(url_string)s" % {'url_string': url_string})
|
||||
|
||||
# Setup WSGIContainer
|
||||
app = WSGIContainer(get_wsgi_application())
|
||||
|
||||
@ -101,7 +93,7 @@ def run_tornado(addr, port):
|
||||
|
||||
# Start the application
|
||||
debug = settings.DEBUG
|
||||
tornado_app = Application(sock_js_router.urls + chatbox_socket_js_router.urls + other_urls, debug=debug)
|
||||
tornado_app = Application(sock_js_router.urls + chatbox_socket_js_router.urls + other_urls, autoreload=debug, debug=debug)
|
||||
server = HTTPServer(tornado_app)
|
||||
server.listen(port=port, address=addr)
|
||||
IOLoop.instance().start()
|
||||
|
@ -1,11 +1,11 @@
|
||||
import ctypes
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
import argparse
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.conf import ENVIRONMENT_VARIABLE
|
||||
@ -13,6 +13,7 @@ from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import activate, check_for_language, get_language
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
DEVELOPMENT_VERSION = 'Development Version'
|
||||
UNIX_VERSION = 'Unix Version'
|
||||
WINDOWS_VERSION = 'Windows Version'
|
||||
WINDOWS_PORTABLE_VERSION = 'Windows Portable Version'
|
||||
@ -30,6 +31,15 @@ class DatabaseInSettingsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownCommand(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ExceptionArgumentParser(argparse.ArgumentParser):
|
||||
def error(self, message):
|
||||
raise UnknownCommand(message)
|
||||
|
||||
|
||||
def detect_openslides_type():
|
||||
"""
|
||||
Returns the type of this OpenSlides version.
|
||||
@ -50,16 +60,19 @@ def detect_openslides_type():
|
||||
return openslides_type
|
||||
|
||||
|
||||
def get_default_settings_path(openslides_type):
|
||||
def get_default_settings_path(openslides_type=None):
|
||||
"""
|
||||
Returns the default settings path according to the OpenSlides type.
|
||||
|
||||
The argument 'openslides_type' has to be one of the three types mentioned in
|
||||
openslides.utils.main.
|
||||
"""
|
||||
if openslides_type is None:
|
||||
openslides_type = detect_openslides_type()
|
||||
|
||||
if openslides_type == UNIX_VERSION:
|
||||
parent_directory = os.environ.get(
|
||||
'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
|
||||
'XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
|
||||
elif openslides_type == WINDOWS_VERSION:
|
||||
parent_directory = get_win32_app_data_path()
|
||||
elif openslides_type == WINDOWS_PORTABLE_VERSION:
|
||||
@ -69,31 +82,52 @@ def get_default_settings_path(openslides_type):
|
||||
return os.path.join(parent_directory, 'openslides', 'settings.py')
|
||||
|
||||
|
||||
def setup_django_settings_module(settings_path):
|
||||
def get_development_settings_path():
|
||||
"""
|
||||
Returns the path to a local development settings.
|
||||
|
||||
On Unix systems: 'development/settings.py'
|
||||
"""
|
||||
return os.path.join('development', 'settings.py')
|
||||
|
||||
|
||||
def setup_django_settings_module(settings_path=None, development=None):
|
||||
"""
|
||||
Sets the environment variable ENVIRONMENT_VARIABLE, that means
|
||||
'DJANGO_SETTINGS_MODULE', to the given settings.
|
||||
|
||||
If no settings_path is given and the environment variable is already set,
|
||||
then this function does nothing.
|
||||
|
||||
If the argument settings_path is set, then the environment variable is
|
||||
always overwritten.
|
||||
"""
|
||||
if settings_path is None and os.environ.get(ENVIRONMENT_VARIABLE, None):
|
||||
return
|
||||
|
||||
if settings_path is None:
|
||||
if development:
|
||||
settings_path = get_development_settings_path()
|
||||
else:
|
||||
settings_path = get_default_settings_path()
|
||||
|
||||
settings_file = os.path.basename(settings_path)
|
||||
settings_module_name = ".".join(settings_file.split('.')[:-1])
|
||||
if '.' in settings_module_name:
|
||||
raise ImproperlyConfigured("'.' is not an allowed character in the settings-file")
|
||||
settings_module_dir = os.path.dirname(settings_path) # TODO: Use absolute path here or not?
|
||||
|
||||
# Change the python path. Also set the environment variable python path, so
|
||||
# change of the python path also works after a reload
|
||||
settings_module_dir = os.path.abspath(os.path.dirname(settings_path))
|
||||
sys.path.insert(0, settings_module_dir)
|
||||
os.environ[ENVIRONMENT_VARIABLE] = '%s' % settings_module_name
|
||||
try:
|
||||
os.environ['PYTHONPATH'] = os.pathsep.join((settings_module_dir, os.environ['PYTHONPATH']))
|
||||
except KeyError:
|
||||
# The environment variable is empty
|
||||
os.environ['PYTHONPATH'] = settings_module_dir
|
||||
|
||||
|
||||
def ensure_settings(settings, args):
|
||||
"""
|
||||
Create settings if a settings path is given and this file still does not exist.
|
||||
"""
|
||||
if settings and not os.path.exists(settings):
|
||||
if not hasattr(args, 'user_data_path'):
|
||||
context = get_default_settings_context()
|
||||
else:
|
||||
context = get_default_settings_context(args.user_data_path)
|
||||
write_settings(settings, **context)
|
||||
print('Settings file at %s successfully created.' % settings)
|
||||
# Set the environment variable to the settings module
|
||||
os.environ[ENVIRONMENT_VARIABLE] = settings_module_name
|
||||
|
||||
|
||||
def get_default_settings_context(user_data_path=None):
|
||||
@ -132,7 +166,7 @@ def get_default_user_data_path(openslides_type):
|
||||
"""
|
||||
if openslides_type == UNIX_VERSION:
|
||||
default_user_data_path = os.environ.get(
|
||||
'XDG_DATA_HOME', os.path.join(os.path.expanduser('~'), '.local', 'share'))
|
||||
'XDG_DATA_HOME', os.path.expanduser('~/.local/share'))
|
||||
elif openslides_type == WINDOWS_VERSION:
|
||||
default_user_data_path = get_win32_app_data_path()
|
||||
elif openslides_type == WINDOWS_PORTABLE_VERSION:
|
||||
@ -192,11 +226,16 @@ def get_win32_portable_user_data_path():
|
||||
return os.path.join(get_win32_portable_path(), 'openslides')
|
||||
|
||||
|
||||
def write_settings(settings_path, template=None, **context):
|
||||
def write_settings(settings_path=None, template=None, **context):
|
||||
"""
|
||||
Creates the settings file at the given path using the given values for the
|
||||
file template.
|
||||
|
||||
Retuns the path to the created settings.
|
||||
"""
|
||||
if settings_path is None:
|
||||
settings_path = get_default_settings_path()
|
||||
|
||||
if template is None:
|
||||
with open(os.path.join(os.path.dirname(__file__), 'settings.py.tpl')) as template_file:
|
||||
template = template_file.read()
|
||||
@ -205,56 +244,16 @@ def write_settings(settings_path, template=None, **context):
|
||||
# from django.core.management.commands.startproject
|
||||
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
||||
context.setdefault('secret_key', get_random_string(50, chars))
|
||||
for key, value in get_default_settings_context().items():
|
||||
context.setdefault(key, value)
|
||||
|
||||
content = template % context
|
||||
settings_module = os.path.realpath(os.path.dirname(settings_path))
|
||||
if not os.path.exists(settings_module):
|
||||
os.makedirs(settings_module)
|
||||
with open(settings_path, 'w') as settings_file:
|
||||
settings_file.write(content)
|
||||
|
||||
|
||||
def get_port(address, port):
|
||||
"""
|
||||
Checks if the port for the server is available and returns it the port. If
|
||||
it is port 80, try also port 8000.
|
||||
|
||||
The argument 'address' should be an IP address. The argument 'port' should
|
||||
be an integer.
|
||||
"""
|
||||
s = socket.socket()
|
||||
try:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind((address, port))
|
||||
s.listen(-1)
|
||||
except socket.error:
|
||||
error = True
|
||||
else:
|
||||
error = False
|
||||
finally:
|
||||
s.close()
|
||||
if error:
|
||||
if port == 80:
|
||||
port = get_port(address, 8000)
|
||||
else:
|
||||
raise PortIsBlockedError('Port %d is not available. Try another port using the --port option.' % port)
|
||||
return port
|
||||
|
||||
|
||||
def get_browser_url(address, port):
|
||||
"""
|
||||
Returns the url to open the web browser.
|
||||
|
||||
The argument 'address' should be an IP address. The argument 'port' should
|
||||
be an integer.
|
||||
"""
|
||||
browser_url = 'http://'
|
||||
if address == '0.0.0.0':
|
||||
browser_url += 'localhost'
|
||||
else:
|
||||
browser_url += address
|
||||
if not port == 80:
|
||||
browser_url += ":%d" % port
|
||||
return browser_url
|
||||
return os.path.realpath(settings_path)
|
||||
|
||||
|
||||
def start_browser(browser_url):
|
||||
@ -277,6 +276,8 @@ def get_database_path_from_settings():
|
||||
"""
|
||||
Retrieves the database path out of the settings file. Returns None,
|
||||
if it is not a SQLite3 database.
|
||||
|
||||
Needed for the backupdb command.
|
||||
"""
|
||||
from django.conf import settings as django_settings
|
||||
from django.db import DEFAULT_DB_ALIAS
|
||||
@ -304,3 +305,12 @@ def translate_customizable_strings(language_code):
|
||||
for name in config.get_all_translatable():
|
||||
config[name] = _(config[name])
|
||||
activate(current_language)
|
||||
|
||||
|
||||
def is_development():
|
||||
"""
|
||||
Returns True if the command is called for development.
|
||||
|
||||
This is the case if manage.py is used, or when the --development flag is set.
|
||||
"""
|
||||
return True if '--development' in sys.argv or 'manage.py' in sys.argv[0] else False
|
||||
|
@ -25,7 +25,7 @@ SECRET_KEY = %(secret_key)r
|
||||
# Use 'DEBUG = True' to get more details for server errors.
|
||||
# SECURITY WARNING: Don't run with debug turned on in production!
|
||||
|
||||
DEBUG = True
|
||||
DEBUG = %(debug)s
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
|
||||
|
@ -2,18 +2,12 @@ from django.core.management import call_command
|
||||
from django.test import TestCase as _TestCase
|
||||
|
||||
from openslides.config.api import config
|
||||
from openslides.core.signals import post_database_setup
|
||||
|
||||
|
||||
class TestCase(_TestCase):
|
||||
"""
|
||||
Overwrites Django's TestCase class to call the post_database_setup
|
||||
signal after the preparation of every test. Also refreshs the config cache.
|
||||
Overwrites Django's TestCase class to refreshs the config cache.
|
||||
"""
|
||||
def _pre_setup(self, *args, **kwargs):
|
||||
return_value = super(TestCase, self)._pre_setup(*args, **kwargs)
|
||||
post_database_setup.send(sender=self)
|
||||
return return_value
|
||||
|
||||
def _post_teardown(self, *args, **kwargs):
|
||||
return_value = super(TestCase, self)._post_teardown(*args, **kwargs)
|
||||
|
@ -21,14 +21,6 @@ SECRET_KEY = 'secret'
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
|
||||
# OpenSlides plugins
|
||||
# Add plugins to this list.
|
||||
|
||||
INSTALLED_PLUGINS += (
|
||||
'tests.utils',
|
||||
)
|
||||
|
||||
INSTALLED_APPS += INSTALLED_PLUGINS
|
||||
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class DummyModel(models.Model):
|
||||
"""
|
||||
Dummy model to test some model views.
|
||||
"""
|
||||
title = models.CharField(max_length=255)
|
@ -2,78 +2,76 @@ import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from openslides.__main__ import (
|
||||
add_general_arguments,
|
||||
django_command_line_utility,
|
||||
start,
|
||||
syncdb)
|
||||
from openslides.config.api import config
|
||||
from openslides.utils.main import (
|
||||
get_browser_url,
|
||||
get_database_path_from_settings,
|
||||
get_default_settings_context,
|
||||
get_default_settings_path,
|
||||
get_default_user_data_path,
|
||||
get_port,
|
||||
PortIsBlockedError,
|
||||
setup_django_settings_module,
|
||||
start_browser,
|
||||
translate_customizable_strings,
|
||||
UNIX_VERSION,
|
||||
WINDOWS_PORTABLE_VERSION)
|
||||
from openslides.utils import main
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
|
||||
class TestFunctions(TestCase):
|
||||
def test_get_default_user_data_path(self):
|
||||
self.assertIn(os.path.join('.local', 'share'), get_default_user_data_path(UNIX_VERSION))
|
||||
@patch('openslides.utils.main.sys')
|
||||
def test_detect_openslides_type_unix(self, mock_sys):
|
||||
"""
|
||||
Tests the return value on a unix system.
|
||||
"""
|
||||
mock_sys.platform = 'linux'
|
||||
self.assertEqual(main.detect_openslides_type(), main.UNIX_VERSION)
|
||||
|
||||
def test_get_default_settings_path(self):
|
||||
self.assertIn(
|
||||
os.path.join('.config', 'openslides', 'settings.py'), get_default_settings_path(UNIX_VERSION))
|
||||
@patch('openslides.utils.main.os.path.basename')
|
||||
@patch('openslides.utils.main.sys')
|
||||
def test_detect_openslides_type_win_portable(self, mock_sys, mock_os):
|
||||
"""
|
||||
Tests the return value on a windows portable system.
|
||||
"""
|
||||
mock_sys.platform = 'win32'
|
||||
mock_os.return_value = 'openslides.exe'
|
||||
self.assertEqual(main.detect_openslides_type(), main.WINDOWS_PORTABLE_VERSION)
|
||||
|
||||
@patch('openslides.utils.main.os.path.basename')
|
||||
@patch('openslides.utils.main.sys')
|
||||
def test_detect_openslides_type_win(self, mock_sys, mock_os):
|
||||
"""
|
||||
Tests the return value on a windows system.
|
||||
"""
|
||||
mock_sys.platform = 'win32'
|
||||
mock_os.return_value = 'python'
|
||||
self.assertEqual(main.detect_openslides_type(), main.WINDOWS_VERSION)
|
||||
|
||||
@patch('openslides.utils.main.detect_openslides_type')
|
||||
@patch('openslides.utils.main.os.path.expanduser')
|
||||
def test_get_default_settings_path_unix(self, mock_expanduser, mock_detect):
|
||||
mock_expanduser.return_value = '/home/test/.config'
|
||||
self.assertEqual(main.get_default_settings_path(main.UNIX_VERSION),
|
||||
'/home/test/.config/openslides/settings.py')
|
||||
|
||||
@patch('openslides.utils.main.get_win32_app_data_path')
|
||||
def test_get_default_settings_path_win(self, mock_win):
|
||||
mock_win.return_value = 'win32'
|
||||
self.assertEqual(main.get_default_settings_path(main.WINDOWS_VERSION),
|
||||
'win32/openslides/settings.py')
|
||||
|
||||
@patch('openslides.utils.main.get_win32_portable_path')
|
||||
def test_get_default_settings_path_portable(self, mock_portable):
|
||||
mock_portable.return_value = 'portable'
|
||||
self.assertEqual(main.get_default_settings_path(main.WINDOWS_PORTABLE_VERSION),
|
||||
'portable/openslides/settings.py')
|
||||
|
||||
def test_get_development_settings_path(self):
|
||||
self.assertEqual(main.get_development_settings_path(), os.sep.join(('development', 'settings.py')))
|
||||
|
||||
def test_setup_django_settings_module(self):
|
||||
main.setup_django_settings_module('test_dir_dhvnghfjdh456fzheg2f/test_path_bngjdhc756dzwncshdfnx.py')
|
||||
|
||||
self.assertEqual(os.environ['DJANGO_SETTINGS_MODULE'], 'test_path_bngjdhc756dzwncshdfnx')
|
||||
self.assertEqual(sys.path[0], os.path.abspath('test_dir_dhvnghfjdh456fzheg2f'))
|
||||
|
||||
@patch('openslides.utils.main.detect_openslides_type')
|
||||
def test_get_default_settings_context_portable(self, detect_mock):
|
||||
detect_mock.return_value = WINDOWS_PORTABLE_VERSION
|
||||
context = get_default_settings_context()
|
||||
detect_mock.return_value = main.WINDOWS_PORTABLE_VERSION
|
||||
context = main.get_default_settings_context()
|
||||
self.assertEqual(context['openslides_user_data_path'], 'get_win32_portable_user_data_path()')
|
||||
|
||||
def test_setup_django_settings_module(self):
|
||||
setup_django_settings_module('test_dir_dhvnghfjdh456fzheg2f/test_path_bngjdhc756dzwncshdfnx.py')
|
||||
self.assertEqual(os.environ['DJANGO_SETTINGS_MODULE'], 'test_path_bngjdhc756dzwncshdfnx')
|
||||
self.assertEqual(sys.path[0], 'test_dir_dhvnghfjdh456fzheg2f')
|
||||
|
||||
def test_setup_django_settings_module_error(self):
|
||||
self.assertRaisesMessage(
|
||||
ImproperlyConfigured,
|
||||
"'.' is not an allowed character in the settings-file",
|
||||
setup_django_settings_module,
|
||||
'wrong.file.py')
|
||||
|
||||
def test_get_browser_url(self):
|
||||
self.assertEqual(get_browser_url('123.456.789.365', 6789), 'http://123.456.789.365:6789')
|
||||
self.assertEqual(get_browser_url('123.456.789.365', 80), 'http://123.456.789.365')
|
||||
self.assertEqual(get_browser_url('0.0.0.0', 6789), 'http://localhost:6789')
|
||||
self.assertEqual(get_browser_url('0.0.0.0', 80), 'http://localhost')
|
||||
|
||||
def test_get_port(self):
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
def block_some_ports(mock):
|
||||
if not 8000 == mock_socket.socket().bind.call_args[0][0][1]:
|
||||
raise MyException
|
||||
|
||||
# Test open port
|
||||
self.assertEqual(get_port('localhost', 8234), 8234)
|
||||
# Test blocked ports
|
||||
with patch('openslides.utils.main.socket') as mock_socket:
|
||||
mock_socket.error = MyException
|
||||
mock_socket.socket().listen = MagicMock(side_effect=block_some_ports)
|
||||
self.assertEqual(get_port('localhost', 80), 8000)
|
||||
self.assertRaises(PortIsBlockedError, get_port, 'localhost', 81)
|
||||
def test_get_default_user_data_path(self):
|
||||
self.assertIn(os.path.join('.local', 'share'), main.get_default_user_data_path(main.UNIX_VERSION))
|
||||
|
||||
@patch('openslides.utils.main.threading.Thread')
|
||||
@patch('openslides.utils.main.time')
|
||||
@ -81,53 +79,18 @@ class TestFunctions(TestCase):
|
||||
def test_start_browser(self, mock_webbrowser, mock_time, mock_Thread):
|
||||
browser_mock = MagicMock()
|
||||
mock_webbrowser.get.return_value = browser_mock
|
||||
start_browser('http://localhost:8234')
|
||||
|
||||
main.start_browser('http://localhost:8234')
|
||||
|
||||
self.assertTrue(mock_Thread.called)
|
||||
inner_function = mock_Thread.call_args[1]['target']
|
||||
inner_function()
|
||||
browser_mock.open.assert_called_with('http://localhost:8234')
|
||||
|
||||
def test_get_database_path_from_settings_memory(self):
|
||||
self.assertEqual(get_database_path_from_settings(), ':memory:')
|
||||
self.assertEqual(main.get_database_path_from_settings(), ':memory:')
|
||||
|
||||
def test_translate_customizable_strings(self):
|
||||
self.assertEqual(config['event_description'], 'Presentation and assembly system')
|
||||
translate_customizable_strings('de')
|
||||
main.translate_customizable_strings('de')
|
||||
self.assertEqual(config['event_description'], u'Präsentations- und Versammlungssystem')
|
||||
|
||||
|
||||
class TestOtherFunctions(TestCase):
|
||||
"""
|
||||
Tests functions in openslides.__main__
|
||||
"""
|
||||
def test_add_general_arguments_wrong_arg(self):
|
||||
self.assertRaisesMessage(
|
||||
TypeError,
|
||||
'The argument invalid_argument is not a valid general argument.',
|
||||
add_general_arguments,
|
||||
None,
|
||||
['invalid_argument'])
|
||||
|
||||
@patch('openslides.__main__.syncdb')
|
||||
@patch('openslides.__main__.runserver')
|
||||
def test_start(self, mock_runserver, mock_syncdb):
|
||||
mock_args = MagicMock()
|
||||
start(settings=None, args=mock_args)
|
||||
self.assertTrue(mock_syncdb.called)
|
||||
mock_runserver.assert_called_with(None, mock_args)
|
||||
|
||||
@patch('openslides.__main__.os.path.exists')
|
||||
@patch('openslides.__main__.os.makedirs')
|
||||
@patch('openslides.__main__.execute_from_command_line')
|
||||
def test_syncdb(self, mock_execute_from_command_line, mock_os, mock_exists):
|
||||
mock_exists.return_value = True
|
||||
mock_args = MagicMock()
|
||||
mock_args.language = None
|
||||
syncdb(settings=None, args=mock_args)
|
||||
self.assertTrue(mock_execute_from_command_line.called)
|
||||
|
||||
@patch('openslides.__main__.execute_from_command_line')
|
||||
def test_django_command_line_utility(self, mock_execute_from_command_line):
|
||||
mock_args = MagicMock()
|
||||
django_command_line_utility(settings=None, args=mock_args)
|
||||
self.assertTrue(mock_execute_from_command_line.called)
|
||||
|
@ -3,7 +3,6 @@ from unittest.mock import patch
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import clear_url_caches
|
||||
from django.db import connection, reset_queries
|
||||
from django.test import RequestFactory
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
@ -13,7 +12,6 @@ from openslides.utils.signals import template_manipulation
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
from . import views as test_views
|
||||
from .models import DummyModel
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='tests.utils.urls')
|
||||
@ -194,15 +192,22 @@ class QuestionViewTest(ViewTestCase):
|
||||
self.assertIn('the question', question)
|
||||
|
||||
|
||||
class DetailViewTest(ViewTestCase):
|
||||
def test_get_object_cache(self):
|
||||
with self.settings(DEBUG=True):
|
||||
DummyModel.objects.create(title='title_ooth8she7yos1Oi8Boh3')
|
||||
reset_queries()
|
||||
client = Client()
|
||||
response = client.get('/dummy_detail_view/1/')
|
||||
self.assertContains(response, 'title_ooth8she7yos1Oi8Boh3')
|
||||
self.assertEqual(len(connection.queries), 3)
|
||||
class SingleObjectMixinTest(TestCase):
|
||||
|
||||
@patch('openslides.utils.views.django_views.detail.SingleObjectMixin.get_object')
|
||||
def test_get_object_cache(self, mock_super_class_get_object):
|
||||
"""
|
||||
Test that the method get_object caches his result.
|
||||
|
||||
Tests that get_object from the django view is only called once, even if
|
||||
get_object on our class is called twice.
|
||||
"""
|
||||
view = views.SingleObjectMixin()
|
||||
|
||||
view.get_object()
|
||||
view.get_object()
|
||||
|
||||
mock_super_class_get_object.assert_called_once_with()
|
||||
|
||||
|
||||
def set_context(sender, request, context, **kwargs):
|
||||
|
@ -26,7 +26,4 @@ urlpatterns += patterns(
|
||||
|
||||
url(r'^permission_mixin3/$',
|
||||
views.PermissionMixinView.as_view(required_permission='agenda.can_see_agenda')),
|
||||
|
||||
url(r'^dummy_detail_view/(?P<pk>\d+)/$',
|
||||
views.DummyDetailView.as_view()),
|
||||
)
|
||||
|
@ -2,8 +2,6 @@ from django.http import HttpResponse
|
||||
|
||||
from openslides.utils import views
|
||||
|
||||
from .models import DummyModel
|
||||
|
||||
|
||||
class GetAbsoluteUrl(object):
|
||||
"""
|
||||
@ -47,15 +45,3 @@ class UrlMixinView(views.UrlMixin, views.View):
|
||||
|
||||
class UrlMixinViewWithObject(views.UrlMixin, views.View):
|
||||
object = GetAbsoluteUrl()
|
||||
|
||||
|
||||
class DummyDetailView(views.DetailView):
|
||||
model = DummyModel
|
||||
|
||||
def get_context_data(self, **context):
|
||||
context = super(DummyDetailView, self).get_context_data(**context)
|
||||
# Just call get_object() some times to test the cache
|
||||
self.get_object()
|
||||
self.get_object()
|
||||
self.get_object()
|
||||
return context
|
||||
|
Loading…
Reference in New Issue
Block a user