Merge pull request #903 from normanjaeckel/CleanUpMain

Clean up main and move the code to __main__.py
This commit is contained in:
Oskar Hahn 2013-10-19 22:15:21 -07:00
commit beab5ca93e
13 changed files with 809 additions and 552 deletions

View File

@ -15,7 +15,15 @@ import threading
import wx import wx
import openslides import openslides
import openslides.main
from openslides.utils.main import (
detect_openslides_type,
filesystem2unicode,
get_default_user_data_path,
get_port,
get_win32_portable_path,
)
# NOTE: djangos translation module can't be used here since it requires # NOTE: djangos translation module can't be used here since it requires
# a defined settings module # a defined settings module
@ -25,7 +33,7 @@ ungettext = lambda msg1, msg2, n: _translations.ungettext(msg1, msg2, n)
def get_data_path(*args): def get_data_path(*args):
path = openslides.main.fs2unicode(__file__) path = filesystem2unicode(__file__)
return os.path.join(os.path.dirname(path), "data", *args) return os.path.join(os.path.dirname(path), "data", *args)
@ -81,7 +89,7 @@ class RunCommandControl(wx.Panel):
if self.is_alive(): if self.is_alive():
raise ValueError("already running a command") raise ValueError("already running a command")
cmd = [sys.executable, "-u", "-m", "openslides.main"] cmd = [sys.executable, "-u", "-m", "openslides"]
cmd.extend(args) cmd.extend(args)
# XXX: subprocess on windows only handles byte strings # XXX: subprocess on windows only handles byte strings
@ -336,12 +344,14 @@ class MainWindow(wx.Frame):
self.SetIcons(icons) self.SetIcons(icons)
self.server_running = False self.server_running = False
if openslides.main.is_portable():
self.gui_settings_path = openslides.main.get_portable_path( # Set path for gui settings to default user data according to the
"openslides", "gui_settings.json") # OpenSlides type. This does not depend on any argument the user might
else: # type in.
self.gui_settings_path = openslides.main.get_user_config_path( openslides_type = detect_openslides_type()
"openslides", "gui_settings.json") default_user_data_path = get_default_user_data_path(openslides_type)
self.gui_settings_path = os.path.join(
default_user_data_path, 'openslides', 'gui_settings.json')
self.backupdb_enabled = False self.backupdb_enabled = False
self.backupdb_destination = "" self.backupdb_destination = ""
@ -417,9 +427,8 @@ class MainWindow(wx.Frame):
self.bt_server.Bind(wx.EVT_BUTTON, self.on_start_server_clicked) self.bt_server.Bind(wx.EVT_BUTTON, self.on_start_server_clicked)
server_box.Add(self.bt_server, flag=wx.EXPAND) server_box.Add(self.bt_server, flag=wx.EXPAND)
host, port = openslides.main.detect_listen_opts() self.host = "0.0.0.0"
self.host = host self.port = unicode(get_port(self.host, 80))
self.port = unicode(port)
# "action" buttons # "action" buttons
action_vbox = wx.BoxSizer(wx.VERTICAL) action_vbox = wx.BoxSizer(wx.VERTICAL)
@ -568,9 +577,8 @@ class MainWindow(wx.Frame):
def do_backup(self): def do_backup(self):
cmd = [ cmd = [
sys.executable, "-u", "-m", "openslides.main", sys.executable, "-u", "-m", "openslides", "backupdb",
"--no-run", self.backupdb_destination,
"--backupdb={0}".format(self.backupdb_destination),
] ]
p = subprocess.Popen( p = subprocess.Popen(
cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
@ -593,11 +601,11 @@ class MainWindow(wx.Frame):
def on_syncdb_clicked(self, evt): def on_syncdb_clicked(self, evt):
self.cmd_run_ctrl.append_message(_("Syncing database...")) self.cmd_run_ctrl.append_message(_("Syncing database..."))
self.cmd_run_ctrl.run_command("--no-run", "--syncdb") self.cmd_run_ctrl.run_command("syncdb")
def on_reset_admin_clicked(self, evt): def on_reset_admin_clicked(self, evt):
self.cmd_run_ctrl.append_message(_("Resetting admin user...")) self.cmd_run_ctrl.append_message(_("Resetting admin user..."))
self.cmd_run_ctrl.run_command("--no-run", "--reset-admin") self.cmd_run_ctrl.run_command("createsuperuser")
def on_about_clicked(self, evt): def on_about_clicked(self, evt):
info = wx.AboutDialogInfo() info = wx.AboutDialogInfo()
@ -624,11 +632,12 @@ class MainWindow(wx.Frame):
args = ["--port", self._port] args = ["--port", self._port]
else: else:
args = ["--address", self._host, "--port", self._port] args = ["--address", self._host, "--port", self._port]
if not self.cb_start_browser.GetValue(): if not self.cb_start_browser.GetValue():
args.append("--no-browser") args.append("--no-browser")
self.server_running = True self.server_running = True
self.cmd_run_ctrl.run_command(*args) self.cmd_run_ctrl.run_command("start", *args)
# initiate backup_timer if backup is enabled # initiate backup_timer if backup is enabled
self.apply_backup_settings() self.apply_backup_settings()
@ -702,7 +711,7 @@ def main():
lang = locale.getdefaultlocale()[0] lang = locale.getdefaultlocale()[0]
if lang: if lang:
global _translations global _translations
localedir = openslides.main.fs2unicode(openslides.__file__) localedir = filesystem2unicode(openslides.__file__)
localedir = os.path.dirname(localedir) localedir = os.path.dirname(localedir)
localedir = os.path.join(localedir, "locale") localedir = os.path.join(localedir, "locale")
_translations = gettext.translation( _translations = gettext.translation(

2
fabfile.py vendored
View File

@ -23,7 +23,7 @@ def test(module='tests'):
environment variable DJANGO_SETTINGS_MODULE is set to 'tests.settings'. environment variable DJANGO_SETTINGS_MODULE is set to 'tests.settings'.
""" """
django.settings_module('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(): def coverage_report_plain():

View File

@ -1,18 +1,18 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Django's execute manager. Manage script for OpenSlides.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
import os, sys import sys
from django.core.management import execute_from_command_line
from openslides.main import get_user_config_path, setup_django_environment from openslides.__main__ import main
if __name__ == "__main__": if __name__ == "__main__":
if 'DJANGO_SETTINGS_MODULE' not in os.environ: if len(sys.argv) == 1:
setup_django_environment( sys.argv.append('--help')
get_user_config_path('openslides', 'settings.py')) exit(main())
execute_from_command_line(sys.argv)

537
openslides/__main__.py Normal file
View File

@ -0,0 +1,537 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.__main__
~~~~~~~~~~~~~~~~~~~
Main script for OpenSlides
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
import argparse
import base64
import os
import shutil
import sys
import time
import threading
import webbrowser
from django.conf import ENVIRONMENT_VARIABLE
from django.core.exceptions import ImproperlyConfigured
from django.core.management import execute_from_command_line
from openslides import get_version
from openslides.utils.main import (
filesystem2unicode,
detect_openslides_type,
get_default_user_data_path,
get_port,
get_win32_app_data_path,
get_win32_portable_path,
UNIX_VERSION,
WINDOWS_VERSION,
WINDOWS_PORTABLE_VERSION)
from openslides.utils.tornado_webserver import run_tornado
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
# Path to Whoosh search index
HAYSTACK_CONNECTIONS['default']['PATH'] = %(whoosh_index_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 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
# Create settings if if still does not exist.
if settings and not os.path.exists(settings):
# Setup path for user specific data (SQLite3 database, media, search index, ...):
# Take it either from command line or get default path
if hasattr(args, 'user_data_path') and args.user_data_path:
user_data_path_values = get_user_data_path_values(
user_data_path=args.user_data_path,
default=False)
else:
openslides_type = detect_openslides_type()
args.user_data_path = get_default_user_data_path(openslides_type)
user_data_path_values = get_user_data_path_values(
user_data_path=args.user_data_path,
default=True,
openslides_type=openslides_type)
create_settings(settings, user_data_path_values)
# 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')
# 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 'python %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 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 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 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_user_data_path_values(user_data_path, default=False, openslides_type=None):
"""
Returns a dictionary of the user specific data path values for the new
settings file.
The argument 'user_data_path' is a path to the directory where OpenSlides
should store the user specific 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, the returned dictionary contains
strings of callable functions for the settings file, else it contains
string paths.
The argument 'openslides_type' can to be one of the three types mentioned in
openslides.utils.main.
"""
user_data_path_values = {}
if default and openslides_type == WINDOWS_PORTABLE_VERSION:
user_data_path_values['import_function'] = 'from openslides.utils.main import get_portable_paths'
user_data_path_values['database_path_value'] = "get_portable_paths('database')"
user_data_path_values['media_path_value'] = "get_portable_paths('media')"
user_data_path_values['whoosh_index_path_value'] = "get_portable_paths('whoosh_index')"
else:
user_data_path_values['import_function'] = ''
# TODO: Decide whether to use only absolute paths here.
user_data_path_values['database_path_value'] = "'%s'" % os.path.join(
user_data_path, 'openslides', 'database.sqlite')
# TODO: Decide whether to use only absolute paths here.
user_data_path_values['media_path_value'] = "'%s'" % os.path.join(
user_data_path, 'openslides', 'media', '')
# TODO: Decide whether to use only absolute paths here.
user_data_path_values['whoosh_index_path_value'] = "'%s'" % os.path.join(
user_data_path, 'openslides', 'whoosh_index', '')
return user_data_path_values
def create_settings(settings_path, user_data_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(user_data_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 ENVIRONMENT_VARIABLE, that means
'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:
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?
sys.path.insert(0, settings_module_dir)
os.environ[ENVIRONMENT_VARIABLE] = '%s' % settings_module_name
def start(args):
"""
Starts OpenSlides: Runs syncdb and runs runserver (tornado webserver).
"""
syncdb(args)
args.start_browser = not args.no_browser
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_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()))
if not os.path.exists(path):
os.makedirs(path)
execute_from_command_line(["", "syncdb", "--noinput"])
return 0
def get_database_path_from_settings():
"""
Retrieves the database path out of the 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. Returns 0 on success, else 1.
"""
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(args):
"""
Deletes the sqlite3 database. Returns 0 on success, else 1.
"""
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)
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 in OpenSlides 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())

View File

@ -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()

View File

@ -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)

View File

@ -11,7 +11,7 @@
""" """
import os import os
from openslides.main import fs2unicode from openslides.utils.main import filesystem2unicode
SITE_ROOT = os.path.realpath(os.path.dirname(__file__)) SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
@ -44,7 +44,7 @@ USE_I18N = True
USE_L10N = True USE_L10N = True
LOCALE_PATHS = ( 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 # 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`` # Absolute path to the directory that holds static media from ``collectstatic``
# Example: "/home/media/static.lawrence.com/" # 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 # 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). # trailing slash if there is a path component (optional in other cases).
@ -64,7 +64,7 @@ STATIC_URL = '/static/'
# Additional directories containing static files (not application specific) # Additional directories containing static files (not application specific)
# Examples: "/home/media/lawrence.com/extra-static/" # Examples: "/home/media/lawrence.com/extra-static/"
STATICFILES_DIRS = ( STATICFILES_DIRS = (
fs2unicode(os.path.join(SITE_ROOT, 'static')), filesystem2unicode(os.path.join(SITE_ROOT, 'static')),
) )
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
@ -97,7 +97,7 @@ TEMPLATE_DIRS = (
# "C:/www/django/templates". # "C:/www/django/templates".
# Always use forward slashes, even on Windows. # Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths. # 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 = ( INSTALLED_APPS = (

View File

@ -1,423 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.main
~~~~~~~~~~~~~~~
Main script to start and set up OpenSlides.
:copyright: 20112013 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()

View File

@ -130,8 +130,11 @@ def create_or_reset_admin_user():
admin = User() admin = User()
admin.username = 'admin' admin.username = 'admin'
admin.last_name = 'Administrator' admin.last_name = 'Administrator'
created = True
else:
created = False
admin.default_password = 'admin' admin.default_password = 'admin'
admin.set_password(admin.default_password) admin.set_password(admin.default_password)
admin.save() admin.save()
admin.groups.add(group_staff) admin.groups.add(group_staff)
return created

157
openslides/utils/main.py Normal file
View File

@ -0,0 +1,157 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.utils.main
~~~~~~~~~~~~~~~~~~~~~
Some functions for OpenSlides.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
import ctypes
import os
import socket
import sys
import tempfile
UNIX_VERSION = 'Unix Version'
WINDOWS_VERSION = 'Windows Version'
WINDOWS_PORTABLE_VERSION = 'Windows Portable Version'
class PortableDirNotWritable(Exception):
pass
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 OpenSlides 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 get_default_user_data_path(openslides_type):
"""
Returns the default path for user specific data 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_user_data_path = filesystem2unicode(os.environ.get(
'XDG_DATA_HOME', os.path.join(os.path.expanduser('~'), '.local', 'share')))
elif openslides_type == WINDOWS_VERSION:
default_user_data_path = get_win32_app_data_path()
elif openslides_type == WINDOWS_PORTABLE_VERSION:
default_user_data_path = get_win32_portable_path()
else:
raise TypeError('%s is not a valid OpenSlides type.' % openslides_type)
return default_user_data_path
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 determine Windows' 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 PortableDirNotWritable(
'Portable directory is not writeable. '
'Please choose another directory for settings and data files.')
else:
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, the media directory and the search index. The argument
'name' can be 'database', 'media' or 'whoosh_index'.
"""
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', '')
elif name == 'whoosh_index':
path = os.path.join(get_win32_portable_path(), 'openslides', 'whoosh_index', '')
else:
raise TypeError('Unknown type %s' % name)
return path
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

View File

@ -45,4 +45,4 @@ setup(
packages=find_packages(exclude=['tests', 'tests.*']), packages=find_packages(exclude=['tests', 'tests.*']),
include_package_data=True, include_package_data=True,
install_requires=install_requires, install_requires=install_requires,
entry_points={'console_scripts': ['openslides = openslides.main:main']}) entry_points={'console_scripts': ['openslides = openslides.__main__:main']})

View File

@ -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()

67
tests/utils/test_main.py Normal file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Tests for openslides.utils.main
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
import os
import sys
from django.core.exceptions import ImproperlyConfigured
from openslides.__main__ import (
get_default_settings_path,
get_browser_url,
get_user_data_path_values,
setup_django_settings_module)
from openslides.utils.test import TestCase
from openslides.utils.main import (
get_default_user_data_path,
UNIX_VERSION,
WINDOWS_PORTABLE_VERSION)
class TestFunctions(TestCase):
def test_get_default_user_data_path(self):
self.assertTrue('.local/share' in get_default_user_data_path(UNIX_VERSION))
def test_get_default_settings_path(self):
self.assertTrue('.config/openslides/settings.py' in get_default_settings_path(UNIX_VERSION))
def test_get_user_data_path_values_case_one(self):
self.assertEqual(
get_user_data_path_values('test_path_dfhvndshfgsef', default=False),
{'import_function': '',
'database_path_value': "'test_path_dfhvndshfgsef/openslides/database.sqlite'",
'media_path_value': "'test_path_dfhvndshfgsef/openslides/media/'",
'whoosh_index_path_value': "'test_path_dfhvndshfgsef/openslides/whoosh_index/'"})
def test_get_user_data_path_values_case_two(self):
self.assertEqual(
get_user_data_path_values('test_path_dfhvndshfgsef', default=True, openslides_type=WINDOWS_PORTABLE_VERSION),
{'import_function': 'from openslides.utils.main import get_portable_paths',
'database_path_value': "get_portable_paths('database')",
'media_path_value': "get_portable_paths('media')",
'whoosh_index_path_value': "get_portable_paths('whoosh_index')"})
def test_setup_django_settings_module(self):
setup_django_settings_module('test_dir_dhvnghfjdh456fzheg2f/test_path_bngjdhc756dzwncshdfnx.py')
self.assertEqual(os.environ['DJANGO_SETTINGS_MODULE'], 'test_path_bngjdhc756dzwncshdfnx')
self.assertEqual(sys.path[0], 'test_dir_dhvnghfjdh456fzheg2f')
def test_setup_django_settings_module_error(self):
self.assertRaisesMessage(
ImproperlyConfigured,
"'.' is not an allowed character in the settings-file",
setup_django_settings_module,
'wrong.file.py')
def test_get_browser_url(self):
self.assertEqual(get_browser_url('123.456.789.365', 6789), 'http://123.456.789.365:6789')
self.assertEqual(get_browser_url('123.456.789.365', 80), 'http://123.456.789.365')
self.assertEqual(get_browser_url('0.0.0.0', 6789), 'http://localhost:6789')
self.assertEqual(get_browser_url('0.0.0.0', 80), 'http://localhost')