Added optional support for database migrations with south. The game/migrate.py program is a simple wrapper that runs the suitable commands for setting up a database and updating it, respectively.

This commit is contained in:
Griatch 2010-10-31 18:21:23 +00:00
parent 7eaf3d221c
commit 7fb6362dc4
9 changed files with 153 additions and 30 deletions

View file

@ -162,7 +162,6 @@ def stop_server(parser, options, args):
else: else:
print '\n\rUnknown OS detected, can not stop. ' print '\n\rUnknown OS detected, can not stop. '
def main(): def main():
""" """
Beginning of the program logic. Beginning of the program logic.
@ -176,14 +175,14 @@ def main():
dest='interactive', dest='interactive',
help='Start in daemon mode (default)') help='Start in daemon mode (default)')
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if "start" in args: if "start" in args:
if options.interactive: if options.interactive:
start_interactive(parser, options, args) start_interactive(parser, options, args)
else: else:
start_daemon(parser, options, args) start_daemon(parser, options, args)
elif "stop" in args: elif "stop" in args:
stop_server(parser, options, args) stop_server(parser, options, args)
else: else:
parser.print_help() parser.print_help()
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -165,7 +165,7 @@ class CmdListScripts(MuxCommand):
else: else:
table[1].append(script.obj.key) table[1].append(script.obj.key)
table[2].append(script.key) table[2].append(script.key)
if not hasattr(script, 'interval') or not script.interval: if not hasattr(script, 'interval') or script.interval < 0:
table[3].append("--") table[3].append("--")
else: else:
table[3].append("%ss" % script.interval) table[3].append("%ss" % script.interval)

View file

@ -95,9 +95,7 @@ from src.settings_default import *
print """ print """
Welcome to Evennia (version %s)! Welcome to Evennia (version %s)!
Created a fresh settings.py file for you.""" % VERSION We created a fresh settings.py file for you.""" % VERSION
try: try:
from game import settings from game import settings

124
game/migrate.py Executable file
View file

@ -0,0 +1,124 @@
#!/usr/bin/env python
"""
Database migration helper, using South.
Usage:
- Install South using the method suitable for your platform
http://south.aeracode.org/docs/installation.html
- You need to have a database setup, either an old one or
a fresh one. If the latter, run manage.py syncdb as normal,
entering superuser info etc.
- Start this tool and use the 'initialize' option. South will
create a migration scheme for all Evennia components.
That's all you need to do until Evennia's database scheme changes,
something which is usually announced with the update. To update
your current database automatically, follow these steps:
- Run this tool
- Select the Update option.
That is all. :)
For more advanced migrations, there might be further instructions.
"""
import os, sys
from subprocess import call
# Set the Python path up so we can get to settings.py from here.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
if not os.path.exists('settings.py'):
# make sure we have a settings.py file.
print " No settings.py file found. Launching manage.py ..."
import game.manage
print """
Now configure Evennia by editing your new settings.py file.
If you haven't already, you should also create/configure the
database with 'python manage.py syncdb' before continuing."""
sys.exit()
# Get the settings
from django.conf import settings
# Prepare all valid apps
APPLIST = [app.split('.')[-1] for app in settings.INSTALLED_APPS
if app.startswith("src.") or app.startswith("game.")]
def run_south(mode):
"""
Simply call manage.py with the appropriate South commands.
"""
if mode == "init":
for appname in APPLIST:
print "Initializing %s ..." % appname
call(["python", "manage.py", "convert_to_south", appname])
print "\nInitialization complete. That's all you need to do for now."
elif mode == "update":
for appname in APPLIST:
print "Updating/migrating schema for %s ..." % appname
call(["python", "manage.py", "schemamigration", appname, "--auto"])
call(["python", "manage.py", "migrate", appname])
print "\nUpdate complete."
def south_ui():
"""
Simple menu for handling migrations.
"""
string = """
Evennia Database Migration Tool
You usually don't need to use this tool unless a new version of Evennia tells you that
the database scheme changed in some way, AND you don't want to reset your database.
This tool will help you to migrate an existing database without having to manually edit
your tables and fields to match the new scheme. For that to work you must have run this
tool *before* applying the changes however.
This is a simple wrapper on top of South, a Django database scheme migration tool.
If you want more control, you can call manage.py directly using the instructions
found at http://south.aeracode.org/docs.
NOTE: Evennia is still in Alpha - there is no guarantee that database changes will still
not be too advanced to handle with this simple tool, and it is too soon to talk
of supplying custom migration schemes to new versions.
Options:
i - Initialize an existing/fresh database with migration mappings (done once)
u - Update an initialized database to the changed scheme
q - Quit
"""
while True:
print string
inp = str(raw_input(" Option > "))
inp = inp.lower()
if inp in ["q", "i", "u"]:
if inp == 'i':
run_south("init")
elif inp == 'u':
run_south("update")
sys.exit()
if __name__ == "__main__":
if not 'south' in settings.INSTALLED_APPS:
string = "\n The 'south' database migration tool does not seem to be installed."
string += "\n You can find it here: http://south.aeracide.org.\n"
print string
else:
south_ui()

