Rework of management command
This commit is contained in:
parent
8b88d38ac5
commit
35024764cf
10
.gitignore
vendored
10
.gitignore
vendored
@ -7,16 +7,12 @@
|
|||||||
.virtualenv/*
|
.virtualenv/*
|
||||||
.venv/*
|
.venv/*
|
||||||
|
|
||||||
# Node modules
|
# Javascript tools and libraries
|
||||||
node_modules/*
|
node_modules/*
|
||||||
|
bower_components/*
|
||||||
|
|
||||||
# Development user data (settings, database, media, search index, static files)
|
# Development user data (settings, database, media, search index, static files)
|
||||||
settings.py
|
development/*
|
||||||
!tests/settings.py
|
|
||||||
database.sqlite
|
|
||||||
media/*
|
|
||||||
whoosh_index/*
|
|
||||||
bower_components/*
|
|
||||||
openslides/static/*
|
openslides/static/*
|
||||||
|
|
||||||
# Package building/IDE
|
# Package building/IDE
|
||||||
|
@ -8,6 +8,6 @@ install:
|
|||||||
- "node_modules/.bin/bower install"
|
- "node_modules/.bin/bower install"
|
||||||
- "node_modules/.bin/gulp --production"
|
- "node_modules/.bin/gulp --production"
|
||||||
script:
|
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"
|
- "coverage report -m --fail-under=80"
|
||||||
- "flake8 --max-line-length=150 --statistics openslides tests"
|
- "flake8 --max-line-length=150 --statistics openslides tests"
|
||||||
|
@ -11,7 +11,7 @@ def test(args=None):
|
|||||||
"""
|
"""
|
||||||
module = getattr(args, 'module', '')
|
module = getattr(args, 'module', '')
|
||||||
return call("DJANGO_SETTINGS_MODULE='tests.settings' coverage run "
|
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')
|
@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
|
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.
|
a plain report and fails under a certain amount of untested lines.
|
||||||
"""
|
"""
|
||||||
test()
|
|
||||||
if plain is None:
|
if plain is None:
|
||||||
plain = getattr(args, 'plain', False)
|
plain = getattr(args, 'plain', False)
|
||||||
if plain:
|
if plain:
|
||||||
|
@ -1,59 +1,50 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.conf import ENVIRONMENT_VARIABLE
|
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
from openslides import get_version
|
from openslides import get_version
|
||||||
from openslides.utils.main import (
|
from openslides.utils.main import (
|
||||||
detect_openslides_type,
|
|
||||||
ensure_settings,
|
|
||||||
get_browser_url,
|
|
||||||
get_database_path_from_settings,
|
|
||||||
get_default_settings_path,
|
get_default_settings_path,
|
||||||
get_default_user_data_path,
|
|
||||||
get_port,
|
|
||||||
setup_django_settings_module,
|
setup_django_settings_module,
|
||||||
|
write_settings,
|
||||||
|
UnknownCommand,
|
||||||
|
ExceptionArgumentParser,
|
||||||
|
get_development_settings_path,
|
||||||
start_browser,
|
start_browser,
|
||||||
translate_customizable_strings,
|
is_development)
|
||||||
write_settings)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
Main entrance to OpenSlides.
|
Main entrance to OpenSlides.
|
||||||
"""
|
"""
|
||||||
# Parse all command line args.
|
parser = get_parser()
|
||||||
args = parse_args()
|
try:
|
||||||
|
known_args, unknown_args = parser.parse_known_args()
|
||||||
# Setup settings path: Take it either from command line or get default path
|
except UnknownCommand:
|
||||||
if hasattr(args, 'settings') and args.settings:
|
unknown_command = True
|
||||||
settings = args.settings
|
|
||||||
setup_django_settings_module(settings)
|
|
||||||
else:
|
else:
|
||||||
if ENVIRONMENT_VARIABLE not in os.environ:
|
unknown_command = False
|
||||||
openslides_type = detect_openslides_type()
|
|
||||||
settings = get_default_settings_path(openslides_type)
|
|
||||||
setup_django_settings_module(settings)
|
|
||||||
else:
|
|
||||||
# The environment variable is set, so we do not need to process
|
|
||||||
# anything more here.
|
|
||||||
settings = None
|
|
||||||
|
|
||||||
# Process the subcommand's callback
|
if unknown_command:
|
||||||
return args.callback(settings, args)
|
# 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:
|
||||||
|
# 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
|
Parses all command line arguments.
|
||||||
Django's command-line utility.
|
|
||||||
"""
|
"""
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1 and not is_development():
|
||||||
sys.argv.append('start')
|
sys.argv.append('start')
|
||||||
|
|
||||||
# Init parser
|
# Init parser
|
||||||
@ -64,7 +55,7 @@ def parse_args():
|
|||||||
'That means OpenSlides will setup default settings and '
|
'That means OpenSlides will setup default settings and '
|
||||||
'database, start the tornado webserver, launch the '
|
'database, start the tornado webserver, launch the '
|
||||||
'default web browser and open the webinterface.')
|
'default web browser and open the webinterface.')
|
||||||
parser = argparse.ArgumentParser(description=description)
|
parser = ExceptionArgumentParser(description=description)
|
||||||
|
|
||||||
# Add version argument
|
# Add version argument
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@ -85,284 +76,88 @@ def parse_args():
|
|||||||
subcommand_start = subparsers.add_parser(
|
subcommand_start = subparsers.add_parser(
|
||||||
'start',
|
'start',
|
||||||
help='Setup settings and database, start tornado webserver, launch the '
|
help='Setup settings and database, start tornado webserver, launch the '
|
||||||
'default web browser and open the webinterface.')
|
'default web browser and open the webinterface. The environment '
|
||||||
add_general_arguments(subcommand_start, ('settings', 'user_data_path', 'language', 'address', 'port'))
|
'variable DJANGO_SETTINGS_MODULE is ignored.')
|
||||||
subcommand_start.add_argument(
|
subcommand_start.add_argument(
|
||||||
'--no-browser',
|
'--no-browser',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Do not launch the default web browser.')
|
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_start.set_defaults(callback=start)
|
||||||
|
subcommand_start.add_argument(
|
||||||
# Subcommand runserver
|
'--development',
|
||||||
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',
|
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Launch the default web browser and open the webinterface.')
|
help='Command for development purposes.')
|
||||||
subcommand_runserver.set_defaults(callback=runserver)
|
|
||||||
|
|
||||||
# Subcommand syncdb
|
# Subcommand createsettings
|
||||||
subcommand_syncdb = subparsers.add_parser(
|
subcommand_createsettings = subparsers.add_parser(
|
||||||
'syncdb',
|
'createsettings',
|
||||||
help='Create or update database tables.')
|
help='Create the settings file.')
|
||||||
add_general_arguments(subcommand_syncdb, ('settings', 'user_data_path', 'language'))
|
subcommand_createsettings.set_defaults(callback=createsettings)
|
||||||
subcommand_syncdb.set_defaults(callback=syncdb)
|
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
|
return parser
|
||||||
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)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
else:
|
|
||||||
if unknown_args:
|
|
||||||
parser.error('Unknown arguments %s found.' % ' '.join(unknown_args))
|
|
||||||
|
|
||||||
return known_args
|
|
||||||
|
|
||||||
|
|
||||||
def add_general_arguments(subcommand, arguments):
|
def start(args):
|
||||||
"""
|
"""
|
||||||
Adds the named arguments to the subcommand.
|
Starts OpenSlides: Runs migrations and runs runserver.
|
||||||
"""
|
"""
|
||||||
general_arguments = {}
|
settings_path = args.settings_path
|
||||||
openslides_type = detect_openslides_type()
|
development = is_development()
|
||||||
|
|
||||||
general_arguments['settings'] = (
|
if settings_path is None:
|
||||||
('-s', '--settings'),
|
if development:
|
||||||
dict(help="Path to settings file. The file must be a python module. "
|
settings_path = get_development_settings_path()
|
||||||
"If if isn't provided, the %s environment variable will be "
|
else:
|
||||||
"used. If the environment variable isn't provided too, a "
|
settings_path = get_default_settings_path()
|
||||||
"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:
|
# Write settings if it does not exists.
|
||||||
try:
|
if not os.path.isfile(settings_path):
|
||||||
args, kwargs = general_arguments[argument]
|
createsettings(args)
|
||||||
except KeyError:
|
|
||||||
raise TypeError('The argument %s is not a valid general argument.' % argument)
|
# Set the django setting module and run migrations
|
||||||
subcommand.add_argument(*args, **kwargs)
|
# 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 start(settings, args):
|
def createsettings(args):
|
||||||
"""
|
"""
|
||||||
Starts OpenSlides: Runs syncdb and runs runserver (tornado webserver).
|
Creates settings for OpenSlides.
|
||||||
"""
|
"""
|
||||||
ensure_settings(settings, args)
|
settings_path = args.settings_path
|
||||||
syncdb(settings, args)
|
development = is_development()
|
||||||
args.start_browser = not args.no_browser
|
context = {}
|
||||||
runserver(settings, args)
|
|
||||||
|
|
||||||
|
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 runserver(settings, args):
|
settings_path = write_settings(settings_path, **context)
|
||||||
"""
|
print('Settings created at %s' % settings_path)
|
||||||
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):
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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 import forms
|
||||||
from django.dispatch import Signal
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
||||||
|
|
||||||
post_database_setup = Signal()
|
|
||||||
|
|
||||||
|
|
||||||
def setup_general_config(sender, **kwargs):
|
def setup_general_config(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -79,6 +79,7 @@ MIDDLEWARE_CLASSES = (
|
|||||||
ROOT_URLCONF = 'openslides.urls'
|
ROOT_URLCONF = 'openslides.urls'
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
|
'openslides.core',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
@ -90,7 +91,6 @@ INSTALLED_APPS = (
|
|||||||
'ckeditor',
|
'ckeditor',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'openslides.poll',
|
'openslides.poll',
|
||||||
'openslides.core',
|
|
||||||
'openslides.account',
|
'openslides.account',
|
||||||
'openslides.projector',
|
'openslides.projector',
|
||||||
'openslides.agenda',
|
'openslides.agenda',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
|
||||||
class MotionAppConfig(AppConfig):
|
class MotionAppConfig(AppConfig):
|
||||||
@ -12,13 +13,12 @@ class MotionAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from openslides.config.signals import config_signal
|
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.projector.api import register_slide_model
|
||||||
from .signals import create_builtin_workflows, setup_motion_config
|
from .signals import create_builtin_workflows, setup_motion_config
|
||||||
|
|
||||||
# Connect signals.
|
# Connect signals.
|
||||||
config_signal.connect(setup_motion_config, dispatch_uid='setup_motion_config')
|
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.
|
# Register slides.
|
||||||
Motion = self.get_model('Motion')
|
Motion = self.get_model('Motion')
|
||||||
|
@ -172,73 +172,75 @@ def create_builtin_workflows(sender, **kwargs):
|
|||||||
connected to the signal openslides.core.signals.post_database_setup
|
connected to the signal openslides.core.signals.post_database_setup
|
||||||
during app loading.
|
during app loading.
|
||||||
"""
|
"""
|
||||||
workflow_1, created = Workflow.objects.get_or_create(name=ugettext_noop('Simple Workflow'))
|
if Workflow.objects.exists():
|
||||||
if created:
|
# If there is at least one workflow, then do nothing.
|
||||||
state_1_1 = State.objects.create(name=ugettext_noop('submitted'),
|
return
|
||||||
workflow=workflow_1,
|
|
||||||
allow_create_poll=True,
|
|
||||||
allow_support=True,
|
|
||||||
allow_submitter_edit=True)
|
|
||||||
state_1_2 = State.objects.create(name=ugettext_noop('accepted'),
|
|
||||||
workflow=workflow_1,
|
|
||||||
action_word=ugettext_noop('Accept'))
|
|
||||||
state_1_3 = State.objects.create(name=ugettext_noop('rejected'),
|
|
||||||
workflow=workflow_1,
|
|
||||||
action_word=ugettext_noop('Reject'))
|
|
||||||
state_1_4 = State.objects.create(name=ugettext_noop('not decided'),
|
|
||||||
workflow=workflow_1,
|
|
||||||
action_word=ugettext_noop('Do not decide'))
|
|
||||||
state_1_1.next_states.add(state_1_2, state_1_3, state_1_4)
|
|
||||||
workflow_1.first_state = state_1_1
|
|
||||||
workflow_1.save()
|
|
||||||
|
|
||||||
workflow_2, created = Workflow.objects.get_or_create(name=ugettext_noop('Complex Workflow'))
|
workflow_1 = Workflow.objects.create(name=ugettext_noop('Simple Workflow'))
|
||||||
if created:
|
state_1_1 = State.objects.create(name=ugettext_noop('submitted'),
|
||||||
state_2_1 = State.objects.create(name=ugettext_noop('published'),
|
workflow=workflow_1,
|
||||||
workflow=workflow_2,
|
allow_create_poll=True,
|
||||||
allow_support=True,
|
allow_support=True,
|
||||||
allow_submitter_edit=True,
|
allow_submitter_edit=True)
|
||||||
dont_set_identifier=True)
|
state_1_2 = State.objects.create(name=ugettext_noop('accepted'),
|
||||||
state_2_2 = State.objects.create(name=ugettext_noop('permitted'),
|
workflow=workflow_1,
|
||||||
workflow=workflow_2,
|
action_word=ugettext_noop('Accept'))
|
||||||
action_word=ugettext_noop('Permit'),
|
state_1_3 = State.objects.create(name=ugettext_noop('rejected'),
|
||||||
allow_create_poll=True,
|
workflow=workflow_1,
|
||||||
allow_submitter_edit=True,
|
action_word=ugettext_noop('Reject'))
|
||||||
versioning=True,
|
state_1_4 = State.objects.create(name=ugettext_noop('not decided'),
|
||||||
leave_old_version_active=True)
|
workflow=workflow_1,
|
||||||
state_2_3 = State.objects.create(name=ugettext_noop('accepted'),
|
action_word=ugettext_noop('Do not decide'))
|
||||||
workflow=workflow_2,
|
state_1_1.next_states.add(state_1_2, state_1_3, state_1_4)
|
||||||
action_word=ugettext_noop('Accept'),
|
workflow_1.first_state = state_1_1
|
||||||
versioning=True)
|
workflow_1.save()
|
||||||
state_2_4 = State.objects.create(name=ugettext_noop('rejected'),
|
|
||||||
workflow=workflow_2,
|
workflow_2 = Workflow.objects.create(name=ugettext_noop('Complex Workflow'))
|
||||||
action_word=ugettext_noop('Reject'),
|
state_2_1 = State.objects.create(name=ugettext_noop('published'),
|
||||||
versioning=True)
|
workflow=workflow_2,
|
||||||
state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'),
|
allow_support=True,
|
||||||
workflow=workflow_2,
|
allow_submitter_edit=True,
|
||||||
action_word=ugettext_noop('Withdraw'),
|
dont_set_identifier=True)
|
||||||
versioning=True)
|
state_2_2 = State.objects.create(name=ugettext_noop('permitted'),
|
||||||
state_2_6 = State.objects.create(name=ugettext_noop('adjourned'),
|
workflow=workflow_2,
|
||||||
workflow=workflow_2,
|
action_word=ugettext_noop('Permit'),
|
||||||
action_word=ugettext_noop('Adjourn'),
|
allow_create_poll=True,
|
||||||
versioning=True)
|
allow_submitter_edit=True,
|
||||||
state_2_7 = State.objects.create(name=ugettext_noop('not concerned'),
|
versioning=True,
|
||||||
workflow=workflow_2,
|
leave_old_version_active=True)
|
||||||
action_word=ugettext_noop('Do not concern'),
|
state_2_3 = State.objects.create(name=ugettext_noop('accepted'),
|
||||||
versioning=True)
|
workflow=workflow_2,
|
||||||
state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'),
|
action_word=ugettext_noop('Accept'),
|
||||||
workflow=workflow_2,
|
versioning=True)
|
||||||
action_word=ugettext_noop('Commit a bill'),
|
state_2_4 = State.objects.create(name=ugettext_noop('rejected'),
|
||||||
versioning=True)
|
workflow=workflow_2,
|
||||||
state_2_9 = State.objects.create(name=ugettext_noop('needs review'),
|
action_word=ugettext_noop('Reject'),
|
||||||
workflow=workflow_2,
|
versioning=True)
|
||||||
action_word=ugettext_noop('Needs review'),
|
state_2_5 = State.objects.create(name=ugettext_noop('withdrawed'),
|
||||||
versioning=True)
|
workflow=workflow_2,
|
||||||
state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'),
|
action_word=ugettext_noop('Withdraw'),
|
||||||
workflow=workflow_2,
|
versioning=True)
|
||||||
action_word=ugettext_noop('Reject (not authorized)'),
|
state_2_6 = State.objects.create(name=ugettext_noop('adjourned'),
|
||||||
versioning=True)
|
workflow=workflow_2,
|
||||||
state_2_1.next_states.add(state_2_2, state_2_5, state_2_10)
|
action_word=ugettext_noop('Adjourn'),
|
||||||
state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9)
|
versioning=True)
|
||||||
workflow_2.first_state = state_2_1
|
state_2_7 = State.objects.create(name=ugettext_noop('not concerned'),
|
||||||
workflow_2.save()
|
workflow=workflow_2,
|
||||||
|
action_word=ugettext_noop('Do not concern'),
|
||||||
|
versioning=True)
|
||||||
|
state_2_8 = State.objects.create(name=ugettext_noop('commited a bill'),
|
||||||
|
workflow=workflow_2,
|
||||||
|
action_word=ugettext_noop('Commit a bill'),
|
||||||
|
versioning=True)
|
||||||
|
state_2_9 = State.objects.create(name=ugettext_noop('needs review'),
|
||||||
|
workflow=workflow_2,
|
||||||
|
action_word=ugettext_noop('Needs review'),
|
||||||
|
versioning=True)
|
||||||
|
state_2_10 = State.objects.create(name=ugettext_noop('rejected (not authorized)'),
|
||||||
|
workflow=workflow_2,
|
||||||
|
action_word=ugettext_noop('Reject (not authorized)'),
|
||||||
|
versioning=True)
|
||||||
|
state_2_1.next_states.add(state_2_2, state_2_5, state_2_10)
|
||||||
|
state_2_2.next_states.add(state_2_3, state_2_4, state_2_5, state_2_6, state_2_7, state_2_8, state_2_9)
|
||||||
|
workflow_2.first_state = state_2_1
|
||||||
|
workflow_2.save()
|
||||||
|
@ -2,6 +2,7 @@ from random import choice
|
|||||||
|
|
||||||
from django.contrib.auth.models import Permission, Group
|
from django.contrib.auth.models import Permission, Group
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils.translation import ugettext_noop
|
||||||
|
|
||||||
from .models import User
|
from .models import User
|
||||||
|
|
||||||
@ -49,6 +50,95 @@ def get_registered_group():
|
|||||||
return Group.objects.get(pk=2)
|
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():
|
def create_or_reset_admin_user():
|
||||||
group_staff = Group.objects.get(pk=4)
|
group_staff = Group.objects.get(pk=4)
|
||||||
try:
|
try:
|
||||||
|
@ -13,10 +13,9 @@ class UsersAppConfig(AppConfig):
|
|||||||
# Import all required stuff.
|
# Import all required stuff.
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from openslides.config.signals import config_signal
|
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.projector.api import register_slide_model
|
||||||
from openslides.utils.rest_api import router
|
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
|
from .views import UserViewSet
|
||||||
|
|
||||||
# Load User model.
|
# Load User model.
|
||||||
@ -24,7 +23,6 @@ class UsersAppConfig(AppConfig):
|
|||||||
|
|
||||||
# Connect signals.
|
# Connect signals.
|
||||||
config_signal.connect(setup_users_config, dispatch_uid='setup_users_config')
|
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')
|
post_save.connect(user_post_save, sender=User, dispatch_uid='users_user_post_save')
|
||||||
|
|
||||||
# Register slides.
|
# Register slides.
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
from django import forms
|
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 as _
|
||||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||||
|
|
||||||
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
|
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):
|
def setup_users_config(sender, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -104,100 +99,6 @@ def setup_users_config(sender, **kwargs):
|
|||||||
groups=(group_general, group_pdf))
|
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):
|
def user_post_save(sender, instance, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Receiver function to add a new user to the registered group. It is
|
Receiver function to add a new user to the registered group. It is
|
||||||
|
@ -70,7 +70,7 @@ class OpenSlidesSockJSConnection(SockJSConnection):
|
|||||||
waiter.send(data)
|
waiter.send(data)
|
||||||
|
|
||||||
|
|
||||||
def run_tornado(addr, port):
|
def run_tornado(addr, port, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Starts the tornado webserver as wsgi server for OpenSlides.
|
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
|
# Don't try to read the command line args from openslides
|
||||||
parse_command_line(args=[])
|
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
|
# Setup WSGIContainer
|
||||||
app = WSGIContainer(get_wsgi_application())
|
app = WSGIContainer(get_wsgi_application())
|
||||||
|
|
||||||
@ -101,7 +93,7 @@ def run_tornado(addr, port):
|
|||||||
|
|
||||||
# Start the application
|
# Start the application
|
||||||
debug = settings.DEBUG
|
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 = HTTPServer(tornado_app)
|
||||||
server.listen(port=port, address=addr)
|
server.listen(port=port, address=addr)
|
||||||
IOLoop.instance().start()
|
IOLoop.instance().start()
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import ctypes
|
import ctypes
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
import argparse
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.conf import ENVIRONMENT_VARIABLE
|
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 activate, check_for_language, get_language
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
DEVELOPMENT_VERSION = 'Development Version'
|
||||||
UNIX_VERSION = 'Unix Version'
|
UNIX_VERSION = 'Unix Version'
|
||||||
WINDOWS_VERSION = 'Windows Version'
|
WINDOWS_VERSION = 'Windows Version'
|
||||||
WINDOWS_PORTABLE_VERSION = 'Windows Portable Version'
|
WINDOWS_PORTABLE_VERSION = 'Windows Portable Version'
|
||||||
@ -30,6 +31,15 @@ class DatabaseInSettingsError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownCommand(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionArgumentParser(argparse.ArgumentParser):
|
||||||
|
def error(self, message):
|
||||||
|
raise UnknownCommand(message)
|
||||||
|
|
||||||
|
|
||||||
def detect_openslides_type():
|
def detect_openslides_type():
|
||||||
"""
|
"""
|
||||||
Returns the type of this OpenSlides version.
|
Returns the type of this OpenSlides version.
|
||||||
@ -50,16 +60,19 @@ def detect_openslides_type():
|
|||||||
return 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.
|
Returns the default settings path according to the OpenSlides type.
|
||||||
|
|
||||||
The argument 'openslides_type' has to be one of the three types mentioned in
|
The argument 'openslides_type' has to be one of the three types mentioned in
|
||||||
openslides.utils.main.
|
openslides.utils.main.
|
||||||
"""
|
"""
|
||||||
|
if openslides_type is None:
|
||||||
|
openslides_type = detect_openslides_type()
|
||||||
|
|
||||||
if openslides_type == UNIX_VERSION:
|
if openslides_type == UNIX_VERSION:
|
||||||
parent_directory = os.environ.get(
|
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:
|
elif openslides_type == WINDOWS_VERSION:
|
||||||
parent_directory = get_win32_app_data_path()
|
parent_directory = get_win32_app_data_path()
|
||||||
elif openslides_type == WINDOWS_PORTABLE_VERSION:
|
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')
|
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
|
Sets the environment variable ENVIRONMENT_VARIABLE, that means
|
||||||
'DJANGO_SETTINGS_MODULE', to the given settings.
|
'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_file = os.path.basename(settings_path)
|
||||||
settings_module_name = ".".join(settings_file.split('.')[:-1])
|
settings_module_name = ".".join(settings_file.split('.')[:-1])
|
||||||
if '.' in settings_module_name:
|
if '.' in settings_module_name:
|
||||||
raise ImproperlyConfigured("'.' is not an allowed character in the settings-file")
|
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)
|
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
|
||||||
|
|
||||||
|
# Set the environment variable to the settings module
|
||||||
def ensure_settings(settings, args):
|
os.environ[ENVIRONMENT_VARIABLE] = settings_module_name
|
||||||
"""
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_settings_context(user_data_path=None):
|
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:
|
if openslides_type == UNIX_VERSION:
|
||||||
default_user_data_path = os.environ.get(
|
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:
|
elif openslides_type == WINDOWS_VERSION:
|
||||||
default_user_data_path = get_win32_app_data_path()
|
default_user_data_path = get_win32_app_data_path()
|
||||||
elif openslides_type == WINDOWS_PORTABLE_VERSION:
|
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')
|
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
|
Creates the settings file at the given path using the given values for the
|
||||||
file template.
|
file template.
|
||||||
|
|
||||||
|
Retuns the path to the created settings.
|
||||||
"""
|
"""
|
||||||
|
if settings_path is None:
|
||||||
|
settings_path = get_default_settings_path()
|
||||||
|
|
||||||
if template is None:
|
if template is None:
|
||||||
with open(os.path.join(os.path.dirname(__file__), 'settings.py.tpl')) as template_file:
|
with open(os.path.join(os.path.dirname(__file__), 'settings.py.tpl')) as template_file:
|
||||||
template = template_file.read()
|
template = template_file.read()
|
||||||
@ -205,56 +244,16 @@ def write_settings(settings_path, template=None, **context):
|
|||||||
# from django.core.management.commands.startproject
|
# from django.core.management.commands.startproject
|
||||||
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
||||||
context.setdefault('secret_key', get_random_string(50, chars))
|
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
|
content = template % context
|
||||||
settings_module = os.path.realpath(os.path.dirname(settings_path))
|
settings_module = os.path.realpath(os.path.dirname(settings_path))
|
||||||
if not os.path.exists(settings_module):
|
if not os.path.exists(settings_module):
|
||||||
os.makedirs(settings_module)
|
os.makedirs(settings_module)
|
||||||
with open(settings_path, 'w') as settings_file:
|
with open(settings_path, 'w') as settings_file:
|
||||||
settings_file.write(content)
|
settings_file.write(content)
|
||||||
|
return os.path.realpath(settings_path)
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def start_browser(browser_url):
|
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,
|
Retrieves the database path out of the settings file. Returns None,
|
||||||
if it is not a SQLite3 database.
|
if it is not a SQLite3 database.
|
||||||
|
|
||||||
|
Needed for the backupdb command.
|
||||||
"""
|
"""
|
||||||
from django.conf import settings as django_settings
|
from django.conf import settings as django_settings
|
||||||
from django.db import DEFAULT_DB_ALIAS
|
from django.db import DEFAULT_DB_ALIAS
|
||||||
@ -304,3 +305,12 @@ def translate_customizable_strings(language_code):
|
|||||||
for name in config.get_all_translatable():
|
for name in config.get_all_translatable():
|
||||||
config[name] = _(config[name])
|
config[name] = _(config[name])
|
||||||
activate(current_language)
|
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.
|
# Use 'DEBUG = True' to get more details for server errors.
|
||||||
# SECURITY WARNING: Don't run with debug turned on in production!
|
# SECURITY WARNING: Don't run with debug turned on in production!
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = %(debug)s
|
||||||
TEMPLATE_DEBUG = DEBUG
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,18 +2,12 @@ from django.core.management import call_command
|
|||||||
from django.test import TestCase as _TestCase
|
from django.test import TestCase as _TestCase
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.core.signals import post_database_setup
|
|
||||||
|
|
||||||
|
|
||||||
class TestCase(_TestCase):
|
class TestCase(_TestCase):
|
||||||
"""
|
"""
|
||||||
Overwrites Django's TestCase class to call the post_database_setup
|
Overwrites Django's TestCase class to refreshs the config cache.
|
||||||
signal after the preparation of every test. Also 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):
|
def _post_teardown(self, *args, **kwargs):
|
||||||
return_value = super(TestCase, self)._post_teardown(*args, **kwargs)
|
return_value = super(TestCase, self)._post_teardown(*args, **kwargs)
|
||||||
|
@ -21,14 +21,6 @@ SECRET_KEY = 'secret'
|
|||||||
DEBUG = True
|
DEBUG = True
|
||||||
TEMPLATE_DEBUG = DEBUG
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
|
||||||
|
|
||||||
# OpenSlides plugins
|
|
||||||
# Add plugins to this list.
|
|
||||||
|
|
||||||
INSTALLED_PLUGINS += (
|
|
||||||
'tests.utils',
|
|
||||||
)
|
|
||||||
|
|
||||||
INSTALLED_APPS += INSTALLED_PLUGINS
|
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
|
import sys
|
||||||
from unittest.mock import MagicMock, patch
|
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.config.api import config
|
||||||
from openslides.utils.main import (
|
from openslides.utils import main
|
||||||
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.test import TestCase
|
from openslides.utils.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
class TestFunctions(TestCase):
|
class TestFunctions(TestCase):
|
||||||
def test_get_default_user_data_path(self):
|
@patch('openslides.utils.main.sys')
|
||||||
self.assertIn(os.path.join('.local', 'share'), get_default_user_data_path(UNIX_VERSION))
|
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):
|
@patch('openslides.utils.main.os.path.basename')
|
||||||
self.assertIn(
|
@patch('openslides.utils.main.sys')
|
||||||
os.path.join('.config', 'openslides', 'settings.py'), get_default_settings_path(UNIX_VERSION))
|
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')
|
@patch('openslides.utils.main.detect_openslides_type')
|
||||||
def test_get_default_settings_context_portable(self, detect_mock):
|
def test_get_default_settings_context_portable(self, detect_mock):
|
||||||
detect_mock.return_value = WINDOWS_PORTABLE_VERSION
|
detect_mock.return_value = main.WINDOWS_PORTABLE_VERSION
|
||||||
context = get_default_settings_context()
|
context = main.get_default_settings_context()
|
||||||
self.assertEqual(context['openslides_user_data_path'], 'get_win32_portable_user_data_path()')
|
self.assertEqual(context['openslides_user_data_path'], 'get_win32_portable_user_data_path()')
|
||||||
|
|
||||||
def test_setup_django_settings_module(self):
|
def test_get_default_user_data_path(self):
|
||||||
setup_django_settings_module('test_dir_dhvnghfjdh456fzheg2f/test_path_bngjdhc756dzwncshdfnx.py')
|
self.assertIn(os.path.join('.local', 'share'), main.get_default_user_data_path(main.UNIX_VERSION))
|
||||||
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)
|
|
||||||
|
|
||||||
@patch('openslides.utils.main.threading.Thread')
|
@patch('openslides.utils.main.threading.Thread')
|
||||||
@patch('openslides.utils.main.time')
|
@patch('openslides.utils.main.time')
|
||||||
@ -81,53 +79,18 @@ class TestFunctions(TestCase):
|
|||||||
def test_start_browser(self, mock_webbrowser, mock_time, mock_Thread):
|
def test_start_browser(self, mock_webbrowser, mock_time, mock_Thread):
|
||||||
browser_mock = MagicMock()
|
browser_mock = MagicMock()
|
||||||
mock_webbrowser.get.return_value = browser_mock
|
mock_webbrowser.get.return_value = browser_mock
|
||||||
start_browser('http://localhost:8234')
|
|
||||||
|
main.start_browser('http://localhost:8234')
|
||||||
|
|
||||||
self.assertTrue(mock_Thread.called)
|
self.assertTrue(mock_Thread.called)
|
||||||
inner_function = mock_Thread.call_args[1]['target']
|
inner_function = mock_Thread.call_args[1]['target']
|
||||||
inner_function()
|
inner_function()
|
||||||
browser_mock.open.assert_called_with('http://localhost:8234')
|
browser_mock.open.assert_called_with('http://localhost:8234')
|
||||||
|
|
||||||
def test_get_database_path_from_settings_memory(self):
|
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):
|
def test_translate_customizable_strings(self):
|
||||||
self.assertEqual(config['event_description'], 'Presentation and assembly system')
|
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')
|
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.contrib.auth.models import AnonymousUser
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.urlresolvers import clear_url_caches
|
from django.core.urlresolvers import clear_url_caches
|
||||||
from django.db import connection, reset_queries
|
|
||||||
from django.test import RequestFactory
|
from django.test import RequestFactory
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
from django.test.utils import override_settings
|
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 openslides.utils.test import TestCase
|
||||||
|
|
||||||
from . import views as test_views
|
from . import views as test_views
|
||||||
from .models import DummyModel
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='tests.utils.urls')
|
@override_settings(ROOT_URLCONF='tests.utils.urls')
|
||||||
@ -194,15 +192,22 @@ class QuestionViewTest(ViewTestCase):
|
|||||||
self.assertIn('the question', question)
|
self.assertIn('the question', question)
|
||||||
|
|
||||||
|
|
||||||
class DetailViewTest(ViewTestCase):
|
class SingleObjectMixinTest(TestCase):
|
||||||
def test_get_object_cache(self):
|
|
||||||
with self.settings(DEBUG=True):
|
@patch('openslides.utils.views.django_views.detail.SingleObjectMixin.get_object')
|
||||||
DummyModel.objects.create(title='title_ooth8she7yos1Oi8Boh3')
|
def test_get_object_cache(self, mock_super_class_get_object):
|
||||||
reset_queries()
|
"""
|
||||||
client = Client()
|
Test that the method get_object caches his result.
|
||||||
response = client.get('/dummy_detail_view/1/')
|
|
||||||
self.assertContains(response, 'title_ooth8she7yos1Oi8Boh3')
|
Tests that get_object from the django view is only called once, even if
|
||||||
self.assertEqual(len(connection.queries), 3)
|
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):
|
def set_context(sender, request, context, **kwargs):
|
||||||
|
@ -26,7 +26,4 @@ urlpatterns += patterns(
|
|||||||
|
|
||||||
url(r'^permission_mixin3/$',
|
url(r'^permission_mixin3/$',
|
||||||
views.PermissionMixinView.as_view(required_permission='agenda.can_see_agenda')),
|
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 openslides.utils import views
|
||||||
|
|
||||||
from .models import DummyModel
|
|
||||||
|
|
||||||
|
|
||||||
class GetAbsoluteUrl(object):
|
class GetAbsoluteUrl(object):
|
||||||
"""
|
"""
|
||||||
@ -47,15 +45,3 @@ class UrlMixinView(views.UrlMixin, views.View):
|
|||||||
|
|
||||||
class UrlMixinViewWithObject(views.UrlMixin, views.View):
|
class UrlMixinViewWithObject(views.UrlMixin, views.View):
|
||||||
object = GetAbsoluteUrl()
|
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