Changed how the Typeclass system returns errors. Instead of echoing typeclass erros to the MUD-info channel (which is not only not only very spammy for everyone but also very hard to make clean so as to avoid recursion at a stage of typeclass failing), the system instead stores a property on itself called 'typeclass_last_errmsg' that holds eventual errors. This means that the task of reporting errors does not fall on the typeclass system itself but on the calling methods, as it should be. So src.utils.create.create_* functions now takes a new optional keyword "report_to" that holds an object to receive errors. If this keyword is given, the function msg():es that object with the error and returns None as before. If report_to is not set however, the create_* methods now return an Exception containing the error text. All default commands have been changed to accomodate for this behaviour, which allows for much more control over errors.

Also, the default ADMIN_MEDIA static files changed location in Django 1.4. The initial_setup function now accounts for this.
This commit is contained in:
Griatch 2012-04-21 16:15:37 +02:00
parent 63329f5420
commit 8c3b49e704
15 changed files with 838 additions and 785 deletions

View file

@ -13,12 +13,12 @@ shorter "create.object()".
The respective object managers hold more methods for manipulating and
searching objects already existing in the database.
Models covered:
Models covered:
Objects
Scripts
Help
Message
Channel
Channel
Players
"""
@ -26,8 +26,7 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.db import IntegrityError
from src.utils.idmapper.models import SharedMemoryModel
from src.utils import logger, utils, idmapper
from src.utils.utils import is_iter, has_parent, inherits_from
from src.utils import utils, logger
# limit symbol import from API
__all__ = ("create_object", "create_script", "create_help_entry", "create_message", "create_channel", "create_player")
@ -35,23 +34,28 @@ __all__ = ("create_object", "create_script", "create_help_entry", "create_messag
GA = object.__getattribute__
#
# Game Object creation
# Game Object creation
#
def create_object(typeclass, key=None, location=None,
home=None, player=None, permissions=None, locks=None,
aliases=None, destination=None):
home=None, player=None, permissions=None, locks=None,
aliases=None, destination=None, report_to=None):
"""
Create a new in-game object. Any game object is a combination
of a database object that stores data persistently to
the database, and a typeclass, which on-the-fly 'decorates'
the database object into whataver different type of object
it is supposed to be in the game.
it is supposed to be in the game.
See src.objects.managers for methods to manipulate existing objects
in the database. src.objects.objects holds the base typeclasses
and src.objects.models hold the database model.
and src.objects.models hold the database model.
report_to is an optional object for reporting errors to in string form.
If report_to is not set, errors will be raised as en Exception
containing the error message. If set, this method will return
None upon errors.
"""
# deferred import to avoid loops
from src.objects.objects import Object
@ -64,32 +68,36 @@ def create_object(typeclass, key=None, location=None,
typeclass = typeclass.typeclass.path
elif isinstance(typeclass, Object) or utils.inherits_from(typeclass, Object):
# this is already an object typeclass, extract its path
typeclass = typeclass.path
typeclass = typeclass.path
# create new database object
# create new database object
new_db_object = ObjectDB()
# assign the typeclass
# assign the typeclass
typeclass = utils.to_unicode(typeclass)
new_db_object.typeclass_path = typeclass
# the name/key is often set later in the typeclass. This
# is set here as a failsafe.
# is set here as a failsafe.
if key:
new_db_object.key = key
new_db_object.key = key
else:
new_db_object.key = "#%i" % new_db_object.id
# this will either load the typeclass or the default one
new_object = new_db_object.typeclass
if not GA(new_db_object, "is_typeclass")(typeclass, exact=True):
if not GA(new_object, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
SharedMemoryModel.delete(new_db_object)
return None
if report_to:
GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_object.key, typeclass,
GA(new_db_object, "typeclass_last_errmsg")))
return None
else:
raise Exception(GA(new_db_object, "typeclass_last_errmsg"))
# from now on we can use the typeclass object
# from now on we can use the typeclass object
# as if it was the database object.
if player:
@ -97,14 +105,14 @@ def create_object(typeclass, key=None, location=None,
new_object.player = player
player.obj = new_object
new_object.destination = destination
new_object.destination = destination
# call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom
# things on its database object.
# things on its database object.
new_object.basetype_setup() # setup the basics of Exits, Characters etc.
new_object.at_object_creation()
# custom-given perms/locks overwrite hooks
if permissions:
new_object.permissions = permissions
@ -119,15 +127,15 @@ def create_object(typeclass, key=None, location=None,
else:
new_object.home = settings.CHARACTER_DEFAULT_HOME
if location:
new_object.move_to(location, quiet=True)
else:
# rooms would have location=None.
new_object.location = None
new_object.location = None
# post-hook setup (mainly used by Exits)
new_object.basetype_posthook_setup()
new_object.basetype_posthook_setup()
new_object.save()
return new_object
@ -136,12 +144,12 @@ def create_object(typeclass, key=None, location=None,
object = create_object
#
# Script creation
# Script creation
#
def create_script(typeclass, key=None, obj=None, locks=None,
interval=None, start_delay=None, repeats=None,
persistent=None, autostart=True):
def create_script(typeclass, key=None, obj=None, locks=None,
interval=None, start_delay=None, repeats=None,
persistent=None, autostart=True, report_to=None):
"""
Create a new script. All scripts are a combination
of a database object that communicates with the
@ -149,42 +157,47 @@ def create_script(typeclass, key=None, obj=None, locks=None,
database object into being different types of scripts.
It's behaviour is similar to the game objects except
scripts has a time component and are more limited in
scope.
scope.
Argument 'typeclass' can be either an actual
typeclass object or a python path to such an object.
Only set key here if you want a unique name for this
particular script (set it in config to give
same key to all scripts of the same type). Set obj
to tie this script to a particular object.
to tie this script to a particular object.
See src.scripts.manager for methods to manipulate existing
scripts in the database.
report_to is an obtional object to receive error messages.
If report_to is not set, an Exception with the
error will be raised. If set, this method will
return None upon errors.
"""
# deferred import to avoid loops.
from src.scripts.scripts import Script
#print "in create_script", typeclass
from src.scripts.models import ScriptDB
#print "in create_script", typeclass
from src.scripts.models import ScriptDB
if not typeclass:
typeclass = settings.BASE_SCRIPT_TYPECLASS
elif isinstance(typeclass, ScriptDB):
# this is already an scriptdb instance, extract its typeclass
typeclass = new_db_object.typeclass.path
typeclass = typeclass.typeclass.path
elif isinstance(typeclass, Script) or utils.inherits_from(typeclass, Script):
# this is already an object typeclass, extract its path
typeclass = typeclass.path
typeclass = typeclass.path
# create new database script
new_db_script = ScriptDB()
new_db_script = ScriptDB()
# assign the typeclass
# assign the typeclass
typeclass = utils.to_unicode(typeclass)
new_db_script.typeclass_path = typeclass
# the name/key is often set later in the typeclass. This
# is set here as a failsafe.
# is set here as a failsafe.
if key:
new_db_script.key = key
else:
@ -195,15 +208,19 @@ def create_script(typeclass, key=None, obj=None, locks=None,
if not GA(new_db_script, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
print "failure:", new_db_script, typeclass
SharedMemoryModel.delete(new_db_script)
return None
if report_to:
GA(report_to, "msg")("Error creating %s (%s): %s" % (new_db_script.key, typeclass,
GA(new_db_script, "typeclass_last_errmsg")))
return None
else:
raise Exception(GA(new_db_script, "typeclass_last_errmsg"))
if obj:
try:
new_script.obj = obj
except ValueError:
new_script.obj = obj.dbobj
new_script.obj = obj.dbobj
# call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom
@ -212,10 +229,10 @@ def create_script(typeclass, key=None, obj=None, locks=None,
# custom-given variables override the hook
if key:
new_script.key = key
new_script.key = key
if locks:
new_script.locks.add(locks)
if interval != None:
if interval != None:
new_script.interval = interval
if start_delay != None:
new_script.start_delay = start_delay
@ -223,13 +240,13 @@ def create_script(typeclass, key=None, obj=None, locks=None,
new_script.repeats = repeats
if persistent != None:
new_script.persistent = persistent
# a new created script should usually be started.
if autostart:
new_script.start()
new_db_script.save()
return new_script
return new_script
#alias
script = create_script
@ -243,7 +260,7 @@ def create_help_entry(key, entrytext, category="General", locks=None):
help entries are dynamic and directly taken from the __doc__ entries
of the command. The database-stored help entries are intended for more
general help on the game, more extensive info, in-game setting information
and so on.
and so on.
"""
from src.help.models import HelpEntry
@ -255,19 +272,19 @@ def create_help_entry(key, entrytext, category="General", locks=None):
if locks:
new_help.locks.add(locks)
new_help.save()
return new_help
return new_help
except IntegrityError:
string = "Could not add help entry: key '%s' already exists." % key
logger.log_errmsg(string)
return None
except Exception:
logger.log_trace()
return None
return None
# alias
help_entry = create_help_entry
#
# Comm system methods
# Comm system methods
#
def create_message(senderobj, message, channels=None,
@ -279,7 +296,7 @@ def create_message(senderobj, message, channels=None,
senderobj - the player sending the message. This must be the actual object.
message - text with the message. Eventual headers, titles etc
should all be included in this text string. Formatting
will be retained.
will be retained.
channels - a channel or a list of channels to send to. The channels
may be actual channel objects or their unique key strings.
receivers - a player to send to, or a list of them. May be Player objects
@ -289,7 +306,7 @@ def create_message(senderobj, message, channels=None,
The Comm system is created very open-ended, so it's fully possible
to let a message both go to several channels and to several receivers
at the same time, it's up to the command definitions to limit this as
desired.
desired.
"""
from src.comms.models import Msg
from src.players.models import PlayerDB
@ -304,10 +321,10 @@ def create_message(senderobj, message, channels=None,
elif hasattr(obj, 'db_player'):
return obj.db_player
else:
return None
return None
if not message:
# we don't allow empty messages.
# we don't allow empty messages.
return
new_message = Msg()
@ -315,19 +332,19 @@ def create_message(senderobj, message, channels=None,
new_message.message = message
new_message.save()
if channels:
if not is_iter(channels):
if not utils.is_iter(channels):
channels = [channels]
new_message.channels = [channel for channel in
[to_object(channel, objtype='channel')
for channel in channels] if channel]
for channel in channels] if channel]
if receivers:
#print "Found receiver:", receivers
if not is_iter(receivers):
if not utils.is_iter(receivers):
receivers = [receivers]
#print "to_player: %s" % to_player(receivers[0])
new_message.receivers = [to_player(receiver) for receiver in
[to_object(receiver) for receiver in receivers]
if receiver]
if receiver]
if locks:
new_message.locks.add(locks)
new_message.save()
@ -343,20 +360,20 @@ def create_channel(key, aliases=None, desc=None,
specifying the receivers explicitly. Instead players may
'connect' to the channel and follow the flow of messages. By
default the channel allows access to all old messages, but
this can be turned off with the keep_log switch.
this can be turned off with the keep_log switch.
key - this must be unique.
key - this must be unique.
aliases - list of alternative (likely shorter) keynames.
locks - lock string definitions
"""
from src.comms.models import Channel
from src.comms import channelhandler
from src.comms.models import Channel
from src.comms import channelhandler
try:
new_channel = Channel()
new_channel.key = key
new_channel.key = key
if aliases:
if not is_iter(aliases):
if not utils.is_iter(aliases):
aliases = [aliases]
new_channel.aliases = ",".join([alias for alias in aliases])
new_channel.desc = desc
@ -369,33 +386,33 @@ def create_channel(key, aliases=None, desc=None,
new_channel.locks.add(locks)
new_channel.save()
channelhandler.CHANNELHANDLER.add_channel(new_channel)
return new_channel
return new_channel
channel = create_channel
channel = create_channel
#
# Player creation methods
# Player creation methods
#
def create_player(name, email, password,
user=None,
typeclass=None,
is_superuser=False,
is_superuser=False,
locks=None, permissions=None,
create_character=True, character_typeclass=None,
character_location=None, character_home=None,
player_dbobj=None):
player_dbobj=None, report_to=None):
"""
This creates a new player, handling the creation of the User
object and its associated Player object.
object and its associated Player object.
If player_dbobj is given, this player object is used instead of
If player_dbobj is given, this player object is used instead of
creating a new one. This is called by the admin interface since it
needs to create the player object in order to relate it automatically
to the user.
to the user.
If create_character is
True, a game player object with the same name as the User/Player will
also be created. Its typeclass and base properties can also be given.
@ -403,15 +420,15 @@ def create_player(name, email, password,
Returns the new game character, or the Player obj if no
character is created. For more info about the typeclass argument,
see create_objects() above.
Note: if user is supplied, it will NOT be modified (args name, email,
passw and is_superuser will be ignored). Change those properties
directly on the User instead.
Note: if user is supplied, it will NOT be modified (args name, email,
passw and is_superuser will be ignored). Change those properties
directly on the User instead.
If no permissions are given (None), the default permission group
as defined in settings.PERMISSION_PLAYER_DEFAULT will be
as defined in settings.PERMISSION_PLAYER_DEFAULT will be
assigned. If permissions are given, no automatic assignment will
occur.
occur.
Concerning is_superuser:
A superuser should have access to everything
@ -420,18 +437,18 @@ def create_player(name, email, password,
django's own creation, not this one).
Usually only the server admin should need to be superuser, all
other access levels can be handled with more fine-grained
permissions or groups.
permissions or groups.
Since superuser overrules all permissions, we don't
set any in this case.
"""
# The system should already have checked so the name/email
# isn't already registered, and that the password is ok before
# getting here.
# getting here.
from src.players.models import PlayerDB
from src.players.player import Player
if not email:
email = "dummy@dummy.com"
if user:
@ -441,7 +458,7 @@ def create_player(name, email, password,
if is_superuser:
new_user = User.objects.create_superuser(name, email, password)
else:
new_user = User.objects.create_user(name, email, password)
new_user = User.objects.create_user(name, email, password)
try:
if not typeclass:
typeclass = settings.BASE_PLAYER_TYPECLASS
@ -450,7 +467,7 @@ def create_player(name, email, password,
typeclass = typeclass.typeclass.path
elif isinstance(typeclass, Player) or utils.inherits_from(typeclass, Player):
# this is already an object typeclass, extract its path
typeclass = typeclass.path
typeclass = typeclass.path
if player_dbobj:
new_db_player = player_dbobj
@ -458,7 +475,7 @@ def create_player(name, email, password,
new_db_player = PlayerDB(db_key=name, user=new_user)
new_db_player.save()
# assign the typeclass
# assign the typeclass
typeclass = utils.to_unicode(typeclass)
new_db_player.typeclass_path = typeclass
@ -468,13 +485,18 @@ def create_player(name, email, password,
if not GA(new_db_player, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
SharedMemoryModel.delete(new_db_player)
return None
if report_to:
GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_player.key, typeclass,
GA(new_db_player, "typeclass_last_errmsg")))
return None
else:
raise Exception(GA(new_db_player, "typeclass_last_errmsg"))
new_player.basetype_setup() # setup the basic locks and cmdset
# call hook method (may override default permissions)
new_player.at_player_creation()
# custom given arguments potentially overrides the hook
# custom given arguments potentially overrides the hook
if permissions:
new_player.permissions = permissions
elif not new_player.permissions:
@ -483,19 +505,19 @@ def create_player(name, email, password,
if locks:
new_player.locks.add(locks)
# create *in-game* 'player' object
# create *in-game* 'player' object
if create_character:
if not character_typeclass:
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
# creating the object automatically links the player
# and object together by player.obj <-> obj.player
new_character = create_object(character_typeclass, key=name,
location=None, home=character_location,
location=None, home=character_location,
permissions=permissions,
player=new_player)
player=new_player, report_to=report_to)
return new_character
return new_player
except Exception,e:
except Exception:
# a failure in creating the character
if not user:
# in there was a failure we clean up everything we can
@ -507,12 +529,12 @@ def create_player(name, email, password,
try:
new_player.delete()
except Exception:
pass
pass
try:
del new_character
except Exception:
pass
raise
pass
raise
# alias
player = create_player