View file

@ -129,7 +129,7 @@ class ObjectDB(TypedObject):
# comma-separated list of alias-names of this object. Note that default # comma-separated list of alias-names of this object. Note that default
# searches only search aliases in the same location as caller. # searches only search aliases in the same location as caller.
db_aliases = models.ForeignKey(Alias, db_index=True, blank=True, null=True) db_aliases = models.ForeignKey(Alias, blank=True, null=True, db_index=True)
# If this is a character object, the player is connected here. # If this is a character object, the player is connected here.
db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True) db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True)
# The location in the game world. Since this one is likely # The location in the game world. Since this one is likely

View file

@ -48,7 +48,7 @@ class ScriptHandler(object):
interval = "inf" interval = "inf"
next_repeat = "inf" next_repeat = "inf"
repeats = "inf" repeats = "inf"
if script.interval: if script.interval > 0:
interval = script.interval interval = script.interval
if script.repeats: if script.repeats:
repeats = script.repeats repeats = script.repeats

View file

@ -441,8 +441,8 @@ try:
except ImportError: except ImportError:
pass pass
# South handles automatic database scheme migrations when evennia updates # South handles automatic database scheme migrations when evennia updates
#try: try:
# import south import south
# INSTALLED_APPS = INSTALLED_APPS + ('south',) INSTALLED_APPS = INSTALLED_APPS + ('south',)
#except ImportError: except ImportError:
# pass pass

View file

@ -4,17 +4,17 @@ from django.db.models.base import Model, ModelBase
from manager import SharedMemoryManager from manager import SharedMemoryManager
TCACHE = {} TCACHE = {} # test cache, for debugging /Griatch
class SharedMemoryModelBase(ModelBase): class SharedMemoryModelBase(ModelBase):
def __new__(cls, name, bases, attrs): #def __new__(cls, name, bases, attrs):
super_new = super(ModelBase, cls).__new__ # super_new = super(ModelBase, cls).__new__
parents = [b for b in bases if isinstance(b, SharedMemoryModelBase)] # parents = [b for b in bases if isinstance(b, SharedMemoryModelBase)]
if not parents: # if not parents:
# If this isn't a subclass of Model, don't do anything special. # # If this isn't a subclass of Model, don't do anything special.
return super_new(cls, name, bases, attrs) # print "not a subclass of Model", name, bases
# return super_new(cls, name, bases, attrs)
return super(SharedMemoryModelBase, cls).__new__(cls, name, bases, attrs) # return super(SharedMemoryModelBase, cls).__new__(cls, name, bases, attrs)
def __call__(cls, *args, **kwargs): def __call__(cls, *args, **kwargs):
""" """
@ -49,6 +49,9 @@ class SharedMemoryModel(Model):
# subclass now? # subclass now?
__metaclass__ = SharedMemoryModelBase __metaclass__ = SharedMemoryModelBase
class Meta:
abstract = True
def _get_cache_key(cls, args, kwargs): def _get_cache_key(cls, args, kwargs):
""" """
This method is used by the caching subsystem to infer the PK value from the constructor arguments. This method is used by the caching subsystem to infer the PK value from the constructor arguments.

View file

@ -21,22 +21,21 @@ def reload_modules():
""" """
Reload modules that don't have any variables that can be reset. Reload modules that don't have any variables that can be reset.
Note that python reloading is a tricky art and strange things have Note that python reloading is a tricky art and strange things have
been known to happen if debugging and reloading a lot while been known to happen if debugging and reloading a lot. A server
working with src/ modules. A cold reboot is often needed cold reboot is often needed eventually.
eventually.
""" """
# We protect e.g. src/ from reload since reloading it in a running # We protect e.g. src/ from reload since reloading it in a running
# server can create unexpected results (and besides, we should # server can create unexpected results (and besides, non-evennia devs
# never need to do that anyway. Updating src requires a server # should never need to do that anyway). Updating src requires a server
# reboot). # reboot.
protected_dirs = ('src.',) protected_dirs = ('src.',)
# flag 'dangerous' typeclasses (those which retain a memory # flag 'dangerous' typeclasses (those which retain a memory
# reference, notably Scripts with a timer component) for # reference, notably Scripts with a timer component) for
# non-reload, since these cannot be safely cleaned from memory # non-reload, since these cannot be safely cleaned from memory
# without causing havoc. A server reboot is required for updating # without causing havoc. A server reboot is required for updating
# these. # these (or killing all running, timed scripts).
unsafe_modules = [] unsafe_modules = []
for scriptobj in ScriptDB.objects.get_all_scripts(): for scriptobj in ScriptDB.objects.get_all_scripts():
if (scriptobj.interval > -1) and scriptobj.typeclass_path: if (scriptobj.interval > -1) and scriptobj.typeclass_path: