OpenSlides/openslides/__main__.py

368 lines
13 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
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,
filesystem2unicode,
get_browser_url,
get_database_path_from_settings,
get_default_settings_path,
get_default_user_data_path,
get_port,
setup_django_settings_module,
start_browser,
write_settings)
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)
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)
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)
def parse_args():
"""
Parses all command line arguments. The subcommand 'django' links to
Django's command-line utility.
"""
if len(sys.argv) == 1:
sys.argv.append('start')
# Init parser
description = 'Start script for OpenSlides.'
if 'manage.py' not in sys.argv[0]:
description += (' If it is called without any argument, this will be '
'treated as if it is called with the "start" subcommand. '
'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)
# Add version argument
parser.add_argument(
'--version',
action='version',
version=get_version(),
help='Show version number and exit.')
# Init subparsers
subparsers = parser.add_subparsers(
dest='subcommand',
title='Available subcommands',
description="Type '%s <subcommand> --help' for help on a "
"specific subcommand." % parser.prog,
help='You can choose only one subcommand at once.')
# Subcommand start
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', 'address', 'port'))
subcommand_start.add_argument(
'--no-browser',
action='store_true',
help='Do not launch the default web browser.')
subcommand_start.set_defaults(callback=start)
# Subcommand runserver
subcommand_runserver = subparsers.add_parser(
'runserver',
help='Run OpenSlides using tornado webserver.')
add_general_arguments(subcommand_runserver, ('settings', 'user_data_path', 'address', 'port'))
subcommand_runserver.add_argument(
'--start-browser',
action='store_true',
help='Launch the default web browser and open the webinterface.')
subcommand_runserver.add_argument(
'--no-reload',
action='store_true',
help='Do not reload the webserver if source code changes.')
subcommand_runserver.set_defaults(callback=runserver)
# Subcommand syncdb
subcommand_syncdb = subparsers.add_parser(
'syncdb',
help='Create or update database tables.')
add_general_arguments(subcommand_syncdb, ('settings', 'user_data_path'))
subcommand_syncdb.set_defaults(callback=syncdb)
# Subcommand createsuperuser
subcommand_createsuperuser = subparsers.add_parser(
'createsuperuser',
help="Make sure the user 'admin' exists and uses 'admin' as password.")
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):
"""
Adds the named arguments to the subcommand.
"""
general_arguments = {}
openslides_type = detect_openslides_type()
general_arguments['settings'] = (
('-s', '--settings'),
dict(help="Path to settings file. If this isn't provided, the "
"%s environment variable will be used. "
"If this 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. This 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 is at the moment %s' % get_default_user_data_path(openslides_type)))
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
args.no_reload = False
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.
from openslides.utils.tornado_webserver import run_tornado
run_tornado(args.address, port, not args.no_reload)
def syncdb(settings, args):
"""
Run syncdb to create or update the database.
"""
ensure_settings(settings, args)
# TODO: Check use of filesystem2unicode here.
db_file = get_database_path_from_settings()
if db_file is not None:
db_dir = filesystem2unicode(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"])
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.participant.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.commit_manually
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.")
# and release the lock again
transaction.commit()
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__":
exit(main())