From 7351aacba5a1429424a6b59dc057cf20e72585f3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 5 Jun 2013 18:47:41 +0200 Subject: [PATCH] Fixed an issue with setting location. Still errors with creating new objects. --- src/objects/models.py | 29 +++++++++++++------------- src/server/caches.py | 1 + src/typeclasses/models.py | 3 ++- src/utils/idmapper/base.py | 42 ++++++++++++++++++++++++++++++-------- src/utils/utils.py | 26 +++++++++++------------ 5 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/objects/models.py b/src/objects/models.py index 8237bd0da2..907feb5945 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -178,9 +178,10 @@ class ObjectDB(TypedObject): # db_key (also 'name' works), db_typeclass_path, db_date_created, # db_permissions # - # These databse fields (including the inherited ones) are all set - # using their corresponding properties, named same as the field, - # but withtout the db_* prefix. + # These databse fields (including the inherited ones) should normally be set + # using their corresponding wrapper properties, named same as the field, but without + # the db_* prefix (e.g. the db_key field is set with self.key instead). The wrappers + # will automatically save and cache the data more efficiently. # If this is a character object, the player is connected here. db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player', @@ -217,7 +218,6 @@ class ObjectDB(TypedObject): _GA(self, "cmdset").update(init_mode=True) _SA(self, "scripts", ScriptHandler(self)) _SA(self, "nicks", ObjectNickHandler(self)) - # store the attribute class # Wrapper properties to easily set database fields. These are # @property decorators that allows to access these fields using @@ -306,16 +306,17 @@ class ObjectDB(TypedObject): del_field_cache(self, "sessid") sessid = property(__sessid_get, __sessid_set, __sessid_del) - def _db_location_handler(self, new_value, old_value=None): + def _db_location_handler(self, loc, old_value=None): "This handles changes to the db_location field." - print "db_location_handler:", new_value, old_value + #print "db_location_handler:", loc, old_value try: old_loc = old_value # new_value can be dbref, typeclass or dbmodel - if ObjectDB.objects.dbref(new_value, reqhash=False): - loc = ObjectDB.objects.dbref_search(new_value) - # this should not fail if new_value is valid. - loc = _GA(loc, "dbobj") + if ObjectDB.objects.dbref(loc, reqhash=False): + loc = ObjectDB.objects.dbref_search(loc) + if loc and type(loc) != ObjectDB: + # this should not fail if new_value is valid. + loc = _GA(loc, "dbobj") # recursive location check def is_loc_loop(loc, depth=0): @@ -328,22 +329,22 @@ class ObjectDB(TypedObject): try: is_loc_loop(loc) except RuntimeWarning: pass - # set the location - _SA(self, "db_location", loc) + #print "db_location_handler2:", _GA(loc, "db_key") if loc else loc, type(loc) # update the contents of each location if old_loc: _GA(_GA(old_loc, "dbobj"), "contents_update")(self, remove=True) if loc: _GA(loc, "contents_update")(self) + return loc except RuntimeError: string = "Cannot set location, " - string += "%s.location = %s would create a location-loop." % (self.key, new_value) + string += "%s.location = %s would create a location-loop." % (self.key, loc) _GA(self, "msg")(_(string)) logger.log_trace(string) raise RuntimeError(string) except Exception, e: string = "Cannot set location (%s): " % str(e) - string += "%s is not a valid location." % new_value + string += "%s is not a valid location." % loc _GA(self, "msg")(_(string)) logger.log_trace(string) raise Exception(string) diff --git a/src/server/caches.py b/src/server/caches.py index 9cbeb5bf4e..c933d46c19 100644 --- a/src/server/caches.py +++ b/src/server/caches.py @@ -90,6 +90,7 @@ def field_pre_save(sender, instance=None, update_fields=None, raw=False, **kwarg """ if raw: return + print "field_pre_save:", _GA(instance, "db_key") if hasattr(instance, "db_key") else instance, update_fields if update_fields: # this is a list of strings at this point. We want field objects update_fields = (instance._meta.get_field_by_name(field)[0] for field in update_fields) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index a39860b898..85cdad472d 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -436,7 +436,8 @@ class TypedObject(SharedMemoryModel): def __init__(self, *args, **kwargs): "We must initialize the parent first - important!" SharedMemoryModel.__init__(self, *args, **kwargs) - self.locks = LockHandler(self) + _SA(self, "dbobj", self) # this allows for self-reference + _SA(self, "locks", LockHandler(self)) class Meta: """ diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 30b22d1cd1..a1b7d1d770 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -11,8 +11,10 @@ import os, threading #from twisted.internet import reactor #from twisted.internet.threads import blockingCallFromThread from twisted.internet.reactor import callFromThread +from django.core.exceptions import ObjectDoesNotExist from django.db.models.base import Model, ModelBase from django.db.models.signals import post_save, pre_delete, post_syncdb +from src.utils.utils import dbref from manager import SharedMemoryManager @@ -92,6 +94,7 @@ class SharedMemoryModelBase(ModelBase): def __init__(cls, *args, **kwargs): """ + Field shortcut creation: Takes field names db_* and creates property wrappers named without the db_ prefix. So db_key -> key This wrapper happens on the class level, so there is no overhead when creating objects. If a class already has a wrapper of the given name, the automatic creation is skipped. Note: Remember to @@ -101,24 +104,47 @@ class SharedMemoryModelBase(ModelBase): def create_wrapper(cls, fieldname, wrappername): "Helper method to create property wrappers with unique names (must be in separate call)" def _get(cls, fname): - return _GA(cls, fname) + "Wrapper for getting database field" + value = _GA(cls, fname) + if hasattr(value, "typeclass"): + return _GA(value, "typeclass") + #print "_get wrapper:", fname, value, type(value) + return value def _set(cls, fname, value): + "Wrapper for setting database field" + if hasattr(value, "dbobj"): + value = _GA(value, "dbobj") + else: + # we also allow setting using dbrefs, if so we try to load the matching object. + # (we assume the object is of the same type as the class holding the field, if + # not a custom handler must be used for that field) + dbid = dbref(value, reqhash=False) + if dbid: + try: + value = cls._default_manager.get(id=dbid) + except ObjectDoesNotExist: + err = "Could not set %s. Tried to treat value '%s' as a dbref, but no matching object with that id was found." + err = err % (fname, value) + raise ObjectDoesNotExist(err) + print "_set wrapper:", fname, value, type(value) _SA(cls, fname, value) - _GA(cls, "save")(update_fields=[fname]) # important! + _GA(cls, "save")(update_fields=[fname]) # important - this saves one field only def _del(cls, fname): + "Wrapper for clearing database field" raise RuntimeError("You cannot delete field %s on %s; set it to None instead." % (fname, cls)) - type(cls).__setattr__(cls, wrappername, property(lambda cls: _get(cls, fieldname), - lambda cls,val: _set(cls, fieldname, val), - lambda cls: _del(cls, fieldname))) - # eclude some models that should not auto-create wrapper fields + type(cls).__setattr__(cls, wrappername, property(fget=lambda cls: _get(cls, fieldname), + fset=lambda cls,val: _set(cls, fieldname, val), + fdel=lambda cls: _del(cls, fieldname), + doc="Wraps setting, saving and caching the %s field." % fieldname)) + # exclude some models that should not auto-create wrapper fields if cls.__name__ in ("ServerConfig", "TypeNick"): return - # dynamically create the properties + # dynamically create the wrapper properties for all fields not already handled for field in cls._meta.fields: fieldname = field.name wrappername = fieldname == "id" and "dbid" or fieldname.replace("db_", "") if not hasattr(cls, wrappername): - # make sure not to overload manually created wrappers on the model + # makes sure not to overload manually created wrappers on the model #print "wrapping %s -> %s" % (fieldname, wrappername) create_wrapper(cls, fieldname, wrappername) diff --git a/src/utils/utils.py b/src/utils/utils.py index 503516e5f8..75b52b89ea 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -287,22 +287,20 @@ def pypath_to_realpath(python_path, file_ending='.py'): def dbref(dbref, reqhash=True): """ - Converts/checks if input is a valid dbref Valid forms of dbref - (database reference number) are either a string '#N' or - an integer N. Output is the integer part. + Converts/checks if input is a valid dbref. + If reqhash is set, only input strings on the form '#N', where N is an integer + is accepted. Otherwise strings '#N', 'N' and integers N are all accepted. + Output is the integer part. """ - if reqhash and not (isinstance(dbref, basestring) and dbref.startswith("#")): - return None - if isinstance(dbref, basestring): + if reqhash: + return (int(dbref.lstrip('#')) if (isinstance(dbref, basestring) and + dbref.startswith("#") and + dbref.lstrip('#').isdigit()) + else None) + elif isinstance(dbref, basestring): dbref = dbref.lstrip('#') - try: - dbref = int(dbref) - if dbref < 1: - return None - except Exception: - return None - return dbref - return None + return int(dbref) if dbref.isdigit() else None + return dbref if isinstance(dbref, int) else None def to_unicode(obj, encoding='utf-8', force_string=False): """