diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index cda1f0bee9..24bbe7ccec 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -92,12 +92,32 @@ CREATED_NEW_GAMEDIR = \ """ +ERROR_INPUT = \ + """ + The argument(s) + {args} + is not recognized by Evennia nor Django. Use -h for help. + """ + ERROR_NO_GAMEDIR = \ """ No Evennia settings file was found. You must run this command from inside a valid game directory first created with --init. """ +WARNING_MOVING_SUPERUSER = \ + """ + Evennia expects a Player superuser with id=1. No such Player was + found. However, another superuser ('{other_key}', id={other_id}) + was found in the database. If you just created this superuser and + still see this text it is probably due to the database being + flushed recently - in this case the database's internal + auto-counter might just start from some value higher than one. + + We will fix this by assigning the id 1 to Player '{other_key}'. + Please confirm this is acceptable before continuing. + """ + WARNING_RUNSERVER = \ """ WARNING: There is no need to run the Django development @@ -127,7 +147,7 @@ ERROR_DATABASE = \ Your database does not seem to be set up correctly. (error was '{traceback}') - Standing in your game directory, try to run + Standing in your game directory, run evennia migrate @@ -430,7 +450,7 @@ def create_superuser(): django.core.management.call_command("createsuperuser", interactive=True) -def check_database(exit_on_error=False): +def check_database(): """ Check database exists """ @@ -441,19 +461,46 @@ def check_database(exit_on_error=False): # database exists and seems set up. Initialize evennia. import evennia evennia.init() - else: - if exit_on_error: - print ERROR_DATABASE.format(traceback=e) - sys.exit() - return False - return True # Try to get Player#1 from evennia.players.models import PlayerDB try: PlayerDB.objects.get(id=1) + except django.db.utils.OperationalError, e: + print ERROR_DATABASE.format(traceback=e) + sys.exit() except PlayerDB.DoesNotExist: # no superuser yet. We need to create it. - create_superuser() + + other_superuser = PlayerDB.objects.filter(is_superuser=True) + if other_superuser: + # Another superuser was found, but not with id=1. This may + # happen if using flush (the auto-id starts at a higher + # value). Wwe copy this superuser into id=1. To do + # this we must deepcopy it, delete it then save the copy + # with the new id. This allows us to avoid the UNIQUE + # constraint on usernames. + other = other_superuser[0] + other_id = other.id + other_key = other.username + print WARNING_MOVING_SUPERUSER.format(other_key=other_key, + other_id=other_id) + res = "" + while res.upper() != "Y": + # ask for permission + res = raw_input("Continue [Y]/N: ") + if res.upper() == "N": + sys.exit() + elif not res: + break + # continue with the + from copy import deepcopy + new = deepcopy(other) + other.delete() + new.id = 1 + new.save() + else: + create_superuser() + check_database() return True @@ -854,16 +901,16 @@ def main(): help="Start given server component under the Python profiler.") parser.add_argument('--dummyrunner', nargs=1, action='store', dest='dummyrunner', metavar="N", help="Tests a running server by connecting N dummy players to it.") - parser.add_argument("mode", metavar="option", nargs='?', default="help", - help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).") - parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all", + parser.add_argument("option", nargs='?', default="noop", + help="Operational mode or management option.") + parser.add_argument("service", metavar="component", nargs='?', default="all", help="Which server component to operate on. One of server, portal or all (default).") - args = parser.parse_args() + args, unknown_args = parser.parse_known_args() # handle arguments - mode, service = args.mode, args.service + option, service = args.option, args.service check_main_evennia_dependencies() @@ -874,31 +921,43 @@ def main(): sys.exit() if args.show_version: - print show_version_info(mode=="help") + print show_version_info(option=="help") sys.exit() - if mode == "help" and not args.dummyrunner: + if option == "help" and not args.dummyrunner: print ABOUT_INFO sys.exit() - check_db = not mode == "migrate" - - # this must be done first - it sets up all the global properties - # and initializes django for the game directory - init_game_directory(CURRENT_DIR, check_db=check_db) if args.dummyrunner: # launch the dummy runner + init_game_directory(CURRENT_DIR, check_db=True) run_dummyrunner(args.dummyrunner[0]) - elif mode == 'menu': + elif option == 'menu': # launch menu for operation + init_game_directory(CURRENT_DIR, check_db=True) run_menu() - elif mode in ('start', 'reload', 'stop'): + elif option in ('start', 'reload', 'stop'): # operate the server directly - server_operation(mode, service, args.interactive, args.profiler) - else: + init_game_directory(CURRENT_DIR, check_db=True) + server_operation(option, service, args.interactive, args.profiler) + elif option != "noop": # pass-through to django manager - if mode in ('runserver', 'testserver'): + init_game_directory(CURRENT_DIR, check_db=False) + if option in ('runserver', 'testserver'): print WARNING_RUNSERVER - django.core.management.call_command(mode) + args = [option] + kwargs = {} + if service not in ("all", "server", "portal"): + args.append(service) + if unknown_args: + for arg in unknown_args: + if arg.startswith("--"): + kwargs[arg.lstrip("--")] = True + else: + args.append(arg) + try: + django.core.management.call_command(*args, **kwargs) + except django.core.management.base.CommandError: + print ERROR_INPUT.format(args=" ".join(args)) if __name__ == '__main__': diff --git a/evennia/server/initial_setup.py b/evennia/server/initial_setup.py index f2705ca448..ca60511b94 100644 --- a/evennia/server/initial_setup.py +++ b/evennia/server/initial_setup.py @@ -70,10 +70,10 @@ def create_objects(): nohome=True) god_character.id = 1 + god_character.save() god_character.db.desc = _('This is User #1.') god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()") god_character.permissions.add("Immortals") - god_character.save() god_player.attributes.add("_first_login", True) god_player.attributes.add("_last_puppet", god_character) @@ -82,6 +82,7 @@ def create_objects(): room_typeclass = settings.BASE_ROOM_TYPECLASS limbo_obj = create.create_object(room_typeclass, _('Limbo'), nohome=True) limbo_obj.id = 2 + limbo_obj.save() string = \ "Welcome to your new {wEvennia{n-based game. From here you are ready " \ "to begin development. Visit http://evennia.com if you should need " \ diff --git a/evennia/server/tests.py b/evennia/server/tests.py index 032187ac81..c5e2657943 100644 --- a/evennia/server/tests.py +++ b/evennia/server/tests.py @@ -36,7 +36,7 @@ from evennia.utils.utils import mod_import class EvenniaTestSuiteRunner(DjangoTestSuiteRunner): """ - This test runner only runs tests on the apps specified in evennia/ and game/ to + This test runner only runs tests on the apps specified in evennia/ avoid running the large number of tests defined by Django """ def build_suite(self, test_labels, extra_tests=None, **kwargs): @@ -67,11 +67,9 @@ def suite(): tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(locktests)) tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(utiltests)) + # load tests from the evennia/tests central location for path in glob.glob(os.path.join(settings.EVENNIA_DIR, "tests", "*.py")): testmod = mod_import(path) tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(testmod)) - #from evennia.tests import test_commands_cmdhandler - #tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_commands_cmdhandler)) - return tsuite diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index 60bd796bea..55ee8c8455 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -438,8 +438,6 @@ class TypedObject(SharedMemoryModel): "Scrambling method for already deleted objects" raise ObjectDoesNotExist("This object was already deleted!") - _is_deleted = False # this is checked by db_* wrappers - def delete(self): "Cleaning up handlers on the typeclass level" global TICKER_HANDLER @@ -455,7 +453,6 @@ class TypedObject(SharedMemoryModel): # scrambling properties self.delete = self._deleted - self._is_deleted = True super(TypedObject, self).delete() # diff --git a/evennia/utils/idmapper/base.py b/evennia/utils/idmapper/base.py index 62600b4a01..1929911240 100755 --- a/evennia/utils/idmapper/base.py +++ b/evennia/utils/idmapper/base.py @@ -310,6 +310,7 @@ class SharedMemoryModel(Model): Delete the object, clearing cache """ self.flush_from_cache() + self._is_deleted = True super(SharedMemoryModel, self).delete(*args, **kwargs) def save(self, *args, **kwargs):