diff --git a/fabfile.py b/fabfile.py index 5ab162176..08a84a310 100644 --- a/fabfile.py +++ b/fabfile.py @@ -23,7 +23,7 @@ def test(module='tests'): environment variable DJANGO_SETTINGS_MODULE is set to 'tests.settings'. """ django.settings_module('tests.settings') - local('coverage run ./manage.py test %s' % module) + local('coverage run ./manage.py django test %s' % module) def coverage_report_plain(): diff --git a/manage.py b/manage.py index 2618206eb..281159766 100644 --- a/manage.py +++ b/manage.py @@ -1,18 +1,19 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ - Django's execute manager. + Manage script for OpenSlides. - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. :license: GNU GPL, see LICENSE for more details. """ -import os, sys -from django.core.management import execute_from_command_line -from openslides.main import get_user_config_path, setup_django_environment +import sys + + +from openslides.__main__ import main + if __name__ == "__main__": - if 'DJANGO_SETTINGS_MODULE' not in os.environ: - setup_django_environment( - get_user_config_path('openslides', 'settings.py')) - execute_from_command_line(sys.argv) + if len(sys.argv) == 1: + sys.argv.append('--help') + exit(main()) diff --git a/openslides/__main__.py b/openslides/__main__.py new file mode 100644 index 000000000..6296d3688 --- /dev/null +++ b/openslides/__main__.py @@ -0,0 +1,530 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.__main__ + ~~~~~~~~~~~~~~~~~~~ + + Main script for OpenSlides + + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +import argparse +import base64 +import imp +import os +import shutil +import socket +import sys +import time +import threading +import webbrowser + +from django.conf import ENVIRONMENT_VARIABLE +from django.core.management import execute_from_command_line + +from openslides import get_version +from openslides.utils.tornado_webserver import run_tornado +from openslides.utils.main import ( + filesystem2unicode, + detect_openslides_type, + get_win32_app_data_path, + get_win32_portable_path, + UNIX_VERSION, + WINDOWS_VERSION, + WINDOWS_PORTABLE_VERSION) + + +SETTINGS_TEMPLATE = """# -*- coding: utf-8 -*- +# +# Settings file for OpenSlides +# + +%(import_function)s +from openslides.global_settings import * + +# Use 'DEBUG = True' to get more details for server errors. Default for releases: False +DEBUG = False +TEMPLATE_DEBUG = DEBUG + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': %(database_path_value)s, + 'USER': '', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + } +} + +# Set timezone +TIME_ZONE = 'Europe/Berlin' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = %(secret_key)r + +# Add OpenSlides plugins to this list (see example entry in comment) +INSTALLED_PLUGINS = ( +# 'pluginname', +) + +INSTALLED_APPS += INSTALLED_PLUGINS + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = %(media_path_value)s +""" + + +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 not hasattr(args, 'settings') or not args.settings: + openslides_type = detect_openslides_type() + args.settings = get_default_settings_path(openslides_type) + + # Create settings if if still does not exist. + if not os.path.exists(args.settings): + # Setup path for local data (SQLite3 database, media, search index, ...): + # Take it either from command line or get default path + if not hasattr(args, 'localpath') or not args.localpath: + openslides_type = detect_openslides_type() + args.localpath = get_default_local_path(openslides_type) + localpath_values = get_localpath_values(localpath=args.localpath, default=True, openslides_type=openslides_type) + else: + localpath_values = get_localpath_values(localpath=args.localpath, default=False) + create_settings(args.settings, localpath_values) + + # Setup DJANGO_SETTINGS_MODULE environment variable + if 'DJANGO_SETTINGS_MODULE' not in os.environ: + setup_django_settings_module(args.settings) + + # Process the subcommand's callback + return args.callback(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') + + 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 settings and database, ' + 'start the tornado webserver, launch the default web ' + 'browser and open the webinterface.') + + parser = argparse.ArgumentParser(description=description) + + parser.add_argument( + '--version', + action='version', + version=get_version(), + help='Show version number and exit.') + + subparsers = parser.add_subparsers( + dest='subcommand', + title='Available subcommands', + description="Type 'python %s --help' for help on a specific subcommand." % parser.prog, + help='You can choose only one subcommand at once.') + + settings_args, settings_kwargs = ( + ('-s', '--settings'), + dict(help='Path to settings file.')) + localpath_args, localpath_kwargs = ( + ('-l', '--localpath'), + dict(help='Path to the directory for local files like SQLite3 database, ' + 'uploaded media and search index. This is only used, when a new ' + 'settings file is created.')) + address_args, address_kwargs = ( + ('-a', '--address',), + dict(default='0.0.0.0', help='IP address to listen on. Default is %(default)s.')) + port_args, port_kwargs = ( + ('-p', '--port'), + dict(type=int, default=80, help='Port to listen on. Default as admin or root is %(default)d, else 8000.')) + + # 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.') + subcommand_start.add_argument(*settings_args, **settings_kwargs) + subcommand_start.add_argument(*localpath_args, **localpath_kwargs) + subcommand_start.add_argument(*address_args, **address_kwargs) + subcommand_start.add_argument(*port_args, **port_kwargs) + subcommand_start.set_defaults(callback=start) + + # Subcommand runserver + subcommand_runserver = subparsers.add_parser( + 'runserver', + help='Run OpenSlides using tornado webserver.') + subcommand_runserver.add_argument(*settings_args, **settings_kwargs) + subcommand_runserver.add_argument(*localpath_args, **localpath_kwargs) + subcommand_runserver.add_argument(*address_args, **address_kwargs) + subcommand_runserver.add_argument(*port_args, **port_kwargs) + 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.') + subcommand_syncdb.add_argument(*settings_args, **settings_kwargs) + subcommand_syncdb.add_argument(*localpath_args, **localpath_kwargs) + 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.") + subcommand_createsuperuser.add_argument(*settings_args, **settings_kwargs) + subcommand_createsuperuser.add_argument(*localpath_args, **localpath_kwargs) + subcommand_createsuperuser.set_defaults(callback=createsuperuser) + + # Subcommand backupdb + subcommand_backupdb = subparsers.add_parser( + 'backupdb', + help='Store a backup copy of the SQLite3 database.') + subcommand_backupdb.add_argument(*settings_args, **settings_kwargs) + subcommand_backupdb.add_argument(*localpath_args, **localpath_kwargs) + 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.') + subcommand_deletedb.add_argument(*settings_args, **settings_kwargs) + subcommand_deletedb.add_argument(*localpath_args, **localpath_kwargs) + subcommand_deletedb.set_defaults(callback=deletedb) + + # Subcommand django + subcommand_django_command_line_utility = subparsers.add_parser( + 'django', + description="Link to Django's command-line utility. Type 'python %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 get_default_settings_path(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 + openslides.utils.main. + """ + if openslides_type == UNIX_VERSION: + parent_directory = filesystem2unicode(os.environ.get( + 'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))) + elif openslides_type == WINDOWS_VERSION: + parent_directory = get_win32_app_data_path() + elif openslides_type == WINDOWS_PORTABLE_VERSION: + parent_directory = get_win32_portable_path() + else: + raise TypeError('%s is not a valid OpenSlides type.' % openslides_type) + return os.path.join(parent_directory, 'openslides', 'settings.py') + + +def get_default_local_path(openslides_type): + """ + Returns the default local path according to the OpenSlides type. + + The argument 'openslides_type' has to be one of the three types mentioned in + openslides.utils.main. + """ + if openslides_type == UNIX_VERSION: + default_local_path = filesystem2unicode(os.environ.get( + 'XDG_DATA_HOME', os.path.join(os.path.expanduser('~'), '.local', 'share'))) + elif openslides_type == WINDOWS_VERSION: + default_local_path = get_win32_app_data_path() + elif openslides_type == WINDOWS_PORTABLE_VERSION: + default_local_path = get_win32_portable_path() + else: + raise TypeError('%s is not a valid OpenSlides type.' % openslides_type) + return default_local_path + + +def get_localpath_values(localpath, default=False, openslides_type=None): + """ + Returns the local path values for the new settings file. + + The argument 'localpath' is a path to the directory where OpenSlides should + store the local data like SQLite3 database, media and search index. + + The argument 'default' is a simple flag. If it is True and the OpenSlides + type is the Windows portable version, this function returns callable + functions for the settings file, else it returns string paths. + + The argument 'openslides_type' can to be one of the three types mentioned in + openslides.utils.main. + """ + localpath_values = {} + if default and openslides_type == WINDOWS_PORTABLE_VERSION: + localpath_values['import_function'] = 'from openslides.utils.main import get_portable_paths' + localpath_values['database_path_value'] = "get_portable_paths('database')" + localpath_values['media_path_value'] = "get_portable_paths('media')" + else: + localpath_values['import_function'] = '' + # TODO: Decide whether to use only absolute paths here. + localpath_values['database_path_value'] = "'%s'" % os.path.join(localpath, 'openslides', 'database.sqlite') + # TODO: Decide whether to use only absolute paths here. + localpath_values['media_path_value'] = "'%s'" % os.path.join(localpath, 'openslides', 'media', '') + return localpath_values + + +def create_settings(settings_path, local_path_values): + """ + Creates the settings file at the given path using the given values for the + file template. + """ + settings_module = os.path.realpath(os.path.dirname(settings_path)) + if not os.path.exists(settings_module): + os.makedirs(settings_module) + context = {'secret_key': base64.b64encode(os.urandom(30))} + context.update(local_path_values) + settings_content = SETTINGS_TEMPLATE % context + with open(settings_path, 'w') as settings_file: + settings_file.write(settings_content) + print('Settings file at %s successfully created.' % settings_path) + + +def setup_django_settings_module(settings_path): + """ + Sets the environment variable DJANGO_SETTINGS_MODULE to the given settings. + """ + settings_file = os.path.basename(settings_path) + settings_module_name = "".join(settings_file.split('.')[:-1]) + if '.' in settings_module_name: + print("'.' is not an allowed character in the settings-file") + sys.exit(1) + settings_module_dir = os.path.dirname(settings_path) + sys.path.append(settings_module_dir) + os.environ[ENVIRONMENT_VARIABLE] = '%s' % settings_module_name + + +def start(args): + """ + Starts OpenSlides: Runs syncdb and runs runserver (tornado webserver) with + the flag 'start_browser'. + """ + syncdb(args) + args.start_browser = True + args.no_reload = False + runserver(args) + + +def runserver(args): + """ + Runs tornado webserver. Runs the function start_browser if the respective + argument is given. + """ + 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) + run_tornado(args.address, port, not args.no_reload) + + +def get_port(address, port): + """ + Returns the port for the server. If port 80 is given, checks if it is + available. If not returns port 8000. + + The argument 'address' should be an IP address. The argument 'port' should + be an integer. + """ + if port == 80: + # test if we can use port 80 + s = socket.socket() + try: + s.bind((address, port)) + s.listen(-1) + except socket.error: + port = 8000 + finally: + s.close() + 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): + """ + Launches the default web browser at the given url and opens the + webinterface. + """ + browser = webbrowser.get() + + def function(): + time.sleep(1) + browser.open(browser_url) + + thread = threading.Thread(target=function) + thread.start() + + +def syncdb(args): + """ + Run syncdb to create or update the database. + """ + # TODO: Check use of filesystem2unicode here. + path = filesystem2unicode(os.path.dirname(get_database_path_from_settings(args.settings))) + if not os.path.exists(path): + os.makedirs(path) + execute_from_command_line(["", "syncdb", "--noinput"]) + + +def get_database_path_from_settings(settings_path): + """ + Retrieves the database path out of the given settings file. Returns None, + if it is not a SQLite3 database. + """ + from django.conf import settings as django_settings + from django.db import DEFAULT_DB_ALIAS + + db_settings = django_settings.DATABASES + default = db_settings.get(DEFAULT_DB_ALIAS) + if not default: + raise Exception("Default databases is not configured") + database_path = default.get('NAME') + if not database_path: + raise Exception('No path specified for default database.') + if default.get('ENGINE') != 'django.db.backends.sqlite3': + database_path = None + return database_path + + +def createsuperuser(args): + """ + Creates or resets the admin user. Returns 0 to show success. + """ + # 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(args): + """ + Stores a backup copy of the SQlite3 database. + """ + 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 as e: + raise Exception("Database backup failed.") + # and release the lock again + transaction.commit() + + database_path = get_database_path_from_settings(args.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(args): + """ + Deletes the sqlite3 database. Returns 0 on success, else 1. + """ + database_path = get_database_path_from_settings(args.settings) + if database_path and os.path.exists(database_path): + os.remove(database_path) + print('SQLite3 database file %s successfully deleted.' % database_path) + return_value = 0 + else: + print('SQLite3 database file %s does not exist.' % database_path) + return_value = 1 + return return_value + + +def django_command_line_utility(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 for use via Django's command line utility." % command) + return_value = 1 + else: + execute_from_command_line(args.django_args) + return_value = 0 + return return_value + + +if __name__ == "__main__": + exit(main()) diff --git a/openslides/core/management/commands/backupdb.py b/openslides/core/management/commands/backupdb.py deleted file mode 100644 index f853184e5..000000000 --- a/openslides/core/management/commands/backupdb.py +++ /dev/null @@ -1,53 +0,0 @@ -import shutil -from optparse import make_option - -import django.conf -import django.db -import django.db.transaction -from django.core.management.base import CommandError, NoArgsCommand - - -class Command(NoArgsCommand): - help = "Backup the openslides database" - option_list = NoArgsCommand.option_list + ( - make_option( - "--destination", action="store", - help="path to the backup database (will be overwritten)"), - ) - - def handle_noargs(self, *args, **kw): - db_settings = django.conf.settings.DATABASES - default = db_settings.get(django.db.DEFAULT_DB_ALIAS) - if not default: - raise CommandError("Default databases is not configured") - - if default.get("ENGINE") != "django.db.backends.sqlite3": - raise CommandError( - "Only sqlite3 databases can currently be backuped") - - src_path = default.get("NAME") - if not src_path: - raise CommandError("No path specified for default database") - - dest_path = kw.get("destination") - if not dest_path: - raise CommandError("--destination must be specified") - - self.do_backup(src_path, dest_path) - - @django.db.transaction.commit_manually - def do_backup(self, 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 = django.db.connection.cursor() - cursor.execute("SELECT count(*) from sqlite_master") - - # now copy the file - try: - shutil.copy(src_path, dest_path) - except IOError as e: - raise CommandError("{0}\nDatabase backup failed!".format(e)) - - # and release the lock again - django.db.transaction.commit() diff --git a/openslides/core/management/commands/runserver.py b/openslides/core/management/commands/runserver.py deleted file mode 100644 index 83af12280..000000000 --- a/openslides/core/management/commands/runserver.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - openslides.utils.management.commands.runserver - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Overrides the Django runserver command to start the tornado webserver. - - :copyright: (c) 2011-2013 by the OpenSlides team, see AUTHORS. - :license: GNU GPL, see LICENSE for more details. - -""" - -from django.core.management.base import BaseCommand - -from openslides.main import main - - -class Command(BaseCommand): - """ - Start the application using the tornado webserver - """ - - help = 'Start the application using the tornado webserver' - - def handle(self, *args, **options): - main(manage_runserver=True) diff --git a/openslides/global_settings.py b/openslides/global_settings.py index 373643730..f3e3b806b 100644 --- a/openslides/global_settings.py +++ b/openslides/global_settings.py @@ -11,7 +11,7 @@ """ import os -from openslides.main import fs2unicode +from openslides.utils.main import filesystem2unicode SITE_ROOT = os.path.realpath(os.path.dirname(__file__)) @@ -44,7 +44,7 @@ USE_I18N = True USE_L10N = True LOCALE_PATHS = ( - fs2unicode(os.path.join(SITE_ROOT, 'locale')), + filesystem2unicode(os.path.join(SITE_ROOT, 'locale')), ) # URL that handles the media served from MEDIA_ROOT. Make sure to use a @@ -54,7 +54,7 @@ MEDIA_URL = '/media/' # Absolute path to the directory that holds static media from ``collectstatic`` # Example: "/home/media/static.lawrence.com/" -STATIC_ROOT = fs2unicode(os.path.join(SITE_ROOT, '../collected-site-static')) +STATIC_ROOT = filesystem2unicode(os.path.join(SITE_ROOT, '../collected-site-static')) # URL that handles the media served from STATIC_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). @@ -64,12 +64,14 @@ STATIC_URL = '/static/' # Additional directories containing static files (not application specific) # Examples: "/home/media/lawrence.com/extra-static/" STATICFILES_DIRS = ( - fs2unicode(os.path.join(SITE_ROOT, 'static')), + filesystem2unicode(os.path.join(SITE_ROOT, 'static')), ) +#XXX: Note this setting (as well as our workaround finder) +# can be removed again once django-bug-#18404 has been resolved STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'openslides.utils.staticfiles.AppDirectoriesFinder', ) MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' @@ -97,7 +99,7 @@ TEMPLATE_DIRS = ( # "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. - fs2unicode(os.path.join(SITE_ROOT, 'templates')), + filesystem2unicode(os.path.join(SITE_ROOT, 'templates')), ) INSTALLED_APPS = ( diff --git a/openslides/main.py b/openslides/main.py deleted file mode 100644 index 9cbabc18f..000000000 --- a/openslides/main.py +++ /dev/null @@ -1,423 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - openslides.main - ~~~~~~~~~~~~~~~ - - Main script to start and set up OpenSlides. - - :copyright: 2011–2013 by OpenSlides team, see AUTHORS. - :license: GNU GPL, see LICENSE for more details. -""" - -import base64 -import ctypes -import optparse -import os -import socket -import sys -import tempfile -import threading -import time -import webbrowser - -from django.conf import ENVIRONMENT_VARIABLE -from django.core.management import execute_from_command_line - -from openslides import get_version -from openslides.utils.tornado_webserver import run_tornado - - -CONFIG_TEMPLATE = """#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import openslides.main -from openslides.global_settings import * - - -# Use 'DEBUG = True' to get more details for server errors -# (Default for releases: 'False') -DEBUG = False -TEMPLATE_DEBUG = DEBUG - -DBPATH = %(dbpath)s - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': DBPATH, - 'USER': '', - 'PASSWORD': '', - 'HOST': '', - 'PORT': '', - } -} - -# Set timezone -TIME_ZONE = 'Europe/Berlin' - -# Make this unique, and don't share it with anybody. -SECRET_KEY = %(default_key)r - -# Add OpenSlides plugins to this list (see example entry in comment) -INSTALLED_PLUGINS = ( -# 'pluginname', -) - -INSTALLED_APPS += INSTALLED_PLUGINS - -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = %(media_root_path)s - -# Path to Whoosh search index -HAYSTACK_CONNECTIONS['default']['PATH'] = %(whoosh_index_path)s -""" - -KEY_LENGTH = 30 - -# sentinel used to signal that the database ought to be stored -# relative to the portable's directory -_portable_db_path = object() - - -def process_options(argv=None, manage_runserver=False): - if argv is None: - argv = sys.argv[1:] - - parser = optparse.OptionParser( - description="Run openslides using the tornado webserver") - parser.add_option( - "-a", "--address", - help="IP Address to listen on. Default: 0.0.0.0") - parser.add_option( - "-p", "--port", type="int", - help="Port to listen on. Default: 8000 (start as admin/root: 80)") - parser.add_option( - "--syncdb", action="store_true", - help="Update/create database before starting the server.") - parser.add_option( - "--backupdb", action="store", metavar="BACKUP_PATH", - help="Make a backup copy of the database to BACKUP_PATH.") - parser.add_option( - "--reset-admin", action="store_true", - help="Make sure the user 'admin' exists and uses 'admin' as password.") - parser.add_option( - "-s", "--settings", help="Set the path to the settings file.") - parser.add_option( - "--no-browser", - action="store_false", dest="start_browser", default=True, - help="Do not automatically start the web browser.") - parser.add_option( - "--no-reload", action="store_true", - help="Do not reload the web server.") - parser.add_option( - "--no-run", action="store_true", - help="Do not start the web server.") - parser.add_option( - "--version", action="store_true", - help="Show version and exit.") - - opts, args = parser.parse_args(argv) - - # Do not parse any argv if the script is started via manage.py runserver. - # This simulates the django runserver command - if manage_runserver: - opts.start_browser = False - opts.no_reload = False - return opts - - if opts.version: - print get_version() - exit(0) - - if args: - sys.stderr.write("This command does not take arguments!\n\n") - parser.print_help() - sys.exit(1) - - return opts - - -def main(argv=None, manage_runserver=False): - opts = process_options(argv, manage_runserver) - _main(opts) - - -def win32_portable_main(argv=None): - """special entry point for the win32 portable version""" - - opts = process_options(argv) - - database_path = None - - if opts.settings is None: - portable_dir = get_portable_path() - try: - fd, test_file = tempfile.mkstemp(dir=portable_dir) - except OSError: - portable_dir_writeable = False - else: - portable_dir_writeable = True - os.close(fd) - os.unlink(test_file) - - if portable_dir_writeable: - opts.settings = os.path.join( - portable_dir, "openslides", "settings.py") - database_path = _portable_db_path - - _main(opts, database_path=database_path) - - -def _main(opts, database_path=None): - # Find the path to the settings - settings_path = opts.settings - if settings_path is None: - settings_path = get_user_config_path('openslides', 'settings.py') - - # Create settings if necessary - if not os.path.exists(settings_path): - create_settings(settings_path, database_path) - - # Set the django environment to the settings - setup_django_environment(settings_path) - - # Find url to openslides - addr, port = detect_listen_opts(opts.address, opts.port) - - # Create Database if necessary - if not database_exists() or opts.syncdb: - run_syncdb() - - # Reset Admin - elif opts.reset_admin: - reset_admin_user() - - if opts.backupdb: - backup_database(opts.backupdb) - - if opts.no_run: - return - - # Start OpenSlides - reload = True - if opts.no_reload: - reload = False - - if opts.start_browser: - if opts.address: - prefix = opts.address - else: - prefix = 'localhost' - if port == 80: - suffix = "" - else: - suffix = ":%d" % port - start_browser("http://%s%s" % (prefix, suffix)) - - # Start the server - run_tornado(addr, port, reload) - - -def create_settings(settings_path, database_path=None): - settings_module = os.path.dirname(settings_path) - - if database_path is _portable_db_path: - database_path = get_portable_db_path() - dbpath_value = 'openslides.main.get_portable_db_path()' - media_root_path_value = 'openslides.main.get_portable_media_root_path()' - whoosh_index_path_value = 'openslides.main.get_portable_whoosh_index_path()' - else: - if database_path is None: - database_path = get_user_data_path('openslides', 'database.sqlite') - dbpath_value = repr(fs2unicode(database_path)) - media_root_path_value = repr(fs2unicode(get_user_data_path('openslides', 'media', ''))) - whoosh_index_path_value = repr(fs2unicode(get_user_data_path('openslides', 'whoosh_index', ''))) - - settings_content = CONFIG_TEMPLATE % dict( - default_key=base64.b64encode(os.urandom(KEY_LENGTH)), - dbpath=dbpath_value, - media_root_path=media_root_path_value, - whoosh_index_path=whoosh_index_path_value) - - if not os.path.exists(settings_module): - os.makedirs(settings_module) - - if not os.path.exists(os.path.dirname(database_path)): - os.makedirs(os.path.dirname(database_path)) - - with open(settings_path, 'w') as file: - file.write(settings_content) - - -def setup_django_environment(settings_path): - settings_file = os.path.basename(settings_path) - settings_module_name = "".join(settings_file.split('.')[:-1]) - if '.' in settings_module_name: - print "'.' is not an allowed character in the settings-file" - sys.exit(1) - settings_module_dir = os.path.dirname(settings_path) - sys.path.append(settings_module_dir) - os.environ[ENVIRONMENT_VARIABLE] = '%s' % settings_module_name - - -def detect_listen_opts(address=None, port=None): - if address is None: - address = "0.0.0.0" - - if port is None: - # test if we can use port 80 - s = socket.socket() - port = 80 - try: - s.bind((address, port)) - s.listen(-1) - except socket.error: - port = 8000 - finally: - s.close() - - return address, port - - -def database_exists(): - """Detect if database exists""" - # can't be imported in global scope as they already require - # the settings module during import - from django.db import DatabaseError - from django.core.exceptions import ImproperlyConfigured - from openslides.participant.models import User - - try: - # TODO: Use another model, the User could be deactivated - User.objects.count() - except DatabaseError: - return False - except ImproperlyConfigured: - print "Your settings file seems broken" - sys.exit(0) - else: - return True - - -def run_syncdb(): - # now initialize the database - argv = ["", "syncdb", "--noinput"] - execute_from_command_line(argv) - - -def reset_admin_user(): - # 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 - create_or_reset_admin_user() - - -def backup_database(dest_path): - argv = ["", "backupdb", "--destination={0}".format(dest_path)] - execute_from_command_line(argv) - - -def start_browser(url): - browser = webbrowser.get() - - def f(): - time.sleep(1) - browser.open(url) - - t = threading.Thread(target=f) - t.start() - - -def fs2unicode(s): - if isinstance(s, unicode): - return s - fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() - return s.decode(fs_encoding) - - -def get_user_config_path(*args): - if sys.platform == "win32": - return win32_get_app_data_path(*args) - - config_home = os.environ.get( - 'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config')) - - return os.path.join(fs2unicode(config_home), *args) - - -def get_user_data_path(*args): - if sys.platform == "win32": - return win32_get_app_data_path(*args) - - data_home = os.environ.get( - 'XDG_DATA_HOME', os.path.join( - os.path.expanduser('~'), '.local', 'share')) - - return os.path.join(fs2unicode(data_home), *args) - - -def is_portable(): - """Return True if openslides is run as portable version""" - - # NOTE: sys.executable is the path of the *interpreter* - # the portable version embeds python so it *is* the interpreter. - # The wrappers generated by pip and co. will spawn - # the usual python(w).exe, so there is no danger of mistaking - # them for the portable even though they may also be called - # openslides.exe - exename = os.path.basename(sys.executable).lower() - return exename == "openslides.exe" - - -def get_portable_path(*args): - # NOTE: sys.executable will be the path to openslides.exe - # since it is essentially a small wrapper that embeds the - # python interpreter - - if not is_portable(): - raise Exception( - "Cannot determine portable path when " - "not running as portable") - - portable_dir = fs2unicode(os.path.dirname(os.path.abspath(sys.executable))) - return os.path.join(portable_dir, *args) - - -def get_portable_db_path(): - return get_portable_path('openslides', 'database.sqlite') - - -def get_portable_media_root_path(): - return get_portable_path('openslides', 'media', '') - - -def get_portable_whoosh_index_path(): - return get_portable_path('openslides', 'whoosh_index', '') - - -def win32_get_app_data_path(*args): - shell32 = ctypes.WinDLL("shell32.dll") - SHGetFolderPath = shell32.SHGetFolderPathW - SHGetFolderPath.argtypes = ( - ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32, - ctypes.c_wchar_p) - SHGetFolderPath.restype = ctypes.c_uint32 - - CSIDL_LOCAL_APPDATA = 0x001c - MAX_PATH = 260 - - buf = ctypes.create_unicode_buffer(MAX_PATH) - res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf) - if res != 0: - raise Exception("Could not deterime APPDATA path") - - return os.path.join(buf.value, *args) - - -if __name__ == "__main__": - if is_portable(): - win32_portable_main() - else: - main() diff --git a/openslides/participant/api.py b/openslides/participant/api.py index be9eb6771..e757bd119 100644 --- a/openslides/participant/api.py +++ b/openslides/participant/api.py @@ -130,8 +130,11 @@ def create_or_reset_admin_user(): admin = User() admin.username = 'admin' admin.last_name = 'Administrator' - + created = True + else: + created = False admin.default_password = 'admin' admin.set_password(admin.default_password) admin.save() admin.groups.add(group_staff) + return created diff --git a/openslides/utils/main.py b/openslides/utils/main.py new file mode 100644 index 000000000..a17ff4e6f --- /dev/null +++ b/openslides/utils/main.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + openslides.utils.main + ~~~~~~~~~~~~~~~~~~~~~ + + Some functions for OpenSlides. + + :copyright: 2011–2013 by OpenSlides team, see AUTHORS. + :license: GNU GPL, see LICENSE for more details. +""" + +import os +import sys +import ctypes + + +UNIX_VERSION = 'Unix Version' +WINDOWS_VERSION = 'Windows Version' +WINDOWS_PORTABLE_VERSION = 'Windows Portable Version' + + +def filesystem2unicode(path): + """ + Transforms a path string to unicode according to the filesystem's encoding. + """ + # TODO: Delete this function after switch to Python 3. + if not isinstance(path, unicode): + filesystem_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() + path = path.decode(filesystem_encoding) + return path + + +def detect_openslides_type(): + """ + Returns the type of this version. + """ + if sys.platform == 'win32': + if os.path.basename(sys.executable).lower() == 'openslides.exe': + # Note: sys.executable is the path of the *interpreter* + # the portable version embeds python so it *is* the interpreter. + # The wrappers generated by pip and co. will spawn the usual + # python(w).exe, so there is no danger of mistaking them + # for the portable even though they may also be called + # openslides.exe + openslides_type = WINDOWS_PORTABLE_VERSION + else: + openslides_type = WINDOWS_VERSION + else: + openslides_type = UNIX_VERSION + return openslides_type + + +def is_portable(): + """ + Helper function just for the GUI. + """ + # TODO: Remove this function. + return detect_openslides_type() == WINDOWS_PORTABLE_VERSION + + +def get_win32_app_data_path(): + """ + Returns the path to Windows' AppData directory. + """ + shell32 = ctypes.WinDLL("shell32.dll") + SHGetFolderPath = shell32.SHGetFolderPathW + SHGetFolderPath.argtypes = ( + ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32, + ctypes.c_wchar_p) + SHGetFolderPath.restype = ctypes.c_uint32 + + CSIDL_LOCAL_APPDATA = 0x001c + MAX_PATH = 260 + + buf = ctypes.create_unicode_buffer(MAX_PATH) + res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf) + if res != 0: + raise Exception("Could not deterime APPDATA path") + + return buf.value + + +def get_win32_portable_path(): + """ + Returns the path to the Windows portable version. + """ + # NOTE: sys.executable will be the path to openslides.exe + # since it is essentially a small wrapper that embeds the + # python interpreter + portable_path = filesystem2unicode(os.path.dirname(os.path.abspath(sys.executable))) + try: + fd, test_file = tempfile.mkstemp(dir=portable_path) + except OSError: + raise Exception('Portable directory is not writeable. Please choose another directory for settings and local files.') + finally: + os.close(fd) + os.unlink(test_file) + return portable_path + + +def get_portable_paths(name): + """ + Returns the paths for the Windows portable version on runtime for the + SQLite3 database and the media directory. The argument 'name' can be + 'database' or 'media'. + """ + if name == 'database': + path = os.path.join(get_win32_portable_path(), 'openslides', 'database.sqlite') + elif name == 'media': + path = os.path.join(get_win32_portable_path(), 'openslides', 'media', '') + else: + raise TypeError('Unknow type %s' % name) + return path diff --git a/setup.py b/setup.py index 4b1535e6a..c9d6670ba 100644 --- a/setup.py +++ b/setup.py @@ -45,4 +45,4 @@ setup( packages=find_packages(exclude=['tests', 'tests.*']), include_package_data=True, install_requires=install_requires, - entry_points={'console_scripts': ['openslides = openslides.main:main']}) + entry_points={'console_scripts': ['openslides = openslides.__main__:main']}) diff --git a/start.py b/start.py deleted file mode 100755 index 693a543e4..000000000 --- a/start.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - Start script for OpenSlides. - - :copyright: 2011, 2012 by OpenSlides team, see AUTHORS. - :license: GNU GPL, see LICENSE for more details. -""" - -from openslides.main import main - -if __name__ == "__main__": - main()