From 8e8d85a4fe113598a1b6493d3bc3b71653fcca7d Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 19 Dec 2014 13:32:08 +0100 Subject: [PATCH 001/250] Added a new fuzzy module load mechanism --- src/utils/utils.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/utils/utils.py b/src/utils/utils.py index 1930b65e5..6305097e9 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -12,6 +12,7 @@ import imp import types import math import re +import importlib import textwrap import datetime import random @@ -871,6 +872,30 @@ def random_string_from_module(module): """ return random.choice(string_from_module(module)) +def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None): + """ + Import a variable based on a fuzzy path. First the literal + path will be tried, then all given defaultdirs will be + prepended to see a match is found. + + path - full or partial python path + variable - name of variable to import from module + defaultpaths - an iterable of python paths to attempt + in order if importing directly from + path does not work. + """ + paths = [path] + make_iter(defaultpaths) + for modpath in paths: + try: + mod = importlib.import_module(path) + except ImportError, ex: + if not str(ex) == "No module named %s" % path: + # this means the module was found but it + # triggers an ImportError on import. + raise ex + return getattr(mod, variable, default) + return default + def init_new_player(player): """ Helper method to call all hooks, set flags etc on a newly created @@ -1013,7 +1038,6 @@ def format_table(table, extra_space=1): for icol, col in enumerate(table)]) return ftable - def get_evennia_pids(): """ Get the currently valids PIDs (Process IDs) of the Portal and Server From 236c0d17d34e067dd04b9731ddd24208b52234a3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 19 Dec 2014 16:29:41 +0100 Subject: [PATCH 002/250] First non-tested version of moving typeclasses to proxy models. --- src/comms/comms.py | 7 +- src/objects/models.py | 1 - src/objects/objects.py | 6 +- src/players/player.py | 7 +- src/scripts/scripts.py | 5 +- src/typeclasses/managers.py | 78 ++++-- src/typeclasses/models.py | 456 +++++------------------------------ src/typeclasses/typeclass.py | 190 --------------- src/utils/idmapper/base.py | 275 ++++++++++++++++++++- 9 files changed, 403 insertions(+), 622 deletions(-) delete mode 100644 src/typeclasses/typeclass.py diff --git a/src/comms/comms.py b/src/comms/comms.py index 5e0aead3e..c7284b740 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -3,17 +3,18 @@ Default Typeclass for Comms. See objects.objects for more information on Typeclassing. """ -from src.comms import Msg, TempMsg -from src.typeclasses.typeclass import TypeClass +from src.comms.models import Msg, TempMsg, ChannelDB +from src.typeclasses.models import TypeclassBase from src.utils import logger from src.utils.utils import make_iter -class Channel(TypeClass): +class Channel(ChannelDB): """ This is the base class for all Comms. Inherit from this to create different types of communication channels. """ + __metaclass__ = TypeclassBase # helper methods, for easy overloading diff --git a/src/objects/models.py b/src/objects/models.py index b5e1302b7..2794a0cf2 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -134,7 +134,6 @@ class ObjectDB(TypedObject): contents - other objects having this object as location exits - exits from this object """ - # # ObjectDB Database model setup # diff --git a/src/objects/objects.py b/src/objects/objects.py index 56c50a3a9..990c83d10 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -16,7 +16,8 @@ they control by simply linking to a new object's user property. """ from django.conf import settings -from src.typeclasses.typeclass import TypeClass +from src.objects.models import ObjectDB +from src.typeclasses.models import TypeclassBase from src.commands import cmdset, command from src.utils.logger import log_depmsg @@ -31,11 +32,12 @@ _DA = object.__delattr__ # Base class to inherit from. # -class Object(TypeClass): +class Object(ObjectDB): """ This is the base class for all in-game objects. Inherit from this to create different types of objects in the game. """ + __metaclass__ = TypeclassBase # __init__ is only defined here in order to present docstring to API. def __init__(self, dbobj): """ diff --git a/src/players/player.py b/src/players/player.py index 4ce3c37be..30e566aee 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -13,7 +13,8 @@ instead for most things). import datetime from django.conf import settings -from src.typeclasses.typeclass import TypeClass +from src.players.models import PlayerDB +from src.typeclasses.models import TypeclassBase from src.comms.models import ChannelDB from src.utils import logger __all__ = ("Player",) @@ -23,10 +24,12 @@ _CMDSET_PLAYER = settings.CMDSET_PLAYER _CONNECT_CHANNEL = None -class Player(TypeClass): +class Player(PlayerDB): """ Base typeclass for all Players. """ + __metaclass__ = TypeclassBase + def __init__(self, dbobj): """ This is the base Typeclass for all Players. Players represent diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index f7749ffa1..78e79bf81 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -9,7 +9,7 @@ from twisted.internet.defer import Deferred, maybeDeferred from twisted.internet.task import LoopingCall from django.conf import settings from django.utils.translation import ugettext as _ -from src.typeclasses.typeclass import TypeClass +from src.typeclasses.models import TypeclassBase from src.scripts.models import ScriptDB from src.comms import channelhandler from src.utils import logger @@ -108,11 +108,12 @@ class ExtendedLoopingCall(LoopingCall): # # Base script, inherit from Script below instead. # -class ScriptBase(TypeClass): +class ScriptBase(ScriptDB): """ Base class for scripts. Don't inherit from this, inherit from the class 'Script' instead. """ + __metaclass__ = TypeclassBase # private methods def __eq__(self, other): diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index c9f62f95b..c59b4d2d3 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -13,45 +13,91 @@ _GA = object.__getattribute__ _Tag = None # -# helper functions for the TypedObjectManager. +# Decorators # def returns_typeclass_list(method): """ - Decorator: Changes return of the decorated method (which are - TypeClassed objects) into object_classes(s) instead. Will always - return a list (may be empty). + Decorator: Always returns a list, even + if it is empty. """ def func(self, *args, **kwargs): - "decorator. Returns a list." self.__doc__ = method.__doc__ - matches = make_iter(method(self, *args, **kwargs)) - return [(hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj - for dbobj in make_iter(matches)] + return list(method(self, *args, **kwargs)) return update_wrapper(func, method) def returns_typeclass(method): """ - Decorator: Will always return a single typeclassed result or None. + Decorator: Returns a single match or None """ def func(self, *args, **kwargs): - "decorator. Returns result or None." self.__doc__ = method.__doc__ - matches = method(self, *args, **kwargs) - dbobj = matches and make_iter(matches)[0] or None - if dbobj: - return (hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj - return None + query = method(self, *args, **kwargs) + return list(query)[0] if query else None return update_wrapper(func, method) # Managers - class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ Common ObjectManager for all dbobjects. """ + # common methods for all typed managers. These are used + # in other methods. Returns querysets. + + def get(self, **kwargs): + """ + Overload the standard get. This will limit itself to only + return the current typeclass. + """ + kwargs.update({"db_typeclass_path":self.model.path}) + return super(TypedObjectManager, self).get(**kwargs) + + def filter(self, **kwargs): + """ + Overload of the standard filter function. This filter will + limit itself to only the current typeclass. + """ + kwargs.update({"db_typeclass_path":self.model.path}) + return super(TypedObjectManager, self).filter(**kwargs) + + def all(self, **kwargs): + """ + Overload method to return all matches, filtering for typeclass + """ + return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path=self.model.path) + + def get_inherit(self, **kwargs): + """ + Variation of get that not only returns the current + typeclass but also all subclasses of that typeclass. + """ + paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) + for cls in self.model.__subclasses__()] + kwargs.update({"db_typeclass_path__in":paths}) + return super(TypedObjectManager, self).get(**kwargs) + + def filter_inherit(self, **kwargs): + """ + Variation of filter that allows results both from typeclass + and from subclasses of typeclass + """ + # query, including all subclasses + paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) + for cls in self.model.__subclasses__()] + kwargs.update({"db_typeclass_path__in":paths}) + return super(TypedObjectManager, self).filter(**kwargs) + + def all_inherit(self, **kwargs): + """ + Return all matches, allowing matches from all subclasses of + the typeclass. + """ + paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) + for cls in self.model.__subclasses__()] + return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path__in=paths) + # Attribute manager methods def get_attribute(self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None): diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 7757bceda..6ff1050cc 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -30,6 +30,7 @@ import sys import re import traceback import weakref +from importlib import import_module from django.db import models from django.core.exceptions import ObjectDoesNotExist @@ -738,6 +739,45 @@ class PermissionHandler(TagHandler): # #------------------------------------------------------------ +# imported for access by other +from src.utils.idmapper.base import SharedMemoryModelBase + +class TypeclassModelBase(SharedMemoryModelBase): + """ + Metaclass for typeclasses + """ + def __init__(cls, *args, **kwargs): + """ + We must define our Typeclasses as proxies. We also store the path + directly on the class, this is useful for managers. + """ + super(TypeclassModelBase, cls).__init__(*args, **kwargs) + class Meta: + proxy = True + cls.Meta = Meta + cls.typename = cls.__name__ + cls.path = "%s.%s" % (cls.__module__, cls.__name__) + + +class TypeclassBase(SharedMemoryModelBase): + """ + Metaclass which should be set for the root of model proxies + that don't define any new fields, like Object, Script etc. + """ + def __init__(cls, *args, **kwargs): + """ + We must define our Typeclasses as proxies. We also store the path + directly on the class, this is useful for managers. + """ + super(TypeclassBase, cls).__init__(*args, **kwargs) + class Meta: + # this is the important bit + proxy = True + cls.Meta = Meta + # convenience for manager methods + cls.typename = cls.__name__ + cls.path = "%s.%s" % (cls.__module__, cls.__name__) + class TypedObject(SharedMemoryModel): """ @@ -795,11 +835,23 @@ class TypedObject(SharedMemoryModel): # quick on-object typeclass cache for speed _cached_typeclass = None - # lock handler self.locks + # typeclass mechanism + + def _import_class(self, path): + path, clsname = path.rsplit(".", 1) + mod = import_module(path) + return getattr(mod, clsname) + def __init__(self, *args, **kwargs): - "We must initialize the parent first - important!" + typeclass_path = kwargs.pop("typeclass", None) super(TypedObject, self).__init__(*args, **kwargs) - _SA(self, "dbobj", self) # this allows for self-reference + if typeclass_path: + self.__class__ = self._import_class(typeclass_path) + self.db_typclass_path = typeclass_path + elif self.db_typeclass_path: + self.__class__ = self._import_class(self.db_typeclass_path) + else: + self.db_typeclass_path = "%s.%s" % (self.__module__, self.__class__.__name__) # initialize all handlers in a lazy fashion @lazy_property @@ -872,37 +924,6 @@ class TypedObject(SharedMemoryModel): def __unicode__(self): return u"%s" % _GA(self, "db_key") - def __getattribute__(self, propname): - """ - Will predominantly look for an attribute - on this object, but if not found we will - check if it might exist on the typeclass instead. Since - the typeclass refers back to the databaseobject as well, we - have to be very careful to avoid loops. - """ - try: - return _GA(self, propname) - except AttributeError: - if propname.startswith('_'): - # don't relay private/special varname lookups to the typeclass - raise AttributeError("private property %s not found on db model (typeclass not searched)." % propname) - # check if the attribute exists on the typeclass instead - # (we make sure to not incur a loop by not triggering the - # typeclass' __getattribute__, since that one would - # try to look back to this very database object.) - return _GA(_GA(self, 'typeclass'), propname) - - def _hasattr(self, obj, attrname): - """ - Loop-safe version of hasattr, to avoid running a lookup that - will be rerouted up the typeclass. Returns True/False. - """ - try: - _GA(obj, attrname) - return True - except AttributeError: - return False - #@property def __dbid_get(self): """ @@ -936,208 +957,6 @@ class TypedObject(SharedMemoryModel): raise Exception("dbref cannot be deleted!") dbref = property(__dbref_get, __dbref_set, __dbref_del) - # the latest error string will be stored here for accessing methods to access. - # It is set by _display_errmsg, which will print to log if error happens - # during server startup. - typeclass_last_errmsg = "" - - # typeclass property - #@property - def __typeclass_get(self): - """ - Getter. Allows for value = self.typeclass. - The typeclass is a class object found at self.typeclass_path; - it allows for extending the Typed object for all different - types of objects that the game needs. This property - handles loading and initialization of the typeclass on the fly. - - Note: The liberal use of _GA and __setattr__ (instead - of normal dot notation) is due to optimization: it avoids calling - the custom self.__getattribute__ more than necessary. - """ - path = _GA(self, "typeclass_path") - typeclass = _GA(self, "_cached_typeclass") - try: - if typeclass and _GA(typeclass, "path") == path: - # don't call at_init() when returning from cache - return typeclass - except AttributeError: - pass - errstring = "" - if not path: - # this means we should get the default obj without giving errors. - return _GA(self, "_get_default_typeclass")(cache=True, silent=True, save=True) - else: - # handle loading/importing of typeclasses, searching all paths. - # (self._typeclass_paths is a shortcut to settings.TYPECLASS_*_PATHS - # where '*' is either OBJECT, SCRIPT or PLAYER depending on the - # typed entities). - typeclass_paths = [path] + ["%s.%s" % (prefix, path) - for prefix in _GA(self, '_typeclass_paths')] - - for tpath in typeclass_paths: - - # try to import and analyze the result - typeclass = _GA(self, "_path_import")(tpath) - if callable(typeclass): - # we succeeded to import. Cache and return. - _SA(self, "typeclass_path", tpath) - typeclass = typeclass(self) - _SA(self, "_cached_typeclass", typeclass) - try: - typeclass.at_init() - except AttributeError: - logger.log_trace("\n%s: Error initializing typeclass %s. Using default." % (self, tpath)) - break - except Exception: - logger.log_trace() - return typeclass - elif hasattr(typeclass, '__file__'): - errstring += "\n%s seems to be just the path to a module. You need" % tpath - errstring += " to specify the actual typeclass name inside the module too." - elif typeclass: - errstring += "\n%s" % typeclass.strip() # this will hold a growing error message. - if not errstring: - errstring = "\nMake sure the path is set correctly. Paths tested:\n" - errstring += ", ".join(typeclass_paths) - errstring += "\nTypeclass code was not found or failed to load." - # If we reach this point we couldn't import any typeclasses. Return - # default. It's up to the calling method to use e.g. self.is_typeclass() - # to detect that the result is not the one asked for. - _GA(self, "_display_errmsg")(errstring.strip()) - return _GA(self, "_get_default_typeclass")(cache=False, silent=False, save=False) - - #@typeclass.deleter - def __typeclass_del(self): - "Deleter. Disallow 'del self.typeclass'" - raise Exception("The typeclass property should never be deleted, only changed in-place!") - - # typeclass property - typeclass = property(__typeclass_get, fdel=__typeclass_del) - - - def _path_import(self, path): - """ - Import a class from a python path of the - form src.objects.object.Object - """ - errstring = "" - if not path: - # this needs not be bad, it just means - # we should use defaults. - return None - try: - modpath, class_name = path.rsplit('.', 1) - module = __import__(modpath, fromlist=["none"]) - return module.__dict__[class_name] - except ImportError: - trc = sys.exc_traceback - if not trc.tb_next: - # we separate between not finding the module, and finding - # a buggy one. - pass - #errstring = "Typeclass not found trying path '%s'." % path - else: - # a bug in the module is reported normally. - trc = traceback.format_exc().strip() - errstring = "\n%sError importing '%s'." % (trc, path) - except (ValueError, TypeError): - errstring = "Malformed typeclass path '%s'." % path - except KeyError: - errstring = "No class '%s' was found in module '%s'." - errstring = errstring % (class_name, modpath) - except Exception: - trc = traceback.format_exc().strip() - errstring = "\n%sException importing '%s'." % (trc, path) - # return the error. - return errstring - - def _display_errmsg(self, message): - """ - Helper function to display error. - """ - _SA(self, "typeclass_last_errmsg", message) - if ServerConfig.objects.conf("server_starting_mode"): - print message - else: - logger.log_errmsg(message) - return - - def _get_default_typeclass(self, cache=False, silent=False, save=False): - """ - This is called when a typeclass fails to - load for whatever reason. - Overload this in different entities. - - Default operation is to load a default typeclass. - """ - defpath = _GA(self, "_default_typeclass_path") - typeclass = _GA(self, "_path_import")(defpath) - # if not silent: - # #errstring = "\n\nUsing Default class '%s'." % defpath - # _GA(self, "_display_errmsg")(errstring) - - if not callable(typeclass): - # if typeclass still doesn't exist at this point, we're in trouble. - # fall back to hardcoded core class which is wrong for e.g. - # scripts/players etc. - failpath = defpath - defpath = "src.objects.objects.Object" - typeclass = _GA(self, "_path_import")(defpath) - if not silent: - #errstring = " %s\n%s" % (typeclass, errstring) - errstring = " Default class '%s' failed to load." % failpath - errstring += "\n Using Evennia's default root '%s'." % defpath - _GA(self, "_display_errmsg")(errstring.strip()) - if not callable(typeclass): - # if this is still giving an error, Evennia is wrongly - # configured or buggy - raise Exception("CRITICAL ERROR: The final fallback typeclass %s cannot load!!" % defpath) - typeclass = typeclass(self) - if save: - _SA(self, 'db_typeclass_path', defpath) - _GA(self, 'save')() - if cache: - _SA(self, "_cached_db_typeclass_path", defpath) - - _SA(self, "_cached_typeclass", typeclass) - try: - typeclass.at_init() - except Exception: - logger.log_trace() - return typeclass - - def is_typeclass(self, typeclass, exact=True): - """ - Returns true if this object has this type - OR has a typeclass which is an subclass of - the given typeclass. This operates on the actually - loaded typeclass (this is important since a failing - typeclass may instead have its default currently loaded) - - typeclass - can be a class object or the - python path to such an object to match against. - - exact - returns true only if the object's - type is exactly this typeclass, ignoring - parents. - """ - try: - typeclass = _GA(typeclass, "path") - except AttributeError: - pass - typeclasses = [typeclass] + ["%s.%s" % (path, typeclass) - for path in _GA(self, "_typeclass_paths")] - if exact: - current_path = _GA(self.typeclass, "path") #"_GA(self, "_cached_db_typeclass_path") - return typeclass and any((current_path == typec for typec in typeclasses)) - else: - # check parent chain - return any((cls for cls in self.typeclass.__class__.mro() - if any(("%s.%s" % (_GA(cls, "__module__"), - _GA(cls, "__name__")) == typec - for typec in typeclasses)))) - # # Object manipulation methods # @@ -1428,168 +1247,3 @@ class TypedObject(SharedMemoryModel): raise Exception("Cannot delete the ndb object!") ndb = property(__ndb_get, __ndb_set, __ndb_del) -# # -# # ***** DEPRECATED METHODS BELOW ******* -# # -# -# # -# # Full attr_obj attributes. You usually access these -# # through the obj.db.attrname method. -# -# # Helper methods for attr_obj attributes -# -# def has_attribute(self, attribute_name): -# """ -# See if we have an attribute set on the object. -# -# attribute_name: (str) The attribute's name. -# """ -# logger.log_depmsg("obj.has_attribute() is deprecated. Use obj.attributes.has().") -# return _GA(self, "attributes").has(attribute_name) -# -# def set_attribute(self, attribute_name, new_value=None, lockstring=""): -# """ -# Sets an attribute on an object. Creates the attribute if need -# be. -# -# attribute_name: (str) The attribute's name. -# new_value: (python obj) The value to set the attribute to. If this is not -# a str, the object will be stored as a pickle. -# lockstring - this sets an access restriction on the attribute object. Note that -# this is normally NOT checked - use the secureattr() access method -# below to perform access-checked modification of attributes. Lock -# types checked by secureattr are 'attrread','attredit','attrcreate'. -# """ -# logger.log_depmsg("obj.set_attribute() is deprecated. Use obj.db.attr=value or obj.attributes.add().") -# _GA(self, "attributes").add(attribute_name, new_value, lockstring=lockstring) -# -# def get_attribute_obj(self, attribute_name, default=None): -# """ -# Get the actual attribute object named attribute_name -# """ -# logger.log_depmsg("obj.get_attribute_obj() is deprecated. Use obj.attributes.get(..., return_obj=True)") -# return _GA(self, "attributes").get(attribute_name, default=default, return_obj=True) -# -# def get_attribute(self, attribute_name, default=None, raise_exception=False): -# """ -# Returns the value of an attribute on an object. You may need to -# type cast the returned value from this function since the attribute -# can be of any type. Returns default if no match is found. -# -# attribute_name: (str) The attribute's name. -# default: What to return if no attribute is found -# raise_exception (bool) - raise an exception if no object exists instead of returning default. -# """ -# logger.log_depmsg("obj.get_attribute() is deprecated. Use obj.db.attr or obj.attributes.get().") -# return _GA(self, "attributes").get(attribute_name, default=default, raise_exception=raise_exception) -# -# def del_attribute(self, attribute_name, raise_exception=False): -# """ -# Removes an attribute entirely. -# -# attribute_name: (str) The attribute's name. -# raise_exception (bool) - raise exception if attribute to delete -# could not be found -# """ -# logger.log_depmsg("obj.del_attribute() is deprecated. Use del obj.db.attr or obj.attributes.remove().") -# _GA(self, "attributes").remove(attribute_name, raise_exception=raise_exception) -# -# def get_all_attributes(self): -# """ -# Returns all attributes defined on the object. -# """ -# logger.log_depmsg("obj.get_all_attributes() is deprecated. Use obj.db.all() or obj.attributes.all().") -# return _GA(self, "attributes").all() -# -# def attr(self, attribute_name=None, value=None, delete=False): -# """ -# This is a convenient wrapper for -# get_attribute, set_attribute, del_attribute -# and get_all_attributes. -# If value is None, attr will act like -# a getter, otherwise as a setter. -# set delete=True to delete the named attribute. -# -# Note that you cannot set the attribute -# value to None using this method. Use set_attribute. -# """ -# logger.log_depmsg("obj.attr() is deprecated. Use handlers obj.db or obj.attributes.") -# if attribute_name is None: -# # act as a list method -# return _GA(self, "attributes").all() -# elif delete is True: -# _GA(self, "attributes").remove(attribute_name) -# elif value is None: -# # act as a getter. -# return _GA(self, "attributes").get(attribute_name) -# else: -# # act as a setter -# self._GA(self, "attributes").add(attribute_name, value) -# -# def secure_attr(self, accessing_object, attribute_name=None, value=None, delete=False, -# default_access_read=True, default_access_edit=True, default_access_create=True): -# """ -# This is a version of attr that requires the accessing object -# as input and will use that to check eventual access locks on -# the Attribute before allowing any changes or reads. -# -# In the cases when this method wouldn't return, it will return -# True for a successful operation, None otherwise. -# -# locktypes checked on the Attribute itself: -# attrread - control access to reading the attribute value -# attredit - control edit/delete access -# locktype checked on the object on which the Attribute is/will be stored: -# attrcreate - control attribute create access (this is checked *on the object* not on the Attribute!) -# -# default_access_* defines which access is assumed if no -# suitable lock is defined on the Atttribute. -# -# """ -# logger.log_depmsg("obj.secure_attr() is deprecated. Use obj.attributes methods, giving accessing_obj keyword.") -# if attribute_name is None: -# return _GA(self, "attributes").all(accessing_obj=accessing_object, default_access=default_access_read) -# elif delete is True: -# # act as deleter -# _GA(self, "attributes").remove(attribute_name, accessing_obj=accessing_object, default_access=default_access_edit) -# elif value is None: -# # act as getter -# return _GA(self, "attributes").get(attribute_name, accessing_obj=accessing_object, default_access=default_access_read) -# else: -# # act as setter -# attr = _GA(self, "attributes").get(attribute_name, return_obj=True) -# if attr: -# # attribute already exists -# _GA(self, "attributes").add(attribute_name, value, accessing_obj=accessing_object, default_access=default_access_edit) -# else: -# # creating a new attribute - check access on storing object! -# _GA(self, "attributes").add(attribute_name, value, accessing_obj=accessing_object, default_access=default_access_create) -# -# def nattr(self, attribute_name=None, value=None, delete=False): -# """ -# This allows for assigning non-persistent data on the object using -# a method call. Will return None if trying to access a non-existing property. -# """ -# logger.log_depmsg("obj.nattr() is deprecated. Use obj.nattributes instead.") -# if attribute_name is None: -# # act as a list method -# if callable(self.ndb.all): -# return self.ndb.all() -# else: -# return [val for val in self.ndb.__dict__.keys() -# if not val.startswith['_']] -# elif delete is True: -# if hasattr(self.ndb, attribute_name): -# _DA(_GA(self, "ndb"), attribute_name) -# elif value is None: -# # act as a getter. -# if hasattr(self.ndb, attribute_name): -# _GA(_GA(self, "ndb"), attribute_name) -# else: -# return None -# else: -# # act as a setter -# _SA(self.ndb, attribute_name, value) -# -# - diff --git a/src/typeclasses/typeclass.py b/src/typeclasses/typeclass.py deleted file mode 100644 index a8fe2a02d..000000000 --- a/src/typeclasses/typeclass.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -A typeclass is the companion of a TypedObject django model. -It 'decorates' the model without actually having to add new -fields to the model - transparently storing data onto its -associated model without the admin/user just having to deal -with a 'normal' Python class. The only restrictions is that -the typeclass must inherit from TypeClass and not reimplement -the get/setters defined below. There are also a few properties -that are protected, so as to not overwrite property names -used by the typesystem or django itself. -""" - -from src.utils.logger import log_trace, log_errmsg - -__all__ = ("TypeClass",) - -# these are called so many times it's worth to avoid lookup calls -_GA = object.__getattribute__ -_SA = object.__setattr__ -_DA = object.__delattr__ - -# To ensure the sanity of the model, there are a -# few property names we won't allow the admin to -# set on the typeclass just like that. Note that these are *not* related -# to *in-game* safety (if you can edit typeclasses you have -# full access anyway), so no protection against changing -# e.g. 'locks' or 'permissions' should go here. -PROTECTED = ('id', 'dbobj', 'db', 'ndb', 'objects', 'typeclass', 'db_player', - 'attr', 'save', 'delete', 'db_model_name','attribute_class', - 'typeclass_paths') - - -# If this is true, all non-protected property assignments -# are directly stored to a database attribute - -class MetaTypeClass(type): - """ - This metaclass just makes sure the class object gets - printed in a nicer way (it might end up having no name at all - otherwise due to the magics being done with get/setattribute). - """ - def __init__(mcs, *args, **kwargs): - """ - Adds some features to typeclassed objects - """ - super(MetaTypeClass, mcs).__init__(*args, **kwargs) - mcs.typename = mcs.__name__ - mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__) - - def __str__(cls): - return "%s" % cls.__name__ - - -class TypeClass(object): - """ - This class implements a 'typeclass' object. This is connected - to a database object inheriting from TypedObject. - the TypeClass allows for all customization. - Most of the time this means that the admin never has to - worry about database access but only deal with extending - TypeClasses to create diverse objects in the game. - - The ObjectType class has all functionality for wrapping a - database object transparently. - - It's up to its child classes to implement eventual custom hooks - and other functions called by the engine. - - """ - __metaclass__ = MetaTypeClass - - def __init__(self, dbobj): - """ - Initialize the object class. There are two ways to call this class. - o = object_class(dbobj) : this is used to initialize dbobj with the - class name - o = dbobj.object_class(dbobj) : this is used when dbobj.object_class - is already set. - - """ - # typecheck of dbobj - we can't allow it to be added here - # unless it's really a TypedObject. - dbobj_cls = _GA(dbobj, '__class__') - dbobj_mro = _GA(dbobj_cls, '__mro__') - if not any('src.typeclasses.models.TypedObject' in str(mro) for mro in dbobj_mro): - raise Exception("dbobj is not a TypedObject: %s: %s" % (dbobj_cls, dbobj_mro)) - - # we should always be able to use dbobj/typeclass to get back an object of the desired type - _SA(self, 'dbobj', dbobj) - _SA(self, 'typeclass', self) - - def __getattribute__(self, propname): - """ - Change the normal property access to - transparently include the properties on - self.dbobj. Note that dbobj properties have - priority, so if you define a same-named - property on the class, it will NOT be - accessible through getattr. - """ - if propname.startswith('__') and propname.endswith('__'): - # python specials are parsed as-is (otherwise things like - # isinstance() fail to identify the typeclass) - return _GA(self, propname) - #print "get %s (dbobj:%s)" % (propname, type(dbobj)) - try: - return _GA(self, propname) - except AttributeError: - try: - dbobj = _GA(self, 'dbobj') - except AttributeError: - log_trace("Typeclass CRITICAL ERROR! dbobj not found for Typeclass %s!" % self) - raise - try: - return _GA(dbobj, propname) - except AttributeError: - string = "Object: '%s' not found on %s(#%s), nor on its typeclass %s." - raise AttributeError(string % (propname, dbobj, _GA(dbobj, "dbid"), _GA(dbobj, "typeclass_path"))) - - def __setattr__(self, propname, value): - """ - Transparently save data. Use property on Typeclass only if - that property is already defined, otherwise relegate to the - dbobj object in all situations. Note that this does not - necessarily mean storing it to the database. - """ - #print "set %s -> %s" % (propname, value) - if propname in PROTECTED: - string = "%s: '%s' is a protected attribute name." - string += " (protected: [%s])" % (", ".join(PROTECTED)) - log_errmsg(string % (self.name, propname)) - return - try: - _GA(self, propname) - _SA(self, propname, value) - except AttributeError: - try: - dbobj = _GA(self, 'dbobj') - except AttributeError: - dbobj = None - if dbobj: - _SA(dbobj, propname, value) - else: - # only as a last resort do we save on the typeclass object - _SA(self, propname, value) - - def __eq__(self, other): - """ - dbobj-recognized comparison - """ - try: - return _GA(_GA(self, "dbobj"), "dbid") == _GA(_GA(other, "dbobj"), "dbid") - except AttributeError: - return id(self) == id(other) - - def __delattr__(self, propname): - """ - Transparently deletes data from the typeclass or dbobj by first - searching on the typeclass, secondly on the dbobj.db. - Will not allow deletion of properties stored directly on dbobj. - """ - if propname in PROTECTED: - string = "%s: '%s' is a protected attribute name." - string += " (protected: [%s])" % (", ".join(PROTECTED)) - log_errmsg(string % (self.name, propname)) - return - - try: - _DA(self, propname) - except AttributeError: - # not on typeclass, try to delete on db/ndb - try: - dbobj = _GA(self, 'dbobj') - except AttributeError: - log_trace("This is probably due to an unsafe reload.") - return # ignore delete - try: - dbobj.del_attribute(propname, raise_exception=True) - except AttributeError: - string = "Object: '%s' not found on %s(#%s), nor on its typeclass %s." - raise AttributeError(string % (propname, dbobj, - dbobj.dbid, - dbobj.typeclass_path,)) - - def __str__(self): - "represent the object" - return self.key - - def __unicode__(self): - return u"%s" % self.key diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 122c5b7d2..5922384ee 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -22,6 +22,21 @@ from manager import SharedMemoryManager AUTO_FLUSH_MIN_INTERVAL = 60.0 * 5 # at least 5 mins between cache flushes +# django patch imports +import copy +import sys +from django.apps import apps +from django.db.models.base import subclass_exception +import warnings +from django.db.models.options import Options +from django.utils.deprecation import RemovedInDjango19Warning +from django.core.exceptions import (ObjectDoesNotExist, + MultipleObjectsReturned, FieldError) +from django.apps.config import MODELS_MODULE_NAME +from django.db.models.fields.related import OneToOneField +#/ django patch imports + + _GA = object.__getattribute__ _SA = object.__setattr__ _DA = object.__delattr__ @@ -72,7 +87,7 @@ class SharedMemoryModelBase(ModelBase): cls._idmapper_recache_protection = False super(SharedMemoryModelBase, cls)._prepare() - def __new__(cls, classname, bases, classdict, *args, **kwargs): + def __new__(cls, name, bases, attrs): """ Field shortcut creation: Takes field names db_* and creates property wrappers named without the db_ prefix. So db_key -> key @@ -158,23 +173,273 @@ class SharedMemoryModelBase(ModelBase): fset = lambda cls, val: _set(cls, fieldname, val) fdel = lambda cls: _del(cls, fieldname) if editable else _del_nonedit(cls,fieldname) # assigning - classdict[wrappername] = property(fget, fset, fdel) + attrs[wrappername] = property(fget, fset, fdel) #type(cls).__setattr__(cls, wrappername, property(fget, fset, fdel))#, doc)) # exclude some models that should not auto-create wrapper fields if cls.__name__ in ("ServerConfig", "TypeNick"): return # dynamically create the wrapper properties for all fields not already handled (manytomanyfields are always handlers) - for fieldname, field in ((fname, field) for fname, field in classdict.items() + for fieldname, field in ((fname, field) for fname, field in attrs.items() if fname.startswith("db_") and type(field).__name__ != "ManyToManyField"): foreignkey = type(field).__name__ == "ForeignKey" #print fieldname, type(field).__name__, field wrappername = "dbid" if fieldname == "id" else fieldname.replace("db_", "", 1) - if wrappername not in classdict: + if wrappername not in attrs: # makes sure not to overload manually created wrappers on the model #print "wrapping %s -> %s" % (fieldname, wrappername) create_wrapper(cls, fieldname, wrappername, editable=field.editable, foreignkey=foreignkey) - return super(SharedMemoryModelBase, cls).__new__(cls, classname, bases, classdict, *args, **kwargs) + + + # django patch + # Evennia mod, based on Django Ticket #11560: https://code.djangoproject.com/ticket/11560 + # The actual patch is small and further down. + super_new = super(ModelBase, cls).__new__ + + # Also ensure initialization is only performed for subclasses of Model + # (excluding Model class itself). + parents = [b for b in bases if isinstance(b, ModelBase)] + if not parents: + return super_new(cls, name, bases, attrs) + + # Create the class. + module = attrs.pop('__module__') + new_class = super_new(cls, name, bases, {'__module__': module}) + attr_meta = attrs.pop('Meta', None) + abstract = getattr(attr_meta, 'abstract', False) + if not attr_meta: + meta = getattr(new_class, 'Meta', None) + else: + meta = attr_meta + base_meta = getattr(new_class, '_meta', None) + + # Look for an application configuration to attach the model to. + app_config = apps.get_containing_app_config(module) + + if getattr(meta, 'app_label', None) is None: + + if app_config is None: + # If the model is imported before the configuration for its + # application is created (#21719), or isn't in an installed + # application (#21680), use the legacy logic to figure out the + # app_label by looking one level up from the package or module + # named 'models'. If no such package or module exists, fall + # back to looking one level up from the module this model is + # defined in. + + # For 'django.contrib.sites.models', this would be 'sites'. + # For 'geo.models.places' this would be 'geo'. + + msg = ( + "Model class %s.%s doesn't declare an explicit app_label " + "and either isn't in an application in INSTALLED_APPS or " + "else was imported before its application was loaded. " % + (module, name)) + if abstract: + msg += "Its app_label will be set to None in Django 1.9." + else: + msg += "This will no longer be supported in Django 1.9." + warnings.warn(msg, RemovedInDjango19Warning, stacklevel=2) + + model_module = sys.modules[new_class.__module__] + package_components = model_module.__name__.split('.') + package_components.reverse() # find the last occurrence of 'models' + try: + app_label_index = package_components.index(MODELS_MODULE_NAME) + 1 + except ValueError: + app_label_index = 1 + kwargs = {"app_label": package_components[app_label_index]} + + else: + kwargs = {"app_label": app_config.label} + + else: + kwargs = {} + + new_class.add_to_class('_meta', Options(meta, **kwargs)) + if not abstract: + new_class.add_to_class( + 'DoesNotExist', + subclass_exception( + str('DoesNotExist'), + tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,), + module, + attached_to=new_class)) + new_class.add_to_class( + 'MultipleObjectsReturned', + subclass_exception( + str('MultipleObjectsReturned'), + tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,), + module, + attached_to=new_class)) + if base_meta and not base_meta.abstract: + # Non-abstract child classes inherit some attributes from their + # non-abstract parent (unless an ABC comes before it in the + # method resolution order). + if not hasattr(meta, 'ordering'): + new_class._meta.ordering = base_meta.ordering + if not hasattr(meta, 'get_latest_by'): + new_class._meta.get_latest_by = base_meta.get_latest_by + + is_proxy = new_class._meta.proxy + + # If the model is a proxy, ensure that the base class + # hasn't been swapped out. + if is_proxy and base_meta and base_meta.swapped: + raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped)) + + if getattr(new_class, '_default_manager', None): + if not is_proxy: + # Multi-table inheritance doesn't inherit default manager from + # parents. + new_class._default_manager = None + new_class._base_manager = None + else: + # Proxy classes do inherit parent's default manager, if none is + # set explicitly. + new_class._default_manager = new_class._default_manager._copy_to_model(new_class) + new_class._base_manager = new_class._base_manager._copy_to_model(new_class) + + # Add all attributes to the class. + for obj_name, obj in attrs.items(): + new_class.add_to_class(obj_name, obj) + + # All the fields of any type declared on this model + new_fields = ( + new_class._meta.local_fields + + new_class._meta.local_many_to_many + + new_class._meta.virtual_fields + ) + field_names = set(f.name for f in new_fields) + + # Basic setup for proxy models. + if is_proxy: + base = None + for parent in [kls for kls in parents if hasattr(kls, '_meta')]: + if parent._meta.abstract: + if parent._meta.fields: + raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) + else: + continue + # Evennia mod, based on Django Ticket #11560: https://code.djangoproject.com/ticket/11560 + # This allows multiple inheritance for proxy models + while parent._meta.proxy: + parent = parent._meta.proxy_for_model + if base is not None and base is not parent: + #if base is not None: + raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name) + else: + base = parent + if base is None: + raise TypeError("Proxy model '%s' has no non-abstract model base class." % name) + new_class._meta.setup_proxy(base) + new_class._meta.concrete_model = base._meta.concrete_model + else: + new_class._meta.concrete_model = new_class + + # Collect the parent links for multi-table inheritance. + parent_links = {} + for base in reversed([new_class] + parents): + # Conceptually equivalent to `if base is Model`. + if not hasattr(base, '_meta'): + continue + # Skip concrete parent classes. + if base != new_class and not base._meta.abstract: + continue + # Locate OneToOneField instances. + for field in base._meta.local_fields: + if isinstance(field, OneToOneField): + parent_links[field.rel.to] = field + + # Do the appropriate setup for any model parents. + for base in parents: + original_base = base + if not hasattr(base, '_meta'): + # Things without _meta aren't functional models, so they're + # uninteresting parents. + continue + + parent_fields = base._meta.local_fields + base._meta.local_many_to_many + # Check for clashes between locally declared fields and those + # on the base classes (we cannot handle shadowed fields at the + # moment). + for field in parent_fields: + if field.name in field_names: + raise FieldError( + 'Local field %r in class %r clashes ' + 'with field of similar name from ' + 'base class %r' % (field.name, name, base.__name__) + ) + if not base._meta.abstract: + # Concrete classes... + base = base._meta.concrete_model + if base in parent_links: + field = parent_links[base] + elif not is_proxy: + attr_name = '%s_ptr' % base._meta.model_name + field = OneToOneField(base, name=attr_name, + auto_created=True, parent_link=True) + # Only add the ptr field if it's not already present; + # e.g. migrations will already have it specified + if not hasattr(new_class, attr_name): + new_class.add_to_class(attr_name, field) + else: + field = None + new_class._meta.parents[base] = field + else: + # .. and abstract ones. + for field in parent_fields: + new_class.add_to_class(field.name, copy.deepcopy(field)) + + # Pass any non-abstract parent classes onto child. + new_class._meta.parents.update(base._meta.parents) + + # Inherit managers from the abstract base classes. + new_class.copy_managers(base._meta.abstract_managers) + + # Proxy models inherit the non-abstract managers from their base, + # unless they have redefined any of them. + if is_proxy: + new_class.copy_managers(original_base._meta.concrete_managers) + + # Inherit virtual fields (like GenericForeignKey) from the parent + # class + for field in base._meta.virtual_fields: + if base._meta.abstract and field.name in field_names: + raise FieldError( + 'Local field %r in class %r clashes ' + 'with field of similar name from ' + 'abstract base class %r' % (field.name, name, base.__name__) + ) + new_class.add_to_class(field.name, copy.deepcopy(field)) + + if abstract: + # Abstract base models can't be instantiated and don't appear in + # the list of models for an app. We do the final setup for them a + # little differently from normal models. + attr_meta.abstract = False + new_class.Meta = attr_meta + return new_class + + new_class._prepare() + new_class._meta.apps.register_model(new_class._meta.app_label, new_class) + return new_class + +class TypeclassModelBase(SharedMemoryModelBase): + """ + Metaclass for typeclasses + """ + def __init__(cls, *args, **kwargs): + """ + We must define our Typeclasses as proxies. We also store the path + directly on the class, this is useful for managers. + """ + super(TypeclassModelBase, cls).__init__(*args, **kwargs) + class Meta: + proxy = True + cls.Meta = Meta + cls.typename = cls.__name__ + cls.path = "%s.%s" % (cls.__module__, cls.__name__) class SharedMemoryModel(Model): From 3adeade69abf884e9fa2342db274d9b234ef915c Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 19 Dec 2014 16:41:20 +0100 Subject: [PATCH 003/250] Some errors in the proxy setting. --- src/typeclasses/managers.py | 7 ++++--- src/utils/idmapper/base.py | 12 +----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index c59b4d2d3..fd07bdd85 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -51,6 +51,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): Overload the standard get. This will limit itself to only return the current typeclass. """ + print self.model kwargs.update({"db_typeclass_path":self.model.path}) return super(TypedObjectManager, self).get(**kwargs) @@ -68,7 +69,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path=self.model.path) - def get_inherit(self, **kwargs): + def get_family(self, **kwargs): """ Variation of get that not only returns the current typeclass but also all subclasses of that typeclass. @@ -78,7 +79,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): kwargs.update({"db_typeclass_path__in":paths}) return super(TypedObjectManager, self).get(**kwargs) - def filter_inherit(self, **kwargs): + def filter_family(self, **kwargs): """ Variation of filter that allows results both from typeclass and from subclasses of typeclass @@ -89,7 +90,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): kwargs.update({"db_typeclass_path__in":paths}) return super(TypedObjectManager, self).filter(**kwargs) - def all_inherit(self, **kwargs): + def all_family(self, **kwargs): """ Return all matches, allowing matches from all subclasses of the typeclass. diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 5922384ee..b26c53ff2 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -425,23 +425,13 @@ class SharedMemoryModelBase(ModelBase): new_class._meta.apps.register_model(new_class._meta.app_label, new_class) return new_class -class TypeclassModelBase(SharedMemoryModelBase): - """ - Metaclass for typeclasses - """ def __init__(cls, *args, **kwargs): """ - We must define our Typeclasses as proxies. We also store the path - directly on the class, this is useful for managers. + This is for the typeclass system. """ - super(TypeclassModelBase, cls).__init__(*args, **kwargs) - class Meta: - proxy = True - cls.Meta = Meta cls.typename = cls.__name__ cls.path = "%s.%s" % (cls.__module__, cls.__name__) - class SharedMemoryModel(Model): # CL: setting abstract correctly to allow subclasses to inherit the default # manager. From 08d0442f9cf5350b3b4cafe55fd08c7b6cf8606d Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 19 Dec 2014 16:56:28 +0100 Subject: [PATCH 004/250] Strange issue with __init__, proxy implementation not working at the moment. --- src/typeclasses/models.py | 21 ++------------------- src/utils/idmapper/base.py | 1 + 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 6ff1050cc..961e4a0b2 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -742,23 +742,6 @@ class PermissionHandler(TagHandler): # imported for access by other from src.utils.idmapper.base import SharedMemoryModelBase -class TypeclassModelBase(SharedMemoryModelBase): - """ - Metaclass for typeclasses - """ - def __init__(cls, *args, **kwargs): - """ - We must define our Typeclasses as proxies. We also store the path - directly on the class, this is useful for managers. - """ - super(TypeclassModelBase, cls).__init__(*args, **kwargs) - class Meta: - proxy = True - cls.Meta = Meta - cls.typename = cls.__name__ - cls.path = "%s.%s" % (cls.__module__, cls.__name__) - - class TypeclassBase(SharedMemoryModelBase): """ Metaclass which should be set for the root of model proxies @@ -775,8 +758,8 @@ class TypeclassBase(SharedMemoryModelBase): proxy = True cls.Meta = Meta # convenience for manager methods - cls.typename = cls.__name__ - cls.path = "%s.%s" % (cls.__module__, cls.__name__) + #cls.typename = cls.__name__ + #cls.path = "%s.%s" % (cls.__module__, cls.__name__) class TypedObject(SharedMemoryModel): diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index b26c53ff2..231f00583 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -429,6 +429,7 @@ class SharedMemoryModelBase(ModelBase): """ This is for the typeclass system. """ + super(SharedMemoryModelBase, cls).__init__(*args, **kwargs) cls.typename = cls.__name__ cls.path = "%s.%s" % (cls.__module__, cls.__name__) From 8314d8ba5e37b52afad8b1b993895b6effe26560 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 20 Dec 2014 17:03:34 +0100 Subject: [PATCH 005/250] Now solving issues with faulty imports. Still nothing functional. --- src/comms/comms.py | 14 +++++++++++++- src/objects/objects.py | 18 +++++++++++++++--- src/players/models.py | 5 ----- src/players/player.py | 18 ++++++++++++++---- src/scripts/scripts.py | 19 ++++++++++++++++--- src/typeclasses/managers.py | 2 +- src/typeclasses/models.py | 35 +++++++++++++++++------------------ src/utils/idmapper/base.py | 3 +-- 8 files changed, 77 insertions(+), 37 deletions(-) diff --git a/src/comms/comms.py b/src/comms/comms.py index c7284b740..896555515 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -14,7 +14,19 @@ class Channel(ChannelDB): This is the base class for all Comms. Inherit from this to create different types of communication channels. """ - __metaclass__ = TypeclassBase + + def __new__(cls, *args, **kwargs): + """ + We must define our Typeclasses as proxies. We also store the path + directly on the class, this is useful for managers. + """ + if hasattr(cls, "Meta"): + cls.Meta.proxy = True + else: + class Meta: + proxy = True + cls.Meta = Meta + return super(TypeclassBase, cls).__new__(*args, **kwargs) # helper methods, for easy overloading diff --git a/src/objects/objects.py b/src/objects/objects.py index 990c83d10..32b7848af 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -37,9 +37,21 @@ class Object(ObjectDB): This is the base class for all in-game objects. Inherit from this to create different types of objects in the game. """ - __metaclass__ = TypeclassBase + def __new__(cls, *args, **kwargs): + """ + We must define our Typeclasses as proxies. We also store the path + directly on the class, this is useful for managers. + """ + if hasattr(cls, "Meta"): + cls.Meta.proxy = True + else: + class Meta: + proxy = True + cls.Meta = Meta + return super(Object, cls).__new__(*args, **kwargs) + # __init__ is only defined here in order to present docstring to API. - def __init__(self, dbobj): + def __init__(self): """ This is the root typeclass object, representing all entities that have an actual presence in-game. Objects generally have a @@ -190,7 +202,7 @@ class Object(ObjectDB): this object speaks """ - super(Object, self).__init__(dbobj) + super(Object, self).__init__() ## methods inherited from the database object (overload them here) diff --git a/src/players/models.py b/src/players/models.py index d8c341b81..86d63831c 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -162,11 +162,6 @@ class PlayerDB(TypedObject, AbstractUser): _GA(self, "save")() cmdset_storage = property(cmdset_storage_get, cmdset_storage_set, cmdset_storage_del) - class Meta: - "Define Django meta options" - verbose_name = "Player" - verbose_name_plural = "Players" - # # PlayerDB main class properties and methods # diff --git a/src/players/player.py b/src/players/player.py index 30e566aee..52fa9c7da 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -14,7 +14,6 @@ instead for most things). import datetime from django.conf import settings from src.players.models import PlayerDB -from src.typeclasses.models import TypeclassBase from src.comms.models import ChannelDB from src.utils import logger __all__ = ("Player",) @@ -28,9 +27,20 @@ class Player(PlayerDB): """ Base typeclass for all Players. """ - __metaclass__ = TypeclassBase + def __new__(cls, *args, **kwargs): + """ + We must define our Typeclasses as proxies. We also store the path + directly on the class, this is useful for managers. + """ + if hasattr(cls, "Meta"): + cls.Meta.proxy = True + else: + class Meta: + proxy = True + cls.Meta = Meta + return super(Player, cls).__new__(*args, **kwargs) - def __init__(self, dbobj): + def __init__(self): """ This is the base Typeclass for all Players. Players represent the person playing the game and tracks account info, password @@ -104,7 +114,7 @@ class Player(PlayerDB): at_server_shutdown() """ - super(Player, self).__init__(dbobj) + super(Player, self).__init__() ## methods inherited from database model diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index 78e79bf81..e33987389 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -113,9 +113,22 @@ class ScriptBase(ScriptDB): Base class for scripts. Don't inherit from this, inherit from the class 'Script' instead. """ - __metaclass__ = TypeclassBase + #__metaclass__ = TypeclassBase # private methods + def __new__(cls, *args, **kwargs): + """ + We must define our Typeclasses as proxies. We also store the path + directly on the class, this is useful for managers. + """ + if hasattr(cls, "Meta"): + cls.Meta.proxy = True + else: + class Meta: + proxy = True + cls.Meta = Meta + return super(ScriptBase, cls).__new__(*args, **kwargs) + def __eq__(self, other): """ This has to be located at this level, having it in the @@ -355,7 +368,7 @@ class Script(ScriptBase): the hooks called by the script machinery. """ - def __init__(self, dbobj): + def __init__(self): """ This is the base TypeClass for all Scripts. Scripts describe events, timers and states in game, they can have a time component or describe @@ -442,7 +455,7 @@ class Script(ScriptBase): """ - super(Script, self).__init__(dbobj) + super(Script, self).__init__() def at_script_creation(self): """ diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index fd07bdd85..1fa7bd88b 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -8,7 +8,7 @@ from django.db.models import Q from src.utils import idmapper from src.utils.utils import make_iter, variable_from_module -__all__ = ("AttributeManager", "TypedObjectManager") +__all__ = ("TypedObjectManager", ) _GA = object.__getattribute__ _Tag = None diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 961e4a0b2..cd373be79 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -742,24 +742,23 @@ class PermissionHandler(TagHandler): # imported for access by other from src.utils.idmapper.base import SharedMemoryModelBase -class TypeclassBase(SharedMemoryModelBase): - """ - Metaclass which should be set for the root of model proxies - that don't define any new fields, like Object, Script etc. - """ - def __init__(cls, *args, **kwargs): - """ - We must define our Typeclasses as proxies. We also store the path - directly on the class, this is useful for managers. - """ - super(TypeclassBase, cls).__init__(*args, **kwargs) - class Meta: - # this is the important bit - proxy = True - cls.Meta = Meta - # convenience for manager methods - #cls.typename = cls.__name__ - #cls.path = "%s.%s" % (cls.__module__, cls.__name__) +#class TypeclassBase(SharedMemoryModelBase): +# """ +# Metaclass which should be set for the root of model proxies +# that don't define any new fields, like Object, Script etc. +# """ +# def __new__(cls, name, bases, attrs): +# """ +# We must define our Typeclasses as proxies. We also store the path +# directly on the class, this is useful for managers. +# """ +# if hasattr(cls, "Meta"): +# cls.Meta.proxy = True +# else: +# class Meta: +# proxy = True +# cls.Meta = Meta +# return super(TypeclassBase, cls).__new__(name, bases, attrs) class TypedObject(SharedMemoryModel): diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 231f00583..259d38721 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -95,7 +95,6 @@ class SharedMemoryModelBase(ModelBase): already has a wrapper of the given name, the automatic creation is skipped. Note: Remember to document this auto-wrapping in the class header, this could seem very much like magic to the user otherwise. """ - def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False): "Helper method to create property wrappers with unique names (must be in separate call)" def _get(cls, fname): @@ -190,7 +189,6 @@ class SharedMemoryModelBase(ModelBase): #print "wrapping %s -> %s" % (fieldname, wrappername) create_wrapper(cls, fieldname, wrappername, editable=field.editable, foreignkey=foreignkey) - # django patch # Evennia mod, based on Django Ticket #11560: https://code.djangoproject.com/ticket/11560 # The actual patch is small and further down. @@ -432,6 +430,7 @@ class SharedMemoryModelBase(ModelBase): super(SharedMemoryModelBase, cls).__init__(*args, **kwargs) cls.typename = cls.__name__ cls.path = "%s.%s" % (cls.__module__, cls.__name__) + print "shared __init__", cls class SharedMemoryModel(Model): # CL: setting abstract correctly to allow subclasses to inherit the default From 32e44dceab281785b83e743922fcd2fc87b13009 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 20 Dec 2014 18:30:39 +0100 Subject: [PATCH 006/250] Made the proxy typeclass system work in principle, using a wrapper of the __new__ method for the class. --- src/comms/comms.py | 15 +------------- src/objects/objects.py | 14 +------------ src/players/player.py | 14 +------------ src/scripts/scripts.py | 17 +-------------- src/typeclasses/managers.py | 1 - src/typeclasses/models.py | 2 +- src/utils/idmapper/base.py | 41 +++++++++++++++++++------------------ 7 files changed, 26 insertions(+), 78 deletions(-) diff --git a/src/comms/comms.py b/src/comms/comms.py index 896555515..2c878058b 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -4,7 +4,6 @@ Default Typeclass for Comms. See objects.objects for more information on Typeclassing. """ from src.comms.models import Msg, TempMsg, ChannelDB -from src.typeclasses.models import TypeclassBase from src.utils import logger from src.utils.utils import make_iter @@ -14,19 +13,7 @@ class Channel(ChannelDB): This is the base class for all Comms. Inherit from this to create different types of communication channels. """ - - def __new__(cls, *args, **kwargs): - """ - We must define our Typeclasses as proxies. We also store the path - directly on the class, this is useful for managers. - """ - if hasattr(cls, "Meta"): - cls.Meta.proxy = True - else: - class Meta: - proxy = True - cls.Meta = Meta - return super(TypeclassBase, cls).__new__(*args, **kwargs) + _is_typeclass = True # helper methods, for easy overloading diff --git a/src/objects/objects.py b/src/objects/objects.py index 32b7848af..a989bb0af 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -17,7 +17,6 @@ they control by simply linking to a new object's user property. from django.conf import settings from src.objects.models import ObjectDB -from src.typeclasses.models import TypeclassBase from src.commands import cmdset, command from src.utils.logger import log_depmsg @@ -37,18 +36,7 @@ class Object(ObjectDB): This is the base class for all in-game objects. Inherit from this to create different types of objects in the game. """ - def __new__(cls, *args, **kwargs): - """ - We must define our Typeclasses as proxies. We also store the path - directly on the class, this is useful for managers. - """ - if hasattr(cls, "Meta"): - cls.Meta.proxy = True - else: - class Meta: - proxy = True - cls.Meta = Meta - return super(Object, cls).__new__(*args, **kwargs) + _is_typeclass = True # __init__ is only defined here in order to present docstring to API. def __init__(self): diff --git a/src/players/player.py b/src/players/player.py index 52fa9c7da..8a7a4761d 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -22,23 +22,11 @@ _MULTISESSION_MODE = settings.MULTISESSION_MODE _CMDSET_PLAYER = settings.CMDSET_PLAYER _CONNECT_CHANNEL = None - class Player(PlayerDB): """ Base typeclass for all Players. """ - def __new__(cls, *args, **kwargs): - """ - We must define our Typeclasses as proxies. We also store the path - directly on the class, this is useful for managers. - """ - if hasattr(cls, "Meta"): - cls.Meta.proxy = True - else: - class Meta: - proxy = True - cls.Meta = Meta - return super(Player, cls).__new__(*args, **kwargs) + _is_typeclass = True def __init__(self): """ diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index e33987389..a301a7f64 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -9,7 +9,6 @@ from twisted.internet.defer import Deferred, maybeDeferred from twisted.internet.task import LoopingCall from django.conf import settings from django.utils.translation import ugettext as _ -from src.typeclasses.models import TypeclassBase from src.scripts.models import ScriptDB from src.comms import channelhandler from src.utils import logger @@ -113,21 +112,7 @@ class ScriptBase(ScriptDB): Base class for scripts. Don't inherit from this, inherit from the class 'Script' instead. """ - #__metaclass__ = TypeclassBase - # private methods - - def __new__(cls, *args, **kwargs): - """ - We must define our Typeclasses as proxies. We also store the path - directly on the class, this is useful for managers. - """ - if hasattr(cls, "Meta"): - cls.Meta.proxy = True - else: - class Meta: - proxy = True - cls.Meta = Meta - return super(ScriptBase, cls).__new__(*args, **kwargs) + _is_typeclass = True def __eq__(self, other): """ diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index 1fa7bd88b..95cee9453 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -51,7 +51,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): Overload the standard get. This will limit itself to only return the current typeclass. """ - print self.model kwargs.update({"db_typeclass_path":self.model.path}) return super(TypedObjectManager, self).get(**kwargs) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index cd373be79..4461c847f 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -740,7 +740,7 @@ class PermissionHandler(TagHandler): #------------------------------------------------------------ # imported for access by other -from src.utils.idmapper.base import SharedMemoryModelBase +#from src.utils.idmapper.base import SharedMemoryModelBase #class TypeclassBase(SharedMemoryModelBase): # """ diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 259d38721..47788059b 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -30,8 +30,7 @@ from django.db.models.base import subclass_exception import warnings from django.db.models.options import Options from django.utils.deprecation import RemovedInDjango19Warning -from django.core.exceptions import (ObjectDoesNotExist, - MultipleObjectsReturned, FieldError) +from django.core.exceptions import MultipleObjectsReturned from django.apps.config import MODELS_MODULE_NAME from django.db.models.fields.related import OneToOneField #/ django patch imports @@ -95,6 +94,15 @@ class SharedMemoryModelBase(ModelBase): already has a wrapper of the given name, the automatic creation is skipped. Note: Remember to document this auto-wrapping in the class header, this could seem very much like magic to the user otherwise. """ + # set up the typeclass handling only if a variable _is_typeclass is set on the class + if "_is_typeclass" in attrs: + if "Meta" in attrs: + attrs["Meta"].proxy = True + else: + class Meta: + proxy = True + attrs["Meta"] = Meta + def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False): "Helper method to create property wrappers with unique names (must be in separate call)" def _get(cls, fname): @@ -189,9 +197,8 @@ class SharedMemoryModelBase(ModelBase): #print "wrapping %s -> %s" % (fieldname, wrappername) create_wrapper(cls, fieldname, wrappername, editable=field.editable, foreignkey=foreignkey) - # django patch - # Evennia mod, based on Django Ticket #11560: https://code.djangoproject.com/ticket/11560 - # The actual patch is small and further down. + # patch start + super_new = super(ModelBase, cls).__new__ # Also ensure initialization is only performed for subclasses of Model @@ -319,16 +326,14 @@ class SharedMemoryModelBase(ModelBase): raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) else: continue - # Evennia mod, based on Django Ticket #11560: https://code.djangoproject.com/ticket/11560 - # This allows multiple inheritance for proxy models - while parent._meta.proxy: - parent = parent._meta.proxy_for_model - if base is not None and base is not parent: - #if base is not None: + if base is not None: raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name) else: base = parent - if base is None: + #if base is None: # patch + while parent._meta.proxy: # patch + parent = parent._meta.proxy_for_model # patch + if base is not None and base is not parent: # patch raise TypeError("Proxy model '%s' has no non-abstract model base class." % name) new_class._meta.setup_proxy(base) new_class._meta.concrete_model = base._meta.concrete_model @@ -423,14 +428,10 @@ class SharedMemoryModelBase(ModelBase): new_class._meta.apps.register_model(new_class._meta.app_label, new_class) return new_class - def __init__(cls, *args, **kwargs): - """ - This is for the typeclass system. - """ - super(SharedMemoryModelBase, cls).__init__(*args, **kwargs) - cls.typename = cls.__name__ - cls.path = "%s.%s" % (cls.__module__, cls.__name__) - print "shared __init__", cls + + # /patch end + #return super(SharedMemoryModelBase, cls).__new__(cls, name, bases, attrs, *args, **kwargs) + class SharedMemoryModel(Model): # CL: setting abstract correctly to allow subclasses to inherit the default From 043ebf72131bebc6227b3428b4203f905d0c6098 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 20 Dec 2014 19:04:49 +0100 Subject: [PATCH 007/250] Fixed metaclass to handle proxy correctly. Some issues with getting path properties set correctly. --- src/comms/comms.py | 3 +- src/objects/objects.py | 4 +- src/players/player.py | 3 +- src/scripts/scripts.py | 3 +- src/typeclasses/models.py | 298 ++++++++++++++++++++++++++++++++++--- src/utils/idmapper/base.py | 243 +----------------------------- 6 files changed, 289 insertions(+), 265 deletions(-) diff --git a/src/comms/comms.py b/src/comms/comms.py index 2c878058b..8bfdb35dc 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -3,6 +3,7 @@ Default Typeclass for Comms. See objects.objects for more information on Typeclassing. """ +from src.typeclasses.models import TypeclassBase from src.comms.models import Msg, TempMsg, ChannelDB from src.utils import logger from src.utils.utils import make_iter @@ -13,7 +14,7 @@ class Channel(ChannelDB): This is the base class for all Comms. Inherit from this to create different types of communication channels. """ - _is_typeclass = True + __metaclass__ = TypeclassBase # helper methods, for easy overloading diff --git a/src/objects/objects.py b/src/objects/objects.py index a989bb0af..3c4184155 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -16,6 +16,7 @@ they control by simply linking to a new object's user property. """ from django.conf import settings +from src.typeclasses.models import TypeclassBase from src.objects.models import ObjectDB from src.commands import cmdset, command from src.utils.logger import log_depmsg @@ -35,8 +36,9 @@ class Object(ObjectDB): """ This is the base class for all in-game objects. Inherit from this to create different types of objects in the game. + """ - _is_typeclass = True + __metaclass__ = TypeclassBase # __init__ is only defined here in order to present docstring to API. def __init__(self): diff --git a/src/players/player.py b/src/players/player.py index 8a7a4761d..d79981071 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -13,6 +13,7 @@ instead for most things). import datetime from django.conf import settings +from src.typeclasses.models import TypeclassBase from src.players.models import PlayerDB from src.comms.models import ChannelDB from src.utils import logger @@ -26,7 +27,7 @@ class Player(PlayerDB): """ Base typeclass for all Players. """ - _is_typeclass = True + __metaclass__ = TypeclassBase def __init__(self): """ diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index a301a7f64..9454e8092 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -8,6 +8,7 @@ It also defines a few common scripts. from twisted.internet.defer import Deferred, maybeDeferred from twisted.internet.task import LoopingCall from django.conf import settings +from src.typeclasses.models import TypeclassBase from django.utils.translation import ugettext as _ from src.scripts.models import ScriptDB from src.comms import channelhandler @@ -112,7 +113,7 @@ class ScriptBase(ScriptDB): Base class for scripts. Don't inherit from this, inherit from the class 'Script' instead. """ - _is_typeclass = True + __metaclass__ = TypeclassBase def __eq__(self, other): """ diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 4461c847f..19edcd3d8 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -46,6 +46,7 @@ from src.server.models import ServerConfig from src.typeclasses import managers from src.locks.lockhandler import LockHandler from src.utils import logger +from django.db.models.base import ModelBase from src.utils.utils import ( make_iter, is_iter, to_str, inherits_from, lazy_property) from src.utils.dbserialize import to_pickle, from_pickle @@ -739,26 +740,285 @@ class PermissionHandler(TagHandler): # #------------------------------------------------------------ -# imported for access by other -#from src.utils.idmapper.base import SharedMemoryModelBase -#class TypeclassBase(SharedMemoryModelBase): -# """ -# Metaclass which should be set for the root of model proxies -# that don't define any new fields, like Object, Script etc. -# """ -# def __new__(cls, name, bases, attrs): -# """ -# We must define our Typeclasses as proxies. We also store the path -# directly on the class, this is useful for managers. -# """ -# if hasattr(cls, "Meta"): -# cls.Meta.proxy = True -# else: -# class Meta: -# proxy = True -# cls.Meta = Meta -# return super(TypeclassBase, cls).__new__(name, bases, attrs) +# +# Meta class for typeclasses +# + +# django patch imports +import copy +from django.apps import apps +from django.db.models.base import subclass_exception +import warnings +from django.db.models.options import Options +from django.utils.deprecation import RemovedInDjango19Warning +from django.core.exceptions import MultipleObjectsReturned +from django.apps.config import MODELS_MODULE_NAME +from django.db.models.fields.related import OneToOneField +#/ django patch imports + +from src.utils.idmapper.base import SharedMemoryModelBase + +class TypeclassBase(SharedMemoryModelBase): + """ + Metaclass which should be set for the root of model proxies + that don't define any new fields, like Object, Script etc. + """ + def __new__(cls, name, bases, attrs): + """ + We must define our Typeclasses as proxies. We also store the path + directly on the class, this is useful for managers. + """ + # typeclass proxy setup + if "Meta" in attrs: + attrs["Meta"].proxy = True + else: + class Meta: + proxy = True + attrs["Meta"] = Meta + + + # patch start - django multi-inheritance + # this is a copy of django.db.models.base.__new__ + # with a few lines (marked patch below) changed + # as per https://code.djangoproject.com/ticket/11560 + + super_new = super(ModelBase, cls).__new__ + + # Also ensure initialization is only performed for subclasses of Model + # (excluding Model class itself). + parents = [b for b in bases if isinstance(b, ModelBase)] + if not parents: + return super_new(cls, name, bases, attrs) + + # Create the class. + module = attrs.pop('__module__') + new_class = super_new(cls, name, bases, {'__module__': module}) + attr_meta = attrs.pop('Meta', None) + abstract = getattr(attr_meta, 'abstract', False) + if not attr_meta: + meta = getattr(new_class, 'Meta', None) + else: + meta = attr_meta + base_meta = getattr(new_class, '_meta', None) + + # Look for an application configuration to attach the model to. + app_config = apps.get_containing_app_config(module) + + if getattr(meta, 'app_label', None) is None: + + if app_config is None: + # If the model is imported before the configuration for its + # application is created (#21719), or isn't in an installed + # application (#21680), use the legacy logic to figure out the + # app_label by looking one level up from the package or module + # named 'models'. If no such package or module exists, fall + # back to looking one level up from the module this model is + # defined in. + + # For 'django.contrib.sites.models', this would be 'sites'. + # For 'geo.models.places' this would be 'geo'. + + msg = ( + "Model class %s.%s doesn't declare an explicit app_label " + "and either isn't in an application in INSTALLED_APPS or " + "else was imported before its application was loaded. " % + (module, name)) + if abstract: + msg += "Its app_label will be set to None in Django 1.9." + else: + msg += "This will no longer be supported in Django 1.9." + warnings.warn(msg, RemovedInDjango19Warning, stacklevel=2) + + model_module = sys.modules[new_class.__module__] + package_components = model_module.__name__.split('.') + package_components.reverse() # find the last occurrence of 'models' + try: + app_label_index = package_components.index(MODELS_MODULE_NAME) + 1 + except ValueError: + app_label_index = 1 + kwargs = {"app_label": package_components[app_label_index]} + + else: + kwargs = {"app_label": app_config.label} + + else: + kwargs = {} + + new_class.add_to_class('_meta', Options(meta, **kwargs)) + if not abstract: + new_class.add_to_class( + 'DoesNotExist', + subclass_exception( + str('DoesNotExist'), + tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,), + module, + attached_to=new_class)) + new_class.add_to_class( + 'MultipleObjectsReturned', + subclass_exception( + str('MultipleObjectsReturned'), + tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,), + module, + attached_to=new_class)) + if base_meta and not base_meta.abstract: + # Non-abstract child classes inherit some attributes from their + # non-abstract parent (unless an ABC comes before it in the + # method resolution order). + if not hasattr(meta, 'ordering'): + new_class._meta.ordering = base_meta.ordering + if not hasattr(meta, 'get_latest_by'): + new_class._meta.get_latest_by = base_meta.get_latest_by + + is_proxy = new_class._meta.proxy + + # If the model is a proxy, ensure that the base class + # hasn't been swapped out. + if is_proxy and base_meta and base_meta.swapped: + raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped)) + + if getattr(new_class, '_default_manager', None): + if not is_proxy: + # Multi-table inheritance doesn't inherit default manager from + # parents. + new_class._default_manager = None + new_class._base_manager = None + else: + # Proxy classes do inherit parent's default manager, if none is + # set explicitly. + new_class._default_manager = new_class._default_manager._copy_to_model(new_class) + new_class._base_manager = new_class._base_manager._copy_to_model(new_class) + + # Add all attributes to the class. + for obj_name, obj in attrs.items(): + new_class.add_to_class(obj_name, obj) + + # All the fields of any type declared on this model + new_fields = ( + new_class._meta.local_fields + + new_class._meta.local_many_to_many + + new_class._meta.virtual_fields + ) + field_names = set(f.name for f in new_fields) + + # Basic setup for proxy models. + if is_proxy: + base = None + for parent in [kls for kls in parents if hasattr(kls, '_meta')]: + if parent._meta.abstract: + if parent._meta.fields: + raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) + else: + continue + #if base is not None: # patch + while parent._meta.proxy: # patch + parent = parent._meta.proxy_for_model # patch + if base is not None and base is not parent: # patch + raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name) + else: + base = parent + if base is None: + raise TypeError("Proxy model '%s' has no non-abstract model base class." % name) + new_class._meta.setup_proxy(base) + new_class._meta.concrete_model = base._meta.concrete_model + else: + new_class._meta.concrete_model = new_class + + # Collect the parent links for multi-table inheritance. + parent_links = {} + for base in reversed([new_class] + parents): + # Conceptually equivalent to `if base is Model`. + if not hasattr(base, '_meta'): + continue + # Skip concrete parent classes. + if base != new_class and not base._meta.abstract: + continue + # Locate OneToOneField instances. + for field in base._meta.local_fields: + if isinstance(field, OneToOneField): + parent_links[field.rel.to] = field + + # Do the appropriate setup for any model parents. + for base in parents: + original_base = base + if not hasattr(base, '_meta'): + # Things without _meta aren't functional models, so they're + # uninteresting parents. + continue + + parent_fields = base._meta.local_fields + base._meta.local_many_to_many + # Check for clashes between locally declared fields and those + # on the base classes (we cannot handle shadowed fields at the + # moment). + for field in parent_fields: + if field.name in field_names: + raise FieldError( + 'Local field %r in class %r clashes ' + 'with field of similar name from ' + 'base class %r' % (field.name, name, base.__name__) + ) + if not base._meta.abstract: + # Concrete classes... + base = base._meta.concrete_model + if base in parent_links: + field = parent_links[base] + elif not is_proxy: + attr_name = '%s_ptr' % base._meta.model_name + field = OneToOneField(base, name=attr_name, + auto_created=True, parent_link=True) + # Only add the ptr field if it's not already present; + # e.g. migrations will already have it specified + if not hasattr(new_class, attr_name): + new_class.add_to_class(attr_name, field) + else: + field = None + new_class._meta.parents[base] = field + else: + # .. and abstract ones. + for field in parent_fields: + new_class.add_to_class(field.name, copy.deepcopy(field)) + + # Pass any non-abstract parent classes onto child. + new_class._meta.parents.update(base._meta.parents) + + # Inherit managers from the abstract base classes. + new_class.copy_managers(base._meta.abstract_managers) + + # Proxy models inherit the non-abstract managers from their base, + # unless they have redefined any of them. + if is_proxy: + new_class.copy_managers(original_base._meta.concrete_managers) + + # Inherit virtual fields (like GenericForeignKey) from the parent + # class + for field in base._meta.virtual_fields: + if base._meta.abstract and field.name in field_names: + raise FieldError( + 'Local field %r in class %r clashes ' + 'with field of similar name from ' + 'abstract base class %r' % (field.name, name, base.__name__) + ) + new_class.add_to_class(field.name, copy.deepcopy(field)) + + if abstract: + # Abstract base models can't be instantiated and don't appear in + # the list of models for an app. We do the final setup for them a + # little differently from normal models. + attr_meta.abstract = False + new_class.Meta = attr_meta + return new_class + + new_class._prepare() + new_class._meta.apps.register_model(new_class._meta.app_label, new_class) + return new_class + + # /patch end + + + +# +# Main TypedObject abstraction +# class TypedObject(SharedMemoryModel): diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 47788059b..47b6c12f5 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -95,14 +95,6 @@ class SharedMemoryModelBase(ModelBase): document this auto-wrapping in the class header, this could seem very much like magic to the user otherwise. """ # set up the typeclass handling only if a variable _is_typeclass is set on the class - if "_is_typeclass" in attrs: - if "Meta" in attrs: - attrs["Meta"].proxy = True - else: - class Meta: - proxy = True - attrs["Meta"] = Meta - def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False): "Helper method to create property wrappers with unique names (must be in separate call)" def _get(cls, fname): @@ -197,240 +189,7 @@ class SharedMemoryModelBase(ModelBase): #print "wrapping %s -> %s" % (fieldname, wrappername) create_wrapper(cls, fieldname, wrappername, editable=field.editable, foreignkey=foreignkey) - # patch start - - super_new = super(ModelBase, cls).__new__ - - # Also ensure initialization is only performed for subclasses of Model - # (excluding Model class itself). - parents = [b for b in bases if isinstance(b, ModelBase)] - if not parents: - return super_new(cls, name, bases, attrs) - - # Create the class. - module = attrs.pop('__module__') - new_class = super_new(cls, name, bases, {'__module__': module}) - attr_meta = attrs.pop('Meta', None) - abstract = getattr(attr_meta, 'abstract', False) - if not attr_meta: - meta = getattr(new_class, 'Meta', None) - else: - meta = attr_meta - base_meta = getattr(new_class, '_meta', None) - - # Look for an application configuration to attach the model to. - app_config = apps.get_containing_app_config(module) - - if getattr(meta, 'app_label', None) is None: - - if app_config is None: - # If the model is imported before the configuration for its - # application is created (#21719), or isn't in an installed - # application (#21680), use the legacy logic to figure out the - # app_label by looking one level up from the package or module - # named 'models'. If no such package or module exists, fall - # back to looking one level up from the module this model is - # defined in. - - # For 'django.contrib.sites.models', this would be 'sites'. - # For 'geo.models.places' this would be 'geo'. - - msg = ( - "Model class %s.%s doesn't declare an explicit app_label " - "and either isn't in an application in INSTALLED_APPS or " - "else was imported before its application was loaded. " % - (module, name)) - if abstract: - msg += "Its app_label will be set to None in Django 1.9." - else: - msg += "This will no longer be supported in Django 1.9." - warnings.warn(msg, RemovedInDjango19Warning, stacklevel=2) - - model_module = sys.modules[new_class.__module__] - package_components = model_module.__name__.split('.') - package_components.reverse() # find the last occurrence of 'models' - try: - app_label_index = package_components.index(MODELS_MODULE_NAME) + 1 - except ValueError: - app_label_index = 1 - kwargs = {"app_label": package_components[app_label_index]} - - else: - kwargs = {"app_label": app_config.label} - - else: - kwargs = {} - - new_class.add_to_class('_meta', Options(meta, **kwargs)) - if not abstract: - new_class.add_to_class( - 'DoesNotExist', - subclass_exception( - str('DoesNotExist'), - tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,), - module, - attached_to=new_class)) - new_class.add_to_class( - 'MultipleObjectsReturned', - subclass_exception( - str('MultipleObjectsReturned'), - tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,), - module, - attached_to=new_class)) - if base_meta and not base_meta.abstract: - # Non-abstract child classes inherit some attributes from their - # non-abstract parent (unless an ABC comes before it in the - # method resolution order). - if not hasattr(meta, 'ordering'): - new_class._meta.ordering = base_meta.ordering - if not hasattr(meta, 'get_latest_by'): - new_class._meta.get_latest_by = base_meta.get_latest_by - - is_proxy = new_class._meta.proxy - - # If the model is a proxy, ensure that the base class - # hasn't been swapped out. - if is_proxy and base_meta and base_meta.swapped: - raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped)) - - if getattr(new_class, '_default_manager', None): - if not is_proxy: - # Multi-table inheritance doesn't inherit default manager from - # parents. - new_class._default_manager = None - new_class._base_manager = None - else: - # Proxy classes do inherit parent's default manager, if none is - # set explicitly. - new_class._default_manager = new_class._default_manager._copy_to_model(new_class) - new_class._base_manager = new_class._base_manager._copy_to_model(new_class) - - # Add all attributes to the class. - for obj_name, obj in attrs.items(): - new_class.add_to_class(obj_name, obj) - - # All the fields of any type declared on this model - new_fields = ( - new_class._meta.local_fields + - new_class._meta.local_many_to_many + - new_class._meta.virtual_fields - ) - field_names = set(f.name for f in new_fields) - - # Basic setup for proxy models. - if is_proxy: - base = None - for parent in [kls for kls in parents if hasattr(kls, '_meta')]: - if parent._meta.abstract: - if parent._meta.fields: - raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) - else: - continue - if base is not None: - raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name) - else: - base = parent - #if base is None: # patch - while parent._meta.proxy: # patch - parent = parent._meta.proxy_for_model # patch - if base is not None and base is not parent: # patch - raise TypeError("Proxy model '%s' has no non-abstract model base class." % name) - new_class._meta.setup_proxy(base) - new_class._meta.concrete_model = base._meta.concrete_model - else: - new_class._meta.concrete_model = new_class - - # Collect the parent links for multi-table inheritance. - parent_links = {} - for base in reversed([new_class] + parents): - # Conceptually equivalent to `if base is Model`. - if not hasattr(base, '_meta'): - continue - # Skip concrete parent classes. - if base != new_class and not base._meta.abstract: - continue - # Locate OneToOneField instances. - for field in base._meta.local_fields: - if isinstance(field, OneToOneField): - parent_links[field.rel.to] = field - - # Do the appropriate setup for any model parents. - for base in parents: - original_base = base - if not hasattr(base, '_meta'): - # Things without _meta aren't functional models, so they're - # uninteresting parents. - continue - - parent_fields = base._meta.local_fields + base._meta.local_many_to_many - # Check for clashes between locally declared fields and those - # on the base classes (we cannot handle shadowed fields at the - # moment). - for field in parent_fields: - if field.name in field_names: - raise FieldError( - 'Local field %r in class %r clashes ' - 'with field of similar name from ' - 'base class %r' % (field.name, name, base.__name__) - ) - if not base._meta.abstract: - # Concrete classes... - base = base._meta.concrete_model - if base in parent_links: - field = parent_links[base] - elif not is_proxy: - attr_name = '%s_ptr' % base._meta.model_name - field = OneToOneField(base, name=attr_name, - auto_created=True, parent_link=True) - # Only add the ptr field if it's not already present; - # e.g. migrations will already have it specified - if not hasattr(new_class, attr_name): - new_class.add_to_class(attr_name, field) - else: - field = None - new_class._meta.parents[base] = field - else: - # .. and abstract ones. - for field in parent_fields: - new_class.add_to_class(field.name, copy.deepcopy(field)) - - # Pass any non-abstract parent classes onto child. - new_class._meta.parents.update(base._meta.parents) - - # Inherit managers from the abstract base classes. - new_class.copy_managers(base._meta.abstract_managers) - - # Proxy models inherit the non-abstract managers from their base, - # unless they have redefined any of them. - if is_proxy: - new_class.copy_managers(original_base._meta.concrete_managers) - - # Inherit virtual fields (like GenericForeignKey) from the parent - # class - for field in base._meta.virtual_fields: - if base._meta.abstract and field.name in field_names: - raise FieldError( - 'Local field %r in class %r clashes ' - 'with field of similar name from ' - 'abstract base class %r' % (field.name, name, base.__name__) - ) - new_class.add_to_class(field.name, copy.deepcopy(field)) - - if abstract: - # Abstract base models can't be instantiated and don't appear in - # the list of models for an app. We do the final setup for them a - # little differently from normal models. - attr_meta.abstract = False - new_class.Meta = attr_meta - return new_class - - new_class._prepare() - new_class._meta.apps.register_model(new_class._meta.app_label, new_class) - return new_class - - - # /patch end - #return super(SharedMemoryModelBase, cls).__new__(cls, name, bases, attrs, *args, **kwargs) + return super(SharedMemoryModelBase, cls).__new__(cls, name, bases, attrs) class SharedMemoryModel(Model): From e28d544fb027a079736dc0a4f0a1574687ae3d53 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 20 Dec 2014 19:19:48 +0100 Subject: [PATCH 008/250] The manager wrapper has trouble finding the path of the current class. --- src/typeclasses/models.py | 6 +++--- src/utils/idmapper/base.py | 17 ++++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 19edcd3d8..46cb0d76c 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -42,10 +42,10 @@ from src.server.caches import get_prop_cache, set_prop_cache #from src.server.caches import set_attr_cache #from src.server.caches import call_ndb_hooks -from src.server.models import ServerConfig +#from src.server.models import ServerConfig from src.typeclasses import managers from src.locks.lockhandler import LockHandler -from src.utils import logger +#from src.utils import logger from django.db.models.base import ModelBase from src.utils.utils import ( make_iter, is_iter, to_str, inherits_from, lazy_property) @@ -752,7 +752,7 @@ from django.db.models.base import subclass_exception import warnings from django.db.models.options import Options from django.utils.deprecation import RemovedInDjango19Warning -from django.core.exceptions import MultipleObjectsReturned +from django.core.exceptions import MultipleObjectsReturned, FieldError from django.apps.config import MODELS_MODULE_NAME from django.db.models.fields.related import OneToOneField #/ django patch imports diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 47b6c12f5..a0931962e 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -22,19 +22,6 @@ from manager import SharedMemoryManager AUTO_FLUSH_MIN_INTERVAL = 60.0 * 5 # at least 5 mins between cache flushes -# django patch imports -import copy -import sys -from django.apps import apps -from django.db.models.base import subclass_exception -import warnings -from django.db.models.options import Options -from django.utils.deprecation import RemovedInDjango19Warning -from django.core.exceptions import MultipleObjectsReturned -from django.apps.config import MODELS_MODULE_NAME -from django.db.models.fields.related import OneToOneField -#/ django patch imports - _GA = object.__getattribute__ _SA = object.__setattr__ @@ -94,6 +81,10 @@ class SharedMemoryModelBase(ModelBase): already has a wrapper of the given name, the automatic creation is skipped. Note: Remember to document this auto-wrapping in the class header, this could seem very much like magic to the user otherwise. """ + + attrs["typename"] = cls.__name__ + attrs["path"] = "%s.%s" % (attrs["__module__"], name) + # set up the typeclass handling only if a variable _is_typeclass is set on the class def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False): "Helper method to create property wrappers with unique names (must be in separate call)" From 4e0b5be962e3fbecf607792c8d827fdf0f1c9b09 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 20 Dec 2014 19:29:38 +0100 Subject: [PATCH 009/250] Fixed creation. Time to start cleaning the .dbobj hierarchy. --- src/objects/objects.py | 4 ++-- src/players/player.py | 4 ++-- src/scripts/scripts.py | 4 ++-- src/typeclasses/models.py | 5 +++++ src/utils/idmapper/base.py | 4 ++-- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/objects/objects.py b/src/objects/objects.py index 3c4184155..cb43ee960 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -41,7 +41,7 @@ class Object(ObjectDB): __metaclass__ = TypeclassBase # __init__ is only defined here in order to present docstring to API. - def __init__(self): + def __init__(self, *args, **kwargs): """ This is the root typeclass object, representing all entities that have an actual presence in-game. Objects generally have a @@ -192,7 +192,7 @@ class Object(ObjectDB): this object speaks """ - super(Object, self).__init__() + super(Object, self).__init__(*args, **kwargs) ## methods inherited from the database object (overload them here) diff --git a/src/players/player.py b/src/players/player.py index d79981071..039b5dfba 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -29,7 +29,7 @@ class Player(PlayerDB): """ __metaclass__ = TypeclassBase - def __init__(self): + def __init__(self, *args, **kwargs): """ This is the base Typeclass for all Players. Players represent the person playing the game and tracks account info, password @@ -103,7 +103,7 @@ class Player(PlayerDB): at_server_shutdown() """ - super(Player, self).__init__() + super(Player, self).__init__(*args, **kwargs) ## methods inherited from database model diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index 9454e8092..4aa289481 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -354,7 +354,7 @@ class Script(ScriptBase): the hooks called by the script machinery. """ - def __init__(self): + def __init__(self, *args, **kwargs): """ This is the base TypeClass for all Scripts. Scripts describe events, timers and states in game, they can have a time component or describe @@ -441,7 +441,7 @@ class Script(ScriptBase): """ - super(Script, self).__init__() + super(Script, self).__init__(*args, **kwargs) def at_script_creation(self): """ diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 46cb0d76c..ed077006c 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -764,11 +764,16 @@ class TypeclassBase(SharedMemoryModelBase): Metaclass which should be set for the root of model proxies that don't define any new fields, like Object, Script etc. """ + def __new__(cls, name, bases, attrs): """ We must define our Typeclasses as proxies. We also store the path directly on the class, this is useful for managers. """ + + attrs["typename"] = cls.__name__ + attrs["path"] = "%s.%s" % (attrs["__module__"], name) + # typeclass proxy setup if "Meta" in attrs: attrs["Meta"].proxy = True diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index a0931962e..c67fb218a 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -82,8 +82,8 @@ class SharedMemoryModelBase(ModelBase): document this auto-wrapping in the class header, this could seem very much like magic to the user otherwise. """ - attrs["typename"] = cls.__name__ - attrs["path"] = "%s.%s" % (attrs["__module__"], name) + #attrs["typename"] = cls.__name__ + #attrs["path"] = "%s.%s" % (attrs["__module__"], name) # set up the typeclass handling only if a variable _is_typeclass is set on the class def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False): From 9ee6b718da32f9a8c12946684bf73d55b20476f1 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 21 Dec 2014 15:02:34 +0100 Subject: [PATCH 010/250] Some issues with unique restraint when re-saving objects. --- src/typeclasses/models.py | 7 +++---- src/utils/idmapper/base.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index ed077006c..8e7f3b45d 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -775,13 +775,12 @@ class TypeclassBase(SharedMemoryModelBase): attrs["path"] = "%s.%s" % (attrs["__module__"], name) # typeclass proxy setup - if "Meta" in attrs: - attrs["Meta"].proxy = True - else: + if not "Meta" in attrs: class Meta: proxy = True attrs["Meta"] = Meta - + attrs["Meta"].proxy = True + attrs["Meta"].app_label = attrs["path"] # patch start - django multi-inheritance # this is a copy of django.db.models.base.__new__ diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index c67fb218a..a0931962e 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -82,8 +82,8 @@ class SharedMemoryModelBase(ModelBase): document this auto-wrapping in the class header, this could seem very much like magic to the user otherwise. """ - #attrs["typename"] = cls.__name__ - #attrs["path"] = "%s.%s" % (attrs["__module__"], name) + attrs["typename"] = cls.__name__ + attrs["path"] = "%s.%s" % (attrs["__module__"], name) # set up the typeclass handling only if a variable _is_typeclass is set on the class def create_wrapper(cls, fieldname, wrappername, editable=True, foreignkey=False): From 3b704d37dc8981a262632a7848fa61611dbe92f9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 21 Dec 2014 16:06:10 +0100 Subject: [PATCH 011/250] Made sure all proxies of a given database model share the same idmapper cache --- src/utils/idmapper/base.py | 52 ++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index a0931962e..3224d3f5a 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -69,8 +69,22 @@ class SharedMemoryModelBase(ModelBase): def _prepare(cls): - cls.__instance_cache__ = {} - cls._idmapper_recache_protection = False + """ + Prepare the cache, making sure that proxies of the same db base + share the same cache. + """ + def prep(dbmodel): + if not hasattr(dbmodel, "__instance_cache__"): + dbmodel.__instance_cache__ = {} + dbmodel.__idmapper_recache_protection = False + if not cls._meta.proxy: + # non-proxy models get the full cache + prep(cls) + else: + # proxies get a reference to the cache + dbmodel = cls._meta.proxy_for_model + prep(dbmodel) + cls.__instance_cache__ = dbmodel.__instance_cache__ super(SharedMemoryModelBase, cls)._prepare() def __new__(cls, name, bases, attrs): @@ -197,6 +211,7 @@ class SharedMemoryModel(Model): # super(SharedMemoryModel, cls).__init__(*args, **kwargs) # cls._idmapper_recache_protection = False + @classmethod def _get_cache_key(cls, args, kwargs): """ This method is used by the caching subsystem to infer the PK value from the constructor arguments. @@ -225,8 +240,9 @@ class SharedMemoryModel(Model): # if the pk value happens to be a model instance (which can happen wich a FK), we'd rather use its own pk as the key result = result._get_pk_val() return result - _get_cache_key = classmethod(_get_cache_key) + #_get_cache_key = classmethod(_get_cache_key) + @classmethod def get_cached_instance(cls, id): """ Method to retrieve a cached instance by pk value. Returns None when not found @@ -234,8 +250,9 @@ class SharedMemoryModel(Model): note that the lookup will be done even when instance caching is disabled. """ return cls.__instance_cache__.get(id) - get_cached_instance = classmethod(get_cached_instance) + #get_cached_instance = classmethod(get_cached_instance) + @classmethod def cache_instance(cls, instance): """ Method to store an instance in the cache. @@ -243,13 +260,15 @@ class SharedMemoryModel(Model): if instance._get_pk_val() is not None: cls.__instance_cache__[instance._get_pk_val()] = instance - cache_instance = classmethod(cache_instance) + #cache_instance = classmethod(cache_instance) + @classmethod def get_all_cached_instances(cls): "return the objects so far cached by idmapper for this class." return cls.__instance_cache__.values() - get_all_cached_instances = classmethod(get_all_cached_instances) + #get_all_cached_instances = classmethod(get_all_cached_instances) + @classmethod def _flush_cached_by_key(cls, key, force=True): "Remove the cached reference." try: @@ -257,8 +276,9 @@ class SharedMemoryModel(Model): del cls.__instance_cache__[key] except KeyError: pass - _flush_cached_by_key = classmethod(_flush_cached_by_key) + #_flush_cached_by_key = classmethod(_flush_cached_by_key) + @classmethod def flush_cached_instance(cls, instance, force=True): """ Method to flush an instance from the cache. The instance will @@ -268,14 +288,9 @@ class SharedMemoryModel(Model): """ cls._flush_cached_by_key(instance._get_pk_val(), force=force) - flush_cached_instance = classmethod(flush_cached_instance) - - # per-instance methods - - def set_recache_protection(cls, mode=True): - "set if this instance should be allowed to be recached." - cls._idmapper_recache_protection = bool(mode) + #flush_cached_instance = classmethod(flush_cached_instance) + @classmethod def flush_instance_cache(cls, force=False): """ This will clean safe objects from the cache. Use force @@ -286,7 +301,13 @@ class SharedMemoryModel(Model): else: cls.__instance_cache__ = dict((key, obj) for key, obj in cls.__instance_cache__.items() if obj._idmapper_recache_protection) - flush_instance_cache = classmethod(flush_instance_cache) + #flush_instance_cache = classmethod(flush_instance_cache) + + # per-instance methods + + def set_recache_protection(cls, mode=True): + "set if this instance should be allowed to be recached." + cls._idmapper_recache_protection = bool(mode) def save(cls, *args, **kwargs): "save method tracking process/thread issues" @@ -305,7 +326,6 @@ class SharedMemoryModel(Model): # in another thread; make sure to save in reactor thread def _save_callback(cls, *args, **kwargs): super(SharedMemoryModel, cls).save(*args, **kwargs) - #blockingCallFromThread(reactor, _save_callback, cls, *args, **kwargs) callFromThread(_save_callback, cls, *args, **kwargs) From b244d70e1686c5e31f779b2a33740354daaaa6f6 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 21 Dec 2014 19:57:01 +0100 Subject: [PATCH 012/250] Resolved a UNIQUE id error when re-saving a typeclassed object. This was due to an idmapper hack which I *think* is not needed anymore, but might need to look at this in the future to see if the related lookup is actually using the cache or not. --- src/utils/idmapper/manager.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/utils/idmapper/manager.py b/src/utils/idmapper/manager.py index 2c017b3af..a1a9ed00f 100755 --- a/src/utils/idmapper/manager.py +++ b/src/utils/idmapper/manager.py @@ -15,11 +15,15 @@ class SharedMemoryManager(Manager): # rel_obj = rel_mgr.using(db).get(**params) # We need to handle using, or the get method will be called on a vanilla # queryset, and we won't get a change to use the cache. - def using(self, alias): - if alias == router.db_for_read(self.model): - return self - else: - return super(SharedMemoryManager, self).using(alias) + + #TODO - removing this for django1.7 - the call mentioned above doesn't happen + # anymore but is the cache still used? /Griatch + #def using(self, alias): + # if alias == router.db_for_read(self.model): + # # this should return a queryset! + # return self + # else: + # return super(SharedMemoryManager, self).using(alias) # TODO: improve on this implementation # We need a way to handle reverse lookups so that this model can From 554d1b9834ddc9ff9d8122d751a370b4df1bfe5b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 21 Dec 2014 23:08:20 +0100 Subject: [PATCH 013/250] Fixed the *_family manager methods to correctly return typeclasses subclassed at any depth. --- src/objects/models.py | 3 +-- src/typeclasses/managers.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/objects/models.py b/src/objects/models.py index 2794a0cf2..e53354f50 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -230,8 +230,7 @@ class ObjectDB(TypedObject): # location getsetter def __location_get(self): "Get location" - loc = _GA(_GA(self, "dbobj"), "db_location") - return _GA(loc, "typeclass") if loc else loc + return self.db_location def __location_set(self, location): "Set location, checking for loops and allowing dbref" diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index 95cee9453..1e389e39b 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -68,13 +68,22 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path=self.model.path) + def _get_subclasses(self, cls): + """ + Recursively get all subclasses to a class + """ + all_subclasses = cls.__subclasses__() + for subclass in all_subclasses: + all_subclasses.extend(self._get_subclasses(subclass)) + return all_subclasses + def get_family(self, **kwargs): """ Variation of get that not only returns the current typeclass but also all subclasses of that typeclass. """ paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self.model.__subclasses__()] + for cls in self._get_subclasses(self.model)] kwargs.update({"db_typeclass_path__in":paths}) return super(TypedObjectManager, self).get(**kwargs) @@ -85,7 +94,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ # query, including all subclasses paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self.model.__subclasses__()] + for cls in self._get_subclasses(self.model)] kwargs.update({"db_typeclass_path__in":paths}) return super(TypedObjectManager, self).filter(**kwargs) @@ -95,7 +104,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): the typeclass. """ paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self.model.__subclasses__()] + for cls in self._get_subclasses(self.model)] return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path__in=paths) From 2ee9e62336d99565da839d4628a155c80b9a0b29 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 08:54:53 +0100 Subject: [PATCH 014/250] Fixed correct and separate handling of database model bases as compared to its proxy classes using different managers for each type (e.g. ObjectDB.objects.all() will return all ObjectDB instances(including proxy instances) whereas Object.objects.all() will only return Objects) --- src/comms/comms.py | 2 + src/comms/managers.py | 8 ++- src/comms/models.py | 4 +- src/objects/__init__.py | 2 +- src/objects/manager.py | 7 +- src/objects/models.py | 4 +- src/objects/objects.py | 2 + src/players/__init__.py | 2 +- src/players/manager.py | 8 ++- src/players/models.py | 4 +- src/players/player.py | 2 + src/scripts/manager.py | 7 +- src/scripts/models.py | 4 +- src/scripts/scripts.py | 2 + src/typeclasses/managers.py | 126 +++++++++++++++++++----------------- 15 files changed, 105 insertions(+), 79 deletions(-) diff --git a/src/comms/comms.py b/src/comms/comms.py index 8bfdb35dc..8ed3e945e 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -5,6 +5,7 @@ See objects.objects for more information on Typeclassing. """ from src.typeclasses.models import TypeclassBase from src.comms.models import Msg, TempMsg, ChannelDB +from src.comms.managers import ChannelManager from src.utils import logger from src.utils.utils import make_iter @@ -15,6 +16,7 @@ class Channel(ChannelDB): types of communication channels. """ __metaclass__ = TypeclassBase + objects = ChannelManager() # helper methods, for easy overloading diff --git a/src/comms/managers.py b/src/comms/managers.py index b2cbce4e5..1823a4279 100644 --- a/src/comms/managers.py +++ b/src/comms/managers.py @@ -4,7 +4,8 @@ These managers handles the from django.db import models from django.db.models import Q -from src.typeclasses.managers import TypedObjectManager, returns_typeclass_list, returns_typeclass +from src.typeclasses.managers import (TypedObjectManager, TypeclassManager, + returns_typeclass_list, returns_typeclass) _GA = object.__getattribute__ _PlayerDB = None @@ -251,7 +252,7 @@ class MsgManager(models.Manager): # Channel manager # -class ChannelManager(TypedObjectManager): +class ChannelDBManager(TypedObjectManager): """ This ChannelManager implements methods for searching and manipulating Channels directly from the database. @@ -373,6 +374,9 @@ class ChannelManager(TypedObjectManager): for a in channel.aliases.all()]] return channels +class ChannelManager(ChannelDBManager, TypeclassManager): + pass + # # PlayerChannelConnection manager diff --git a/src/comms/models.py b/src/comms/models.py index a56c665ad..6b12da2c3 100644 --- a/src/comms/models.py +++ b/src/comms/models.py @@ -22,7 +22,7 @@ be able to delete connections on the fly). from datetime import datetime from django.conf import settings from django.db import models -from src.typeclasses.models import TypedObject, TagHandler, AttributeHandler, AliasHandler +from src.typeclasses.models import TypedObject from src.utils.idmapper.models import SharedMemoryModel from src.comms import managers from src.comms.managers import identify_object @@ -356,7 +356,7 @@ class ChannelDB(TypedObject): related_name="subscription_set", null=True, verbose_name='subscriptions', db_index=True) # Database manager - objects = managers.ChannelManager() + objects = managers.ChannelDBManager() _typeclass_paths = settings.CHANNEL_TYPECLASS_PATHS _default_typeclass_path = settings.BASE_CHANNEL_TYPECLASS or "src.comms.comms.Channel" diff --git a/src/objects/__init__.py b/src/objects/__init__.py index 4931eaa39..2cdc469ce 100644 --- a/src/objects/__init__.py +++ b/src/objects/__init__.py @@ -6,7 +6,7 @@ Also, the initiated object manager is available as src.objects.manager. """ -from src.objects.objects import * +#from src.objects.objects import * from src.objects.models import ObjectDB manager = ObjectDB.objects diff --git a/src/objects/manager.py b/src/objects/manager.py index ac5c80d62..2be80ac06 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -5,7 +5,7 @@ from itertools import chain from django.db.models import Q from django.conf import settings from django.db.models.fields import exceptions -from src.typeclasses.managers import TypedObjectManager +from src.typeclasses.managers import TypedObjectManager, TypeclassManager from src.typeclasses.managers import returns_typeclass, returns_typeclass_list from src.utils import utils from src.utils.utils import to_unicode, is_iter, make_iter, string_partial_matching @@ -22,7 +22,7 @@ _ATTR = None _AT_MULTIMATCH_INPUT = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1)) -class ObjectManager(TypedObjectManager): +class ObjectDBManager(TypedObjectManager): """ This ObjectManager implementes methods for searching and manipulating Objects directly from the database. @@ -413,3 +413,6 @@ class ObjectManager(TypedObjectManager): """ self.filter(db_sessid__isnull=False).update(db_sessid=None) + +class ObjectManager(ObjectDBManager, TypeclassManager): + pass diff --git a/src/objects/models.py b/src/objects/models.py index e53354f50..5a2b6ceeb 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -20,7 +20,7 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from src.typeclasses.models import TypedObject, NickHandler -from src.objects.manager import ObjectManager +from src.objects.manager import ObjectDBManager from src.players.models import PlayerDB from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler @@ -171,7 +171,7 @@ class ObjectDB(TypedObject): help_text="optional python path to a cmdset class.") # Database manager - objects = ObjectManager() + objects = ObjectDBManager() # caches for quick lookups of typeclass loading. _typeclass_paths = settings.OBJECT_TYPECLASS_PATHS diff --git a/src/objects/objects.py b/src/objects/objects.py index cb43ee960..a90c25b92 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -17,6 +17,7 @@ they control by simply linking to a new object's user property. from django.conf import settings from src.typeclasses.models import TypeclassBase +from src.objects.manager import ObjectTypeclassManager from src.objects.models import ObjectDB from src.commands import cmdset, command from src.utils.logger import log_depmsg @@ -39,6 +40,7 @@ class Object(ObjectDB): """ __metaclass__ = TypeclassBase + objects = ObjectTypeclassManager() # __init__ is only defined here in order to present docstring to API. def __init__(self, *args, **kwargs): diff --git a/src/players/__init__.py b/src/players/__init__.py index b49e80468..a448c50b4 100644 --- a/src/players/__init__.py +++ b/src/players/__init__.py @@ -7,7 +7,7 @@ Also, the initiated object manager is available as src.players.manager. """ -from src.players.player import * +#from src.players.player import * from src.players.models import PlayerDB manager = PlayerDB.objects diff --git a/src/players/manager.py b/src/players/manager.py index d7526c294..67c72bd14 100644 --- a/src/players/manager.py +++ b/src/players/manager.py @@ -5,7 +5,8 @@ The managers for the custom Player object and permissions. import datetime from django.contrib.auth.models import UserManager #from functools import update_wrapper -from src.typeclasses.managers import returns_typeclass_list, returns_typeclass, TypedObjectManager +from src.typeclasses.managers import (returns_typeclass_list, returns_typeclass, + TypedObjectManager, TypeclassManager) #from src.utils import logger __all__ = ("PlayerManager",) @@ -14,7 +15,7 @@ __all__ = ("PlayerManager",) # Player Manager # -class PlayerManager(TypedObjectManager, UserManager): +class PlayerDBManager(TypedObjectManager, UserManager): """ This PlayerManager implements methods for searching and manipulating Players directly from the database. @@ -149,3 +150,6 @@ class PlayerManager(TypedObjectManager, UserManager): if old_character and delete_old_character: old_character.delete() return True + +class PlayerManager(PlayerDBManager, TypeclassManager): + pass diff --git a/src/players/models.py b/src/players/models.py index 86d63831c..90bef3c7a 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -21,7 +21,7 @@ from django.db import models from django.contrib.auth.models import AbstractUser from django.utils.encoding import smart_str -from src.players import manager +from src.players.manager import PlayerDBManager from src.scripts.models import ScriptDB from src.typeclasses.models import (TypedObject, NickHandler) from src.scripts.scripthandler import ScriptHandler @@ -100,7 +100,7 @@ class PlayerDB(TypedObject, AbstractUser): db_is_bot = models.BooleanField(default=False, verbose_name="is_bot", help_text="Used to identify irc/imc2/rss bots") # Database manager - objects = manager.PlayerManager() + objects = PlayerDBManager() # caches for quick lookups _typeclass_paths = settings.PLAYER_TYPECLASS_PATHS diff --git a/src/players/player.py b/src/players/player.py index 039b5dfba..653c725c1 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -14,6 +14,7 @@ instead for most things). import datetime from django.conf import settings from src.typeclasses.models import TypeclassBase +from src.players.manager import PlayerManager from src.players.models import PlayerDB from src.comms.models import ChannelDB from src.utils import logger @@ -28,6 +29,7 @@ class Player(PlayerDB): Base typeclass for all Players. """ __metaclass__ = TypeclassBase + objects = PlayerManager() def __init__(self, *args, **kwargs): """ diff --git a/src/scripts/manager.py b/src/scripts/manager.py index 1dc4b5f63..6c82077d4 100644 --- a/src/scripts/manager.py +++ b/src/scripts/manager.py @@ -3,7 +3,7 @@ The custom manager for Scripts. """ from django.db.models import Q -from src.typeclasses.managers import TypedObjectManager +from src.typeclasses.managers import TypedObjectManager, TypeclassManager from src.typeclasses.managers import returns_typeclass_list from src.utils.utils import make_iter __all__ = ("ScriptManager",) @@ -12,7 +12,7 @@ _GA = object.__getattribute__ VALIDATE_ITERATION = 0 -class ScriptManager(TypedObjectManager): +class ScriptDBManager(TypedObjectManager): """ This Scriptmanager implements methods for searching and manipulating Scripts directly from the database. @@ -230,3 +230,6 @@ class ScriptManager(TypedObjectManager): new_script = create.create_script(typeclass, key=new_key, obj=new_obj, locks=new_locks, autostart=True) return new_script + +class ScriptManager(ScriptDBManager, TypeclassManager): + pass diff --git a/src/scripts/models.py b/src/scripts/models.py index 45347cff9..c1717fc0b 100644 --- a/src/scripts/models.py +++ b/src/scripts/models.py @@ -28,7 +28,7 @@ from django.conf import settings from django.db import models from django.core.exceptions import ObjectDoesNotExist from src.typeclasses.models import TypedObject -from src.scripts.manager import ScriptManager +from src.scripts.manager import ScriptDBManager from src.utils.utils import dbref, to_str __all__ = ("ScriptDB",) @@ -98,7 +98,7 @@ class ScriptDB(TypedObject): db_is_active = models.BooleanField('script active', default=False) # Database manager - objects = ScriptManager() + objects = ScriptDBManager() # caches for quick lookups _typeclass_paths = settings.SCRIPT_TYPECLASS_PATHS diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index 4aa289481..ec14aba91 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -11,6 +11,7 @@ from django.conf import settings from src.typeclasses.models import TypeclassBase from django.utils.translation import ugettext as _ from src.scripts.models import ScriptDB +from src.scripts.manager import ScriptManager from src.comms import channelhandler from src.utils import logger @@ -114,6 +115,7 @@ class ScriptBase(ScriptDB): from the class 'Script' instead. """ __metaclass__ = TypeclassBase + objects = ScriptManager() def __eq__(self, other): """ diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index 1e389e39b..42d6f3d26 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -46,67 +46,6 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): # common methods for all typed managers. These are used # in other methods. Returns querysets. - def get(self, **kwargs): - """ - Overload the standard get. This will limit itself to only - return the current typeclass. - """ - kwargs.update({"db_typeclass_path":self.model.path}) - return super(TypedObjectManager, self).get(**kwargs) - - def filter(self, **kwargs): - """ - Overload of the standard filter function. This filter will - limit itself to only the current typeclass. - """ - kwargs.update({"db_typeclass_path":self.model.path}) - return super(TypedObjectManager, self).filter(**kwargs) - - def all(self, **kwargs): - """ - Overload method to return all matches, filtering for typeclass - """ - return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path=self.model.path) - - def _get_subclasses(self, cls): - """ - Recursively get all subclasses to a class - """ - all_subclasses = cls.__subclasses__() - for subclass in all_subclasses: - all_subclasses.extend(self._get_subclasses(subclass)) - return all_subclasses - - def get_family(self, **kwargs): - """ - Variation of get that not only returns the current - typeclass but also all subclasses of that typeclass. - """ - paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self._get_subclasses(self.model)] - kwargs.update({"db_typeclass_path__in":paths}) - return super(TypedObjectManager, self).get(**kwargs) - - def filter_family(self, **kwargs): - """ - Variation of filter that allows results both from typeclass - and from subclasses of typeclass - """ - # query, including all subclasses - paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self._get_subclasses(self.model)] - kwargs.update({"db_typeclass_path__in":paths}) - return super(TypedObjectManager, self).filter(**kwargs) - - def all_family(self, **kwargs): - """ - Return all matches, allowing matches from all subclasses of - the typeclass. - """ - paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) - for cls in self._get_subclasses(self.model)] - return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path__in=paths) - # Attribute manager methods def get_attribute(self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None): @@ -337,3 +276,68 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): query = query | Q(db_typeclass_path__exact=parent.path) # actually query the database return self.filter(query) + + +class TypeclassManager(TypedObjectManager): + def get(self, **kwargs): + """ + Overload the standard get. This will limit itself to only + return the current typeclass. + """ + kwargs.update({"db_typeclass_path":self.model.path}) + return super(TypedObjectManager, self).get(**kwargs) + + def filter(self, **kwargs): + """ + Overload of the standard filter function. This filter will + limit itself to only the current typeclass. + """ + kwargs.update({"db_typeclass_path":self.model.path}) + return super(TypedObjectManager, self).filter(**kwargs) + + def all(self, **kwargs): + """ + Overload method to return all matches, filtering for typeclass + """ + return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path=self.model.path) + + def _get_subclasses(self, cls): + """ + Recursively get all subclasses to a class + """ + all_subclasses = cls.__subclasses__() + for subclass in all_subclasses: + all_subclasses.extend(self._get_subclasses(subclass)) + return all_subclasses + + def get_family(self, **kwargs): + """ + Variation of get that not only returns the current + typeclass but also all subclasses of that typeclass. + """ + paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) + for cls in self._get_subclasses(self.model)] + kwargs.update({"db_typeclass_path__in":paths}) + return super(TypedObjectManager, self).get(**kwargs) + + def filter_family(self, **kwargs): + """ + Variation of filter that allows results both from typeclass + and from subclasses of typeclass + """ + # query, including all subclasses + paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) + for cls in self._get_subclasses(self.model)] + kwargs.update({"db_typeclass_path__in":paths}) + return super(TypedObjectManager, self).filter(**kwargs) + + def all_family(self, **kwargs): + """ + Return all matches, allowing matches from all subclasses of + the typeclass. + """ + paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) + for cls in self._get_subclasses(self.model)] + return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path__in=paths) + + From 1be49e7bea59d317179f01fcbb4079df25e6929d Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 09:01:40 +0100 Subject: [PATCH 015/250] Fixed a bug in object manager. --- src/objects/objects.py | 4 ++-- src/utils/idmapper/base.py | 17 +++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/objects/objects.py b/src/objects/objects.py index a90c25b92..249e80184 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -17,7 +17,7 @@ they control by simply linking to a new object's user property. from django.conf import settings from src.typeclasses.models import TypeclassBase -from src.objects.manager import ObjectTypeclassManager +from src.objects.manager import ObjectManager from src.objects.models import ObjectDB from src.commands import cmdset, command from src.utils.logger import log_depmsg @@ -40,7 +40,7 @@ class Object(ObjectDB): """ __metaclass__ = TypeclassBase - objects = ObjectTypeclassManager() + objects = ObjectManager() # __init__ is only defined here in order to present docstring to API. def __init__(self, *args, **kwargs): diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 3224d3f5a..cdfd7cb2e 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -305,28 +305,33 @@ class SharedMemoryModel(Model): # per-instance methods - def set_recache_protection(cls, mode=True): + def set_recache_protection(self, mode=True): "set if this instance should be allowed to be recached." - cls._idmapper_recache_protection = bool(mode) + self._idmapper_recache_protection = bool(mode) - def save(cls, *args, **kwargs): + def save(self, *args, **kwargs): "save method tracking process/thread issues" + # don't allow saving base objects + if not self._meta.proxy: + raise RuntimeError("Don't create instances of %s, " + "use its child typeclasses instead." % self.__class__.__name__) + if _IS_SUBPROCESS: # we keep a store of objects modified in subprocesses so # we know to update their caches in the central process global PROC_MODIFIED_COUNT, PROC_MODIFIED_OBJS PROC_MODIFIED_COUNT += 1 - PROC_MODIFIED_OBJS[PROC_MODIFIED_COUNT] = cls + PROC_MODIFIED_OBJS[PROC_MODIFIED_COUNT] = self if _IS_MAIN_THREAD: # in main thread - normal operation - super(SharedMemoryModel, cls).save(*args, **kwargs) + super(SharedMemoryModel, self).save(*args, **kwargs) else: # in another thread; make sure to save in reactor thread def _save_callback(cls, *args, **kwargs): super(SharedMemoryModel, cls).save(*args, **kwargs) - callFromThread(_save_callback, cls, *args, **kwargs) + callFromThread(_save_callback, self, *args, **kwargs) class WeakSharedMemoryModelBase(SharedMemoryModelBase): From 14086e3b3d123289137309f858f21321a2a8b526 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 14:14:21 +0100 Subject: [PATCH 016/250] General database operations works with reworked, proxy-based typeclass system. --- src/typeclasses/managers.py | 1 + src/typeclasses/models.py | 10 +++++++++- src/utils/idmapper/base.py | 5 ----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index 42d6f3d26..be830f034 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -279,6 +279,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): class TypeclassManager(TypedObjectManager): + def get(self, **kwargs): """ Overload the standard get. This will limit itself to only diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 8e7f3b45d..22046ea9a 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -234,7 +234,7 @@ class AttributeHandler(object): "Initialize handler" self.obj = obj self._objid = obj.id - self._model = to_str(obj.__class__.__name__.lower()) + self._model = to_str(obj.__dbclass__.__name__.lower()) self._cache = None def _recache(self): @@ -1091,6 +1091,7 @@ class TypedObject(SharedMemoryModel): def __init__(self, *args, **kwargs): typeclass_path = kwargs.pop("typeclass", None) super(TypedObject, self).__init__(*args, **kwargs) + self.__dbclass__ = self._meta.proxy_for_model if typeclass_path: self.__class__ = self._import_class(typeclass_path) self.db_typclass_path = typeclass_path @@ -1368,6 +1369,13 @@ class TypedObject(SharedMemoryModel): self._is_deleted = True super(TypedObject, self).delete() + def save(self, *args, **kwargs): + "Block saving non-proxy typeclassed objects" + if not self._meta.proxy: + raise RuntimeError("Don't create instances of %s, " + "use its child typeclasses instead." % self.__class__.__name__) + super(TypedObject, self).save(*args, **kwargs) + # # Memory management # diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index cdfd7cb2e..70e4a8187 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -312,11 +312,6 @@ class SharedMemoryModel(Model): def save(self, *args, **kwargs): "save method tracking process/thread issues" - # don't allow saving base objects - if not self._meta.proxy: - raise RuntimeError("Don't create instances of %s, " - "use its child typeclasses instead." % self.__class__.__name__) - if _IS_SUBPROCESS: # we keep a store of objects modified in subprocesses so # we know to update their caches in the central process From c73e013459879b3700a4ef142dff7a6847f49df8 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 14:34:02 +0100 Subject: [PATCH 017/250] Fixed bug in taghandler that stopped tags.get from working correctly with unset categories. --- src/typeclasses/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 22046ea9a..5c46d055f 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -628,7 +628,7 @@ class TagHandler(object): """ self.obj = obj self._objid = obj.id - self._model = obj.__class__.__name__.lower() + self._model = obj.__dbclass__.__name__.lower() self._cache = None def _recache(self): @@ -661,7 +661,7 @@ class TagHandler(object): cachestring = "%s-%s" % (tagstr, category) self._cache[cachestring] = tagobj - def get(self, key, category="", return_tagobj=False): + def get(self, key, category=None, return_tagobj=False): """ Get the tag for the given key or list of tags. If return_data=True, return the matching Tag objects instead. From 2cc43159418e9563d20c2bfa13e331e284b869e9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 16:30:06 +0100 Subject: [PATCH 018/250] Removed stale remnant of PlayerChannelConnection from comm manager. --- src/comms/managers.py | 110 ------------------------------------ src/comms/models.py | 15 ++--- src/server/initial_setup.py | 3 +- 3 files changed, 7 insertions(+), 121 deletions(-) diff --git a/src/comms/managers.py b/src/comms/managers.py index 1823a4279..cdab16ae2 100644 --- a/src/comms/managers.py +++ b/src/comms/managers.py @@ -302,49 +302,6 @@ class ChannelDBManager(TypedObjectManager): """ return player.dbobj.subscription_set.all() - -# def del_channel(self, channelkey): -# """ -# Delete channel matching channelkey. -# Also cleans up channelhandler. -# """ -# channels = self.filter(db_key__iexact=channelkey) -# if not channels: -# # no aliases allowed for deletion. -# return False -# for channel in channels: -# channel.delete() -# from src.comms.channelhandler import CHANNELHANDLER -# CHANNELHANDLER.update() -# return None - -# def get_all_connections(self, channel, online=False): -# """ -# Return the connections of all players listening -# to this channel. If Online is true, it only returns -# connected players. -# """ -# global _SESSIONS -# if not _SESSIONS: -# from src.server.sessionhandler import SESSIONS as _SESSIONS -# -# PlayerChannelConnection = ContentType.objects.get(app_label="comms", -# model="playerchannelconnection").model_class() -# players = [] -# if online: -# session_list = _SESSIONS.get_sessions() -# unique_online_users = set(sess.uid for sess in session_list if sess.logged_in) -# online_players = (sess.get_player() for sess in session_list if sess.uid in unique_online_users) -# for player in online_players: -# players.extend(PlayerChannelConnection.objects.filter( -# db_player=player.dbobj, db_channel=channel.dbobj)) -# else: -# players.extend(PlayerChannelConnection.objects.get_all_connections(channel)) -# -# external_connections = ExternalChannelConnection.objects.get_all_connections(channel) -# -# return itertools.chain(players, external_connections) - @returns_typeclass_list def channel_search(self, ostring, exact=True): """ @@ -378,70 +335,3 @@ class ChannelManager(ChannelDBManager, TypeclassManager): pass -# -# PlayerChannelConnection manager -# -class PlayerChannelConnectionManager(models.Manager): - """ - This PlayerChannelConnectionManager implements methods for searching - and manipulating PlayerChannelConnections directly from the database. - - These methods will all return database objects - (or QuerySets) directly. - - A PlayerChannelConnection defines a user's subscription to an in-game - channel - deleting the connection object will disconnect the player - from the channel. - - Evennia-specific: - get_all_player_connections - has_connection - get_all_connections - create_connection - break_connection - - """ - @returns_typeclass_list - def get_all_player_connections(self, player): - "Get all connections that the given player has." - player = to_object(player) - return self.filter(db_player=player) - - def has_player_connection(self, player, channel): - "Checks so a connection exists player<->channel" - if player and channel: - return self.filter(db_player=player.dbobj).filter( - db_channel=channel.dbobj).count() > 0 - return False - - def get_all_connections(self, channel): - """ - Get all connections for a channel - """ - channel = to_object(channel, objtype='channel') - return self.filter(db_channel=channel) - - def create_connection(self, player, channel): - """ - Connect a player to a channel. player and channel - can be actual objects or keystrings. - """ - player = to_object(player) - channel = to_object(channel, objtype='channel') - if not player or not channel: - raise CommError("NOTFOUND") - new_connection = self.model(db_player=player, db_channel=channel) - new_connection.save() - return new_connection - - def break_connection(self, player, channel): - "Remove link between player and channel" - player = to_object(player) - channel = to_object(channel, objtype='channel') - if not player or not channel: - raise CommError("NOTFOUND") - conns = self.filter(db_player=player).filter(db_channel=channel) - for conn in conns: - conn.delete() - - diff --git a/src/comms/models.py b/src/comms/models.py index 6b12da2c3..d31e26781 100644 --- a/src/comms/models.py +++ b/src/comms/models.py @@ -174,8 +174,7 @@ class Msg(SharedMemoryModel): Getter. Allows for value = self.receivers. Returns three lists of receivers: players, objects and channels. """ - return [hasattr(o, "typeclass") and o.typeclass or o for o in - list(self.db_receivers_players.all()) + list(self.db_receivers_objects.all())] + return list(self.db_receivers_players.all()) + list(self.db_receivers_objects.all()) #@receivers.setter def __receivers_set(self, value): @@ -371,7 +370,7 @@ class ChannelDB(TypedObject): # def __str__(self): - return "Channel '%s' (%s)" % (self.key, self.typeclass.db.desc) + return "Channel '%s' (%s)" % (self.key, self.db.desc) def has_connection(self, player): """ @@ -387,33 +386,31 @@ class ChannelDB(TypedObject): "Connect the user to this channel. This checks access." if hasattr(player, "player"): player = player.player - player = player.typeclass # check access if not self.access(player, 'listen'): return False # pre-join hook - connect = self.typeclass.pre_join_channel(player) + connect = self.pre_join_channel(player) if not connect: return False # subscribe self.db_subscriptions.add(player.dbobj) # post-join hook - self.typeclass.post_join_channel(player) + self.post_join_channel(player) return True def disconnect(self, player): "Disconnect user from this channel." if hasattr(player, "player"): player = player.player - player = player.typeclass # pre-disconnect hook - disconnect = self.typeclass.pre_leave_channel(player) + disconnect = self.pre_leave_channel(player) if not disconnect: return False # disconnect self.db_subscriptions.remove(player.dbobj) # post-disconnect hook - self.typeclass.post_leave_channel(player.dbobj) + self.post_leave_channel(player.dbobj) return True def access(self, accessing_obj, access_type='listen', default=False): diff --git a/src/server/initial_setup.py b/src/server/initial_setup.py index a7af784ed..10b5e9049 100644 --- a/src/server/initial_setup.py +++ b/src/server/initial_setup.py @@ -258,9 +258,8 @@ def handle_setup(last_step): for profile in PlayerDB.objects.all(): profile.delete() elif last_step + num == 3: - from src.comms.models import ChannelDB, PlayerChannelConnection + from src.comms.models import ChannelDB ChannelDB.objects.all().delete() - PlayerChannelConnection.objects.all().delete() raise ServerConfig.objects.conf("last_initial_setup_step", last_step + num + 1) # We got through the entire list. Set last_step to -1 so we don't From e214c036c8dfc13f028c07dba3afddbc7d2e93d0 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 21:27:32 +0100 Subject: [PATCH 019/250] Resolved the setting of __dbclass__ which happened in the wrong order. --- src/typeclasses/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 5c46d055f..21f298ddc 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -1091,7 +1091,6 @@ class TypedObject(SharedMemoryModel): def __init__(self, *args, **kwargs): typeclass_path = kwargs.pop("typeclass", None) super(TypedObject, self).__init__(*args, **kwargs) - self.__dbclass__ = self._meta.proxy_for_model if typeclass_path: self.__class__ = self._import_class(typeclass_path) self.db_typclass_path = typeclass_path @@ -1099,6 +1098,8 @@ class TypedObject(SharedMemoryModel): self.__class__ = self._import_class(self.db_typeclass_path) else: self.db_typeclass_path = "%s.%s" % (self.__module__, self.__class__.__name__) + # important to put this at the end since _meta is based on the set __class__ + self.__dbclass__ = self._meta.proxy_for_model # initialize all handlers in a lazy fashion @lazy_property From a93d318121bf5b97a33403bdef52b658a9eea240 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 21:46:52 +0100 Subject: [PATCH 020/250] Starting to remove obj.typeclass references. Server now starts and stops cleanly, but not yet tested with actual gameplay. --- src/scripts/models.py | 3 +-- src/server/server.py | 29 +++++++++++------------------ src/utils/idmapper/base.py | 2 -- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/scripts/models.py b/src/scripts/models.py index c1717fc0b..99ec793f4 100644 --- a/src/scripts/models.py +++ b/src/scripts/models.py @@ -124,8 +124,7 @@ class ScriptDB(TypedObject): obj = _GA(self, "db_player") if not obj: obj = _GA(self, "db_obj") - if obj: - return obj.typeclass + return obj def __set_obj(self, value): """ diff --git a/src/server/server.py b/src/server/server.py index a32b0d980..618fe5e32 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -232,8 +232,8 @@ class Evennia(object): self.update_defaults() #print "run_init_hooks:", ObjectDB.get_all_cached_instances() - [(o.typeclass, o.at_init()) for o in ObjectDB.get_all_cached_instances()] - [(p.typeclass, p.at_init()) for p in PlayerDB.get_all_cached_instances()] + [o.at_init() for o in ObjectDB.get_all_cached_instances()] + [p.at_init() for p in PlayerDB.get_all_cached_instances()] with open(SERVER_RESTART, 'r') as f: mode = f.read() @@ -305,11 +305,9 @@ class Evennia(object): if mode == 'reload': # call restart hooks - yield [(o.typeclass, o.at_server_reload()) - for o in ObjectDB.get_all_cached_instances()] - yield [(p.typeclass, p.at_server_reload()) - for p in PlayerDB.get_all_cached_instances()] - yield [(s.typeclass, s.pause(), s.at_server_reload()) + yield [o.at_server_reload() for o in ObjectDB.get_all_cached_instances()] + yield [p.at_server_reload() for p in PlayerDB.get_all_cached_instances()] + yield [(s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()] yield self.sessions.all_sessions_portal_sync() ServerConfig.objects.conf("server_restart_mode", "reload") @@ -325,19 +323,14 @@ class Evennia(object): if mode == 'reset': # don't unset the is_connected flag on reset, otherwise # same as shutdown - yield [(o.typeclass, o.at_server_shutdown()) - for o in ObjectDB.get_all_cached_instances()] - yield [(p.typeclass, p.at_server_shutdown()) - for p in PlayerDB.get_all_cached_instances()] + yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()] + yield [p.at_server_shutdown() for p in PlayerDB.get_all_cached_instances()] else: # shutdown - yield [_SA(p, "is_connected", False) - for p in PlayerDB.get_all_cached_instances()] - yield [(o.typeclass, o.at_server_shutdown()) - for o in ObjectDB.get_all_cached_instances()] - yield [(p.typeclass, p.unpuppet_all(), p.at_server_shutdown()) + yield [_SA(p, "is_connected", False) for p in PlayerDB.get_all_cached_instances()] + yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()] + yield [(p.unpuppet_all(), p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()] - yield [(s.typeclass, s.at_server_shutdown()) - for s in ScriptDB.get_all_cached_instances()] + yield [s.at_server_shutdown() for s in ScriptDB.get_all_cached_instances()] yield ObjectDB.objects.clear_all_sessids() ServerConfig.objects.conf("server_restart_mode", "reset") diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 70e4a8187..0b6eb8203 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -266,7 +266,6 @@ class SharedMemoryModel(Model): def get_all_cached_instances(cls): "return the objects so far cached by idmapper for this class." return cls.__instance_cache__.values() - #get_all_cached_instances = classmethod(get_all_cached_instances) @classmethod def _flush_cached_by_key(cls, key, force=True): @@ -276,7 +275,6 @@ class SharedMemoryModel(Model): del cls.__instance_cache__[key] except KeyError: pass - #_flush_cached_by_key = classmethod(_flush_cached_by_key) @classmethod def flush_cached_instance(cls, instance, force=True): From c6c91c7a0bfafa75d05ca39db49a6bef2096d62e Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 22:04:52 +0100 Subject: [PATCH 021/250] Removing some more .typeclass properties. --- src/comms/models.py | 3 +- src/help/models.py | 94 ------------------------------------------- src/objects/models.py | 18 ++++----- src/players/models.py | 18 ++++----- 4 files changed, 19 insertions(+), 114 deletions(-) diff --git a/src/comms/models.py b/src/comms/models.py index d31e26781..755f6bee3 100644 --- a/src/comms/models.py +++ b/src/comms/models.py @@ -121,8 +121,7 @@ class Msg(SharedMemoryModel): #@property def __senders_get(self): "Getter. Allows for value = self.sender" - return [hasattr(o, "typeclass") and o.typeclass or o for o in - list(self.db_sender_players.all()) + + return list(self.db_sender_players.all()) + list(self.db_sender_objects.all()) + self.extra_senders] diff --git a/src/help/models.py b/src/help/models.py index d81f7a284..40ce080e7 100644 --- a/src/help/models.py +++ b/src/help/models.py @@ -82,100 +82,6 @@ class HelpEntry(SharedMemoryModel): verbose_name = "Help Entry" verbose_name_plural = "Help Entries" - # Wrapper properties to easily set database fields. These are - # @property decorators that allows to access these fields using - # normal python operations (without having to remember to save() - # etc). So e.g. a property 'attr' has a get/set/del decorator - # defined that allows the user to do self.attr = value, - # value = self.attr and del self.attr respectively (where self - # is the object in question). - - # key property (wraps db_key) - #@property - #def __key_get(self): - # "Getter. Allows for value = self.key" - # return self.db_key - ##@key.setter - #def __key_set(self, value): - # "Setter. Allows for self.key = value" - # self.db_key = value - # self.save() - ##@key.deleter - #def __key_del(self): - # "Deleter. Allows for del self.key. Deletes entry." - # self.delete() - #key = property(__key_get, __key_set, __key_del) - - ## help_category property (wraps db_help_category) - ##@property - #def __help_category_get(self): - # "Getter. Allows for value = self.help_category" - # return self.db_help_category - ##@help_category.setter - #def __help_category_set(self, value): - # "Setter. Allows for self.help_category = value" - # self.db_help_category = value - # self.save() - ##@help_category.deleter - #def __help_category_del(self): - # "Deleter. Allows for del self.help_category" - # self.db_help_category = "General" - # self.save() - #help_category = property(__help_category_get, __help_category_set, __help_category_del) - - ## entrytext property (wraps db_entrytext) - ##@property - #def __entrytext_get(self): - # "Getter. Allows for value = self.entrytext" - # return self.db_entrytext - ##@entrytext.setter - #def __entrytext_set(self, value): - # "Setter. Allows for self.entrytext = value" - # self.db_entrytext = value - # self.save() - ##@entrytext.deleter - #def __entrytext_del(self): - # "Deleter. Allows for del self.entrytext" - # self.db_entrytext = "" - # self.save() - #entrytext = property(__entrytext_get, __entrytext_set, __entrytext_del) - - ## permissions property - ##@property - #def __permissions_get(self): - # "Getter. Allows for value = self.permissions. Returns a list of permissions." - # return [perm.strip() for perm in self.db_permissions.split(',')] - ##@permissions.setter - #def __permissions_set(self, value): - # "Setter. Allows for self.permissions = value. Stores as a comma-separated string." - # if is_iter(value): - # value = ",".join([str(val).strip().lower() for val in value]) - # self.db_permissions = value - # self.save() - ##@permissions.deleter - #def __permissions_del(self): - # "Deleter. Allows for del self.permissions" - # self.db_permissions = "" - # self.save() - #permissions = property(__permissions_get, __permissions_set, __permissions_del) - - # lock_storage property (wraps db_lock_storage) - ##@property - #def __lock_storage_get(self): - # "Getter. Allows for value = self.lock_storage" - # return self.db_lock_storage - ##@nick.setter - #def __lock_storage_set(self, value): - # """Saves the lock_storagetodate. This is usually not called directly, but through self.lock()""" - # self.db_lock_storage = value - # self.save() - ##@nick.deleter - #def __lock_storage_del(self): - # "Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead""" - # logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self) - #lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del) - - # # # HelpEntry main class methods diff --git a/src/objects/models.py b/src/objects/models.py index 5a2b6ceeb..5eca888b1 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -510,7 +510,7 @@ class ObjectDB(TypedObject): raw_string = to_unicode(raw_string) raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=True) - return cmdhandler.cmdhandler(_GA(self, "typeclass"), raw_string, callertype="object", sessid=sessid, **kwargs) + return cmdhandler.cmdhandler(self, raw_string, callertype="object", sessid=sessid, **kwargs) def msg(self, text=None, from_obj=None, sessid=0, **kwargs): """ @@ -543,11 +543,11 @@ class ObjectDB(TypedObject): if from_obj: # call hook try: - _GA(from_obj, "at_msg_send")(text=text, to_obj=_GA(self, "typeclass"), **kwargs) + from_obj.at_msg_send(text=text, to_obj=self, **kwargs) except Exception: logger.log_trace() try: - if not _GA(_GA(self, "typeclass"), "at_msg_receive")(text=text, **kwargs): + if not self.at_msg_receive(text=text, **kwargs) # if at_msg_receive returns false, we abort message to this object return except Exception: @@ -625,7 +625,7 @@ class ObjectDB(TypedObject): # Before the move, call eventual pre-commands. try: - if not self.at_before_move(_GA(destination, "typeclass")): + if not self.at_before_move(destination): return except Exception: logerr(errtxt % "at_before_move()") @@ -646,7 +646,7 @@ class ObjectDB(TypedObject): # Call hook on source location try: - source_location.at_object_leave(_GA(self, "typeclass"), _GA(destination, "typeclass")) + source_location.at_object_leave(self, destination) except Exception: logerr(errtxt % "at_object_leave()") #emit_to_obj.msg(errtxt % "at_object_leave()") @@ -656,7 +656,7 @@ class ObjectDB(TypedObject): if not quiet: #tell the old room we are leaving try: - self.announce_move_from(_GA(destination, "typeclass")) + self.announce_move_from(destination) except Exception: logerr(errtxt % "at_announce_move()") #emit_to_obj.msg(errtxt % "at_announce_move()" ) @@ -675,7 +675,7 @@ class ObjectDB(TypedObject): if not quiet: # Tell the new room we are there. try: - self.announce_move_to(_GA(source_location, "typeclass")) + self.announce_move_to(source_location) except Exception: logerr(errtxt % "announce_move_to()") #emit_to_obj.msg(errtxt % "announce_move_to()") @@ -685,7 +685,7 @@ class ObjectDB(TypedObject): # Perform eventual extra commands on the receiving location # (the object has already arrived at this point) try: - destination.at_object_receive(_GA(self, "typeclass"), _GA(source_location, "typeclass")) + destination.at_object_receive(self, source_location) except Exception: logerr(errtxt % "at_object_receive()") #emit_to_obj.msg(errtxt % "at_object_receive()") @@ -695,7 +695,7 @@ class ObjectDB(TypedObject): # Execute eventual extra commands on this object after moving it # (usually calling 'look') try: - self.at_after_move(_GA(source_location, "typeclass")) + self.at_after_move(source_location) except Exception: logerr(errtxt % "at_after_move") #emit_to_obj.msg(errtxt % "at_after_move()") diff --git a/src/players/models.py b/src/players/models.py index 90bef3c7a..ebe0ad67e 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -239,7 +239,7 @@ class PlayerDB(TypedObject, AbstractUser): if from_obj: # call hook try: - _GA(from_obj, "at_msg_send")(text=text, to_obj=_GA(self, "typeclass"), **kwargs) + _GA(from_obj, "at_msg_send")(text=text, to_obj=self, **kwargs) except Exception: pass sessions = _MULTISESSION_MODE > 1 and sessid and _GA(self, "get_session")(sessid) or None @@ -318,7 +318,7 @@ class PlayerDB(TypedObject, AbstractUser): # server kill or similar if normal_mode: - _GA(obj.typeclass, "at_pre_puppet")(_GA(self, "typeclass"), sessid=sessid) + obj.at_pre_puppet(self, sessid=sessid) # do the connection obj.sessid.add(sessid) obj.player = self @@ -327,7 +327,7 @@ class PlayerDB(TypedObject, AbstractUser): # validate/start persistent scripts on object ScriptDB.objects.validate(obj=obj) if normal_mode: - _GA(obj.typeclass, "at_post_puppet")() + obj.at_post_puppet() return True def unpuppet_object(self, sessid): @@ -345,11 +345,11 @@ class PlayerDB(TypedObject, AbstractUser): if not obj: return False # do the disconnect, but only if we are the last session to puppet - _GA(obj.typeclass, "at_pre_unpuppet")() + obj.at_pre_unpuppet() obj.dbobj.sessid.remove(sessid) if not obj.dbobj.sessid.count(): del obj.dbobj.player - _GA(obj.typeclass, "at_post_unpuppet")(_GA(self, "typeclass"), sessid=sessid) + obj.at_post_unpuppet(self, sessid=sessid) session.puppet = None session.puid = None return True @@ -377,7 +377,7 @@ class PlayerDB(TypedObject, AbstractUser): return None if return_dbobj: return session.puppet - return session.puppet and session.puppet.typeclass or None + return session.puppet and session.puppet or None def get_all_puppets(self, return_dbobj=False): """ @@ -387,7 +387,7 @@ class PlayerDB(TypedObject, AbstractUser): if session.puppet] if return_dbobj: return puppets - return [puppet.typeclass for puppet in puppets] + return [puppet for puppet in puppets] def __get_single_puppet(self): """ @@ -447,7 +447,7 @@ class PlayerDB(TypedObject, AbstractUser): except IndexError: # this can happen for bots sessid = None - return cmdhandler.cmdhandler(self.typeclass, raw_string, + return cmdhandler.cmdhandler(self, raw_string, callertype="player", sessid=sessid, **kwargs) def search(self, searchdata, return_puppet=False, **kwargs): @@ -469,7 +469,7 @@ class PlayerDB(TypedObject, AbstractUser): return_puppet = kwargs.get("return_character") matches = _GA(self, "__class__").objects.player_search(searchdata) - matches = _AT_SEARCH_RESULT(_GA(self, "typeclass"), searchdata, matches, global_search=True) + matches = _AT_SEARCH_RESULT(self, searchdata, matches, global_search=True) if matches and return_puppet: try: return _GA(matches, "puppet") From 9321573c23edac6b8115a6baf467a9f0300bdac3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 22:14:23 +0100 Subject: [PATCH 022/250] Fixed a typeclass call. --- src/typeclasses/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 21f298ddc..284373f8d 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -1263,7 +1263,7 @@ class TypedObject(SharedMemoryModel): _SA(self, "typeclass_path", new_typeclass.strip()) # this will automatically use a default class if # there is an error with the given typeclass. - new_typeclass = self.typeclass + new_typeclass = self if self.typeclass_path != new_typeclass.path and no_default: # something went wrong; the default was loaded instead, # and we don't allow that; instead we return to previous. From 0b5e2b94ff68039cb202377833bd3e537f31f56b Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 22:46:58 +0100 Subject: [PATCH 023/250] Starting the move of typeclass methods off *DB models and onto the typeclasses. --- src/comms/comms.py | 6 +++--- src/comms/models.py | 6 +++--- src/objects/models.py | 2 +- src/players/player.py | 18 +++++++++--------- src/server/serversession.py | 8 ++++---- src/typeclasses/managers.py | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/comms/comms.py b/src/comms/comms.py index 8ed3e945e..5e62202c1 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -169,8 +169,8 @@ class Channel(ChannelDB): this channel, and sending them a message. """ # get all players connected to this channel and send to them - for player in self.dbobj.db_subscriptions.all(): - player = player.typeclass + for player in self.db_subscriptions.all(): + player = player try: # note our addition of the from_channel keyword here. This could be checked # by a custom player.msg() to treat channel-receives differently. @@ -222,7 +222,7 @@ class Channel(ChannelDB): msgobj = TempMsg() msgobj.header = header msgobj.message = msg - msgobj.channels = [self.dbobj] # add this channel + msgobj.channels = [self] # add this channel if not msgobj.senders: msgobj.senders = senders diff --git a/src/comms/models.py b/src/comms/models.py index 755f6bee3..fa822d81a 100644 --- a/src/comms/models.py +++ b/src/comms/models.py @@ -121,9 +121,9 @@ class Msg(SharedMemoryModel): #@property def __senders_get(self): "Getter. Allows for value = self.sender" - return list(self.db_sender_players.all()) + - list(self.db_sender_objects.all()) + - self.extra_senders] + return list(self.db_sender_players.all()) + \ + list(self.db_sender_objects.all()) + \ + self.extra_senders #@sender.setter def __senders_set(self, value): diff --git a/src/objects/models.py b/src/objects/models.py index 5eca888b1..fef5912cc 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -547,7 +547,7 @@ class ObjectDB(TypedObject): except Exception: logger.log_trace() try: - if not self.at_msg_receive(text=text, **kwargs) + if not self.at_msg_receive(text=text, **kwargs): # if at_msg_receive returns false, we abort message to this object return except Exception: diff --git a/src/players/player.py b/src/players/player.py index 653c725c1..6a6198e31 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -123,8 +123,8 @@ class Player(PlayerDB): a command on a Character, the character automatically stores and handles the sessid). kwargs - extra data to send through protocol - """ - self.dbobj.msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) + """ + self.msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) def swap_character(self, new_character, delete_old_character=False): """ @@ -135,7 +135,7 @@ class Player(PlayerDB): Returns: True/False depending on if swap suceeded or not. """ - return self.dbobj.swap_character(new_character, delete_old_character=delete_old_character) + return self.swap_character(new_character, delete_old_character=delete_old_character) def execute_cmd(self, raw_string, sessid=None, **kwargs): """ @@ -163,7 +163,7 @@ class Player(PlayerDB): be useful for coders intending to implement some sort of nested command structure. """ - return self.dbobj.execute_cmd(raw_string, sessid=sessid, **kwargs) + return self.execute_cmd(raw_string, sessid=sessid, **kwargs) def search(self, searchdata, return_puppet=False, **kwargs): """ @@ -183,7 +183,7 @@ class Player(PlayerDB): # handle wrapping of common terms if searchdata.lower() in ("me", "*me", "self", "*self",): return self - return self.dbobj.search(searchdata, return_puppet=return_puppet, **kwargs) + return self.search(searchdata, return_puppet=return_puppet, **kwargs) def is_typeclass(self, typeclass, exact=False): """ @@ -200,7 +200,7 @@ class Player(PlayerDB): Returns: Boolean """ - return self.dbobj.is_typeclass(typeclass, exact=exact) + return self.is_typeclass(typeclass, exact=exact) def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True): """ @@ -235,7 +235,7 @@ class Player(PlayerDB): boolean True/False depending on if the swap worked or not. """ - self.dbobj.swap_typeclass(new_typeclass, + self.swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default) def access(self, accessing_obj, access_type='read', default=False, **kwargs): @@ -248,7 +248,7 @@ class Player(PlayerDB): default (bool) - what to return if no lock of access_type was found **kwargs - passed to the at_access hook along with the result. """ - result = self.dbobj.access(accessing_obj, access_type=access_type, default=default) + result = self.access(accessing_obj, access_type=access_type, default=default) self.at_access(result, accessing_obj, access_type, **kwargs) return result @@ -261,7 +261,7 @@ class Player(PlayerDB): on the object. (example: 'Builders') Note that this method does -not- call the at_access hook. """ - return self.dbobj.check_permstring(permstring) + return self.check_permstring(permstring) ## player hooks diff --git a/src/server/serversession.py b/src/server/serversession.py index 1a8896b75..c6b721839 100644 --- a/src/server/serversession.py +++ b/src/server/serversession.py @@ -115,18 +115,18 @@ class ServerSession(Session): if self.logged_in: sessid = self.sessid player = self.player - _GA(player.dbobj, "unpuppet_object")(sessid) - uaccount = player.dbobj + player.unpuppet_object(sessid) + uaccount = player uaccount.last_login = datetime.now() uaccount.save() # calling player hook - _GA(player.typeclass, "at_disconnect")() + player.at_disconnect() self.logged_in = False if not self.sessionhandler.sessions_from_player(player): # no more sessions connected to this player player.is_connected = False # this may be used to e.g. delete player after disconnection etc - _GA(player.typeclass, "at_post_disconnect")() + player.at_post_disconnect() def get_player(self): """ diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index be830f034..900ac2914 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -34,7 +34,7 @@ def returns_typeclass(method): def func(self, *args, **kwargs): self.__doc__ = method.__doc__ query = method(self, *args, **kwargs) - return list(query)[0] if query else None + return query return update_wrapper(func, method) # Managers From 749715a1932af61ed20f746e0eadbef0953d68e9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 22 Dec 2014 23:03:00 +0100 Subject: [PATCH 024/250] Server can be connected to, not yet get to a command line; may need to work on different app_labels. --- src/commands/cmdhandler.py | 6 +++--- src/objects/models.py | 17 +++++++---------- src/objects/objects.py | 24 ++++++++++++------------ src/players/player.py | 16 ++++++++-------- src/scripts/manager.py | 7 +++---- 5 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/commands/cmdhandler.py b/src/commands/cmdhandler.py index de21c3c1c..c24ca3c69 100644 --- a/src/commands/cmdhandler.py +++ b/src/commands/cmdhandler.py @@ -130,7 +130,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, if location and not obj_cmdset.no_objs: # Gather all cmdsets stored on objects in the room and # also in the caller's inventory and the location itself - local_objlist = yield (location.contents_get(exclude=obj.dbobj) + + local_objlist = yield (location.contents_get(exclude=obj) + obj.contents + [location]) for lobj in local_objlist: @@ -283,11 +283,11 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess session = called_by player = session.player if player: - obj = yield _GA(player.dbobj, "get_puppet")(session.sessid) + obj = yield player.get_puppet(session.sessid) elif callertype == "player": player = called_by if sessid: - obj = yield _GA(player.dbobj, "get_puppet")(sessid) + obj = yield player.get_puppet(sessid) elif callertype == "object": obj = called_by else: diff --git a/src/objects/models.py b/src/objects/models.py index fef5912cc..d1f7135d1 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -252,14 +252,14 @@ class ObjectDB(TypedObject): raise RuntimeError elif loc == None: raise RuntimeWarning - return is_loc_loop(_GA(_GA(loc, "dbobj"), "db_location"), depth + 1) + return is_loc_loop(loc.db_location, depth + 1) try: is_loc_loop(location) except RuntimeWarning: pass # actually set the field - _SA(_GA(self, "dbobj"), "db_location", _GA(location, "dbobj") if location else location) - _GA(_GA(self, "dbobj"), "save")(update_fields=["db_location"]) + self.db_location = location + self.save(update_fields=["db_location"]) except RuntimeError: errmsg = "Error: %s.location = %s creates a location loop." % (self.key, location) logger.log_errmsg(errmsg) @@ -271,8 +271,8 @@ class ObjectDB(TypedObject): def __location_del(self): "Cleanly delete the location reference" - _SA(_GA(self, "dbobj"), "db_location", None) - _GA(_GA(self, "dbobj"), "save")(upate_fields=["db_location"]) + self.db_location = None + self.save(update_fields=["db_location"]) location = property(__location_get, __location_set, __location_del) class Meta: @@ -323,8 +323,7 @@ class ObjectDB(TypedObject): exclude is one or more objects to not return """ if exclude: - exclude = [obj.dbobj for obj in make_iter(exclude)] - return ObjectDB.objects.get_contents(self, excludeobj=exclude) + return ObjectDB.objects.get_contents(self, excludeobj=make_iter(exclude)) return ObjectDB.objects.get_contents(self) contents = property(contents_get) @@ -423,7 +422,7 @@ class ObjectDB(TypedObject): # location(s) were given candidates = [] for obj in make_iter(location): - candidates.extend([o.dbobj for o in obj.contents]) + candidates.extend(obj.contents) else: # local search. Candidates are self.contents, self.location # and self.location.contents @@ -434,8 +433,6 @@ class ObjectDB(TypedObject): else: # normally we are included in location.contents candidates.append(self) - # db manager expects database objects - candidates = [obj.dbobj for obj in candidates] results = ObjectDB.objects.object_search(searchdata, attribute_name=attribute_name, diff --git a/src/objects/objects.py b/src/objects/objects.py index 249e80184..6c62c0a29 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -268,7 +268,7 @@ class Object(ObjectDB): if searchdata.lower() in ("me", "self",): return self - return self.dbobj.search(searchdata, + return super(Object, self).search(searchdata, global_search=global_search, use_nicks=use_nicks, typeclass=typeclass, @@ -305,7 +305,7 @@ class Object(ObjectDB): # searchdata is a string; wrap some common self-references if searchdata.lower() in ("me", "self",): return self.player - return self.dbobj.search_player(searchdata, quiet=quiet) + return super(Object, self).search_player(searchdata, quiet=quiet) def execute_cmd(self, raw_string, sessid=None, **kwargs): """ @@ -333,7 +333,7 @@ class Object(ObjectDB): useful for coders intending to implement some sort of nested command structure. """ - return self.dbobj.execute_cmd(raw_string, sessid=sessid, **kwargs) + return super(Object, self).execute_cmd(raw_string, sessid=sessid, **kwargs) def msg(self, text=None, from_obj=None, sessid=None, **kwargs): """ @@ -347,7 +347,7 @@ class Object(ObjectDB): default to self.sessid or from_obj.sessid. """ - self.dbobj.msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) + super(Object, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) def msg_contents(self, text=None, exclude=None, from_obj=None, **kwargs): """ @@ -356,7 +356,7 @@ class Object(ObjectDB): exclude is a list of objects not to send to. See self.msg() for more info. """ - self.dbobj.msg_contents(text, exclude=exclude, + super(Object, self).msg_contents(text, exclude=exclude, from_obj=from_obj, **kwargs) def move_to(self, destination, quiet=False, @@ -387,7 +387,7 @@ class Object(ObjectDB): emit_to_obj. """ - return self.dbobj.move_to(destination, quiet=quiet, + return super(Object, self).move_to(destination, quiet=quiet, emit_to_obj=emit_to_obj, use_destination=use_destination) @@ -402,7 +402,7 @@ class Object(ObjectDB): _copy by default. Returns: Object (copy of this one) """ - return self.dbobj.copy(new_key=new_key) + return super(Object, self).copy(new_key=new_key) def delete(self): """ @@ -415,7 +415,7 @@ class Object(ObjectDB): were errors during deletion or deletion otherwise failed. """ - return self.dbobj.delete() + return super(Object, self).delete() # methods inherited from the typeclass system @@ -434,7 +434,7 @@ class Object(ObjectDB): Returns: Boolean """ - return self.dbobj.is_typeclass(typeclass, exact=exact) + return super(Object, self).is_typeclass(typeclass, exact=exact) def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True): """ @@ -470,7 +470,7 @@ class Object(ObjectDB): """ - return self.dbobj.swap_typeclass(new_typeclass, + return super(Object, self).swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default) def access(self, accessing_obj, access_type='read', default=False, **kwargs): @@ -483,7 +483,7 @@ class Object(ObjectDB): default (bool) - what to return if no lock of access_type was found **kwargs - passed to at_access hook along with result,accessing_obj and access_type """ - result = self.dbobj.access(accessing_obj, access_type=access_type, default=default) + result = super(Object, self).access(accessing_obj, access_type=access_type, default=default) self.at_access(result, accessing_obj, access_type, **kwargs) return result @@ -504,7 +504,7 @@ class Object(ObjectDB): permission on the object. (example: 'Builders') """ - return self.dbobj.check_permstring(permstring) + return super(Object, self).check_permstring(permstring) def __eq__(self, other): """ diff --git a/src/players/player.py b/src/players/player.py index 6a6198e31..7cba8e61c 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -124,7 +124,7 @@ class Player(PlayerDB): handles the sessid). kwargs - extra data to send through protocol """ - self.msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) + super(Player, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) def swap_character(self, new_character, delete_old_character=False): """ @@ -135,7 +135,7 @@ class Player(PlayerDB): Returns: True/False depending on if swap suceeded or not. """ - return self.swap_character(new_character, delete_old_character=delete_old_character) + return super(Player, self).swap_character(new_character, delete_old_character=delete_old_character) def execute_cmd(self, raw_string, sessid=None, **kwargs): """ @@ -163,7 +163,7 @@ class Player(PlayerDB): be useful for coders intending to implement some sort of nested command structure. """ - return self.execute_cmd(raw_string, sessid=sessid, **kwargs) + return super(Player, self).execute_cmd(raw_string, sessid=sessid, **kwargs) def search(self, searchdata, return_puppet=False, **kwargs): """ @@ -183,7 +183,7 @@ class Player(PlayerDB): # handle wrapping of common terms if searchdata.lower() in ("me", "*me", "self", "*self",): return self - return self.search(searchdata, return_puppet=return_puppet, **kwargs) + return super(Player, self).search(searchdata, return_puppet=return_puppet, **kwargs) def is_typeclass(self, typeclass, exact=False): """ @@ -200,7 +200,7 @@ class Player(PlayerDB): Returns: Boolean """ - return self.is_typeclass(typeclass, exact=exact) + return super(Player, self).is_typeclass(typeclass, exact=exact) def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True): """ @@ -235,7 +235,7 @@ class Player(PlayerDB): boolean True/False depending on if the swap worked or not. """ - self.swap_typeclass(new_typeclass, + super(Player, self).swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default) def access(self, accessing_obj, access_type='read', default=False, **kwargs): @@ -248,7 +248,7 @@ class Player(PlayerDB): default (bool) - what to return if no lock of access_type was found **kwargs - passed to the at_access hook along with the result. """ - result = self.access(accessing_obj, access_type=access_type, default=default) + result = super(Player, self).access(accessing_obj, access_type=access_type, default=default) self.at_access(result, accessing_obj, access_type, **kwargs) return result @@ -261,7 +261,7 @@ class Player(PlayerDB): on the object. (example: 'Builders') Note that this method does -not- call the at_access hook. """ - return self.check_permstring(permstring) + return super(Player, self).check_permstring(permstring) ## player hooks diff --git a/src/scripts/manager.py b/src/scripts/manager.py index 6c82077d4..b2509eb1f 100644 --- a/src/scripts/manager.py +++ b/src/scripts/manager.py @@ -44,7 +44,6 @@ class ScriptDBManager(TypedObjectManager): """ if not obj: return [] - obj = obj.dbobj player = _GA(_GA(obj, "__class__"), "__name__") == "PlayerDB" if key: dbref = self.dbref(key) @@ -161,7 +160,7 @@ class ScriptDBManager(TypedObjectManager): # turn off the activity flag for all remaining scripts scripts = self.get_all_scripts() for script in scripts: - script.dbobj.is_active = False + script.is_active = False elif not scripts: # normal operation @@ -180,7 +179,7 @@ class ScriptDBManager(TypedObjectManager): #print "scripts to validate: [%s]" % (", ".join(script.key for script in scripts)) for script in scripts: - #print "validating %s (%i) (init_mode=%s)" % (script.key, id(script.dbobj), init_mode) + #print "validating %s (%i) (init_mode=%s)" % (script.key, id(script), init_mode) if script.is_valid(): nr_started += script.start(force_restart=init_mode) #print "back from start. nr_started=", nr_started @@ -212,7 +211,7 @@ class ScriptDBManager(TypedObjectManager): return [dbref_match] # not a dbref; normal search - obj_restriction = obj and Q(db_obj=obj.dbobj) or Q() + obj_restriction = obj and Q(db_obj=obj) or Q() timed_restriction = only_timed and Q(interval__gt=0) or Q() scripts = self.filter(timed_restriction & obj_restriction & Q(db_key__iexact=ostring)) return scripts From 969b947ba0ed077e210bb31545d1352e51b7144f Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 23 Dec 2014 11:53:32 +0100 Subject: [PATCH 025/250] One needs to turn off all imports of typeclasses in __init__ or django will create migrations for them. This might be interesting for the future but not for development. --- ev.py | 4 ++-- src/objects/models.py | 1 + src/objects/objects.py | 43 ++++++++++++++++++++------------------- src/players/player.py | 24 +++++++++++----------- src/scripts/__init__.py | 4 +++- src/scripts/scripts.py | 1 - src/settings_default.py | 12 +++++------ src/typeclasses/models.py | 6 ++++-- 8 files changed, 50 insertions(+), 45 deletions(-) diff --git a/ev.py b/ev.py index af05cb09c..f5fe439bf 100644 --- a/ev.py +++ b/ev.py @@ -100,7 +100,7 @@ from src.help.models import HelpEntry from src.typeclasses.models import Attribute # players -from src.players.player import Player +from src.players.player import DefaultPlayer from src.players.models import PlayerDB # commands @@ -119,7 +119,7 @@ from src.comms.models import Msg, ChannelDB from src.comms.comms import Channel # objects -from src.objects.objects import Object, Character, Room, Exit +from src.objects.objects import DefaultObject, DefaultCharacter, DefaultRoom, DefaultExit # utils diff --git a/src/objects/models.py b/src/objects/models.py index d1f7135d1..9bab7715c 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -843,3 +843,4 @@ class ObjectDB(TypedObject): # Perform the deletion of the object super(ObjectDB, self).delete() return True + diff --git a/src/objects/objects.py b/src/objects/objects.py index 6c62c0a29..0a949eb93 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -33,7 +33,7 @@ _DA = object.__delattr__ # Base class to inherit from. # -class Object(ObjectDB): +class DefaultObject(ObjectDB): """ This is the base class for all in-game objects. Inherit from this to create different types of objects in the game. @@ -194,7 +194,7 @@ class Object(ObjectDB): this object speaks """ - super(Object, self).__init__(*args, **kwargs) + super(DefaultObject, self).__init__(*args, **kwargs) ## methods inherited from the database object (overload them here) @@ -268,7 +268,7 @@ class Object(ObjectDB): if searchdata.lower() in ("me", "self",): return self - return super(Object, self).search(searchdata, + return super(DefaultObject, self).search(searchdata, global_search=global_search, use_nicks=use_nicks, typeclass=typeclass, @@ -305,7 +305,7 @@ class Object(ObjectDB): # searchdata is a string; wrap some common self-references if searchdata.lower() in ("me", "self",): return self.player - return super(Object, self).search_player(searchdata, quiet=quiet) + return super(DefaultObject, self).search_player(searchdata, quiet=quiet) def execute_cmd(self, raw_string, sessid=None, **kwargs): """ @@ -333,7 +333,7 @@ class Object(ObjectDB): useful for coders intending to implement some sort of nested command structure. """ - return super(Object, self).execute_cmd(raw_string, sessid=sessid, **kwargs) + return super(DefaultObject, self).execute_cmd(raw_string, sessid=sessid, **kwargs) def msg(self, text=None, from_obj=None, sessid=None, **kwargs): """ @@ -347,7 +347,7 @@ class Object(ObjectDB): default to self.sessid or from_obj.sessid. """ - super(Object, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) + super(DefaultObject, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) def msg_contents(self, text=None, exclude=None, from_obj=None, **kwargs): """ @@ -356,7 +356,7 @@ class Object(ObjectDB): exclude is a list of objects not to send to. See self.msg() for more info. """ - super(Object, self).msg_contents(text, exclude=exclude, + super(DefaultObject, self).msg_contents(text, exclude=exclude, from_obj=from_obj, **kwargs) def move_to(self, destination, quiet=False, @@ -387,7 +387,7 @@ class Object(ObjectDB): emit_to_obj. """ - return super(Object, self).move_to(destination, quiet=quiet, + return super(DefaultObject, self).move_to(destination, quiet=quiet, emit_to_obj=emit_to_obj, use_destination=use_destination) @@ -400,9 +400,9 @@ class Object(ObjectDB): new_key (string) - new key/name of copied object. If new_key is not specified, the copy will be named _copy by default. - Returns: Object (copy of this one) + Returns: DefaultObject (copy of this one) """ - return super(Object, self).copy(new_key=new_key) + return super(DefaultObject, self).copy(new_key=new_key) def delete(self): """ @@ -415,7 +415,7 @@ class Object(ObjectDB): were errors during deletion or deletion otherwise failed. """ - return super(Object, self).delete() + return super(DefaultObject, self).delete() # methods inherited from the typeclass system @@ -434,7 +434,7 @@ class Object(ObjectDB): Returns: Boolean """ - return super(Object, self).is_typeclass(typeclass, exact=exact) + return super(DefaultObject, self).is_typeclass(typeclass, exact=exact) def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True): """ @@ -470,7 +470,7 @@ class Object(ObjectDB): """ - return super(Object, self).swap_typeclass(new_typeclass, + return super(DefaultObject, self).swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default) def access(self, accessing_obj, access_type='read', default=False, **kwargs): @@ -483,7 +483,7 @@ class Object(ObjectDB): default (bool) - what to return if no lock of access_type was found **kwargs - passed to at_access hook along with result,accessing_obj and access_type """ - result = super(Object, self).access(accessing_obj, access_type=access_type, default=default) + result = super(DefaultObject, self).access(accessing_obj, access_type=access_type, default=default) self.at_access(result, accessing_obj, access_type, **kwargs) return result @@ -504,7 +504,7 @@ class Object(ObjectDB): permission on the object. (example: 'Builders') """ - return super(Object, self).check_permstring(permstring) + return super(DefaultObject, self).check_permstring(permstring) def __eq__(self, other): """ @@ -901,11 +901,12 @@ class Object(ObjectDB): """ return message + # # Base Character object # -class Character(Object): +class DefaultCharacter(DefaultObject): """ This is just like the Object except it implements its own version of the at_object_creation to set up the script @@ -921,7 +922,7 @@ class Character(Object): you want to fundamentally change how a Character object works). """ - super(Character, self).basetype_setup() + super(DefaultCharacter, self).basetype_setup() self.locks.add(";".join(["get:false()", # noone can pick up the character "call:false()"])) # no commands can be called on character from outside # add the default cmdset @@ -982,7 +983,7 @@ class Character(Object): # Base Room object # -class Room(Object): +class DefaultRoom(DefaultObject): """ This is the base room object. It's just like any Object except its location is None. @@ -993,7 +994,7 @@ class Room(Object): (since default is None anyway) """ - super(Room, self).basetype_setup() + super(DefaultRoom, self).basetype_setup() self.locks.add(";".join(["get:false()", "puppet:false()"])) # would be weird to puppet a room ... self.location = None @@ -1003,7 +1004,7 @@ class Room(Object): # Base Exit object # -class Exit(Object): +class DefaultExit(DefaultObject): """ This is the base exit object - it connects a location to another. This is done by the exit assigning a "command" on itself with the @@ -1079,7 +1080,7 @@ class Exit(Object): You should normally not need to overload this - if you do make sure you include all the functionality in this method. """ - super(Exit, self).basetype_setup() + super(DefaultExit, self).basetype_setup() # setting default locks (overload these in at_object_creation() self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ... diff --git a/src/players/player.py b/src/players/player.py index 7cba8e61c..37d6169d0 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -24,7 +24,7 @@ _MULTISESSION_MODE = settings.MULTISESSION_MODE _CMDSET_PLAYER = settings.CMDSET_PLAYER _CONNECT_CHANNEL = None -class Player(PlayerDB): +class DefaultPlayer(PlayerDB): """ Base typeclass for all Players. """ @@ -105,7 +105,7 @@ class Player(PlayerDB): at_server_shutdown() """ - super(Player, self).__init__(*args, **kwargs) + super(DefaultPlayer, self).__init__(*args, **kwargs) ## methods inherited from database model @@ -116,7 +116,7 @@ class Player(PlayerDB): the server. text (string) - text data to send - from_obj (Object/Player) - source object of message to send + from_obj (Object/DefaultPlayer) - source object of message to send sessid - the session id of the session to send to. If not given, return to all sessions connected to this player. This is usually only relevant when using msg() directly from a player-command (from @@ -124,7 +124,7 @@ class Player(PlayerDB): handles the sessid). kwargs - extra data to send through protocol """ - super(Player, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) + super(DefaultPlayer, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) def swap_character(self, new_character, delete_old_character=False): """ @@ -135,7 +135,7 @@ class Player(PlayerDB): Returns: True/False depending on if swap suceeded or not. """ - return super(Player, self).swap_character(new_character, delete_old_character=delete_old_character) + return super(DefaultPlayer, self).swap_character(new_character, delete_old_character=delete_old_character) def execute_cmd(self, raw_string, sessid=None, **kwargs): """ @@ -163,7 +163,7 @@ class Player(PlayerDB): be useful for coders intending to implement some sort of nested command structure. """ - return super(Player, self).execute_cmd(raw_string, sessid=sessid, **kwargs) + return super(DefaultPlayer, self).execute_cmd(raw_string, sessid=sessid, **kwargs) def search(self, searchdata, return_puppet=False, **kwargs): """ @@ -183,7 +183,7 @@ class Player(PlayerDB): # handle wrapping of common terms if searchdata.lower() in ("me", "*me", "self", "*self",): return self - return super(Player, self).search(searchdata, return_puppet=return_puppet, **kwargs) + return super(DefaultPlayer, self).search(searchdata, return_puppet=return_puppet, **kwargs) def is_typeclass(self, typeclass, exact=False): """ @@ -200,7 +200,7 @@ class Player(PlayerDB): Returns: Boolean """ - return super(Player, self).is_typeclass(typeclass, exact=exact) + return super(DefaultPlayer, self).is_typeclass(typeclass, exact=exact) def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True): """ @@ -235,7 +235,7 @@ class Player(PlayerDB): boolean True/False depending on if the swap worked or not. """ - super(Player, self).swap_typeclass(new_typeclass, + super(DefaultPlayer, self).swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default) def access(self, accessing_obj, access_type='read', default=False, **kwargs): @@ -248,7 +248,7 @@ class Player(PlayerDB): default (bool) - what to return if no lock of access_type was found **kwargs - passed to the at_access hook along with the result. """ - result = super(Player, self).access(accessing_obj, access_type=access_type, default=default) + result = super(DefaultPlayer, self).access(accessing_obj, access_type=access_type, default=default) self.at_access(result, accessing_obj, access_type, **kwargs) return result @@ -261,7 +261,7 @@ class Player(PlayerDB): on the object. (example: 'Builders') Note that this method does -not- call the at_access hook. """ - return super(Player, self).check_permstring(permstring) + return super(DefaultPlayer, self).check_permstring(permstring) ## player hooks @@ -431,7 +431,7 @@ class Player(PlayerDB): """ pass -class Guest(Player): +class Guest(DefaultPlayer): """ This class is used for guest logins. Unlike Players, Guests and their characters are deleted after disconnection. diff --git a/src/scripts/__init__.py b/src/scripts/__init__.py index 54d7d20b3..0b77cd143 100644 --- a/src/scripts/__init__.py +++ b/src/scripts/__init__.py @@ -7,7 +7,9 @@ Also, the initiated object manager is available as src.scripts.manager. """ -from src.scripts.scripts import * +# Note - we MUST NOT import src.scripts.scripts here, or +# proxy models will fall under Django migrations. +#from src.scripts.scripts import * from src.scripts.models import ScriptDB manager = ScriptDB.objects diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index ec14aba91..5cf0e81cd 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -345,7 +345,6 @@ class ScriptBase(ScriptDB): "called when typeclass re-caches. Usually not used for scripts." pass - # # Base Script - inherit from this # diff --git a/src/settings_default.py b/src/settings_default.py index e1e549575..7a6d5b2e1 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -288,17 +288,17 @@ PLAYER_TYPECLASS_PATHS = ["game.gamesrc.objects", "contrib"] CHANNEL_TYPECLASS_PATHS = ["game.gamesrc.conf", "contrib"] # Typeclass for player objects (linked to a character) (fallback) -BASE_PLAYER_TYPECLASS = "src.players.player.Player" +BASE_PLAYER_TYPECLASS = "src.players.player.DefaultPlayer" # Typeclass and base for all objects (fallback) -BASE_OBJECT_TYPECLASS = "src.objects.objects.Object" +BASE_OBJECT_TYPECLASS = "src.objects.objects.DefaultObject" # Typeclass for character objects linked to a player (fallback) -BASE_CHARACTER_TYPECLASS = "src.objects.objects.Character" +BASE_CHARACTER_TYPECLASS = "src.objects.objects.DefaultCharacter" # Typeclass for rooms (fallback) -BASE_ROOM_TYPECLASS = "src.objects.objects.Room" +BASE_ROOM_TYPECLASS = "src.objects.objects.DefaultRoom" # Typeclass for Exit objects (fallback). -BASE_EXIT_TYPECLASS = "src.objects.objects.Exit" +BASE_EXIT_TYPECLASS = "src.objects.objects.DefaultExit" # Typeclass for Channel (fallback). -BASE_CHANNEL_TYPECLASS = "src.comms.comms.Channel" +BASE_CHANNEL_TYPECLASS = "src.comms.comms.DefaultChannel" # Typeclass for Scripts (fallback). You usually don't need to change this # but create custom variations of scripts on a per-case basis instead. BASE_SCRIPT_TYPECLASS = "src.scripts.scripts.DoNothing" diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 284373f8d..d7433f3e3 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -780,7 +780,6 @@ class TypeclassBase(SharedMemoryModelBase): proxy = True attrs["Meta"] = Meta attrs["Meta"].proxy = True - attrs["Meta"].app_label = attrs["path"] # patch start - django multi-inheritance # this is a copy of django.db.models.base.__new__ @@ -1086,7 +1085,10 @@ class TypedObject(SharedMemoryModel): def _import_class(self, path): path, clsname = path.rsplit(".", 1) mod = import_module(path) - return getattr(mod, clsname) + try: + return getattr(mod, clsname) + except AttributeError: + raise AttributeError("module '%s' has no attribute '%s'" % (path, clsname)) def __init__(self, *args, **kwargs): typeclass_path = kwargs.pop("typeclass", None) From 24764743ff4409d8e6ae537ca63d230e047ea3c3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 23 Dec 2014 21:33:03 +0100 Subject: [PATCH 026/250] Made a new version of create_object function, and made Objects creatable using o=Object(). --- src/objects/__init__.py | 5 +- src/objects/models.py | 688 +---------------------- src/objects/objects.py | 1055 +++++++++++++++++++++++------------ src/players/bots.py | 4 +- src/server/initial_setup.py | 11 +- src/typeclasses/models.py | 51 +- src/utils/create.py | 145 ++--- src/utils/utils.py | 72 ++- 8 files changed, 858 insertions(+), 1173 deletions(-) diff --git a/src/objects/__init__.py b/src/objects/__init__.py index 2cdc469ce..0f255657c 100644 --- a/src/objects/__init__.py +++ b/src/objects/__init__.py @@ -7,6 +7,5 @@ Also, the initiated object manager is available as src.objects.manager. """ #from src.objects.objects import * -from src.objects.models import ObjectDB - -manager = ObjectDB.objects +#from src.objects.models import ObjectDB +#manager = ObjectDB.objects diff --git a/src/objects/models.py b/src/objects/models.py index 9bab7715c..5752c126e 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -14,81 +14,14 @@ the database object. Like everything else, they can be accessed transparently through the decorating TypeClass. """ -import traceback from django.db import models from django.conf import settings from django.core.exceptions import ObjectDoesNotExist -from src.typeclasses.models import TypedObject, NickHandler +from src.typeclasses.models import TypedObject from src.objects.manager import ObjectDBManager -from src.players.models import PlayerDB -from src.commands.cmdsethandler import CmdSetHandler -from src.commands import cmdhandler -from src.scripts.scripthandler import ScriptHandler from src.utils import logger -from src.utils.utils import (make_iter, to_str, to_unicode, lazy_property, - variable_from_module, dbref) - -MULTISESSION_MODE = settings.MULTISESSION_MODE -from django.utils.translation import ugettext as _ - -#__all__ = ("ObjectDB", ) - -_ScriptDB = None -_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) -_SESSIONS = None - -_GA = object.__getattribute__ -_SA = object.__setattr__ -_DA = object.__delattr__ - -# the sessid_max is based on the length of the db_sessid csv field (excluding commas) -_SESSID_MAX = 16 if MULTISESSION_MODE in (1, 3) else 1 - -class SessidHandler(object): - """ - Handles the get/setting of the sessid - comma-separated integer field - """ - def __init__(self, obj): - self.obj = obj - self._cache = set() - self._recache() - - def _recache(self): - self._cache = list(set(int(val) for val in (_GA(self.obj, "db_sessid") or "").split(",") if val)) - - def get(self): - "Returns a list of one or more session ids" - return self._cache - - def add(self, sessid): - "Add sessid to handler" - _cache = self._cache - if sessid not in _cache: - if len(_cache) >= _SESSID_MAX: - return - _cache.append(sessid) - _SA(self.obj, "db_sessid", ",".join(str(val) for val in _cache)) - _GA(self.obj, "save")(update_fields=["db_sessid"]) - - def remove(self, sessid): - "Remove sessid from handler" - _cache = self._cache - if sessid in _cache: - _cache.remove(sessid) - _SA(self.obj, "db_sessid", ",".join(str(val) for val in _cache)) - _GA(self.obj, "save")(update_fields=["db_sessid"]) - - def clear(self): - "Clear sessids" - self._cache = [] - _SA(self.obj, "db_sessid", None) - _GA(self.obj, "save")(update_fields=["db_sessid"]) - - def count(self): - "Return amount of sessions connected" - return len(self._cache) +from src.utils.utils import (make_iter, dbref) #------------------------------------------------------------ @@ -173,27 +106,7 @@ class ObjectDB(TypedObject): # Database manager objects = ObjectDBManager() - # caches for quick lookups of typeclass loading. - _typeclass_paths = settings.OBJECT_TYPECLASS_PATHS - _default_typeclass_path = settings.BASE_OBJECT_TYPECLASS or "src.objects.objects.Object" - - # lazy-load handlers - @lazy_property - def cmdset(self): - return CmdSetHandler(self, True) - - @lazy_property - def scripts(self): - return ScriptHandler(self) - - @lazy_property - def nicks(self): - return NickHandler(self) - - @lazy_property - def sessid(self): - return SessidHandler(self) - + # field-related field-related properties def _at_db_player_postsave(self): """ This hook is called automatically after the player field is saved. @@ -201,30 +114,21 @@ class ObjectDB(TypedObject): # we need to re-cache this for superusers to bypass. self.locks.cache_lock_bypass(self) - # cmdset_storage property. We use a custom wrapper to manage this. This also - # seems very sensitive to caching, so leaving it be for now. /Griatch - #@property + # cmdset_storage property handling def __cmdset_storage_get(self): - """ - Getter. Allows for value = self.name. - Returns a list of cmdset_storage. - """ - storage = _GA(self, "db_cmdset_storage") - # we need to check so storage is not None + "getter" + storage = self.db_cmdset_storage return [path.strip() for path in storage.split(',')] if storage else [] - #@cmdset_storage.setter + def __cmdset_storage_set(self, value): - """ - Setter. Allows for self.name = value. - Stores as a comma-separated string. - """ - _SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value))) - _GA(self, "save")() - #@cmdset_storage.deleter + "setter" + self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value)) + self.save(update_fields=["db_cmdset_storage"]) + def __cmdset_storage_del(self): - "Deleter. Allows for del self.name" - _SA(self, "db_cmdset_storage", None) - _GA(self, "save")() + "deleter" + self.db_cmdset_storage = None + self.save(update_fields=["db_cmdset_storage"]) cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del) # location getsetter @@ -280,567 +184,3 @@ class ObjectDB(TypedObject): verbose_name = "Object" verbose_name_plural = "Objects" - # - # ObjectDB class access methods/properties - # - - #@property - def __sessions_get(self): - """ - Retrieve sessions connected to this object. - """ - # if the player is not connected, this will simply be an empty list. - if _GA(self, "db_player"): - return _GA(_GA(self, "db_player"), "get_all_sessions")() - return [] - sessions = property(__sessions_get) - - #@property - def __has_player_get(self): - """ - Convenience function for checking if an active player is - currently connected to this object - """ - return any(_GA(self, "sessions")) - has_player = property(__has_player_get) - is_player = property(__has_player_get) - - #@property - def __is_superuser_get(self): - "Check if user has a player, and if so, if it is a superuser." - return (_GA(self, "db_player") and _GA(_GA(self, "db_player"), "is_superuser") - and not _GA(_GA(self, "db_player"), "attributes").get("_quell")) - is_superuser = property(__is_superuser_get) - - # contents - - def contents_get(self, exclude=None): - """ - Returns the contents of this object, i.e. all - objects that has this object set as its location. - This should be publically available. - - exclude is one or more objects to not return - """ - if exclude: - return ObjectDB.objects.get_contents(self, excludeobj=make_iter(exclude)) - return ObjectDB.objects.get_contents(self) - contents = property(contents_get) - - #@property - def __exits_get(self): - """ - Returns all exits from this object, i.e. all objects - at this location having the property destination != None. - """ - return [exi for exi in _GA(self, "contents") - if exi.destination] - exits = property(__exits_get) - - # - # Main Search method - # - - def search(self, searchdata, - global_search=False, - use_nicks=True, # should this default to off? - typeclass=None, - location=None, - attribute_name=None, - quiet=False, - exact=False): - """ - Returns the typeclass of an Object matching a search string/condition - - Perform a standard object search in the database, handling - multiple results and lack thereof gracefully. By default, only - objects in self's current location or inventory is searched. - Note: to find Players, use eg. ev.player_search. - - Inputs: - - searchdata (str or obj): Primary search criterion. Will be matched - against object.key (with object.aliases second) unless - the keyword attribute_name specifies otherwise. - Special strings: - # - search by unique dbref. This is always - a global search. - me,self - self-reference to this object - - - can be used to differentiate - between multiple same-named matches - global_search (bool): Search all objects globally. This is overruled - by "location" keyword. - use_nicks (bool): Use nickname-replace (nicktype "object") on the - search string - typeclass (str or Typeclass, or list of either): Limit search only - to Objects with this typeclass. May be a list of typeclasses - for a broader search. - location (Object): Specify a location to search, if different from the - self's given location plus its contents. This can also - be a list of locations. - attribute_name (str): Define which property to search. If set, no - key+alias search will be performed. This can be used to - search database fields (db_ will be automatically - appended), and if that fails, it will try to return - objects having Attributes with this name and value - equal to searchdata. A special use is to search for - "key" here if you want to do a key-search without - including aliases. - quiet (bool) - don't display default error messages - this tells the - search method that the user wants to handle all errors - themselves. It also changes the return value type, see - below. - exact (bool) - if unset (default) - prefers to match to beginning of - string rather than not matching at all. If set, requires - exact mathing of entire string. - - Returns: - quiet=False (default): - no match or multimatch: - auto-echoes errors to self.msg, then returns None - (results are handled by settings.SEARCH_AT_RESULT - and settings.SEARCH_AT_MULTIMATCH_INPUT) - match: - a unique object match - quiet=True: - returns a list of 0, 1 or more matches - - """ - is_string = isinstance(searchdata, basestring) - - if use_nicks: - # do nick-replacement on search - searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True) - - candidates=None - if(global_search or (is_string and searchdata.startswith("#") and - len(searchdata) > 1 and searchdata[1:].isdigit())): - # only allow exact matching if searching the entire database - # or unique #dbrefs - exact = True - elif location: - # location(s) were given - candidates = [] - for obj in make_iter(location): - candidates.extend(obj.contents) - else: - # local search. Candidates are self.contents, self.location - # and self.location.contents - location = self.location - candidates = self.contents - if location: - candidates = candidates + [location] + location.contents - else: - # normally we are included in location.contents - candidates.append(self) - - results = ObjectDB.objects.object_search(searchdata, - attribute_name=attribute_name, - typeclass=typeclass, - candidates=candidates, - exact=exact) - if quiet: - return results - return _AT_SEARCH_RESULT(self, searchdata, results, global_search) - - def search_player(self, searchdata, quiet=False): - """ - Simple shortcut wrapper to search for players, not characters. - - searchdata - search criterion - the key or dbref of the player - to search for. If this is "here" or "me", search - for the player connected to this object. - quiet - return the results as a list rather than echo eventual - standard error messages. - - Returns: - quiet=False (default): - no match or multimatch: - auto-echoes errors to self.msg, then returns None - (results are handled by settings.SEARCH_AT_RESULT - and settings.SEARCH_AT_MULTIMATCH_INPUT) - match: - a unique player match - quiet=True: - no match or multimatch: - returns None or list of multi-matches - match: - a unique object match - """ - results = PlayerDB.objects.player_search(searchdata) - if quiet: - return results - return _AT_SEARCH_RESULT(self, searchdata, results, global_search=True) - - # - # Execution/action methods - # - - def execute_cmd(self, raw_string, sessid=None, **kwargs): - """ - Do something as this object. This method is a copy of the execute_ - cmd method on the session. This is never called normally, it's only - used when wanting specifically to let an object be the caller of a - command. It makes use of nicks of eventual connected players as well. - - Argument: - raw_string (string) - raw command input - sessid (int) - optional session id to return results to - **kwargs - other keyword arguments will be added to the found command - object instace as variables before it executes. This is - unused by default Evennia but may be used to set flags and - change operating paramaters for commands at run-time. - - Returns Deferred - this is an asynchronous Twisted object that will - not fire until the command has actually finished executing. To - overload this one needs to attach callback functions to it, with - addCallback(function). This function will be called with an - eventual return value from the command execution. - - This return is not used at all by Evennia by default, but might - be useful for coders intending to implement some sort of nested - command structure. - """ - # nick replacement - we require full-word matching. - - # do text encoding conversion - raw_string = to_unicode(raw_string) - raw_string = self.nicks.nickreplace(raw_string, - categories=("inputline", "channel"), include_player=True) - return cmdhandler.cmdhandler(self, raw_string, callertype="object", sessid=sessid, **kwargs) - - def msg(self, text=None, from_obj=None, sessid=0, **kwargs): - """ - Emits something to a session attached to the object. - - message (str): The message to send - from_obj (obj): object that is sending. - data (object): an optional data object that may or may not - be used by the protocol. - sessid (int): sessid to relay to, if any. - If set to 0 (default), use either from_obj.sessid (if set) or self.sessid automatically - If None, echo to all connected sessions - - When this message is called, from_obj.at_msg_send and self.at_msg_receive are called. - - """ - global _SESSIONS - if not _SESSIONS: - from src.server.sessionhandler import SESSIONS as _SESSIONS - - text = to_str(text, force_string=True) if text else "" - - if "data" in kwargs: - # deprecation warning - logger.log_depmsg("ObjectDB.msg(): 'data'-dict keyword is deprecated. Use **kwargs instead.") - data = kwargs.pop("data") - if isinstance(data, dict): - kwargs.update(data) - - if from_obj: - # call hook - try: - from_obj.at_msg_send(text=text, to_obj=self, **kwargs) - except Exception: - logger.log_trace() - try: - if not self.at_msg_receive(text=text, **kwargs): - # if at_msg_receive returns false, we abort message to this object - return - except Exception: - logger.log_trace() - - sessions = _SESSIONS.session_from_sessid([sessid] if sessid else make_iter(_GA(self, "sessid").get())) - for session in sessions: - session.msg(text=text, **kwargs) - - def msg_contents(self, message, exclude=None, from_obj=None, **kwargs): - """ - Emits something to all objects inside an object. - - exclude is a list of objects not to send to. See self.msg() for - more info. - """ - contents = _GA(self, "contents") - if exclude: - exclude = make_iter(exclude) - contents = [obj for obj in contents if obj not in exclude] - for obj in contents: - obj.msg(message, from_obj=from_obj, **kwargs) - - def move_to(self, destination, quiet=False, - emit_to_obj=None, use_destination=True, to_none=False): - """ - Moves this object to a new location. - - Moves this object to a new location. Note that if is an - exit object (i.e. it has "destination"!=None), the move_to will - happen to this destination and -not- into the exit object itself, unless - use_destination=False. Note that no lock checks are done by this - function, such things are assumed to have been handled before calling - move_to. - - destination: (Object) Reference to the object to move to. This - can also be an exit object, in which case the destination - property is used as destination. - quiet: (bool) If true, don't emit left/arrived messages. - emit_to_obj: (Object) object to receive error messages - use_destination (bool): Default is for objects to use the "destination" - property of destinations as the target to move to. - Turning off this keyword allows objects to move - "inside" exit objects. - to_none - allow destination to be None. Note that no hooks are run when - moving to a None location. If you want to run hooks, - run them manually (and make sure they can manage None - locations). - - Returns True/False depending on if there were problems with the move. - This method may also return various error messages to the - emit_to_obj. - """ - def logerr(string=""): - trc = traceback.format_exc() - errstring = "%s%s" % (trc, string) - logger.log_trace() - _GA(self, "msg")(errstring) - - errtxt = _("Couldn't perform move ('%s'). Contact an admin.") - if not emit_to_obj: - emit_to_obj = self - - if not destination: - if to_none: - # immediately move to None. There can be no hooks called since - # there is no destination to call them with. - self.location = None - return True - emit_to_obj.msg(_("The destination doesn't exist.")) - return - if destination.destination and use_destination: - # traverse exits - destination = destination.destination - - # Before the move, call eventual pre-commands. - try: - if not self.at_before_move(destination): - return - except Exception: - logerr(errtxt % "at_before_move()") - #emit_to_obj.msg(errtxt % "at_before_move()") - #logger.log_trace() - return False - - # Save the old location - source_location = _GA(self, "location") - if not source_location: - # there was some error in placing this room. - # we have to set one or we won't be able to continue - if _GA(self, "home"): - source_location = _GA(self, "home") - else: - default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - source_location = default_home - - # Call hook on source location - try: - source_location.at_object_leave(self, destination) - except Exception: - logerr(errtxt % "at_object_leave()") - #emit_to_obj.msg(errtxt % "at_object_leave()") - #logger.log_trace() - return False - - if not quiet: - #tell the old room we are leaving - try: - self.announce_move_from(destination) - except Exception: - logerr(errtxt % "at_announce_move()") - #emit_to_obj.msg(errtxt % "at_announce_move()" ) - #logger.log_trace() - return False - - # Perform move - try: - #print "move_to location:", destination - _SA(self, "location", destination) - except Exception: - emit_to_obj.msg(errtxt % "location change") - logger.log_trace() - return False - - if not quiet: - # Tell the new room we are there. - try: - self.announce_move_to(source_location) - except Exception: - logerr(errtxt % "announce_move_to()") - #emit_to_obj.msg(errtxt % "announce_move_to()") - #logger.log_trace() - return False - - # Perform eventual extra commands on the receiving location - # (the object has already arrived at this point) - try: - destination.at_object_receive(self, source_location) - except Exception: - logerr(errtxt % "at_object_receive()") - #emit_to_obj.msg(errtxt % "at_object_receive()") - #logger.log_trace() - return False - - # Execute eventual extra commands on this object after moving it - # (usually calling 'look') - try: - self.at_after_move(source_location) - except Exception: - logerr(errtxt % "at_after_move") - #emit_to_obj.msg(errtxt % "at_after_move()") - #logger.log_trace() - return False - return True - - # - # Object Swap, Delete and Cleanup methods - # - - def clear_exits(self): - """ - Destroys all of the exits and any exits pointing to this - object as a destination. - """ - for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]: - out_exit.delete() - for in_exit in ObjectDB.objects.filter(db_destination=self): - in_exit.delete() - - def clear_contents(self): - """ - Moves all objects (players/things) to their home - location or to default home. - """ - # Gather up everything that thinks this is its location. - objs = ObjectDB.objects.filter(db_location=self) - default_home_id = int(settings.DEFAULT_HOME.lstrip("#")) - try: - default_home = ObjectDB.objects.get(id=default_home_id) - if default_home.dbid == _GA(self, "dbid"): - # we are deleting default home! - default_home = None - except Exception: - string = _("Could not find default home '(#%d)'.") - logger.log_errmsg(string % default_home_id) - default_home = None - - for obj in objs: - home = obj.home - # Obviously, we can't send it back to here. - if not home or (home and home.dbid == _GA(self, "dbid")): - obj.home = default_home - home = default_home - - # If for some reason it's still None... - if not home: - string = "Missing default home, '%s(#%d)' " - string += "now has a null location." - obj.location = None - obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin.")) - logger.log_errmsg(string % (obj.name, obj.dbid)) - return - - if obj.has_player: - if home: - string = "Your current location has ceased to exist," - string += " moving you to %s(#%d)." - obj.msg(_(string) % (home.name, home.dbid)) - else: - # Famous last words: The player should never see this. - string = "This place should not exist ... contact an admin." - obj.msg(_(string)) - obj.move_to(home) - - def copy(self, new_key=None): - """ - Makes an identical copy of this object. If you want to customize the - copy by changing some settings, use ObjectDB.object.copy_object() - directly. - - new_key (string) - new key/name of copied object. If new_key is not - specified, the copy will be named _copy - by default. - Returns: Object (copy of this one) - """ - def find_clone_key(): - """ - Append 01, 02 etc to obj.key. Checks next higher number in the - same location, then adds the next number available - - returns the new clone name on the form keyXX - """ - key = _GA(self, "key") - num = 1 - for obj in (obj for obj in self.location.contents - if obj.key.startswith(key) and - obj.key.lstrip(key).isdigit()): - num += 1 - return "%s%03i" % (key, num) - new_key = new_key or find_clone_key() - return ObjectDB.objects.copy_object(self, new_key=new_key) - - delete_iter = 0 - def delete(self): - """ - Deletes this object. - Before deletion, this method makes sure to move all contained - objects to their respective home locations, as well as clean - up all exits to/from the object. - """ - global _ScriptDB - if not _ScriptDB: - from src.scripts.models import ScriptDB as _ScriptDB - - if _GA(self, "delete_iter") > 0: - # make sure to only call delete once on this object - # (avoid recursive loops) - return False - - if not self.at_object_delete(): - # this is an extra pre-check - # run before deletion mechanism - # is kicked into gear. - _SA(self, "delete_iter", 0) - return False - - self.delete_iter += 1 - - # See if we need to kick the player off. - - for session in _GA(self, "sessions"): - session.msg(_("Your character %s has been destroyed.") % _GA(self, "key")) - # no need to disconnect, Player just jumps to OOC mode. - # sever the connection (important!) - if _GA(self, 'player'): - _SA(_GA(self, "player"), "character", None) - _SA(self, "player", None) - - for script in _ScriptDB.objects.get_all_scripts_on_obj(self): - script.stop() - #for script in _GA(self, "scripts").all(): - # script.stop() - - # if self.player: - # self.player.user.is_active = False - # self.player.user.save( - - # Destroy any exits to and from this room, if any - _GA(self, "clear_exits")() - # Clear out any non-exit objects located within the object - _GA(self, "clear_contents")() - _GA(self, "attributes").clear() - _GA(self, "nicks").clear() - _GA(self, "aliases").clear() - - # Perform the deletion of the object - super(ObjectDB, self).delete() - return True - diff --git a/src/objects/objects.py b/src/objects/objects.py index 0a949eb93..4bacf0336 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -15,18 +15,76 @@ That an object is controlled by a player/user is just defined by its they control by simply linking to a new object's user property. """ +import traceback from django.conf import settings -from src.typeclasses.models import TypeclassBase + +from src.typeclasses.models import TypeclassBase, NickHandler from src.objects.manager import ObjectManager from src.objects.models import ObjectDB +from src.scripts.scripthandler import ScriptHandler from src.commands import cmdset, command -from src.utils.logger import log_depmsg +from src.commands.cmdsethandler import CmdSetHandler +from src.commands import cmdhandler +from src.utils.logger import log_depmsg, log_trace, log_errmsg +from src.utils.utils import (variable_from_module, lazy_property, + make_iter, to_str, to_unicode) -__all__ = ("Object", "Character", "Room", "Exit") +MULTISESSION_MODE = settings.MULTISESSION_MODE + +_ScriptDB = None +_SESSIONS = None + +_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) +# the sessid_max is based on the length of the db_sessid csv field (excluding commas) +_SESSID_MAX = 16 if MULTISESSION_MODE in (1, 3) else 1 + +from django.utils.translation import ugettext as _ + +class SessidHandler(object): + """ + Handles the get/setting of the sessid + comma-separated integer field + """ + def __init__(self, obj): + self.obj = obj + self._cache = set() + self._recache() + + def _recache(self): + self._cache = list(set(int(val) for val in (self.obj.db_sessid or "").split(",") if val)) + + def get(self): + "Returns a list of one or more session ids" + return self._cache + + def add(self, sessid): + "Add sessid to handler" + _cache = self._cache + if sessid not in _cache: + if len(_cache) >= _SESSID_MAX: + return + _cache.append(sessid) + self.obj.db_sessid = ",".join(str(val) for val in _cache) + self.obj.save(update_fields=["db_sessid"]) + + def remove(self, sessid): + "Remove sessid from handler" + _cache = self._cache + if sessid in _cache: + _cache.remove(sessid) + self.obj.db_sessid = ",".join(str(val) for val in _cache) + self.obj.save(update_fields=["db_sessid"]) + + def clear(self): + "Clear sessids" + self._cache = [] + self.obj.db_sessid = None + self.obj.save(update_fields=["db_sessid"]) + + def count(self): + "Return amount of sessions connected" + return len(self._cache) -_GA = object.__getattribute__ -_SA = object.__setattr__ -_DA = object.__delattr__ # @@ -35,172 +93,227 @@ _DA = object.__delattr__ class DefaultObject(ObjectDB): """ - This is the base class for all in-game objects. Inherit from this - to create different types of objects in the game. + This is the root typeclass object, representing all entities + that have an actual presence in-game. Objects generally have a + location. They can also be manipulated and looked at. Most + game entities you define should inherit from Object at some distance. + Evennia defines some important subclasses of Object by default, namely + Characters, Exits and Rooms (see the bottom of this module). - """ + Note that all new Objects and their subclasses *must* always be + created using the ev.create_object() function. This is so the + typeclass system can be correctly initiated behind the scenes. + + + Object Typeclass API: + + * Available properties (only available on *initiated* typeclass objects) + + key (string) - name of object + name (string) - same as key + aliases (list of strings) - aliases to the object. Will be saved to + database as AliasDB entries but returned as strings. + dbref (int, read-only) - unique #id-number. Also "id" can be used. + dbobj (Object, read-only) - link to database model. dbobj.typeclass + points back to this class + typeclass (Object, read-only) - this links back to this class as an + identified only. Use self.swap_typeclass() to switch. + date_created (string) - time stamp of object creation + permissions (list of strings) - list of permission strings + + player (Player) - controlling player (if any, only set together with + sessid below) + sessid (int, read-only) - session id (if any, only set together with + player above) + location (Object) - current location. Is None if this is a room + home (Object) - safety start-location + sessions (list of Sessions, read-only) - returns all sessions + connected to this object + has_player (bool, read-only)- will only return *connected* players + contents (list of Objects, read-only) - returns all objects inside + this object (including exits) + exits (list of Objects, read-only) - returns all exits from this + object, if any + destination (Object) - only set if this object is an exit. + is_superuser (bool, read-only) - True/False if this user is a superuser + + * Handlers available + + locks - lock-handler: use locks.add() to add new lock strings + db - attribute-handler: store/retrieve database attributes on this + self.db.myattr=val, val=self.db.myattr + ndb - non-persistent attribute handler: same as db but does not + create a database entry when storing data + scripts - script-handler. Add new scripts to object with scripts.add() + cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object + nicks - nick-handler. New nicks with nicks.add(). + + * Helper methods (see src.objects.objects.py for full headers) + + search(ostring, global_search=False, use_nicks=True, + typeclass=None, + attribute_name=None, use_nicks=True, location=None, + quiet=False, exact=False) + execute_cmd(raw_string) + msg(text=None, from_obj=None, sessid=0, **kwargs) + msg_contents(message, exclude=None, from_obj=None, **kwargs) + move_to(destination, quiet=False, emit_to_obj=None, + use_destination=True, to_none=False) + copy(new_key=None) + delete() + is_typeclass(typeclass, exact=False) + swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) + access(accessing_obj, access_type='read', default=False) + check_permstring(permstring) + + * Hook methods + + basetype_setup() - only called once, used for behind-the-scenes + setup. Normally not modified. + basetype_posthook_setup() - customization in basetype, after the + object has been created; Normally not modified. + + at_object_creation() - only called once, when object is first created. + Object customizations go here. + at_object_delete() - called just before deleting an object. If + returning False, deletion is aborted. Note that + all objects inside a deleted object are + automatically moved to their , they don't + need to be removed here. + + at_init() called whenever typeclass is cached from + memory, at least once every server restart/reload + at_cmdset_get(**kwargs) - this is called just before the command + handler requests a cmdset from this object, usually + without any kwargs + at_pre_puppet(player)- (player-controlled objects only) called just + before puppeting + at_post_puppet() - (player-controlled objects only) called just + after completing connection player<->object + at_pre_unpuppet() - (player-controlled objects only) called just + before un-puppeting + at_post_unpuppet(player) (player-controlled objects only) called + just after disconnecting player<->object link + at_server_reload() - called before server is reloaded + at_server_shutdown() - called just before server is fully shut down + + at_before_move(destination) called just before moving + object to the destination. If returns + False, move is cancelled. + announce_move_from(destination) - called in old location, just before + move, if obj.move_to() has + quiet=False + announce_move_to(source_location) - called in new location, + just after move, if obj.move_to() + has quiet=False + at_after_move(source_location) - always called after a move + has been successfully performed. + at_object_leave(obj, target_location) - called when an object leaves + this object in any fashion + at_object_receive(obj, source_location) - called when this object + receives another object + at_access(result, **kwargs) - this is called with the result of an + access call, along with any kwargs used + for that call. The return of this + method does not affect the result of the + lock check. + at_before_traverse(traversing_object) - (exit-objects only) called + just before an object + traverses this object + at_after_traverse(traversing_object, source_location) - (exit-objects + only) called just after a traversal has happened. + at_failed_traverse(traversing_object) - (exit-objects only) called + if traversal fails and property err_traverse is not defined. + + at_msg_receive(self, msg, from_obj=None, data=None) - called when a + message (via self.msg()) is sent to this obj. + If returns false, aborts send. + at_msg_send(self, msg, to_obj=None, data=None) - called when this + objects sends a message to someone via self.msg(). + + return_appearance(looker) - describes this object. Used by "look" + command by default + at_desc(looker=None) - called by 'look' whenever the appearance + is requested. + at_get(getter) - called after object has been picked up. + Does not stop pickup. + at_drop(dropper) - called when this object has been dropped. + at_say(speaker, message) - by default, called if an object inside + this object speaks + + """ + # typeclass setup __metaclass__ = TypeclassBase objects = ObjectManager() - # __init__ is only defined here in order to present docstring to API. - def __init__(self, *args, **kwargs): + # on-object properties + + @lazy_property + def cmdset(self): + return CmdSetHandler(self, True) + + @lazy_property + def scripts(self): + return ScriptHandler(self) + + @lazy_property + def nicks(self): + return NickHandler(self) + + @lazy_property + def sessid(self): + return SessidHandler(self) + + @property + def sessions(self): """ - This is the root typeclass object, representing all entities - that have an actual presence in-game. Objects generally have a - location. They can also be manipulated and looked at. Most - game entities you define should inherit from Object at some distance. - Evennia defines some important subclasses of Object by default, namely - Characters, Exits and Rooms (see the bottom of this module). + Retrieve sessions connected to this object. + """ + # if the player is not connected, this will simply be an empty list. + if self.db_player: + return self.db_player.get_all_sessions() + return [] - Note that all new Objects and their subclasses *must* always be - created using the ev.create_object() function. This is so the - typeclass system can be correctly initiated behind the scenes. + @property + def has_player(self): + """ + Convenience function for checking if an active player is + currently connected to this object + """ + return any(self.sessions) + @property + def is_superuser(self): + "Check if user has a player, and if so, if it is a superuser." + return self.db_player and self.db_player.is_superuser \ + and not self.db_player.attributes.get("_quell") - Object Typeclass API: + @property + def contents(self): + """ + Returns the contents of this object, i.e. all + objects that has this object set as its location. + This should be publically available. - * Available properties (only available on *initiated* typeclass objects) + exclude is one or more objects to not return + """ + return ObjectDB.objects.get_contents(self) - key (string) - name of object - name (string) - same as key - aliases (list of strings) - aliases to the object. Will be saved to - database as AliasDB entries but returned as strings. - dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Object, read-only) - link to database model. dbobj.typeclass - points back to this class - typeclass (Object, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. - date_created (string) - time stamp of object creation - permissions (list of strings) - list of permission strings + @property + def exits(self): + """ + Returns all exits from this object, i.e. all objects + at this location having the property destination != None. + """ + return [exi for exi in self.contents if exi.destination] - player (Player) - controlling player (if any, only set together with - sessid below) - sessid (int, read-only) - session id (if any, only set together with - player above) - location (Object) - current location. Is None if this is a room - home (Object) - safety start-location - sessions (list of Sessions, read-only) - returns all sessions - connected to this object - has_player (bool, read-only)- will only return *connected* players - contents (list of Objects, read-only) - returns all objects inside - this object (including exits) - exits (list of Objects, read-only) - returns all exits from this - object, if any - destination (Object) - only set if this object is an exit. - is_superuser (bool, read-only) - True/False if this user is a superuser - - * Handlers available - - locks - lock-handler: use locks.add() to add new lock strings - db - attribute-handler: store/retrieve database attributes on this - self.db.myattr=val, val=self.db.myattr - ndb - non-persistent attribute handler: same as db but does not - create a database entry when storing data - scripts - script-handler. Add new scripts to object with scripts.add() - cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object - nicks - nick-handler. New nicks with nicks.add(). - - * Helper methods (see src.objects.objects.py for full headers) - - search(ostring, global_search=False, use_nicks=True, - typeclass=None, - attribute_name=None, use_nicks=True, location=None, - quiet=False, exact=False) - execute_cmd(raw_string) - msg(text=None, from_obj=None, sessid=0, **kwargs) - msg_contents(message, exclude=None, from_obj=None, **kwargs) - move_to(destination, quiet=False, emit_to_obj=None, - use_destination=True, to_none=False) - copy(new_key=None) - delete() - is_typeclass(typeclass, exact=False) - swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) - access(accessing_obj, access_type='read', default=False) - check_permstring(permstring) - - * Hook methods - - basetype_setup() - only called once, used for behind-the-scenes - setup. Normally not modified. - basetype_posthook_setup() - customization in basetype, after the - object has been created; Normally not modified. - - at_object_creation() - only called once, when object is first created. - Object customizations go here. - at_object_delete() - called just before deleting an object. If - returning False, deletion is aborted. Note that - all objects inside a deleted object are - automatically moved to their , they don't - need to be removed here. - - at_init() called whenever typeclass is cached from - memory, at least once every server restart/reload - at_cmdset_get(**kwargs) - this is called just before the command - handler requests a cmdset from this object, usually - without any kwargs - at_pre_puppet(player)- (player-controlled objects only) called just - before puppeting - at_post_puppet() - (player-controlled objects only) called just - after completing connection player<->object - at_pre_unpuppet() - (player-controlled objects only) called just - before un-puppeting - at_post_unpuppet(player) (player-controlled objects only) called - just after disconnecting player<->object link - at_server_reload() - called before server is reloaded - at_server_shutdown() - called just before server is fully shut down - - at_before_move(destination) called just before moving - object to the destination. If returns - False, move is cancelled. - announce_move_from(destination) - called in old location, just before - move, if obj.move_to() has - quiet=False - announce_move_to(source_location) - called in new location, - just after move, if obj.move_to() - has quiet=False - at_after_move(source_location) - always called after a move - has been successfully performed. - at_object_leave(obj, target_location) - called when an object leaves - this object in any fashion - at_object_receive(obj, source_location) - called when this object - receives another object - at_access(result, **kwargs) - this is called with the result of an - access call, along with any kwargs used - for that call. The return of this - method does not affect the result of the - lock check. - at_before_traverse(traversing_object) - (exit-objects only) called - just before an object - traverses this object - at_after_traverse(traversing_object, source_location) - (exit-objects - only) called just after a traversal has happened. - at_failed_traverse(traversing_object) - (exit-objects only) called - if traversal fails and property err_traverse is not defined. - - at_msg_receive(self, msg, from_obj=None, data=None) - called when a - message (via self.msg()) is sent to this obj. - If returns false, aborts send. - at_msg_send(self, msg, to_obj=None, data=None) - called when this - objects sends a message to someone via self.msg(). - - return_appearance(looker) - describes this object. Used by "look" - command by default - at_desc(looker=None) - called by 'look' whenever the appearance - is requested. - at_get(getter) - called after object has been picked up. - Does not stop pickup. - at_drop(dropper) - called when this object has been dropped. - at_say(speaker, message) - by default, called if an object inside - this object speaks - - """ - super(DefaultObject, self).__init__(*args, **kwargs) + # main methods ## methods inherited from the database object (overload them here) def search(self, searchdata, global_search=False, - use_nicks=True, + use_nicks=True, # should this default to off? typeclass=None, location=None, attribute_name=None, @@ -216,66 +329,96 @@ class DefaultObject(ObjectDB): Inputs: - searchdata (str): Primary search criterion. Will be matched against - object.key (with object.aliases second) - unless the keyword attribute_name specifies otherwise. - Special strings: - # - search by unique dbref. This is always a - global search. + searchdata (str or obj): Primary search criterion. Will be matched + against object.key (with object.aliases second) unless + the keyword attribute_name specifies otherwise. + Special strings: + # - search by unique dbref. This is always + a global search. me,self - self-reference to this object - here - current location - - - can be used to differentiate between - multiple same-named matches + - - can be used to differentiate + between multiple same-named matches global_search (bool): Search all objects globally. This is overruled by "location" keyword. use_nicks (bool): Use nickname-replace (nicktype "object") on the search string - typeclass (str or Typeclass): Limit search only to Objects with this - typeclass. May be a list of typeclasses for a - broader search. + typeclass (str or Typeclass, or list of either): Limit search only + to Objects with this typeclass. May be a list of typeclasses + for a broader search. location (Object): Specify a location to search, if different from the - self's given location - plus its contents. This can also be a list of locations. - attribute_name (str): Use this named Attribute to match ostring against, - instead of object.key. - quiet (bool) - don't display default error messages - return multiple - matches as a list and no matches as None. If not - set (default), will echo error messages and return None. + self's given location plus its contents. This can also + be a list of locations. + attribute_name (str): Define which property to search. If set, no + key+alias search will be performed. This can be used to + search database fields (db_ will be automatically + appended), and if that fails, it will try to return + objects having Attributes with this name and value + equal to searchdata. A special use is to search for + "key" here if you want to do a key-search without + including aliases. + quiet (bool) - don't display default error messages - this tells the + search method that the user wants to handle all errors + themselves. It also changes the return value type, see + below. exact (bool) - if unset (default) - prefers to match to beginning of - string rather than not matching at all. If set, - requires exact mathing of entire string. + string rather than not matching at all. If set, requires + exact mathing of entire string. Returns: - quiet=False (default): no match or multimatch: - auto-echoes errors to self.msg, then returns None + auto-echoes errors to self.msg, then returns None (results are handled by settings.SEARCH_AT_RESULT - and settings.SEARCH_AT_MULTIMATCH_INPUT) + and settings.SEARCH_AT_MULTIMATCH_INPUT) match: a unique object match quiet=True: - no match or multimatch: - returns None or list of multi-matches - match: - a unique object match + returns a list of 0, 1 or more matches """ - if isinstance(searchdata, basestring): + is_string = isinstance(searchdata, basestring) + + if is_string: # searchdata is a string; wrap some common self-references if searchdata.lower() in ("here", ): return self.location if searchdata.lower() in ("me", "self",): return self - return super(DefaultObject, self).search(searchdata, - global_search=global_search, - use_nicks=use_nicks, - typeclass=typeclass, - location=location, - attribute_name=attribute_name, - quiet=quiet, - exact=exact) + if use_nicks: + # do nick-replacement on search + searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True) + + candidates=None + if(global_search or (is_string and searchdata.startswith("#") and + len(searchdata) > 1 and searchdata[1:].isdigit())): + # only allow exact matching if searching the entire database + # or unique #dbrefs + exact = True + elif location: + # location(s) were given + candidates = [] + for obj in make_iter(location): + candidates.extend(obj.contents) + else: + # local search. Candidates are self.contents, self.location + # and self.location.contents + location = self.location + candidates = self.contents + if location: + candidates = candidates + [location] + location.contents + else: + # normally we are included in location.contents + candidates.append(self) + + results = ObjectDB.objects.object_search(searchdata, + attribute_name=attribute_name, + typeclass=typeclass, + candidates=candidates, + exact=exact) + if quiet: + return results + return _AT_SEARCH_RESULT(self, searchdata, results, global_search) def search_player(self, searchdata, quiet=False): """ @@ -305,19 +448,23 @@ class DefaultObject(ObjectDB): # searchdata is a string; wrap some common self-references if searchdata.lower() in ("me", "self",): return self.player - return super(DefaultObject, self).search_player(searchdata, quiet=quiet) + + results = self.player.__class__.objects.player_search(searchdata) + + if quiet: + return results + return _AT_SEARCH_RESULT(self, searchdata, results, global_search=True) def execute_cmd(self, raw_string, sessid=None, **kwargs): """ - Do something as this object. This command transparently - lets its typeclass execute the command. This method is - never called normally, it is only called explicitly in - code. + Do something as this object. This method is a copy of the execute_ + cmd method on the session. This is never called normally, it's only + used when wanting specifically to let an object be the caller of a + command. It makes use of nicks of eventual connected players as well. Argument: raw_string (string) - raw command input - sessid (int) - id of session executing the command. This sets the - sessid property on the command. + sessid (int) - optional session id to return results to **kwargs - other keyword arguments will be added to the found command object instace as variables before it executes. This is unused by default Evennia but may be used to set flags and @@ -329,45 +476,89 @@ class DefaultObject(ObjectDB): addCallback(function). This function will be called with an eventual return value from the command execution. - This return is not used at all by Evennia by default, but might be - useful for coders intending to implement some sort of nested + This return is not used at all by Evennia by default, but might + be useful for coders intending to implement some sort of nested command structure. """ - return super(DefaultObject, self).execute_cmd(raw_string, sessid=sessid, **kwargs) + # nick replacement - we require full-word matching. - def msg(self, text=None, from_obj=None, sessid=None, **kwargs): + # do text encoding conversion + raw_string = to_unicode(raw_string) + raw_string = self.nicks.nickreplace(raw_string, + categories=("inputline", "channel"), include_player=True) + return cmdhandler.cmdhandler(self, raw_string, callertype="object", sessid=sessid, **kwargs) + + + def msg(self, text=None, from_obj=None, sessid=0, **kwargs): """ - Emits something to any sessions attached to the object. + Emits something to a session attached to the object. message (str): The message to send from_obj (obj): object that is sending. data (object): an optional data object that may or may not be used by the protocol. - sessid: optional session target. If sessid=0, the session will - default to self.sessid or from_obj.sessid. + sessid (int): sessid to relay to, if any. + If set to 0 (default), use either from_obj.sessid (if set) or self.sessid automatically + If None, echo to all connected sessions + + When this message is called, from_obj.at_msg_send and self.at_msg_receive are called. + """ + global _SESSIONS + if not _SESSIONS: + from src.server.sessionhandler import SESSIONS as _SESSIONS - super(DefaultObject, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) + text = to_str(text, force_string=True) if text else "" - def msg_contents(self, text=None, exclude=None, from_obj=None, **kwargs): + if "data" in kwargs: + # deprecation warning + log_depmsg("ObjectDB.msg(): 'data'-dict keyword is deprecated. Use **kwargs instead.") + data = kwargs.pop("data") + if isinstance(data, dict): + kwargs.update(data) + + if from_obj: + # call hook + try: + from_obj.at_msg_send(text=text, to_obj=self, **kwargs) + except Exception: + log_trace() + try: + if not self.at_msg_receive(text=text, **kwargs): + # if at_msg_receive returns false, we abort message to this object + return + except Exception: + log_trace() + + sessions = _SESSIONS.session_from_sessid([sessid] if sessid else make_iter(self.sessid.get())) + for session in sessions: + session.msg(text=text, **kwargs) + + def msg_contents(self, message, exclude=None, from_obj=None, **kwargs): """ Emits something to all objects inside an object. exclude is a list of objects not to send to. See self.msg() for more info. """ - super(DefaultObject, self).msg_contents(text, exclude=exclude, - from_obj=from_obj, **kwargs) + contents = self.contents + if exclude: + exclude = make_iter(exclude) + contents = [obj for obj in contents if obj not in exclude] + for obj in contents: + obj.msg(message, from_obj=from_obj, **kwargs) def move_to(self, destination, quiet=False, emit_to_obj=None, use_destination=True, to_none=False): """ + Moves this object to a new location. + Moves this object to a new location. Note that if is an exit object (i.e. it has "destination"!=None), the move_to will - happen to this destination and -not- into the exit object itself, - unless use_destination=False. Note that no lock checks are done by - this function, such things are assumed to have been handled before - calling move_to. + happen to this destination and -not- into the exit object itself, unless + use_destination=False. Note that no lock checks are done by this + function, such things are assumed to have been handled before calling + move_to. destination: (Object) Reference to the object to move to. This can also be an exit object, in which case the destination @@ -375,21 +566,175 @@ class DefaultObject(ObjectDB): quiet: (bool) If true, don't emit left/arrived messages. emit_to_obj: (Object) object to receive error messages use_destination (bool): Default is for objects to use the "destination" - property of destinations as the target to move to. - Turning off this keyword allows objects to move - "inside" exit objects. - to_none - allow destination to be None. Note that no hooks are run - when moving to a None location. If you want to run hooks, run - them manually (and make sure the hooks can handle a None - location). - Returns True/False depending on if there were problems with the move. - This method may also return various error messages to the - emit_to_obj. + property of destinations as the target to move to. + Turning off this keyword allows objects to move + "inside" exit objects. + to_none - allow destination to be None. Note that no hooks are run when + moving to a None location. If you want to run hooks, + run them manually (and make sure they can manage None + locations). + Returns True/False depending on if there were problems with the move. + This method may also return various error messages to the + emit_to_obj. """ - return super(DefaultObject, self).move_to(destination, quiet=quiet, - emit_to_obj=emit_to_obj, - use_destination=use_destination) + def logerr(string=""): + trc = traceback.format_exc() + errstring = "%s%s" % (trc, string) + log_trace() + self.msg(errstring) + + errtxt = _("Couldn't perform move ('%s'). Contact an admin.") + if not emit_to_obj: + emit_to_obj = self + + if not destination: + if to_none: + # immediately move to None. There can be no hooks called since + # there is no destination to call them with. + self.location = None + return True + emit_to_obj.msg(_("The destination doesn't exist.")) + return + if destination.destination and use_destination: + # traverse exits + destination = destination.destination + + # Before the move, call eventual pre-commands. + try: + if not self.at_before_move(destination): + return + except Exception: + logerr(errtxt % "at_before_move()") + #emit_to_obj.msg(errtxt % "at_before_move()") + #logger.log_trace() + return False + + # Save the old location + source_location = self.location + if not source_location: + # there was some error in placing this room. + # we have to set one or we won't be able to continue + if self.home: + source_location = self.home + else: + default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) + source_location = default_home + + # Call hook on source location + try: + source_location.at_object_leave(self, destination) + except Exception: + logerr(errtxt % "at_object_leave()") + #emit_to_obj.msg(errtxt % "at_object_leave()") + #logger.log_trace() + return False + + if not quiet: + #tell the old room we are leaving + try: + self.announce_move_from(destination) + except Exception: + logerr(errtxt % "at_announce_move()") + #emit_to_obj.msg(errtxt % "at_announce_move()" ) + #logger.log_trace() + return False + + # Perform move + try: + #print "move_to location:", destination + self.location = destination + except Exception: + emit_to_obj.msg(errtxt % "location change") + log_trace() + return False + + if not quiet: + # Tell the new room we are there. + try: + self.announce_move_to(source_location) + except Exception: + logerr(errtxt % "announce_move_to()") + #emit_to_obj.msg(errtxt % "announce_move_to()") + #logger.log_trace() + return False + + # Perform eventual extra commands on the receiving location + # (the object has already arrived at this point) + try: + destination.at_object_receive(self, source_location) + except Exception: + logerr(errtxt % "at_object_receive()") + #emit_to_obj.msg(errtxt % "at_object_receive()") + #logger.log_trace() + return False + + # Execute eventual extra commands on this object after moving it + # (usually calling 'look') + try: + self.at_after_move(source_location) + except Exception: + logerr(errtxt % "at_after_move") + #emit_to_obj.msg(errtxt % "at_after_move()") + #logger.log_trace() + return False + return True + + def clear_exits(self): + """ + Destroys all of the exits and any exits pointing to this + object as a destination. + """ + for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]: + out_exit.delete() + for in_exit in ObjectDB.objects.filter(db_destination=self): + in_exit.delete() + + def clear_contents(self): + """ + Moves all objects (players/things) to their home + location or to default home. + """ + # Gather up everything that thinks this is its location. + objs = ObjectDB.objects.filter(db_location=self) + default_home_id = int(settings.DEFAULT_HOME.lstrip("#")) + try: + default_home = ObjectDB.objects.get(id=default_home_id) + if default_home.dbid == self.dbid: + # we are deleting default home! + default_home = None + except Exception: + string = _("Could not find default home '(#%d)'.") + log_errmsg(string % default_home_id) + default_home = None + + for obj in objs: + home = obj.home + # Obviously, we can't send it back to here. + if not home or (home and home.dbid == self.dbid): + obj.home = default_home + home = default_home + + # If for some reason it's still None... + if not home: + string = "Missing default home, '%s(#%d)' " + string += "now has a null location." + obj.location = None + obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin.")) + log_errmsg(string % (obj.name, obj.dbid)) + return + + if obj.has_player: + if home: + string = "Your current location has ceased to exist," + string += " moving you to %s(#%d)." + obj.msg(_(string) % (home.name, home.dbid)) + else: + # Famous last words: The player should never see this. + string = "This place should not exist ... contact an admin." + obj.msg(_(string)) + obj.move_to(home) + def copy(self, new_key=None): """ @@ -398,113 +743,84 @@ class DefaultObject(ObjectDB): directly. new_key (string) - new key/name of copied object. If new_key is not - specified, the copy will be named - _copy by default. - Returns: DefaultObject (copy of this one) + specified, the copy will be named _copy + by default. + Returns: Object (copy of this one) """ - return super(DefaultObject, self).copy(new_key=new_key) + def find_clone_key(): + """ + Append 01, 02 etc to obj.key. Checks next higher number in the + same location, then adds the next number available + returns the new clone name on the form keyXX + """ + key = self.key + num = 1 + for obj in (obj for obj in self.location.contents + if obj.key.startswith(key) and + obj.key.lstrip(key).isdigit()): + num += 1 + return "%s%03i" % (key, num) + new_key = new_key or find_clone_key() + return ObjectDB.objects.copy_object(self, new_key=new_key) + + delete_iter = 0 def delete(self): """ Deletes this object. Before deletion, this method makes sure to move all contained objects to their respective home locations, as well as clean up all exits to/from the object. - - Returns: boolean True if deletion succeded, False if there - were errors during deletion or deletion otherwise - failed. """ - return super(DefaultObject, self).delete() + global _ScriptDB + if not _ScriptDB: + from src.scripts.models import ScriptDB as _ScriptDB - # methods inherited from the typeclass system - - def is_typeclass(self, typeclass, exact=False): - """ - Returns true if this object has this type - OR has a typeclass which is an subclass of - the given typeclass. - - typeclass - can be a class object or the - python path to such an object to match against. - - exact - returns true only if the object's - type is exactly this typeclass, ignoring - parents. - - Returns: Boolean - """ - return super(DefaultObject, self).is_typeclass(typeclass, exact=exact) - - def swap_typeclass(self, new_typeclass, clean_attributes=False, no_default=True): - """ - This performs an in-situ swap of the typeclass. This means - that in-game, this object will suddenly be something else. - Player will not be affected. To 'move' a player to a different - object entirely (while retaining this object's type), use - self.player.swap_object(). - - Note that this might be an error prone operation if the - old/new typeclass was heavily customized - your code - might expect one and not the other, so be careful to - bug test your code if using this feature! Often its easiest - to create a new object and just swap the player over to - that one instead. - - Arguments: - new_typeclass (path/classobj) - type to switch to - clean_attributes (bool/list) - will delete all attributes - stored on this object (but not any - of the database fields such as name or - location). You can't get attributes back, - but this is often the safest bet to make - sure nothing in the new typeclass clashes - with the old one. If you supply a list, - only those named attributes will be cleared. - no_default - if this is active, the swapper will not allow for - swapping to a default typeclass in case the given - one fails for some reason. Instead the old one - will be preserved. - Returns: - boolean True/False depending on if the swap worked or not. - - - """ - return super(DefaultObject, self).swap_typeclass(new_typeclass, - clean_attributes=clean_attributes, no_default=no_default) - - def access(self, accessing_obj, access_type='read', default=False, **kwargs): - """ - Determines if another object has permission to access this object in - whatever way. - - accessing_obj (Object)- object trying to access this one - access_type (string) - type of access sought - default (bool) - what to return if no lock of access_type was found - **kwargs - passed to at_access hook along with result,accessing_obj and access_type - """ - result = super(DefaultObject, self).access(accessing_obj, access_type=access_type, default=default) - self.at_access(result, accessing_obj, access_type, **kwargs) - return result - - # OBS: DEPRECATED! - if result: - self.at_access_success(accessing_obj, access_type) - return True - else: - self.at_access_failure(accessing_obj, access_type) + if self.delete_iter > 0: + # make sure to only call delete once on this object + # (avoid recursive loops) return False - def check_permstring(self, permstring): - """ - This explicitly checks the given string against this object's - 'permissions' property without involving any locks. + if not self.at_object_delete(): + # this is an extra pre-check + # run before deletio field-related properties + # is kicked into gear. + self.delete_iter = 0 + return False - permstring (string) - permission string that need to match a - permission on the object. - (example: 'Builders') - """ - return super(DefaultObject, self).check_permstring(permstring) + self.delete_iter += 1 + + # See if we need to kick the player off. + + for session in self.sessions: + session.msg(_("Your character %s has been destroyed.") % self.key) + # no need to disconnect, Player just jumps to OOC mode. + # sever the connection (important!) + if self.player: + self.player.character = None + self.player = None + + for script in _ScriptDB.objects.get_all_scripts_on_obj(self): + script.stop() + #for script in _GA(self, "scripts").all(): + # script.stop() + + # if self.player: + # self.player.user.is_active = False + # self.player.user.save( + + # Destroy any exits to and from this room, if any + self.clear_exits() + # Clear out any non-exit objects located within the object + self.clear_contents() + self.attributes.clear() + self.nicks.clear() + self.aliases.clear() + + # Perform the deletion of the object + super(ObjectDB, self).delete() + return True + # methods inherited from the typeclass system def __eq__(self, other): """ @@ -514,14 +830,61 @@ class DefaultObject(ObjectDB): parent doesn't work. """ try: - return _GA(_GA(self, "dbobj"), "dbid") == _GA(_GA(other, "dbobj"), "dbid") + return self.dbid == other.dbid except AttributeError: # compare players instead try: - return _GA(_GA(_GA(self, "dbobj"), "player"), "uid") == _GA(_GA(other, "player"), "uid") + return self.player.uid == other.player.uid except AttributeError: return False + def at_instance_creation(self): + """ + This is called by the typeclass system whenever an instance of + this class is saved for the first time. It is a generic hook + for calling the startup hooks for the various game entities. + When overloading you generally don't overload this but + overload the hooks called by this method. + """ + self.basetype_setup() + self.at_object_creation() + + if hasattr(self, "_createdict"): + # this will only be set if the utils.create function + # was used to create the object. We want the create + # call's kwargs to override the values set by hooks. + cdict = self._createdict + updates = [] + if not cdict["key"]: + self.db_key = "#%i" % self.dbid + updates.append("db_key") + elif self.key != cdict["key"]: + updates.append("db_key") + self.db_key = cdict["key"] + if cdict["location"] and self.location != cdict["location"]: + self.db_location = cdict["location"] + updates.append("db_location") + if cdict["home"] and self.home != cdict["home"]: + self.home = cdict["home"] + updates.append("db_home") + if cdict["destination"] and self.destination != cdict["destination"]: + self.destination = cdict["destination"] + updates.append("db_destination") + if updates: + self.save(update_fields=updates) + if cdict["permissions"]: + self.permissions.add(cdict["permissions"]) + if cdict["locks"]: + self.locks.add(cdict["locks"]) + if cdict["aliases"]: + self.aliases.add(cdict["aliases"]) + if cdict["location"]: + cdict["location"].at_object_receive(self, None) + self.at_after_move(None) + + self.basetype_posthook_setup() + + ## hooks called by the game engine def basetype_setup(self): @@ -550,17 +913,19 @@ class DefaultObject(ObjectDB): def basetype_posthook_setup(self): """ - Called once, after basetype_setup and at_object_creation. This should - generally not be overloaded unless you are redefining how a - room/exit/object works. It allows for basetype-like setup after the - object is created. An example of this is EXITs, who need to know keys, - aliases, locks etc to set up their exit-cmdsets. + Called once, after basetype_setup and at_object_creation. This + should generally not be overloaded unless you are redefining + how a room/exit/object works. It allows for basetype-like + setup after the object is created. An example of this is + EXITs, who need to know keys, aliases, locks etc to set up + their exit-cmdsets. """ pass def at_object_creation(self): """ - Called once, when this object is first created. + Called once, when this object is first created. This is + the normal hook to overload for most object types. """ pass @@ -574,6 +939,8 @@ class DefaultObject(ObjectDB): def at_init(self): """ + DEPRECATED: Use __init__ instead. + This is always called whenever this object is initiated -- that is, whenever it its typeclass is cached from memory. This happens on-demand first time the object is used or activated @@ -582,14 +949,13 @@ class DefaultObject(ObjectDB): """ pass - def at_cmdset_get(self, **kwargs): """ Called just before cmdsets on this object are requested by the command handler. If changes need to be done on the fly to the cmdset before passing them on to the cmdhandler, this is the - place to do it. This is called also if the object currently - have no cmdsets. **kwargs are usually not set but could be + place to do it. This is called also if the object currently + have no cmdsets. **kwargs are usually not set but could be used e.g. to force rebuilding of a dynamically created cmdset or similar. """ @@ -655,29 +1021,6 @@ class DefaultObject(ObjectDB): """ pass - def at_access_success(self, accessing_obj, access_type): - """ - OBS: DEPRECATED. Use at_access instead - - This hook is called whenever accessing_obj succeed a lock check of - type access_type on this object, for whatever reason. The return value - of this hook is not used, the lock will still pass regardless of what - this hook does (use lockstring/funcs to tweak the lock result). - """ - log_depmsg("at_access_success is deprecated. Use at_access(result,**kwargs) instead.") - pass - - def at_access_failure(self, accessing_obj, access_type): - """ - OBS: DEPRECATED. Use at_access instead - - This hook is called whenever accessing_obj fails a lock check of type - access_type on this object, for whatever reason. The return value of - this hook is not used, the lock will still fail regardless of what - this hook does (use lockstring/funcs to tweak the lock result). - """ - log_depmsg("at_access_failure is deprecated. Use at_access(result,**kwargs) instead.") - pass # hooks called when moving the object diff --git a/src/players/bots.py b/src/players/bots.py index 6a32b1926..229875815 100644 --- a/src/players/bots.py +++ b/src/players/bots.py @@ -5,7 +5,7 @@ Player that are controlled by the server. """ from django.conf import settings -from src.players.player import Player +from src.players.player import DefaultPlayer from src.scripts.scripts import Script from src.commands.command import Command from src.commands.cmdset import CmdSet @@ -87,7 +87,7 @@ class BotCmdSet(CmdSet): # Bot base class -class Bot(Player): +class Bot(DefaultPlayer): """ A Bot will start itself when the server starts (it will generally not do so diff --git a/src/server/initial_setup.py b/src/server/initial_setup.py index 10b5e9049..524752bab 100644 --- a/src/server/initial_setup.py +++ b/src/server/initial_setup.py @@ -9,10 +9,10 @@ Everything starts at handle_setup() import django from django.conf import settings from django.contrib.auth import get_user_model +from django.utils.translation import ugettext as _ from src.server.models import ServerConfig from src.utils import create -from django.utils.translation import ugettext as _ - +from src.utils.utils import class_from_module def create_config_values(): """ @@ -26,10 +26,10 @@ def get_god_player(): """ Creates the god user. """ - PlayerDB = get_user_model() + Player = class_from_module(settings.BASE_PLAYER_TYPECLASS) try: - god_player = PlayerDB.objects.get(id=1) - except PlayerDB.DoesNotExist: + god_player = Player.objects.get(id=1) + except Player.DoesNotExist: txt = "\n\nNo superuser exists yet. The superuser is the 'owner'" txt += "\account on the Evennia server. Create a new superuser using" txt += "\nthe command" @@ -78,6 +78,7 @@ def create_objects(): god_character.save() god_player.attributes.add("_first_login", True) + print god_character god_player.attributes.add("_last_puppet", god_character) god_player.db._playable_characters.append(god_character) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index d7433f3e3..fcecdbe31 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -30,7 +30,9 @@ import sys import re import traceback import weakref -from importlib import import_module + +from django.db.models import signals +from django.dispatch import dispatcher from django.db import models from django.core.exceptions import ObjectDoesNotExist @@ -38,6 +40,7 @@ from django.conf import settings from django.utils.encoding import smart_str from src.utils.idmapper.models import SharedMemoryModel +from src.utils.idmapper.base import SharedMemoryModelBase from src.server.caches import get_prop_cache, set_prop_cache #from src.server.caches import set_attr_cache @@ -48,7 +51,8 @@ from src.locks.lockhandler import LockHandler #from src.utils import logger from django.db.models.base import ModelBase from src.utils.utils import ( - make_iter, is_iter, to_str, inherits_from, lazy_property) + make_iter, is_iter, to_str, inherits_from, lazy_property, + class_from_module) from src.utils.dbserialize import to_pickle, from_pickle from src.utils.picklefield import PickledObjectField @@ -56,6 +60,8 @@ __all__ = ("Attribute", "TypeNick", "TypedObject") TICKER_HANDLER = None +_DB_REGEX = re.compile(r"db$") + _PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE @@ -757,7 +763,16 @@ from django.apps.config import MODELS_MODULE_NAME from django.db.models.fields.related import OneToOneField #/ django patch imports -from src.utils.idmapper.base import SharedMemoryModelBase + +# signal receivers. Assigned in __new__ +def post_save(sender, instance, created, **kwargs): + """ + Is called Receive a signal just after the object is saved. + """ + if created: + instance.at_instance_creation() + #TODO - put OOB handler here? + class TypeclassBase(SharedMemoryModelBase): """ @@ -1013,10 +1028,14 @@ class TypeclassBase(SharedMemoryModelBase): new_class._prepare() new_class._meta.apps.register_model(new_class._meta.app_label, new_class) - return new_class + + #return new_class # /patch end + # attach signal + signals.post_save.connect(post_save, sender=new_class) + return new_class # @@ -1082,22 +1101,14 @@ class TypedObject(SharedMemoryModel): # typeclass mechanism - def _import_class(self, path): - path, clsname = path.rsplit(".", 1) - mod = import_module(path) - try: - return getattr(mod, clsname) - except AttributeError: - raise AttributeError("module '%s' has no attribute '%s'" % (path, clsname)) - def __init__(self, *args, **kwargs): typeclass_path = kwargs.pop("typeclass", None) super(TypedObject, self).__init__(*args, **kwargs) if typeclass_path: - self.__class__ = self._import_class(typeclass_path) + self.__class__ = class_from_module(typeclass_path) self.db_typclass_path = typeclass_path elif self.db_typeclass_path: - self.__class__ = self._import_class(self.db_typeclass_path) + self.__class__ = class_from_module(self.db_typeclass_path) else: self.db_typeclass_path = "%s.%s" % (self.__module__, self.__class__.__name__) # important to put this at the end since _meta is based on the set __class__ @@ -1372,12 +1383,12 @@ class TypedObject(SharedMemoryModel): self._is_deleted = True super(TypedObject, self).delete() - def save(self, *args, **kwargs): - "Block saving non-proxy typeclassed objects" - if not self._meta.proxy: - raise RuntimeError("Don't create instances of %s, " - "use its child typeclasses instead." % self.__class__.__name__) - super(TypedObject, self).save(*args, **kwargs) + #def save(self, *args, **kwargs): + # "Block saving non-proxy typeclassed objects" + # if not self._meta.proxy: + # raise RuntimeError("Don't create instances of %s, " + # "use its child typeclasses instead." % self.__class__.__name__) + # super(TypedObject, self).save(*args, **kwargs) # # Memory management diff --git a/src/utils/create.py b/src/utils/create.py index 43e2f04cd..4ab9492ea 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -25,12 +25,12 @@ from django.conf import settings from django.db import IntegrityError from src.utils.idmapper.models import SharedMemoryModel from src.utils import utils, logger -from src.utils.utils import make_iter +from src.utils.utils import make_iter, class_from_module, dbid_to_obj # delayed imports _User = None -_Object = None _ObjectDB = None +_Object = None _Script = None _ScriptDB = None _HelpEntry = None @@ -58,11 +58,7 @@ def handle_dbref(inp, objclass, raise_errors=True): objects. """ if not (isinstance(inp, basestring) and inp.startswith("#")): - try: - return inp.dbobj - except AttributeError: - return inp - + return inp # a string, analyze it inp = inp.lstrip('#') try: @@ -87,119 +83,54 @@ def create_object(typeclass=None, key=None, location=None, home=None, permissions=None, locks=None, aliases=None, destination=None, report_to=None, nohome=False): """ - 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. - 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. + Create a new in-game object. - 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. - nohome - this allows the creation of objects without a default home location; - this only used when creating the default location itself or during unittests + keywords: + typeclass - class or python path to a typeclass + key - name of the new object. If not set, a name of #dbref will be set. + home - obj or #dbref to use as the object's home location + permissions - a comma-separated string of permissions + locks - one or more lockstrings, separated by semicolons + aliases - a list of alternative keys + destination - obj or #dbref to use as an Exit's target + + nohome - this allows the creation of objects without a default home location; + only used when creating the default location itself or during unittests """ - global _Object, _ObjectDB - if not _Object: - from src.objects.objects import Object as _Object - if not _ObjectDB: - from src.objects.models import ObjectDB as _ObjectDB + typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS - # input validation + if isinstance(typeclass, basestring): + # a path is given. Load the actual typeclass + typeclass = class_from_module(typeclass, settings.OBJECT_TYPECLASS_PATHS) - if not typeclass: - typeclass = settings.BASE_OBJECT_TYPECLASS - elif isinstance(typeclass, _ObjectDB): - # this is already an objectdb instance, extract its typeclass - 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 = utils.to_unicode(typeclass) + # Setup input for the create command. We use ObjectDB as baseclass here + # to give us maximum freedom (the typeclasses will load + # correctly when each object is recovered). - # Setup input for the create command - - location = handle_dbref(location, _ObjectDB) - destination = handle_dbref(destination, _ObjectDB) - home = handle_dbref(home, _ObjectDB) + location = dbid_to_obj(location, _ObjectDB) + destination = dbid_to_obj(destination, _ObjectDB) + home = dbid_to_obj(home, _ObjectDB) if not home: try: - home = handle_dbref(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None + home = dbid_to_obj(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None except _ObjectDB.DoesNotExist: raise _ObjectDB.DoesNotExist("settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." % settings.DEFAULT_HOME) - # create new database object all in one go - new_db_object = _ObjectDB(db_key=key, db_location=location, + # create new instance + new_object = typeclass(db_key=key, db_location=location, db_destination=destination, db_home=home, - db_typeclass_path=typeclass) - - if not key: - # the object should always have a key, so if not set we give a default - new_db_object.key = "#%i" % new_db_object.dbid - - # this will either load the typeclass or the default one (will also save object) - new_object = new_db_object.typeclass - - 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 - try: - SharedMemoryModel.delete(new_db_object) - except AssertionError: - # this happens if object was never created - pass - if report_to: - report_to = handle_dbref(report_to, _ObjectDB) - _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 - # as if it was the database object. - - # call the hook methods. This is where all at_creation - # customization happens as the typeclass stores custom - # things on its database object. - - # note - this may override input keys, locations etc! - new_object.basetype_setup() # setup the basics of Exits, Characters etc. - new_object.at_object_creation() - - # we want the input to override that set in the hooks, so - # we re-apply those if needed - if new_object.key != key: - new_object.key = key - if new_object.location != location: - new_object.location = location - if new_object.home != home: - new_object.home = home - if new_object.destination != destination: - new_object.destination = destination - - # custom-given perms/locks do overwrite hooks - if permissions: - new_object.permissions.add(permissions) - if locks: - new_object.locks.add(locks) - if aliases: - new_object.aliases.add(aliases) - - # trigger relevant move_to hooks in order to display messages. - if location: - location.at_object_receive(new_object, None) - new_object.at_after_move(None) - - # post-hook setup (mainly used by Exits) - new_object.basetype_posthook_setup() - + db_typeclass_path=typeclass.path) + # store the call signature for the signal + new_object._createdict = {"key":key, "location":location, "destination":destination, + "home":home, "typeclass":typeclass.path, "permissions":permissions, + "locks":locks, "aliases":aliases, "destination":destination, + "report_to":report_to, "nohome":nohome} + # this will trigger the save signal which in turn calls the + # at_instance_creation hook on the typeclass, where the _createdict can be + # used. + new_object.save() return new_object #alias for create_object diff --git a/src/utils/utils.py b/src/utils/utils.py index 6305097e9..fa17d4e0e 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -12,11 +12,11 @@ import imp import types import math import re -import importlib import textwrap import datetime import random import traceback +from importlib import import_module from inspect import ismodule from collections import defaultdict from twisted.internet import threads, defer, reactor @@ -347,16 +347,43 @@ def dbref(dbref, reqhash=True): Output is the integer part. """ if reqhash: - return (int(dbref.lstrip('#')) if (isinstance(dbref, basestring) and + num = (int(dbref.lstrip('#')) if (isinstance(dbref, basestring) and dbref.startswith("#") and dbref.lstrip('#').isdigit()) else None) + return num if num > 0 else None elif isinstance(dbref, basestring): dbref = dbref.lstrip('#') - return int(dbref) if dbref.isdigit() else None - return dbref if isinstance(dbref, int) else None + return int(dbref) if dbref.isdigit() and int(dbref) > 0 else None + else: + return dbref if isinstance(dbref, int) else None +def dbid_to_obj(inp, objclass, raise_errors=True): + """ + Convert a #dbid to a valid object of objclass. objclass + should be a valid object class to filter against (objclass.filter ...) + If not raise_errors is set, this will swallow errors of non-existing + objects. + """ + dbid = dbref(inp) + if not dbid: + # we only convert #dbrefs + return inp + try: + if int(inp) < 0: + return None + except ValueError: + return None + + # if we get to this point, inp is an integer dbref; get the matching object + try: + return objclass.objects.get(id=inp) + except Exception: + if raise_errors: + raise + return inp + def to_unicode(obj, encoding='utf-8', force_string=False): """ This decodes a suitable object to the unicode format. Note that @@ -887,15 +914,48 @@ def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None): paths = [path] + make_iter(defaultpaths) for modpath in paths: try: - mod = importlib.import_module(path) + mod = import_module(path) except ImportError, ex: - if not str(ex) == "No module named %s" % path: + if not str(ex).startswith ("No module named %s" % path): # this means the module was found but it # triggers an ImportError on import. raise ex return getattr(mod, variable, default) return default +def class_from_module(path, defaultpaths=None): + """ + Return a class from a module, given the module's path. This is + primarily used to convert db_typeclass_path:s to classes. + + if a list of defaultpaths is given, try subsequent runs by + prepending those paths to the given path. + """ + cls = None + if defaultpaths: + paths = [path] + make_iter(defaultpaths) if defaultpaths else [] + else: + paths = [path] + + for path in paths: + path, clsname = path.rsplit(".", 1) + try: + mod = import_module(path) + except ImportError, ex: + # normally this is due to a not-found property + if not str(ex).startswith ("No module named %s" % path): + raise ex + try: + cls = getattr(mod, clsname) + break + except AttributeError, ex: + if not str(ex).startswith("Object 'module' has no attribute '%s'" % clsname): + raise ex + if not cls: + raise ImportError("Could not load typeclass '%s'." % path) + return cls + + def init_new_player(player): """ Helper method to call all hooks, set flags etc on a newly created From 302f5bdd81a350c5835b185017bc1fb42b1813cd Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 23 Dec 2014 22:25:39 +0100 Subject: [PATCH 027/250] Refactored the typeclass base, moved Attributes/Tags into separate modules and the django-proxy patch to its on module too. Lots of other cleanups. --- src/comms/__init__.py | 6 +- src/help/__init__.py | 6 +- src/objects/objects.py | 3 +- src/players/__init__.py | 6 +- src/players/models.py | 3 +- src/scripts/__init__.py | 4 +- src/server/initial_setup.py | 1 - src/typeclasses/attributes.py | 515 ++++++++++++++ src/typeclasses/django_new_patch.py | 257 +++++++ src/typeclasses/models.py | 1008 +-------------------------- src/typeclasses/tags.py | 193 +++++ 11 files changed, 1018 insertions(+), 984 deletions(-) create mode 100644 src/typeclasses/attributes.py create mode 100644 src/typeclasses/django_new_patch.py create mode 100644 src/typeclasses/tags.py diff --git a/src/comms/__init__.py b/src/comms/__init__.py index 721f85bbb..da76254d7 100644 --- a/src/comms/__init__.py +++ b/src/comms/__init__.py @@ -8,7 +8,7 @@ src.comms.channelmanager. """ -from src.comms.models import * +#from src.comms.models import * -msgmanager = Msg.objects -channelmanager = ChannelDB.objects +#msgmanager = Msg.objects +#channelmanager = ChannelDB.objects diff --git a/src/help/__init__.py b/src/help/__init__.py index e1f630d05..556eb55a7 100644 --- a/src/help/__init__.py +++ b/src/help/__init__.py @@ -6,6 +6,6 @@ Also, the initiated object manager is available as src.help.manager. """ -from src.help.models import * - -manager = HelpEntry.objects +#from src.help.models import * +# +#manager = HelpEntry.objects diff --git a/src/objects/objects.py b/src/objects/objects.py index 4bacf0336..76b3693b9 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -18,7 +18,8 @@ they control by simply linking to a new object's user property. import traceback from django.conf import settings -from src.typeclasses.models import TypeclassBase, NickHandler +from src.typeclasses.models import TypeclassBase +from src.typeclasses.attributes import NickHandler from src.objects.manager import ObjectManager from src.objects.models import ObjectDB from src.scripts.scripthandler import ScriptHandler diff --git a/src/players/__init__.py b/src/players/__init__.py index a448c50b4..8e65cbb29 100644 --- a/src/players/__init__.py +++ b/src/players/__init__.py @@ -8,6 +8,6 @@ Also, the initiated object manager is available as src.players.manager. """ #from src.players.player import * -from src.players.models import PlayerDB - -manager = PlayerDB.objects +#from src.players.models import PlayerDB +# +#manager = PlayerDB.objects diff --git a/src/players/models.py b/src/players/models.py index ebe0ad67e..e16c58979 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -23,7 +23,8 @@ from django.utils.encoding import smart_str from src.players.manager import PlayerDBManager from src.scripts.models import ScriptDB -from src.typeclasses.models import (TypedObject, NickHandler) +from src.typeclasses.models import TypedObject +from src.typeclasses.attributes import NickHandler from src.scripts.scripthandler import ScriptHandler from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler diff --git a/src/scripts/__init__.py b/src/scripts/__init__.py index 0b77cd143..26dd16ec7 100644 --- a/src/scripts/__init__.py +++ b/src/scripts/__init__.py @@ -10,6 +10,6 @@ Also, the initiated object manager is available as src.scripts.manager. # Note - we MUST NOT import src.scripts.scripts here, or # proxy models will fall under Django migrations. #from src.scripts.scripts import * -from src.scripts.models import ScriptDB +#from src.scripts.models import ScriptDB -manager = ScriptDB.objects +#manager = ScriptDB.objects diff --git a/src/server/initial_setup.py b/src/server/initial_setup.py index 524752bab..43aed5248 100644 --- a/src/server/initial_setup.py +++ b/src/server/initial_setup.py @@ -78,7 +78,6 @@ def create_objects(): god_character.save() god_player.attributes.add("_first_login", True) - print god_character god_player.attributes.add("_last_puppet", god_character) god_player.db._playable_characters.append(god_character) diff --git a/src/typeclasses/attributes.py b/src/typeclasses/attributes.py new file mode 100644 index 000000000..38783400d --- /dev/null +++ b/src/typeclasses/attributes.py @@ -0,0 +1,515 @@ +""" +Attributes are arbitrary data stored on objects. Attributes supports +both pure-string values and pickled arbitrary data. + +Attributes are also used to implement Nicks. This module also contains +the Attribute- and NickHandlers as well as the NAttributeHandler, +which is a non-db version of Attributes. + + +""" +import re +import weakref + +from django.db import models +from django.conf import settings +from django.utils.encoding import smart_str + +from src.locks.lockhandler import LockHandler +from src.utils.idmapper.models import SharedMemoryModel +from src.utils.dbserialize import to_pickle, from_pickle +from src.utils.picklefield import PickledObjectField +from src.utils.utils import lazy_property, to_str, make_iter + +_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE + +#------------------------------------------------------------ +# +# Attributes +# +#------------------------------------------------------------ + +class Attribute(SharedMemoryModel): + """ + Attributes are things that are specific to different types of objects. For + example, a drink container needs to store its fill level, whereas an exit + needs to store its open/closed/locked/unlocked state. These are done via + attributes, rather than making different classes for each object type and + storing them directly. The added benefit is that we can add/remove + attributes on the fly as we like. + The Attribute class defines the following properties: + key - primary identifier + lock_storage - perm strings + obj - which object the attribute is defined on + date_created - when the attribute was created. + value - the data stored in the attribute, in pickled form + using wrappers to be able to store/retrieve models. + strvalue - string-only data. This data is not pickled and is + thus faster to search for in the database. + category - optional character string for grouping the Attribute + + """ + + # + # Attribute Database Model setup + # + # These database fields are all set using their corresponding properties, + # named same as the field, but withtout the db_* prefix. + db_key = models.CharField('key', max_length=255, db_index=True) + db_value = PickledObjectField( + 'value', null=True, + help_text="The data returned when the attribute is accessed. Must be " + "written as a Python literal if editing through the admin " + "interface. Attribute values which are not Python literals " + "cannot be edited through the admin interface.") + db_strvalue = models.TextField( + 'strvalue', null=True, blank=True, + help_text="String-specific storage for quick look-up") + db_category = models.CharField( + 'category', max_length=128, db_index=True, blank=True, null=True, + help_text="Optional categorization of attribute.") + # Lock storage + db_lock_storage = models.TextField( + 'locks', blank=True, + help_text="Lockstrings for this object are stored here.") + db_model = models.CharField( + 'model', max_length=32, db_index=True, blank=True, null=True, + help_text="Which model of object this attribute is attached to (A " + "natural key like objects.dbobject). You should not change " + "this value unless you know what you are doing.") + # subclass of Attribute (None or nick) + db_attrtype = models.CharField( + 'attrtype', max_length=16, db_index=True, blank=True, null=True, + help_text="Subclass of Attribute (None or nick)") + # time stamp + db_date_created = models.DateTimeField( + 'date_created', editable=False, auto_now_add=True) + + # Database manager + #objects = managers.AttributeManager() + + @lazy_property + def locks(self): + return LockHandler(self) + + class Meta: + "Define Django meta options" + verbose_name = "Evennia Attribute" + + # read-only wrappers + key = property(lambda self: self.db_key) + strvalue = property(lambda self: self.db_strvalue) + category = property(lambda self: self.db_category) + model = property(lambda self: self.db_model) + attrtype = property(lambda self: self.db_attrtype) + date_created = property(lambda self: self.db_date_created) + + def __lock_storage_get(self): + return self.db_lock_storage + def __lock_storage_set(self, value): + self.db_lock_storage = value + self.save(update_fields=["db_lock_storage"]) + def __lock_storage_del(self): + self.db_lock_storage = "" + self.save(update_fields=["db_lock_storage"]) + lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del) + + # Wrapper properties to easily set database fields. These are + # @property decorators that allows to access these fields using + # normal python operations (without having to remember to save() + # etc). So e.g. a property 'attr' has a get/set/del decorator + # defined that allows the user to do self.attr = value, + # value = self.attr and del self.attr respectively (where self + # is the object in question). + + # value property (wraps db_value) + #@property + def __value_get(self): + """ + Getter. Allows for value = self.value. + We cannot cache here since it makes certain cases (such + as storing a dbobj which is then deleted elsewhere) out-of-sync. + The overhead of unpickling seems hard to avoid. + """ + return from_pickle(self.db_value, db_obj=self) + + #@value.setter + def __value_set(self, new_value): + """ + Setter. Allows for self.value = value. We cannot cache here, + see self.__value_get. + """ + self.db_value = to_pickle(new_value) + self.save(update_fields=["db_value"]) + + #@value.deleter + def __value_del(self): + "Deleter. Allows for del attr.value. This removes the entire attribute." + self.delete() + value = property(__value_get, __value_set, __value_del) + + # + # + # Attribute methods + # + # + + def __str__(self): + return smart_str("%s(%s)" % (self.db_key, self.id)) + + def __unicode__(self): + return u"%s(%s)" % (self.db_key,self.id) + + def access(self, accessing_obj, access_type='read', default=False, **kwargs): + """ + Determines if another object has permission to access. + accessing_obj - object trying to access this one + access_type - type of access sought + default - what to return if no lock of access_type was found + **kwargs - passed to at_access hook along with result. + """ + result = self.locks.check(accessing_obj, access_type=access_type, default=default) + #self.at_access(result, **kwargs) + return result + + +# +# Handlers making use of the Attribute model +# + +class AttributeHandler(object): + """ + Handler for adding Attributes to the object. + """ + _m2m_fieldname = "db_attributes" + _attrcreate = "attrcreate" + _attredit = "attredit" + _attrread = "attrread" + _attrtype = None + + def __init__(self, obj): + "Initialize handler" + self.obj = obj + self._objid = obj.id + self._model = to_str(obj.__dbclass__.__name__.lower()) + self._cache = None + + def _recache(self): + "Cache all attributes of this object" + query = {"%s__id" % self._model : self._objid, + "attribute__db_attrtype" : self._attrtype} + attrs = [conn.attribute for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] + self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(), + attr.db_category.lower() if conn.attribute.db_category else None), + attr) for attr in attrs) + + def has(self, key, category=None): + """ + Checks if the given Attribute (or list of Attributes) exists on + the object. + + If an iterable is given, returns list of booleans. + """ + if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: + self._recache() + key = [k.strip().lower() for k in make_iter(key) if k] + category = category.strip().lower() if category is not None else None + searchkeys = ["%s-%s" % (k, category) for k in make_iter(key)] + ret = [self._cache.get(skey) for skey in searchkeys if skey in self._cache] + return ret[0] if len(ret) == 1 else ret + + def get(self, key=None, category=None, default=None, return_obj=False, + strattr=False, raise_exception=False, accessing_obj=None, + default_access=True, not_found_none=False): + """ + Returns the value of the given Attribute or list of Attributes. + strattr will cause the string-only value field instead of the normal + pickled field data. Use to get back values from Attributes added with + the strattr keyword. + If return_obj=True, return the matching Attribute object + instead. Returns default if no matches (or [ ] if key was a list + with no matches). If raise_exception=True, failure to find a + match will raise AttributeError instead. + + If accessing_obj is given, its "attrread" permission lock will be + checked before displaying each looked-after Attribute. If no + accessing_obj is given, no check will be done. + """ + + class RetDefault(object): + "Holds default values" + def __init__(self): + self.value = default + self.strvalue = str(default) if default is not None else None + + if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: + self._recache() + ret = [] + key = [k.strip().lower() for k in make_iter(key) if k] + category = category.strip().lower() if category is not None else None + #print "cache:", self._cache.keys(), key + if not key: + # return all with matching category (or no category) + catkey = "-%s" % category if category is not None else None + ret = [attr for key, attr in self._cache.items() if key and key.endswith(catkey)] + else: + for searchkey in ("%s-%s" % (k, category) for k in key): + attr_obj = self._cache.get(searchkey) + if attr_obj: + ret.append(attr_obj) + else: + if raise_exception: + raise AttributeError + else: + ret.append(RetDefault()) + if accessing_obj: + # check 'attrread' locks + ret = [attr for attr in ret if attr.access(accessing_obj, self._attrread, default=default_access)] + if strattr: + ret = ret if return_obj else [attr.strvalue for attr in ret if attr] + else: + ret = ret if return_obj else [attr.value for attr in ret if attr] + if not ret: + return ret if len(key) > 1 else default + return ret[0] if len(ret)==1 else ret + + + def add(self, key, value, category=None, lockstring="", + strattr=False, accessing_obj=None, default_access=True): + """ + Add attribute to object, with optional lockstring. + + If strattr is set, the db_strvalue field will be used (no pickling). + Use the get() method with the strattr keyword to get it back. + + If accessing_obj is given, self.obj's 'attrcreate' lock access + will be checked against it. If no accessing_obj is given, no check + will be done. + """ + if accessing_obj and not self.obj.access(accessing_obj, + self._attrcreate, default=default_access): + # check create access + return + if self._cache is None: + self._recache() + if not key: + return + + category = category.strip().lower() if category is not None else None + keystr = key.strip().lower() + cachekey = "%s-%s" % (keystr, category) + attr_obj = self._cache.get(cachekey) + + if attr_obj: + # update an existing attribute object + if strattr: + # store as a simple string (will not notify OOB handlers) + attr_obj.db_strvalue = value + attr_obj.save(update_fields=["db_strvalue"]) + else: + # store normally (this will also notify OOB handlers) + attr_obj.value = value + else: + # create a new Attribute (no OOB handlers can be notified) + kwargs = {"db_key" : keystr, "db_category" : category, + "db_model" : self._model, "db_attrtype" : self._attrtype, + "db_value" : None if strattr else to_pickle(value), + "db_strvalue" : value if strattr else None} + new_attr = Attribute(**kwargs) + new_attr.save() + getattr(self.obj, self._m2m_fieldname).add(new_attr) + self._cache[cachekey] = new_attr + + + def batch_add(self, key, value, category=None, lockstring="", + strattr=False, accessing_obj=None, default_access=True): + """ + Batch-version of add(). This is more efficient than + repeat-calling add. + + key and value must be sequences of the same length, each + representing a key-value pair. + + """ + if accessing_obj and not self.obj.access(accessing_obj, + self._attrcreate, default=default_access): + # check create access + return + if self._cache is None: + self._recache() + if not key: + return + + keys, values= make_iter(key), make_iter(value) + + if len(keys) != len(values): + raise RuntimeError("AttributeHandler.add(): key and value of different length: %s vs %s" % key, value) + category = category.strip().lower() if category is not None else None + new_attrobjs = [] + for ikey, keystr in enumerate(keys): + keystr = keystr.strip().lower() + new_value = values[ikey] + cachekey = "%s-%s" % (keystr, category) + attr_obj = self._cache.get(cachekey) + + if attr_obj: + # update an existing attribute object + if strattr: + # store as a simple string (will not notify OOB handlers) + attr_obj.db_strvalue = new_value + attr_obj.save(update_fields=["db_strvalue"]) + else: + # store normally (this will also notify OOB handlers) + attr_obj.value = new_value + else: + # create a new Attribute (no OOB handlers can be notified) + kwargs = {"db_key" : keystr, "db_category" : category, + "db_attrtype" : self._attrtype, + "db_value" : None if strattr else to_pickle(new_value), + "db_strvalue" : value if strattr else None} + new_attr = Attribute(**kwargs) + new_attr.save() + new_attrobjs.append(new_attr) + if new_attrobjs: + # Add new objects to m2m field all at once + getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs) + self._recache() + + + def remove(self, key, raise_exception=False, category=None, + accessing_obj=None, default_access=True): + """Remove attribute or a list of attributes from object. + + If accessing_obj is given, will check against the 'attredit' lock. + If not given, this check is skipped. + """ + if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: + self._recache() + key = [k.strip().lower() for k in make_iter(key) if k] + category = category.strip().lower() if category is not None else None + for searchstr in ("%s-%s" % (k, category) for k in key): + attr_obj = self._cache.get(searchstr) + if attr_obj: + if not (accessing_obj and not attr_obj.access(accessing_obj, + self._attredit, default=default_access)): + attr_obj.delete() + elif not attr_obj and raise_exception: + raise AttributeError + self._recache() + + def clear(self, category=None, accessing_obj=None, default_access=True): + """ + Remove all Attributes on this object. If accessing_obj is + given, check the 'attredit' lock on each Attribute before + continuing. If not given, skip check. + """ + if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: + self._recache() + if accessing_obj: + [attr.delete() for attr in self._cache.values() + if attr.access(accessing_obj, self._attredit, default=default_access)] + else: + [attr.delete() for attr in self._cache.values()] + self._recache() + + def all(self, accessing_obj=None, default_access=True): + """ + Return all Attribute objects on this object. + + If accessing_obj is given, check the "attrread" lock on + each attribute before returning them. If not given, this + check is skipped. + """ + if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: + self._recache() + attrs = sorted(self._cache.values(), key=lambda o: o.id) + if accessing_obj: + return [attr for attr in attrs + if attr.access(accessing_obj, self._attredit, default=default_access)] + else: + return attrs + + +class NickHandler(AttributeHandler): + """ + Handles the addition and removal of Nicks + (uses Attributes' strvalue and category fields) + + Nicks are stored as Attributes + with categories nick_ + """ + _attrtype = "nick" + + def has(self, key, category="inputline"): + return super(NickHandler, self).has(key, category=category) + + def get(self, key=None, category="inputline", **kwargs): + "Get the replacement value matching the given key and category" + return super(NickHandler, self).get(key=key, category=category, strattr=True, **kwargs) + + def add(self, key, replacement, category="inputline", **kwargs): + "Add a new nick" + super(NickHandler, self).add(key, replacement, category=category, strattr=True, **kwargs) + + def remove(self, key, category="inputline", **kwargs): + "Remove Nick with matching category" + super(NickHandler, self).remove(key, category=category, **kwargs) + + def nickreplace(self, raw_string, categories=("inputline", "channel"), include_player=True): + "Replace entries in raw_string with nick replacement" + raw_string + obj_nicks, player_nicks = [], [] + for category in make_iter(categories): + obj_nicks.extend([n for n in make_iter(self.get(category=category, return_obj=True)) if n]) + if include_player and self.obj.has_player: + for category in make_iter(categories): + player_nicks.extend([n for n in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) if n]) + for nick in obj_nicks + player_nicks: + # make a case-insensitive match here + match = re.match(re.escape(nick.db_key), raw_string, re.IGNORECASE) + if match: + raw_string = raw_string.replace(match.group(), nick.db_strvalue, 1) + break + return raw_string + + +class NAttributeHandler(object): + """ + This stand-alone handler manages non-database saving. + It is similar to AttributeHandler and is used + by the .ndb handler in the same way as .db does + for the AttributeHandler. + """ + def __init__(self, obj): + "initialized on the object" + self._store = {} + self.obj = weakref.proxy(obj) + + def has(self, key): + "Check if object has this attribute or not" + return key in self._store + + def get(self, key): + "Returns named key value" + return self._store.get(key, None) + + def add(self, key, value): + "Add new key and value" + self._store[key] = value + self.obj.set_recache_protection() + + def remove(self, key): + "Remove key from storage" + if key in self._store: + del self._store[key] + self.obj.set_recache_protection(self._store) + + def clear(self): + "Remove all nattributes from handler" + self._store = {} + + def all(self, return_tuples=False): + "List all keys or (keys, values) stored, except _keys" + if return_tuples: + return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] + return [key for key in self._store if not key.startswith("_")] diff --git a/src/typeclasses/django_new_patch.py b/src/typeclasses/django_new_patch.py new file mode 100644 index 000000000..eee233c0a --- /dev/null +++ b/src/typeclasses/django_new_patch.py @@ -0,0 +1,257 @@ +""" +This is a patch of django.db.models.base.py:__new__, to allow for the +proxy system to allow multiple inheritance when both parents are of +the same base model. + +This patch is implemented as per +https://code.djangoproject.com/ticket/11560 and will hopefully be +possibe to remove as it gets adde to django's main branch. +""" + +# django patch imports +import sys +import copy +import warnings +from django.apps import apps +from django.db.models.base import ModelBase +from django.db.models.base import subclass_exception +from django.core.exceptions import ObjectDoesNotExist +from django.db.models.options import Options +from django.utils.deprecation import RemovedInDjango19Warning +from django.core.exceptions import MultipleObjectsReturned, FieldError +from django.apps.config import MODELS_MODULE_NAME +from django.db.models.fields.related import OneToOneField +#/ django patch imports + +def patched_new(cls, name, bases, attrs): + "Patched version of __new__" + + super_new = super(ModelBase, cls).__new__ + + # Also ensure initialization is only performed for subclasses of Model + # (excluding Model class itself). + parents = [b for b in bases if isinstance(b, ModelBase)] + if not parents: + return super_new(cls, name, bases, attrs) + + # Create the class. + module = attrs.pop('__module__') + new_class = super_new(cls, name, bases, {'__module__': module}) + attr_meta = attrs.pop('Meta', None) + abstract = getattr(attr_meta, 'abstract', False) + if not attr_meta: + meta = getattr(new_class, 'Meta', None) + else: + meta = attr_meta + base_meta = getattr(new_class, '_meta', None) + + # Look for an application configuration to attach the model to. + app_config = apps.get_containing_app_config(module) + + if getattr(meta, 'app_label', None) is None: + + if app_config is None: + # If the model is imported before the configuration for its + # application is created (#21719), or isn't in an installed + # application (#21680), use the legacy logic to figure out the + # app_label by looking one level up from the package or module + # named 'models'. If no such package or module exists, fall + # back to looking one level up from the module this model is + # defined in. + + # For 'django.contrib.sites.models', this would be 'sites'. + # For 'geo.models.places' this would be 'geo'. + + msg = ( + "Model class %s.%s doesn't declare an explicit app_label " + "and either isn't in an application in INSTALLED_APPS or " + "else was imported before its application was loaded. " % + (module, name)) + if abstract: + msg += "Its app_label will be set to None in Django 1.9." + else: + msg += "This will no longer be supported in Django 1.9." + warnings.warn(msg, RemovedInDjango19Warning, stacklevel=2) + + model_module = sys.modules[new_class.__module__] + package_components = model_module.__name__.split('.') + package_components.reverse() # find the last occurrence of 'models' + try: + app_label_index = package_components.index(MODELS_MODULE_NAME) + 1 + except ValueError: + app_label_index = 1 + kwargs = {"app_label": package_components[app_label_index]} + + else: + kwargs = {"app_label": app_config.label} + + else: + kwargs = {} + + new_class.add_to_class('_meta', Options(meta, **kwargs)) + if not abstract: + new_class.add_to_class( + 'DoesNotExist', + subclass_exception( + str('DoesNotExist'), + tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,), + module, + attached_to=new_class)) + new_class.add_to_class( + 'MultipleObjectsReturned', + subclass_exception( + str('MultipleObjectsReturned'), + tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,), + module, + attached_to=new_class)) + if base_meta and not base_meta.abstract: + # Non-abstract child classes inherit some attributes from their + # non-abstract parent (unless an ABC comes before it in the + # method resolution order). + if not hasattr(meta, 'ordering'): + new_class._meta.ordering = base_meta.ordering + if not hasattr(meta, 'get_latest_by'): + new_class._meta.get_latest_by = base_meta.get_latest_by + + is_proxy = new_class._meta.proxy + + # If the model is a proxy, ensure that the base class + # hasn't been swapped out. + if is_proxy and base_meta and base_meta.swapped: + raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped)) + + if getattr(new_class, '_default_manager', None): + if not is_proxy: + # Multi-table inheritance doesn't inherit default manager from + # parents. + new_class._default_manager = None + new_class._base_manager = None + else: + # Proxy classes do inherit parent's default manager, if none is + # set explicitly. + new_class._default_manager = new_class._default_manager._copy_to_model(new_class) + new_class._base_manager = new_class._base_manager._copy_to_model(new_class) + + # Add all attributes to the class. + for obj_name, obj in attrs.items(): + new_class.add_to_class(obj_name, obj) + + # All the fields of any type declared on this model + new_fields = ( + new_class._meta.local_fields + + new_class._meta.local_many_to_many + + new_class._meta.virtual_fields + ) + field_names = set(f.name for f in new_fields) + + # Basic setup for proxy models. + if is_proxy: + base = None + for parent in [kls for kls in parents if hasattr(kls, '_meta')]: + if parent._meta.abstract: + if parent._meta.fields: + raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) + else: + continue + #if base is not None: # patch + while parent._meta.proxy: # patch + parent = parent._meta.proxy_for_model # patch + if base is not None and base is not parent: # patch + raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name) + else: + base = parent + if base is None: + raise TypeError("Proxy model '%s' has no non-abstract model base class." % name) + new_class._meta.setup_proxy(base) + new_class._meta.concrete_model = base._meta.concrete_model + else: + new_class._meta.concrete_model = new_class + + # Collect the parent links for multi-table inheritance. + parent_links = {} + for base in reversed([new_class] + parents): + # Conceptually equivalent to `if base is Model`. + if not hasattr(base, '_meta'): + continue + # Skip concrete parent classes. + if base != new_class and not base._meta.abstract: + continue + # Locate OneToOneField instances. + for field in base._meta.local_fields: + if isinstance(field, OneToOneField): + parent_links[field.rel.to] = field + + # Do the appropriate setup for any model parents. + for base in parents: + original_base = base + if not hasattr(base, '_meta'): + # Things without _meta aren't functional models, so they're + # uninteresting parents. + continue + + parent_fields = base._meta.local_fields + base._meta.local_many_to_many + # Check for clashes between locally declared fields and those + # on the base classes (we cannot handle shadowed fields at the + # moment). + for field in parent_fields: + if field.name in field_names: + raise FieldError( + 'Local field %r in class %r clashes ' + 'with field of similar name from ' + 'base class %r' % (field.name, name, base.__name__) + ) + if not base._meta.abstract: + # Concrete classes... + base = base._meta.concrete_model + if base in parent_links: + field = parent_links[base] + elif not is_proxy: + attr_name = '%s_ptr' % base._meta.model_name + field = OneToOneField(base, name=attr_name, + auto_created=True, parent_link=True) + # Only add the ptr field if it's not already present; + # e.g. migrations will already have it specified + if not hasattr(new_class, attr_name): + new_class.add_to_class(attr_name, field) + else: + field = None + new_class._meta.parents[base] = field + else: + # .. and abstract ones. + for field in parent_fields: + new_class.add_to_class(field.name, copy.deepcopy(field)) + + # Pass any non-abstract parent classes onto child. + new_class._meta.parents.update(base._meta.parents) + + # Inherit managers from the abstract base classes. + new_class.copy_managers(base._meta.abstract_managers) + + # Proxy models inherit the non-abstract managers from their base, + # unless they have redefined any of them. + if is_proxy: + new_class.copy_managers(original_base._meta.concrete_managers) + + # Inherit virtual fields (like GenericForeignKey) from the parent + # class + for field in base._meta.virtual_fields: + if base._meta.abstract and field.name in field_names: + raise FieldError( + 'Local field %r in class %r clashes ' + 'with field of similar name from ' + 'abstract base class %r' % (field.name, name, base.__name__) + ) + new_class.add_to_class(field.name, copy.deepcopy(field)) + + if abstract: + # Abstract base models can't be instantiated and don't appear in + # the list of models for an app. We do the final setup for them a + # little differently from normal models. + attr_meta.abstract = False + new_class.Meta = attr_meta + return new_class + + new_class._prepare() + new_class._meta.apps.register_model(new_class._meta.app_label, new_class) + + return new_class diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index fcecdbe31..3cb3445ed 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -26,19 +26,16 @@ these to create custom managers. """ -import sys -import re -import traceback -import weakref - from django.db.models import signals -from django.dispatch import dispatcher from django.db import models from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from django.utils.encoding import smart_str +from src.typeclasses.attributes import Attribute, AttributeHandler, NAttributeHandler +from src.typeclasses.tags import Tag, TagHandler, AliasHandler, PermissionHandler + from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.base import SharedMemoryModelBase from src.server.caches import get_prop_cache, set_prop_cache @@ -48,698 +45,18 @@ from src.server.caches import get_prop_cache, set_prop_cache #from src.server.models import ServerConfig from src.typeclasses import managers from src.locks.lockhandler import LockHandler -#from src.utils import logger -from django.db.models.base import ModelBase from src.utils.utils import ( - make_iter, is_iter, to_str, inherits_from, lazy_property, + is_iter, to_str, inherits_from, lazy_property, class_from_module) -from src.utils.dbserialize import to_pickle, from_pickle -from src.utils.picklefield import PickledObjectField +from src.typeclasses.django_new_patch import patched_new __all__ = ("Attribute", "TypeNick", "TypedObject") TICKER_HANDLER = None -_DB_REGEX = re.compile(r"db$") - _PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE -_GA = object.__getattribute__ -_SA = object.__setattr__ -_DA = object.__delattr__ - - -#------------------------------------------------------------ -# -# Attributes -# -#------------------------------------------------------------ - -class Attribute(SharedMemoryModel): - """ - Abstract django model. - - Attributes are things that are specific to different types of objects. For - example, a drink container needs to store its fill level, whereas an exit - needs to store its open/closed/locked/unlocked state. These are done via - attributes, rather than making different classes for each object type and - storing them directly. The added benefit is that we can add/remove - attributes on the fly as we like. - The Attribute class defines the following properties: - key - primary identifier - lock_storage - perm strings - obj - which object the attribute is defined on - date_created - when the attribute was created. - value - the data stored in the attribute, in pickled form - using wrappers to be able to store/retrieve models. - strvalue - string-only data. This data is not pickled and is - thus faster to search for in the database. - category - optional character string for grouping the Attribute - - """ - - # - # Attribute Database Model setup - # - # These database fields are all set using their corresponding properties, - # named same as the field, but withtout the db_* prefix. - db_key = models.CharField('key', max_length=255, db_index=True) - db_value = PickledObjectField( - 'value', null=True, - help_text="The data returned when the attribute is accessed. Must be " - "written as a Python literal if editing through the admin " - "interface. Attribute values which are not Python literals " - "cannot be edited through the admin interface.") - db_strvalue = models.TextField( - 'strvalue', null=True, blank=True, - help_text="String-specific storage for quick look-up") - db_category = models.CharField( - 'category', max_length=128, db_index=True, blank=True, null=True, - help_text="Optional categorization of attribute.") - # Lock storage - db_lock_storage = models.TextField( - 'locks', blank=True, - help_text="Lockstrings for this object are stored here.") - db_model = models.CharField( - 'model', max_length=32, db_index=True, blank=True, null=True, - help_text="Which model of object this attribute is attached to (A " - "natural key like objects.dbobject). You should not change " - "this value unless you know what you are doing.") - # subclass of Attribute (None or nick) - db_attrtype = models.CharField( - 'attrtype', max_length=16, db_index=True, blank=True, null=True, - help_text="Subclass of Attribute (None or nick)") - # time stamp - db_date_created = models.DateTimeField( - 'date_created', editable=False, auto_now_add=True) - - # Database manager - #objects = managers.AttributeManager() - - @lazy_property - def locks(self): - return LockHandler(self) - - class Meta: - "Define Django meta options" - verbose_name = "Evennia Attribute" - - # read-only wrappers - key = property(lambda self: self.db_key) - strvalue = property(lambda self: self.db_strvalue) - category = property(lambda self: self.db_category) - model = property(lambda self: self.db_model) - attrtype = property(lambda self: self.db_attrtype) - date_created = property(lambda self: self.db_date_created) - - def __lock_storage_get(self): - return self.db_lock_storage - def __lock_storage_set(self, value): - self.db_lock_storage = value - self.save(update_fields=["db_lock_storage"]) - def __lock_storage_del(self): - self.db_lock_storage = "" - self.save(update_fields=["db_lock_storage"]) - lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del) - - # Wrapper properties to easily set database fields. These are - # @property decorators that allows to access these fields using - # normal python operations (without having to remember to save() - # etc). So e.g. a property 'attr' has a get/set/del decorator - # defined that allows the user to do self.attr = value, - # value = self.attr and del self.attr respectively (where self - # is the object in question). - - # value property (wraps db_value) - #@property - def __value_get(self): - """ - Getter. Allows for value = self.value. - We cannot cache here since it makes certain cases (such - as storing a dbobj which is then deleted elsewhere) out-of-sync. - The overhead of unpickling seems hard to avoid. - """ - return from_pickle(self.db_value, db_obj=self) - - #@value.setter - def __value_set(self, new_value): - """ - Setter. Allows for self.value = value. We cannot cache here, - see self.__value_get. - """ - self.db_value = to_pickle(new_value) - self.save(update_fields=["db_value"]) - - #@value.deleter - def __value_del(self): - "Deleter. Allows for del attr.value. This removes the entire attribute." - self.delete() - value = property(__value_get, __value_set, __value_del) - - # - # - # Attribute methods - # - # - - def __str__(self): - return smart_str("%s(%s)" % (_GA(self, "db_key"), _GA(self, "id"))) - - def __unicode__(self): - return u"%s(%s)" % (_GA(self, "db_key"), _GA(self, "id")) - - def access(self, accessing_obj, access_type='read', default=False, **kwargs): - """ - Determines if another object has permission to access. - accessing_obj - object trying to access this one - access_type - type of access sought - default - what to return if no lock of access_type was found - **kwargs - passed to at_access hook along with result. - """ - result = self.locks.check(accessing_obj, access_type=access_type, default=default) - #self.at_access(result, **kwargs) - return result - - -# -# Handlers making use of the Attribute model -# - -class AttributeHandler(object): - """ - Handler for adding Attributes to the object. - """ - _m2m_fieldname = "db_attributes" - _attrcreate = "attrcreate" - _attredit = "attredit" - _attrread = "attrread" - _attrtype = None - - def __init__(self, obj): - "Initialize handler" - self.obj = obj - self._objid = obj.id - self._model = to_str(obj.__dbclass__.__name__.lower()) - self._cache = None - - def _recache(self): - "Cache all attributes of this object" - query = {"%s__id" % self._model : self._objid, - "attribute__db_attrtype" : self._attrtype} - attrs = [conn.attribute for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] - self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(), - attr.db_category.lower() if conn.attribute.db_category else None), - attr) for attr in attrs) - - def has(self, key, category=None): - """ - Checks if the given Attribute (or list of Attributes) exists on - the object. - - If an iterable is given, returns list of booleans. - """ - if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: - self._recache() - key = [k.strip().lower() for k in make_iter(key) if k] - category = category.strip().lower() if category is not None else None - searchkeys = ["%s-%s" % (k, category) for k in make_iter(key)] - ret = [self._cache.get(skey) for skey in searchkeys if skey in self._cache] - return ret[0] if len(ret) == 1 else ret - - def get(self, key=None, category=None, default=None, return_obj=False, - strattr=False, raise_exception=False, accessing_obj=None, - default_access=True, not_found_none=False): - """ - Returns the value of the given Attribute or list of Attributes. - strattr will cause the string-only value field instead of the normal - pickled field data. Use to get back values from Attributes added with - the strattr keyword. - If return_obj=True, return the matching Attribute object - instead. Returns default if no matches (or [ ] if key was a list - with no matches). If raise_exception=True, failure to find a - match will raise AttributeError instead. - - If accessing_obj is given, its "attrread" permission lock will be - checked before displaying each looked-after Attribute. If no - accessing_obj is given, no check will be done. - """ - - class RetDefault(object): - "Holds default values" - def __init__(self): - self.value = default - self.strvalue = str(default) if default is not None else None - - if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: - self._recache() - ret = [] - key = [k.strip().lower() for k in make_iter(key) if k] - category = category.strip().lower() if category is not None else None - #print "cache:", self._cache.keys(), key - if not key: - # return all with matching category (or no category) - catkey = "-%s" % category if category is not None else None - ret = [attr for key, attr in self._cache.items() if key and key.endswith(catkey)] - else: - for searchkey in ("%s-%s" % (k, category) for k in key): - attr_obj = self._cache.get(searchkey) - if attr_obj: - ret.append(attr_obj) - else: - if raise_exception: - raise AttributeError - else: - ret.append(RetDefault()) - if accessing_obj: - # check 'attrread' locks - ret = [attr for attr in ret if attr.access(accessing_obj, self._attrread, default=default_access)] - if strattr: - ret = ret if return_obj else [attr.strvalue for attr in ret if attr] - else: - ret = ret if return_obj else [attr.value for attr in ret if attr] - if not ret: - return ret if len(key) > 1 else default - return ret[0] if len(ret)==1 else ret - - - def add(self, key, value, category=None, lockstring="", - strattr=False, accessing_obj=None, default_access=True): - """ - Add attribute to object, with optional lockstring. - - If strattr is set, the db_strvalue field will be used (no pickling). - Use the get() method with the strattr keyword to get it back. - - If accessing_obj is given, self.obj's 'attrcreate' lock access - will be checked against it. If no accessing_obj is given, no check - will be done. - """ - if accessing_obj and not self.obj.access(accessing_obj, - self._attrcreate, default=default_access): - # check create access - return - if self._cache is None: - self._recache() - if not key: - return - - category = category.strip().lower() if category is not None else None - keystr = key.strip().lower() - cachekey = "%s-%s" % (keystr, category) - attr_obj = self._cache.get(cachekey) - - if attr_obj: - # update an existing attribute object - if strattr: - # store as a simple string (will not notify OOB handlers) - attr_obj.db_strvalue = value - attr_obj.save(update_fields=["db_strvalue"]) - else: - # store normally (this will also notify OOB handlers) - attr_obj.value = value - else: - # create a new Attribute (no OOB handlers can be notified) - kwargs = {"db_key" : keystr, "db_category" : category, - "db_model" : self._model, "db_attrtype" : self._attrtype, - "db_value" : None if strattr else to_pickle(value), - "db_strvalue" : value if strattr else None} - new_attr = Attribute(**kwargs) - new_attr.save() - getattr(self.obj, self._m2m_fieldname).add(new_attr) - self._cache[cachekey] = new_attr - - - def batch_add(self, key, value, category=None, lockstring="", - strattr=False, accessing_obj=None, default_access=True): - """ - Batch-version of add(). This is more efficient than - repeat-calling add. - - key and value must be sequences of the same length, each - representing a key-value pair. - - """ - if accessing_obj and not self.obj.access(accessing_obj, - self._attrcreate, default=default_access): - # check create access - return - if self._cache is None: - self._recache() - if not key: - return - - keys, values= make_iter(key), make_iter(value) - - if len(keys) != len(values): - raise RuntimeError("AttributeHandler.add(): key and value of different length: %s vs %s" % key, value) - category = category.strip().lower() if category is not None else None - new_attrobjs = [] - for ikey, keystr in enumerate(keys): - keystr = keystr.strip().lower() - new_value = values[ikey] - cachekey = "%s-%s" % (keystr, category) - attr_obj = self._cache.get(cachekey) - - if attr_obj: - # update an existing attribute object - if strattr: - # store as a simple string (will not notify OOB handlers) - attr_obj.db_strvalue = new_value - attr_obj.save(update_fields=["db_strvalue"]) - else: - # store normally (this will also notify OOB handlers) - attr_obj.value = new_value - else: - # create a new Attribute (no OOB handlers can be notified) - kwargs = {"db_key" : keystr, "db_category" : category, - "db_attrtype" : self._attrtype, - "db_value" : None if strattr else to_pickle(new_value), - "db_strvalue" : value if strattr else None} - new_attr = Attribute(**kwargs) - new_attr.save() - new_attrobjs.append(new_attr) - if new_attrobjs: - # Add new objects to m2m field all at once - getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs) - self._recache() - - - def remove(self, key, raise_exception=False, category=None, - accessing_obj=None, default_access=True): - """Remove attribute or a list of attributes from object. - - If accessing_obj is given, will check against the 'attredit' lock. - If not given, this check is skipped. - """ - if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: - self._recache() - key = [k.strip().lower() for k in make_iter(key) if k] - category = category.strip().lower() if category is not None else None - for searchstr in ("%s-%s" % (k, category) for k in key): - attr_obj = self._cache.get(searchstr) - if attr_obj: - if not (accessing_obj and not attr_obj.access(accessing_obj, - self._attredit, default=default_access)): - attr_obj.delete() - elif not attr_obj and raise_exception: - raise AttributeError - self._recache() - - def clear(self, category=None, accessing_obj=None, default_access=True): - """ - Remove all Attributes on this object. If accessing_obj is - given, check the 'attredit' lock on each Attribute before - continuing. If not given, skip check. - """ - if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: - self._recache() - if accessing_obj: - [attr.delete() for attr in self._cache.values() - if attr.access(accessing_obj, self._attredit, default=default_access)] - else: - [attr.delete() for attr in self._cache.values()] - self._recache() - - def all(self, accessing_obj=None, default_access=True): - """ - Return all Attribute objects on this object. - - If accessing_obj is given, check the "attrread" lock on - each attribute before returning them. If not given, this - check is skipped. - """ - if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: - self._recache() - attrs = sorted(self._cache.values(), key=lambda o: o.id) - if accessing_obj: - return [attr for attr in attrs - if attr.access(accessing_obj, self._attredit, default=default_access)] - else: - return attrs - - -class NickHandler(AttributeHandler): - """ - Handles the addition and removal of Nicks - (uses Attributes' strvalue and category fields) - - Nicks are stored as Attributes - with categories nick_ - """ - _attrtype = "nick" - - def has(self, key, category="inputline"): - return super(NickHandler, self).has(key, category=category) - - def get(self, key=None, category="inputline", **kwargs): - "Get the replacement value matching the given key and category" - return super(NickHandler, self).get(key=key, category=category, strattr=True, **kwargs) - - def add(self, key, replacement, category="inputline", **kwargs): - "Add a new nick" - super(NickHandler, self).add(key, replacement, category=category, strattr=True, **kwargs) - - def remove(self, key, category="inputline", **kwargs): - "Remove Nick with matching category" - super(NickHandler, self).remove(key, category=category, **kwargs) - - def nickreplace(self, raw_string, categories=("inputline", "channel"), include_player=True): - "Replace entries in raw_string with nick replacement" - raw_string - obj_nicks, player_nicks = [], [] - for category in make_iter(categories): - obj_nicks.extend([n for n in make_iter(self.get(category=category, return_obj=True)) if n]) - if include_player and self.obj.has_player: - for category in make_iter(categories): - player_nicks.extend([n for n in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) if n]) - for nick in obj_nicks + player_nicks: - # make a case-insensitive match here - match = re.match(re.escape(nick.db_key), raw_string, re.IGNORECASE) - if match: - raw_string = raw_string.replace(match.group(), nick.db_strvalue, 1) - break - return raw_string - - -class NAttributeHandler(object): - """ - This stand-alone handler manages non-database saving. - It is similar to AttributeHandler and is used - by the .ndb handler in the same way as .db does - for the AttributeHandler. - """ - def __init__(self, obj): - "initialized on the object" - self._store = {} - self.obj = weakref.proxy(obj) - - def has(self, key): - "Check if object has this attribute or not" - return key in self._store - - def get(self, key): - "Returns named key value" - return self._store.get(key, None) - - def add(self, key, value): - "Add new key and value" - self._store[key] = value - self.obj.set_recache_protection() - - def remove(self, key): - "Remove key from storage" - if key in self._store: - del self._store[key] - self.obj.set_recache_protection(self._store) - - def clear(self): - "Remove all nattributes from handler" - self._store = {} - - def all(self, return_tuples=False): - "List all keys or (keys, values) stored, except _keys" - if return_tuples: - return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] - return [key for key in self._store if not key.startswith("_")] - - -#------------------------------------------------------------ -# -# Tags -# -#------------------------------------------------------------ - -class Tag(models.Model): - """ - Tags are quick markers for objects in-game. An typeobject - can have any number of tags, stored via its db_tags property. - Tagging similar objects will make it easier to quickly locate the - group later (such as when implementing zones). The main advantage - of tagging as opposed to using Attributes is speed; a tag is very - limited in what data it can hold, and the tag key+category is - indexed for efficient lookup in the database. Tags are shared between - objects - a new tag is only created if the key+category combination - did not previously exist, making them unsuitable for storing - object-related data (for this a full Attribute - should be used). - The 'db_data' field is intended as a documentation - field for the tag itself, such as to document what this tag+category - stands for and display that in a web interface or similar. - - The main default use for Tags is to implement Aliases for objects. - this uses the 'aliases' tag category, which is also checked by the - default search functions of Evennia to allow quick searches by alias. - """ - db_key = models.CharField('key', max_length=255, null=True, - help_text="tag identifier", db_index=True) - db_category = models.CharField('category', max_length=64, null=True, - help_text="tag category", db_index=True) - db_data = models.TextField('data', null=True, blank=True, - help_text="optional data field with extra information. This is not searched for.") - # this is "objectdb" etc. Required behind the scenes - db_model = models.CharField('model', max_length=32, null=True, help_text="database model to Tag", db_index=True) - # this is None, alias or permission - db_tagtype = models.CharField('tagtype', max_length=16, null=True, help_text="overall type of Tag", db_index=True) - - class Meta: - "Define Django meta options" - verbose_name = "Tag" - unique_together = (('db_key', 'db_category', 'db_tagtype'),) - index_together = (('db_key', 'db_category', 'db_tagtype'),) - - def __unicode__(self): - return u"%s" % self.db_key - - def __str__(self): - return str(self.db_key) - - -# -# Handlers making use of the Tags model -# - -class TagHandler(object): - """ - Generic tag-handler. Accessed via TypedObject.tags. - """ - _m2m_fieldname = "db_tags" - _tagtype = None - - def __init__(self, obj): - """ - Tags are stored internally in the TypedObject.db_tags m2m field - with an tag.db_model based on the obj the taghandler is stored on - and with a tagtype given by self.handlertype - """ - self.obj = obj - self._objid = obj.id - self._model = obj.__dbclass__.__name__.lower() - self._cache = None - - def _recache(self): - "Cache all tags of this object" - query = {"%s__id" % self._model : self._objid, - "tag__db_tagtype" : self._tagtype} - tagobjs = [conn.tag for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] - self._cache = dict(("%s-%s" % (to_str(tagobj.db_key).lower(), - tagobj.db_category.lower() if tagobj.db_category else None), - tagobj) for tagobj in tagobjs) - - def add(self, tag=None, category=None, data=None): - "Add a new tag to the handler. Tag is a string or a list of strings." - if not tag: - return - for tagstr in make_iter(tag): - if not tagstr: - continue - tagstr = tagstr.strip().lower() - category = category.strip().lower() if category is not None else None - data = str(data) if data is not None else None - # this will only create tag if no matches existed beforehand (it - # will overload data on an existing tag since that is not - # considered part of making the tag unique) - tagobj = self.obj.__class__.objects.create_tag(key=tagstr, category=category, data=data, - tagtype=self._tagtype) - getattr(self.obj, self._m2m_fieldname).add(tagobj) - if self._cache is None: - self._recache() - cachestring = "%s-%s" % (tagstr, category) - self._cache[cachestring] = tagobj - - def get(self, key, category=None, return_tagobj=False): - """ - Get the tag for the given key or list of tags. If - return_data=True, return the matching Tag objects instead. - Returns a single tag if a unique match, otherwise a list - """ - if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: - self._recache() - ret = [] - category = category.strip().lower() if category is not None else None - searchkey = ["%s-%s" % (key.strip().lower(), category) if key is not None else None for key in make_iter(key)] - ret = [val for val in (self._cache.get(keystr) for keystr in searchkey) if val] - ret = [to_str(tag.db_data) for tag in ret] if return_tagobj else ret - return ret[0] if len(ret) == 1 else ret - - def remove(self, key, category=None): - "Remove a tag from the handler based ond key and category." - for key in make_iter(key): - if not (key or key.strip()): # we don't allow empty tags - continue - tagstr = key.strip().lower() - category = category.strip().lower() if category is not None else None - - # This does not delete the tag object itself. Maybe it should do - # that when no objects reference the tag anymore (how to check)? - tagobj = self.obj.db_tags.filter(db_key=tagstr, db_category=category) - if tagobj: - getattr(self.obj, self._m2m_fieldname).remove(tagobj[0]) - self._recache() - - def clear(self): - "Remove all tags from the handler" - getattr(self.obj, self._m2m_fieldname).clear() - self._recache() - - def all(self, category=None, return_key_and_category=False): - """ - Get all tags in this handler. - If category is given, return only Tags with this category. If - return_keys_and_categories is set, return a list of tuples [(key, category), ...] - """ - if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: - self._recache() - if category: - category = category.strip().lower() if category is not None else None - matches = [tag for tag in self._cache.values() if tag.db_category == category] - else: - matches = self._cache.values() - - if matches: - matches = sorted(matches, key=lambda o: o.id) - if return_key_and_category: - # return tuple (key, category) - return [(to_str(p.db_key), to_str(p.db_category)) for p in matches] - else: - return [to_str(p.db_key) for p in matches] - return [] - - def __str__(self): - return ",".join(self.all()) - - def __unicode(self): - return u",".join(self.all()) - - -class AliasHandler(TagHandler): - _tagtype = "alias" - - -class PermissionHandler(TagHandler): - _tagtype = "permission" - - #------------------------------------------------------------ # # Typed Objects @@ -751,18 +68,6 @@ class PermissionHandler(TagHandler): # Meta class for typeclasses # -# django patch imports -import copy -from django.apps import apps -from django.db.models.base import subclass_exception -import warnings -from django.db.models.options import Options -from django.utils.deprecation import RemovedInDjango19Warning -from django.core.exceptions import MultipleObjectsReturned, FieldError -from django.apps.config import MODELS_MODULE_NAME -from django.db.models.fields.related import OneToOneField -#/ django patch imports - # signal receivers. Assigned in __new__ def post_save(sender, instance, created, **kwargs): @@ -782,10 +87,11 @@ class TypeclassBase(SharedMemoryModelBase): def __new__(cls, name, bases, attrs): """ - We must define our Typeclasses as proxies. We also store the path - directly on the class, this is useful for managers. + We must define our Typeclasses as proxies. We also store the + path directly on the class, this is required by managers. """ + # storage of stats attrs["typename"] = cls.__name__ attrs["path"] = "%s.%s" % (attrs["__module__"], name) @@ -796,245 +102,15 @@ class TypeclassBase(SharedMemoryModelBase): attrs["Meta"] = Meta attrs["Meta"].proxy = True - # patch start - django multi-inheritance + # patch for django proxy multi-inheritance # this is a copy of django.db.models.base.__new__ - # with a few lines (marked patch below) changed - # as per https://code.djangoproject.com/ticket/11560 - - super_new = super(ModelBase, cls).__new__ - - # Also ensure initialization is only performed for subclasses of Model - # (excluding Model class itself). - parents = [b for b in bases if isinstance(b, ModelBase)] - if not parents: - return super_new(cls, name, bases, attrs) - - # Create the class. - module = attrs.pop('__module__') - new_class = super_new(cls, name, bases, {'__module__': module}) - attr_meta = attrs.pop('Meta', None) - abstract = getattr(attr_meta, 'abstract', False) - if not attr_meta: - meta = getattr(new_class, 'Meta', None) - else: - meta = attr_meta - base_meta = getattr(new_class, '_meta', None) - - # Look for an application configuration to attach the model to. - app_config = apps.get_containing_app_config(module) - - if getattr(meta, 'app_label', None) is None: - - if app_config is None: - # If the model is imported before the configuration for its - # application is created (#21719), or isn't in an installed - # application (#21680), use the legacy logic to figure out the - # app_label by looking one level up from the package or module - # named 'models'. If no such package or module exists, fall - # back to looking one level up from the module this model is - # defined in. - - # For 'django.contrib.sites.models', this would be 'sites'. - # For 'geo.models.places' this would be 'geo'. - - msg = ( - "Model class %s.%s doesn't declare an explicit app_label " - "and either isn't in an application in INSTALLED_APPS or " - "else was imported before its application was loaded. " % - (module, name)) - if abstract: - msg += "Its app_label will be set to None in Django 1.9." - else: - msg += "This will no longer be supported in Django 1.9." - warnings.warn(msg, RemovedInDjango19Warning, stacklevel=2) - - model_module = sys.modules[new_class.__module__] - package_components = model_module.__name__.split('.') - package_components.reverse() # find the last occurrence of 'models' - try: - app_label_index = package_components.index(MODELS_MODULE_NAME) + 1 - except ValueError: - app_label_index = 1 - kwargs = {"app_label": package_components[app_label_index]} - - else: - kwargs = {"app_label": app_config.label} - - else: - kwargs = {} - - new_class.add_to_class('_meta', Options(meta, **kwargs)) - if not abstract: - new_class.add_to_class( - 'DoesNotExist', - subclass_exception( - str('DoesNotExist'), - tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist,), - module, - attached_to=new_class)) - new_class.add_to_class( - 'MultipleObjectsReturned', - subclass_exception( - str('MultipleObjectsReturned'), - tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned,), - module, - attached_to=new_class)) - if base_meta and not base_meta.abstract: - # Non-abstract child classes inherit some attributes from their - # non-abstract parent (unless an ABC comes before it in the - # method resolution order). - if not hasattr(meta, 'ordering'): - new_class._meta.ordering = base_meta.ordering - if not hasattr(meta, 'get_latest_by'): - new_class._meta.get_latest_by = base_meta.get_latest_by - - is_proxy = new_class._meta.proxy - - # If the model is a proxy, ensure that the base class - # hasn't been swapped out. - if is_proxy and base_meta and base_meta.swapped: - raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped)) - - if getattr(new_class, '_default_manager', None): - if not is_proxy: - # Multi-table inheritance doesn't inherit default manager from - # parents. - new_class._default_manager = None - new_class._base_manager = None - else: - # Proxy classes do inherit parent's default manager, if none is - # set explicitly. - new_class._default_manager = new_class._default_manager._copy_to_model(new_class) - new_class._base_manager = new_class._base_manager._copy_to_model(new_class) - - # Add all attributes to the class. - for obj_name, obj in attrs.items(): - new_class.add_to_class(obj_name, obj) - - # All the fields of any type declared on this model - new_fields = ( - new_class._meta.local_fields + - new_class._meta.local_many_to_many + - new_class._meta.virtual_fields - ) - field_names = set(f.name for f in new_fields) - - # Basic setup for proxy models. - if is_proxy: - base = None - for parent in [kls for kls in parents if hasattr(kls, '_meta')]: - if parent._meta.abstract: - if parent._meta.fields: - raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) - else: - continue - #if base is not None: # patch - while parent._meta.proxy: # patch - parent = parent._meta.proxy_for_model # patch - if base is not None and base is not parent: # patch - raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name) - else: - base = parent - if base is None: - raise TypeError("Proxy model '%s' has no non-abstract model base class." % name) - new_class._meta.setup_proxy(base) - new_class._meta.concrete_model = base._meta.concrete_model - else: - new_class._meta.concrete_model = new_class - - # Collect the parent links for multi-table inheritance. - parent_links = {} - for base in reversed([new_class] + parents): - # Conceptually equivalent to `if base is Model`. - if not hasattr(base, '_meta'): - continue - # Skip concrete parent classes. - if base != new_class and not base._meta.abstract: - continue - # Locate OneToOneField instances. - for field in base._meta.local_fields: - if isinstance(field, OneToOneField): - parent_links[field.rel.to] = field - - # Do the appropriate setup for any model parents. - for base in parents: - original_base = base - if not hasattr(base, '_meta'): - # Things without _meta aren't functional models, so they're - # uninteresting parents. - continue - - parent_fields = base._meta.local_fields + base._meta.local_many_to_many - # Check for clashes between locally declared fields and those - # on the base classes (we cannot handle shadowed fields at the - # moment). - for field in parent_fields: - if field.name in field_names: - raise FieldError( - 'Local field %r in class %r clashes ' - 'with field of similar name from ' - 'base class %r' % (field.name, name, base.__name__) - ) - if not base._meta.abstract: - # Concrete classes... - base = base._meta.concrete_model - if base in parent_links: - field = parent_links[base] - elif not is_proxy: - attr_name = '%s_ptr' % base._meta.model_name - field = OneToOneField(base, name=attr_name, - auto_created=True, parent_link=True) - # Only add the ptr field if it's not already present; - # e.g. migrations will already have it specified - if not hasattr(new_class, attr_name): - new_class.add_to_class(attr_name, field) - else: - field = None - new_class._meta.parents[base] = field - else: - # .. and abstract ones. - for field in parent_fields: - new_class.add_to_class(field.name, copy.deepcopy(field)) - - # Pass any non-abstract parent classes onto child. - new_class._meta.parents.update(base._meta.parents) - - # Inherit managers from the abstract base classes. - new_class.copy_managers(base._meta.abstract_managers) - - # Proxy models inherit the non-abstract managers from their base, - # unless they have redefined any of them. - if is_proxy: - new_class.copy_managers(original_base._meta.concrete_managers) - - # Inherit virtual fields (like GenericForeignKey) from the parent - # class - for field in base._meta.virtual_fields: - if base._meta.abstract and field.name in field_names: - raise FieldError( - 'Local field %r in class %r clashes ' - 'with field of similar name from ' - 'abstract base class %r' % (field.name, name, base.__name__) - ) - new_class.add_to_class(field.name, copy.deepcopy(field)) - - if abstract: - # Abstract base models can't be instantiated and don't appear in - # the list of models for an app. We do the final setup for them a - # little differently from normal models. - attr_meta.abstract = False - new_class.Meta = attr_meta - return new_class - - new_class._prepare() - new_class._meta.apps.register_model(new_class._meta.app_label, new_class) - - #return new_class - - # /patch end + # with a few lines changed as per + # https://code.djangoproject.com/ticket/11560 + new_class = patched_new(cls, name, bases, attrs) # attach signal signals.post_save.connect(post_save, sender=new_class) + return new_class @@ -1180,10 +256,10 @@ class TypedObject(SharedMemoryModel): return other and hasattr(other, 'dbid') and self.dbid == other.dbid def __str__(self): - return smart_str("%s" % _GA(self, "db_key")) + return smart_str("%s" % self.db_key) def __unicode__(self): - return u"%s" % _GA(self, "db_key") + return u"%s" % self.db_key #@property def __dbid_get(self): @@ -1193,7 +269,7 @@ class TypedObject(SharedMemoryModel): """ dbid = get_prop_cache(self, "_dbid") if not dbid: - dbid = _GA(self, "id") + dbid = self.id set_prop_cache(self, "_dbid", dbid) return dbid @@ -1209,7 +285,7 @@ class TypedObject(SharedMemoryModel): """ Returns the object's dbref on the form #NN. """ - return "#%s" % _GA(self, "_TypedObject__dbid_get")() + return "#%s" % GA(self, "_TypedObject__dbid_get")() def __dbref_set(self): raise Exception("dbref cannot be set!") @@ -1273,14 +349,14 @@ class TypedObject(SharedMemoryModel): "Script '%s'.\nStop and start a new Script of the " \ "right type instead." % self.key) - _SA(self, "typeclass_path", new_typeclass.strip()) + self.typeclass_path = new_typeclass.strip() # this will automatically use a default class if # there is an error with the given typeclass. new_typeclass = self if self.typeclass_path != new_typeclass.path and no_default: # something went wrong; the default was loaded instead, # and we don't allow that; instead we return to previous. - _SA(self, "typeclass_path", old_typeclass_path) + self.typeclass_path = old_typeclass_path return False if clean_attributes: @@ -1370,26 +446,18 @@ class TypedObject(SharedMemoryModel): if not TICKER_HANDLER: from src.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.remove(self) # removes objects' all ticker subscriptions - _GA(self, "permissions").clear() - _GA(self, "attributes").clear() - _GA(self, "aliases").clear() + self.permissions.clear() + self.attributes.clear() + self.aliases.clear() if hasattr(self, "nicks"): - _GA(self, "nicks").clear() - _SA(self, "_cached_typeclass", None) - _GA(self, "flush_from_cache")() + self.nicks.clear() + self.flush_from_cache() # scrambling properties self.delete = self._deleted self._is_deleted = True super(TypedObject, self).delete() - #def save(self, *args, **kwargs): - # "Block saving non-proxy typeclassed objects" - # if not self._meta.proxy: - # raise RuntimeError("Don't create instances of %s, " - # "use its child typeclasses instead." % self.__class__.__name__) - # super(TypedObject, self).save(*args, **kwargs) - # # Memory management # @@ -1425,25 +493,25 @@ class TypedObject(SharedMemoryModel): class DbHolder(object): "Holder for allowing property access of attributes" def __init__(self, obj): - _SA(self, "attrhandler", _GA(obj, "attributes")) + self.attrhandler = obj.attributes def __getattribute__(self, attrname): if attrname == 'all': # we allow to overload our default .all - attr = _GA(self, "attrhandler").get("all") + attr = self.attrhandler.get("all") if attr: return attr - return _GA(self, 'all') - return _GA(self, "attrhandler").get(attrname) + return self.all + return self.attrhandler.get(attrname) def __setattr__(self, attrname, value): - _GA(self, "attrhandler").add(attrname, value) + self.attrhandler.add(attrname, value) def __delattr__(self, attrname): - _GA(self, "attrhandler").remove(attrname) + self.attrhandler.remove(attrname) def get_all(self): - return _GA(self, "attrhandler").all() + return self.attrhandler.all() all = property(get_all) self._db_holder = DbHolder(self) return self._db_holder @@ -1479,25 +547,25 @@ class TypedObject(SharedMemoryModel): class NDbHolder(object): "Holder for allowing property access of attributes" def __init__(self, obj): - _SA(self, "nattrhandler", _GA(obj, "nattributes")) + self.nattrhandler = obj.nattributes def __getattribute__(self, attrname): if attrname == 'all': # we allow to overload our default .all - attr = _GA(self, "nattrhandler").get("all") + attr = self.nattrhandler.get('all') if attr: return attr - return _GA(self, 'all') - return _GA(self, "nattrhandler").get(attrname) + return self.all + return self.nattrhandler.get(attrname) def __setattr__(self, attrname, value): - _GA(self, "nattrhandler").add(attrname, value) + self.nattrhandler.add(attrname, value) def __delattr__(self, attrname): - _GA(self, "nattrhandler").remove(attrname) + self.nattrhandler.remove(attrname) def get_all(self): - return _GA(self, "nattrhandler").all() + return self.nattrhandler.all() all = property(get_all) self._ndb_holder = NDbHolder(self) return self._ndb_holder diff --git a/src/typeclasses/tags.py b/src/typeclasses/tags.py new file mode 100644 index 000000000..396dc0267 --- /dev/null +++ b/src/typeclasses/tags.py @@ -0,0 +1,193 @@ +""" +Tags are entities that are attached to objects like Attributes but +which are unique to an individual object - any number of objects +can have the same Tag attached to them. + +Tags are used for tagging, obviously, but the data structure +is also used for storing Aliases and Permissions. This module +contains the respective handlers. + +""" + +from django.conf import settings +from django.db import models +from src.utils.utils import to_str, make_iter + + +_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE + +#------------------------------------------------------------ +# +# Tags +# +#------------------------------------------------------------ + +class Tag(models.Model): + """ + Tags are quick markers for objects in-game. An typeobject + can have any number of tags, stored via its db_tags property. + Tagging similar objects will make it easier to quickly locate the + group later (such as when implementing zones). The main advantage + of tagging as opposed to using Attributes is speed; a tag is very + limited in what data it can hold, and the tag key+category is + indexed for efficient lookup in the database. Tags are shared between + objects - a new tag is only created if the key+category combination + did not previously exist, making them unsuitable for storing + object-related data (for this a full Attribute + should be used). + The 'db_data' field is intended as a documentation + field for the tag itself, such as to document what this tag+category + stands for and display that in a web interface or similar. + + The main default use for Tags is to implement Aliases for objects. + this uses the 'aliases' tag category, which is also checked by the + default search functions of Evennia to allow quick searches by alias. + """ + db_key = models.CharField('key', max_length=255, null=True, + help_text="tag identifier", db_index=True) + db_category = models.CharField('category', max_length=64, null=True, + help_text="tag category", db_index=True) + db_data = models.TextField('data', null=True, blank=True, + help_text="optional data field with extra information. This is not searched for.") + # this is "objectdb" etc. Required behind the scenes + db_model = models.CharField('model', max_length=32, null=True, help_text="database model to Tag", db_index=True) + # this is None, alias or permission + db_tagtype = models.CharField('tagtype', max_length=16, null=True, help_text="overall type of Tag", db_index=True) + + class Meta: + "Define Django meta options" + verbose_name = "Tag" + unique_together = (('db_key', 'db_category', 'db_tagtype'),) + index_together = (('db_key', 'db_category', 'db_tagtype'),) + + def __unicode__(self): + return u"%s" % self.db_key + + def __str__(self): + return str(self.db_key) + + +# +# Handlers making use of the Tags model +# + +class TagHandler(object): + """ + Generic tag-handler. Accessed via TypedObject.tags. + """ + _m2m_fieldname = "db_tags" + _tagtype = None + + def __init__(self, obj): + """ + Tags are stored internally in the TypedObject.db_tags m2m field + with an tag.db_model based on the obj the taghandler is stored on + and with a tagtype given by self.handlertype + """ + self.obj = obj + self._objid = obj.id + self._model = obj.__dbclass__.__name__.lower() + self._cache = None + + def _recache(self): + "Cache all tags of this object" + query = {"%s__id" % self._model : self._objid, + "tag__db_tagtype" : self._tagtype} + tagobjs = [conn.tag for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] + self._cache = dict(("%s-%s" % (to_str(tagobj.db_key).lower(), + tagobj.db_category.lower() if tagobj.db_category else None), + tagobj) for tagobj in tagobjs) + + def add(self, tag=None, category=None, data=None): + "Add a new tag to the handler. Tag is a string or a list of strings." + if not tag: + return + for tagstr in make_iter(tag): + if not tagstr: + continue + tagstr = tagstr.strip().lower() + category = category.strip().lower() if category is not None else None + data = str(data) if data is not None else None + # this will only create tag if no matches existed beforehand (it + # will overload data on an existing tag since that is not + # considered part of making the tag unique) + tagobj = self.obj.__class__.objects.create_tag(key=tagstr, category=category, data=data, + tagtype=self._tagtype) + getattr(self.obj, self._m2m_fieldname).add(tagobj) + if self._cache is None: + self._recache() + cachestring = "%s-%s" % (tagstr, category) + self._cache[cachestring] = tagobj + + def get(self, key, category=None, return_tagobj=False): + """ + Get the tag for the given key or list of tags. If + return_data=True, return the matching Tag objects instead. + Returns a single tag if a unique match, otherwise a list + """ + if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: + self._recache() + ret = [] + category = category.strip().lower() if category is not None else None + searchkey = ["%s-%s" % (key.strip().lower(), category) if key is not None else None for key in make_iter(key)] + ret = [val for val in (self._cache.get(keystr) for keystr in searchkey) if val] + ret = [to_str(tag.db_data) for tag in ret] if return_tagobj else ret + return ret[0] if len(ret) == 1 else ret + + def remove(self, key, category=None): + "Remove a tag from the handler based ond key and category." + for key in make_iter(key): + if not (key or key.strip()): # we don't allow empty tags + continue + tagstr = key.strip().lower() + category = category.strip().lower() if category is not None else None + + # This does not delete the tag object itself. Maybe it should do + # that when no objects reference the tag anymore (how to check)? + tagobj = self.obj.db_tags.filter(db_key=tagstr, db_category=category) + if tagobj: + getattr(self.obj, self._m2m_fieldname).remove(tagobj[0]) + self._recache() + + def clear(self): + "Remove all tags from the handler" + getattr(self.obj, self._m2m_fieldname).clear() + self._recache() + + def all(self, category=None, return_key_and_category=False): + """ + Get all tags in this handler. + If category is given, return only Tags with this category. If + return_keys_and_categories is set, return a list of tuples [(key, category), ...] + """ + if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: + self._recache() + if category: + category = category.strip().lower() if category is not None else None + matches = [tag for tag in self._cache.values() if tag.db_category == category] + else: + matches = self._cache.values() + + if matches: + matches = sorted(matches, key=lambda o: o.id) + if return_key_and_category: + # return tuple (key, category) + return [(to_str(p.db_key), to_str(p.db_category)) for p in matches] + else: + return [to_str(p.db_key) for p in matches] + return [] + + def __str__(self): + return ",".join(self.all()) + + def __unicode(self): + return u",".join(self.all()) + + +class AliasHandler(TagHandler): + _tagtype = "alias" + + +class PermissionHandler(TagHandler): + _tagtype = "permission" + From db512cbbf541fa6ec09e09f0351c3e68a00e8198 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Dec 2014 01:24:26 +0100 Subject: [PATCH 028/250] Moved object methods up from ObjectDB and mainly onto the typeclass. In the process of converting players in the same way. --- src/objects/objects.py | 1 + src/players/models.py | 87 ++------------- src/players/player.py | 213 +++++++++++++++++++++--------------- src/server/initial_setup.py | 18 +-- src/typeclasses/models.py | 80 +++++--------- src/utils/dbserialize.py | 2 +- 6 files changed, 175 insertions(+), 226 deletions(-) diff --git a/src/objects/objects.py b/src/objects/objects.py index 76b3693b9..5d1818755 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -823,6 +823,7 @@ class DefaultObject(ObjectDB): return True # methods inherited from the typeclass system + def __eq__(self, other): """ Checks for equality against an id string or another object or user. diff --git a/src/players/models.py b/src/players/models.py index e16c58979..951c09712 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -24,12 +24,9 @@ from django.utils.encoding import smart_str from src.players.manager import PlayerDBManager from src.scripts.models import ScriptDB from src.typeclasses.models import TypedObject -from src.typeclasses.attributes import NickHandler -from src.scripts.scripthandler import ScriptHandler -from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler from src.utils import utils, logger -from src.utils.utils import to_str, make_iter, lazy_property +from src.utils.utils import to_str, make_iter from django.utils.translation import ugettext as _ @@ -111,20 +108,6 @@ class PlayerDB(TypedObject, AbstractUser): app_label = 'players' verbose_name = 'Player' - # lazy-loading of handlers - @lazy_property - def cmdset(self): - return CmdSetHandler(self, True) - - @lazy_property - def scripts(self): - return ScriptHandler(self) - - @lazy_property - def nicks(self): - return NickHandler(self) - - # alias to the objs property def __characters_get(self): return self.objs @@ -143,7 +126,7 @@ class PlayerDB(TypedObject, AbstractUser): """ Getter. Allows for value = self.name. Returns a list of cmdset_storage. """ - storage = _GA(self, "db_cmdset_storage") + storage = self.db_cmdset_storage # we need to check so storage is not None return [path.strip() for path in storage.split(',')] if storage else [] @@ -168,20 +151,22 @@ class PlayerDB(TypedObject, AbstractUser): # def __str__(self): - return smart_str("%s(player %s)" % (_GA(self, "name"), _GA(self, "dbid"))) + return smart_str("%s(player %s)" % (self.name, self.dbid)) def __unicode__(self): - return u"%s(player#%s)" % (_GA(self, "name"), _GA(self, "dbid")) + return u"%s(player#%s)" % (self.name, self.dbid) #@property def __username_get(self): - return _GA(self, "username") + return self.username def __username_set(self, value): - _SA(self, "username", value) + self.username = value + self.save(update_fields=["username"]) def __username_del(self): - _DA(self, "username") + del self.username + # aliases name = property(__username_get, __username_set, __username_del) key = property(__username_get, __username_set, __username_del) @@ -198,64 +183,10 @@ class PlayerDB(TypedObject, AbstractUser): raise Exception("User id cannot be deleted!") uid = property(__uid_get, __uid_set, __uid_del) - #@property - #def __is_superuser_get(self): - # "Superusers have all permissions." - # return self.db_is_superuser - # #is_suser = get_prop_cache(self, "_is_superuser") - # #if is_suser == None: - # # is_suser = _GA(self, "user").is_superuser - # # set_prop_cache(self, "_is_superuser", is_suser) - # #return is_suser - #is_superuser = property(__is_superuser_get) - # # PlayerDB class access methods # - def msg(self, text=None, from_obj=None, sessid=None, **kwargs): - """ - Evennia -> User - This is the main route for sending data back to the user from the - server. - - outgoing_string (string) - text data to send - from_obj (Object/Player) - source object of message to send. Its - at_msg_send() hook will be called. - sessid - the session id of the session to send to. If not given, return - to all sessions connected to this player. This is usually only - relevant when using msg() directly from a player-command (from - a command on a Character, the character automatically stores - and handles the sessid). Can also be a list of sessids. - kwargs (dict) - All other keywords are parsed as extra data. - """ - if "data" in kwargs: - # deprecation warning - logger.log_depmsg("PlayerDB:msg() 'data'-dict keyword is deprecated. Use **kwargs instead.") - data = kwargs.pop("data") - if isinstance(data, dict): - kwargs.update(data) - - text = to_str(text, force_string=True) if text else "" - if from_obj: - # call hook - try: - _GA(from_obj, "at_msg_send")(text=text, to_obj=self, **kwargs) - except Exception: - pass - sessions = _MULTISESSION_MODE > 1 and sessid and _GA(self, "get_session")(sessid) or None - if sessions: - for session in make_iter(sessions): - obj = session.puppet - if obj and not obj.at_msg_receive(text=text, **kwargs): - # if hook returns false, cancel send - continue - session.msg(text=text, **kwargs) - else: - # if no session was specified, send to them all - for sess in _GA(self, 'get_all_sessions')(): - sess.msg(text=text, **kwargs) - # session-related methods def get_session(self, sessid): diff --git a/src/players/player.py b/src/players/player.py index 37d6169d0..dfcb90671 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -18,7 +18,13 @@ from src.players.manager import PlayerManager from src.players.models import PlayerDB from src.comms.models import ChannelDB from src.utils import logger -__all__ = ("Player",) +from src.utils.utils import lazy_property, to_str, make_iter +from src.typeclasses.attributes import NickHandler +from src.scripts.scripthandler import ScriptHandler +from src.commands.cmdsethandler import CmdSetHandler + + +__all__ = ("DefaultPlayer",) _MULTISESSION_MODE = settings.MULTISESSION_MODE _CMDSET_PLAYER = settings.CMDSET_PLAYER @@ -26,105 +32,140 @@ _CONNECT_CHANNEL = None class DefaultPlayer(PlayerDB): """ - Base typeclass for all Players. - """ + This is the base Typeclass for all Players. Players represent + the person playing the game and tracks account info, password + etc. They are OOC entities without presence in-game. A Player + can connect to a Character Object in order to "enter" the + game. + + Player Typeclass API: + + * Available properties (only available on initiated typeclass objects) + + key (string) - name of player + name (string)- wrapper for user.username + aliases (list of strings) - aliases to the object. Will be saved to + database as AliasDB entries but returned as strings. + dbref (int, read-only) - unique #id-number. Also "id" can be used. + dbobj (Player, read-only) - link to database model. dbobj.typeclass + points back to this class + typeclass (Player, read-only) - this links back to this class as an + identified only. Use self.swap_typeclass() to switch. + date_created (string) - time stamp of object creation + permissions (list of strings) - list of permission strings + + user (User, read-only) - django User authorization object + obj (Object) - game object controlled by player. 'character' can also + be used. + sessions (list of Sessions) - sessions connected to this player + is_superuser (bool, read-only) - if the connected user is a superuser + + * Handlers + + locks - lock-handler: use locks.add() to add new lock strings + db - attribute-handler: store/retrieve database attributes on this + self.db.myattr=val, val=self.db.myattr + ndb - non-persistent attribute handler: same as db but does not + create a database entry when storing data + scripts - script-handler. Add new scripts to object with scripts.add() + cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object + nicks - nick-handler. New nicks with nicks.add(). + + * Helper methods + + msg(outgoing_string, from_obj=None, **kwargs) + swap_character(new_character, delete_old_character=False) + execute_cmd(raw_string) + search(ostring, global_search=False, attribute_name=None, + use_nicks=False, location=None, + ignore_errors=False, player=False) + is_typeclass(typeclass, exact=False) + swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) + access(accessing_obj, access_type='read', default=False) + check_permstring(permstring) + + * Hook methods + + basetype_setup() + at_player_creation() + + - note that the following hooks are also found on Objects and are + usually handled on the character level: + + at_init() + at_access() + at_cmdset_get(**kwargs) + at_first_login() + at_post_login(sessid=None) + at_disconnect() + at_message_receive() + at_message_send() + at_server_reload() + at_server_shutdown() + + """ + __metaclass__ = TypeclassBase objects = PlayerManager() - def __init__(self, *args, **kwargs): - """ - This is the base Typeclass for all Players. Players represent - the person playing the game and tracks account info, password - etc. They are OOC entities without presence in-game. A Player - can connect to a Character Object in order to "enter" the - game. + # properties + @lazy_property + def cmdset(self): + return CmdSetHandler(self, True) - Player Typeclass API: + @lazy_property + def scripts(self): + return ScriptHandler(self) - * Available properties (only available on initiated typeclass objects) + @lazy_property + def nicks(self): + return NickHandler(self) - key (string) - name of player - name (string)- wrapper for user.username - aliases (list of strings) - aliases to the object. Will be saved to - database as AliasDB entries but returned as strings. - dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Player, read-only) - link to database model. dbobj.typeclass - points back to this class - typeclass (Player, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. - date_created (string) - time stamp of object creation - permissions (list of strings) - list of permission strings - - user (User, read-only) - django User authorization object - obj (Object) - game object controlled by player. 'character' can also - be used. - sessions (list of Sessions) - sessions connected to this player - is_superuser (bool, read-only) - if the connected user is a superuser - - * Handlers - - locks - lock-handler: use locks.add() to add new lock strings - db - attribute-handler: store/retrieve database attributes on this - self.db.myattr=val, val=self.db.myattr - ndb - non-persistent attribute handler: same as db but does not - create a database entry when storing data - scripts - script-handler. Add new scripts to object with scripts.add() - cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object - nicks - nick-handler. New nicks with nicks.add(). - - * Helper methods - - msg(outgoing_string, from_obj=None, **kwargs) - swap_character(new_character, delete_old_character=False) - execute_cmd(raw_string) - search(ostring, global_search=False, attribute_name=None, - use_nicks=False, location=None, - ignore_errors=False, player=False) - is_typeclass(typeclass, exact=False) - swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) - access(accessing_obj, access_type='read', default=False) - check_permstring(permstring) - - * Hook methods - - basetype_setup() - at_player_creation() - - - note that the following hooks are also found on Objects and are - usually handled on the character level: - - at_init() - at_access() - at_cmdset_get(**kwargs) - at_first_login() - at_post_login(sessid=None) - at_disconnect() - at_message_receive() - at_message_send() - at_server_reload() - at_server_shutdown() - - """ - super(DefaultPlayer, self).__init__(*args, **kwargs) ## methods inherited from database model def msg(self, text=None, from_obj=None, sessid=None, **kwargs): """ Evennia -> User - This is the main route for sending data back to the user from - the server. + This is the main route for sending data back to the user from the + server. - text (string) - text data to send - from_obj (Object/DefaultPlayer) - source object of message to send - sessid - the session id of the session to send to. If not given, - return to all sessions connected to this player. This is usually only - relevant when using msg() directly from a player-command (from - a command on a Character, the character automatically stores and - handles the sessid). - kwargs - extra data to send through protocol + outgoing_string (string) - text data to send + from_obj (Object/Player) - source object of message to send. Its + at_msg_send() hook will be called. + sessid - the session id of the session to send to. If not given, return + to all sessions connected to this player. This is usually only + relevant when using msg() directly from a player-command (from + a command on a Character, the character automatically stores + and handles the sessid). Can also be a list of sessids. + kwargs (dict) - All other keywords are parsed as extra data. """ - super(DefaultPlayer, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) + if "data" in kwargs: + # deprecation warning + logger.log_depmsg("PlayerDB:msg() 'data'-dict keyword is deprecated. Use **kwargs instead.") + data = kwargs.pop("data") + if isinstance(data, dict): + kwargs.update(data) + + text = to_str(text, force_string=True) if text else "" + if from_obj: + # call hook + try: + from_obj.at_msg_send(text=text, to_obj=self, **kwargs) + except Exception: + pass + sessions = _MULTISESSION_MODE > 1 and sessid and self.get_session(sessid) or None + if sessions: + for session in make_iter(sessions): + obj = session.puppet + if obj and not obj.at_msg_receive(text=text, **kwargs): + # if hook returns false, cancel send + continue + session.msg(text=text, **kwargs) + else: + # if no session was specified, send to them all + for sess in self.get_all_sessions(): + sess.msg(text=text, **kwargs) def swap_character(self, new_character, delete_old_character=False): """ diff --git a/src/server/initial_setup.py b/src/server/initial_setup.py index 43aed5248..8ac302bb8 100644 --- a/src/server/initial_setup.py +++ b/src/server/initial_setup.py @@ -10,6 +10,7 @@ import django from django.conf import settings from django.contrib.auth import get_user_model from django.utils.translation import ugettext as _ +from src.players.models import PlayerDB from src.server.models import ServerConfig from src.utils import create from src.utils.utils import class_from_module @@ -26,15 +27,14 @@ def get_god_player(): """ Creates the god user. """ - Player = class_from_module(settings.BASE_PLAYER_TYPECLASS) try: - god_player = Player.objects.get(id=1) - except Player.DoesNotExist: - txt = "\n\nNo superuser exists yet. The superuser is the 'owner'" - txt += "\account on the Evennia server. Create a new superuser using" - txt += "\nthe command" - txt += "\n\n python manage.py createsuperuser" - txt += "\n\nFollow the prompts, then restart the server." + god_player = PlayerDB.objects.get(id=1) + except PlayerDB.DoesNotExist: + txt = "\n\nNo superuser exists yet. The superuser is the 'owner'\n" \ + "account on the Evennia server. Create a new superuser using\n" \ + "the command\n\n" \ + " python manage.py createsuperuser\n\n" \ + "Follow the prompts, then restart the server." raise Exception(txt) return god_player @@ -56,7 +56,7 @@ def create_objects(): # run all creation hooks on god_player (we must do so manually # since the manage.py command does not) - god_player.typeclass_path = player_typeclass + god_player.swap_typeclass(player_typeclass, clean_attributes=True) god_player.basetype_setup() god_player.at_player_creation() god_player.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all()") diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 3cb3445ed..588cc962a 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -56,6 +56,8 @@ TICKER_HANDLER = None _PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE +_GA = object.__getattribute__ +_SA = object.__setattr__ #------------------------------------------------------------ # @@ -150,16 +152,12 @@ class TypedObject(SharedMemoryModel): # Main identifier of the object, for searching. Is accessed with self.key # or self.name db_key = models.CharField('key', max_length=255, db_index=True) - # This is the python path to the type class this object is tied to the + # This is the python path to the type class this object is tied to. The # typeclass is what defines what kind of Object this is) db_typeclass_path = models.CharField('typeclass', max_length=255, null=True, help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.") # Creation date. This is not changed once the object is created. db_date_created = models.DateTimeField('creation date', editable=False, auto_now_add=True) - # Permissions (access these through the 'permissions' property) - #db_permissions = models.CharField('permissions', max_length=255, blank=True, - # help_text="a comma-separated list of text strings checked by - # in-game locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. Character objects use 'Players' by default. Most other objects don't have any permissions.") # Lock storage db_lock_storage = models.TextField('locks', blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.") @@ -178,6 +176,11 @@ class TypedObject(SharedMemoryModel): # typeclass mechanism def __init__(self, *args, **kwargs): + """ + This is the main function of the typeclass system - + to dynamically re-apply a class based on the + db_typeclass_path rather than use the one in the model. + """ typeclass_path = kwargs.pop("typeclass", None) super(TypedObject, self).__init__(*args, **kwargs) if typeclass_path: @@ -250,8 +253,6 @@ class TypedObject(SharedMemoryModel): # # - _typeclass_paths = settings.OBJECT_TYPECLASS_PATHS - def __eq__(self, other): return other and hasattr(other, 'dbid') and self.dbid == other.dbid @@ -332,16 +333,13 @@ class TypedObject(SharedMemoryModel): boolean True/False depending on if the swap worked or not. """ - if callable(new_typeclass): - # this is an actual class object - build the path - cls = new_typeclass - new_typeclass = "%s.%s" % (cls.__module__, cls.__name__) - else: - new_typeclass = "%s" % to_str(new_typeclass) - # Try to set the new path - # this will automatically save to database - old_typeclass_path = self.typeclass_path + if not callable(new_typeclass): + # this is an actual class object - build the path + new_typeclass = class_from_module(new_typeclass) + + # if we get to this point, the class is ok. + if inherits_from(self, "src.scripts.models.ScriptDB"): if self.interval > 0: @@ -349,15 +347,7 @@ class TypedObject(SharedMemoryModel): "Script '%s'.\nStop and start a new Script of the " \ "right type instead." % self.key) - self.typeclass_path = new_typeclass.strip() - # this will automatically use a default class if - # there is an error with the given typeclass. - new_typeclass = self - if self.typeclass_path != new_typeclass.path and no_default: - # something went wrong; the default was loaded instead, - # and we don't allow that; instead we return to previous. - self.typeclass_path = old_typeclass_path - return False + self.typeclass_path = new_typeclass.path if clean_attributes: # Clean out old attributes @@ -373,21 +363,7 @@ class TypedObject(SharedMemoryModel): self.nattributes.clear() if run_start_hooks: - # run hooks for this new typeclass - if inherits_from(self, "src.objects.models.ObjectDB"): - new_typeclass.basetype_setup() - new_typeclass.at_object_creation() - elif inherits_from(self, "src.players.models.PlayerDB"): - new_typeclass.basetype_setup() - new_typeclass.at_player_creation() - elif inherits_from(self, "src.scripts.models.ScriptDB"): - new_typeclass.at_script_creation() - new_typeclass.start() - elif inherits_from(self, "src.channels.models.Channel"): - # channels do no initial setup - pass - - return True + self.at_instance_creation() # # Lock / permission methods @@ -493,25 +469,25 @@ class TypedObject(SharedMemoryModel): class DbHolder(object): "Holder for allowing property access of attributes" def __init__(self, obj): - self.attrhandler = obj.attributes + _SA(self, "attrhandler", obj.attributes) def __getattribute__(self, attrname): if attrname == 'all': # we allow to overload our default .all - attr = self.attrhandler.get("all") + attr = _GA(self, "attrhandler").get("all") if attr: return attr return self.all - return self.attrhandler.get(attrname) + return _GA(self, "attrhandler").get(attrname) def __setattr__(self, attrname, value): - self.attrhandler.add(attrname, value) + _GA(self, "attrhandler").add(attrname, value) def __delattr__(self, attrname): - self.attrhandler.remove(attrname) + _GA(self, "attrhandler").remove(attrname) def get_all(self): - return self.attrhandler.all() + return _GA(self, "attrhandler").all() all = property(get_all) self._db_holder = DbHolder(self) return self._db_holder @@ -547,25 +523,25 @@ class TypedObject(SharedMemoryModel): class NDbHolder(object): "Holder for allowing property access of attributes" def __init__(self, obj): - self.nattrhandler = obj.nattributes + _SA(self, "nattrhandler", obj.nattributes) def __getattribute__(self, attrname): if attrname == 'all': # we allow to overload our default .all - attr = self.nattrhandler.get('all') + attr = _GA(self, "nattrhandler").get('all') if attr: return attr return self.all - return self.nattrhandler.get(attrname) + return _GA(self, "nattrhandler").get(attrname) def __setattr__(self, attrname, value): - self.nattrhandler.add(attrname, value) + _GA(self, "nattrhandler").add(attrname, value) def __delattr__(self, attrname): - self.nattrhandler.remove(attrname) + _GA(self, "nattrhandler").remove(attrname) def get_all(self): - return self.nattrhandler.all() + return _GA(self, "nattrhandler").all() all = property(get_all) self._ndb_holder = NDbHolder(self) return self._ndb_holder diff --git a/src/utils/dbserialize.py b/src/utils/dbserialize.py index 44eea29a1..bc4d29295 100644 --- a/src/utils/dbserialize.py +++ b/src/utils/dbserialize.py @@ -216,7 +216,7 @@ def pack_dbobj(item): _init_globals() obj = hasattr(item, 'dbobj') and item.dbobj or item natural_key = _FROM_MODEL_MAP[hasattr(obj, "id") and hasattr(obj, "db_date_created") and - hasattr(obj, '__class__') and obj.__class__.__name__.lower()] + hasattr(obj, '__dbclass__') and obj.__dbclass__.__name__.lower()] # build the internal representation as a tuple # ("__packed_dbobj__", key, creation_time, id) return natural_key and ('__packed_dbobj__', natural_key, From 9af9f94fa0aae61b99b06ecc0c536569786e4135 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 25 Dec 2014 14:43:43 +0100 Subject: [PATCH 029/250] Moved Players over to the new proxy system, made the start-hook called by the save-signal system into at_first_save() --- src/objects/objects.py | 4 +- src/players/manager.py | 56 ++++---- src/players/models.py | 238 +--------------------------------- src/players/player.py | 266 ++++++++++++++++++++++++++++++++------ src/scripts/models.py | 40 +++--- src/scripts/scripts.py | 219 ++++++++++++++++++------------- src/typeclasses/models.py | 7 +- src/utils/create.py | 214 +++++++----------------------- src/utils/spawner.py | 5 +- 9 files changed, 465 insertions(+), 584 deletions(-) diff --git a/src/objects/objects.py b/src/objects/objects.py index 5d1818755..416d3d69d 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -840,7 +840,7 @@ class DefaultObject(ObjectDB): except AttributeError: return False - def at_instance_creation(self): + def at_first_save(self): """ This is called by the typeclass system whenever an instance of this class is saved for the first time. It is a generic hook @@ -874,6 +874,7 @@ class DefaultObject(ObjectDB): updates.append("db_destination") if updates: self.save(update_fields=updates) + if cdict["permissions"]: self.permissions.add(cdict["permissions"]) if cdict["locks"]: @@ -883,6 +884,7 @@ class DefaultObject(ObjectDB): if cdict["location"]: cdict["location"].at_object_receive(self, None) self.at_after_move(None) + del self._createdict self.basetype_posthook_setup() diff --git a/src/players/manager.py b/src/players/manager.py index 67c72bd14..4525c8bd1 100644 --- a/src/players/manager.py +++ b/src/players/manager.py @@ -37,7 +37,7 @@ class PlayerDBManager(TypedObjectManager, UserManager): get_player_from_uid get_player_from_name player_search (equivalent to ev.search_player) - swap_character + #swap_character """ def num_total_players(self): @@ -123,33 +123,33 @@ class PlayerDBManager(TypedObjectManager, UserManager): else: return self.filter(username__icontains=ostring) - def swap_character(self, player, new_character, delete_old_character=False): - """ - This disconnects a player from the current character (if any) and - connects to a new character object. - - """ - - if new_character.player: - # the new character is already linked to a player! - return False - - # do the swap - old_character = player.character - if old_character: - old_character.player = None - try: - player.character = new_character - new_character.player = player - except Exception: - # recover old setup - if old_character: - old_character.player = player - player.character = old_character - return False - if old_character and delete_old_character: - old_character.delete() - return True +# def swap_character(self, player, new_character, delete_old_character=False): +# """ +# This disconnects a player from the current character (if any) and +# connects to a new character object. +# +# """ +# +# if new_character.player: +# # the new character is already linked to a player! +# return False +# +# # do the swap +# old_character = player.character +# if old_character: +# old_character.player = None +# try: +# player.character = new_character +# new_character.player = player +# except Exception: +# # recover old setup +# if old_character: +# old_character.player = player +# player.character = old_character +# return False +# if old_character and delete_old_character: +# old_character.delete() +# return True class PlayerManager(PlayerDBManager, TypeclassManager): pass diff --git a/src/players/models.py b/src/players/models.py index 951c09712..d1ae75266 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -22,21 +22,14 @@ from django.contrib.auth.models import AbstractUser from django.utils.encoding import smart_str from src.players.manager import PlayerDBManager -from src.scripts.models import ScriptDB from src.typeclasses.models import TypedObject -from src.commands import cmdhandler -from src.utils import utils, logger -from src.utils.utils import to_str, make_iter - -from django.utils.translation import ugettext as _ +from src.utils.utils import make_iter __all__ = ("PlayerDB",) #_ME = _("me") #_SELF = _("self") -_SESSIONS = None -_AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _MULTISESSION_MODE = settings.MULTISESSION_MODE _GA = object.__getattribute__ @@ -147,7 +140,7 @@ class PlayerDB(TypedObject, AbstractUser): cmdset_storage = property(cmdset_storage_get, cmdset_storage_set, cmdset_storage_del) # - # PlayerDB main class properties and methods + # property/field access # def __str__(self): @@ -182,230 +175,3 @@ class PlayerDB(TypedObject, AbstractUser): def __uid_del(self): raise Exception("User id cannot be deleted!") uid = property(__uid_get, __uid_set, __uid_del) - - # - # PlayerDB class access methods - # - - # session-related methods - - def get_session(self, sessid): - """ - Return session with given sessid connected to this player. - note that the sessionhandler also accepts sessid as an iterable. - """ - global _SESSIONS - if not _SESSIONS: - from src.server.sessionhandler import SESSIONS as _SESSIONS - return _SESSIONS.session_from_player(self, sessid) - - def get_all_sessions(self): - "Return all sessions connected to this player" - global _SESSIONS - if not _SESSIONS: - from src.server.sessionhandler import SESSIONS as _SESSIONS - return _SESSIONS.sessions_from_player(self) - sessions = property(get_all_sessions) # alias shortcut - - def disconnect_session_from_player(self, sessid): - """ - Access method for disconnecting a given session from the player - (connection happens automatically in the sessionhandler) - """ - # this should only be one value, loop just to make sure to - # clean everything - sessions = (session for session in self.get_all_sessions() - if session.sessid == sessid) - for session in sessions: - # this will also trigger unpuppeting - session.sessionhandler.disconnect(session) - - # puppeting operations - - def puppet_object(self, sessid, obj, normal_mode=True): - """ - Use the given session to control (puppet) the given object (usually - a Character type). Note that we make no puppet checks here, that must - have been done before calling this method. - - sessid - session id of session to connect - obj - the object to connect to - normal_mode - trigger hooks and extra checks - this is turned off when - the server reloads, to quickly re-connect puppets. - - returns True if successful, False otherwise - """ - session = self.get_session(sessid) - if not session: - return False - if normal_mode and session.puppet: - # cleanly unpuppet eventual previous object puppeted by this session - self.unpuppet_object(sessid) - if obj.player and obj.player.is_connected and obj.player != self: - # we don't allow to puppet an object already controlled by an active - # player. To kick a player, call unpuppet_object on them explicitly. - return - # if we get to this point the character is ready to puppet or it - # was left with a lingering player/sessid reference from an unclean - # server kill or similar - - if normal_mode: - obj.at_pre_puppet(self, sessid=sessid) - # do the connection - obj.sessid.add(sessid) - obj.player = self - session.puid = obj.id - session.puppet = obj - # validate/start persistent scripts on object - ScriptDB.objects.validate(obj=obj) - if normal_mode: - obj.at_post_puppet() - return True - - def unpuppet_object(self, sessid): - """ - Disengage control over an object - - sessid - the session id to disengage - - returns True if successful - """ - session = self.get_session(sessid) - if not session: - return False - obj = hasattr(session, "puppet") and session.puppet or None - if not obj: - return False - # do the disconnect, but only if we are the last session to puppet - obj.at_pre_unpuppet() - obj.dbobj.sessid.remove(sessid) - if not obj.dbobj.sessid.count(): - del obj.dbobj.player - obj.at_post_unpuppet(self, sessid=sessid) - session.puppet = None - session.puid = None - return True - - def unpuppet_all(self): - """ - Disconnect all puppets. This is called by server - before a reset/shutdown. - """ - for session in self.get_all_sessions(): - self.unpuppet_object(session.sessid) - - def get_puppet(self, sessid, return_dbobj=False): - """ - Get an object puppeted by this session through this player. This is - the main method for retrieving the puppeted object from the - player's end. - - sessid - return character connected to this sessid, - character - return character if connected to this player, else None. - - """ - session = self.get_session(sessid) - if not session: - return None - if return_dbobj: - return session.puppet - return session.puppet and session.puppet or None - - def get_all_puppets(self, return_dbobj=False): - """ - Get all currently puppeted objects as a list - """ - puppets = [session.puppet for session in self.get_all_sessions() - if session.puppet] - if return_dbobj: - return puppets - return [puppet for puppet in puppets] - - def __get_single_puppet(self): - """ - This is a legacy convenience link for users of - MULTISESSION_MODE 0 or 1. It will return - only the first puppet. For mode 2, this returns - a list of all characters. - """ - puppets = self.get_all_puppets() - if _MULTISESSION_MODE in (0, 1): - return puppets and puppets[0] or None - return puppets - character = property(__get_single_puppet) - puppet = property(__get_single_puppet) - - # utility methods - - def delete(self, *args, **kwargs): - """ - Deletes the player permanently. - """ - for session in self.get_all_sessions(): - # unpuppeting all objects and disconnecting the user, if any - # sessions remain (should usually be handled from the - # deleting command) - self.unpuppet_object(session.sessid) - session.sessionhandler.disconnect(session, reason=_("Player being deleted.")) - self.scripts.stop() - _GA(self, "attributes").clear() - _GA(self, "nicks").clear() - _GA(self, "aliases").clear() - super(PlayerDB, self).delete(*args, **kwargs) - - def execute_cmd(self, raw_string, sessid=None, **kwargs): - """ - Do something as this player. This method is never called normally, - but only when the player object itself is supposed to execute the - command. It takes player nicks into account, but not nicks of - eventual puppets. - - raw_string - raw command input coming from the command line. - sessid - the optional session id to be responsible for the command-send - **kwargs - other keyword arguments will be added to the found command - object instace as variables before it executes. This is - unused by default Evennia but may be used to set flags and - change operating paramaters for commands at run-time. - """ - raw_string = utils.to_unicode(raw_string) - raw_string = self.nicks.nickreplace(raw_string, - categories=("inputline", "channel"), include_player=False) - if not sessid and _MULTISESSION_MODE in (0, 1): - # in this case, we should either have only one sessid, or the sessid - # should not matter (since the return goes to all of them we can - # just use the first one as the source) - try: - sessid = self.get_all_sessions()[0].sessid - except IndexError: - # this can happen for bots - sessid = None - return cmdhandler.cmdhandler(self, raw_string, - callertype="player", sessid=sessid, **kwargs) - - def search(self, searchdata, return_puppet=False, **kwargs): - """ - This is similar to the ObjectDB search method but will search for - Players only. Errors will be echoed, and None returned if no Player - is found. - searchdata - search criterion, the Player's key or dbref to search for - return_puppet - will try to return the object the player controls - instead of the Player object itself. If no - puppeted object exists (since Player is OOC), None will - be returned. - Extra keywords are ignored, but are allowed in call in order to make - API more consistent with objects.models.TypedObject.search. - """ - #TODO deprecation - if "return_character" in kwargs: - logger.log_depmsg("Player.search's 'return_character' keyword is deprecated. Use the return_puppet keyword instead.") - return_puppet = kwargs.get("return_character") - - matches = _GA(self, "__class__").objects.player_search(searchdata) - matches = _AT_SEARCH_RESULT(self, searchdata, matches, global_search=True) - if matches and return_puppet: - try: - return _GA(matches, "puppet") - except AttributeError: - return None - return matches - diff --git a/src/players/player.py b/src/players/player.py index dfcb90671..4a160a4d0 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -17,15 +17,23 @@ from src.typeclasses.models import TypeclassBase from src.players.manager import PlayerManager from src.players.models import PlayerDB from src.comms.models import ChannelDB +from src.commands import cmdhandler +from src.scripts.models import ScriptDB from src.utils import logger -from src.utils.utils import lazy_property, to_str, make_iter +from src.utils.utils import (lazy_property, to_str, + make_iter, to_unicode, + variable_from_module) from src.typeclasses.attributes import NickHandler from src.scripts.scripthandler import ScriptHandler from src.commands.cmdsethandler import CmdSetHandler +from django.utils.translation import ugettext as _ __all__ = ("DefaultPlayer",) +_SESSIONS = None + +_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _MULTISESSION_MODE = settings.MULTISESSION_MODE _CMDSET_PLAYER = settings.CMDSET_PLAYER _CONNECT_CHANNEL = None @@ -74,7 +82,7 @@ class DefaultPlayer(PlayerDB): * Helper methods msg(outgoing_string, from_obj=None, **kwargs) - swap_character(new_character, delete_old_character=False) + #swap_character(new_character, delete_old_character=False) execute_cmd(raw_string) search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, @@ -122,6 +130,171 @@ class DefaultPlayer(PlayerDB): return NickHandler(self) + # session-related methods + + def get_session(self, sessid): + """ + Return session with given sessid connected to this player. + note that the sessionhandler also accepts sessid as an iterable. + """ + global _SESSIONS + if not _SESSIONS: + from src.server.sessionhandler import SESSIONS as _SESSIONS + return _SESSIONS.session_from_player(self, sessid) + + def get_all_sessions(self): + "Return all sessions connected to this player" + global _SESSIONS + if not _SESSIONS: + from src.server.sessionhandler import SESSIONS as _SESSIONS + return _SESSIONS.sessions_from_player(self) + sessions = property(get_all_sessions) # alias shortcut + + def disconnect_session_from_player(self, sessid): + """ + Access method for disconnecting a given session from the player + (connection happens automatically in the sessionhandler) + """ + # this should only be one value, loop just to make sure to + # clean everything + sessions = (session for session in self.get_all_sessions() + if session.sessid == sessid) + for session in sessions: + # this will also trigger unpuppeting + session.sessionhandler.disconnect(session) + + # puppeting operations + + def puppet_object(self, sessid, obj, normal_mode=True): + """ + Use the given session to control (puppet) the given object (usually + a Character type). Note that we make no puppet checks here, that must + have been done before calling this method. + + sessid - session id of session to connect + obj - the object to connect to + normal_mode - trigger hooks and extra checks - this is turned off when + the server reloads, to quickly re-connect puppets. + + returns True if successful, False otherwise + """ + session = self.get_session(sessid) + if not session: + return False + if normal_mode and session.puppet: + # cleanly unpuppet eventual previous object puppeted by this session + self.unpuppet_object(sessid) + if obj.player and obj.player.is_connected and obj.player != self: + # we don't allow to puppet an object already controlled by an active + # player. To kick a player, call unpuppet_object on them explicitly. + return + # if we get to this point the character is ready to puppet or it + # was left with a lingering player/sessid reference from an unclean + # server kill or similar + + if normal_mode: + obj.at_pre_puppet(self, sessid=sessid) + # do the connection + obj.sessid.add(sessid) + obj.player = self + session.puid = obj.id + session.puppet = obj + # validate/start persistent scripts on object + ScriptDB.objects.validate(obj=obj) + if normal_mode: + obj.at_post_puppet() + return True + + def unpuppet_object(self, sessid): + """ + Disengage control over an object + + sessid - the session id to disengage + + returns True if successful + """ + session = self.get_session(sessid) + if not session: + return False + obj = hasattr(session, "puppet") and session.puppet or None + if not obj: + return False + # do the disconnect, but only if we are the last session to puppet + obj.at_pre_unpuppet() + obj.dbobj.sessid.remove(sessid) + if not obj.dbobj.sessid.count(): + del obj.dbobj.player + obj.at_post_unpuppet(self, sessid=sessid) + session.puppet = None + session.puid = None + return True + + def unpuppet_all(self): + """ + Disconnect all puppets. This is called by server + before a reset/shutdown. + """ + for session in self.get_all_sessions(): + self.unpuppet_object(session.sessid) + + def get_puppet(self, sessid, return_dbobj=False): + """ + Get an object puppeted by this session through this player. This is + the main method for retrieving the puppeted object from the + player's end. + + sessid - return character connected to this sessid, + character - return character if connected to this player, else None. + + """ + session = self.get_session(sessid) + if not session: + return None + if return_dbobj: + return session.puppet + return session.puppet and session.puppet or None + + def get_all_puppets(self, return_dbobj=False): + """ + Get all currently puppeted objects as a list + """ + puppets = [session.puppet for session in self.get_all_sessions() + if session.puppet] + if return_dbobj: + return puppets + return [puppet for puppet in puppets] + + def __get_single_puppet(self): + """ + This is a legacy convenience link for users of + MULTISESSION_MODE 0 or 1. It will return + only the first puppet. For mode 2, this returns + a list of all characters. + """ + puppets = self.get_all_puppets() + if _MULTISESSION_MODE in (0, 1): + return puppets and puppets[0] or None + return puppets + character = property(__get_single_puppet) + puppet = property(__get_single_puppet) + + # utility methods + + def delete(self, *args, **kwargs): + """ + Deletes the player permanently. + """ + for session in self.get_all_sessions(): + # unpuppeting all objects and disconnecting the user, if any + # sessions remain (should usually be handled from the + # deleting command) + self.unpuppet_object(session.sessid) + session.sessionhandler.disconnect(session, reason=_("Player being deleted.")) + self.scripts.stop() + self.attributes.clear() + self.nicks.clear() + self.aliases.clear() + super(PlayerDB, self).delete(*args, **kwargs) ## methods inherited from database model def msg(self, text=None, from_obj=None, sessid=None, **kwargs): @@ -167,48 +340,38 @@ class DefaultPlayer(PlayerDB): for sess in self.get_all_sessions(): sess.msg(text=text, **kwargs) - def swap_character(self, new_character, delete_old_character=False): - """ - Swaps the character controlled by this Player, if possible. - - new_character (Object) - character/object to swap to - delete_old_character (bool) - delete the old character when swapping - - Returns: True/False depending on if swap suceeded or not. - """ - return super(DefaultPlayer, self).swap_character(new_character, delete_old_character=delete_old_character) - def execute_cmd(self, raw_string, sessid=None, **kwargs): """ - Do something as this object. This command transparently - lets its typeclass execute the command. This method - is -not- called by Evennia normally, it is here to be - called explicitly in code. + Do something as this player. This method is never called normally, + but only when the player object itself is supposed to execute the + command. It takes player nicks into account, but not nicks of + eventual puppets. - Argument: - raw_string (string) - raw command input - sessid (int) - id of session executing the command. This sets the - sessid property on the command + raw_string - raw command input coming from the command line. + sessid - the optional session id to be responsible for the command-send **kwargs - other keyword arguments will be added to the found command object instace as variables before it executes. This is unused by default Evennia but may be used to set flags and change operating paramaters for commands at run-time. - - Returns Deferred - this is an asynchronous Twisted object that will - not fire until the command has actually finished executing. To - overload this one needs to attach callback functions to it, with - addCallback(function). This function will be called with an - eventual return value from the command execution. - - This return is not used at all by Evennia by default, but might - be useful for coders intending to implement some sort of nested - command structure. """ - return super(DefaultPlayer, self).execute_cmd(raw_string, sessid=sessid, **kwargs) + raw_string = to_unicode(raw_string) + raw_string = self.nicks.nickreplace(raw_string, + categories=("inputline", "channel"), include_player=False) + if not sessid and _MULTISESSION_MODE in (0, 1): + # in this case, we should either have only one sessid, or the sessid + # should not matter (since the return goes to all of them we can + # just use the first one as the source) + try: + sessid = self.get_all_sessions()[0].sessid + except IndexError: + # this can happen for bots + sessid = None + return cmdhandler.cmdhandler(self, raw_string, + callertype="player", sessid=sessid, **kwargs) def search(self, searchdata, return_puppet=False, **kwargs): """ - This is similar to the Object search method but will search for + This is similar to the ObjectDB search method but will search for Players only. Errors will be echoed, and None returned if no Player is found. searchdata - search criterion, the Player's key or dbref to search for @@ -224,7 +387,14 @@ class DefaultPlayer(PlayerDB): # handle wrapping of common terms if searchdata.lower() in ("me", "*me", "self", "*self",): return self - return super(DefaultPlayer, self).search(searchdata, return_puppet=return_puppet, **kwargs) + matches = self.__class__.objects.player_search(searchdata) + matches = _AT_SEARCH_RESULT(self, searchdata, matches, global_search=True) + if matches and return_puppet: + try: + return matches.puppet + except AttributeError: + return None + return matches def is_typeclass(self, typeclass, exact=False): """ @@ -332,6 +502,7 @@ class DefaultPlayer(PlayerDB): lockstring = "attrread:perm(Admins);attredit:perm(Admins);attrcreate:perm(Admins)" self.attributes.add("_playable_characters", [], lockstring=lockstring) + # TODO - handle this in __init__ instead. def at_init(self): """ This is always called whenever this object is initiated -- @@ -344,12 +515,35 @@ class DefaultPlayer(PlayerDB): """ pass + # Note that the hooks below also exist in the character object's # typeclass. You can often ignore these and rely on the character # ones instead, unless you are implementing a multi-character game # and have some things that should be done regardless of which # character is currently connected to this player. + def at_first_save(self): + """ + This is a generic hook called by Evennia when this object is + saved to the database the very first time. You generally + don't override this method but the hooks called by it. + """ + self.basetype_setup() + self.at_player_creation() + + permissions = settings.PERMISSION_PLAYER_DEFAULT + if hasattr(self, "_createdict"): + # this will only be set if the utils.create_player + # function was used to create the object. + cdict = self._createdict + if "locks" in cdict: + self.locks.add(cdict["locks"]) + if "permissions" in cdict: + permissions = cdict["permissions"] + del self._createdict + + self.permissions.add(permissions) + def at_access(self, result, accessing_obj, access_type, **kwargs): """ This is called with the result of an access call, along with @@ -373,8 +567,7 @@ class DefaultPlayer(PlayerDB): def at_first_login(self): """ - Only called once, the very first - time the user logs in. + Called the very first time this player logs into the game. """ pass @@ -472,6 +665,7 @@ class DefaultPlayer(PlayerDB): """ pass + class Guest(DefaultPlayer): """ This class is used for guest logins. Unlike Players, Guests and their diff --git a/src/scripts/models.py b/src/scripts/models.py index 99ec793f4..1d73aa8eb 100644 --- a/src/scripts/models.py +++ b/src/scripts/models.py @@ -158,23 +158,23 @@ class ScriptDB(TypedObject): object = property(__get_obj, __set_obj) - def at_typeclass_error(self): - """ - If this is called, it means the typeclass has a critical - error and cannot even be loaded. We don't allow a script - to be created under those circumstances. Already created, - permanent scripts are set to already be active so they - won't get activated now (next reboot the bug might be fixed) - """ - # By setting is_active=True, we trick the script not to run "again". - self.is_active = True - return super(ScriptDB, self).at_typeclass_error() - - delete_iter = 0 - def delete(self): - "Delete script" - if self.delete_iter > 0: - return - self.delete_iter += 1 - _GA(self, "attributes").clear() - super(ScriptDB, self).delete() +# def at_typeclass_error(self): +# """ +# If this is called, it means the typeclass has a critical +# error and cannot even be loaded. We don't allow a script +# to be created under those circumstances. Already created, +# permanent scripts are set to already be active so they +# won't get activated now (next reboot the bug might be fixed) +# """ +# # By setting is_active=True, we trick the script not to run "again". +# self.is_active = True +# return super(ScriptDB, self).at_typeclass_error() +# +# delete_iter = 0 +# def delete(self): +# "Delete script" +# if self.delete_iter > 0: +# return +# self.delete_iter += 1 +# _GA(self, "attributes").clear() +# super(ScriptDB, self).delete() diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index 5cf0e81cd..cb4e5eefd 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -351,109 +351,146 @@ class ScriptBase(ScriptDB): class Script(ScriptBase): """ - This is the class you should inherit from, it implements - the hooks called by the script machinery. + This is the base TypeClass for all Scripts. Scripts describe events, + timers and states in game, they can have a time component or describe + a state that changes under certain conditions. + + Script API: + + * Available properties (only available on initiated Typeclass objects) + + key (string) - name of object + name (string)- same as key + aliases (list of strings) - aliases to the object. Will be saved to + database as AliasDB entries but returned as strings. + dbref (int, read-only) - unique #id-number. Also "id" can be used. + dbobj (Object, read-only) - link to database model. dbobj.typeclass + points back to this class + typeclass (Object, read-only) - this links back to this class as an + identified only. Use self.swap_typeclass() to switch. + date_created (string) - time stamp of object creation + permissions (list of strings) - list of permission strings + + desc (string) - optional description of script, shown in listings + obj (Object) - optional object that this script is connected to + and acts on (set automatically + by obj.scripts.add()) + interval (int) - how often script should run, in seconds. + <=0 turns off ticker + start_delay (bool) - if the script should start repeating right + away or wait self.interval seconds + repeats (int) - how many times the script should repeat before + stopping. <=0 means infinite repeats + persistent (bool) - if script should survive a server shutdown or not + is_active (bool) - if script is currently running + + * Handlers + + locks - lock-handler: use locks.add() to add new lock strings + db - attribute-handler: store/retrieve database attributes on this + self.db.myattr=val, val=self.db.myattr + ndb - non-persistent attribute handler: same as db but does not + create a database entry when storing data + + * Helper methods + + start() - start script (this usually happens automatically at creation + and obj.script.add() etc) + stop() - stop script, and delete it + pause() - put the script on hold, until unpause() is called. If script + is persistent, the pause state will survive a shutdown. + unpause() - restart a previously paused script. The script will + continue as if it was never paused. + force_repeat() - force-step the script, regardless of how much remains + until next step. This counts like a normal firing in all ways. + time_until_next_repeat() - if a timed script (interval>0), returns + time until next tick + remaining_repeats() - number of repeats remaining, if limited + + * Hook methods + + at_script_creation() - called only once, when an object of this + class is first created. + is_valid() - is called to check if the script is valid to be running + at the current time. If is_valid() returns False, the + running script is stopped and removed from the game. You + can use this to check state changes (i.e. an script + tracking some combat stats at regular intervals is only + valid to run while there is actual combat going on). + at_start() - Called every time the script is started, which for + persistent scripts is at least once every server start. + Note that this is unaffected by self.delay_start, which + only delays the first call to at_repeat(). It will also + be called after a pause, to allow for setting up the script. + at_repeat() - Called every self.interval seconds. It will be called + immediately upon launch unless self.delay_start is True, + which will delay the first call of this method by + self.interval seconds. If self.interval<=0, this method + will never be called. + at_stop() - Called as the script object is stopped and is about to + be removed from the game, e.g. because is_valid() + returned False or self.stop() was called manually. + at_server_reload() - Called when server reloads. Can be used to save + temporary variables you want should survive a reload. + at_server_shutdown() - called at a full server shutdown. + """ - - def __init__(self, *args, **kwargs): + def at_first_save(self): """ - This is the base TypeClass for all Scripts. Scripts describe events, - timers and states in game, they can have a time component or describe - a state that changes under certain conditions. + This is called after very first time this object is saved. + Generally, you don't need to overload this, but only the hooks + called by this method. + """ + self.at_script_creation(self) - Script API: + if hasattr(self, "_createdict"): + # this will only be set if the utils.create_script + # function was used to create the object. We want + # the create call's kwargs to override the values + # set by hooks. + cdict = self._createdict + updates = [] + if not cdict["key"]: + self.db_key = "#%i" % self.dbid + updates.append("db_key") + elif self.key != cdict["db_key"]: + self.db_key = cdict["key"] + updates.append("db_key") + if cdict["interval"] and self.interval != cdict["interval"]: + self.db_interval = cdict["interval"] + updates.append("db_interval") + if cdict["start_delay"] and self.start_delay != cdict["start_delay"]: + self.db_start_delay = cdict["start_delay"] + updates.append("db_start_delay") + if cdict["repeats"] and self.repeats != cdict["repeats"]: + self.db_repeats = cdict["repeats"] + updates.append("db_repeats") + if cdict["persistent"] and self.persistent != cdict["persistent"]: + self.db_persistent = cdict["persistent"] + updates.append("db_persistent") + if updates: + self.save(update_fields=updates) - * Available properties (only available on initiated Typeclass objects) + if cdict["permissions"]: + self.permissions.add(cdict["permissions"]) + if cdict["locks"]: + self.locks.add(cdict["locks"]) + if cdict["aliases"]: + self.aliases.add(cdict["aliases"]) - key (string) - name of object - name (string)- same as key - aliases (list of strings) - aliases to the object. Will be saved to - database as AliasDB entries but returned as strings. - dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Object, read-only) - link to database model. dbobj.typeclass - points back to this class - typeclass (Object, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. - date_created (string) - time stamp of object creation - permissions (list of strings) - list of permission strings + if not cdict["autostart"]: + # don't auto-start the script + return - desc (string) - optional description of script, shown in listings - obj (Object) - optional object that this script is connected to - and acts on (set automatically - by obj.scripts.add()) - interval (int) - how often script should run, in seconds. - <=0 turns off ticker - start_delay (bool) - if the script should start repeating right - away or wait self.interval seconds - repeats (int) - how many times the script should repeat before - stopping. <=0 means infinite repeats - persistent (bool) - if script should survive a server shutdown or not - is_active (bool) - if script is currently running + # auto-start script (default) + self.start() - * Handlers - - locks - lock-handler: use locks.add() to add new lock strings - db - attribute-handler: store/retrieve database attributes on this - self.db.myattr=val, val=self.db.myattr - ndb - non-persistent attribute handler: same as db but does not - create a database entry when storing data - - * Helper methods - - start() - start script (this usually happens automatically at creation - and obj.script.add() etc) - stop() - stop script, and delete it - pause() - put the script on hold, until unpause() is called. If script - is persistent, the pause state will survive a shutdown. - unpause() - restart a previously paused script. The script will - continue as if it was never paused. - force_repeat() - force-step the script, regardless of how much remains - until next step. This counts like a normal firing in all ways. - time_until_next_repeat() - if a timed script (interval>0), returns - time until next tick - remaining_repeats() - number of repeats remaining, if limited - - * Hook methods - - at_script_creation() - called only once, when an object of this - class is first created. - is_valid() - is called to check if the script is valid to be running - at the current time. If is_valid() returns False, the - running script is stopped and removed from the game. You - can use this to check state changes (i.e. an script - tracking some combat stats at regular intervals is only - valid to run while there is actual combat going on). - at_start() - Called every time the script is started, which for - persistent scripts is at least once every server start. - Note that this is unaffected by self.delay_start, which - only delays the first call to at_repeat(). It will also - be called after a pause, to allow for setting up the script. - at_repeat() - Called every self.interval seconds. It will be called - immediately upon launch unless self.delay_start is True, - which will delay the first call of this method by - self.interval seconds. If self.interval<=0, this method - will never be called. - at_stop() - Called as the script object is stopped and is about to - be removed from the game, e.g. because is_valid() - returned False or self.stop() was called manually. - at_server_reload() - Called when server reloads. Can be used to save - temporary variables you want should survive a reload. - at_server_shutdown() - called at a full server shutdown. - - - """ - super(Script, self).__init__(*args, **kwargs) def at_script_creation(self): """ Only called once, by the create function. """ - self.key = "" - self.desc = "" - self.interval = 0 # infinite - self.start_delay = False - self.repeats = 0 # infinite - self.persistent = False + pass def is_valid(self): """ diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 588cc962a..874e3dab8 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -74,10 +74,10 @@ _SA = object.__setattr__ # signal receivers. Assigned in __new__ def post_save(sender, instance, created, **kwargs): """ - Is called Receive a signal just after the object is saved. + Receives a signal just after the object is saved. """ if created: - instance.at_instance_creation() + instance.at_first_save() #TODO - put OOB handler here? @@ -363,7 +363,8 @@ class TypedObject(SharedMemoryModel): self.nattributes.clear() if run_start_hooks: - self.at_instance_creation() + # fake this call to mimic the first save + self.at_first_save() # # Lock / permission methods diff --git a/src/utils/create.py b/src/utils/create.py index 4ab9492ea..92a428b11 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -48,33 +48,6 @@ __all__ = ("create_object", "create_script", "create_help_entry", _GA = object.__getattribute__ -# Helper function - -def handle_dbref(inp, objclass, raise_errors=True): - """ - Convert a #dbid to a valid object of objclass. objclass - should be a valid object class to filter against (objclass.filter ...) - If not raise_errors is set, this will swallow errors of non-existing - objects. - """ - if not (isinstance(inp, basestring) and inp.startswith("#")): - return inp - # a string, analyze it - inp = inp.lstrip('#') - try: - if int(inp) < 0: - return None - except ValueError: - return None - - # if we get to this point, inp is an integer dbref; get the matching object - try: - return objclass.objects.get(id=inp) - except Exception: - if raise_errors: - raise - return inp - # # Game Object creation # @@ -128,7 +101,7 @@ def create_object(typeclass=None, key=None, location=None, "locks":locks, "aliases":aliases, "destination":destination, "report_to":report_to, "nohome":nohome} # this will trigger the save signal which in turn calls the - # at_instance_creation hook on the typeclass, where the _createdict can be + # at_first_save hook on the typeclass, where the _createdict can be # used. new_object.save() return new_object @@ -168,83 +141,33 @@ def create_script(typeclass, key=None, obj=None, player=None, locks=None, error will be raised. If set, this method will return None upon errors. """ - global _Script, _ScriptDB - if not _Script: - from src.scripts.scripts import Script as _Script - if not _ScriptDB: - from src.scripts.models import ScriptDB as _ScriptDB + typeclass = typeclass if typeclass else settings.BASE_SCRIPT_TYPECLASS - if not typeclass: - typeclass = settings.BASE_SCRIPT_TYPECLASS - elif isinstance(typeclass, _ScriptDB): - # this is already an scriptdb instance, extract its typeclass - 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 + if isinstance(typeclass, basestring): + # a path is given. Load the actual typeclass + typeclass = class_from_module(typeclass, settings.SCRIPT_TYPECLASS_PATHS) - # create new database script - new_db_script = _ScriptDB() + # validate input + player = dbid_to_obj(player) + obj = dbid_to_obj(obj) - # 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. - if key: - new_db_script.key = key - else: - new_db_script.key = "#%i" % new_db_script.id - - # this will either load the typeclass or the default one - new_script = new_db_script.typeclass - - 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 - SharedMemoryModel.delete(new_db_script) - 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: - new_script.obj = obj - if player: - new_script.player = player - - # call the hook method. This is where all at_creation - # customization happens as the typeclass stores custom - # things on its database object. - new_script.at_script_creation() - - # custom-given variables override the hook - if key: - new_script.key = key - if locks: - new_script.locks.add(locks) - if interval is not None: - new_script.interval = interval - if start_delay is not None: - new_script.start_delay = start_delay - if repeats is not None: - new_script.repeats = repeats - if persistent is not None: - new_script.persistent = persistent - - # must do this before starting the script since some - # scripts may otherwise run for a very short time and - # try to delete itself before we have a time to save it. - new_db_script.save() - - # a new created script should usually be started. - if autostart: - new_script.start() + # create new instance + new_script = typeclass(db_key=key, db_obj=obj, db_player=player, + db_interval=interval, db_start_delay=start_delay, + db_repeats=repeats, db_peristent=persistent) + # store the call signature for the signal + new_script._createdict = {"key":key, "obj":obj, "player":player, + "locks":locks, "interval":interval, + "start_delay":start_delay, "repeats":repeats, + "persistent":persistent, "autostart":autostart, + "report_to":report_to} + # this will trigger the save signal which in turn calls the + # at_first_save hook on the tyepclass, where the _createdict + # can be used. + new_script.save() return new_script + #alias script = create_script @@ -413,11 +336,16 @@ def create_player(key, email, password, operations and is thus not suitable for play-testing the game. """ - global _PlayerDB, _Player - if not _PlayerDB: - from src.players.models import PlayerDB as _PlayerDB - if not _Player: - from src.players.player import Player as _Player + typeclass = typeclass if typeclass else settings.BASE_PLAYER_TYPECLASS + + if isinstance(typeclass, basestring): + # a path is given. Load the actual typeclass. + typeclass = class_from_module(typeclass, settings.OBJECT_TYPECLASS_PATHS) + typeclass_path = typeclass.path + + # setup input for the create command. We use PlayerDB as baseclass + # here to give us maximum freedom (the typeclasses will load + # correctly when each object is recovered). if not email: email = "dummy@dummy.com" @@ -425,69 +353,23 @@ def create_player(key, email, password, raise ValueError("A Player with the name '%s' already exists." % key) # this handles a given dbref-relocate to a player. - report_to = handle_dbref(report_to, _PlayerDB) + report_to = dbid_to_obj(report_to, _PlayerDB) - try: + # create the correct player object + if is_superuser: + new_player = _PlayerDB.objects.create_superuser(key, email, password) + else: + new_player = _PlayerDB.objects.create_user(key, email, password) + new_player.db_typeclass_path = typeclass_path + # store the call signature for the signal + new_player._createdict = {"locks":locks, "permissions":permissions, + "report_to":report_to} - # create the correct Player object - if is_superuser: - new_db_player = _PlayerDB.objects.create_superuser(key, email, password) - else: - new_db_player = _PlayerDB.objects.create_user(key, email, password) - - if not typeclass: - typeclass = settings.BASE_PLAYER_TYPECLASS - elif isinstance(typeclass, _PlayerDB): - # this is an PlayerDB instance, extract its typeclass path - typeclass = typeclass.typeclass.path - elif isinstance(typeclass, _Player) or utils.inherits_from(typeclass, _Player): - # this is Player object typeclass, extract its path - typeclass = typeclass.path - - # assign the typeclass - typeclass = utils.to_unicode(typeclass) - new_db_player.typeclass_path = typeclass - - # this will either load the typeclass or the default one - new_player = new_db_player.typeclass - - 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) - 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 - if permissions: - new_player.permissions.add(permissions) - elif not new_player.permissions: - new_player.permissions.add(settings.PERMISSION_PLAYER_DEFAULT) - if locks: - new_player.locks.add(locks) - return new_player - - except Exception: - # a failure in creating the player; we try to clean - # up as much as we can - logger.log_trace() - try: - new_player.delete() - except Exception: - pass - try: - del new_player - except Exception: - pass - raise + # saving will trigger the signal that calls the + # at_first_save hook on the typeclass, where the _createdict + # can be used. + new_player.save() + return new_player # alias player = create_player diff --git a/src/utils/spawner.py b/src/utils/spawner.py index b4bbdc311..cddaab274 100644 --- a/src/utils/spawner.py +++ b/src/utils/spawner.py @@ -76,12 +76,11 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' from django.conf import settings from random import randint from src.objects.models import ObjectDB -from src.utils.create import handle_dbref -from src.utils.utils import make_iter, all_from_module +from src.utils.utils import make_iter, all_from_module, dbid_to_obj _CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination") -_handle_dbref = lambda inp: handle_dbref(inp, ObjectDB) +_handle_dbref = lambda inp: dbid_to_obj(inp, ObjectDB) def _validate_prototype(key, prototype, protparents, visited): From dd20740e73c11eef55cc98a7d3b138486e09a59a Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 25 Dec 2014 17:08:12 +0100 Subject: [PATCH 030/250] Added Channels to new proxy system. --- src/comms/comms.py | 28 ++++++++++++++++++++++++++++ src/comms/models.py | 1 - src/utils/create.py | 42 ++++++++++++++++-------------------------- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/comms/comms.py b/src/comms/comms.py index 5e62202c1..5f5d052d2 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -18,6 +18,34 @@ class Channel(ChannelDB): __metaclass__ = TypeclassBase objects = ChannelManager() + def at_first_save(self): + """ + Called by the typeclass system the very first time the channel + is saved to the database. Generally, don't overload this but + the hooks called by this method. + """ + self.at_channel_creation() + + if hasattr(self, "_createdict"): + # this is only set if the channel was created + # with the utils.create.create_channel function. + cdict = self._createdict + if not cdict["key"]: + self.db_key = "#i" % self.dbid + elif cdict["key"] and self.key != cdict["key"]: + self.key = cdict["key"] + if cdict["keep_log"]: + self.db_keep_log = cdict["keep_log"] + if cdict["aliases"]: + self.aliases.add(cdict["aliases"]) + if cdict["locks"]: + self.locks.add(cdict["locks"]) + if cdict["keep_log"]: + self.attributes.add("keep_log", cdict["keep_log"]) + if cdict["desc"]: + self.attributes.add("desc", cdict["desc"]) + + # helper methods, for easy overloading def channel_prefix(self, msg=None, emit=False): diff --git a/src/comms/models.py b/src/comms/models.py index fa822d81a..69704ed31 100644 --- a/src/comms/models.py +++ b/src/comms/models.py @@ -346,7 +346,6 @@ class ChannelDB(TypedObject): key - main name for channel desc - optional description of channel aliases - alternative names for the channel - keep_log - bool if the channel should remember messages permissions - perm strings """ diff --git a/src/utils/create.py b/src/utils/create.py index 92a428b11..53fd3a2bc 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -271,33 +271,23 @@ def create_channel(key, aliases=None, desc=None, aliases - list of alternative (likely shorter) keynames. locks - lock string definitions """ - global _ChannelDB, _channelhandler - if not _ChannelDB: - from src.comms.models import ChannelDB as _ChannelDB - if not _channelhandler: - from src.comms import channelhandler as _channelhandler - if not typeclass: - typeclass = settings.BASE_CHANNEL_TYPECLASS - try: - new_channel = _ChannelDB(typeclass=typeclass, db_key=key) - new_channel.save() - new_channel = new_channel.typeclass - if aliases: - if not utils.is_iter(aliases): - aliases = [aliases] - new_channel.aliases.add(aliases) - new_channel.save() - new_channel.db.desc = desc - new_channel.db.keep_log = keep_log - except IntegrityError: - string = "Could not add channel: key '%s' already exists." % key - logger.log_errmsg(string) - return None - if locks: - new_channel.locks.add(locks) + typeclass = typeclass if typeclass else settings.BASE_CHANNEL_TYPECLASS + + if isinstance(typeclass, basestring): + # a path is given. Load the actual typeclass + typeclass = class_from_module(typeclass, settings.CHANNEL_TYPECLASS_PATHS) + + # create new instance + new_channel = typeclass(db_key=key) + + # store call signature for the signal + new_channel._createdict = {"key":key, "aliases":aliases, + "desc":desc, "locks":locks, "keep_log":keep_log} + + # this will trigger the save signal which in turn calls the + # at_first_save hook on the typeclass, where the _createdict can be + # used. new_channel.save() - _channelhandler.CHANNELHANDLER.add_channel(new_channel) - new_channel.at_channel_create() return new_channel channel = create_channel From 11449f3d62edc4747f797d30dc69dc1d53f8b055 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 25 Dec 2014 18:58:11 +0100 Subject: [PATCH 031/250] Fixed some errors in channel loading. --- src/settings_default.py | 2 +- src/typeclasses/models.py | 5 +++-- src/utils/utils.py | 21 +++++++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/settings_default.py b/src/settings_default.py index 7a6d5b2e1..db9a2d247 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -298,7 +298,7 @@ BASE_ROOM_TYPECLASS = "src.objects.objects.DefaultRoom" # Typeclass for Exit objects (fallback). BASE_EXIT_TYPECLASS = "src.objects.objects.DefaultExit" # Typeclass for Channel (fallback). -BASE_CHANNEL_TYPECLASS = "src.comms.comms.DefaultChannel" +BASE_CHANNEL_TYPECLASS = "src.comms.comms.Channel" # Typeclass for Scripts (fallback). You usually don't need to change this # but create custom variations of scripts on a per-case basis instead. BASE_SCRIPT_TYPECLASS = "src.scripts.scripts.DoNothing" diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 874e3dab8..7a98390e4 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -46,7 +46,7 @@ from src.server.caches import get_prop_cache, set_prop_cache from src.typeclasses import managers from src.locks.lockhandler import LockHandler from src.utils.utils import ( - is_iter, to_str, inherits_from, lazy_property, + is_iter, inherits_from, lazy_property, class_from_module) from src.typeclasses.django_new_patch import patched_new @@ -191,7 +191,7 @@ class TypedObject(SharedMemoryModel): else: self.db_typeclass_path = "%s.%s" % (self.__module__, self.__class__.__name__) # important to put this at the end since _meta is based on the set __class__ - self.__dbclass__ = self._meta.proxy_for_model + self.__dbclass__ = self._meta.proxy_for_model or self.__class__ # initialize all handlers in a lazy fashion @lazy_property @@ -348,6 +348,7 @@ class TypedObject(SharedMemoryModel): "right type instead." % self.key) self.typeclass_path = new_typeclass.path + self.__class__ = new_typeclass if clean_attributes: # Clean out old attributes diff --git a/src/utils/utils.py b/src/utils/utils.py index fa17d4e0e..89c54bab8 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -933,24 +933,29 @@ def class_from_module(path, defaultpaths=None): """ cls = None if defaultpaths: - paths = [path] + make_iter(defaultpaths) if defaultpaths else [] + paths = [path] + ["%s.%s" % (dpath, path) for dpath in make_iter(defaultpaths)] if defaultpaths else [] else: paths = [path] - for path in paths: - path, clsname = path.rsplit(".", 1) + for testpath in paths: + if "." in path: + testpath, clsname = testpath.rsplit(".", 1) + else: + testpath, clsname = ".", testpath try: - mod = import_module(path) + mod = import_module(testpath) except ImportError, ex: # normally this is due to a not-found property - if not str(ex).startswith ("No module named %s" % path): - raise ex + if not str(ex).startswith("No module named"): + exc = sys.exc_info() + raise exc[1], None, exc[2] try: cls = getattr(mod, clsname) break except AttributeError, ex: - if not str(ex).startswith("Object 'module' has no attribute '%s'" % clsname): - raise ex + if not str(ex).startswith("'module' object has no attribute '%s'" % clsname): + exc = sys.exc_info() + raise exc[1], None, exc[2] if not cls: raise ImportError("Could not load typeclass '%s'." % path) return cls From b8396142590dd710d644fa22926f4b0670567e54 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 26 Dec 2014 13:31:23 +0100 Subject: [PATCH 032/250] Fixed the entire first_init script process with the new typeclass system. --- src/comms/comms.py | 71 +++++++++++++++++++++++++++++++++---- src/comms/models.py | 64 --------------------------------- src/scripts/models.py | 6 +--- src/scripts/scripts.py | 45 ++++++++--------------- src/server/initial_setup.py | 18 +++++----- src/utils/create.py | 31 ++++++++++++---- 6 files changed, 114 insertions(+), 121 deletions(-) diff --git a/src/comms/comms.py b/src/comms/comms.py index 5f5d052d2..303d17ac7 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -45,9 +45,74 @@ class Channel(ChannelDB): if cdict["desc"]: self.attributes.add("desc", cdict["desc"]) + def at_channel_creation(self): + """ + Called once, when the channel is first created. + """ + pass # helper methods, for easy overloading + def has_connection(self, player): + """ + Checks so this player is actually listening + to this channel. + """ + if hasattr(player, "player"): + player = player.player + player = player.dbobj + return player in self.db_subscriptions.all() + + def connect(self, player): + "Connect the user to this channel. This checks access." + if hasattr(player, "player"): + player = player.player + # check access + if not self.access(player, 'listen'): + return False + # pre-join hook + connect = self.pre_join_channel(player) + if not connect: + return False + # subscribe + self.db_subscriptions.add(player) + # post-join hook + self.post_join_channel(player) + return True + + def disconnect(self, player): + "Disconnect user from this channel." + if hasattr(player, "player"): + player = player.player + # pre-disconnect hook + disconnect = self.pre_leave_channel(player) + if not disconnect: + return False + # disconnect + self.db_subscriptions.remove(player.dbobj) + # post-disconnect hook + self.post_leave_channel(player.dbobj) + return True + + def access(self, accessing_obj, access_type='listen', default=False): + """ + Determines if another object has permission to access. + accessing_obj - object trying to access this one + access_type - type of access sought + default - what to return if no lock of access_type was found + """ + return self.locks.check(accessing_obj, access_type=access_type, default=default) + + def delete(self): + """ + Deletes channel while also cleaning up channelhandler + """ + self.attributes.clear() + self.aliases.clear() + super(Channel, self).delete() + from src.comms.channelhandler import CHANNELHANDLER + CHANNELHANDLER.update() + def channel_prefix(self, msg=None, emit=False): """ How the channel should prefix itself for users. Return a string. @@ -134,12 +199,6 @@ class Channel(ChannelDB): msg.message = body return msg - def at_channel_create(self): - """ - Run at channel creation. - """ - pass - def pre_join_channel(self, joiner): """ Run right before a channel is joined. If this returns a false value, diff --git a/src/comms/models.py b/src/comms/models.py index 69704ed31..21d4369b0 100644 --- a/src/comms/models.py +++ b/src/comms/models.py @@ -363,69 +363,5 @@ class ChannelDB(TypedObject): verbose_name = "Channel" verbose_name_plural = "Channels" - # - # Channel class methods - # - def __str__(self): return "Channel '%s' (%s)" % (self.key, self.db.desc) - - def has_connection(self, player): - """ - Checks so this player is actually listening - to this channel. - """ - if hasattr(player, "player"): - player = player.player - player = player.dbobj - return player in self.db_subscriptions.all() - - def connect(self, player): - "Connect the user to this channel. This checks access." - if hasattr(player, "player"): - player = player.player - # check access - if not self.access(player, 'listen'): - return False - # pre-join hook - connect = self.pre_join_channel(player) - if not connect: - return False - # subscribe - self.db_subscriptions.add(player.dbobj) - # post-join hook - self.post_join_channel(player) - return True - - def disconnect(self, player): - "Disconnect user from this channel." - if hasattr(player, "player"): - player = player.player - # pre-disconnect hook - disconnect = self.pre_leave_channel(player) - if not disconnect: - return False - # disconnect - self.db_subscriptions.remove(player.dbobj) - # post-disconnect hook - self.post_leave_channel(player.dbobj) - return True - - def access(self, accessing_obj, access_type='listen', default=False): - """ - Determines if another object has permission to access. - accessing_obj - object trying to access this one - access_type - type of access sought - default - what to return if no lock of access_type was found - """ - return self.locks.check(accessing_obj, access_type=access_type, default=default) - - def delete(self): - """ - Deletes channel while also cleaning up channelhandler - """ - _GA(self, "attributes").clear() - _GA(self, "aliases").clear() - super(ChannelDB, self).delete() - from src.comms.channelhandler import CHANNELHANDLER - CHANNELHANDLER.update() diff --git a/src/scripts/models.py b/src/scripts/models.py index 1d73aa8eb..f9177c8a8 100644 --- a/src/scripts/models.py +++ b/src/scripts/models.py @@ -73,7 +73,7 @@ class ScriptDB(TypedObject): # # ScriptDB Database Model setup # - # These databse fields are all set using their corresponding properties, + # These database fields are all set using their corresponding properties, # named same as the field, but withtou the db_* prefix. # inherited fields (from TypedObject): @@ -100,10 +100,6 @@ class ScriptDB(TypedObject): # Database manager objects = ScriptDBManager() - # caches for quick lookups - _typeclass_paths = settings.SCRIPT_TYPECLASS_PATHS - _default_typeclass_path = settings.BASE_SCRIPT_TYPECLASS or "src.scripts.scripts.DoNothing" - class Meta: "Define Django meta options" verbose_name = "Script" diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index cb4e5eefd..5beefedb4 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -135,7 +135,7 @@ class ScriptBase(ScriptDB): if self.db._paused_time: # the script was paused; restarting callcount = self.db._paused_callcount or 0 - self.ndb._task.start(self.dbobj.db_interval, + self.ndb._task.start(self.db_interval, now=False, start_delay=self.db._paused_time, count_start=callcount) @@ -143,8 +143,8 @@ class ScriptBase(ScriptDB): del self.db._paused_repeats else: # starting script anew - self.ndb._task.start(self.dbobj.db_interval, - now=not self.dbobj.db_start_delay) + self.ndb._task.start(self.db_interval, + now=not self.db_start_delay) def _stop_task(self): "stop task runner" @@ -159,7 +159,7 @@ class ScriptBase(ScriptDB): {"key": self.key, "dbid": self.dbid, "cname": cname, "err": e.getErrorMessage()} try: - self.dbobj.db_obj.msg(estring) + self.db_obj.msg(estring) except Exception: pass logger.log_errmsg(estring) @@ -176,7 +176,7 @@ class ScriptBase(ScriptDB): # check repeats callcount = self.ndb._task.callcount - maxcount = self.dbobj.db_repeats + maxcount = self.db_repeats if maxcount > 0 and maxcount <= callcount: #print "stopping script!" self.stop() @@ -210,7 +210,7 @@ class ScriptBase(ScriptDB): "Get the number of returning repeats. Returns None if unlimited repeats." task = self.ndb._task if task: - return max(0, self.dbobj.db_repeats - task.callcount) + return max(0, self.db_repeats - task.callcount) def start(self, force_restart=False): """ @@ -224,10 +224,7 @@ class ScriptBase(ScriptDB): Used in counting. """ - #print "Script %s (%s) start (active:%s, force:%s) ..." % (self.key, id(self.dbobj), - # self.is_active, force_restart) - - if self.dbobj.is_active and not force_restart: + if self.is_active and not force_restart: # script already runs and should not be restarted. return 0 @@ -235,11 +232,11 @@ class ScriptBase(ScriptDB): if obj: # check so the scripted object is valid and initalized try: - _GA(obj.dbobj, 'cmdset') + obj.cmdset except AttributeError: # this means the object is not initialized. logger.log_trace() - self.dbobj.is_active = False + self.is_active = False return 0 # try to restart a paused script @@ -247,13 +244,13 @@ class ScriptBase(ScriptDB): return 1 # start the script from scratch - self.dbobj.is_active = True + self.is_active = True try: self.at_start() except Exception: logger.log_trace() - if self.dbobj.db_interval > 0: + if self.db_interval > 0: self._start_task() return 1 @@ -274,7 +271,7 @@ class ScriptBase(ScriptDB): logger.log_trace() self._stop_task() try: - self.dbobj.delete() + self.delete() except AssertionError: logger.log_trace() return 0 @@ -292,7 +289,7 @@ class ScriptBase(ScriptDB): self.db._paused_time = task.next_call_time() self.db._paused_callcount = task.callcount self._stop_task() - self.dbobj.is_active = False + self.is_active = False def unpause(self): """ @@ -300,7 +297,7 @@ class ScriptBase(ScriptDB): """ if self.db._paused_time: # only unpause if previously paused - self.dbobj.is_active = True + self.is_active = True try: self.at_start() @@ -364,10 +361,6 @@ class Script(ScriptBase): aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Object, read-only) - link to database model. dbobj.typeclass - points back to this class - typeclass (Object, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. date_created (string) - time stamp of object creation permissions (list of strings) - list of permission strings @@ -441,7 +434,7 @@ class Script(ScriptBase): Generally, you don't need to overload this, but only the hooks called by this method. """ - self.at_script_creation(self) + self.at_script_creation() if hasattr(self, "_createdict"): # this will only be set if the utils.create_script @@ -470,14 +463,6 @@ class Script(ScriptBase): updates.append("db_persistent") if updates: self.save(update_fields=updates) - - if cdict["permissions"]: - self.permissions.add(cdict["permissions"]) - if cdict["locks"]: - self.locks.add(cdict["locks"]) - if cdict["aliases"]: - self.aliases.add(cdict["aliases"]) - if not cdict["autostart"]: # don't auto-start the script return diff --git a/src/server/initial_setup.py b/src/server/initial_setup.py index 8ac302bb8..bc87d5673 100644 --- a/src/server/initial_setup.py +++ b/src/server/initial_setup.py @@ -69,7 +69,8 @@ def create_objects(): # it to exist in Limbo. character_typeclass = settings.BASE_CHARACTER_TYPECLASS god_character = create.create_object(character_typeclass, - key=god_player.username, nohome=True) + key=god_player.username, + nohome=True) god_character.id = 1 god_character.db.desc = _('This is User #1.') @@ -84,14 +85,13 @@ def create_objects(): room_typeclass = settings.BASE_ROOM_TYPECLASS limbo_obj = create.create_object(room_typeclass, _('Limbo'), nohome=True) limbo_obj.id = 2 - string = " ".join([ - "Welcome to your new {wEvennia{n-based game. From here you are ready", - "to begin development. Visit http://evennia.com if you should need", - "help or would like to participate in community discussions. If you", - "are logged in as User #1 you can create a demo/tutorial area with", - "'@batchcommand contrib.tutorial_world.build'. Log out and create", - "a new non-admin account at the login screen to play the tutorial", - "properly."]) + 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 " \ + "help or would like to participate in community discussions. If you " \ + "are logged in as user #1 you can create a demo/tutorial area with " \ + "{w@batchcommand contrib.tutorial_world.build{n. Use {w@quell{n or login " \ + "as normal player to play the demo properly." string = _(string) limbo_obj.db.desc = string limbo_obj.save() diff --git a/src/utils/create.py b/src/utils/create.py index 53fd3a2bc..1e3549051 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -23,8 +23,7 @@ Models covered: """ from django.conf import settings from django.db import IntegrityError -from src.utils.idmapper.models import SharedMemoryModel -from src.utils import utils, logger +from src.utils import logger from src.utils.utils import make_iter, class_from_module, dbid_to_obj # delayed imports @@ -71,6 +70,11 @@ def create_object(typeclass=None, key=None, location=None, nohome - this allows the creation of objects without a default home location; only used when creating the default location itself or during unittests """ + global _ObjectDB + if not _ObjectDB: + from src.objects.models import ObjectDB as _ObjectDB + + typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS if isinstance(typeclass, basestring): @@ -141,6 +145,10 @@ def create_script(typeclass, key=None, obj=None, player=None, locks=None, error will be raised. If set, this method will return None upon errors. """ + global _ScriptDB + if not _ScriptDB: + from src.scripts.models import ScriptDB as _ScriptDB + typeclass = typeclass if typeclass else settings.BASE_SCRIPT_TYPECLASS if isinstance(typeclass, basestring): @@ -148,13 +156,18 @@ def create_script(typeclass, key=None, obj=None, player=None, locks=None, typeclass = class_from_module(typeclass, settings.SCRIPT_TYPECLASS_PATHS) # validate input - player = dbid_to_obj(player) - obj = dbid_to_obj(obj) + kwarg = {} + if key: kwarg["db_key"] = key + if player: kwarg["db_player"] = dbid_to_obj(player, _ScriptDB) + if obj: kwarg["db_obj"] = dbid_to_obj(obj, _ScriptDB) + if interval: kwarg["db_interval"] = interval + if start_delay: kwarg["db_start_delay"] = start_delay + if repeats: kwarg["db_repeats"] = repeats + if persistent: kwarg["db_persistent"] = persistent # create new instance - new_script = typeclass(db_key=key, db_obj=obj, db_player=player, - db_interval=interval, db_start_delay=start_delay, - db_repeats=repeats, db_peristent=persistent) + new_script = typeclass(**kwarg) + # store the call signature for the signal new_script._createdict = {"key":key, "obj":obj, "player":player, "locks":locks, "interval":interval, @@ -326,6 +339,10 @@ def create_player(key, email, password, operations and is thus not suitable for play-testing the game. """ + global _PlayerDB + if not _PlayerDB: + from src.players.models import PlayerDB as _PlayerDB + typeclass = typeclass if typeclass else settings.BASE_PLAYER_TYPECLASS if isinstance(typeclass, basestring): From 3d557f6bf9673052e7559428a757ca4c1e7fbdd7 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 28 Dec 2014 20:00:31 +0100 Subject: [PATCH 033/250] Can log in, still errors all over the place due to missing .typeclass/.dbobj --- src/objects/objects.py | 7 ++++--- src/players/player.py | 7 +++---- src/typeclasses/models.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/objects/objects.py b/src/objects/objects.py index 416d3d69d..0744216fd 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -289,8 +289,7 @@ class DefaultObject(ObjectDB): return self.db_player and self.db_player.is_superuser \ and not self.db_player.attributes.get("_quell") - @property - def contents(self): + def contents_get(self, exclude=None): """ Returns the contents of this object, i.e. all objects that has this object set as its location. @@ -298,7 +297,9 @@ class DefaultObject(ObjectDB): exclude is one or more objects to not return """ - return ObjectDB.objects.get_contents(self) + return ObjectDB.objects.get_contents(self, excludeobj=exclude) + contents = property(contents_get) + @property def exits(self): diff --git a/src/players/player.py b/src/players/player.py index 4a160a4d0..644316ee7 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -221,9 +221,9 @@ class DefaultPlayer(PlayerDB): return False # do the disconnect, but only if we are the last session to puppet obj.at_pre_unpuppet() - obj.dbobj.sessid.remove(sessid) - if not obj.dbobj.sessid.count(): - del obj.dbobj.player + obj.sessid.remove(sessid) + if not obj.sessid.count(): + del obj.player obj.at_post_unpuppet(self, sessid=sessid) session.puppet = None session.puid = None @@ -244,7 +244,6 @@ class DefaultPlayer(PlayerDB): player's end. sessid - return character connected to this sessid, - character - return character if connected to this player, else None. """ session = self.get_session(sessid) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 7a98390e4..79f794165 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -286,7 +286,7 @@ class TypedObject(SharedMemoryModel): """ Returns the object's dbref on the form #NN. """ - return "#%s" % GA(self, "_TypedObject__dbid_get")() + return "#%s" % self.dbid def __dbref_set(self): raise Exception("dbref cannot be set!") From 0b01df1fcc518231d9c8dce80fc6018877fc3934 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 1 Jan 2015 21:13:36 +0100 Subject: [PATCH 034/250] Fixed creation function that caused entities to be wrong-named in certain circumstances. Server now starts and shutsdown without tracebacks. --- src/comms/comms.py | 3 ++- src/objects/objects.py | 5 +++-- src/scripts/scripts.py | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/comms/comms.py b/src/comms/comms.py index 303d17ac7..22d625bd0 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -31,7 +31,8 @@ class Channel(ChannelDB): # with the utils.create.create_channel function. cdict = self._createdict if not cdict["key"]: - self.db_key = "#i" % self.dbid + if not self.db_key: + self.db_key = "#i" % self.dbid elif cdict["key"] and self.key != cdict["key"]: self.key = cdict["key"] if cdict["keep_log"]: diff --git a/src/objects/objects.py b/src/objects/objects.py index 0744216fd..5dd39d9de 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -859,8 +859,9 @@ class DefaultObject(ObjectDB): cdict = self._createdict updates = [] if not cdict["key"]: - self.db_key = "#%i" % self.dbid - updates.append("db_key") + if not self.db_key: + self.db_key = "#%i" % self.dbid + updates.append("db_key") elif self.key != cdict["key"]: updates.append("db_key") self.db_key = cdict["key"] diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index 5beefedb4..e8c9cd991 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -444,9 +444,10 @@ class Script(ScriptBase): cdict = self._createdict updates = [] if not cdict["key"]: - self.db_key = "#%i" % self.dbid - updates.append("db_key") - elif self.key != cdict["db_key"]: + if not self.db_key: + self.db_key = "#%i" % self.dbid + updates.append("db_key") + elif self.db_key != cdict["db_key"]: self.db_key = cdict["key"] updates.append("db_key") if cdict["interval"] and self.interval != cdict["interval"]: From 1130dc5757e48228c0850d3098b4defe3dcaf26a Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 2 Jan 2015 19:01:09 +0100 Subject: [PATCH 035/250] Started to go through the unittest errors, fixing player creation. --- src/commands/default/comms.py | 2 +- src/commands/default/system.py | 4 ++-- src/commands/default/tests.py | 23 ++++++++++++----------- src/comms/comms.py | 17 ++++++++--------- src/objects/objects.py | 28 ++++++++++++++-------------- src/players/models.py | 4 ---- src/players/player.py | 4 ++-- src/scripts/scripts.py | 14 +++++++------- src/typeclasses/models.py | 2 ++ src/utils/create.py | 18 +++++++++--------- src/utils/idmapper/base.py | 2 +- 11 files changed, 58 insertions(+), 60 deletions(-) diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index a507d236f..a6f2d0d40 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -386,7 +386,7 @@ class CmdCBoot(MuxPlayerCommand): string = "You don't control this channel." self.msg(string) return - if not player.dbobj in channel.db_subscriptions.all(): + if not player in channel.db_subscriptions.all(): string = "Player %s is not connected to channel %s." % (player.key, channel.key) self.msg(string) return diff --git a/src/commands/default/system.py b/src/commands/default/system.py index 596177e24..b7f88db11 100644 --- a/src/commands/default/system.py +++ b/src/commands/default/system.py @@ -394,7 +394,7 @@ class CmdObjects(MuxCommand): latesttable.align = 'l' for obj in objs: latesttable.add_row(utils.datetime_format(obj.date_created), - obj.dbref, obj.key, obj.typeclass.path) + obj.dbref, obj.key, obj.path) string = "\n{wObject subtype totals (out of %i Objects):{n\n%s" % (nobjs, totaltable) string += "\n{wObject typeclass distribution:{n\n%s" % typetable @@ -437,7 +437,7 @@ class CmdPlayers(MuxCommand): plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):] latesttable = EvTable("{wcreated{n", "{wdbref{n", "{wname{n", "{wtypeclass{n", border="cells", align="l") for ply in plyrs: - latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.typeclass.path) + latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path) string = "\n{wPlayer typeclass distribution:{n\n%s" % typetable string += "\n{wLast %s Players created:{n\n%s" % (min(nplayers, nlim), latesttable) diff --git a/src/commands/default/tests.py b/src/commands/default/tests.py index 05ef9b60c..664e5be88 100644 --- a/src/commands/default/tests.py +++ b/src/commands/default/tests.py @@ -16,8 +16,8 @@ import re from django.conf import settings from django.utils.unittest import TestCase from src.server.serversession import ServerSession -from src.objects.objects import Object, Character -from src.players.player import Player +from src.objects.objects import DefaultObject, DefaultCharacter +from src.players.player import DefaultPlayer from src.utils import create, ansi from src.server.sessionhandler import SESSIONS @@ -41,13 +41,13 @@ SESSIONS.data_out = dummy SESSIONS.disconnect = dummy -class TestObjectClass(Object): +class TestObjectClass(DefaultObject): def msg(self, text="", **kwargs): "test message" pass -class TestCharacterClass(Character): +class TestCharacterClass(DefaultCharacter): def msg(self, text="", **kwargs): "test message" if self.player: @@ -58,17 +58,18 @@ class TestCharacterClass(Character): self.ndb.stored_msg.append(text) -class TestPlayerClass(Player): +class TestPlayerClass(DefaultPlayer): def msg(self, text="", **kwargs): "test message" if not self.ndb.stored_msg: self.ndb.stored_msg = [] self.ndb.stored_msg.append(text) - def _get_superuser(self): - "test with superuser flag" - return self.ndb.is_superuser - is_superuser = property(_get_superuser) + # not supported to overload is_superuser field with property. + #def _get_superuser(self): + # "test with superuser flag" + # return self.ndb.is_superuser + #is_superuser = property(_get_superuser) class CommandTest(TestCase): @@ -81,10 +82,10 @@ class CommandTest(TestCase): #print "creating player %i: %s" % (self.CID, self.__class__.__name__) self.player = create.create_player("TestPlayer%i" % self.CID, "test@test.com", "testpassword", typeclass=TestPlayerClass) self.player2 = create.create_player("TestPlayer%ib" % self.CID, "test@test.com", "testpassword", typeclass=TestPlayerClass) - self.room1 = create.create_object("src.objects.objects.Room", key="Room%i"%self.CID, nohome=True) + self.room1 = create.create_object("src.objects.objects.DefaultRoom", key="Room%i"%self.CID, nohome=True) self.room1.db.desc = "room_desc" settings.DEFAULT_HOME = "#%i" % self.room1.id # we must have a default home - self.room2 = create.create_object("src.objects.objects.Room", key="Room%ib" % self.CID) + self.room2 = create.create_object("src.objects.objects.DefaultRoom", key="Room%ib" % self.CID) self.obj1 = create.create_object(TestObjectClass, key="Obj%i" % self.CID, location=self.room1, home=self.room1) self.obj2 = create.create_object(TestObjectClass, key="Obj%ib" % self.CID, location=self.room1, home=self.room1) self.char1 = create.create_object(TestCharacterClass, key="Char%i" % self.CID, location=self.room1, home=self.room1) diff --git a/src/comms/comms.py b/src/comms/comms.py index 22d625bd0..f6e7da869 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -30,20 +30,20 @@ class Channel(ChannelDB): # this is only set if the channel was created # with the utils.create.create_channel function. cdict = self._createdict - if not cdict["key"]: + if not cdict.get("key"): if not self.db_key: self.db_key = "#i" % self.dbid elif cdict["key"] and self.key != cdict["key"]: self.key = cdict["key"] - if cdict["keep_log"]: + if cdict.get("keep_log"): self.db_keep_log = cdict["keep_log"] - if cdict["aliases"]: + if cdict.get("aliases"): self.aliases.add(cdict["aliases"]) - if cdict["locks"]: + if cdict.get("locks"): self.locks.add(cdict["locks"]) - if cdict["keep_log"]: + if cdict.get("keep_log"): self.attributes.add("keep_log", cdict["keep_log"]) - if cdict["desc"]: + if cdict.get("desc"): self.attributes.add("desc", cdict["desc"]) def at_channel_creation(self): @@ -61,7 +61,6 @@ class Channel(ChannelDB): """ if hasattr(player, "player"): player = player.player - player = player.dbobj return player in self.db_subscriptions.all() def connect(self, player): @@ -90,9 +89,9 @@ class Channel(ChannelDB): if not disconnect: return False # disconnect - self.db_subscriptions.remove(player.dbobj) + self.db_subscriptions.remove(player) # post-disconnect hook - self.post_leave_channel(player.dbobj) + self.post_leave_channel(player) return True def access(self, accessing_obj, access_type='listen', default=False): diff --git a/src/objects/objects.py b/src/objects/objects.py index 5dd39d9de..597a0fd7c 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -283,11 +283,11 @@ class DefaultObject(ObjectDB): """ return any(self.sessions) - @property - def is_superuser(self): - "Check if user has a player, and if so, if it is a superuser." - return self.db_player and self.db_player.is_superuser \ - and not self.db_player.attributes.get("_quell") + #@property + #def is_superuser(self): + # "Check if user has a player, and if so, if it is a superuser." + # return self.db_player and self.db_player.is_superuser \ + # and not self.db_player.attributes.get("_quell") def contents_get(self, exclude=None): """ @@ -858,32 +858,32 @@ class DefaultObject(ObjectDB): # call's kwargs to override the values set by hooks. cdict = self._createdict updates = [] - if not cdict["key"]: + if not cdict.get("key"): if not self.db_key: self.db_key = "#%i" % self.dbid updates.append("db_key") - elif self.key != cdict["key"]: + elif self.key != cdict.get("key"): updates.append("db_key") self.db_key = cdict["key"] - if cdict["location"] and self.location != cdict["location"]: + if cdict.get("location") and self.location != cdict["location"]: self.db_location = cdict["location"] updates.append("db_location") - if cdict["home"] and self.home != cdict["home"]: + if cdict.get("home") and self.home != cdict["home"]: self.home = cdict["home"] updates.append("db_home") - if cdict["destination"] and self.destination != cdict["destination"]: + if cdict.get("destination") and self.destination != cdict["destination"]: self.destination = cdict["destination"] updates.append("db_destination") if updates: self.save(update_fields=updates) - if cdict["permissions"]: + if cdict.get("permissions"): self.permissions.add(cdict["permissions"]) - if cdict["locks"]: + if cdict.get("locks"): self.locks.add(cdict["locks"]) - if cdict["aliases"]: + if cdict.get("aliases"): self.aliases.add(cdict["aliases"]) - if cdict["location"]: + if cdict.get("location"): cdict["location"].at_object_receive(self, None) self.at_after_move(None) del self._createdict diff --git a/src/players/models.py b/src/players/models.py index d1ae75266..83963967b 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -93,10 +93,6 @@ class PlayerDB(TypedObject, AbstractUser): # Database manager objects = PlayerDBManager() - # caches for quick lookups - _typeclass_paths = settings.PLAYER_TYPECLASS_PATHS - _default_typeclass_path = settings.BASE_PLAYER_TYPECLASS or "src.players.player.Player" - class Meta: app_label = 'players' verbose_name = 'Player' diff --git a/src/players/player.py b/src/players/player.py index 644316ee7..67fe1872c 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -535,9 +535,9 @@ class DefaultPlayer(PlayerDB): # this will only be set if the utils.create_player # function was used to create the object. cdict = self._createdict - if "locks" in cdict: + if cdict.get("locks"): self.locks.add(cdict["locks"]) - if "permissions" in cdict: + if cdict.get("permissions"): permissions = cdict["permissions"] del self._createdict diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index e8c9cd991..2a63d430c 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -443,28 +443,28 @@ class Script(ScriptBase): # set by hooks. cdict = self._createdict updates = [] - if not cdict["key"]: + if not cdict.get("key"): if not self.db_key: self.db_key = "#%i" % self.dbid updates.append("db_key") - elif self.db_key != cdict["db_key"]: + elif self.db_key != cdict["key"]: self.db_key = cdict["key"] updates.append("db_key") - if cdict["interval"] and self.interval != cdict["interval"]: + if cdict.get("interval") and self.interval != cdict["interval"]: self.db_interval = cdict["interval"] updates.append("db_interval") - if cdict["start_delay"] and self.start_delay != cdict["start_delay"]: + if cdict.get("start_delay") and self.start_delay != cdict["start_delay"]: self.db_start_delay = cdict["start_delay"] updates.append("db_start_delay") - if cdict["repeats"] and self.repeats != cdict["repeats"]: + if cdict.get("repeats") and self.repeats != cdict["repeats"]: self.db_repeats = cdict["repeats"] updates.append("db_repeats") - if cdict["persistent"] and self.persistent != cdict["persistent"]: + if cdict.get("persistent") and self.persistent != cdict["persistent"]: self.db_persistent = cdict["persistent"] updates.append("db_persistent") if updates: self.save(update_fields=updates) - if not cdict["autostart"]: + if not cdict.get("autostart"): # don't auto-start the script return diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 79f794165..3fabdd1e5 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -325,6 +325,8 @@ class TypedObject(SharedMemoryModel): sure nothing in the new typeclass clashes with the old one. If you supply a list, only those named attributes will be cleared. + run_start_hooks - trigger the start hooks of the object, as if + it was created for the first time. no_default - if this is active, the swapper will not allow for swapping to a default typeclass in case the given one fails for some reason. Instead the old one diff --git a/src/utils/create.py b/src/utils/create.py index 1e3549051..25363623b 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -23,6 +23,7 @@ Models covered: """ from django.conf import settings from django.db import IntegrityError +from django.utils import timezone from src.utils import logger from src.utils.utils import make_iter, class_from_module, dbid_to_obj @@ -348,7 +349,6 @@ def create_player(key, email, password, if isinstance(typeclass, basestring): # a path is given. Load the actual typeclass. typeclass = class_from_module(typeclass, settings.OBJECT_TYPECLASS_PATHS) - typeclass_path = typeclass.path # setup input for the create command. We use PlayerDB as baseclass # here to give us maximum freedom (the typeclasses will load @@ -362,16 +362,16 @@ def create_player(key, email, password, # this handles a given dbref-relocate to a player. report_to = dbid_to_obj(report_to, _PlayerDB) - # create the correct player object - if is_superuser: - new_player = _PlayerDB.objects.create_superuser(key, email, password) - else: - new_player = _PlayerDB.objects.create_user(key, email, password) - new_player.db_typeclass_path = typeclass_path - # store the call signature for the signal + # create the correct player entity, using the setup from + # base django auth. + now = timezone.now() + email = typeclass.objects.normalize_email(email) + new_player = typeclass(username=key, email=email, + is_staff=is_superuser, is_superuser=is_superuser, + last_login=now, date_joined=now) + new_player.set_password(password) new_player._createdict = {"locks":locks, "permissions":permissions, "report_to":report_to} - # saving will trigger the signal that calls the # at_first_save hook on the typeclass, where the _createdict # can be used. diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 0b6eb8203..8b7957191 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -187,8 +187,8 @@ class SharedMemoryModelBase(ModelBase): for fieldname, field in ((fname, field) for fname, field in attrs.items() if fname.startswith("db_") and type(field).__name__ != "ManyToManyField"): foreignkey = type(field).__name__ == "ForeignKey" - #print fieldname, type(field).__name__, field wrappername = "dbid" if fieldname == "id" else fieldname.replace("db_", "", 1) + #print fieldname, wrappername if wrappername not in attrs: # makes sure not to overload manually created wrappers on the model #print "wrapping %s -> %s" % (fieldname, wrappername) From 70da53569d57cb0e6833a0cb384f2be608411155 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 2 Jan 2015 20:48:34 +0100 Subject: [PATCH 036/250] Removed .typeclass and .dbobj references in codebase. --- contrib/tutorial_world/mob.py | 3 +-- game/gamesrc/objects/examples/object.py | 3 --- game/gamesrc/objects/examples/player.py | 2 -- game/gamesrc/scripts/examples/script.py | 4 --- src/commands/default/building.py | 16 ++++++------ src/commands/default/comms.py | 8 +++--- src/commands/default/general.py | 1 - src/comms/managers.py | 2 +- src/comms/models.py | 5 ++-- src/locks/lockfuncs.py | 2 +- src/objects/admin.py | 1 - src/objects/manager.py | 8 +++--- src/objects/objects.py | 20 ++++++++++----- src/players/admin.py | 2 +- src/players/bots.py | 16 ++++++------ src/players/player.py | 4 --- src/scripts/scripthandler.py | 2 +- src/scripts/tickerhandler.py | 15 +++-------- src/server/oobhandler.py | 25 ------------------ src/typeclasses/attributes.py | 2 +- src/utils/dbserialize.py | 11 +++----- src/utils/spawner.py | 34 +++++++------------------ src/utils/utils.py | 1 - 23 files changed, 60 insertions(+), 127 deletions(-) diff --git a/contrib/tutorial_world/mob.py b/contrib/tutorial_world/mob.py index bbdb4c561..e4a221ce1 100644 --- a/contrib/tutorial_world/mob.py +++ b/contrib/tutorial_world/mob.py @@ -104,7 +104,6 @@ class AttackTimer(Script): "Called every self.interval seconds." if self.obj.db.inactive: return - #print "attack timer: at_repeat", self.dbobj.id, self.ndb.twisted_task, # id(self.ndb.twisted_task) if self.obj.db.roam_mode: self.obj.roam() @@ -382,4 +381,4 @@ class Enemy(Mob): string = self.db.respawn_text if not string: string = "%s fades into existence from out of thin air. It's looking pissed." % self.key - self.location.msg_contents(string) \ No newline at end of file + self.location.msg_contents(string) diff --git a/game/gamesrc/objects/examples/object.py b/game/gamesrc/objects/examples/object.py index 57dbc7024..51cbc4d21 100644 --- a/game/gamesrc/objects/examples/object.py +++ b/game/gamesrc/objects/examples/object.py @@ -44,10 +44,7 @@ class Object(DefaultObject): aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class - typeclass (Object, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. date_created (string) - time stamp of object creation permissions (list of strings) - list of permission strings diff --git a/game/gamesrc/objects/examples/player.py b/game/gamesrc/objects/examples/player.py index 339e0994e..f587a71f2 100644 --- a/game/gamesrc/objects/examples/player.py +++ b/game/gamesrc/objects/examples/player.py @@ -39,8 +39,6 @@ class Player(DefaultPlayer): name (string)- wrapper for user.username aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Player, read-only) - link to database model. dbobj.typeclass points back to this class - typeclass (Player, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch. date_created (string) - time stamp of object creation permissions (list of strings) - list of permission strings diff --git a/game/gamesrc/scripts/examples/script.py b/game/gamesrc/scripts/examples/script.py index 05b670fb9..7e310f5a1 100644 --- a/game/gamesrc/scripts/examples/script.py +++ b/game/gamesrc/scripts/examples/script.py @@ -35,10 +35,6 @@ class ExampleScript(BaseScript): aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Object, read-only) - link to database model. dbobj.typeclass - points back to this class - typeclass (Object, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. date_created (string) - time stamp of object creation permissions (list of strings) - list of permission strings diff --git a/src/commands/default/building.py b/src/commands/default/building.py index b1fcdd3c7..837864fff 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -150,7 +150,7 @@ class CmdSetObjAlias(MuxCommand): old_aliases = obj.aliases.all() if old_aliases: caller.msg("Cleared aliases from %s: %s" % (obj.key, ", ".join(old_aliases))) - obj.dbobj.aliases.clear() + obj.aliases.clear() else: caller.msg("No aliases to clear.") return @@ -447,10 +447,10 @@ class CmdCreate(ObjManipCommand): continue if aliases: string = "You create a new %s: %s (aliases: %s)." - string = string % (obj.typeclass.typename, obj.name, ", ".join(aliases)) + string = string % (obj.typename, obj.name, ", ".join(aliases)) else: string = "You create a new %s: %s." - string = string % (obj.typeclass.typename, obj.name) + string = string % (obj.typename, obj.name) # set a default desc if not obj.db.desc: obj.db.desc = "You see nothing special." @@ -1401,7 +1401,7 @@ class CmdTypeclass(MuxCommand): # current one instead. if hasattr(obj, "typeclass"): string = "%s's current typeclass is '%s' (%s)." % (obj.name, - obj.typeclass.typename, obj.typeclass.path) + obj.typename, obj.path) else: string = "%s is not a typed object." % obj.name caller.msg(string) @@ -1427,7 +1427,7 @@ class CmdTypeclass(MuxCommand): ok = obj.swap_typeclass(typeclass, clean_attributes=reset) if ok: if is_same: - string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.typeclass.path) + string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path) else: string = "%s changed typeclass from %s to %s.\n" % (obj.name, old_typeclass_path, @@ -1677,7 +1677,7 @@ class CmdExamine(ObjManipCommand): string += "\n{wPlayer Perms{n: %s" % (", ".join(perms)) if obj.player.attributes.has("_quell"): string += " {r(quelled){n" - string += "\n{wTypeclass{n: %s (%s)" % (obj.typeclass.typename, + string += "\n{wTypeclass{n: %s (%s)" % (obj.typename, obj.typeclass_path) if hasattr(obj, "location"): string += "\n{wLocation{n: %s" % obj.location @@ -1928,7 +1928,7 @@ class CmdFind(MuxCommand): else: result=result[0] string += "\n{g %s(%s) - %s{n" % (result.key, result.dbref, - result.typeclass.path) + result.path) else: # Not a player/dbref search but a wider search; build a queryset. # Searchs for key and aliases @@ -1946,7 +1946,7 @@ class CmdFind(MuxCommand): if nresults: # convert result to typeclasses. - results = [result.typeclass for result in results] + results = [result for result in results] if "room" in switches: results = [obj for obj in results if inherits_from(obj, ROOM_TYPECLASS)] if "exit" in switches: diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index a6f2d0d40..2f67e661b 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -792,7 +792,7 @@ class CmdIRC2Chan(MuxCommand): if 'list' in self.switches: # show all connections - ircbots = [bot.typeclass for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")] + ircbots = [bot for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")] if ircbots: from src.utils.evtable import EvTable table = EvTable("{wdbid{n", "{wbotname{n", "{wev-channel{n", "{wirc-channel{n", maxwidth=78) @@ -842,7 +842,7 @@ class CmdIRC2Chan(MuxCommand): bot = PlayerDB.objects.filter(username__iexact=botname) if bot: # re-use an existing bot - bot = bot[0].typeclass + bot = bot[0] if not bot.is_bot: self.msg("Player '%s' already exists and is not a bot." % botname) return @@ -901,7 +901,7 @@ class CmdRSS2Chan(MuxCommand): if 'list' in self.switches: # show all connections - rssbots = [bot.typeclass for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")] + rssbots = [bot for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")] if rssbots: from src.utils.evtable import EvTable table = EvTable("{wdbid{n", "{wupdate rate{n", "{wev-channel", "{wRSS feed URL{n", border="cells", maxwidth=78) @@ -938,7 +938,7 @@ class CmdRSS2Chan(MuxCommand): bot = PlayerDB.objects.filter(username__iexact=botname) if bot: # re-use existing bot - bot = bot[0].typeclass + bot = bot[0] if not bot.is_bot: self.msg("Player '%s' already exists and is not a bot." % botname) return diff --git a/src/commands/default/general.py b/src/commands/default/general.py index 74303bb22..7ffcd8e14 100644 --- a/src/commands/default/general.py +++ b/src/commands/default/general.py @@ -155,7 +155,6 @@ class CmdNick(MuxCommand): string = "" for switch in switches: oldnick = caller.nicks.get(key=nick, category=switch) - #oldnick = Nick.objects.filter(db_obj=caller.dbobj, db_nick__iexact=nick, db_type__iexact=switch) if not real: # removal of nick if oldnick: diff --git a/src/comms/managers.py b/src/comms/managers.py index cdab16ae2..4a2b08f11 100644 --- a/src/comms/managers.py +++ b/src/comms/managers.py @@ -300,7 +300,7 @@ class ChannelDBManager(TypedObjectManager): """ Return all channels a given player is subscribed to """ - return player.dbobj.subscription_set.all() + return player.__dbclass__.subscription_set.all() @returns_typeclass_list def channel_search(self, ostring, exact=True): diff --git a/src/comms/models.py b/src/comms/models.py index 21d4369b0..6c274e540 100644 --- a/src/comms/models.py +++ b/src/comms/models.py @@ -223,8 +223,9 @@ class Msg(SharedMemoryModel): def __channels_set(self, value): """ Setter. Allows for self.channels = value. - Requires a channel to be added.""" - for val in (v.dbobj for v in make_iter(value) if v): + Requires a channel to be added. + """ + for val in (v for v in make_iter(value) if v): self.db_receivers_channels.add(val) #@channels.deleter diff --git a/src/locks/lockfuncs.py b/src/locks/lockfuncs.py index 461dc6daf..d5d76cf60 100644 --- a/src/locks/lockfuncs.py +++ b/src/locks/lockfuncs.py @@ -125,7 +125,7 @@ def self(accessing_obj, accessed_obj, *args, **kwargs): This can be used to lock specifically only to the same object that the lock is defined on. """ - return accessing_obj.typeclass == accessed_obj.typeclass + return accessing_obj == accessed_obj def perm(accessing_obj, accessed_obj, *args, **kwargs): diff --git a/src/objects/admin.py b/src/objects/admin.py index 078700cc1..207240416 100644 --- a/src/objects/admin.py +++ b/src/objects/admin.py @@ -119,7 +119,6 @@ class ObjectDBAdmin(admin.ModelAdmin): obj.save() if not change: # adding a new object - obj = obj.typeclass obj.basetype_setup() obj.basetype_posthook_setup() obj.at_object_creation() diff --git a/src/objects/manager.py b/src/objects/manager.py index 2be80ac06..eb530febb 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -156,8 +156,6 @@ class ObjectDBManager(TypedObjectManager): if isinstance(property_name, basestring): if not property_name.startswith('db_'): property_name = "db_%s" % property_name - if hasattr(property_value, 'dbobj'): - property_value = property_value.dbobj querykwargs = {property_name:property_value} cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() @@ -299,7 +297,7 @@ class ObjectDBManager(TypedObjectManager): if candidates: # Convenience check to make sure candidates are really dbobjs - candidates = [cand.dbobj for cand in make_iter(candidates) if cand] + candidates = [cand for cand in make_iter(candidates) if cand] if typeclass: candidates = [cand for cand in candidates if _GA(cand, "db_typeclass_path") in typeclass] @@ -309,7 +307,7 @@ class ObjectDBManager(TypedObjectManager): # Easiest case - dbref matching (always exact) dbref_match = self.dbref_search(dbref) if dbref_match: - if not candidates or dbref_match.dbobj in candidates: + if not candidates or dbref_match in candidates: return [dbref_match] else: return [] @@ -402,7 +400,7 @@ class ObjectDBManager(TypedObjectManager): # copy over all scripts, if any for script in original_object.scripts.all(): - ScriptDB.objects.copy_script(script, new_obj=new_object.dbobj) + ScriptDB.objects.copy_script(script, new_obj=new_object) return new_object diff --git a/src/objects/objects.py b/src/objects/objects.py index 597a0fd7c..60bdd6b94 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -115,10 +115,6 @@ class DefaultObject(ObjectDB): aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Object, read-only) - link to database model. dbobj.typeclass - points back to this class - typeclass (Object, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. date_created (string) - time stamp of object creation permissions (list of strings) - list of permission strings @@ -886,6 +882,16 @@ class DefaultObject(ObjectDB): if cdict.get("location"): cdict["location"].at_object_receive(self, None) self.at_after_move(None) + + if cdict.get("attributes"): + # this should be a dict of attrname:value + keys, values = cdict["attributes"].keys(), cdict["attributes"].values() + self.attributes.batch_add(keys, values) + if cdict.get("nattributes"): + # this should be a dict of nattrname:value + for key, value in cdict["nattributes"].items(): + self.nattributes.add(key, value) + del self._createdict self.basetype_posthook_setup() @@ -1437,8 +1443,8 @@ class DefaultExit(DefaultObject): "get:false()"])) # noone can pick up the exit # an exit should have a destination (this is replaced at creation time) - if self.dbobj.location: - self.destination = self.dbobj.location + if self.location: + self.destination = self.location def at_cmdset_get(self, **kwargs): """ @@ -1452,7 +1458,7 @@ class DefaultExit(DefaultObject): if "force_init" in kwargs or not self.cmdset.has_cmdset("_exitset", must_be_default=True): # we are resetting, or no exit-cmdset was set. Create one dynamically. - self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False) + self.cmdset.add_default(self.create_exit_cmdset(self), permanent=False) # this and other hooks are what usually can be modified safely. diff --git a/src/players/admin.py b/src/players/admin.py index d01646cd6..a456ed989 100644 --- a/src/players/admin.py +++ b/src/players/admin.py @@ -204,7 +204,7 @@ class PlayerDBAdmin(BaseUserAdmin): obj.save() if not change: #calling hooks for new player - ply = obj.typeclass + ply = obj ply.basetype_setup() ply.at_player_creation() diff --git a/src/players/bots.py b/src/players/bots.py index 229875815..1190c577b 100644 --- a/src/players/bots.py +++ b/src/players/bots.py @@ -76,7 +76,7 @@ class CmdBotListen(Command): key = "bot_data_in" def func(self): "Relay to typeclass" - self.obj.typeclass.execute_cmd(self.args.strip(), sessid=self.sessid) + self.obj.execute_cmd(self.args.strip(), sessid=self.sessid) class BotCmdSet(CmdSet): "Holds the BotListen command" @@ -196,9 +196,9 @@ class IRCBot(Bot): # cache channel lookup self.ndb.ev_channel = self.db.ev_channel if "from_channel" in kwargs and text and self.ndb.ev_channel.dbid == kwargs["from_channel"]: - if "from_obj" not in kwargs or kwargs["from_obj"] != [self.dbobj.id]: + if "from_obj" not in kwargs or kwargs["from_obj"] != [self.id]: text = "bot_data_out %s" % text - self.dbobj.msg(text=text) + self.msg(text=text) def execute_cmd(self, text=None, sessid=None): """ @@ -209,7 +209,7 @@ class IRCBot(Bot): # cache channel lookup self.ndb.ev_channel = self.db.ev_channel if self.ndb.ev_channel: - self.ndb.ev_channel.msg(text, senders=self.dbobj.id) + self.ndb.ev_channel.msg(text, senders=self.id) # RSS @@ -258,7 +258,7 @@ class RSSBot(Bot): # cache channel lookup self.ndb.ev_channel = self.db.ev_channel if self.ndb.ev_channel: - self.ndb.ev_channel.msg(text, senders=self.dbobj.id) + self.ndb.ev_channel.msg(text, senders=self.id) class IMC2Bot(Bot): """ @@ -318,9 +318,9 @@ class IMC2Bot(Bot): # cache channel lookup self.ndb.ev_channel = self.db.ev_channel if "from_channel" in kwargs and text and self.ndb.ev_channel.dbid == kwargs["from_channel"]: - if "from_obj" not in kwargs or kwargs["from_obj"] != [self.dbobj.id]: + if "from_obj" not in kwargs or kwargs["from_obj"] != [self.id]: text = "bot_data_out %s" % text - self.dbobj.msg(text=text) + self.msg(text=text) def execute_cmd(self, text=None, sessid=None): """ @@ -330,5 +330,5 @@ class IMC2Bot(Bot): # cache channel lookup self.ndb.ev_channel = self.db.ev_channel if self.ndb.ev_channel: - self.ndb.ev_channel.msg(text, senders=self.dbobj.id) + self.ndb.ev_channel.msg(text, senders=self.id) diff --git a/src/players/player.py b/src/players/player.py index 67fe1872c..4926b2f79 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -55,10 +55,6 @@ class DefaultPlayer(PlayerDB): aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Player, read-only) - link to database model. dbobj.typeclass - points back to this class - typeclass (Player, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. date_created (string) - time stamp of object creation permissions (list of strings) - list of permission strings diff --git a/src/scripts/scripthandler.py b/src/scripts/scripthandler.py index 796d9564e..429397d88 100644 --- a/src/scripts/scripthandler.py +++ b/src/scripts/scripthandler.py @@ -58,7 +58,7 @@ class ScriptHandler(object): definition) autostart - start the script upon adding it """ - if self.obj.dbobj.__class__.__name__ == "PlayerDB": + if self.obj.__class__.__name__ == "PlayerDB": # we add to a Player, not an Object script = create.create_script(scriptclass, key=key, player=self.obj, autostart=autostart) diff --git a/src/scripts/tickerhandler.py b/src/scripts/tickerhandler.py index cf3421a9b..86234575c 100644 --- a/src/scripts/tickerhandler.py +++ b/src/scripts/tickerhandler.py @@ -202,19 +202,10 @@ class TickerHandler(object): is a boolean True if obj was a database object, False otherwise. """ - try: - obj = obj.typeclass - except AttributeError: - pass - dbobj = None - try: - dbobj = obj.dbobj - except AttributeError: - pass - isdb = True - if dbobj: + if hasattr(obj, "db_key"): # create a store_key using the database representation - objkey = pack_dbobj(dbobj) + objkey = pack_dbobj(obj) + isdb = True else: # non-db object, look for a property "key" on it, otherwise # use its memory location. diff --git a/src/server/oobhandler.py b/src/server/oobhandler.py index 1dc4aca88..423840576 100644 --- a/src/server/oobhandler.py +++ b/src/server/oobhandler.py @@ -83,10 +83,6 @@ class TrackerHandler(object): This is initiated and stored on the object as a property _trackerhandler. """ - try: - obj = obj.dbobj - except AttributeError: - pass self.obj = obj self.ntrackers = 0 # initiate store only with valid on-object fieldnames @@ -194,10 +190,6 @@ class ReportAttributeTracker(TrackerBase): def update(self, new_value, *args, **kwargs): "Called by cache when attribute's db_value field updates" - try: - new_value = new_value.dbobj - except AttributeError: - new_value = to_str(new_value, force_string=True) kwargs[self.attrname] = new_value # this is a wrapper call for sending oob data back to session self.oobhandler.msg(self.sessid, "report", *args, **kwargs) @@ -282,11 +274,6 @@ class OOBHandler(object): named as propname, this will be used as the property name when assigning the OOB to obj, otherwise tracker_key is used as the property name. """ - try: - obj = obj.dbobj - except AttributeError: - pass - if not "_trackerhandler" in _GA(obj, "__dict__"): # assign trackerhandler to object _SA(obj, "_trackerhandler", TrackerHandler(obj)) @@ -305,10 +292,6 @@ class OOBHandler(object): Remove the OOB from obj. If oob implements an at_delete hook, this will be called with args, kwargs """ - try: - obj = obj.dbobj - except AttributeError: - pass try: # call at_remove hook on the trackerclass _GA(obj, "_trackerhandler").remove(propname, trackerclass, *args, **kwargs) @@ -348,10 +331,6 @@ class OOBHandler(object): Object and name in a way the Attribute expects. """ # get the attribute object if we can - try: - attrobj = obj.dbobj - except AttributeError: - pass attrobj = obj.attributes.get(attr_name, return_obj=True) #print "track_attribute attrobj:", attrobj, id(attrobj) if attrobj: @@ -361,10 +340,6 @@ class OOBHandler(object): """ Shortcut for deactivating tracking for a given attribute. """ - try: - obj = obj.dbobj - except AttributeError: - pass attrobj = obj.attributes.get(attr_name, return_obj=True) if attrobj: self._untrack(attrobj, sessid, "db_value", trackerclass, attr_name) diff --git a/src/typeclasses/attributes.py b/src/typeclasses/attributes.py index 38783400d..ce06327ee 100644 --- a/src/typeclasses/attributes.py +++ b/src/typeclasses/attributes.py @@ -75,7 +75,7 @@ class Attribute(SharedMemoryModel): db_model = models.CharField( 'model', max_length=32, db_index=True, blank=True, null=True, help_text="Which model of object this attribute is attached to (A " - "natural key like objects.dbobject). You should not change " + "natural key like 'objects.dbobject'). You should not change " "this value unless you know what you are doing.") # subclass of Attribute (None or nick) db_attrtype = models.CharField( diff --git a/src/utils/dbserialize.py b/src/utils/dbserialize.py index bc4d29295..143e3c7f9 100644 --- a/src/utils/dbserialize.py +++ b/src/utils/dbserialize.py @@ -42,7 +42,6 @@ _GA = object.__getattribute__ _SA = object.__setattr__ _FROM_MODEL_MAP = None _TO_MODEL_MAP = None -_TO_TYPECLASS = lambda o: hasattr(o, 'typeclass') and o.typeclass or o _IS_PACKED_DBOBJ = lambda o: type(o) == tuple and len(o) == 4 and o[0] == '__packed_dbobj__' if uses_database("mysql") and ServerConfig.objects.get_mysql_db_version() < '5.6.4': # mysql <5.6.4 don't support millisecond precision @@ -214,7 +213,7 @@ def pack_dbobj(item): ("__packed_dbobj__", key, creation_time, id) """ _init_globals() - obj = hasattr(item, 'dbobj') and item.dbobj or item + obj = item natural_key = _FROM_MODEL_MAP[hasattr(obj, "id") and hasattr(obj, "db_date_created") and hasattr(obj, '__dbclass__') and obj.__dbclass__.__name__.lower()] # build the internal representation as a tuple @@ -232,16 +231,12 @@ def unpack_dbobj(item): """ _init_globals() try: - obj = item[3] and _TO_TYPECLASS(_TO_MODEL_MAP[item[1]].objects.get(id=item[3])) + obj = item[3] and _TO_MODEL_MAP[item[1]].objects.get(id=item[3]) except ObjectDoesNotExist: return None # even if we got back a match, check the sanity of the date (some # databases may 're-use' the id) - try: - dbobj = obj.dbobj - except AttributeError: - dbobj = obj - return _TO_DATESTRING(dbobj) == item[2] and obj or None + return _TO_DATESTRING(obj) == item[2] and obj or None # # Access methods diff --git a/src/utils/spawner.py b/src/utils/spawner.py index cddaab274..659428a65 100644 --- a/src/utils/spawner.py +++ b/src/utils/spawner.py @@ -137,33 +137,17 @@ def _batch_create_object(*objparams): #dbobjs = _ObjectDB.objects.bulk_create(dbobjs) objs = [] - for iobj, dbobj in enumerate(dbobjs): + for iobj, obj in enumerate(dbobjs): # call all setup hooks on each object objparam = objparams[iobj] - obj = dbobj.typeclass # this saves dbobj if not done already - obj.basetype_setup() - obj.at_object_creation() - - if objparam[1]: - # permissions - obj.permissions.add(objparam[1]) - if objparam[2]: - # locks - obj.locks.add(objparam[2]) - if objparam[3]: - # aliases - obj.aliases.add(objparam[3]) - if objparam[4]: - # nattributes - for key, value in objparam[4].items(): - obj.nattributes.add(key, value) - if objparam[5]: - # attributes - keys, values = objparam[5].keys(), objparam[5].values() - obj.attributes.batch_add(keys, values) - - obj.basetype_posthook_setup() - objs.append(obj) + # setup + obj._createdict = {"pernmissions": objparam[1], + "locks": objparam[2], + "aliases": objparam[3], + "attributes": objparam[4], + "nattributes": objparam[5]} + # this triggers all hooks + obj.save() return objs diff --git a/src/utils/utils.py b/src/utils/utils.py index 89c54bab8..017bbcd90 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -584,7 +584,6 @@ def clean_object_caches(obj): #print "recaching:", obj if not obj: return - obj = hasattr(obj, "dbobj") and obj.dbobj or obj # contents cache try: _SA(obj, "_contents_cache", None) From 71b6600d8715d6e8c8cdabc05c2d97fbc68f2421 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 2 Jan 2015 21:41:29 +0100 Subject: [PATCH 037/250] Changed .typename to properly handle proxy classes. --- src/commands/default/tests.py | 2 +- src/comms/managers.py | 2 +- src/typeclasses/models.py | 2 +- src/utils/idmapper/base.py | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/default/tests.py b/src/commands/default/tests.py index 664e5be88..75336cdf0 100644 --- a/src/commands/default/tests.py +++ b/src/commands/default/tests.py @@ -232,7 +232,7 @@ from src.commands.default import building class TestBuilding(CommandTest): CID = 6 def test_cmds(self): - self.call(building.CmdCreate(), "/drop TestObj1", "You create a new Object: TestObj1.") + self.call(building.CmdCreate(), "/drop TestObj1", "You create a new DefaultObject: TestObj1.") self.call(building.CmdExamine(), "TestObj1", "Name/key: TestObj1") self.call(building.CmdSetObjAlias(), "TestObj1 = TestObj1b","Alias(es) for 'TestObj1' set to testobj1b.") self.call(building.CmdCopy(), "TestObj1 = TestObj2;TestObj2b, TestObj3;TestObj3b", "Copied TestObj1 to 'TestObj3' (aliases: ['TestObj3b']") diff --git a/src/comms/managers.py b/src/comms/managers.py index 4a2b08f11..fe00a8185 100644 --- a/src/comms/managers.py +++ b/src/comms/managers.py @@ -300,7 +300,7 @@ class ChannelDBManager(TypedObjectManager): """ Return all channels a given player is subscribed to """ - return player.__dbclass__.subscription_set.all() + return player.player.subscription_set.all() @returns_typeclass_list def channel_search(self, ostring, exact=True): diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 3fabdd1e5..8f2ebe23a 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -94,7 +94,7 @@ class TypeclassBase(SharedMemoryModelBase): """ # storage of stats - attrs["typename"] = cls.__name__ + attrs["typename"] = name#cls.__name__ attrs["path"] = "%s.%s" % (attrs["__module__"], name) # typeclass proxy setup diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 8b7957191..0c82f7118 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -187,6 +187,7 @@ class SharedMemoryModelBase(ModelBase): for fieldname, field in ((fname, field) for fname, field in attrs.items() if fname.startswith("db_") and type(field).__name__ != "ManyToManyField"): foreignkey = type(field).__name__ == "ForeignKey" + #print fieldname, type(field).__name__, field wrappername = "dbid" if fieldname == "id" else fieldname.replace("db_", "", 1) #print fieldname, wrappername if wrappername not in attrs: From 2782e03478b80af9ce4b4695e83e9fd8ecdd203d Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 3 Jan 2015 15:08:06 +0100 Subject: [PATCH 038/250] Made unittest suite finish without errors using new proxy system. --- contrib/README | 4 --- src/commands/default/building.py | 41 ++++++++++++++++-------------- src/commands/default/muxcommand.py | 4 +-- src/commands/default/tests.py | 10 ++++---- src/comms/comms.py | 1 - src/comms/managers.py | 31 ++++++++++------------ src/objects/objects.py | 10 ++++---- src/tests/test_scripts_models.py | 8 ++---- src/typeclasses/models.py | 24 +++++++++++++++++ 9 files changed, 73 insertions(+), 60 deletions(-) diff --git a/contrib/README b/contrib/README index 57bed4319..5983b6111 100644 --- a/contrib/README +++ b/contrib/README @@ -47,7 +47,3 @@ things you want from here into game/gamesrc and change them there. Evennia's ooc mode. Works well with the menu login contrib and is intended as a starting point for building a more full-featured character creation system. - -* Evlang (Griatch 2012) - A heavily restricted version of Python for use - as a "softcode" language by Players in-game. Contains a complete - system with examples of objects and commands for coding. diff --git a/src/commands/default/building.py b/src/commands/default/building.py index 837864fff..0bcda6900 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -1353,6 +1353,7 @@ class CmdTypeclass(MuxCommand): @typclass[/switch] [= ] @type '' @parent '' + @swap - this is a shorthand for using /force/reset flags. Switch: reset - clean out *all* the attributes on the object - @@ -1407,6 +1408,10 @@ class CmdTypeclass(MuxCommand): caller.msg(string) return + if self.cmdstring == "@swap": + self.switches.append("force") + self.switches.append("reset") + # we have an =, a typeclass was supplied. typeclass = self.rhs @@ -1414,7 +1419,7 @@ class CmdTypeclass(MuxCommand): caller.msg("You are not allowed to do that.") return - if not hasattr(obj, 'swap_typeclass') or not hasattr(obj, 'typeclass'): + if not hasattr(obj, 'swap_typeclass'): caller.msg("This object cannot have a type at all!") return @@ -1424,25 +1429,23 @@ class CmdTypeclass(MuxCommand): else: reset = "reset" in self.switches old_typeclass_path = obj.typeclass_path - ok = obj.swap_typeclass(typeclass, clean_attributes=reset) - if ok: - if is_same: - string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path) - else: - string = "%s changed typeclass from %s to %s.\n" % (obj.name, - old_typeclass_path, - obj.typeclass_path) - string += "Creation hooks were run." - if reset: - string += " All old attributes where deleted before the swap." - else: - string += " Note that the typeclassed object could have ended up with a mixture of old" - string += "\nand new attributes. Use /reset to remove old attributes if you don't want this." + + # we let this raise exception if needed + obj.swap_typeclass(typeclass, clean_attributes=reset) + + if is_same: + string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path) else: - string = obj.typeclass_last_errmsg - string += "\nCould not swap '%s' (%s) to typeclass '%s'." % (obj.name, - old_typeclass_path, - typeclass) + string = "%s changed typeclass from %s to %s.\n" % (obj.name, + old_typeclass_path, + obj.typeclass_path) + string += "Creation hooks were run." + if reset: + string += " All old attributes where deleted before the swap." + else: + string += " Note that the typeclassed object could have ended up with a mixture of old" + string += "\nand new attributes. Use /reset to remove old attributes if you don't want this." + caller.msg(string) diff --git a/src/commands/default/muxcommand.py b/src/commands/default/muxcommand.py index 965189d2a..b41a6d1d1 100644 --- a/src/commands/default/muxcommand.py +++ b/src/commands/default/muxcommand.py @@ -183,11 +183,11 @@ class MuxPlayerCommand(MuxCommand): """ super(MuxPlayerCommand, self).parse() - if utils.inherits_from(self.caller, "src.objects.objects.Object"): + if utils.inherits_from(self.caller, "src.objects.objects.DefaultObject"): # caller is an Object/Character self.character = self.caller self.caller = self.caller.player - elif utils.inherits_from(self.caller, "src.players.players.Player"): + elif utils.inherits_from(self.caller, "src.players.players.DefaultPlayer"): # caller was already a Player self.character = self.caller.get_puppet(self.sessid) else: diff --git a/src/commands/default/tests.py b/src/commands/default/tests.py index 75336cdf0..c22d2418f 100644 --- a/src/commands/default/tests.py +++ b/src/commands/default/tests.py @@ -159,7 +159,7 @@ class TestGeneral(CommandTest): self.call(general.CmdLook(), "here", "Room1\n room_desc") self.call(general.CmdHome(), "", "You are already home") self.call(general.CmdInventory(), "", "You are not carrying anything.") - self.call(general.CmdPose(), "looks around", "Char1 looks around") + self.call(general.CmdPose(), "looks around", "") # TODO-check this self.call(general.CmdHome(), "", "You are already home") self.call(general.CmdNick(), "testalias = testaliasedstring1", "Nick set:") self.call(general.CmdNick(), "/player testalias = testaliasedstring2", "Nick set:") @@ -251,8 +251,8 @@ class TestBuilding(CommandTest): self.call(building.CmdUnLink(), "TestExit1", "Former exit TestExit1 no longer links anywhere.") self.call(building.CmdSetHome(), "Obj6 = Room6b", "Obj6's home location was changed from Room6") self.call(building.CmdListCmdSets(), "", ":") - self.call(building.CmdTypeclass(), "Obj6 = src.objects.objects.Exit", - "Obj6 changed typeclass from src.commands.default.tests.TestObjectClass to src.objects.objects.Exit") + self.call(building.CmdTypeclass(), "Obj6 = src.objects.objects.DefaultExit", + "Obj6 changed typeclass from src.commands.default.tests.TestObjectClass to src.objects.objects.DefaultExit") self.call(building.CmdLock(), "Obj6 = test:perm(Immortals)", "Added lock 'test:perm(Immortals)' to Obj6.") self.call(building.CmdFind(), "TestRoom1", "One Match") self.call(building.CmdScript(), "Obj6 = src.scripts.scripts.Script", "Script src.scripts.scripts.Script successfully added") @@ -271,11 +271,11 @@ class TestComms(CommandTest): self.call(comms.CmdAllCom(), "", "Available channels (use comlist,addcom and delcom to manage") self.call(comms.CmdClock(), "testchan=send:all()", "Lock(s) applied. Current locks on testchan:") self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.") - self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message") + self.call(comms.CmdCemit(), "testchan = Test Message", "Sent to channel testchan: Test Message") self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestPlayer7") self.call(comms.CmdPage(), "TestPlayer7b = Test", "TestPlayer7b is offline. They will see your message if they list their pages later.|You paged TestPlayer7b with: 'Test'.") self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] = [:reason]") # noone else connected to boot - self.call(comms.CmdCdestroy(), "testchan" ,"[testchan] TestPlayer7: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.") + self.call(comms.CmdCdestroy(), "testchan" ,"Channel 'testchan' was destroyed.") from src.commands.default import batchprocess diff --git a/src/comms/comms.py b/src/comms/comms.py index f6e7da869..1b59fdba9 100644 --- a/src/comms/comms.py +++ b/src/comms/comms.py @@ -257,7 +257,6 @@ class Channel(ChannelDB): """ # get all players connected to this channel and send to them for player in self.db_subscriptions.all(): - player = player try: # note our addition of the from_channel keyword here. This could be checked # by a custom player.msg() to treat channel-receives differently. diff --git a/src/comms/managers.py b/src/comms/managers.py index fe00a8185..b4dd8a374 100644 --- a/src/comms/managers.py +++ b/src/comms/managers.py @@ -53,25 +53,20 @@ def identify_object(inp): from src.objects.models import ObjectDB as _ObjectDB if not _ChannelDB: from src.comms.models import ChannelDB as _ChannelDB + if not inp: return inp, None - # try to identify the type - try: - obj = _GA(inp, "dbobj") # this works for all typeclassed entities - except AttributeError: - obj = inp - typ = type(obj) - if typ == _PlayerDB: - return obj, "player" - elif typ == _ObjectDB: - return obj, "object" - elif typ == _ChannelDB: - return obj, "channel" - elif dbref(obj): - return dbref(obj), "dbref" - elif typ == basestring: - return obj, "string" - return obj, None # Something else + if isinstance(inp, basestring): + return inp, "string" + elif inp.is_typeclass(_PlayerDB, exact=False): + return inp, "player" + elif inp.is_typeclass(_ObjectDB, exact=False): + return inp, "object" + elif inp.is_typeclass(_ChannelDB, exact=False): + return inp, "channel" + elif dbref(inp): + return dbref(inp), "dbref" + return inp, None # something else def to_object(inp, objtype='player'): @@ -300,7 +295,7 @@ class ChannelDBManager(TypedObjectManager): """ Return all channels a given player is subscribed to """ - return player.player.subscription_set.all() + return player.subscription_set.all() @returns_typeclass_list def channel_search(self, ostring, exact=True): diff --git a/src/objects/objects.py b/src/objects/objects.py index 60bdd6b94..651256adc 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -279,11 +279,11 @@ class DefaultObject(ObjectDB): """ return any(self.sessions) - #@property - #def is_superuser(self): - # "Check if user has a player, and if so, if it is a superuser." - # return self.db_player and self.db_player.is_superuser \ - # and not self.db_player.attributes.get("_quell") + @property + def is_superuser(self): + "Check if user has a player, and if so, if it is a superuser." + return self.db_player and self.db_player.is_superuser \ + and not self.db_player.attributes.get("_quell") def contents_get(self, exclude=None): """ diff --git a/src/tests/test_scripts_models.py b/src/tests/test_scripts_models.py index 43f7ca27a..75b500dc0 100644 --- a/src/tests/test_scripts_models.py +++ b/src/tests/test_scripts_models.py @@ -1,9 +1,5 @@ -try: - # this is an optimized version only available in later Django versions - from django.utils.unittest import TestCase -except ImportError: - # if the first fails, we use the old version - from django.test import TestCase +# this is an optimized version only available in later Django versions +from django.utils.unittest import TestCase from src.scripts.models import ScriptDB, ObjectDoesNotExist from src.utils.create import create_script diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 8f2ebe23a..b092dd2ce 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -299,6 +299,30 @@ class TypedObject(SharedMemoryModel): # Object manipulation methods # + def is_typeclass(self, typeclass, exact=True): + """ + Returns true if this object has this type OR has a typeclass + which is an subclass of the given typeclass. This operates on + the actually loaded typeclass (this is important since a + failing typeclass may instead have its default currently + loaded) typeclass - can be a class object or the python path + to such an object to match against. + + typeclass - a class or the full python path to the class + exact - returns true only + if the object's type is exactly this typeclass, ignoring + parents. + """ + if not isinstance(typeclass, basestring): + typeclass = typeclass.path + + if exact: + return typeclass == self.path + else: + # check parent chain + selfpath = self.path + return any(cls for cls in self.__class__.mro() if cls.path == selfpath) + def swap_typeclass(self, new_typeclass, clean_attributes=False, run_start_hooks=True, no_default=True): """ From bb5b2b6fd89d21b60e39aefb545aa89eaa2e8b68 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 3 Jan 2015 15:22:27 +0100 Subject: [PATCH 039/250] Fixed bug in class_import --- src/utils/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/utils.py b/src/utils/utils.py index 017bbcd90..f91758030 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -948,6 +948,7 @@ def class_from_module(path, defaultpaths=None): if not str(ex).startswith("No module named"): exc = sys.exc_info() raise exc[1], None, exc[2] + continue try: cls = getattr(mod, clsname) break From 2e3a19840e46f62e3d877afd1373a1f325acab2c Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 3 Jan 2015 17:48:32 +0100 Subject: [PATCH 040/250] Made tutorialworld install cleanly again. --- contrib/tutorial_world/objects.py | 6 +++--- contrib/tutorial_world/rooms.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/tutorial_world/objects.py b/contrib/tutorial_world/objects.py index 1e7561ff9..d84377b9e 100644 --- a/contrib/tutorial_world/objects.py +++ b/contrib/tutorial_world/objects.py @@ -23,7 +23,7 @@ import time import random from ev import create_object -from ev import Object, Exit, Command, CmdSet, Script +from ev import DefaultObject, DefaultExit, Command, CmdSet, Script #------------------------------------------------------------ # @@ -42,7 +42,7 @@ from ev import Object, Exit, Command, CmdSet, Script #------------------------------------------------------------ -class TutorialObject(Object): +class TutorialObject(DefaultObject): """ This is the baseclass for all objects in the tutorial. """ @@ -558,7 +558,7 @@ class CmdSetCrumblingWall(CmdSet): self.add(CmdPressButton()) -class CrumblingWall(TutorialObject, Exit): +class CrumblingWall(TutorialObject, DefaultExit): """ The CrumblingWall can be examined in various ways, but only if a lit light source is in the room. The traversal diff --git a/contrib/tutorial_world/rooms.py b/contrib/tutorial_world/rooms.py index 514124a47..753a19919 100644 --- a/contrib/tutorial_world/rooms.py +++ b/contrib/tutorial_world/rooms.py @@ -5,7 +5,7 @@ Room Typeclasses for the TutorialWorld. """ import random -from ev import CmdSet, Script, Command, Room +from ev import CmdSet, Script, Command, DefaultRoom from ev import utils, create_object, search_object from contrib.tutorial_world import scripts as tut_scripts from contrib.tutorial_world.objects import LightSource, TutorialObject @@ -67,7 +67,7 @@ class TutorialRoomCmdSet(CmdSet): self.add(CmdTutorial()) -class TutorialRoom(Room): +class TutorialRoom(DefaultRoom): """ This is the base room type for all rooms in the tutorial world. It defines a cmdset on itself for reading tutorial info about the location. From 4e4d8484d44f62ac9551f6afa415cd391ae1d663 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 3 Jan 2015 18:44:21 +0100 Subject: [PATCH 041/250] Made sure to re-cache the superuser bypass correctly. --- src/objects/models.py | 8 -------- src/players/player.py | 2 ++ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/objects/models.py b/src/objects/models.py index 5752c126e..7bba3fec5 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -106,14 +106,6 @@ class ObjectDB(TypedObject): # Database manager objects = ObjectDBManager() - # field-related field-related properties - def _at_db_player_postsave(self): - """ - This hook is called automatically after the player field is saved. - """ - # we need to re-cache this for superusers to bypass. - self.locks.cache_lock_bypass(self) - # cmdset_storage property handling def __cmdset_storage_get(self): "getter" diff --git a/src/players/player.py b/src/players/player.py index 4926b2f79..49fd53194 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -199,6 +199,8 @@ class DefaultPlayer(PlayerDB): ScriptDB.objects.validate(obj=obj) if normal_mode: obj.at_post_puppet() + # re-cache locks to make sure superuser bypass is updated + obj.locks.cache_lock_bypass(obj) return True def unpuppet_object(self, sessid): From 84483b7842e2855b2c62e17d3202ad99c8a1a096 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 4 Jan 2015 14:48:49 +0100 Subject: [PATCH 042/250] Merged manage.py into evennia.py. --- game/evennia.py | 178 ++++++++++++++++++++++++++++++------- game/manage.py | 27 ++---- src/utils/idmapper/base.py | 5 +- 3 files changed, 160 insertions(+), 50 deletions(-) diff --git a/game/evennia.py b/game/evennia.py index 2abd00b52..27cccf6ac 100755 --- a/game/evennia.py +++ b/game/evennia.py @@ -20,16 +20,102 @@ import django from django.core import management sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' +# Check/Create settings + +_CREATED_SETTINGS = False if not os.path.exists('settings.py'): - # make sure we have a settings.py file. - print " No settings.py file found. launching manage.py ..." + # If settings.py doesn't already exist, create it and populate it with some + # basic stuff. - # this triggers the settings file creation. - import game.manage + # make random secret_key. + import random + import string + secret_key = list((string.letters + + string.digits + string.punctuation).replace("\\", "").replace("'", '"')) + random.shuffle(secret_key) + secret_key = "".join(secret_key[:40]) + + settings_file = open('settings.py', 'w') + _CREATED_SETTINGS = True + + string = \ + """ +###################################################################### +# Evennia MU* server configuration file +# +# You may customize your setup by copy&pasting the variables you want +# to change from the master config file src/settings_default.py to +# this file. Try to *only* copy over things you really need to customize +# and do *not* make any changes to src/settings_default.py directly. +# This way you'll always have a sane default to fall back on +# (also, the master config file may change with server updates). +# +###################################################################### + +from src.settings_default import * + +###################################################################### +# Custom settings +###################################################################### + + +###################################################################### +# SECRET_KEY was randomly seeded when settings.py was first created. +# Don't share this with anybody. It is used by Evennia to handle +# cryptographic hashing for things like cookies on the web side. +###################################################################### +SECRET_KEY = '%s' + +""" % secret_key + + settings_file.write(string) + settings_file.close() + + print """ + Welcome to Evennia! + + No previous setting file was found so we created a fresh + settings.py file for you. No database was created. You may edit + the settings file now if you like, but you don't have to touch + anything if you just want to quickly get started. + + Once you are ready to continue, run evennia.py again to + initialize the database and a third time to start the server. + + The first time the server starts it will set things up for you. + Make sure to create a superuser when asked. The superuser's + email-address does not have to exist. + """ sys.exit() +#------------------------------------------------------------ +# Test the import of the settings file +#------------------------------------------------------------ +try: + from game import settings +except Exception: + import traceback + string = "\n" + traceback.format_exc() + + # note - if this fails, ugettext will also fail, so we cannot translate this string. + + string += """\n + Error: Couldn't import the file 'settings.py' in the directory containing %(file)r. + There are usually two reasons for this: + 1) The settings module contains errors. Review the traceback above to resolve the + problem, then try again. + 2) If you get errors on finding DJANGO_SETTINGS_MODULE you might have set up django + wrong in some way. If you run a virtual machine, it might be worth to restart it + to see if this resolves the issue. Evennia should not require you to define any + environment variables manually. + """ % {'file': __file__} + print string + sys.exit(1) + +# set the settings location +os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' + # required since django1.7. django.setup() @@ -150,29 +236,6 @@ PORTAL_PY_FILE = os.path.join(settings.SRC_DIR, 'server/portal.py') SERVER_LOGFILE = settings.SERVER_LOG_FILE PORTAL_LOGFILE = settings.PORTAL_LOG_FILE -# Check so a database exists and is accessible -from django.db import DatabaseError -from src.players.models import PlayerDB -try: - superuser = PlayerDB.objects.get(id=1) -except DatabaseError, e: - print """ - Your database does not seem to be set up correctly. - (error was '%s') - - Please run: - - python manage.py syncdb - - When you have a database set up, rerun evennia.py. - """ % e - sys.exit() -except PlayerDB.DoesNotExist: - # no superuser yet. We need to create it. - from django.core.management import call_command - print "\nCreate a superuser below. The superuser is Player #1, the 'owner' account of the server.\n" - call_command("createsuperuser", interactive=True) - # Add this to the environmental variable for the 'twistd' command. currpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if 'PYTHONPATH' in os.environ: @@ -485,13 +548,49 @@ def error_check_python_modules(): imp(settings.BASE_EXIT_TYPECLASS) imp(settings.BASE_SCRIPT_TYPECLASS) +def create_database(): + from django.core.management import call_command + print "\nCreating a database ...\n" + call_command("migrate", interactive=False) + print "\n ... database initialized.\n" + +def create_superuser(): + from django.core.management import call_command + print "\nCreate a superuser below. The superuser is Player #1, the 'owner' account of the server.\n" + call_command("createsuperuser", interactive=True) + +def check_database(automigrate=False): + # Check so a database exists and is accessible + from django.db import DatabaseError + from src.players.models import PlayerDB + try: + superuser = PlayerDB.objects.get(id=1) + except DatabaseError, e: + if automigrate: + create_database() + create_superuser() + else: + print """ + Your database does not seem to be set up correctly. + (error was '%s') + + Try to run + + python evennia.py + + to initialize the database according to your settings. + """ % e + sys.exit() + except PlayerDB.DoesNotExist: + # no superuser yet. We need to create it. + create_superuser() def main(): """ This handles command line input. """ - parser = OptionParser(usage="%prog [-i] start|stop|reload|menu [server|portal]", + parser = OptionParser(usage="%prog [-i] start|stop|reload|menu [server|portal]|manager args", description=CMDLINE_HELP) parser.add_option('-i', '--interactive', action='store_true', dest='interactive', default=False, @@ -514,8 +613,14 @@ def main(): if len(args) > 1: service = args[1] + if mode in ["start", "menu"]: + check_database(True) + elif mode not in ["stop"]: + check_database(False) + if mode not in ['menu', 'start', 'reload', 'stop']: - print "mode should be none, 'menu', 'start', 'reload' or 'stop'." + from django.core.management import call_command + call_command(mode) sys.exit() if service not in ['server', 'portal', 'all']: print "service should be none, 'server', 'portal' or 'all'." @@ -534,6 +639,19 @@ def main(): if __name__ == '__main__': # start Evennia + + if _CREATED_SETTINGS: + # if settings were created, info has already been printed. + sys.exit() + from src.utils.utils import check_evennia_dependencies if check_evennia_dependencies(): + if len(sys.argv) > 1 and sys.argv[1] in ('runserver', 'testserver'): + print """ + WARNING: There is no need to run the Django development + webserver to test out Evennia web features (the web client + will in fact not work since the Django test server knows + nothing about MUDs). Instead, just start Evennia with the + webserver component active (this is the default). + """ main() diff --git a/game/manage.py b/game/manage.py index f4f69e2b0..3ce38e736 100755 --- a/game/manage.py +++ b/game/manage.py @@ -11,16 +11,6 @@ import os # Tack on the root evennia directory to the python path. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -#------------------------------------------------------------ -# Get Evennia version -#------------------------------------------------------------ -try: - f = open(os.pardir + os.sep + 'VERSION.txt', 'r''') - VERSION = "%s" % f.read().strip() - f.close() -except IOError: - VERSION = "Unknown version" - #------------------------------------------------------------ # Check so session file exists in the current dir- if not, create it. #------------------------------------------------------------ @@ -80,20 +70,20 @@ SECRET_KEY = '%s' This looks like your first startup, so we created a fresh game/settings.py file for you. No database has yet been created. - You may edit the settings file now if you like, but you don't + You may edit the settings file now if you like, but you don't have to touch anything if you just want to quickly get started. - + Once you are ready to continue, run python manage.py migrate - - This will initialize the database. When that is done you can + + This will initialize the database. When that is done you can start Evennia itself with - + python evennia.py -i start - - The first time the server starts it will set things up for you. - Make sure to create a superuser when asked. The superuser's + + The first time the server starts it will set things up for you. + Make sure to create a superuser when asked. The superuser's email-address does not have to exist. """ @@ -144,5 +134,6 @@ if __name__ == "__main__": nothing about MUDs). Instead, just start Evennia with the webserver component active (this is the default). """ + print sys.argv from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) diff --git a/src/utils/idmapper/base.py b/src/utils/idmapper/base.py index 0c82f7118..ed3e4368e 100755 --- a/src/utils/idmapper/base.py +++ b/src/utils/idmapper/base.py @@ -76,7 +76,7 @@ class SharedMemoryModelBase(ModelBase): def prep(dbmodel): if not hasattr(dbmodel, "__instance_cache__"): dbmodel.__instance_cache__ = {} - dbmodel.__idmapper_recache_protection = False + dbmodel._idmapper_recache_protection = False if not cls._meta.proxy: # non-proxy models get the full cache prep(cls) @@ -85,6 +85,7 @@ class SharedMemoryModelBase(ModelBase): dbmodel = cls._meta.proxy_for_model prep(dbmodel) cls.__instance_cache__ = dbmodel.__instance_cache__ + cls._idmapper_recache_protection = False super(SharedMemoryModelBase, cls)._prepare() def __new__(cls, name, bases, attrs): @@ -210,7 +211,7 @@ class SharedMemoryModel(Model): #def __init__(cls, *args, **kwargs): # super(SharedMemoryModel, cls).__init__(*args, **kwargs) - # cls._idmapper_recache_protection = False + # cls.__idmapper_recache_protection = False @classmethod def _get_cache_key(cls, args, kwargs): From 2846e6483363ef627a1bdf0bb515b6ffc3725384 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jan 2015 14:39:10 +0100 Subject: [PATCH 043/250] Mayor first overhaul of the evennia.py launcher. Not tested yet. --- game/evennia.py | 630 +++++++++++++++++++++++++++--------------------- 1 file changed, 360 insertions(+), 270 deletions(-) diff --git a/game/evennia.py b/game/evennia.py index 27cccf6ac..92bc298c7 100755 --- a/game/evennia.py +++ b/game/evennia.py @@ -8,71 +8,70 @@ Sets the appropriate environmental variables and launches the server and portal through the runner. Run without arguments to get a menu. Run the script with the -h flag to see usage information. +Usage: + + evennia init - creates a new game location, sets up a custom + settings file and copies all templates to + evennia [settings][options] - handles server start/stop/restart if called + from the game folder. Can be called outside + the game folder if called with the path + to the settings file. + """ import os import sys import signal -from optparse import OptionParser -from subprocess import Popen +import shutil +import importlib import django - -# Set the Python path up so we can get to settings.py from here. +from argparse import ArgumentParser +from subprocess import Popen from django.core import management -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +# Signal processing +SIG = signal.SIGINT -# Check/Create settings +# Set up the main python paths to Evennia +EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin") +EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "lib") +EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "runner.py") +EVENNIA_TEMPLATE = os.path.join(EVENNIA_ROOT, "game_template") -_CREATED_SETTINGS = False -if not os.path.exists('settings.py'): - # If settings.py doesn't already exist, create it and populate it with some - # basic stuff. +EVENNIA_VERSION = "Unknown" +TWISTED_BINARY = "twistd" - # make random secret_key. - import random - import string - secret_key = list((string.letters + - string.digits + string.punctuation).replace("\\", "").replace("'", '"')) - random.shuffle(secret_key) - secret_key = "".join(secret_key[:40]) +# Game directory structure +SETTINGFILE = "settings.py" +SERVERDIR = "server" +CONFDIR = os.path.join(SERVERDIR, "conf") +SETTINGS_PATH = os.path.join(CONFDIR, SETTINGFILE) +SETTINGS_DOTPATH = "server.conf.settings" +CURRENT_DIR = os.getcwd() +GAMEDIR = CURRENT_DIR - settings_file = open('settings.py', 'w') - _CREATED_SETTINGS = True +# Operational setup +SERVER_LOGFILE = None +PORTAL_LOGFILE = None +SERVER_PIDFILE = None +PORTAL_PIDFILE = None +SERVER_RESTART = None +PORTAL_RESTART = None +SERVER_PY_FILE = None +PORTAL_PY_FILE = None - string = \ +# add Evennia root and bin dir to PYTHONPATH +sys.path.insert(0, EVENNIA_ROOT) + + +#------------------------------------------------------------ +# +# Messages +# +#------------------------------------------------------------ + +WELCOME_MESSAGE = \ """ -###################################################################### -# Evennia MU* server configuration file -# -# You may customize your setup by copy&pasting the variables you want -# to change from the master config file src/settings_default.py to -# this file. Try to *only* copy over things you really need to customize -# and do *not* make any changes to src/settings_default.py directly. -# This way you'll always have a sane default to fall back on -# (also, the master config file may change with server updates). -# -###################################################################### - -from src.settings_default import * - -###################################################################### -# Custom settings -###################################################################### - - -###################################################################### -# SECRET_KEY was randomly seeded when settings.py was first created. -# Don't share this with anybody. It is used by Evennia to handle -# cryptographic hashing for things like cookies on the web side. -###################################################################### -SECRET_KEY = '%s' - -""" % secret_key - - settings_file.write(string) - settings_file.close() - - print """ Welcome to Evennia! No previous setting file was found so we created a fresh @@ -87,81 +86,101 @@ SECRET_KEY = '%s' Make sure to create a superuser when asked. The superuser's email-address does not have to exist. """ - sys.exit() -#------------------------------------------------------------ -# Test the import of the settings file -#------------------------------------------------------------ -try: - from game import settings -except Exception: - import traceback - string = "\n" + traceback.format_exc() +WARNING_RUNSERVER = \ + """ + WARNING: There is no need to run the Django development + webserver to test out Evennia web features (the web client + will in fact not work since the Django test server knows + nothing about MUDs). Instead, just start Evennia with the + webserver component active (this is the default). + """ - # note - if this fails, ugettext will also fail, so we cannot translate this string. - - string += """\n - Error: Couldn't import the file 'settings.py' in the directory containing %(file)r. +ERROR_SETTINGS = \ + """ + ERROR: Could not import the file {settingsfile} from {settingspath}. There are usually two reasons for this: - 1) The settings module contains errors. Review the traceback above to resolve the - problem, then try again. - 2) If you get errors on finding DJANGO_SETTINGS_MODULE you might have set up django - wrong in some way. If you run a virtual machine, it might be worth to restart it - to see if this resolves the issue. Evennia should not require you to define any - environment variables manually. - """ % {'file': __file__} - print string - sys.exit(1) + 1) The settings file is a normal Python module. It may contain a syntax error. + Resolve the problem and try again. + 2) Django is not correctly installed. This usually shows by errors involving + 'DJANGO_SETTINGS_MODULE'. If you run a virtual machine, it might be worth to restart it + to see if this resolves the issue. + """.format(settingsfile=SETTINGFILE, settingspath=SETTINGS_PATH) -# set the settings location -os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' +ERROR_DATABASE = \ + """ + Your database does not seem to be set up correctly. + (error was '{traceback}') -# required since django1.7. -django.setup() + Try to run -# signal processing -SIG = signal.SIGINT + python evennia.py + to initialize the database according to your settings. + """ + +ERROR_WINDOWS_WIN32API = \ + """ + ERROR: Unable to import win32api, which Twisted requires to run. + You may download it from: + + http://sourceforge.net/projects/pywin32 + or + http://starship.python.net/crew/mhammond/win32/Downloads.html + """ + +INFO_WINDOWS_BATFILE = \ + """ + INFO: Since you are running Windows, a file 'twistd.bat' was + created for you. This is a simple batch file that tries to call + the twisted executable. Evennia determined this to be: + + %(twistd_path)s + + If you run into errors at startup you might need to edit + twistd.bat to point to the actual location of the Twisted + executable (usually called twistd.py) on your machine. + + This procedure is only done once. Run evennia.py again when you + are ready to start the server. + """ CMDLINE_HELP = \ -""" -Main Evennia launcher. When starting in interactive (-i) mode, only -the Server will do so since this is the most commonly useful setup. To -activate interactive mode also for the Portal, use the menu or launch -the two services one after the other as two separate calls to this -program. -""" + """ + Main Evennia launcher. When starting in interactive (-i) mode, only + the Server will do so since this is the most commonly useful setup. To + activate interactive mode also for the Portal, use the menu or launch + the two services one after the other as two separate calls to this + program. + """ VERSION_INFO = \ -""" - Evennia {version} - {about} - OS: {os} - Python: {python} - Twisted: {twisted} - Django: {django} - {south} -""" + """ + Evennia {version} + {about} + OS: {os} + Python: {python} + Twisted: {twisted} + Django: {django} + """ ABOUT_INFO= \ -""" - MUD/MUX/MU* development system + """ + Evennia MUD/MUX/MU* development system - Licence: BSD 3-Clause Licence - Web: http://www.evennia.com - Irc: #evennia on FreeNode - Forum: http://www.evennia.com/discussions - Maintainer (2010-): Griatch (griatch AT gmail DOT com) - Maintainer (2006-10): Greg Taylor -""" + Licence: BSD 3-Clause Licence + Web: http://www.evennia.com + Irc: #evennia on FreeNode + Forum: http://www.evennia.com/discussions + Maintainer (2010-): Griatch (griatch AT gmail DOT com) + Maintainer (2006-10): Greg Taylor + """ HELP_ENTRY = \ """ - (version %s) - -All launcher functionality can be accessed directly from the command -line. See python evennia.py -h for options. +See python evennia.py -h for controlling Evennia directly from +the command line. Evennia has two parts that both must run: @@ -212,89 +231,185 @@ MENU = \ """ +#------------------------------------------------------------ # -# System Configuration and setup +# Functions # +#------------------------------------------------------------ -SERVER_PIDFILE = "server.pid" -PORTAL_PIDFILE = "portal.pid" - -SERVER_RESTART = "server.restart" -PORTAL_RESTART = "portal.restart" - -# Get the settings -from django.conf import settings - -from src.utils.utils import get_evennia_version -EVENNIA_VERSION = get_evennia_version() - -# Setup access of the evennia server itself -SERVER_PY_FILE = os.path.join(settings.SRC_DIR, 'server/server.py') -PORTAL_PY_FILE = os.path.join(settings.SRC_DIR, 'server/portal.py') - -# Get logfile names -SERVER_LOGFILE = settings.SERVER_LOG_FILE -PORTAL_LOGFILE = settings.PORTAL_LOG_FILE - -# Add this to the environmental variable for the 'twistd' command. -currpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -if 'PYTHONPATH' in os.environ: - os.environ['PYTHONPATH'] += (":%s" % currpath) -else: - os.environ['PYTHONPATH'] = currpath - -TWISTED_BINARY = 'twistd' -if os.name == 'nt': - # Windows needs more work to get the correct binary +def evennia_version(): + """ + Get the Evennia version info from the main package. + """ + version = "Unknown" + with open(os.path.join(EVENNIA_ROOT, "VERSION.txt"), 'r') as f: + version = f.read().strip() try: - # Test for for win32api - import win32api - except ImportError: - print """ - ERROR: Unable to import win32api, which Twisted requires to run. - You may download it from: + version = "%s(GIT %s)" % (version, os.popen("git rev-parse --short HEAD").read().strip()) + except IOError: + pass + return version - http://sourceforge.net/projects/pywin32 - or - http://starship.python.net/crew/mhammond/win32/Downloads.html""" + +def init_game_directory(path): + """ + Try to analyze the given path to find settings.py - this defines + the game directory and also sets PYTHONPATH as well as the + django path. + """ + global GAMEDIR + if os.path.exists(os.path.join(path, SETTINGFILE)): + # path given to server/conf/ + GAMEDIR = os.path.dirname(os.path.dirname(os.path.dirname(path))) + elif os.path.exists(SETTINGS_PATH): + # path given to somewhere else in gamedir + GAMEDIR = os.path.dirname(os.path.dirname(path)) + else: + # Assume path given to root game dir + GAMEDIR = path + + # set pythonpath to gamedir + sys.path.insert(0, GAMEDIR) + # set the settings location + os.environ['DJANGO_SETTINGS_MODULE'] = SETTINGS_DOTPATH + # required since django1.7. + django.setup() + + # check all dependencies + from evennia.utils.utils import check_evennia_dependencies + if not check_evennia_ + + + + # test existence of settings module + try: + settings = importlib.import_module(SETTINGS_DOTPATH) + except Exception: + import traceback + print "\n" + traceback.format_exc() + print ERROR_SETTINGS sys.exit() - if not os.path.exists('twistd.bat'): - # Test for executable twisted batch file. This calls the twistd.py - # executable that is usually not found on the path in Windows. - # It's not enough to locate scripts.twistd, what we want is the - # executable script C:\PythonXX/Scripts/twistd.py. Alas we cannot - # hardcode this location since we don't know if user has Python - # in a non-standard location, so we try to figure it out. - from twisted.scripts import twistd - twistd_path = os.path.abspath( - os.path.join(os.path.dirname(twistd.__file__), - os.pardir, os.pardir, os.pardir, os.pardir, - 'scripts', 'twistd.py')) - bat_file = open('twistd.bat', 'w') - bat_file.write("@\"%s\" \"%s\" %%*" % (sys.executable, twistd_path)) - bat_file.close() - print """ - INFO: Since you are running Windows, a file 'twistd.bat' was - created for you. This is a simple batch file that tries to call - the twisted executable. Evennia determined this to be: + # set up the Evennia executables and log file locations + global SERVER_PY_FILE, PORTAL_PY_FILE + global SERVER_LOGFILE, PORTAL_LOGFILE + global SERVER_PIDFILE, PORTAL_PIDFILE + global SERVER_RESTART, PORTAL_RESTART + global EVENNIA_VERSION - %(twistd_path)s + SERVER_PY_FILE = os.path.join(settings.LIB_DIR, "server/server.py") + PORTAL_PY_FILE = os.path.join(settings.LIB_DIR, "portal/server.py") - If you run into errors at startup you might need to edit - twistd.bat to point to the actual location of the Twisted - executable (usually called twistd.py) on your machine. + SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid") + PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid") - This procedure is only done once. Run evennia.py again when you - are ready to start the server. - """ % {'twistd_path': twistd_path} - sys.exit() + SERVER_RESTART = os.path.join(GAMEDIR, SERVERDIR, "server.restart") + PORTAL_RESTART = os.path.join(GAMEDIR, SERVERDIR, "portal.restart") - TWISTED_BINARY = 'twistd.bat' + SERVER_LOGFILE = settings.SERVER_LOG_FILE + PORTAL_LOGFILE = settings.PORTAL_LOG_FILE + + # This also tests the library access + from evennia.utils.utils import get_evennia_version + EVENNIA_VERSION = get_evennia_version() + + # set up twisted + if os.name == 'nt': + # We need to handle Windows twisted separately. We create a + # batchfile in game/server, linking to the actual binary + + global TWISTED_BINARY + TWISTED_BINARY = "twistd.bat" + + # add path so system can find the batfile + sys.path.insert(0, os.path.join(GAMEDIR, SERVERDIR)) + + try: + importlib.import_module("win32api") + except ImportError: + print ERROR_WINDOWS_WIN32API + sys.exit() + + if not os.path.exists(os.path.join(EVENNIA_BIN, TWISTED_BINARY)): + # Test for executable twisted batch file. This calls the + # twistd.py executable that is usually not found on the + # path in Windows. It's not enough to locate + # scripts.twistd, what we want is the executable script + # C:\PythonXX/Scripts/twistd.py. Alas we cannot hardcode + # this location since we don't know if user has Python in + # a non-standard location. So we try to figure it out. + twistd = importlib.import_module("twisted.scripts.twistd") + twistd_dir = os.path.dirname(twistd.__file__) + + # note that we hope the twistd package won't change here, since we + # try to get to the executable by relative path. + twistd_path = os.path.abspath(os.path.join(twistd_dir, + os.pardir, os.pardir, os.pardir, os.pardir, + 'scripts', 'twistd.py')) + + with open('twistd.bat', 'w') as bat_file: + # build a custom bat file for windows + bat_file.write("@\"%s\" \"%s\" %%*" % (sys.executable, twistd_path)) + + print INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path) + + +# +# Check/Create settings +# + +def create_secret_key(): + """ + Randomly create the secret key for the settings file + """ + import random + import string + secret_key = list((string.letters + + string.digits + string.punctuation).replace("\\", "").replace("'", '"')) + random.shuffle(secret_key) + secret_key = "".join(secret_key[:40]) + return secret_key + + +def create_settings_file(): + """ + Uses the template settings file to build a working + settings file. + """ + settings_path = os.path.join(GAMEDIR, "server", "conf", "settings.py") + with open(settings_path, 'r') as f: + settings_string = f.read() + + # tweak the settings + setting_dict = {"servername":"Evennia", + "secret_key":create_secret_key} + + # modify the settings + settings_string.format(**setting_dict) + + with open(settings_path, 'w') as f: + f.write(settingsj_string) # Functions +def create_game_directory(dirname): + """ + Initialize a new game directory named dirname + at the current path. This means copying the + template directory from evennia's root. + """ + global GAMEDIR + GAMEDIR = os.abspath(os.path.join(CURRENT_DIR, dirname)) + if os.path.exists(GAMEDIR): + print "Cannot create new Evennia game dir: '%s' already exists." % dirname + sys.exit() + # copy template directory + shutil.copytree(EVENNIA_TEMPLATE, GAMEDIR) + # pre-build settings file in the new GAMEDIR + create_settings_file() + + def get_pid(pidfile): """ Get the PID (Process ID) by trying to access @@ -351,25 +466,19 @@ def show_version_info(about=False): import os, sys import twisted import django - try: - import south - sversion = "South %s" % south.__version__ - except ImportError: - sversion = "South " return VERSION_INFO.format(version=EVENNIA_VERSION, about=ABOUT_INFO if about else "", os=os.name, python=sys.version.split()[0], twisted=twisted.version.short(), - django=django.get_version(), - south=sversion) + django=django.get_version()) def run_menu(): """ This launches an interactive menu. """ - cmdstr = [sys.executable, "runner.py"] + cmdstr = [sys.executable, EVENNIA_RUNNER] while True: # menu loop @@ -405,7 +514,10 @@ def run_menu(): cmdstr.extend(['--iportal']) elif inp == 4: cmdstr.extend(['--iserver', '--iportal']) - return cmdstr + # start server + cmdstr.append("start") + Popen(cmdstr) + return elif inp < 10: if inp == 5: if os.name == 'nt': @@ -427,20 +539,18 @@ def run_menu(): return else: print "Not a valid option." - return None -def handle_args(options, mode, service): +def server_operation(mode, service, interactive): """ Handle argument options given on the command line. - options - parsed object for command line mode - str; start/stop etc service - str; server, portal or all + interactive - bool; use interactive mode or daemon """ - inter = options.interactive - cmdstr = [sys.executable, "runner.py"] + cmdstr = [sys.executable, EVENNIA_RUNNER] errmsg = "The %s does not seem to be running." if mode == 'start': @@ -450,21 +560,23 @@ def handle_args(options, mode, service): # starting one or many services if service == 'server': - if inter: + if interactive: cmdstr.append('--iserver') cmdstr.append('--noportal') elif service == 'portal': - if inter: + if interactive: cmdstr.append('--iportal') cmdstr.append('--noserver') management.call_command('collectstatic', verbosity=1, interactive=False) else: # all # for convenience we don't start logging of # portal, only of server with this command. - if inter: + if interactive: cmdstr.extend(['--iserver']) management.call_command('collectstatic', verbosity=1, interactive=False) - return cmdstr + # start the server + cmdstr.append("start") + Popen(cmdstr) elif mode == 'reload': # restarting services @@ -493,7 +605,6 @@ def handle_args(options, mode, service): else: kill(PORTAL_PIDFILE, SIG, "Portal stopped.", errmsg % 'Portal', PORTAL_RESTART, restart=False) kill(SERVER_PIDFILE, SIG, "Server stopped.", errmsg % 'Server', restart="shutdown") - return None def error_check_python_modules(): @@ -504,20 +615,16 @@ def error_check_python_modules(): the python source files themselves). Best they fail already here before we get any further. """ - def imp(path, split=True): - mod, fromlist = path, "None" - if split: - mod, fromlist = path.rsplit('.', 1) - __import__(mod, fromlist=[fromlist]) + from django.conf import settings # core modules - imp(settings.COMMAND_PARSER) - imp(settings.SEARCH_AT_RESULT) - imp(settings.SEARCH_AT_MULTIMATCH_INPUT) - imp(settings.CONNECTION_SCREEN_MODULE, split=False) + importlib.import_module(settings.COMMAND_PARSER) + importlib.import_module(settings.SEARCH_AT_RESULT) + importlib.import_module(settings.SEARCH_AT_MULTIMATCH_INPUT) + importlib.import_module(settings.CONNECTION_SCREEN_MODULE, split=False) #imp(settings.AT_INITIAL_SETUP_HOOK_MODULE, split=False) for path in settings.LOCK_FUNC_MODULES: - imp(path, split=False) + importlib.import_module(path, split=False) # cmdsets deprstring = "settings.%s should be renamed to %s. If defaults are used, " \ @@ -541,12 +648,13 @@ def error_check_python_modules(): if not cmdsethandler.import_cmdset(settings.CMDSET_CHARACTER, None): print "Warning: CMDSET_CHARACTER failed to load" if not cmdsethandler.import_cmdset(settings.CMDSET_PLAYER, None): print "Warning: CMDSET_PLAYER failed to load" # typeclasses - imp(settings.BASE_PLAYER_TYPECLASS) - imp(settings.BASE_OBJECT_TYPECLASS) - imp(settings.BASE_CHARACTER_TYPECLASS) - imp(settings.BASE_ROOM_TYPECLASS) - imp(settings.BASE_EXIT_TYPECLASS) - imp(settings.BASE_SCRIPT_TYPECLASS) + importlib.import_module(settings.BASE_PLAYER_TYPECLASS) + importlib.import_module(settings.BASE_OBJECT_TYPECLASS) + importlib.import_module(settings.BASE_CHARACTER_TYPECLASS) + importlib.import_module(settings.BASE_ROOM_TYPECLASS) + importlib.import_module(settings.BASE_EXIT_TYPECLASS) + importlib.import_module(settings.BASE_SCRIPT_TYPECLASS) + def create_database(): from django.core.management import call_command @@ -554,32 +662,25 @@ def create_database(): call_command("migrate", interactive=False) print "\n ... database initialized.\n" + def create_superuser(): from django.core.management import call_command print "\nCreate a superuser below. The superuser is Player #1, the 'owner' account of the server.\n" call_command("createsuperuser", interactive=True) + def check_database(automigrate=False): # Check so a database exists and is accessible from django.db import DatabaseError from src.players.models import PlayerDB try: - superuser = PlayerDB.objects.get(id=1) + PlayerDB.objects.get(id=1) except DatabaseError, e: if automigrate: create_database() create_superuser() else: - print """ - Your database does not seem to be set up correctly. - (error was '%s') - - Try to run - - python evennia.py - - to initialize the database according to your settings. - """ % e + print ERROR_DATABASE.format(traceback=e) sys.exit() except PlayerDB.DoesNotExist: # no superuser yet. We need to create it. @@ -587,71 +688,60 @@ def check_database(automigrate=False): def main(): """ - This handles command line input. + Run the evennia main program. """ - parser = OptionParser(usage="%prog [-i] start|stop|reload|menu [server|portal]|manager args", + # set up argument parser + + parser = ArgumentParser(#usage="%prog [-i] start|stop|reload|menu [server|portal]|manager args", description=CMDLINE_HELP) - parser.add_option('-i', '--interactive', action='store_true', + parser.add_argument('-i', '--interactive', action='store_true', dest='interactive', default=False, help="Start given processes in interactive mode.") - parser.add_option('-v', '--version', action='store_true', + parser.add_argument('-v', '--version', action='store_true', dest='show_version', default=False, help="Show version info.") + parser.add_argument('--init', action='store', dest="init", metavar="dirname") + parser.add_argument('-c', '--config', action='store', dest="config", default=None) + parser.add_argument("mode", default="menu") + parser.add_argument("service", choices=["all", "server", "portal"], default="all") - options, args = parser.parse_args() + args = parser.parse_args() - if not args: - if options.show_version: - print show_version_info() - return - mode = "menu" - service = 'all' - if args: - mode = args[0] - service = "all" - if len(args) > 1: - service = args[1] + # handle arguments - if mode in ["start", "menu"]: - check_database(True) - elif mode not in ["stop"]: - check_database(False) + if args.show_version: + print show_version_info() - if mode not in ['menu', 'start', 'reload', 'stop']: - from django.core.management import call_command - call_command(mode) - sys.exit() - if service not in ['server', 'portal', 'all']: - print "service should be none, 'server', 'portal' or 'all'." + mode, service = args.mode, args.service + + if args.init: + create_game_directory(args.init) sys.exit() + # this must be done first - it sets up all the global properties + # and initializes django for the game directory + init_game_directory(CURRENT_DIR) + if mode == 'menu': - # launch menu - cmdstr = run_menu() + # launch menu for operation + check_database(True) + run_menu() + elif mode in ('start', 'reload', 'stop'): + # operate the server directly + if mode != "stop": + check_database(False) + server_operation(mode, service, args.interactive) else: - # handle command-line arguments - cmdstr = handle_args(options, mode, service) - if cmdstr: - # call the runner. - cmdstr.append('start') - Popen(cmdstr) + # pass-through to django manager + from django.core.management import call_command + call_command(mode) + if __name__ == '__main__': - # start Evennia + # start Evennia from the command line - if _CREATED_SETTINGS: - # if settings were created, info has already been printed. - sys.exit() - - from src.utils.utils import check_evennia_dependencies if check_evennia_dependencies(): if len(sys.argv) > 1 and sys.argv[1] in ('runserver', 'testserver'): - print """ - WARNING: There is no need to run the Django development - webserver to test out Evennia web features (the web client - will in fact not work since the Django test server knows - nothing about MUDs). Instead, just start Evennia with the - webserver component active (this is the default). - """ + print WARNING_RUNSERVER main() From 2b3a32e447b79c8cdb7705112802537be44ce6ac Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 6 Jan 2015 14:53:45 +0100 Subject: [PATCH 044/250] Reshuffling the Evennia package into the new template paradigm. --- {game => bin}/evennia.py | 0 {game => bin}/runner.py | 0 .../web => contrib/red_button}/__init__.py | 0 .../red_button}/cmdset_red_button.py | 0 .../red_button}/red_button.py | 0 .../red_button}/red_button_scripts.py | 0 .../gamesrc/scripts/examples/bodyfunctions.py | 63 - game/gamesrc/world/examples/batch_code.py | 85 - game/logs/README | 8 - game/manage.py | 139 - .../examples => game_template}/__init__.py | 0 {src => game_template/commands}/__init__.py | 0 .../commands}/cmdset.py | 0 .../commands}/command.py | 0 {game => game_template/server}/__init__.py | 0 .../server/conf}/__init__.py | 0 .../server/conf}/at_initial_setup.py | 0 .../server/conf}/at_server_startstop.py | 0 .../server/conf}/connection_screens.py | 0 .../server/conf}/lockfuncs.py | 0 .../server/conf}/mssp.py | 0 .../server/conf}/oobfuncs.py | 0 .../server/conf}/portal_services_plugin.py | 0 .../server/conf}/server_services_plugin.py | 0 .../server/web}/__init__.py | 0 .../server}/web/examples/__init__.py | 0 .../server}/web/examples/urls.py | 0 .../server}/web/media/README.md | 0 .../server}/web/static/README.md | 0 .../server/web/static/admin/css/base.css | 847 ++ .../web/static/admin/css/changelists.css | 293 + .../server/web/static/admin/css/dashboard.css | 30 + .../server/web/static/admin/css/forms.css | 376 + .../server/web/static/admin/css/ie.css | 63 + .../server/web/static/admin/css/login.css | 60 + .../server/web/static/admin/css/rtl.css | 250 + .../server/web/static/admin/css/widgets.css | 578 + .../web/static/admin/img/changelist-bg.gif | Bin 0 -> 50 bytes .../static/admin/img/changelist-bg_rtl.gif | Bin 0 -> 75 bytes .../web/static/admin/img/chooser-bg.gif | Bin 0 -> 199 bytes .../static/admin/img/chooser_stacked-bg.gif | Bin 0 -> 212 bytes .../static/admin/img/default-bg-reverse.gif | Bin 0 -> 835 bytes .../web/static/admin/img/default-bg.gif | Bin 0 -> 836 bytes .../web/static/admin/img/deleted-overlay.gif | Bin 0 -> 45 bytes .../static/admin/img/gis/move_vertex_off.png | Bin 0 -> 711 bytes .../static/admin/img/gis/move_vertex_on.png | Bin 0 -> 506 bytes .../server/web/static/admin/img/icon-no.gif | Bin 0 -> 176 bytes .../web/static/admin/img/icon-unknown.gif | Bin 0 -> 130 bytes .../server/web/static/admin/img/icon-yes.gif | Bin 0 -> 299 bytes .../web/static/admin/img/icon_addlink.gif | Bin 0 -> 119 bytes .../web/static/admin/img/icon_alert.gif | Bin 0 -> 145 bytes .../web/static/admin/img/icon_calendar.gif | Bin 0 -> 192 bytes .../web/static/admin/img/icon_changelink.gif | Bin 0 -> 119 bytes .../web/static/admin/img/icon_clock.gif | Bin 0 -> 390 bytes .../web/static/admin/img/icon_deletelink.gif | Bin 0 -> 181 bytes .../web/static/admin/img/icon_error.gif | Bin 0 -> 319 bytes .../web/static/admin/img/icon_searchbox.png | Bin 0 -> 368 bytes .../web/static/admin/img/icon_success.gif | Bin 0 -> 341 bytes .../static/admin/img/inline-delete-8bit.png | Bin 0 -> 395 bytes .../web/static/admin/img/inline-delete.png | Bin 0 -> 707 bytes .../static/admin/img/inline-restore-8bit.png | Bin 0 -> 363 bytes .../web/static/admin/img/inline-restore.png | Bin 0 -> 557 bytes .../static/admin/img/inline-splitter-bg.gif | Bin 0 -> 94 bytes .../web/static/admin/img/nav-bg-grabber.gif | Bin 0 -> 116 bytes .../web/static/admin/img/nav-bg-reverse.gif | Bin 0 -> 178 bytes .../web/static/admin/img/nav-bg-selected.gif | Bin 0 -> 265 bytes .../server/web/static/admin/img/nav-bg.gif | Bin 0 -> 265 bytes .../web/static/admin/img/selector-icons.gif | Bin 0 -> 2771 bytes .../web/static/admin/img/selector-search.gif | Bin 0 -> 552 bytes .../web/static/admin/img/sorting-icons.gif | Bin 0 -> 369 bytes .../server/web/static/admin/img/tool-left.gif | Bin 0 -> 197 bytes .../web/static/admin/img/tool-left_over.gif | Bin 0 -> 203 bytes .../web/static/admin/img/tool-right.gif | Bin 0 -> 198 bytes .../web/static/admin/img/tool-right_over.gif | Bin 0 -> 200 bytes .../web/static/admin/img/tooltag-add.gif | Bin 0 -> 932 bytes .../web/static/admin/img/tooltag-add.png | Bin 0 -> 119 bytes .../web/static/admin/img/tooltag-add_over.gif | Bin 0 -> 336 bytes .../static/admin/img/tooltag-arrowright.gif | Bin 0 -> 351 bytes .../static/admin/img/tooltag-arrowright.png | Bin 0 -> 200 bytes .../admin/img/tooltag-arrowright_over.gif | Bin 0 -> 354 bytes .../web/static/admin/js/LICENSE-JQUERY.txt | 20 + .../server/web/static/admin/js/SelectBox.js | 114 + .../web/static/admin/js/SelectFilter2.js | 161 + .../server/web/static/admin/js/actions.js | 144 + .../server/web/static/admin/js/actions.min.js | 6 + .../admin/js/admin/DateTimeShortcuts.js | 356 + .../admin/js/admin/RelatedObjectLookups.js | 97 + .../web/static/admin/js/admin/ordering.js | 137 + .../server/web/static/admin/js/calendar.js | 169 + .../server/web/static/admin/js/collapse.js | 24 + .../web/static/admin/js/collapse.min.js | 2 + .../server/web/static/admin/js/core.js | 222 + .../static/admin/js/getElementsBySelector.js | 167 + .../server/web/static/admin/js/inlines.js | 272 + .../server/web/static/admin/js/inlines.min.js | 9 + .../server/web/static/admin/js/jquery.init.js | 7 + .../server/web/static/admin/js/jquery.js | 9597 +++++++++++++++++ .../server/web/static/admin/js/jquery.min.js | 5 + .../server/web/static/admin/js/prepopulate.js | 39 + .../web/static/admin/js/prepopulate.min.js | 1 + .../server/web/static/admin/js/timeparse.js | 94 + .../server/web/static/admin/js/urlify.js | 147 + .../web/static/ev}/css/prosimii-print.css | 0 .../static/ev}/css/prosimii-screen-alt.css | 0 .../web/static/ev}/css/prosimii-screen.css | 0 .../server/web/static/ev}/images/LICENCE | 0 .../web/static/ev/images/evennia_logo.png | Bin 0 -> 693876 bytes .../static/ev/images/evennia_logo_small.png | Bin .../server/web/static/ev}/images/favicon.ico | Bin .../evennia_general/css/prosimii-print.css | 225 + .../css/prosimii-screen-alt.css | 342 + .../evennia_general/css/prosimii-screen.css | 359 + .../web/static/evennia_general/images/LICENCE | 4 + .../evennia_general/images/evennia_logo.png | Bin 0 -> 17938 bytes .../images/evennia_logo_small.png | Bin 0 -> 17938 bytes .../static/evennia_general/images/favicon.ico | Bin 0 -> 1406 bytes .../web}/static/webclient/css/webclient.css | 0 .../webclient/js/evennia_ajax_webclient.js | 0 .../static/webclient/js/evennia_webclient.js | 298 + .../js/evennia_websocket_webclient.js | 0 .../server}/web/static_overrides/README.md | 0 .../server}/web/template_overrides/README.md | 0 .../tests => game_template/types}/__init__.py | 0 .../types}/character.py | 0 .../examples => game_template/types}/exit.py | 0 .../types}/object.py | 0 .../types}/player.py | 0 .../examples => game_template/types}/room.py | 0 .../types}/script.py | 0 .../world}/__init__.py | 0 .../world}/batch_cmds.ev | 0 .../world}/prototypes.py | 0 {src/web => lib}/__init__.py | 0 {game/gamesrc => lib}/commands/__init__.py | 0 {src => lib}/commands/cmdhandler.py | 0 {src => lib}/commands/cmdparser.py | 0 {src => lib}/commands/cmdset.py | 0 {src => lib}/commands/cmdsethandler.py | 0 {src => lib}/commands/command.py | 0 {src => lib}/commands/connection_screen.py | 0 {src => lib}/commands/default/__init__.py | 0 {src => lib}/commands/default/admin.py | 0 {src => lib}/commands/default/batchprocess.py | 0 {src => lib}/commands/default/building.py | 0 .../commands/default/cmdset_character.py | 0 .../commands/default/cmdset_player.py | 0 .../commands/default/cmdset_session.py | 0 .../commands/default/cmdset_unloggedin.py | 0 {src => lib}/commands/default/comms.py | 0 {src => lib}/commands/default/general.py | 0 {src => lib}/commands/default/help.py | 0 {src => lib}/commands/default/muxcommand.py | 0 {src => lib}/commands/default/player.py | 0 {src => lib}/commands/default/syscommands.py | 0 {src => lib}/commands/default/system.py | 0 {src => lib}/commands/default/tests.py | 0 {src => lib}/commands/default/unloggedin.py | 0 {src => lib}/comms/__init__.py | 0 {src => lib}/comms/admin.py | 0 {src => lib}/comms/channelhandler.py | 0 {src => lib}/comms/comms.py | 0 {src => lib}/comms/managers.py | 0 {src => lib}/comms/migrations/0001_initial.py | 0 .../0002_msg_db_hide_from_objects.py | 0 .../migrations/0003_auto_20140917_0756.py | 0 .../comms/migrations}/__init__.py | 0 {src => lib}/comms/models.py | 0 {src => lib}/help/__init__.py | 0 {src => lib}/help/admin.py | 0 {src => lib}/help/manager.py | 0 {src => lib}/help/migrations/0001_initial.py | 0 .../conf => lib/help/migrations}/__init__.py | 0 {src => lib}/help/models.py | 0 .../conf/examples => lib/locks}/__init__.py | 0 {src => lib}/locks/lockfuncs.py | 0 {src => lib}/locks/lockhandler.py | 0 {src => lib}/locks/tests.py | 0 {src => lib}/objects/__init__.py | 0 {src => lib}/objects/admin.py | 0 {src => lib}/objects/manager.py | 0 .../objects/migrations/0001_initial.py | 0 .../migrations/0002_auto_20140917_0756.py | 0 .../objects/migrations}/__init__.py | 0 {src => lib}/objects/models.py | 0 {src => lib}/objects/objects.py | 0 {src => lib}/players/__init__.py | 0 {src => lib}/players/admin.py | 0 {src => lib}/players/bots.py | 0 {src => lib}/players/manager.py | 0 .../players/migrations/0001_initial.py | 0 .../players/migrations}/__init__.py | 0 {src => lib}/players/models.py | 0 {src => lib}/players/player.py | 0 {src => lib}/scripts/__init__.py | 0 {src => lib}/scripts/admin.py | 0 {src => lib}/scripts/manager.py | 0 .../scripts/migrations/0001_initial.py | 0 .../scripts/migrations}/__init__.py | 0 {src => lib}/scripts/models.py | 0 {src => lib}/scripts/scripthandler.py | 0 {src => lib}/scripts/scripts.py | 0 {src => lib}/scripts/tickerhandler.py | 0 {src => lib}/server/__init__.py | 0 {src => lib}/server/admin.py | 0 {src => lib}/server/amp.py | 0 {src => lib}/server/caches.py | 0 {src => lib}/server/initial_setup.py | 0 {src => lib}/server/manager.py | 0 .../server/migrations/0001_initial.py | 0 .../server/migrations}/__init__.py | 0 {src => lib}/server/models.py | 0 {src => lib}/server/oob_cmds.py | 0 {src => lib}/server/oobhandler.py | 0 .../server/portal}/__init__.py | 0 {src => lib}/server/portal/imc2.py | 0 .../server/portal/imc2lib}/__init__.py | 0 .../server/portal/imc2lib/imc2_ansi.py | 0 .../server/portal/imc2lib/imc2_packets.py | 0 {src => lib}/server/portal/irc.py | 0 {src => lib}/server/portal/mccp.py | 0 {src => lib}/server/portal/msdp.py | 0 {src => lib}/server/portal/mssp.py | 0 {src => lib}/server/portal/mxp.py | 0 {src => lib}/server/portal/naws.py | 0 {src => lib}/server/portal/portal.py | 0 .../server/portal/portalsessionhandler.py | 0 {src => lib}/server/portal/rss.py | 0 {src => lib}/server/portal/ssh.py | 0 {src => lib}/server/portal/ssl.py | 0 {src => lib}/server/portal/telnet.py | 0 {src => lib}/server/portal/ttype.py | 0 {src => lib}/server/portal/webclient.py | 0 .../server/portal/websocket_client.py | 0 {src => lib}/server/server.py | 0 {src => lib}/server/serversession.py | 0 {src => lib}/server/session.py | 0 {src => lib}/server/sessionhandler.py | 0 {src => lib}/server/tests.py | 0 {src => lib}/server/webserver.py | 0 {src => lib}/settings_default.py | 0 {src/web/webclient => lib/tests}/__init__.py | 0 .../tests/test_commands_cmdhandler.py | 0 {src => lib}/tests/test_commands_cmdparser.py | 0 {src => lib}/tests/test_commands_cmdset.py | 0 .../tests/test_commands_cmdsethandler.py | 0 {src => lib}/tests/test_commands_command.py | 0 .../tests/test_comms_channelhandler.py | 0 {src => lib}/tests/test_comms_comms.py | 0 {src => lib}/tests/test_comms_models.py | 0 {src => lib}/tests/test_locks_lockfuncs.py | 0 {src => lib}/tests/test_locks_lockhandler.py | 0 {src => lib}/tests/test_objects_models.py | 0 {src => lib}/tests/test_objects_objects.py | 0 {src => lib}/tests/test_players_bots.py | 0 {src => lib}/tests/test_players_models.py | 0 {src => lib}/tests/test_scripts_models.py | 0 .../tests/test_scripts_scripthandler.py | 0 {src => lib}/tests/test_scripts_scripts.py | 0 .../tests/test_scripts_tickerhandler.py | 0 {src => lib}/tests/test_server_amp.py | 0 {src => lib}/tests/test_server_caches.py | 0 .../tests/test_server_initial_setup.py | 0 {src => lib}/tests/test_server_manager.py | 0 {src => lib}/tests/test_server_models.py | 0 {src => lib}/tests/test_server_oob_msdp.py | 0 {src => lib}/tests/test_server_oobhandler.py | 0 {src => lib}/tests/test_server_server.py | 0 .../tests/test_server_serversession.py | 0 {src => lib}/tests/test_server_session.py | 0 {src => lib}/tests/test_server_webserver.py | 0 {src => lib}/tests/test_typeclasses_models.py | 0 .../tests/test_typeclasses_typeclass.py | 0 {src => lib}/tests/test_utils_ansi.py | 0 {src => lib}/tests/test_utils_ansi_new.py | 0 .../tests/test_utils_batchprocessors.py | 0 {src => lib}/tests/test_utils_create.py | 0 {src => lib}/tests/test_utils_dbserialize.py | 0 {src => lib}/tests/test_utils_evform.py | 0 {src => lib}/tests/test_utils_evtable.py | 0 {src => lib}/tests/test_utils_gametime.py | 0 {src => lib}/tests/test_utils_logger.py | 0 {src => lib}/tests/test_utils_picklefield.py | 0 {src => lib}/tests/test_utils_prettytable.py | 0 {src => lib}/tests/test_utils_search.py | 0 {src => lib}/tests/test_utils_tests.py | 0 {src => lib}/tests/test_utils_text2html.py | 0 {src => lib}/tests/test_utils_utils.py | 0 {src/commands => lib/typeclasses}/__init__.py | 0 {src => lib}/typeclasses/admin.py | 0 {src => lib}/typeclasses/attributes.py | 0 {src => lib}/typeclasses/django_new_patch.py | 0 {src => lib}/typeclasses/managers.py | 0 .../typeclasses/migrations/0001_initial.py | 0 .../typeclasses}/migrations/__init__.py | 0 {src => lib}/typeclasses/models.py | 0 {src => lib}/typeclasses/tags.py | 0 {src => lib}/utils/__init__.py | 0 {src => lib}/utils/ansi.py | 0 {src => lib}/utils/batchprocessors.py | 0 {src => lib}/utils/create.py | 0 {src => lib}/utils/dbserialize.py | 0 {src => lib}/utils/dummyrunner/README.txt | 0 .../utils/dummyrunner/__init__.py | 0 {src => lib}/utils/dummyrunner/dummyrunner.py | 0 .../utils/dummyrunner/dummyrunner_actions.py | 0 {src => lib}/utils/dummyrunner/memplot.py | 0 .../utils/dummyrunner/test_queries.py | 0 {src => lib}/utils/evennia-mode.el | 0 {src => lib}/utils/evform.py | 0 {src => lib}/utils/evform_test.py | 0 {src => lib}/utils/evtable.py | 0 {src => lib}/utils/gametime.py | 0 {src => lib}/utils/idmapper/EVENNIA.txt | 0 {src => lib}/utils/idmapper/LICENSE | 0 {src => lib}/utils/idmapper/README.rst | 0 {src => lib}/utils/idmapper/__init__.py | 0 {src => lib}/utils/idmapper/base.py | 0 {src => lib}/utils/idmapper/manager.py | 0 {src => lib}/utils/idmapper/models.py | 0 {src => lib}/utils/idmapper/tests.py | 0 {src => lib}/utils/inlinefunc.py | 0 {src => lib}/utils/logger.py | 0 {src => lib}/utils/picklefield.py | 0 {src => lib}/utils/prettytable.py | 0 {src => lib}/utils/search.py | 0 {src => lib}/utils/spawner.py | 0 {src => lib}/utils/tests.py | 0 {src => lib}/utils/text2html.py | 0 {src => lib}/utils/txws.py | 0 {src => lib}/utils/utils.py | 0 lib/web/__init__.py | 0 .../evennia_general/css/prosimii-print.css | 225 + .../css/prosimii-screen-alt.css | 342 + .../evennia_general/css/prosimii-screen.css | 359 + lib/web/static/evennia_general/images/LICENCE | 4 + .../evennia_general/images/evennia_logo.png | Bin 0 -> 17938 bytes .../static/evennia_general/images/favicon.ico | Bin 0 -> 1406 bytes .../evennia_general/evennia_admin.html | 0 .../web/templates/evennia_general/index.html | 0 .../web/templates/evennia_general/tbi.html | 0 {src => lib}/web/templates/prosimii/404.html | 0 {src => lib}/web/templates/prosimii/500.html | 0 {src => lib}/web/templates/prosimii/base.html | 0 .../templates/prosimii/flatpages/default.html | 0 .../prosimii/registration/logged_out.html | 0 .../prosimii/registration/login.html | 0 {src => lib}/web/urls.py | 0 lib/web/utils/__init__.py | 0 {src => lib}/web/utils/apache_wsgi.conf | 0 {src => lib}/web/utils/backends.py | 0 .../web/utils/evennia_modpy_apache.conf | 0 .../web/utils/evennia_wsgi_apache.conf | 0 {src => lib}/web/utils/general_context.py | 0 {src => lib}/web/views.py | 0 lib/web/webclient/__init__.py | 0 {src => lib}/web/webclient/models.py | 0 .../static/webclient/css/webclient.css | 150 + .../webclient/js/evennia_ajax_webclient.js | 298 + .../js/evennia_websocket_webclient.js | 357 + .../web/webclient/templates/webclient.html | 0 {src => lib}/web/webclient/urls.py | 0 {src => lib}/web/webclient/views.py | 0 src/help/migrations/__init__.py | 1 - src/locks/__init__.py | 1 - src/players/migrations/__init__.py | 1 - src/scripts/migrations/__init__.py | 1 - src/server/migrations/__init__.py | 1 - src/server/portal/__init__.py | 1 - src/server/portal/imc2lib/__init__.py | 1 - src/typeclasses/__init__.py | 1 - src/typeclasses/migrations/__init__.py | 1 - 371 files changed, 17250 insertions(+), 304 deletions(-) rename {game => bin}/evennia.py (100%) rename {game => bin}/runner.py (100%) rename {game/gamesrc/web => contrib/red_button}/__init__.py (100%) rename {game/gamesrc/commands/examples => contrib/red_button}/cmdset_red_button.py (100%) rename {game/gamesrc/objects/examples => contrib/red_button}/red_button.py (100%) rename {game/gamesrc/scripts/examples => contrib/red_button}/red_button_scripts.py (100%) delete mode 100644 game/gamesrc/scripts/examples/bodyfunctions.py delete mode 100644 game/gamesrc/world/examples/batch_code.py delete mode 100644 game/logs/README delete mode 100755 game/manage.py rename {game/gamesrc/world/examples => game_template}/__init__.py (100%) rename {src => game_template/commands}/__init__.py (100%) rename {game/gamesrc/commands/examples => game_template/commands}/cmdset.py (100%) rename {game/gamesrc/commands/examples => game_template/commands}/command.py (100%) rename {game => game_template/server}/__init__.py (100%) rename {game/gamesrc => game_template/server/conf}/__init__.py (100%) rename {game/gamesrc/conf/examples => game_template/server/conf}/at_initial_setup.py (100%) rename {game/gamesrc/conf/examples => game_template/server/conf}/at_server_startstop.py (100%) rename {game/gamesrc/conf/examples => game_template/server/conf}/connection_screens.py (100%) rename {game/gamesrc/conf/examples => game_template/server/conf}/lockfuncs.py (100%) rename {game/gamesrc/conf/examples => game_template/server/conf}/mssp.py (100%) rename {game/gamesrc/conf/examples => game_template/server/conf}/oobfuncs.py (100%) rename {game/gamesrc/conf/examples => game_template/server/conf}/portal_services_plugin.py (100%) rename {game/gamesrc/conf/examples => game_template/server/conf}/server_services_plugin.py (100%) rename {src/objects/migrations => game_template/server/web}/__init__.py (100%) rename {game/gamesrc => game_template/server}/web/examples/__init__.py (100%) rename {game/gamesrc => game_template/server}/web/examples/urls.py (100%) rename {game/gamesrc => game_template/server}/web/media/README.md (100%) rename {game/gamesrc => game_template/server}/web/static/README.md (100%) create mode 100644 game_template/server/web/static/admin/css/base.css create mode 100644 game_template/server/web/static/admin/css/changelists.css create mode 100644 game_template/server/web/static/admin/css/dashboard.css create mode 100644 game_template/server/web/static/admin/css/forms.css create mode 100644 game_template/server/web/static/admin/css/ie.css create mode 100644 game_template/server/web/static/admin/css/login.css create mode 100644 game_template/server/web/static/admin/css/rtl.css create mode 100644 game_template/server/web/static/admin/css/widgets.css create mode 100644 game_template/server/web/static/admin/img/changelist-bg.gif create mode 100644 game_template/server/web/static/admin/img/changelist-bg_rtl.gif create mode 100644 game_template/server/web/static/admin/img/chooser-bg.gif create mode 100644 game_template/server/web/static/admin/img/chooser_stacked-bg.gif create mode 100644 game_template/server/web/static/admin/img/default-bg-reverse.gif create mode 100644 game_template/server/web/static/admin/img/default-bg.gif create mode 100644 game_template/server/web/static/admin/img/deleted-overlay.gif create mode 100644 game_template/server/web/static/admin/img/gis/move_vertex_off.png create mode 100644 game_template/server/web/static/admin/img/gis/move_vertex_on.png create mode 100644 game_template/server/web/static/admin/img/icon-no.gif create mode 100644 game_template/server/web/static/admin/img/icon-unknown.gif create mode 100644 game_template/server/web/static/admin/img/icon-yes.gif create mode 100644 game_template/server/web/static/admin/img/icon_addlink.gif create mode 100644 game_template/server/web/static/admin/img/icon_alert.gif create mode 100644 game_template/server/web/static/admin/img/icon_calendar.gif create mode 100644 game_template/server/web/static/admin/img/icon_changelink.gif create mode 100644 game_template/server/web/static/admin/img/icon_clock.gif create mode 100644 game_template/server/web/static/admin/img/icon_deletelink.gif create mode 100644 game_template/server/web/static/admin/img/icon_error.gif create mode 100644 game_template/server/web/static/admin/img/icon_searchbox.png create mode 100644 game_template/server/web/static/admin/img/icon_success.gif create mode 100644 game_template/server/web/static/admin/img/inline-delete-8bit.png create mode 100644 game_template/server/web/static/admin/img/inline-delete.png create mode 100644 game_template/server/web/static/admin/img/inline-restore-8bit.png create mode 100644 game_template/server/web/static/admin/img/inline-restore.png create mode 100644 game_template/server/web/static/admin/img/inline-splitter-bg.gif create mode 100644 game_template/server/web/static/admin/img/nav-bg-grabber.gif create mode 100644 game_template/server/web/static/admin/img/nav-bg-reverse.gif create mode 100644 game_template/server/web/static/admin/img/nav-bg-selected.gif create mode 100644 game_template/server/web/static/admin/img/nav-bg.gif create mode 100644 game_template/server/web/static/admin/img/selector-icons.gif create mode 100644 game_template/server/web/static/admin/img/selector-search.gif create mode 100644 game_template/server/web/static/admin/img/sorting-icons.gif create mode 100644 game_template/server/web/static/admin/img/tool-left.gif create mode 100644 game_template/server/web/static/admin/img/tool-left_over.gif create mode 100644 game_template/server/web/static/admin/img/tool-right.gif create mode 100644 game_template/server/web/static/admin/img/tool-right_over.gif create mode 100644 game_template/server/web/static/admin/img/tooltag-add.gif create mode 100644 game_template/server/web/static/admin/img/tooltag-add.png create mode 100644 game_template/server/web/static/admin/img/tooltag-add_over.gif create mode 100644 game_template/server/web/static/admin/img/tooltag-arrowright.gif create mode 100644 game_template/server/web/static/admin/img/tooltag-arrowright.png create mode 100644 game_template/server/web/static/admin/img/tooltag-arrowright_over.gif create mode 100644 game_template/server/web/static/admin/js/LICENSE-JQUERY.txt create mode 100644 game_template/server/web/static/admin/js/SelectBox.js create mode 100644 game_template/server/web/static/admin/js/SelectFilter2.js create mode 100644 game_template/server/web/static/admin/js/actions.js create mode 100644 game_template/server/web/static/admin/js/actions.min.js create mode 100644 game_template/server/web/static/admin/js/admin/DateTimeShortcuts.js create mode 100644 game_template/server/web/static/admin/js/admin/RelatedObjectLookups.js create mode 100644 game_template/server/web/static/admin/js/admin/ordering.js create mode 100644 game_template/server/web/static/admin/js/calendar.js create mode 100644 game_template/server/web/static/admin/js/collapse.js create mode 100644 game_template/server/web/static/admin/js/collapse.min.js create mode 100644 game_template/server/web/static/admin/js/core.js create mode 100644 game_template/server/web/static/admin/js/getElementsBySelector.js create mode 100644 game_template/server/web/static/admin/js/inlines.js create mode 100644 game_template/server/web/static/admin/js/inlines.min.js create mode 100644 game_template/server/web/static/admin/js/jquery.init.js create mode 100644 game_template/server/web/static/admin/js/jquery.js create mode 100644 game_template/server/web/static/admin/js/jquery.min.js create mode 100644 game_template/server/web/static/admin/js/prepopulate.js create mode 100644 game_template/server/web/static/admin/js/prepopulate.min.js create mode 100644 game_template/server/web/static/admin/js/timeparse.js create mode 100644 game_template/server/web/static/admin/js/urlify.js rename {src/web/static/evennia_general => game_template/server/web/static/ev}/css/prosimii-print.css (100%) rename {src/web/static/evennia_general => game_template/server/web/static/ev}/css/prosimii-screen-alt.css (100%) rename {src/web/static/evennia_general => game_template/server/web/static/ev}/css/prosimii-screen.css (100%) rename {src/web/static/evennia_general => game_template/server/web/static/ev}/images/LICENCE (100%) create mode 100644 game_template/server/web/static/ev/images/evennia_logo.png rename src/web/static/evennia_general/images/evennia_logo.png => game_template/server/web/static/ev/images/evennia_logo_small.png (100%) mode change 100755 => 100644 rename {src/web/static/evennia_general => game_template/server/web/static/ev}/images/favicon.ico (100%) create mode 100644 game_template/server/web/static/evennia_general/css/prosimii-print.css create mode 100644 game_template/server/web/static/evennia_general/css/prosimii-screen-alt.css create mode 100644 game_template/server/web/static/evennia_general/css/prosimii-screen.css create mode 100644 game_template/server/web/static/evennia_general/images/LICENCE create mode 100644 game_template/server/web/static/evennia_general/images/evennia_logo.png create mode 100644 game_template/server/web/static/evennia_general/images/evennia_logo_small.png create mode 100644 game_template/server/web/static/evennia_general/images/favicon.ico rename {src/web/webclient => game_template/server/web}/static/webclient/css/webclient.css (100%) rename {src/web/webclient => game_template/server/web}/static/webclient/js/evennia_ajax_webclient.js (100%) create mode 100644 game_template/server/web/static/webclient/js/evennia_webclient.js rename {src/web/webclient => game_template/server/web}/static/webclient/js/evennia_websocket_webclient.js (100%) rename {game/gamesrc => game_template/server}/web/static_overrides/README.md (100%) rename {game/gamesrc => game_template/server}/web/template_overrides/README.md (100%) rename {src/tests => game_template/types}/__init__.py (100%) rename {game/gamesrc/objects/examples => game_template/types}/character.py (100%) rename {game/gamesrc/objects/examples => game_template/types}/exit.py (100%) rename {game/gamesrc/objects/examples => game_template/types}/object.py (100%) rename {game/gamesrc/objects/examples => game_template/types}/player.py (100%) rename {game/gamesrc/objects/examples => game_template/types}/room.py (100%) rename {game/gamesrc/scripts/examples => game_template/types}/script.py (100%) rename {src/utils/dummyrunner => game_template/world}/__init__.py (100%) rename {game/gamesrc/world/examples => game_template/world}/batch_cmds.ev (100%) rename {game/gamesrc/world/examples => game_template/world}/prototypes.py (100%) rename {src/web => lib}/__init__.py (100%) rename {game/gamesrc => lib}/commands/__init__.py (100%) rename {src => lib}/commands/cmdhandler.py (100%) rename {src => lib}/commands/cmdparser.py (100%) rename {src => lib}/commands/cmdset.py (100%) rename {src => lib}/commands/cmdsethandler.py (100%) rename {src => lib}/commands/command.py (100%) rename {src => lib}/commands/connection_screen.py (100%) rename {src => lib}/commands/default/__init__.py (100%) rename {src => lib}/commands/default/admin.py (100%) rename {src => lib}/commands/default/batchprocess.py (100%) rename {src => lib}/commands/default/building.py (100%) rename {src => lib}/commands/default/cmdset_character.py (100%) rename {src => lib}/commands/default/cmdset_player.py (100%) rename {src => lib}/commands/default/cmdset_session.py (100%) rename {src => lib}/commands/default/cmdset_unloggedin.py (100%) rename {src => lib}/commands/default/comms.py (100%) rename {src => lib}/commands/default/general.py (100%) rename {src => lib}/commands/default/help.py (100%) rename {src => lib}/commands/default/muxcommand.py (100%) rename {src => lib}/commands/default/player.py (100%) rename {src => lib}/commands/default/syscommands.py (100%) rename {src => lib}/commands/default/system.py (100%) rename {src => lib}/commands/default/tests.py (100%) rename {src => lib}/commands/default/unloggedin.py (100%) rename {src => lib}/comms/__init__.py (100%) rename {src => lib}/comms/admin.py (100%) rename {src => lib}/comms/channelhandler.py (100%) rename {src => lib}/comms/comms.py (100%) rename {src => lib}/comms/managers.py (100%) rename {src => lib}/comms/migrations/0001_initial.py (100%) rename {src => lib}/comms/migrations/0002_msg_db_hide_from_objects.py (100%) rename {src => lib}/comms/migrations/0003_auto_20140917_0756.py (100%) rename {game/gamesrc/commands/examples => lib/comms/migrations}/__init__.py (100%) rename {src => lib}/comms/models.py (100%) rename {src => lib}/help/__init__.py (100%) rename {src => lib}/help/admin.py (100%) rename {src => lib}/help/manager.py (100%) rename {src => lib}/help/migrations/0001_initial.py (100%) rename {game/gamesrc/conf => lib/help/migrations}/__init__.py (100%) rename {src => lib}/help/models.py (100%) rename {game/gamesrc/conf/examples => lib/locks}/__init__.py (100%) rename {src => lib}/locks/lockfuncs.py (100%) rename {src => lib}/locks/lockhandler.py (100%) rename {src => lib}/locks/tests.py (100%) rename {src => lib}/objects/__init__.py (100%) rename {src => lib}/objects/admin.py (100%) rename {src => lib}/objects/manager.py (100%) rename {src => lib}/objects/migrations/0001_initial.py (100%) rename {src => lib}/objects/migrations/0002_auto_20140917_0756.py (100%) rename {src/web/utils => lib/objects/migrations}/__init__.py (100%) rename {src => lib}/objects/models.py (100%) rename {src => lib}/objects/objects.py (100%) rename {src => lib}/players/__init__.py (100%) rename {src => lib}/players/admin.py (100%) rename {src => lib}/players/bots.py (100%) rename {src => lib}/players/manager.py (100%) rename {src => lib}/players/migrations/0001_initial.py (100%) rename {game/gamesrc/objects => lib/players/migrations}/__init__.py (100%) rename {src => lib}/players/models.py (100%) rename {src => lib}/players/player.py (100%) rename {src => lib}/scripts/__init__.py (100%) rename {src => lib}/scripts/admin.py (100%) rename {src => lib}/scripts/manager.py (100%) rename {src => lib}/scripts/migrations/0001_initial.py (100%) rename {game/gamesrc/objects/examples => lib/scripts/migrations}/__init__.py (100%) rename {src => lib}/scripts/models.py (100%) rename {src => lib}/scripts/scripthandler.py (100%) rename {src => lib}/scripts/scripts.py (100%) rename {src => lib}/scripts/tickerhandler.py (100%) rename {src => lib}/server/__init__.py (100%) rename {src => lib}/server/admin.py (100%) rename {src => lib}/server/amp.py (100%) rename {src => lib}/server/caches.py (100%) rename {src => lib}/server/initial_setup.py (100%) rename {src => lib}/server/manager.py (100%) rename {src => lib}/server/migrations/0001_initial.py (100%) rename {game/gamesrc/scripts => lib/server/migrations}/__init__.py (100%) rename {src => lib}/server/models.py (100%) rename {src => lib}/server/oob_cmds.py (100%) rename {src => lib}/server/oobhandler.py (100%) rename {game/gamesrc/scripts/examples => lib/server/portal}/__init__.py (100%) rename {src => lib}/server/portal/imc2.py (100%) rename {game/gamesrc/world => lib/server/portal/imc2lib}/__init__.py (100%) rename {src => lib}/server/portal/imc2lib/imc2_ansi.py (100%) rename {src => lib}/server/portal/imc2lib/imc2_packets.py (100%) rename {src => lib}/server/portal/irc.py (100%) rename {src => lib}/server/portal/mccp.py (100%) rename {src => lib}/server/portal/msdp.py (100%) rename {src => lib}/server/portal/mssp.py (100%) rename {src => lib}/server/portal/mxp.py (100%) rename {src => lib}/server/portal/naws.py (100%) rename {src => lib}/server/portal/portal.py (100%) rename {src => lib}/server/portal/portalsessionhandler.py (100%) rename {src => lib}/server/portal/rss.py (100%) rename {src => lib}/server/portal/ssh.py (100%) rename {src => lib}/server/portal/ssl.py (100%) rename {src => lib}/server/portal/telnet.py (100%) rename {src => lib}/server/portal/ttype.py (100%) rename {src => lib}/server/portal/webclient.py (100%) rename {src => lib}/server/portal/websocket_client.py (100%) rename {src => lib}/server/server.py (100%) rename {src => lib}/server/serversession.py (100%) rename {src => lib}/server/session.py (100%) rename {src => lib}/server/sessionhandler.py (100%) rename {src => lib}/server/tests.py (100%) rename {src => lib}/server/webserver.py (100%) rename {src => lib}/settings_default.py (100%) rename {src/web/webclient => lib/tests}/__init__.py (100%) rename {src => lib}/tests/test_commands_cmdhandler.py (100%) rename {src => lib}/tests/test_commands_cmdparser.py (100%) rename {src => lib}/tests/test_commands_cmdset.py (100%) rename {src => lib}/tests/test_commands_cmdsethandler.py (100%) rename {src => lib}/tests/test_commands_command.py (100%) rename {src => lib}/tests/test_comms_channelhandler.py (100%) rename {src => lib}/tests/test_comms_comms.py (100%) rename {src => lib}/tests/test_comms_models.py (100%) rename {src => lib}/tests/test_locks_lockfuncs.py (100%) rename {src => lib}/tests/test_locks_lockhandler.py (100%) rename {src => lib}/tests/test_objects_models.py (100%) rename {src => lib}/tests/test_objects_objects.py (100%) rename {src => lib}/tests/test_players_bots.py (100%) rename {src => lib}/tests/test_players_models.py (100%) rename {src => lib}/tests/test_scripts_models.py (100%) rename {src => lib}/tests/test_scripts_scripthandler.py (100%) rename {src => lib}/tests/test_scripts_scripts.py (100%) rename {src => lib}/tests/test_scripts_tickerhandler.py (100%) rename {src => lib}/tests/test_server_amp.py (100%) rename {src => lib}/tests/test_server_caches.py (100%) rename {src => lib}/tests/test_server_initial_setup.py (100%) rename {src => lib}/tests/test_server_manager.py (100%) rename {src => lib}/tests/test_server_models.py (100%) rename {src => lib}/tests/test_server_oob_msdp.py (100%) rename {src => lib}/tests/test_server_oobhandler.py (100%) rename {src => lib}/tests/test_server_server.py (100%) rename {src => lib}/tests/test_server_serversession.py (100%) rename {src => lib}/tests/test_server_session.py (100%) rename {src => lib}/tests/test_server_webserver.py (100%) rename {src => lib}/tests/test_typeclasses_models.py (100%) rename {src => lib}/tests/test_typeclasses_typeclass.py (100%) rename {src => lib}/tests/test_utils_ansi.py (100%) rename {src => lib}/tests/test_utils_ansi_new.py (100%) rename {src => lib}/tests/test_utils_batchprocessors.py (100%) rename {src => lib}/tests/test_utils_create.py (100%) rename {src => lib}/tests/test_utils_dbserialize.py (100%) rename {src => lib}/tests/test_utils_evform.py (100%) rename {src => lib}/tests/test_utils_evtable.py (100%) rename {src => lib}/tests/test_utils_gametime.py (100%) rename {src => lib}/tests/test_utils_logger.py (100%) rename {src => lib}/tests/test_utils_picklefield.py (100%) rename {src => lib}/tests/test_utils_prettytable.py (100%) rename {src => lib}/tests/test_utils_search.py (100%) rename {src => lib}/tests/test_utils_tests.py (100%) rename {src => lib}/tests/test_utils_text2html.py (100%) rename {src => lib}/tests/test_utils_utils.py (100%) rename {src/commands => lib/typeclasses}/__init__.py (100%) rename {src => lib}/typeclasses/admin.py (100%) rename {src => lib}/typeclasses/attributes.py (100%) rename {src => lib}/typeclasses/django_new_patch.py (100%) rename {src => lib}/typeclasses/managers.py (100%) rename {src => lib}/typeclasses/migrations/0001_initial.py (100%) rename {src/comms => lib/typeclasses}/migrations/__init__.py (100%) rename {src => lib}/typeclasses/models.py (100%) rename {src => lib}/typeclasses/tags.py (100%) rename {src => lib}/utils/__init__.py (100%) rename {src => lib}/utils/ansi.py (100%) rename {src => lib}/utils/batchprocessors.py (100%) rename {src => lib}/utils/create.py (100%) rename {src => lib}/utils/dbserialize.py (100%) rename {src => lib}/utils/dummyrunner/README.txt (100%) rename game/logs/.empty => lib/utils/dummyrunner/__init__.py (100%) rename {src => lib}/utils/dummyrunner/dummyrunner.py (100%) rename {src => lib}/utils/dummyrunner/dummyrunner_actions.py (100%) rename {src => lib}/utils/dummyrunner/memplot.py (100%) rename {src => lib}/utils/dummyrunner/test_queries.py (100%) rename {src => lib}/utils/evennia-mode.el (100%) rename {src => lib}/utils/evform.py (100%) rename {src => lib}/utils/evform_test.py (100%) rename {src => lib}/utils/evtable.py (100%) rename {src => lib}/utils/gametime.py (100%) rename {src => lib}/utils/idmapper/EVENNIA.txt (100%) rename {src => lib}/utils/idmapper/LICENSE (100%) rename {src => lib}/utils/idmapper/README.rst (100%) rename {src => lib}/utils/idmapper/__init__.py (100%) rename {src => lib}/utils/idmapper/base.py (100%) rename {src => lib}/utils/idmapper/manager.py (100%) rename {src => lib}/utils/idmapper/models.py (100%) rename {src => lib}/utils/idmapper/tests.py (100%) rename {src => lib}/utils/inlinefunc.py (100%) rename {src => lib}/utils/logger.py (100%) rename {src => lib}/utils/picklefield.py (100%) rename {src => lib}/utils/prettytable.py (100%) rename {src => lib}/utils/search.py (100%) rename {src => lib}/utils/spawner.py (100%) rename {src => lib}/utils/tests.py (100%) rename {src => lib}/utils/text2html.py (100%) rename {src => lib}/utils/txws.py (100%) rename {src => lib}/utils/utils.py (100%) create mode 100644 lib/web/__init__.py create mode 100644 lib/web/static/evennia_general/css/prosimii-print.css create mode 100644 lib/web/static/evennia_general/css/prosimii-screen-alt.css create mode 100644 lib/web/static/evennia_general/css/prosimii-screen.css create mode 100644 lib/web/static/evennia_general/images/LICENCE create mode 100755 lib/web/static/evennia_general/images/evennia_logo.png create mode 100644 lib/web/static/evennia_general/images/favicon.ico rename {src => lib}/web/templates/evennia_general/evennia_admin.html (100%) rename {src => lib}/web/templates/evennia_general/index.html (100%) rename {src => lib}/web/templates/evennia_general/tbi.html (100%) rename {src => lib}/web/templates/prosimii/404.html (100%) rename {src => lib}/web/templates/prosimii/500.html (100%) rename {src => lib}/web/templates/prosimii/base.html (100%) rename {src => lib}/web/templates/prosimii/flatpages/default.html (100%) rename {src => lib}/web/templates/prosimii/registration/logged_out.html (100%) rename {src => lib}/web/templates/prosimii/registration/login.html (100%) rename {src => lib}/web/urls.py (100%) create mode 100644 lib/web/utils/__init__.py rename {src => lib}/web/utils/apache_wsgi.conf (100%) rename {src => lib}/web/utils/backends.py (100%) rename {src => lib}/web/utils/evennia_modpy_apache.conf (100%) rename {src => lib}/web/utils/evennia_wsgi_apache.conf (100%) rename {src => lib}/web/utils/general_context.py (100%) rename {src => lib}/web/views.py (100%) create mode 100644 lib/web/webclient/__init__.py rename {src => lib}/web/webclient/models.py (100%) create mode 100644 lib/web/webclient/static/webclient/css/webclient.css create mode 100644 lib/web/webclient/static/webclient/js/evennia_ajax_webclient.js create mode 100644 lib/web/webclient/static/webclient/js/evennia_websocket_webclient.js rename {src => lib}/web/webclient/templates/webclient.html (100%) rename {src => lib}/web/webclient/urls.py (100%) rename {src => lib}/web/webclient/views.py (100%) delete mode 100644 src/help/migrations/__init__.py delete mode 100644 src/locks/__init__.py delete mode 100644 src/players/migrations/__init__.py delete mode 100644 src/scripts/migrations/__init__.py delete mode 100644 src/server/migrations/__init__.py delete mode 100644 src/server/portal/__init__.py delete mode 100644 src/server/portal/imc2lib/__init__.py delete mode 100644 src/typeclasses/__init__.py delete mode 100644 src/typeclasses/migrations/__init__.py diff --git a/game/evennia.py b/bin/evennia.py similarity index 100% rename from game/evennia.py rename to bin/evennia.py diff --git a/game/runner.py b/bin/runner.py similarity index 100% rename from game/runner.py rename to bin/runner.py diff --git a/game/gamesrc/web/__init__.py b/contrib/red_button/__init__.py similarity index 100% rename from game/gamesrc/web/__init__.py rename to contrib/red_button/__init__.py diff --git a/game/gamesrc/commands/examples/cmdset_red_button.py b/contrib/red_button/cmdset_red_button.py similarity index 100% rename from game/gamesrc/commands/examples/cmdset_red_button.py rename to contrib/red_button/cmdset_red_button.py diff --git a/game/gamesrc/objects/examples/red_button.py b/contrib/red_button/red_button.py similarity index 100% rename from game/gamesrc/objects/examples/red_button.py rename to contrib/red_button/red_button.py diff --git a/game/gamesrc/scripts/examples/red_button_scripts.py b/contrib/red_button/red_button_scripts.py similarity index 100% rename from game/gamesrc/scripts/examples/red_button_scripts.py rename to contrib/red_button/red_button_scripts.py diff --git a/game/gamesrc/scripts/examples/bodyfunctions.py b/game/gamesrc/scripts/examples/bodyfunctions.py deleted file mode 100644 index 56534d7be..000000000 --- a/game/gamesrc/scripts/examples/bodyfunctions.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Example script for testing. This adds a simple timer that -has your character make observations and noices at irregular -intervals. - -To test, use - @script me = examples.bodyfunctions.BodyFunctions - -The script will only send messages to the object it -is stored on, so make sure to put it on yourself -or you won't see any messages! - -""" -import random -from ev import Script - -class BodyFunctions(Script): - """ - This class defines the script itself - """ - - def at_script_creation(self): - self.key = "bodyfunction" - self.desc = "Adds various timed events to a character." - self.interval = 20 # seconds - #self.repeats = 5 # repeat only a certain number of times - self.start_delay = True # wait self.interval until first call - #self.persistent = True - - def at_repeat(self): - """ - This gets called every self.interval seconds. We make - a random check here so as to only return 33% of the time. - """ - - if random.random() < 0.66: - # no message this time - return - rand = random.random() - # return a random message - if rand < 0.1: - string = "You tap your foot, looking around." - elif rand < 0.2: - string = "You have an itch. Hard to reach too." - elif rand < 0.3: - string = "You think you hear someone behind you. ... but when you look there's noone there." - elif rand < 0.4: - string = "You inspect your fingernails. Nothing to report." - elif rand < 0.5: - string = "You cough discreetly into your hand." - elif rand < 0.6: - string = "You scratch your head, looking around." - elif rand < 0.7: - string = "You blink, forgetting what it was you were going to do." - elif rand < 0.8: - string = "You feel lonely all of a sudden." - elif rand < 0.9: - string = "You get a great idea. Of course you won't tell anyone." - else: - string = "You suddenly realize how much you love Evennia!" - - # echo the message to the object - self.obj.msg(string) diff --git a/game/gamesrc/world/examples/batch_code.py b/game/gamesrc/world/examples/batch_code.py deleted file mode 100644 index fe43209a4..000000000 --- a/game/gamesrc/world/examples/batch_code.py +++ /dev/null @@ -1,85 +0,0 @@ -# -# Batchcode script -# -# -# The Batch-code processor accepts full python modules (e.g. "batch.py") that -# looks identical to normal Python files with a few exceptions that allows them -# to the executed in blocks. This way of working assures a sequential execution -# of the file and allows for features like stepping from block to block -# (without executing those coming before), as well as automatic deletion -# of created objects etc. You can however also run a batch-code python file -# directly using Python (and can also be de). - -# Code blocks are separated by python comments starting with special code words. - -# #HEADER - this denotes commands global to the entire file, such as -# import statements and global variables. They will -# automatically be made available for each block. Observe -# that changes to these variables made in one block is not -# preserved between blocks!) -# #CODE (infotext) [objname, objname, ...] - This designates a code block that -# will be executed like a stand-alone piece of code together with -# any #HEADER defined. -# infotext is a describing text about what goes in in this block. -# It will be shown by the batchprocessing command. -# s mark the (variable-)names of objects created in -# the code, and which may be auto-deleted by the processor if -# desired (such as when debugging the script). E.g., if the code -# contains the command myobj = create.create_object(...), you could -# put 'myobj' in the #CODE header regardless of what the created -# object is actually called in-game. -# #INSERT filename - this includes another code batch file. The named file will -# be loaded and run at this point. Note that code from the inserted -# file will NOT share #HEADERs with the importing file, but will -# only use the headers in the importing file. Make sure to not -# create a cyclic import here! - -# The following variable is automatically made available for the script: - -# caller - the object executing the script -# - - -#HEADER - -# everything in this block will be appended to the beginning of -# all other #CODE blocks when they are executed. - -from ev import create_object, search_object -from game.gamesrc.objects.examples import red_button -from ev import Object - -limbo = search_object('Limbo')[0] - - -#CODE (create red button) - -# This is the first code block. Within each block, python -# code works as normal. Note how we make use if imports and -# 'limbo' defined in the #HEADER block. This block's header -# offers no information about red_button variable, so it -# won't be able to be deleted in debug mode. - -# create a red button in limbo -red_button = create_object(red_button.RedButton, key="Red button", - location=limbo, aliases=["button"]) - -# we take a look at what we created -caller.msg("A %s was created." % red_button.key) - -#CODE (create table and chair) table, chair - -# this code block has 'table' and 'chair' set as deletable -# objects. This means that when the batchcode processor runs in -# testing mode, objects created in these variables will be deleted -# again (so as to avoid duplicate objects when testing the script many -# times). - -# the python variables we assign to must match the ones given in the -# header for the system to be able to delete them afterwards during a -# debugging run. -table = create_object(Object, key="Table", location=limbo) -chair = create_object(Object, key="Chair", location=limbo) - -string = "A %s and %s were created. If debug was active, they were deleted again." -caller.msg(string % (table, chair)) diff --git a/game/logs/README b/game/logs/README deleted file mode 100644 index 0c62c5425..000000000 --- a/game/logs/README +++ /dev/null @@ -1,8 +0,0 @@ -This directory holds log files for the server. - -portal.log - logs from Portal (if run in daemon mode) -server.log - logs from Server (if run in daemon mode) -http_requests.log - http request info (if running webserver/client) - -All log files are rotated to their *.old version when the server is fully -stopped, then restarted again (i.e. not on normal reloads). diff --git a/game/manage.py b/game/manage.py deleted file mode 100755 index 3ce38e736..000000000 --- a/game/manage.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python -""" -Set up the evennia system. A first startup consists of giving -the command './manage syncdb' to setup the system and create -the database. -""" - -import sys -import os - -# Tack on the root evennia directory to the python path. -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -#------------------------------------------------------------ -# Check so session file exists in the current dir- if not, create it. -#------------------------------------------------------------ - -_CREATED_SETTINGS = False -if not os.path.exists('settings.py'): - # If settings.py doesn't already exist, create it and populate it with some - # basic stuff. - - # make random secret_key. - import random - import string - secret_key = list((string.letters + - string.digits + string.punctuation).replace("\\", "").replace("'", '"')) - random.shuffle(secret_key) - secret_key = "".join(secret_key[:40]) - - settings_file = open('settings.py', 'w') - _CREATED_SETTINGS = True - - string = \ - """ -###################################################################### -# Evennia MU* server configuration file -# -# You may customize your setup by copy&pasting the variables you want -# to change from the master config file src/settings_default.py to -# this file. Try to *only* copy over things you really need to customize -# and do *not* make any changes to src/settings_default.py directly. -# This way you'll always have a sane default to fall back on -# (also, the master config file may change with server updates). -# -###################################################################### - -from src.settings_default import * - -###################################################################### -# Custom settings -###################################################################### - - -###################################################################### -# SECRET_KEY was randomly seeded when settings.py was first created. -# Don't share this with anybody. It is used by Evennia to handle -# cryptographic hashing for things like cookies on the web side. -###################################################################### -SECRET_KEY = '%s' - -""" % secret_key - - settings_file.write(string) - settings_file.close() - - # obs - this string cannot be under i18n since settings didn't exist yet. - print """ - Welcome to Evennia! - - This looks like your first startup, so we created a fresh - game/settings.py file for you. No database has yet been created. - You may edit the settings file now if you like, but you don't - have to touch anything if you just want to quickly get started. - - Once you are ready to continue, run - - python manage.py migrate - - This will initialize the database. When that is done you can - start Evennia itself with - - python evennia.py -i start - - The first time the server starts it will set things up for you. - Make sure to create a superuser when asked. The superuser's - email-address does not have to exist. - """ - - -#------------------------------------------------------------ -# Test the import of the settings file -#------------------------------------------------------------ -try: - from game import settings -except Exception: - import traceback - string = "\n" + traceback.format_exc() - - # note - if this fails, ugettext will also fail, so we cannot translate this string. - - string += """\n - Error: Couldn't import the file 'settings.py' in the directory containing %(file)r. - There are usually two reasons for this: - 1) The settings module contains errors. Review the traceback above to resolve the - problem, then try again. - 2) If you get errors on finding DJANGO_SETTINGS_MODULE you might have set up django - wrong in some way. If you run a virtual machine, it might be worth to restart it - to see if this resolves the issue. Evennia should not require you to define any - environment variables manually. - """ % {'file': __file__} - print string - sys.exit(1) - -os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' - -#------------------------------------------------------------ -# This is run only if the module is called as a program -#------------------------------------------------------------ -if __name__ == "__main__": - - if _CREATED_SETTINGS: - # if settings were created, info has already been printed. - sys.exit() - - # run the standard django manager, if dependencies match - from src.utils.utils import check_evennia_dependencies - if check_evennia_dependencies(): - if len(sys.argv) > 1 and sys.argv[1] in ('runserver', 'testserver'): - print """ - WARNING: There is no need to run the Django development - webserver to test out Evennia web features (the web client - will in fact not work since the Django test server knows - nothing about MUDs). Instead, just start Evennia with the - webserver component active (this is the default). - """ - print sys.argv - from django.core.management import execute_from_command_line - execute_from_command_line(sys.argv) diff --git a/game/gamesrc/world/examples/__init__.py b/game_template/__init__.py similarity index 100% rename from game/gamesrc/world/examples/__init__.py rename to game_template/__init__.py diff --git a/src/__init__.py b/game_template/commands/__init__.py similarity index 100% rename from src/__init__.py rename to game_template/commands/__init__.py diff --git a/game/gamesrc/commands/examples/cmdset.py b/game_template/commands/cmdset.py similarity index 100% rename from game/gamesrc/commands/examples/cmdset.py rename to game_template/commands/cmdset.py diff --git a/game/gamesrc/commands/examples/command.py b/game_template/commands/command.py similarity index 100% rename from game/gamesrc/commands/examples/command.py rename to game_template/commands/command.py diff --git a/game/__init__.py b/game_template/server/__init__.py similarity index 100% rename from game/__init__.py rename to game_template/server/__init__.py diff --git a/game/gamesrc/__init__.py b/game_template/server/conf/__init__.py similarity index 100% rename from game/gamesrc/__init__.py rename to game_template/server/conf/__init__.py diff --git a/game/gamesrc/conf/examples/at_initial_setup.py b/game_template/server/conf/at_initial_setup.py similarity index 100% rename from game/gamesrc/conf/examples/at_initial_setup.py rename to game_template/server/conf/at_initial_setup.py diff --git a/game/gamesrc/conf/examples/at_server_startstop.py b/game_template/server/conf/at_server_startstop.py similarity index 100% rename from game/gamesrc/conf/examples/at_server_startstop.py rename to game_template/server/conf/at_server_startstop.py diff --git a/game/gamesrc/conf/examples/connection_screens.py b/game_template/server/conf/connection_screens.py similarity index 100% rename from game/gamesrc/conf/examples/connection_screens.py rename to game_template/server/conf/connection_screens.py diff --git a/game/gamesrc/conf/examples/lockfuncs.py b/game_template/server/conf/lockfuncs.py similarity index 100% rename from game/gamesrc/conf/examples/lockfuncs.py rename to game_template/server/conf/lockfuncs.py diff --git a/game/gamesrc/conf/examples/mssp.py b/game_template/server/conf/mssp.py similarity index 100% rename from game/gamesrc/conf/examples/mssp.py rename to game_template/server/conf/mssp.py diff --git a/game/gamesrc/conf/examples/oobfuncs.py b/game_template/server/conf/oobfuncs.py similarity index 100% rename from game/gamesrc/conf/examples/oobfuncs.py rename to game_template/server/conf/oobfuncs.py diff --git a/game/gamesrc/conf/examples/portal_services_plugin.py b/game_template/server/conf/portal_services_plugin.py similarity index 100% rename from game/gamesrc/conf/examples/portal_services_plugin.py rename to game_template/server/conf/portal_services_plugin.py diff --git a/game/gamesrc/conf/examples/server_services_plugin.py b/game_template/server/conf/server_services_plugin.py similarity index 100% rename from game/gamesrc/conf/examples/server_services_plugin.py rename to game_template/server/conf/server_services_plugin.py diff --git a/src/objects/migrations/__init__.py b/game_template/server/web/__init__.py similarity index 100% rename from src/objects/migrations/__init__.py rename to game_template/server/web/__init__.py diff --git a/game/gamesrc/web/examples/__init__.py b/game_template/server/web/examples/__init__.py similarity index 100% rename from game/gamesrc/web/examples/__init__.py rename to game_template/server/web/examples/__init__.py diff --git a/game/gamesrc/web/examples/urls.py b/game_template/server/web/examples/urls.py similarity index 100% rename from game/gamesrc/web/examples/urls.py rename to game_template/server/web/examples/urls.py diff --git a/game/gamesrc/web/media/README.md b/game_template/server/web/media/README.md similarity index 100% rename from game/gamesrc/web/media/README.md rename to game_template/server/web/media/README.md diff --git a/game/gamesrc/web/static/README.md b/game_template/server/web/static/README.md similarity index 100% rename from game/gamesrc/web/static/README.md rename to game_template/server/web/static/README.md diff --git a/game_template/server/web/static/admin/css/base.css b/game_template/server/web/static/admin/css/base.css new file mode 100644 index 000000000..995183e23 --- /dev/null +++ b/game_template/server/web/static/admin/css/base.css @@ -0,0 +1,847 @@ +/* + DJANGO Admin styles +*/ + +body { + margin: 0; + padding: 0; + font-size: 12px; + font-family: "Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; + color: #333; + background: #fff; +} + +/* LINKS */ + +a:link, a:visited { + color: #5b80b2; + text-decoration: none; +} + +a:hover { + color: #036; +} + +a img { + border: none; +} + +a.section:link, a.section:visited { + color: #fff; + text-decoration: none; +} + +/* GLOBAL DEFAULTS */ + +p, ol, ul, dl { + margin: .2em 0 .8em 0; +} + +p { + padding: 0; + line-height: 140%; +} + +h1,h2,h3,h4,h5 { + font-weight: bold; +} + +h1 { + font-size: 18px; + color: #666; + padding: 0 6px 0 0; + margin: 0 0 .2em 0; +} + +h2 { + font-size: 16px; + margin: 1em 0 .5em 0; +} + +h2.subhead { + font-weight: normal; + margin-top: 0; +} + +h3 { + font-size: 14px; + margin: .8em 0 .3em 0; + color: #666; + font-weight: bold; +} + +h4 { + font-size: 12px; + margin: 1em 0 .8em 0; + padding-bottom: 3px; +} + +h5 { + font-size: 10px; + margin: 1.5em 0 .5em 0; + color: #666; + text-transform: uppercase; + letter-spacing: 1px; +} + +ul li { + list-style-type: square; + padding: 1px 0; +} + +ul.plainlist { + margin-left: 0 !important; +} + +ul.plainlist li { + list-style-type: none; +} + +li ul { + margin-bottom: 0; +} + +li, dt, dd { + font-size: 11px; + line-height: 14px; +} + +dt { + font-weight: bold; + margin-top: 4px; +} + +dd { + margin-left: 0; +} + +form { + margin: 0; + padding: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +blockquote { + font-size: 11px; + color: #777; + margin-left: 2px; + padding-left: 10px; + border-left: 5px solid #ddd; +} + +code, pre { + font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; + background: inherit; + color: #666; + font-size: 11px; +} + +pre.literal-block { + margin: 10px; + background: #eee; + padding: 6px 8px; +} + +code strong { + color: #930; +} + +hr { + clear: both; + color: #eee; + background-color: #eee; + height: 1px; + border: none; + margin: 0; + padding: 0; + font-size: 1px; + line-height: 1px; +} + +/* TEXT STYLES & MODIFIERS */ + +.small { + font-size: 11px; +} + +.tiny { + font-size: 10px; +} + +p.tiny { + margin-top: -2px; +} + +.mini { + font-size: 9px; +} + +p.mini { + margin-top: -3px; +} + +.help, p.help { + font-size: 10px !important; + color: #999; +} + +img.help-tooltip { + cursor: help; +} + +p img, h1 img, h2 img, h3 img, h4 img, td img { + vertical-align: middle; +} + +.quiet, a.quiet:link, a.quiet:visited { + color: #999 !important; + font-weight: normal !important; +} + +.quiet strong { + font-weight: bold !important; +} + +.float-right { + float: right; +} + +.float-left { + float: left; +} + +.clear { + clear: both; +} + +.align-left { + text-align: left; +} + +.align-right { + text-align: right; +} + +.example { + margin: 10px 0; + padding: 5px 10px; + background: #efefef; +} + +.nowrap { + white-space: nowrap; +} + +/* TABLES */ + +table { + border-collapse: collapse; + border-color: #ccc; +} + +td, th { + font-size: 11px; + line-height: 13px; + border-bottom: 1px solid #eee; + vertical-align: top; + padding: 5px; + font-family: "Lucida Grande", Verdana, Arial, sans-serif; +} + +th { + text-align: left; + font-size: 12px; + font-weight: bold; +} + +thead th, +tfoot td { + color: #666; + padding: 2px 5px; + font-size: 11px; + background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; + border-left: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +tfoot td { + border-bottom: none; + border-top: 1px solid #ddd; +} + +thead th:first-child, +tfoot td:first-child { + border-left: none !important; +} + +thead th.optional { + font-weight: normal !important; +} + +fieldset table { + border-right: 1px solid #eee; +} + +tr.row-label td { + font-size: 9px; + padding-top: 2px; + padding-bottom: 0; + border-bottom: none; + color: #666; + margin-top: -1px; +} + +tr.alt { + background: #f6f6f6; +} + +.row1 { + background: #EDF3FE; +} + +.row2 { + background: #fff; +} + +/* SORTABLE TABLES */ + +thead th { + padding: 2px 5px; + line-height: normal; +} + +thead th a:link, thead th a:visited { + color: #666; +} + +thead th.sorted { + background: #c5c5c5 url(../img/nav-bg-selected.gif) top left repeat-x; +} + +thead th.sorted .text { + padding-right: 42px; +} + +table thead th .text span { + padding: 2px 5px; + display:block; +} + +table thead th .text a { + display: block; + cursor: pointer; + padding: 2px 5px; +} + +table thead th.sortable:hover { + background: #fff url(../img/nav-bg-reverse.gif) 0 -5px repeat-x; +} + +thead th.sorted a.sortremove { + visibility: hidden; +} + +table thead th.sorted:hover a.sortremove { + visibility: visible; +} + +table thead th.sorted .sortoptions { + display: block; + padding: 4px 5px 0 5px; + float: right; + text-align: right; +} + +table thead th.sorted .sortpriority { + font-size: .8em; + min-width: 12px; + text-align: center; + vertical-align: top; +} + +table thead th.sorted .sortoptions a { + width: 14px; + height: 12px; + display: inline-block; +} + +table thead th.sorted .sortoptions a.sortremove { + background: url(../img/sorting-icons.gif) -4px -5px no-repeat; +} + +table thead th.sorted .sortoptions a.sortremove:hover { + background: url(../img/sorting-icons.gif) -4px -27px no-repeat; +} + +table thead th.sorted .sortoptions a.ascending { + background: url(../img/sorting-icons.gif) -5px -50px no-repeat; +} + +table thead th.sorted .sortoptions a.ascending:hover { + background: url(../img/sorting-icons.gif) -5px -72px no-repeat; +} + +table thead th.sorted .sortoptions a.descending { + background: url(../img/sorting-icons.gif) -5px -94px no-repeat; +} + +table thead th.sorted .sortoptions a.descending:hover { + background: url(../img/sorting-icons.gif) -5px -115px no-repeat; +} + +/* ORDERABLE TABLES */ + +table.orderable tbody tr td:hover { + cursor: move; +} + +table.orderable tbody tr td:first-child { + padding-left: 14px; + background-image: url(../img/nav-bg-grabber.gif); + background-repeat: repeat-y; +} + +table.orderable-initalized .order-cell, body>tr>td.order-cell { + display: none; +} + +/* FORM DEFAULTS */ + +input, textarea, select, .form-row p { + margin: 2px 0; + padding: 2px 3px; + vertical-align: middle; + font-family: "Lucida Grande", Verdana, Arial, sans-serif; + font-weight: normal; + font-size: 11px; +} + +textarea { + vertical-align: top !important; +} + +input[type=text], input[type=password], input[type=email], input[type=url], input[type=number], +textarea, select, .vTextField { + border: 1px solid #ccc; +} + +/* FORM BUTTONS */ + +.button, input[type=submit], input[type=button], .submit-row input { + background: #fff url(../img/nav-bg.gif) bottom repeat-x; + padding: 3px 5px; + color: black; + border: 1px solid #bbb; + border-color: #ddd #aaa #aaa #ddd; +} + +.button:active, input[type=submit]:active, input[type=button]:active { + background-image: url(../img/nav-bg-reverse.gif); + background-position: top; +} + +.button[disabled], input[type=submit][disabled], input[type=button][disabled] { + background-image: url(../img/nav-bg.gif); + background-position: bottom; + opacity: 0.4; +} + +.button.default, input[type=submit].default, .submit-row input.default { + border: 2px solid #5b80b2; + background: #7CA0C7 url(../img/default-bg.gif) bottom repeat-x; + font-weight: bold; + color: #fff; + float: right; +} + +.button.default:active, input[type=submit].default:active { + background-image: url(../img/default-bg-reverse.gif); + background-position: top; +} + +.button[disabled].default, input[type=submit][disabled].default, input[type=button][disabled].default { + background-image: url(../img/default-bg.gif); + background-position: bottom; + opacity: 0.4; +} + + +/* MODULES */ + +.module { + border: 1px solid #ccc; + margin-bottom: 5px; + background: #fff; +} + +.module p, .module ul, .module h3, .module h4, .module dl, .module pre { + padding-left: 10px; + padding-right: 10px; +} + +.module blockquote { + margin-left: 12px; +} + +.module ul, .module ol { + margin-left: 1.5em; +} + +.module h3 { + margin-top: .6em; +} + +.module h2, .module caption, .inline-group h2 { + margin: 0; + padding: 2px 5px 3px 5px; + font-size: 11px; + text-align: left; + font-weight: bold; + background: #7CA0C7 url(../img/default-bg.gif) top left repeat-x; + color: #fff; +} + +.module table { + border-collapse: collapse; +} + +/* MESSAGES & ERRORS */ + +ul.messagelist { + padding: 0; + margin: 0; +} + +ul.messagelist li { + font-size: 12px; + font-weight: bold; + display: block; + padding: 5px 5px 4px 25px; + margin: 0 0 3px 0; + border-bottom: 1px solid #ddd; + color: #666; + background: #dfd url(../img/icon_success.gif) 5px .3em no-repeat; +} + +ul.messagelist li.warning { + background: #ffc url(../img/icon_alert.gif) 5px .3em no-repeat; +} + +ul.messagelist li.error { + background: #ffefef url(../img/icon_error.gif) 5px .3em no-repeat; +} + +.errornote { + font-size: 12px !important; + font-weight: bold; + display: block; + padding: 5px 5px 4px 25px; + margin: 0 0 3px 0; + border: 1px solid #c22; + color: #c11; + background: #ffefef url(../img/icon_error.gif) 5px .38em no-repeat; +} + +.errornote, ul.errorlist { + border-radius: 1px; +} + +ul.errorlist { + margin: 0 0 4px !important; + padding: 0 !important; + color: #fff; + background: #c11; +} + +ul.errorlist li { + font-size: 12px !important; + display: block; + padding: 5px 5px 4px 7px; + margin: 3px 0 0 0; +} + +ul.errorlist li:first-child { + margin-top: 0; +} + +ul.errorlist li a { + color: #fff; + text-decoration: underline; +} + +td ul.errorlist { + margin: 0 !important; + padding: 0 !important; +} + +td ul.errorlist li { + margin: 0 !important; +} + +.errors, .form-row.errors { + background: #ffefef; +} + +.form-row.errors { + border: 1px solid #c22; + margin: -1px; +} + +.errors input, .errors select, .errors textarea { + border: 1px solid #c11; +} + +div.system-message { + background: #ffc; + margin: 10px; + padding: 6px 8px; + font-size: .8em; +} + +div.system-message p.system-message-title { + padding: 4px 5px 4px 25px; + margin: 0; + color: #c11; + background: #ffefef url(../img/icon_error.gif) 5px .3em no-repeat; +} + +.description { + font-size: 12px; + padding: 5px 0 0 12px; +} + +/* BREADCRUMBS */ + +div.breadcrumbs { + background: #fff url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; + padding: 2px 8px 3px 8px; + font-size: 11px; + color: #999; + border-top: 1px solid #fff; + border-bottom: 1px solid #ddd; + text-align: left; +} + +/* ACTION ICONS */ + +.addlink { + padding-left: 12px; + background: url(../img/icon_addlink.gif) 0 .2em no-repeat; +} + +.changelink { + padding-left: 12px; + background: url(../img/icon_changelink.gif) 0 .2em no-repeat; +} + +.deletelink { + padding-left: 12px; + background: url(../img/icon_deletelink.gif) 0 .25em no-repeat; +} + +a.deletelink:link, a.deletelink:visited { + color: #CC3434; +} + +a.deletelink:hover { + color: #993333; +} + +/* OBJECT TOOLS */ + +.object-tools { + font-size: 10px; + font-weight: bold; + font-family: Arial,Helvetica,sans-serif; + padding-left: 0; + float: right; + position: relative; + margin-top: -2.4em; + margin-bottom: -2em; +} + +.form-row .object-tools { + margin-top: 5px; + margin-bottom: 5px; + float: none; + height: 2em; + padding-left: 3.5em; +} + +.object-tools li { + display: block; + float: left; + margin-left: 5px; + height: 16px; +} + +.object-tools a { + border-radius: 15px; +} + +.object-tools a:link, .object-tools a:visited { + display: block; + float: left; + color: #fff; + padding: .2em 10px; + background: #999; +} + +.object-tools a:hover, .object-tools li:hover a { + background-color: #5b80b2; +} + +.object-tools a.viewsitelink, .object-tools a.golink { + background: #999 url(../img/tooltag-arrowright.png) 95% center no-repeat; + padding-right: 26px; +} + +.object-tools a.addlink { + background: #999 url(../img/tooltag-add.png) 95% center no-repeat; + padding-right: 26px; +} + +/* OBJECT HISTORY */ + +table#change-history { + width: 100%; +} + +table#change-history tbody th { + width: 16em; +} + +/* PAGE STRUCTURE */ + +#container { + position: relative; + width: 100%; + min-width: 760px; + padding: 0; +} + +#content { + margin: 10px 15px; +} + +#content-main { + float: left; + width: 100%; +} + +#content-related { + float: right; + width: 18em; + position: relative; + margin-right: -19em; +} + +#footer { + clear: both; + padding: 10px; +} + +/* COLUMN TYPES */ + +.colMS { + margin-right: 20em !important; +} + +.colSM { + margin-left: 20em !important; +} + +.colSM #content-related { + float: left; + margin-right: 0; + margin-left: -19em; +} + +.colSM #content-main { + float: right; +} + +.popup .colM { + width: 95%; +} + +.subcol { + float: left; + width: 46%; + margin-right: 15px; +} + +.dashboard #content { + width: 500px; +} + +/* HEADER */ + +#header { + width: 100%; + background: #417690; + color: #ffc; + overflow: hidden; +} + +#header a:link, #header a:visited { + color: #fff; +} + +#header a:hover { + text-decoration: underline; +} + +#branding { + float: left; +} +#branding h1 { + padding: 0 10px; + font-size: 18px; + margin: 8px 0; + font-weight: normal; +} + +#branding h1, #branding h1 a:link, #branding h1 a:visited { + color: #f4f379; +} + +#branding h2 { + padding: 0 10px; + font-size: 14px; + margin: -8px 0 8px 0; + font-weight: normal; + color: #ffc; +} + +#branding a:hover { + text-decoration: none; +} + +#user-tools { + float: right; + padding: 1.2em 10px; + font-size: 11px; + text-align: right; +} + +/* SIDEBAR */ + +#content-related h3 { + font-size: 12px; + color: #666; + margin-bottom: 3px; +} + +#content-related h4 { + font-size: 11px; +} + +#content-related .module h2 { + background: #eee url(../img/nav-bg.gif) bottom left repeat-x; + color: #666; +} diff --git a/game_template/server/web/static/admin/css/changelists.css b/game_template/server/web/static/admin/css/changelists.css new file mode 100644 index 000000000..28021d02b --- /dev/null +++ b/game_template/server/web/static/admin/css/changelists.css @@ -0,0 +1,293 @@ +/* CHANGELISTS */ + +#changelist { + position: relative; + width: 100%; +} + +#changelist table { + width: 100%; +} + +.change-list .hiddenfields { display:none; } + +.change-list .filtered table { + border-right: 1px solid #ddd; +} + +.change-list .filtered { + min-height: 400px; +} + +.change-list .filtered { + background: white url(../img/changelist-bg.gif) top right repeat-y !important; +} + +.change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { + margin-right: 160px !important; + width: auto !important; +} + +.change-list .filtered table tbody th { + padding-right: 1em; +} + +#changelist-form .results { + overflow-x: auto; +} + +#changelist .toplinks { + border-bottom: 1px solid #ccc !important; +} + +#changelist .paginator { + color: #666; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; + background: white url(../img/nav-bg.gif) 0 180% repeat-x; + overflow: hidden; +} + +.change-list .filtered .paginator { + border-right: 1px solid #ddd; +} + +/* CHANGELIST TABLES */ + +#changelist table thead th { + padding: 0; + white-space: nowrap; + vertical-align: middle; +} + +#changelist table thead th.action-checkbox-column { + width: 1.5em; + text-align: center; +} + +#changelist table tbody td, #changelist table tbody th { + border-left: 1px solid #ddd; +} + +#changelist table tbody td:first-child, #changelist table tbody th:first-child { + border-left: 0; + border-right: 1px solid #ddd; +} + +#changelist table tbody td.action-checkbox { + text-align:center; +} + +#changelist table tfoot { + color: #666; +} + +/* TOOLBAR */ + +#changelist #toolbar { + padding: 3px; + border-bottom: 1px solid #ddd; + background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; + color: #666; +} + +#changelist #toolbar form input { + font-size: 11px; + padding: 1px 2px; +} + +#changelist #toolbar form #searchbar { + padding: 2px; +} + +#changelist #changelist-search img { + vertical-align: middle; +} + +/* FILTER COLUMN */ + +#changelist-filter { + position: absolute; + top: 0; + right: 0; + z-index: 1000; + width: 160px; + border-left: 1px solid #ddd; + background: #efefef; + margin: 0; +} + +#changelist-filter h2 { + font-size: 11px; + padding: 2px 5px; + border-bottom: 1px solid #ddd; +} + +#changelist-filter h3 { + font-size: 12px; + margin-bottom: 0; +} + +#changelist-filter ul { + padding-left: 0; + margin-left: 10px; +} + +#changelist-filter li { + list-style-type: none; + margin-left: 0; + padding-left: 0; +} + +#changelist-filter a { + color: #999; +} + +#changelist-filter a:hover { + color: #036; +} + +#changelist-filter li.selected { + border-left: 5px solid #ccc; + padding-left: 5px; + margin-left: -10px; +} + +#changelist-filter li.selected a { + color: #5b80b2 !important; +} + +/* DATE DRILLDOWN */ + +.change-list ul.toplinks { + display: block; + background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; + border-top: 1px solid white; + float: left; + padding: 0 !important; + margin: 0 !important; + width: 100%; +} + +.change-list ul.toplinks li { + padding: 3px 6px; + font-weight: bold; + list-style-type: none; + display: inline-block; +} + +.change-list ul.toplinks .date-back a { + color: #999; +} + +.change-list ul.toplinks .date-back a:hover { + color: #036; +} + +/* PAGINATOR */ + +.paginator { + font-size: 11px; + padding-top: 10px; + padding-bottom: 10px; + line-height: 22px; + margin: 0; + border-top: 1px solid #ddd; +} + +.paginator a:link, .paginator a:visited { + padding: 2px 6px; + border: solid 1px #ccc; + background: white; + text-decoration: none; +} + +.paginator a.showall { + padding: 0 !important; + border: none !important; +} + +.paginator a.showall:hover { + color: #036 !important; + background: transparent !important; +} + +.paginator .end { + border-width: 2px !important; + margin-right: 6px; +} + +.paginator .this-page { + padding: 2px 6px; + font-weight: bold; + font-size: 13px; + vertical-align: top; +} + +.paginator a:hover { + color: white; + background: #5b80b2; + border-color: #036; +} + +/* ACTIONS */ + +.filtered .actions { + margin-right: 160px !important; + border-right: 1px solid #ddd; +} + +#changelist table input { + margin: 0; +} + +#changelist table tbody tr.selected { + background-color: #FFFFCC; +} + +#changelist .actions { + color: #999; + padding: 3px; + border-top: 1px solid #fff; + border-bottom: 1px solid #ddd; + background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; +} + +#changelist .actions.selected { + background: #fffccf; + border-top: 1px solid #fffee8; + border-bottom: 1px solid #edecd6; +} + +#changelist .actions span.all, +#changelist .actions span.action-counter, +#changelist .actions span.clear, +#changelist .actions span.question { + font-size: 11px; + margin: 0 0.5em; + display: none; +} + +#changelist .actions:last-child { + border-bottom: none; +} + +#changelist .actions select { + border: 1px solid #aaa; + margin-left: 0.5em; + padding: 1px 2px; +} + +#changelist .actions label { + font-size: 11px; + margin-left: 0.5em; +} + +#changelist #action-toggle { + display: none; +} + +#changelist .actions .button { + font-size: 11px; + padding: 1px 2px; +} diff --git a/game_template/server/web/static/admin/css/dashboard.css b/game_template/server/web/static/admin/css/dashboard.css new file mode 100644 index 000000000..05808bcb0 --- /dev/null +++ b/game_template/server/web/static/admin/css/dashboard.css @@ -0,0 +1,30 @@ +/* DASHBOARD */ + +.dashboard .module table th { + width: 100%; +} + +.dashboard .module table td { + white-space: nowrap; +} + +.dashboard .module table td a { + display: block; + padding-right: .6em; +} + +/* RECENT ACTIONS MODULE */ + +.module ul.actionlist { + margin-left: 0; +} + +ul.actionlist li { + list-style-type: none; +} + +ul.actionlist li { + overflow: hidden; + text-overflow: ellipsis; + -o-text-overflow: ellipsis; +} diff --git a/game_template/server/web/static/admin/css/forms.css b/game_template/server/web/static/admin/css/forms.css new file mode 100644 index 000000000..d088d8db4 --- /dev/null +++ b/game_template/server/web/static/admin/css/forms.css @@ -0,0 +1,376 @@ +@import url('widgets.css'); + +/* FORM ROWS */ + +.form-row { + overflow: hidden; + padding: 8px 12px; + font-size: 11px; + border-bottom: 1px solid #eee; +} + +.form-row img, .form-row input { + vertical-align: middle; +} + +form .form-row p { + padding-left: 0; + font-size: 11px; +} + +.hidden { + display: none; +} + +/* FORM LABELS */ + +form h4 { + margin: 0 !important; + padding: 0 !important; + border: none !important; +} + +label { + font-weight: normal !important; + color: #666; + font-size: 12px; +} + +.required label, label.required { + font-weight: bold !important; + color: #333 !important; +} + +/* RADIO BUTTONS */ + +form ul.radiolist li { + list-style-type: none; +} + +form ul.radiolist label { + float: none; + display: inline; +} + +form ul.inline { + margin-left: 0; + padding: 0; +} + +form ul.inline li { + float: left; + padding-right: 7px; +} + +/* ALIGNED FIELDSETS */ + +.aligned label { + display: block; + padding: 3px 10px 0 0; + float: left; + width: 8em; + word-wrap: break-word; +} + +.aligned ul label { + display: inline; + float: none; + width: auto; +} + +.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { + width: 350px; +} + +form .aligned p, form .aligned ul { + margin-left: 7em; + padding-left: 30px; +} + +form .aligned table p { + margin-left: 0; + padding-left: 0; +} + +form .aligned p.help { + padding-left: 38px; +} + +.aligned .vCheckboxLabel { + float: none !important; + display: inline; + padding-left: 4px; +} + +.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField { + width: 610px; +} + +.checkbox-row p.help { + margin-left: 0; + padding-left: 0 !important; +} + +fieldset .field-box { + float: left; + margin-right: 20px; +} + +/* WIDE FIELDSETS */ + +.wide label { + width: 15em !important; +} + +form .wide p { + margin-left: 15em; +} + +form .wide p.help { + padding-left: 38px; +} + +.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { + width: 450px; +} + +/* COLLAPSED FIELDSETS */ + +fieldset.collapsed * { + display: none; +} + +fieldset.collapsed h2, fieldset.collapsed { + display: block !important; +} + +fieldset.collapsed h2 { + background-image: url(../img/nav-bg.gif); + background-position: bottom left; + color: #999; +} + +fieldset.collapsed .collapse-toggle { + background: transparent; + display: inline !important; +} + +/* MONOSPACE TEXTAREAS */ + +fieldset.monospace textarea { + font-family: "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; +} + +/* SUBMIT ROW */ + +.submit-row { + padding: 5px 7px; + text-align: right; + background: white url(../img/nav-bg.gif) 0 100% repeat-x; + border: 1px solid #ccc; + margin: 5px 0; + overflow: hidden; +} + +body.popup .submit-row { + overflow: auto; +} + +.submit-row input { + margin: 0 0 0 5px; +} + +.submit-row p { + margin: 0.3em; +} + +.submit-row p.deletelink-box { + float: left; +} + +.submit-row .deletelink { + background: url(../img/icon_deletelink.gif) 0 50% no-repeat; + padding-left: 14px; +} + +/* CUSTOM FORM FIELDS */ + +.vSelectMultipleField { + vertical-align: top !important; +} + +.vCheckboxField { + border: none; +} + +.vDateField, .vTimeField { + margin-right: 2px; +} + +.vDateField { + min-width: 6.85em; +} + +.vTimeField { + min-width: 4.7em; +} + +.vURLField { + width: 30em; +} + +.vLargeTextField, .vXMLLargeTextField { + width: 48em; +} + +.flatpages-flatpage #id_content { + height: 40.2em; +} + +.module table .vPositiveSmallIntegerField { + width: 2.2em; +} + +.vTextField { + width: 20em; +} + +.vIntegerField { + width: 5em; +} + +.vBigIntegerField { + width: 10em; +} + +.vForeignKeyRawIdAdminField { + width: 5em; +} + +/* INLINES */ + +.inline-group { + padding: 0; + border: 1px solid #ccc; + margin: 10px 0; +} + +.inline-group .aligned label { + width: 8em; +} + +.inline-related { + position: relative; +} + +.inline-related h3 { + margin: 0; + color: #666; + padding: 3px 5px; + font-size: 11px; + background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; + border-bottom: 1px solid #ddd; +} + +.inline-related h3 span.delete { + float: right; +} + +.inline-related h3 span.delete label { + margin-left: 2px; + font-size: 11px; +} + +.inline-related fieldset { + margin: 0; + background: #fff; + border: none; + width: 100%; +} + +.inline-related fieldset.module h3 { + margin: 0; + padding: 2px 5px 3px 5px; + font-size: 11px; + text-align: left; + font-weight: bold; + background: #bcd; + color: #fff; +} + +.inline-group .tabular fieldset.module { + border: none; + border-bottom: 1px solid #ddd; +} + +.inline-related.tabular fieldset.module table { + width: 100%; +} + +.last-related fieldset { + border: none; +} + +.inline-group .tabular tr.has_original td { + padding-top: 2em; +} + +.inline-group .tabular tr td.original { + padding: 2px 0 0 0; + width: 0; + _position: relative; +} + +.inline-group .tabular th.original { + width: 0px; + padding: 0; +} + +.inline-group .tabular td.original p { + position: absolute; + left: 0; + height: 1.1em; + padding: 2px 7px; + overflow: hidden; + font-size: 9px; + font-weight: bold; + color: #666; + _width: 700px; +} + +.inline-group ul.tools { + padding: 0; + margin: 0; + list-style: none; +} + +.inline-group ul.tools li { + display: inline; + padding: 0 5px; +} + +.inline-group div.add-row, +.inline-group .tabular tr.add-row td { + color: #666; + padding: 3px 5px; + border-bottom: 1px solid #ddd; + background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; +} + +.inline-group .tabular tr.add-row td { + padding: 4px 5px 3px; + border-bottom: none; +} + +.inline-group ul.tools a.add, +.inline-group div.add-row a, +.inline-group .tabular tr.add-row td a { + background: url(../img/icon_addlink.gif) 0 50% no-repeat; + padding-left: 14px; + font-size: 11px; + outline: 0; /* Remove dotted border around link */ +} + +.empty-form { + display: none; +} diff --git a/game_template/server/web/static/admin/css/ie.css b/game_template/server/web/static/admin/css/ie.css new file mode 100644 index 000000000..fd00f7f20 --- /dev/null +++ b/game_template/server/web/static/admin/css/ie.css @@ -0,0 +1,63 @@ +/* IE 6 & 7 */ + +/* Proper fixed width for dashboard in IE6 */ + +.dashboard #content { + *width: 768px; +} + +.dashboard #content-main { + *width: 535px; +} + +/* IE 6 ONLY */ + +/* Keep header from flowing off the page */ + +#container { + _position: static; +} + +/* Put the right sidebars back on the page */ + +.colMS #content-related { + _margin-right: 0; + _margin-left: 10px; + _position: static; +} + +/* Put the left sidebars back on the page */ + +.colSM #content-related { + _margin-right: 10px; + _margin-left: -115px; + _position: static; +} + +.form-row { + _height: 1%; +} + +/* Fix right margin for changelist filters in IE6 */ + +#changelist-filter ul { + _margin-right: -10px; +} + +/* IE ignores min-height, but treats height as if it were min-height */ + +.change-list .filtered { + _height: 400px; +} + +/* IE doesn't know alpha transparency in PNGs */ + +.inline-deletelink { + background: transparent url(../img/inline-delete-8bit.png) no-repeat; +} + +/* IE7 doesn't support inline-block */ +.change-list ul.toplinks li { + zoom: 1; + *display: inline; +} \ No newline at end of file diff --git a/game_template/server/web/static/admin/css/login.css b/game_template/server/web/static/admin/css/login.css new file mode 100644 index 000000000..a91de117b --- /dev/null +++ b/game_template/server/web/static/admin/css/login.css @@ -0,0 +1,60 @@ +/* LOGIN FORM */ + +body.login { + background: #eee; +} + +.login #container { + background: white; + border: 1px solid #ccc; + width: 28em; + min-width: 300px; + margin-left: auto; + margin-right: auto; + margin-top: 100px; +} + +.login #content-main { + width: 100%; +} + +.login form { + margin-top: 1em; +} + +.login .form-row { + padding: 4px 0; + float: left; + width: 100%; +} + +.login .form-row label { + padding-right: 0.5em; + line-height: 2em; + font-size: 1em; + clear: both; + color: #333; +} + +.login .form-row #id_username, .login .form-row #id_password { + clear: both; + padding: 6px; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.login span.help { + font-size: 10px; + display: block; +} + +.login .submit-row { + clear: both; + padding: 1em 0 0 9.4em; +} + +.login .password-reset-link { + text-align: center; +} diff --git a/game_template/server/web/static/admin/css/rtl.css b/game_template/server/web/static/admin/css/rtl.css new file mode 100644 index 000000000..ba9f1b5ad --- /dev/null +++ b/game_template/server/web/static/admin/css/rtl.css @@ -0,0 +1,250 @@ +body { + direction: rtl; +} + +/* LOGIN */ + +.login .form-row { + float: right; +} + +.login .form-row label { + float: right; + padding-left: 0.5em; + padding-right: 0; + text-align: left; +} + +.login .submit-row { + clear: both; + padding: 1em 9.4em 0 0; +} + +/* GLOBAL */ + +th { + text-align: right; +} + +.module h2, .module caption { + text-align: right; +} + +.addlink, .changelink { + padding-left: 0px; + padding-right: 12px; + background-position: 100% 0.2em; +} + +.deletelink { + padding-left: 0px; + padding-right: 12px; + background-position: 100% 0.25em; +} + +.object-tools { + float: left; +} + +thead th:first-child, +tfoot td:first-child { + border-left: 1px solid #ddd !important; +} + +/* LAYOUT */ + +#user-tools { + right: auto; + left: 0; + text-align: left; +} + +div.breadcrumbs { + text-align: right; +} + +#content-main { + float: right; +} + +#content-related { + float: left; + margin-left: -19em; + margin-right: auto; +} + +.colMS { + margin-left: 20em !important; + margin-right: 10px !important; +} + +/* SORTABLE TABLES */ + +table thead th.sorted .sortoptions { + float: left; +} + +thead th.sorted .text { + padding-right: 0; + padding-left: 42px; +} + +/* dashboard styles */ + +.dashboard .module table td a { + padding-left: .6em; + padding-right: 12px; +} + +/* changelists styles */ + +.change-list .filtered { + background: white url(../img/changelist-bg_rtl.gif) top left repeat-y !important; +} + +.change-list .filtered table { + border-left: 1px solid #ddd; + border-right: 0px none; +} + +#changelist-filter { + right: auto; + left: 0; + border-left: 0px none; + border-right: 1px solid #ddd; +} + +.change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { + margin-right: 0px !important; + margin-left: 160px !important; +} + +#changelist-filter li.selected { + border-left: 0px none; + padding-left: 0px; + margin-left: 0; + border-right: 5px solid #ccc; + padding-right: 5px; + margin-right: -10px; +} + +.filtered .actions { + border-left:1px solid #DDDDDD; + margin-left:160px !important; + border-right: 0 none; + margin-right:0 !important; +} + +#changelist table tbody td:first-child, #changelist table tbody th:first-child { + border-right: 0; + border-left: 1px solid #ddd; +} + +/* FORMS */ + +.aligned label { + padding: 0 0 3px 1em; + float: right; +} + +.submit-row { + text-align: left +} + +.submit-row p.deletelink-box { + float: right; +} + +.submit-row .deletelink { + background: url(../img/icon_deletelink.gif) 0 50% no-repeat; + padding-right: 14px; +} + +.vDateField, .vTimeField { + margin-left: 2px; +} + +form ul.inline li { + float: right; + padding-right: 0; + padding-left: 7px; +} + +input[type=submit].default, .submit-row input.default { + float: left; +} + +fieldset .field-box { + float: right; + margin-left: 20px; + margin-right: 0; +} + +.errorlist li { + background-position: 100% .3em; + padding: 4px 25px 4px 5px; +} + +.errornote { + background-position: 100% .3em; + padding: 4px 25px 4px 5px; +} + +/* WIDGETS */ + +.calendarnav-previous { + top: 0; + left: auto; + right: 0; +} + +.calendarnav-next { + top: 0; + right: auto; + left: 0; +} + +.calendar caption, .calendarbox h2 { + text-align: center; +} + +.selector { + float: right; +} + +.selector .selector-filter { + text-align: right; +} + +.inline-deletelink { + float: left; +} + +/* MISC */ + +.inline-related h2, .inline-group h2 { + text-align: right +} + +.inline-related h3 span.delete { + padding-right: 20px; + padding-left: inherit; + left: 10px; + right: inherit; + float:left; +} + +.inline-related h3 span.delete label { + margin-left: inherit; + margin-right: 2px; +} + +/* IE7 specific bug fixes */ + +div.colM { + position: relative; +} + +.submit-row input { + float: left; +} \ No newline at end of file diff --git a/game_template/server/web/static/admin/css/widgets.css b/game_template/server/web/static/admin/css/widgets.css new file mode 100644 index 000000000..56817228f --- /dev/null +++ b/game_template/server/web/static/admin/css/widgets.css @@ -0,0 +1,578 @@ +/* SELECTOR (FILTER INTERFACE) */ + +.selector { + width: 840px; + float: left; +} + +.selector select { + width: 400px; + height: 17.2em; +} + +.selector-available, .selector-chosen { + float: left; + width: 400px; + text-align: center; + margin-bottom: 5px; +} + +.selector-chosen select { + border-top: none; +} + +.selector-available h2, .selector-chosen h2 { + border: 1px solid #ccc; +} + +.selector .selector-available h2 { + background: white url(../img/nav-bg.gif) bottom left repeat-x; + color: #666; +} + +.selector .selector-filter { + background: white; + border: 1px solid #ccc; + border-width: 0 1px; + padding: 3px; + color: #999; + font-size: 10px; + margin: 0; + text-align: left; +} + +.selector .selector-filter label, +.inline-group .aligned .selector .selector-filter label { + width: 16px; + padding: 2px; +} + +.selector .selector-available input { + width: 360px; +} + +.selector ul.selector-chooser { + float: left; + width: 22px; + background-color: #eee; + border-radius: 10px; + margin: 10em 5px 0 5px; + padding: 0; +} + +.selector-chooser li { + margin: 0; + padding: 3px; + list-style-type: none; +} + +.selector select { + margin-bottom: 10px; + margin-top: 0; +} + +.selector-add, .selector-remove { + width: 16px; + height: 16px; + display: block; + text-indent: -3000px; + overflow: hidden; +} + +.selector-add { + background: url(../img/selector-icons.gif) 0 -161px no-repeat; + cursor: default; + margin-bottom: 2px; +} + +.active.selector-add { + background: url(../img/selector-icons.gif) 0 -187px no-repeat; + cursor: pointer; +} + +.selector-remove { + background: url(../img/selector-icons.gif) 0 -109px no-repeat; + cursor: default; +} + +.active.selector-remove { + background: url(../img/selector-icons.gif) 0 -135px no-repeat; + cursor: pointer; +} + +a.selector-chooseall, a.selector-clearall { + display: inline-block; + text-align: left; + margin-left: auto; + margin-right: auto; + font-weight: bold; + color: #666; +} + +a.selector-chooseall { + padding: 3px 18px 3px 0; +} + +a.selector-clearall { + padding: 3px 0 3px 18px; +} + +a.active.selector-chooseall:hover, a.active.selector-clearall:hover { + color: #036; +} + +a.selector-chooseall { + background: url(../img/selector-icons.gif) right -263px no-repeat; + cursor: default; +} + +a.active.selector-chooseall { + background: url(../img/selector-icons.gif) right -289px no-repeat; + cursor: pointer; +} + +a.selector-clearall { + background: url(../img/selector-icons.gif) left -211px no-repeat; + cursor: default; +} + +a.active.selector-clearall { + background: url(../img/selector-icons.gif) left -237px no-repeat; + cursor: pointer; +} + +/* STACKED SELECTORS */ + +.stacked { + float: left; + width: 500px; +} + +.stacked select { + width: 480px; + height: 10.1em; +} + +.stacked .selector-available, .stacked .selector-chosen { + width: 480px; +} + +.stacked .selector-available { + margin-bottom: 0; +} + +.stacked .selector-available input { + width: 442px; +} + +.stacked ul.selector-chooser { + height: 22px; + width: 50px; + margin: 0 0 3px 40%; + background-color: #eee; + border-radius: 10px; +} + +.stacked .selector-chooser li { + float: left; + padding: 3px 3px 3px 5px; +} + +.stacked .selector-chooseall, .stacked .selector-clearall { + display: none; +} + +.stacked .selector-add { + background: url(../img/selector-icons.gif) 0 -57px no-repeat; + cursor: default; +} + +.stacked .active.selector-add { + background: url(../img/selector-icons.gif) 0 -83px no-repeat; + cursor: pointer; +} + +.stacked .selector-remove { + background: url(../img/selector-icons.gif) 0 -5px no-repeat; + cursor: default; +} + +.stacked .active.selector-remove { + background: url(../img/selector-icons.gif) 0 -31px no-repeat; + cursor: pointer; +} + +/* DATE AND TIME */ + +p.datetime { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-size: 11px; + font-weight: bold; +} + +.datetime span { + font-size: 11px; + color: #ccc; + font-weight: normal; + white-space: nowrap; +} + +table p.datetime { + font-size: 10px; + margin-left: 0; + padding-left: 0; +} + +/* URL */ + +p.url { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-size: 11px; + font-weight: bold; +} + +.url a { + font-weight: normal; +} + +/* FILE UPLOADS */ + +p.file-upload { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-size: 11px; + font-weight: bold; +} + +.file-upload a { + font-weight: normal; +} + +.file-upload .deletelink { + margin-left: 5px; +} + +span.clearable-file-input label { + color: #333; + font-size: 11px; + display: inline; + float: none; +} + +/* CALENDARS & CLOCKS */ + +.calendarbox, .clockbox { + margin: 5px auto; + font-size: 11px; + width: 16em; + text-align: center; + background: white; + position: relative; +} + +.clockbox { + width: auto; +} + +.calendar { + margin: 0; + padding: 0; +} + +.calendar table { + margin: 0; + padding: 0; + border-collapse: collapse; + background: white; + width: 100%; +} + +.calendar caption, .calendarbox h2 { + margin: 0; + font-size: 11px; + text-align: center; + border-top: none; +} + +.calendar th { + font-size: 10px; + color: #666; + padding: 2px 3px; + text-align: center; + background: #e1e1e1 url(../img/nav-bg.gif) 0 50% repeat-x; + border-bottom: 1px solid #ddd; +} + +.calendar td { + font-size: 11px; + text-align: center; + padding: 0; + border-top: 1px solid #eee; + border-bottom: none; +} + +.calendar td.selected a { + background: #C9DBED; +} + +.calendar td.nonday { + background: #efefef; +} + +.calendar td.today a { + background: #ffc; +} + +.calendar td a, .timelist a { + display: block; + font-weight: bold; + padding: 4px; + text-decoration: none; + color: #444; +} + +.calendar td a:hover, .timelist a:hover { + background: #5b80b2; + color: white; +} + +.calendar td a:active, .timelist a:active { + background: #036; + color: white; +} + +.calendarnav { + font-size: 10px; + text-align: center; + color: #ccc; + margin: 0; + padding: 1px 3px; +} + +.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { + color: #999; +} + +.calendar-shortcuts { + background: white; + font-size: 10px; + line-height: 11px; + border-top: 1px solid #eee; + padding: 3px 0 4px; + color: #ccc; +} + +.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { + display: block; + position: absolute; + font-weight: bold; + font-size: 12px; + background: #C9DBED url(../img/default-bg.gif) bottom left repeat-x; + padding: 1px 4px 2px 4px; + color: white; +} + +.calendarnav-previous:hover, .calendarnav-next:hover { + background: #036; +} + +.calendarnav-previous { + top: 0; + left: 0; +} + +.calendarnav-next { + top: 0; + right: 0; +} + +.calendar-cancel { + margin: 0 !important; + padding: 0 !important; + font-size: 10px; + background: #e1e1e1 url(../img/nav-bg.gif) 0 50% repeat-x; + border-top: 1px solid #ddd; +} + +.calendar-cancel:hover { + background: #e1e1e1 url(../img/nav-bg-reverse.gif) 0 50% repeat-x; +} + +.calendar-cancel a { + color: black; + display: block; +} + +ul.timelist, .timelist li { + list-style-type: none; + margin: 0; + padding: 0; +} + +.timelist a { + padding: 2px; +} + +/* INLINE ORDERER */ + +ul.orderer { + position: relative; + padding: 0 !important; + margin: 0 !important; + list-style-type: none; +} + +ul.orderer li { + list-style-type: none; + display: block; + padding: 0; + margin: 0; + border: 1px solid #bbb; + border-width: 0 1px 1px 0; + white-space: nowrap; + overflow: hidden; + background: #e2e2e2 url(../img/nav-bg-grabber.gif) repeat-y; +} + +ul.orderer li:hover { + cursor: move; + background-color: #ddd; +} + +ul.orderer li a.selector { + margin-left: 12px; + overflow: hidden; + width: 83%; + font-size: 10px !important; + padding: 0.6em 0; +} + +ul.orderer li a:link, ul.orderer li a:visited { + color: #333; +} + +ul.orderer li .inline-deletelink { + position: absolute; + right: 4px; + margin-top: 0.6em; +} + +ul.orderer li.selected { + background-color: #f8f8f8; + border-right-color: #f8f8f8; +} + +ul.orderer li.deleted { + background: #bbb url(../img/deleted-overlay.gif); +} + +ul.orderer li.deleted a:link, ul.orderer li.deleted a:visited { + color: #888; +} + +ul.orderer li.deleted .inline-deletelink { + background-image: url(../img/inline-restore.png); +} + +ul.orderer li.deleted:hover, ul.orderer li.deleted a.selector:hover { + cursor: default; +} + +/* EDIT INLINE */ + +.inline-deletelink { + float: right; + text-indent: -9999px; + background: transparent url(../img/inline-delete.png) no-repeat; + width: 15px; + height: 15px; + border: 0px none; + outline: 0; /* Remove dotted border around link */ +} + +.inline-deletelink:hover { + background-position: -15px 0; + cursor: pointer; +} + +.editinline button.addlink { + border: 0px none; + color: #5b80b2; + font-size: 100%; + cursor: pointer; +} + +.editinline button.addlink:hover { + color: #036; + cursor: pointer; +} + +.editinline table .help { + text-align: right; + float: right; + padding-left: 2em; +} + +.editinline tfoot .addlink { + white-space: nowrap; +} + +.editinline table thead th:last-child { + border-left: none; +} + +.editinline tr.deleted { + background: #ddd url(../img/deleted-overlay.gif); +} + +.editinline tr.deleted .inline-deletelink { + background-image: url(../img/inline-restore.png); +} + +.editinline tr.deleted td:hover { + cursor: default; +} + +.editinline tr.deleted td:first-child { + background-image: none !important; +} + +/* EDIT INLINE - STACKED */ + +.editinline-stacked { + min-width: 758px; +} + +.editinline-stacked .inline-object { + margin-left: 210px; + background: white; +} + +.editinline-stacked .inline-source { + float: left; + width: 200px; + background: #f8f8f8; +} + +.editinline-stacked .inline-splitter { + float: left; + width: 9px; + background: #f8f8f8 url(../img/inline-splitter-bg.gif) 50% 50% no-repeat; + border-right: 1px solid #ccc; +} + +.editinline-stacked .controls { + clear: both; + background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; + padding: 3px 4px; + font-size: 11px; + border-top: 1px solid #ddd; +} diff --git a/game_template/server/web/static/admin/img/changelist-bg.gif b/game_template/server/web/static/admin/img/changelist-bg.gif new file mode 100644 index 0000000000000000000000000000000000000000..94e09f7715e559bf199dc0bec756ae844baa8a6f GIT binary patch literal 50 zcmZ?wbh9u|T*$!0@PUEh{rmTK@7~p600NK*1Ct<6-^$Z(`4`XG5|y^@-k#s`q72pm D$4Cvt?bv=6id7ONcO70{{r%8=U|E literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/chooser-bg.gif b/game_template/server/web/static/admin/img/chooser-bg.gif new file mode 100644 index 0000000000000000000000000000000000000000..30e83c2518b0e5ee5f4bda6ce790b47f6dbdbfbb GIT binary patch literal 199 zcmZ?wbhEHb6l2h5*v!E2@87@o@85s?`0?-Gzu&)q|NQy$j~_oiefsp_!-t2MMY9TZUv9~%Sp!>_)cW&ci3#* zUhC5m*S~nZz=*>R=VoO!-wlwA$Z`-01 q@~q#j3=%veq*@rvq^WHhU}-`1SSm{{H^+^Yi-p`uF$u@bK{c{r&s< z`||Sg^z`)c@$vTd_5c6?A^8LV00000EC2ui05AX-000DmFvuy15jgA3yCGB Oo}Zwd4gw#T5db^5xM#Bf literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/default-bg-reverse.gif b/game_template/server/web/static/admin/img/default-bg-reverse.gif new file mode 100644 index 0000000000000000000000000000000000000000..a28f4ad51a64131afbee7e2c3b6af352893c21e5 GIT binary patch literal 835 zcmV-J1HAl4Nk%v~VG#fy0QLX?ywd5g$>Wc((SfDPyV2;h%;kux&VHiEgQv@Yq{*Sa z+l{W!qQ2X>&*yuf#)zuThN#S(y4aey*L$GHimcC{yxF9{+^)ysgs01&yxO+S=DE=5 zq`=*$!rrpW%Ia#o)Ef<&3V-tHt57%Hx^1)~CYVyVB@=p~tMo;ew{h zv&!VG#o?8+)Qqmrnz+`6sm*|+$&#|ts>I*7&E~4Z;GetNkg(B_veJR3%8#$nw9VzU z&E=W6*PXi9oVwYCsLX|_%&NoRhN#WD(ddt_(zwp%xX$ON!QH9E;Dn~jv&-bS&gP%H z*|yE)hN;YZp~kn*=6s;Xx6kLIz1xql(y__nrNG^btIvL;$&0MdEC2ui01*Hm000O7 zfOifPgAR3s6N!Zm4lp*4BuykFJTOf*l}$X9EG!3~2cDpzoqh=-BB!URRtX8Ms!u=z zUbD1M1G%=j2p9;zy>Gw17{$G1Xb@%)%w}{DRc2LX%n%O-*9O`T+}qs-6yY>9I~3y- zJAD*?=sPhn3-R$WM@RDTQ%3>JcA0syKJpvRw!9yxMc>2a`&3xzovx=8UshKv*x zrBwWoF@=l|HCC*s;bR3396o&D2#Io}gqKQ&P@xj@r5G_1lt>sdX2ggH5q<(4L&hf= zpb;u$$grh?Ql=T4R*EFeIr5KLGLmcd%i6bbUe36dy_8@~t=1<8vdNG`N!QKH3#5}!MR z)_mbYfB-X2r#`*swCdC*JV5b~Bz9~MxJ{UV0|gEkBL#wo0UoA+5ir4%YY4HNd5A1o z;>^)NJp}VL4YJ!gaiT$Y@80pSX|Q3#jTWc((SfDPyV2;h%;kux&VHiEgQv@Yq{*Sa z+l{W!qQ2X>&*yuf#)zuThN#S(y4aey*L$GHimcC{yxF9{+^)ysgs01&yxO+S=DE=5 zq`=*$!rrpW%Ia#o)Ef<&3V-tHt57%Hx^1)~CYVyVB@=p~tMo;ew{h zv&!VG#o?8+)Qqmrnz+`6sm*|+$&#|ts>I*7&E~4Z;GetNkg(B_veJR3%8#$nw9VzU z&E=W6*PXi9oVwYCsLX|_%&NoRhN#WD(ddt_(zwp%xX$ON!QH9E;Dn~jv&-bS&gP%H z*|yE)hN;YZp~kn*=6s;Xx6kLIz1xql(y__nrNG^btIvL;$&0MdEC2ui01*Hm000O7 zfB=GngoT0x1WZIsh=@c4X^)Hq8yhz_C@7YhmN%6fC~FO)b|)tdr>LlSYz-PAudpFm zS#fh3xFN7$Mg_iMyEiEN27bWH6 z=HM3>ML{o4NKx(YFF{dAFGWZyZdf27DgX-9f+e7qGeUGM>CmP_hlM&i=nx`A;t~!V zesHj0A)^I895r&}pnw5`k|<3oSa~uJg9;fkY^eZKW(JxS@T~!jKv(R@CsZ0tXHsK5$_Dsx`$288T9sm2&pO+7vRWKDg+S zBgd5z~BMB}|@Q2`r zz!0M(GL*z*R@l?k*F|^NbkFEGAeD6Lr>o96_tdQ_$ug(M$DcUs_2^&rxr;JCKhN^= zGOMd=ghxm3IRAD|6i2K*dHVn8s@NajIqUTR2v1HTb!>_nqp27>`XK0!QyAbG8d0CA^F9Cx|8`Ux^7w#1hTk>)Z> z(jcFpXfa(;R1{={7MV5+3lFf?Vy&HYrCcdFs;KyOm=EBs5ygXBYq_}i>Vb?getdik z1axF^S%p;#RdeO?k9{10j>wELh)9l6DFO8@^|iIO1~iS#7=yKzIPTKvJS0t1(27OX zED#B9m7S%S$g~L?jj;#{HVN{sy_`*bwOnuSu)F(;!=n#WF@q^-d?WC=txYt`YI`}d z2t^e-aTlPLIkSRK-WXXWf($fM@8R|K&W*wM-t4jdY`u~wQq3Z~a_`|Sm6w7`<7)(N zm;x}jwK+A4Y2bAdf6y|d;Bv#1Ml-w-cwE`LQsme{%Sn7BN%F@irYVn1n{=2=V1=U# zEAT(0AgQYR^5Xdfcsfi#;r#0t!86r@QjICV%6p{_bOc97ED*c7B?{Ut#9L~0h(`w!0soRBa=!hZ`i3C(tMK-$w zq0zX*pg%xHYGQBoG7^Fp?f}pI6#xKNE*G6GrH2hC8gPGHzny?LP4i#C0l+j(@bfMu za~&je9lU;SE=0n!TuLi4l;uYlmIlMpLJ2Es7&hpF|OoMDi!f?u@=39aU~C_P=#aL^dh_Y z^LGv_IP*V9D>BBFd}#2i;VJgl&*=_EHTCD=-<4BqAQh@q7p&k5eY7C(@7f)D8DQ$7 zAQv>{)BVrv!0oKw@zG51AmyF|Q4~RQ;VNUzxj#fv1Xr)$IQmdWMruYncy8a%peRXj wVTV2;1d|B?Ap|@JEC~W)u~^{H-_Gvh3u9NM*1xcED*ylh07*qoM6N<$g2#E+6951J literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/icon-no.gif b/game_template/server/web/static/admin/img/icon-no.gif new file mode 100644 index 0000000000000000000000000000000000000000..1b4ee5814570885705399533f1182f8b0491c5fb GIT binary patch literal 176 zcmZ?wbhEHb`H-TFR%!C^)o_GDj!gPtK1gc27Dv@$SQ0{~`FJvsmY literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/icon-unknown.gif b/game_template/server/web/static/admin/img/icon-unknown.gif new file mode 100644 index 0000000000000000000000000000000000000000..cfd2b02ad91b3677dbe59111faaf4f437c362cb8 GIT binary patch literal 130 zcmV-|0Db>QNk%w1VF~~W0J9GO^z`(anwr7E!O_vts;a8Fxw+ur;K|9!=;-Lh#l`#k z`?0aH)z#IOmX?c)i~s-sA^8LW000jFEC2ui015yK000Cp@IAI#TTH&>x=&LlD2fp{ kltU;-pbSpsb&B9v9)J|xHP4tFtdrsVKoW`tBZ&Y2J8`5w82|tP literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/icon-yes.gif b/game_template/server/web/static/admin/img/icon-yes.gif new file mode 100644 index 0000000000000000000000000000000000000000..73992827403791d6c1a75a079880e41dce7e0214 GIT binary patch literal 299 zcmZ?wbhEHbb?NhTQ$x_deWPc4O)NkN2|oXRf%p{M+wuUw(Z# z`TWGXJ8Mf07p=Or^7yl3mtJ2C+~V)C-fh~&DX}}E_C4PF@Y93ee}B)tGUw-?pC_Il zZ#vO%{oS?y|Nqw=uUUR`+4?){5_iQh&Q{xM6OkFieY2o T4)tf0@^WEj=4)bdWUvMRbX#E6 literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/icon_addlink.gif b/game_template/server/web/static/admin/img/icon_addlink.gif new file mode 100644 index 0000000000000000000000000000000000000000..ee70e1adba52480cc6aedbee650000c5d55b0088 GIT binary patch literal 119 zcmZ?wbhEHb(s)E@aY^3 F)&O8RB1ZrK literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/icon_alert.gif b/game_template/server/web/static/admin/img/icon_alert.gif new file mode 100644 index 0000000000000000000000000000000000000000..a1dde2625445b76d041ae02ccfcb83481ca63c5e GIT binary patch literal 145 zcmV;C0B-+BNk%w1VGsZi0J9GO|G@+Q!3O`;RR7pu|IkAJ%Ps%YPXF0v|INcdJ{u&=}=IXLDhr+J%S1nrq(gCL;wIgri4F* literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/icon_calendar.gif b/game_template/server/web/static/admin/img/icon_calendar.gif new file mode 100644 index 0000000000000000000000000000000000000000..7587b305a4ee702cbed3bee1ae17c78feb85d00b GIT binary patch literal 192 zcmV;x06+gnNk%w1VGsZi0J8u9nVFf2iHY^~_4)bv@bK{4+uQ&D|FN{U?d|Qf&F0D5 z?Wd=w{QUgs>FMX^=l}ozA^8LW000jFEC2ui01yBW000DS@X1N*1UPGamH(iU1QH+` z43ii};vPZqm~L$+una7G@AI)4YnU1cj)Wk=I*&Aa=g_Vl48 zmH)wj0Spv>vM@3*@G|Itcpx(vSX4HgyeYC&>*nrB_bxSQsBGn6*)YRRaLr}Q6>6LJ P$Rx*~-FRR+2ZJ>L#Kbnb literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/icon_clock.gif b/game_template/server/web/static/admin/img/icon_clock.gif new file mode 100644 index 0000000000000000000000000000000000000000..ff2d57e0a3b6373b7bd9540e688b1b4c71081cb7 GIT binary patch literal 390 zcmV;10eSvMNk%w1VGsZi0M$DH{QUg>{{H#-`S$kq$lLA4+3fD_?(*{T{{H>?`uXqg z@BaV(?CkCH^Yi-p`u6wt#Mtch_4dHk>iGEh^7Hca^z*{j>crRU`1twz{QT_e?DqEc z$J*@b>gvMQ>+|&V$lC14+wAM>>;3)w@9*%!*X#51^1;{Z`}_OH+U@-O`^ehu#MkWU z>FEFe{^{xI#n|ld@$l~N@5kBgv9!0z+wHW?=G)ubr>Cd?|NohpnY8A@0000000000 z00000A^8LW0027xEC2ui01yBW000J~z@2cXD;jmfB(YW_ga{xGQmJF&uGa!=Dy-IU zx$q)@gRrJva4tXt05Uks30VcZ5F=VbfDzy%IyZGW2r3RV4+8@cI5vSg1UL%-4ihvL z4F?pBk1IFOZ1vml&CJGE4FE})gH$(*xI2#8t kA}zwiLpm2FSXaY=R2~vG+}zkoL`Ox%;6gX&=t@BVI|kg>kN^Mx literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/icon_deletelink.gif b/game_template/server/web/static/admin/img/icon_deletelink.gif new file mode 100644 index 0000000000000000000000000000000000000000..72523e3a3ba1446c8f768c157cea642119a02741 GIT binary patch literal 181 zcmZ?wbhEHbc&kkH2hg{xUB9fxq8%JG(R5 zT3?Eao`!{eDJnj1U~tLY{9|s;X>G0VWo1vp!yowhEs~V{|NlP&4xspxg^__loIwX9 z53-Yi)#yQKi;S#{{sX6 zi;Dh#`0#&e>3>Vh|D2rv=gC*q|>i-WM_<#QV|C1;G zS5*8rF!=rZ_kUyK|3JV%TcG%pg^__lo4aiRnY%vG=3p{kB40{i-Of=+@!=XF*PxeFcKRp5+{MAgNb8s5C%r0Nf+V(Fc^hI32V>!mF$oB=+5Fr} zS@EPfJ&q(r71kQJj|+D5gx-cdfOUJF*3qLUpX@fhwj2Z2NqP6D^+9o>vF~16iNV_P zex5r<@ABBawmM*6NbZs;Z(r0sJ19qig#gHWiPP;Qdj|l(`@h|k4Q2l`VT~qprl8mW O0000z_u719puq!NScSPpF zg#4pv1xM2>&lOf*D6TwP`0>{VkG0l*8=OKmdxUNAj@;&(aUeGLNb=vme}Ptmoj^XJ z_>+Z^fkBHw2V?}uPYi5D4pR#}bfiS5bS&9qz$LFSv4l%VM0VNAuq7M$%x`OYevyni z6y)sQCFr(n`AWsDVxcXCnHqNfPZgaKf;1I5H8q8UQ?&$wdD=UoSe-bM`a7eHH~@pX3AZUw**Q-a$B>F! ztrKr=Yc}9;F^sEP<>n$(`sY7?u41ok{?o=aDc_ikE__wyGB|VQ^y4}i)8{7jjRziW zWUGH=WYhPK*<`iF!A)ju@)o}Kg)K}zLYiIb+Pf7eGI?>na!@|xVkorHV+YfQ?MuAg z@|CcCdDQaSD5k#PP-zbv&$a)aU$%TM?7cSs{*JlDYX#Oi?a^KlyEQB$CcW%X`i6gI z?dKote=zN6@Xw<+-ygU)|H5tdm42e~0drUR>AA++`7K+t{M~&$wP%?>-X)!`lzy#O bcZ8c|t#ZiQiz<~spD}p4`njxgN@xNAak0;{ literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/inline-delete.png b/game_template/server/web/static/admin/img/inline-delete.png new file mode 100644 index 0000000000000000000000000000000000000000..c5fe53c4e3bbd5f2e349be9c87c896ab99f7b6c3 GIT binary patch literal 707 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-y!3-of#o4MdFfg(P_=LCux&QzF2afeg65dw;$omkMMVXW?CtFZk`pIR1d({T}zkh%K{{83Apa1^-`}_AVkOX=^Bseb$ zXk1uHkY6w`V3=6hIXJnvdHDDRg+wJJrDYYA)wOi>^bL$m&CIQA?d%<#T)lmK{emN- zV&W1~QqwZB^9qZKYisN38@hTXOqe-)(UN5=R&O|P@X@QU-+%lB+H!Mm$rGT-;+`&! zAr-e;Cq{-Ja^PuYcU{#KefL!pw}wQabHGaP2^;l#oTg{~{$Jnq@=QSgz6$wC?Z4;F zncX5<*Znupk-2PZ*!}3avlz}rF0bBaJeT>JqK52Q%>_lhUgq6w8ghqKZ-z)2vqWtF z;bS)S-X1oY3Jwbf{&@-08`So>F$KEh-w1Np$hl76a{52nIT2@ex$F0|b{rP3S(9== zh(XZP$?gDGw7ZIu=Lz>aXBebCEu%l?lwsCvUAE-@q8|1er`{)A(RgVs@%7ZRNlR literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/inline-restore-8bit.png b/game_template/server/web/static/admin/img/inline-restore-8bit.png new file mode 100644 index 0000000000000000000000000000000000000000..d29fe1da636554080638326474fd122a52d2580a GIT binary patch literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-y!3-of#o4L?DUSf35ZC|z|Ns8|`}_CrpFe+o z|Ni}}SFfHud-mwjqkH%6-MV$_+O=z!E?qiz?%dI%M-Lu6xOeZ~ty{Nl+_-V&%9RTi zESNE4#)JtI8X6jah8%q?0Ww;mB*-rq$i)U2&I;?j0;<{N>EaktajSLWZN3%-9+%Up zue-X;r~LnaIoDd^+gVS88J07rXkHa(d|>%Jbw&TBz*Cmx2cDnZ@@rMsQ?uBOjCps> z*dB`Pe^<@&kkRLnu#rib`1}cl5)ZB{7tlEpzTVC-+9TwF@;*(D17Ef6>R3IvHmDk~ zPr4m2S+yrw;pB{o7t$2moMtO>OfYB`s**6)Sm(6=vd9=Y0kbtDnm{r-UW|Qe3-Z literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/inline-restore.png b/game_template/server/web/static/admin/img/inline-restore.png new file mode 100644 index 0000000000000000000000000000000000000000..2a6778884027c4ec32dcadb1a06874549e3f7dc5 GIT binary patch literal 557 zcmeAS@N?(olHy`uVBq!ia0vp^3P8-y!3-of#o4L?sRIE%A+A9B|Ns9$a`1!w`}ZF` zdi3(;%TJ#^eevSO%a<>KF1J&*-Vb!TWl4}p~_mQGJnv%`q&x#~R-L{~x|2{j$-oNBdI0&7SLF-wH+rxxP#7 zuP1YPJbQii(Yq~dP3f;q_7ymM@$-|P&Q+m*ZIiNEIupx$mp2`2I28ol zEyZ+|jP^OIj@Aj>X>S{JXDn^bV=UatdgKOEd4O-t9mlDyRi6I^|6Sg>J7q`9{w&%0 qi*h}eZttiIyT8tSGt2p3-)FP?x|keocymf06y2V#elF{r5}E)*XPtQf literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/inline-splitter-bg.gif b/game_template/server/web/static/admin/img/inline-splitter-bg.gif new file mode 100644 index 0000000000000000000000000000000000000000..2fc918db98f2730cdf7660ab08bdc57f4a406f20 GIT binary patch literal 94 zcmZ?wbh9u|WMyDw_{_jyV{4#2a57-S&KT*C(G{DQl+NNW9y=`fHnu-v==- Ot=>b+CQo8uum%9*_Ae;_ literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/nav-bg-reverse.gif b/game_template/server/web/static/admin/img/nav-bg-reverse.gif new file mode 100644 index 0000000000000000000000000000000000000000..19913fb0b069276d27d11e90842792284ecc5cd8 GIT binary patch literal 178 zcmV;j08Rf#Nk%v~VG#fy0P_F<|NsB^`1t<*{`U6v@$vEd`}^(f?dRv`;^N}{{r&3d z>hA9DU$B!R9e*F0P^XD&Kym`~Lm=e{4X9 z0SG{LF|hV2sP&~}&dXS}F6Z^GaGmodx!1$iy|0P;dtku_cTK7O6OU3(=2%Uiw4%hT z*KOU6HQ%^iR_%ZAF4;$K_VK literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/nav-bg.gif b/game_template/server/web/static/admin/img/nav-bg.gif new file mode 100644 index 0000000000000000000000000000000000000000..d7e22ff1b3ba4c2e4c0995adf9e8defb8f9b32d7 GIT binary patch literal 265 zcmZ?wbh9u|6l9QR_`<;O{rmT~Z{I$D{`}ReSAYNh{q^hDhYue(@t*9{u_A=gpfpKY#xG_U+rVXV3oq`}hCvu4kkJBw+-!bOW0h%H;bV&yW+$#R@9*pI_UiEW z?CP?Ck95?C|R9>g4S7hI*~?c(b5<>~I>>hs{~^5^I0=I80; z=IGz%@8ag`<>lq%*5<_4=+e^C z#nk1*)a1g{=h4y8!qVf<&(F@z&b-j%ywK&%&CR~h<;={?xXN z&E(0+$+peq$jHdI%ip)m;Iz!+w9MqT%HPMw$F$4hwaenO%j2}k;Izu%vdZJM$=|Zc z;j_rz#Kgq1$KA2V-NVDftj6HN!otD9!KlRGz`(%2zrU)&+P=QNr@`C2yu78s+@-+W zroh{!z}uj}25$81 znTg4wKV*_V0SZ({QZjm6}T8>Lu zxjGjJ)+xulI<9B;qVJnt{R*M#&-Y#bxc995vnxO+na5sZdb#&&u1=qRmR%x-M3#sxVCMpF zM)_t4c82h#EK=5a=bwS9!2+Lmh7dxTW=hHBop%bsr7UTxAq1q7a#@O-V_v!d4{KVP zr4Xc&%0j7s=B=|F-c zK>^HRP9tb=A*~?X*zo{Cb_@eY8{Br=#w+&yb=oDHs5S!M>yn1Bo=p=wh%g8vwPz!3OK2&oU50>;X_7L=3TT*kpXd2@U|| zzzH-UgA6wKNQ2JFZsccxDd4oMPaN%ZBl8p-G`Jj% z!Xz!s6@Vx$Otk~qAx-e$zfEprRTWH%Oow9jPh8 z1m3CnV1VVRVZK`2id^Qo0RTv#I_8(frida+Fa-cpn7{6N>9W&KhwXC5Mh8%;t8RO2 zn#AV20U#K!i1CV{^7`vwfbjhDlL20OW~*Vg8RCjeKKb&83v@RsqKA%(YX!mGJ1TX% z?f(1lGgQMQ!renoAu;r|(vAV(bxFp%-Y;(rJ{gEC?ykUGS|MQ1SAGh$SO z`xWAXPPo7YH&}@5bO=LEFu({nbfIUQ;T~1f0#h!)!a`83KmM4(K z$ic%{Fl7Wlj0F$>$zh=(l4BAgG~y8JPy_OqXA&}4j}D{5KL*8uLCO%IK*sY7t65Ep z1+1dglyQ#%8f1R;yI=m|$Hq3s4+&9X3zc|akPuuV6QpWE3nZ0^OE6#{A&>>DT9v9* z5fW8>+y@dep(X~jU`&~4K`GRv1_m%7Q)=j=Ah~48PF`|LnE(`9w&cl93PFyt7-c0( z$$(6TGL>ynR0ulh%21X~)kfJQsE zvX+74Wih*%%u-+go5?I^I~!WhWFR!36>T3!bDA(#;WVfTL~35F8rHP753YF)Y+@r@ zLCl6WdUP|JNKL9z3lf7sKm!_qhyg+fpax)wp$J6~12EJ;0E4K35;e)e5h$UCg3KTp zUP$W^Brt=tCSwK!Q3NQc0)(;XfGVI+1VOAZ3r4Jf0Du(&D+X(gy*A?y82~H+l#qxkpsX!!5Wy=>p*6(4T*qa3J3^-M79A}f~Wx&T&hA4PFvQ3P}LZ8=m{N+ z!BvF+%s>&;poSt!yVTzL_P4#U0Vc#D4ouhpAqkM0FZQqpUx>pF2{4Eqd=ZA~Vxb9P z@P)bvVgqq7cMn*ozz&!zj^hpl6TZL%CYq3d1nfY)=_QCb(rbhECZGwv$geo$I}J>5 zz`jkGhJg2bUU`UP8arq&7KkekOmJ5o0-i<~?kiw^xt(K0HGWe3p!hdNxXX?bKL8&QBj8J?XD3l~Ic!Z5=ll-6NG+o2uB Zpam_8(T+!#coX3W$0kPo=cNV&06TG~#AN^g literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/selector-search.gif b/game_template/server/web/static/admin/img/selector-search.gif new file mode 100644 index 0000000000000000000000000000000000000000..6d5f4c74923af2daba13baa484364380fb1614b9 GIT binary patch literal 552 zcmZ?wbhEHb6krfwc*ekR;r}fg8=Jp>|9X0Qe){yu!NI}X+uO&-$I8koH8s`3!otqZ z?(W^YSy@?TW@e{PpT2zg^6J&ABO)T)-Q5!s60Tjl=Ire3>gqaW%9PsL+PQP*Ha9mX zCnrZoN9W|^#K*^f{rYv@ym@clym|8E$=&V_@LPJBVtE(?uxDXW;RaaLxbLLEYd;7Mww%N01*VNRUKY#wgg9n+J znUg0^j*E-y=;*j{51X2rQc_ZG-@bkS{(VPBM~0yT6o0ZXf?TTuB0zD%z~0)x z(A3=0+M*~cF2*k1)@;Hotjo#FYS$;lCc@9cB5K1e*(T4%&$OJ0N1L&YotI^~f{vr% zn$~qnssdI5qW0Tb4Ak_r)E!)0jSnm~~hECZvY5o%BVqvfb0K*)eZU6uP literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/sorting-icons.gif b/game_template/server/web/static/admin/img/sorting-icons.gif new file mode 100644 index 0000000000000000000000000000000000000000..451aae59874c795763cee9b044bf2c487f15a853 GIT binary patch literal 369 zcmZ?wbhEHb6k}*%IKsg2;>C++&z_w!(hg+Pin}?%lgLZrnIy)1g(XR`svm zGk^a4IdkUBoH=vGj2X$3R|a-2{Qv(y1JOY7CkrDxgCK(rNC(JH2G(~8Dt#%L^DAd!+Lo{0I{PlaWBPvS zl4w&vYiXCFU#`t13Om+FTO9ns`tXZiGLm(GX1Uw7}z rTlY@?-zz3A8IKJL+X^m4Dfq0|aN9iZ(F((o&leUP?N)YTV6X-NKX0r` literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/tool-left.gif b/game_template/server/web/static/admin/img/tool-left.gif new file mode 100644 index 0000000000000000000000000000000000000000..011490ff3a0100bea63eca7d8a3f821edecf6d3f GIT binary patch literal 197 zcmV;$06PCiNk%w1VF>^d0K@+9+1 z>Fn(6>gwvFqods1+`_`bsi~>N#Kh<4=efDLot>Sks;aTEvEJU^(b3W4lL@fC0syEMTS%hy>Zm!0PcpEpJwG;hh<#5^Y_P4;QTzJF~^?;ZNVo;-~tj z3GxIURaQ?9NK-h~-O_%Ehr|6W>p_W03|&Y3moVBW-4OX{v_eKqd6L;=PDTc60DL-S AivR!s literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/tool-right.gif b/game_template/server/web/static/admin/img/tool-right.gif new file mode 100644 index 0000000000000000000000000000000000000000..cdc140cc590a56bf45ceef6eaeebf47e4a699ac3 GIT binary patch literal 198 zcmV;%06G6hNk%w1VF>^d0K@+9+1 z>Fn(6>gwvFqods1+`_`bsi~>N#Kh<4=efDLot>Sks;aTEvEJU^(b3W4R literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/tool-right_over.gif b/game_template/server/web/static/admin/img/tool-right_over.gif new file mode 100644 index 0000000000000000000000000000000000000000..4db977e838dd97ae4f59524a764cf8298f19ccc5 GIT binary patch literal 200 zcmZ?wbhEHblL@fC0syEMTS%hy>Zm!0M`SJhE@w+}Rz8zHMDu50@FVv!<=}o0a$2j_;|E wNJ^Lce8rW`28j}!f$kDtvUrX>)$-C{u;f$x#4um1{ZvBtsg!MMAsh_W0PS95AOHXW literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/tooltag-add.gif b/game_template/server/web/static/admin/img/tooltag-add.gif new file mode 100644 index 0000000000000000000000000000000000000000..8b53d49ae58dbc324ca7fb318198b187fc124c09 GIT binary patch literal 932 zcmZ?wbhEHb6lM@)_|Cxa`}glZfBv+zw0!^m{n4XG%aU|5d-nPB=MNq{xOeZ~wr$(``}?0g zdp3Xm{Fg6Zwzjt3y?giHzkgr9e*ORde?vn9!zdUHfx#C7ia%Mvj?w{lD0p`BHNuZH8`gv5jGtilW#8Ul-s@(Rm3u`n_%YH1afFWM53xbWyi0reSoZU`=F zW>B;|HRonfOG771<|UO1;}pj!{KgMbG6N3=Of=;-YN#}Nd4NeFEUKo%!I@!YFuRjN h0z*m|qaatng9}?1vhrtKUbNTY0V7A3A`=sXH2_xRgyR4J literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/tooltag-add.png b/game_template/server/web/static/admin/img/tooltag-add.png new file mode 100644 index 0000000000000000000000000000000000000000..1488ecf2ef9be1d5ba1541904b0761831eacf942 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJTYyi9>who^t@)Y*6k#k0@(X5g zcy=QV$dUJSaSW-rwe;*lMj)@r;OGC$*+-Xki)?Au7Z81S`izJHgX-)r-*y8f89ZJ6 KT-G@yGywo`GbQ~1 literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/tooltag-add_over.gif b/game_template/server/web/static/admin/img/tooltag-add_over.gif new file mode 100644 index 0000000000000000000000000000000000000000..bfc52f10de75998687154585752513a27a02e5c4 GIT binary patch literal 336 zcmZ?wbhEHb6lM@)xXQp_AKmol&!20zAN~IQ+b5;fJ+bBa>km(!zx7XVkIL_<>s=I* z-Fg4<%i9m06*kQ}fA#LqpFhjm=B8Ioc1vhpx_)m~&D4mz?loHv1!s0R#56sA{$|&q zv$L0NTfOzr<~=7olUmZNro(_t( z{<*|8FIl(u()IiQ|Npm*YGNP-DE?#tE7t*$AU`p%OuD z64&(xJYQ3LjTSFY75~+{EbMWTq?Tj+^`lM+ch;D9HZb0eJO28D|1uloD@pFe*-apJ^Hn>L+2d-m?#yFEQU|Ni~^ z`t|Go|Nk2r8W_k0ia%MvT6I7q$WIJxH4gmQ9x7=Hf&p1aV>r7mD6wA%agA(z(Y9-o zn{Z!7=F3()sh$@ap>8_^S=AZ45`|}1urBhDn)0zi%$1R$zCpXHxz#1VpeR78J1{*n zKx$GzLbz~Ucw``(R#|u;i#+q?x}l#o5$^*7-1rs_)vpOO{(vYlW{PgEasf C+m{mn literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/tooltag-arrowright.png b/game_template/server/web/static/admin/img/tooltag-arrowright.png new file mode 100644 index 0000000000000000000000000000000000000000..2f05598f53be50627daf5265ce2fa3c7fc48b3cf GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`{hlt4Ar-gIUf#&r>>$#R*xah< z%NF1bP0l+XkKCcREV literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/img/tooltag-arrowright_over.gif b/game_template/server/web/static/admin/img/tooltag-arrowright_over.gif new file mode 100644 index 0000000000000000000000000000000000000000..7163189604a638ee170f093cd042075a7da48c7a GIT binary patch literal 354 zcmZ?wbhEHb6lM@)xXQp_AKmol&!20zAN~IQ+b5;fJ+bBa>km(!zx7XVkIL_<>s=I* z-Fg4<%i9m06*kQ}fA#LqpFhjm=B8Ioc1vhpx_)m~&D4mz?loHv1!s0R#56sA{$|&q zv$L0NTfOzr<~=7olUmZNro(_t( z{<*|8FIl(u()IiQ|Npm*YGNP-DE?#tE7t*$AU`p%)jRO!c&Mb=iC%tqw1%_mf>OuD z64%5AjkaH#++@37WWH>5lj_LG3QgM?$)(Pyl_<=6_Sxb99hOrSl|Hhr49pEp`qi!N zF8KvT0m8k3>6rnFQvwpgW$MEt13C4|!UI|47?&<{5fc~Cj$Ol`%&8*1!G}pzckjNv NA{t6st9%_9tO2syk|zKF literal 0 HcmV?d00001 diff --git a/game_template/server/web/static/admin/js/LICENSE-JQUERY.txt b/game_template/server/web/static/admin/js/LICENSE-JQUERY.txt new file mode 100644 index 000000000..a4c5bd76a --- /dev/null +++ b/game_template/server/web/static/admin/js/LICENSE-JQUERY.txt @@ -0,0 +1,20 @@ +Copyright (c) 2010 John Resig, http://jquery.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/game_template/server/web/static/admin/js/SelectBox.js b/game_template/server/web/static/admin/js/SelectBox.js new file mode 100644 index 000000000..db3206ccf --- /dev/null +++ b/game_template/server/web/static/admin/js/SelectBox.js @@ -0,0 +1,114 @@ +var SelectBox = { + cache: new Object(), + init: function(id) { + var box = document.getElementById(id); + var node; + SelectBox.cache[id] = new Array(); + var cache = SelectBox.cache[id]; + for (var i = 0; (node = box.options[i]); i++) { + cache.push({value: node.value, text: node.text, displayed: 1}); + } + }, + redisplay: function(id) { + // Repopulate HTML select box from cache + var box = document.getElementById(id); + box.options.length = 0; // clear all options + for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) { + var node = SelectBox.cache[id][i]; + if (node.displayed) { + var new_option = new Option(node.text, node.value, false, false); + // Shows a tooltip when hovering over the option + new_option.setAttribute("title", node.text); + box.options[box.options.length] = new_option; + } + } + }, + filter: function(id, text) { + // Redisplay the HTML select box, displaying only the choices containing ALL + // the words in text. (It's an AND search.) + var tokens = text.toLowerCase().split(/\s+/); + var node, token; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + node.displayed = 1; + for (var j = 0; (token = tokens[j]); j++) { + if (node.text.toLowerCase().indexOf(token) == -1) { + node.displayed = 0; + } + } + } + SelectBox.redisplay(id); + }, + delete_from_cache: function(id, value) { + var node, delete_index = null; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + if (node.value == value) { + delete_index = i; + break; + } + } + var j = SelectBox.cache[id].length - 1; + for (var i = delete_index; i < j; i++) { + SelectBox.cache[id][i] = SelectBox.cache[id][i+1]; + } + SelectBox.cache[id].length--; + }, + add_to_cache: function(id, option) { + SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); + }, + cache_contains: function(id, value) { + // Check if an item is contained in the cache + var node; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + if (node.value == value) { + return true; + } + } + return false; + }, + move: function(from, to) { + var from_box = document.getElementById(from); + var to_box = document.getElementById(to); + var option; + for (var i = 0; (option = from_box.options[i]); i++) { + if (option.selected && SelectBox.cache_contains(from, option.value)) { + SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); + SelectBox.delete_from_cache(from, option.value); + } + } + SelectBox.redisplay(from); + SelectBox.redisplay(to); + }, + move_all: function(from, to) { + var from_box = document.getElementById(from); + var to_box = document.getElementById(to); + var option; + for (var i = 0; (option = from_box.options[i]); i++) { + if (SelectBox.cache_contains(from, option.value)) { + SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); + SelectBox.delete_from_cache(from, option.value); + } + } + SelectBox.redisplay(from); + SelectBox.redisplay(to); + }, + sort: function(id) { + SelectBox.cache[id].sort( function(a, b) { + a = a.text.toLowerCase(); + b = b.text.toLowerCase(); + try { + if (a > b) return 1; + if (a < b) return -1; + } + catch (e) { + // silently fail on IE 'unknown' exception + } + return 0; + } ); + }, + select_all: function(id) { + var box = document.getElementById(id); + for (var i = 0; i < box.options.length; i++) { + box.options[i].selected = 'selected'; + } + } +} diff --git a/game_template/server/web/static/admin/js/SelectFilter2.js b/game_template/server/web/static/admin/js/SelectFilter2.js new file mode 100644 index 000000000..d9f6aad39 --- /dev/null +++ b/game_template/server/web/static/admin/js/SelectFilter2.js @@ -0,0 +1,161 @@ +/* +SelectFilter2 - Turns a multiple-select box into a filter interface. + +Requires core.js, SelectBox.js and addevent.js. +*/ +(function($) { +function findForm(node) { + // returns the node of the form containing the given node + if (node.tagName.toLowerCase() != 'form') { + return findForm(node.parentNode); + } + return node; +} + +window.SelectFilter = { + init: function(field_id, field_name, is_stacked, admin_static_prefix) { + if (field_id.match(/__prefix__/)){ + // Don't intialize on empty forms. + return; + } + var from_box = document.getElementById(field_id); + from_box.id += '_from'; // change its ID + from_box.className = 'filtered'; + + var ps = from_box.parentNode.getElementsByTagName('p'); + for (var i=0; i, because it just gets in the way. + from_box.parentNode.removeChild(ps[i]); + } else if (ps[i].className.indexOf("help") != -1) { + // Move help text up to the top so it isn't below the select + // boxes or wrapped off on the side to the right of the add + // button: + from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild); + } + } + + //
or
+ var selector_div = quickElement('div', from_box.parentNode); + selector_div.className = is_stacked ? 'selector stacked' : 'selector'; + + //
+ var selector_available = quickElement('div', selector_div); + selector_available.className = 'selector-available'; + var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name])); + quickElement('img', title_available, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of available %s. You may choose some by selecting them in the box below and then clicking the "Choose" arrow between the two boxes.'), [field_name])); + + var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter'); + filter_p.className = 'selector-filter'; + + var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + "_input"); + + var search_selector_img = quickElement('img', search_filter_label, '', 'src', admin_static_prefix + 'img/selector-search.gif', 'class', 'help-tooltip', 'alt', '', 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name])); + + filter_p.appendChild(document.createTextNode(' ')); + + var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter")); + filter_input.id = field_id + '_input'; + + selector_available.appendChild(from_box); + var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_all_link'); + choose_all.className = 'selector-chooseall'; + + //
    + var selector_chooser = quickElement('ul', selector_div); + selector_chooser.className = 'selector-chooser'; + var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_link'); + add_link.className = 'selector-add'; + var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_link'); + remove_link.className = 'selector-remove'; + + //
    + var selector_chosen = quickElement('div', selector_div); + selector_chosen.className = 'selector-chosen'; + var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name])); + quickElement('img', title_chosen, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of chosen %s. You may remove some by selecting them in the box below and then clicking the "Remove" arrow between the two boxes.'), [field_name])); + + var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name')); + to_box.className = 'filtered'; + var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_all_link'); + clear_all.className = 'selector-clearall'; + + from_box.setAttribute('name', from_box.getAttribute('name') + '_old'); + + // Set up the JavaScript event handlers for the select box filter interface + addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); + addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); + addEvent(from_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) }); + addEvent(to_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) }); + addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); SelectFilter.refresh_icons(field_id); }); + addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); SelectFilter.refresh_icons(field_id); }); + addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); + SelectBox.init(field_id + '_from'); + SelectBox.init(field_id + '_to'); + // Move selected from_box options to to_box + SelectBox.move(field_id + '_from', field_id + '_to'); + + if (!is_stacked) { + // In horizontal mode, give the same height to the two boxes. + var j_from_box = $(from_box); + var j_to_box = $(to_box); + var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); } + if (j_from_box.outerHeight() > 0) { + resize_filters(); // This fieldset is already open. Resize now. + } else { + // This fieldset is probably collapsed. Wait for its 'show' event. + j_to_box.closest('fieldset').one('show.fieldset', resize_filters); + } + } + + // Initial icon refresh + SelectFilter.refresh_icons(field_id); + }, + refresh_icons: function(field_id) { + var from = $('#' + field_id + '_from'); + var to = $('#' + field_id + '_to'); + var is_from_selected = from.find('option:selected').length > 0; + var is_to_selected = to.find('option:selected').length > 0; + // Active if at least one item is selected + $('#' + field_id + '_add_link').toggleClass('active', is_from_selected); + $('#' + field_id + '_remove_link').toggleClass('active', is_to_selected); + // Active if the corresponding box isn't empty + $('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0); + $('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0); + }, + filter_key_up: function(event, field_id) { + var from = document.getElementById(field_id + '_from'); + // don't submit form if user pressed Enter + if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) { + from.selectedIndex = 0; + SelectBox.move(field_id + '_from', field_id + '_to'); + from.selectedIndex = 0; + return false; + } + var temp = from.selectedIndex; + SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value); + from.selectedIndex = temp; + return true; + }, + filter_key_down: function(event, field_id) { + var from = document.getElementById(field_id + '_from'); + // right arrow -- move across + if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) { + var old_index = from.selectedIndex; + SelectBox.move(field_id + '_from', field_id + '_to'); + from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index; + return false; + } + // down arrow -- wrap around + if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) { + from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1; + } + // up arrow -- wrap around + if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) { + from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1; + } + return true; + } +} + +})(django.jQuery); diff --git a/game_template/server/web/static/admin/js/actions.js b/game_template/server/web/static/admin/js/actions.js new file mode 100644 index 000000000..58f572f25 --- /dev/null +++ b/game_template/server/web/static/admin/js/actions.js @@ -0,0 +1,144 @@ +(function($) { + var lastChecked; + + $.fn.actions = function(opts) { + var options = $.extend({}, $.fn.actions.defaults, opts); + var actionCheckboxes = $(this); + var list_editable_changed = false; + var checker = function(checked) { + if (checked) { + showQuestion(); + } else { + reset(); + } + $(actionCheckboxes).prop("checked", checked) + .parent().parent().toggleClass(options.selectedClass, checked); + }, + updateCounter = function() { + var sel = $(actionCheckboxes).filter(":checked").length; + // _actions_icnt is defined in the generated HTML + // and contains the total amount of objects in the queryset + $(options.counterContainer).html(interpolate( + ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { + sel: sel, + cnt: _actions_icnt + }, true)); + $(options.allToggle).prop("checked", function() { + var value; + if (sel == actionCheckboxes.length) { + value = true; + showQuestion(); + } else { + value = false; + clearAcross(); + } + return value; + }); + }, + showQuestion = function() { + $(options.acrossClears).hide(); + $(options.acrossQuestions).show(); + $(options.allContainer).hide(); + }, + showClear = function() { + $(options.acrossClears).show(); + $(options.acrossQuestions).hide(); + $(options.actionContainer).toggleClass(options.selectedClass); + $(options.allContainer).show(); + $(options.counterContainer).hide(); + }, + reset = function() { + $(options.acrossClears).hide(); + $(options.acrossQuestions).hide(); + $(options.allContainer).hide(); + $(options.counterContainer).show(); + }, + clearAcross = function() { + reset(); + $(options.acrossInput).val(0); + $(options.actionContainer).removeClass(options.selectedClass); + }; + // Show counter by default + $(options.counterContainer).show(); + // Check state of checkboxes and reinit state if needed + $(this).filter(":checked").each(function(i) { + $(this).parent().parent().toggleClass(options.selectedClass); + updateCounter(); + if ($(options.acrossInput).val() == 1) { + showClear(); + } + }); + $(options.allToggle).show().click(function() { + checker($(this).prop("checked")); + updateCounter(); + }); + $("a", options.acrossQuestions).click(function(event) { + event.preventDefault(); + $(options.acrossInput).val(1); + showClear(); + }); + $("a", options.acrossClears).click(function(event) { + event.preventDefault(); + $(options.allToggle).prop("checked", false); + clearAcross(); + checker(0); + updateCounter(); + }); + lastChecked = null; + $(actionCheckboxes).click(function(event) { + if (!event) { event = window.event; } + var target = event.target ? event.target : event.srcElement; + if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey === true) { + var inrange = false; + $(lastChecked).prop("checked", target.checked) + .parent().parent().toggleClass(options.selectedClass, target.checked); + $(actionCheckboxes).each(function() { + if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) { + inrange = (inrange) ? false : true; + } + if (inrange) { + $(this).prop("checked", target.checked) + .parent().parent().toggleClass(options.selectedClass, target.checked); + } + }); + } + $(target).parent().parent().toggleClass(options.selectedClass, target.checked); + lastChecked = target; + updateCounter(); + }); + $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() { + list_editable_changed = true; + }); + $('form#changelist-form button[name="index"]').click(function(event) { + if (list_editable_changed) { + return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); + } + }); + $('form#changelist-form input[name="_save"]').click(function(event) { + var action_changed = false; + $('select option:selected', options.actionContainer).each(function() { + if ($(this).val()) { + action_changed = true; + } + }); + if (action_changed) { + if (list_editable_changed) { + return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")); + } else { + return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.")); + } + } + }); + }; + /* Setup plugin defaults */ + $.fn.actions.defaults = { + actionContainer: "div.actions", + counterContainer: "span.action-counter", + allContainer: "div.actions span.all", + acrossInput: "div.actions input.select-across", + acrossQuestions: "div.actions span.question", + acrossClears: "div.actions span.clear", + allToggle: "#action-toggle", + selectedClass: "selected" + }; +})(django.jQuery); diff --git a/game_template/server/web/static/admin/js/actions.min.js b/game_template/server/web/static/admin/js/actions.min.js new file mode 100644 index 000000000..0dd6683fb --- /dev/null +++ b/game_template/server/web/static/admin/js/actions.min.js @@ -0,0 +1,6 @@ +(function(a){var f;a.fn.actions=function(q){var b=a.extend({},a.fn.actions.defaults,q),g=a(this),e=!1,m=function(c){c?k():l();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).prop("checked",function(){var a;c==g.length?(a=!0,k()):(a=!1,n());return a})},k=function(){a(b.acrossClears).hide(); +a(b.acrossQuestions).show();a(b.allContainer).hide()},p=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},n=function(){l();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass); +h();1==a(b.acrossInput).val()&&p()});a(b.allToggle).show().click(function(){m(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);p()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);n();m(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&&a.data(f)!=a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass, +d.checked);a(g).each(function(){if(a.data(this)==a.data(f)||a.data(this)==a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(a){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))}); +a('form#changelist-form input[name="_save"]').click(function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})}; +a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); diff --git a/game_template/server/web/static/admin/js/admin/DateTimeShortcuts.js b/game_template/server/web/static/admin/js/admin/DateTimeShortcuts.js new file mode 100644 index 000000000..fdd4bef53 --- /dev/null +++ b/game_template/server/web/static/admin/js/admin/DateTimeShortcuts.js @@ -0,0 +1,356 @@ +// Inserts shortcut buttons after all of the following: +// +// + +var DateTimeShortcuts = { + calendars: [], + calendarInputs: [], + clockInputs: [], + dismissClockFunc: [], + dismissCalendarFunc: [], + calendarDivName1: 'calendarbox', // name of calendar
    that gets toggled + calendarDivName2: 'calendarin', // name of
    that contains calendar + calendarLinkName: 'calendarlink',// name of the link that is used to toggle + clockDivName: 'clockbox', // name of clock
    that gets toggled + clockLinkName: 'clocklink', // name of the link that is used to toggle + shortCutsClass: 'datetimeshortcuts', // class of the clock and cal shortcuts + timezoneWarningClass: 'timezonewarning', // class of the warning for timezone mismatch + timezoneOffset: 0, + admin_media_prefix: '', + init: function() { + // Get admin_media_prefix by grabbing it off the window object. It's + // set in the admin/base.html template, so if it's not there, someone's + // overridden the template. In that case, we'll set a clearly-invalid + // value in the hopes that someone will examine HTTP requests and see it. + if (window.__admin_media_prefix__ != undefined) { + DateTimeShortcuts.admin_media_prefix = window.__admin_media_prefix__; + } else { + DateTimeShortcuts.admin_media_prefix = '/missing-admin-media-prefix/'; + } + + if (window.__admin_utc_offset__ != undefined) { + var serverOffset = window.__admin_utc_offset__; + var localOffset = new Date().getTimezoneOffset() * -60; + DateTimeShortcuts.timezoneOffset = localOffset - serverOffset; + } + + var inputs = document.getElementsByTagName('input'); + for (i=0; i 0) { + message = ngettext( + 'Note: You are %s hour ahead of server time.', + 'Note: You are %s hours ahead of server time.', + timezoneOffset + ); + } + else { + timezoneOffset *= -1 + message = ngettext( + 'Note: You are %s hour behind server time.', + 'Note: You are %s hours behind server time.', + timezoneOffset + ); + } + message = interpolate(message, [timezoneOffset]); + + var $warning = $(''); + $warning.attr('class', warningClass); + $warning.text(message); + + $(inp).parent() + .append($('
    ')) + .append($warning) + }, + // Add clock widget to a given field + addClock: function(inp) { + var num = DateTimeShortcuts.clockInputs.length; + DateTimeShortcuts.clockInputs[num] = inp; + DateTimeShortcuts.dismissClockFunc[num] = function() { DateTimeShortcuts.dismissClock(num); return true; }; + + // Shortcut links (clock icon and "Now" link) + var shortcuts_span = document.createElement('span'); + shortcuts_span.className = DateTimeShortcuts.shortCutsClass; + inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling); + var now_link = document.createElement('a'); + now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", -1);"); + now_link.appendChild(document.createTextNode(gettext('Now'))); + var clock_link = document.createElement('a'); + clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');'); + clock_link.id = DateTimeShortcuts.clockLinkName + num; + quickElement('img', clock_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/icon_clock.gif', 'alt', gettext('Clock')); + shortcuts_span.appendChild(document.createTextNode('\240')); + shortcuts_span.appendChild(now_link); + shortcuts_span.appendChild(document.createTextNode('\240|\240')); + shortcuts_span.appendChild(clock_link); + + // Create clock link div + // + // Markup looks like: + //
    + //

    Choose a time

    + // + //

    Cancel

    + //
    + + var clock_box = document.createElement('div'); + clock_box.style.display = 'none'; + clock_box.style.position = 'absolute'; + clock_box.className = 'clockbox module'; + clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num); + document.body.appendChild(clock_box); + addEvent(clock_box, 'click', cancelEventPropagation); + + quickElement('h2', clock_box, gettext('Choose a time')); + var time_list = quickElement('ul', clock_box); + time_list.className = 'timelist'; + quickElement("a", quickElement("li", time_list), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", -1);"); + quickElement("a", quickElement("li", time_list), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 0);"); + quickElement("a", quickElement("li", time_list), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 6);"); + quickElement("a", quickElement("li", time_list), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", 12);"); + + var cancel_p = quickElement('p', clock_box); + cancel_p.className = 'calendar-cancel'; + quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');'); + django.jQuery(document).bind('keyup', function(event) { + if (event.which == 27) { + // ESC key closes popup + DateTimeShortcuts.dismissClock(num); + event.preventDefault(); + } + }); + }, + openClock: function(num) { + var clock_box = document.getElementById(DateTimeShortcuts.clockDivName+num) + var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName+num) + + // Recalculate the clockbox position + // is it left-to-right or right-to-left layout ? + if (getStyle(document.body,'direction')!='rtl') { + clock_box.style.left = findPosX(clock_link) + 17 + 'px'; + } + else { + // since style's width is in em, it'd be tough to calculate + // px value of it. let's use an estimated px for now + // TODO: IE returns wrong value for findPosX when in rtl mode + // (it returns as it was left aligned), needs to be fixed. + clock_box.style.left = findPosX(clock_link) - 110 + 'px'; + } + clock_box.style.top = Math.max(0, findPosY(clock_link) - 30) + 'px'; + + // Show the clock box + clock_box.style.display = 'block'; + addEvent(document, 'click', DateTimeShortcuts.dismissClockFunc[num]); + }, + dismissClock: function(num) { + document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none'; + removeEvent(document, 'click', DateTimeShortcuts.dismissClockFunc[num]); + }, + handleClockQuicklink: function(num, val) { + var d; + if (val == -1) { + d = DateTimeShortcuts.now(); + } + else { + d = new Date(1970, 1, 1, val, 0, 0, 0) + } + DateTimeShortcuts.clockInputs[num].value = d.strftime(get_format('TIME_INPUT_FORMATS')[0]); + DateTimeShortcuts.clockInputs[num].focus(); + DateTimeShortcuts.dismissClock(num); + }, + // Add calendar widget to a given field. + addCalendar: function(inp) { + var num = DateTimeShortcuts.calendars.length; + + DateTimeShortcuts.calendarInputs[num] = inp; + DateTimeShortcuts.dismissCalendarFunc[num] = function() { DateTimeShortcuts.dismissCalendar(num); return true; }; + + // Shortcut links (calendar icon and "Today" link) + var shortcuts_span = document.createElement('span'); + shortcuts_span.className = DateTimeShortcuts.shortCutsClass; + inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling); + var today_link = document.createElement('a'); + today_link.setAttribute('href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);'); + today_link.appendChild(document.createTextNode(gettext('Today'))); + var cal_link = document.createElement('a'); + cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');'); + cal_link.id = DateTimeShortcuts.calendarLinkName + num; + quickElement('img', cal_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/icon_calendar.gif', 'alt', gettext('Calendar')); + shortcuts_span.appendChild(document.createTextNode('\240')); + shortcuts_span.appendChild(today_link); + shortcuts_span.appendChild(document.createTextNode('\240|\240')); + shortcuts_span.appendChild(cal_link); + + // Create calendarbox div. + // + // Markup looks like: + // + //
    + //

    + // + // February 2003 + //

    + //
    + // + //
    + //
    + // Yesterday | Today | Tomorrow + //
    + //

    Cancel

    + //
    + var cal_box = document.createElement('div'); + cal_box.style.display = 'none'; + cal_box.style.position = 'absolute'; + cal_box.className = 'calendarbox module'; + cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num); + document.body.appendChild(cal_box); + addEvent(cal_box, 'click', cancelEventPropagation); + + // next-prev links + var cal_nav = quickElement('div', cal_box); + var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', 'javascript:DateTimeShortcuts.drawPrev('+num+');'); + cal_nav_prev.className = 'calendarnav-previous'; + var cal_nav_next = quickElement('a', cal_nav, '>', 'href', 'javascript:DateTimeShortcuts.drawNext('+num+');'); + cal_nav_next.className = 'calendarnav-next'; + + // main box + var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num); + cal_main.className = 'calendar'; + DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num)); + DateTimeShortcuts.calendars[num].drawCurrent(); + + // calendar shortcuts + var shortcuts = quickElement('div', cal_box); + shortcuts.className = 'calendar-shortcuts'; + quickElement('a', shortcuts, gettext('Yesterday'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', -1);'); + shortcuts.appendChild(document.createTextNode('\240|\240')); + quickElement('a', shortcuts, gettext('Today'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);'); + shortcuts.appendChild(document.createTextNode('\240|\240')); + quickElement('a', shortcuts, gettext('Tomorrow'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', +1);'); + + // cancel bar + var cancel_p = quickElement('p', cal_box); + cancel_p.className = 'calendar-cancel'; + quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');'); + django.jQuery(document).bind('keyup', function(event) { + if (event.which == 27) { + // ESC key closes popup + DateTimeShortcuts.dismissCalendar(num); + event.preventDefault(); + } + }); + }, + openCalendar: function(num) { + var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num) + var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num) + var inp = DateTimeShortcuts.calendarInputs[num]; + + // Determine if the current value in the input has a valid date. + // If so, draw the calendar with that date's year and month. + if (inp.value) { + var date_parts = inp.value.split('-'); + var year = date_parts[0]; + var month = parseFloat(date_parts[1]); + var selected = new Date(inp.value); + if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) { + DateTimeShortcuts.calendars[num].drawDate(month, year, selected); + } + } + + // Recalculate the clockbox position + // is it left-to-right or right-to-left layout ? + if (getStyle(document.body,'direction')!='rtl') { + cal_box.style.left = findPosX(cal_link) + 17 + 'px'; + } + else { + // since style's width is in em, it'd be tough to calculate + // px value of it. let's use an estimated px for now + // TODO: IE returns wrong value for findPosX when in rtl mode + // (it returns as it was left aligned), needs to be fixed. + cal_box.style.left = findPosX(cal_link) - 180 + 'px'; + } + cal_box.style.top = Math.max(0, findPosY(cal_link) - 75) + 'px'; + + cal_box.style.display = 'block'; + addEvent(document, 'click', DateTimeShortcuts.dismissCalendarFunc[num]); + }, + dismissCalendar: function(num) { + document.getElementById(DateTimeShortcuts.calendarDivName1+num).style.display = 'none'; + removeEvent(document, 'click', DateTimeShortcuts.dismissCalendarFunc[num]); + }, + drawPrev: function(num) { + DateTimeShortcuts.calendars[num].drawPreviousMonth(); + }, + drawNext: function(num) { + DateTimeShortcuts.calendars[num].drawNextMonth(); + }, + handleCalendarCallback: function(num) { + var format = get_format('DATE_INPUT_FORMATS')[0]; + // the format needs to be escaped a little + format = format.replace('\\', '\\\\'); + format = format.replace('\r', '\\r'); + format = format.replace('\n', '\\n'); + format = format.replace('\t', '\\t'); + format = format.replace("'", "\\'"); + return ["function(y, m, d) { DateTimeShortcuts.calendarInputs[", + num, + "].value = new Date(y, m-1, d).strftime('", + format, + "');DateTimeShortcuts.calendarInputs[", + num, + "].focus();document.getElementById(DateTimeShortcuts.calendarDivName1+", + num, + ").style.display='none';}"].join(''); + }, + handleCalendarQuickLink: function(num, offset) { + var d = DateTimeShortcuts.now(); + d.setDate(d.getDate() + offset) + DateTimeShortcuts.calendarInputs[num].value = d.strftime(get_format('DATE_INPUT_FORMATS')[0]); + DateTimeShortcuts.calendarInputs[num].focus(); + DateTimeShortcuts.dismissCalendar(num); + } +} + +addEvent(window, 'load', DateTimeShortcuts.init); diff --git a/game_template/server/web/static/admin/js/admin/RelatedObjectLookups.js b/game_template/server/web/static/admin/js/admin/RelatedObjectLookups.js new file mode 100644 index 000000000..0d7ca41d9 --- /dev/null +++ b/game_template/server/web/static/admin/js/admin/RelatedObjectLookups.js @@ -0,0 +1,97 @@ +// Handles related-objects functionality: lookup link for raw_id_fields +// and Add Another links. + +function html_unescape(text) { + // Unescape a string that was escaped using django.utils.html.escape. + text = text.replace(/</g, '<'); + text = text.replace(/>/g, '>'); + text = text.replace(/"/g, '"'); + text = text.replace(/'/g, "'"); + text = text.replace(/&/g, '&'); + return text; +} + +// IE doesn't accept periods or dashes in the window name, but the element IDs +// we use to generate popup window names may contain them, therefore we map them +// to allowed characters in a reversible way so that we can locate the correct +// element when the popup window is dismissed. +function id_to_windowname(text) { + text = text.replace(/\./g, '__dot__'); + text = text.replace(/\-/g, '__dash__'); + return text; +} + +function windowname_to_id(text) { + text = text.replace(/__dot__/g, '.'); + text = text.replace(/__dash__/g, '-'); + return text; +} + +function showRelatedObjectLookupPopup(triggeringLink) { + var name = triggeringLink.id.replace(/^lookup_/, ''); + name = id_to_windowname(name); + var href; + if (triggeringLink.href.search(/\?/) >= 0) { + href = triggeringLink.href + '&_popup=1'; + } else { + href = triggeringLink.href + '?_popup=1'; + } + var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); + win.focus(); + return false; +} + +function dismissRelatedLookupPopup(win, chosenId) { + var name = windowname_to_id(win.name); + var elem = document.getElementById(name); + if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { + elem.value += ',' + chosenId; + } else { + document.getElementById(name).value = chosenId; + } + win.close(); +} + +function showAddAnotherPopup(triggeringLink) { + var name = triggeringLink.id.replace(/^add_/, ''); + name = id_to_windowname(name); + var href = triggeringLink.href; + if (href.indexOf('?') == -1) { + href += '?_popup=1'; + } else { + href += '&_popup=1'; + } + var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); + win.focus(); + return false; +} + +function dismissAddAnotherPopup(win, newId, newRepr) { + // newId and newRepr are expected to have previously been escaped by + // django.utils.html.escape. + newId = html_unescape(newId); + newRepr = html_unescape(newRepr); + var name = windowname_to_id(win.name); + var elem = document.getElementById(name); + var o; + if (elem) { + var elemName = elem.nodeName.toUpperCase(); + if (elemName == 'SELECT') { + o = new Option(newRepr, newId); + elem.options[elem.options.length] = o; + o.selected = true; + } else if (elemName == 'INPUT') { + if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { + elem.value += ',' + newId; + } else { + elem.value = newId; + } + } + } else { + var toId = name + "_to"; + o = new Option(newRepr, newId); + SelectBox.add_to_cache(toId, o); + SelectBox.redisplay(toId); + } + win.close(); +} diff --git a/game_template/server/web/static/admin/js/admin/ordering.js b/game_template/server/web/static/admin/js/admin/ordering.js new file mode 100644 index 000000000..595be4d62 --- /dev/null +++ b/game_template/server/web/static/admin/js/admin/ordering.js @@ -0,0 +1,137 @@ +addEvent(window, 'load', reorder_init); + +var lis; +var top = 0; +var left = 0; +var height = 30; + +function reorder_init() { + lis = document.getElementsBySelector('ul#orderthese li'); + var input = document.getElementsBySelector('input[name=order_]')[0]; + setOrder(input.value.split(',')); + input.disabled = true; + draw(); + // Now initialize the dragging behavior + var limit = (lis.length - 1) * height; + for (var i = 0; i < lis.length; i++) { + var li = lis[i]; + var img = document.getElementById('handle'+li.id); + li.style.zIndex = 1; + Drag.init(img, li, left + 10, left + 10, top + 10, top + 10 + limit); + li.onDragStart = startDrag; + li.onDragEnd = endDrag; + img.style.cursor = 'move'; + } +} + +function submitOrderForm() { + var inputOrder = document.getElementsBySelector('input[name=order_]')[0]; + inputOrder.value = getOrder(); + inputOrder.disabled=false; +} + +function startDrag() { + this.style.zIndex = '10'; + this.className = 'dragging'; +} + +function endDrag(x, y) { + this.style.zIndex = '1'; + this.className = ''; + // Work out how far along it has been dropped, using x co-ordinate + var oldIndex = this.index; + var newIndex = Math.round((y - 10 - top) / height); + // 'Snap' to the correct position + this.style.top = (10 + top + newIndex * height) + 'px'; + this.index = newIndex; + moveItem(oldIndex, newIndex); +} + +function moveItem(oldIndex, newIndex) { + // Swaps two items, adjusts the index and left co-ord for all others + if (oldIndex == newIndex) { + return; // Nothing to swap; + } + var direction, lo, hi; + if (newIndex > oldIndex) { + lo = oldIndex; + hi = newIndex; + direction = -1; + } else { + direction = 1; + hi = oldIndex; + lo = newIndex; + } + var lis2 = new Array(); // We will build the new order in this array + for (var i = 0; i < lis.length; i++) { + if (i < lo || i > hi) { + // Position of items not between the indexes is unaffected + lis2[i] = lis[i]; + continue; + } else if (i == newIndex) { + lis2[i] = lis[oldIndex]; + continue; + } else { + // Item is between the two indexes - move it along 1 + lis2[i] = lis[i - direction]; + } + } + // Re-index everything + reIndex(lis2); + lis = lis2; + draw(); +// document.getElementById('hiddenOrder').value = getOrder(); + document.getElementsBySelector('input[name=order_]')[0].value = getOrder(); +} + +function reIndex(lis) { + for (var i = 0; i < lis.length; i++) { + lis[i].index = i; + } +} + +function draw() { + for (var i = 0; i < lis.length; i++) { + var li = lis[i]; + li.index = i; + li.style.position = 'absolute'; + li.style.left = (10 + left) + 'px'; + li.style.top = (10 + top + (i * height)) + 'px'; + } +} + +function getOrder() { + var order = new Array(lis.length); + for (var i = 0; i < lis.length; i++) { + order[i] = lis[i].id.substring(1, 100); + } + return order.join(','); +} + +function setOrder(id_list) { + /* Set the current order to match the lsit of IDs */ + var temp_lis = new Array(); + for (var i = 0; i < id_list.length; i++) { + var id = 'p' + id_list[i]; + temp_lis[temp_lis.length] = document.getElementById(id); + } + reIndex(temp_lis); + lis = temp_lis; + draw(); +} + +function addEvent(elm, evType, fn, useCapture) +// addEvent and removeEvent +// cross-browser event handling for IE5+, NS6 and Mozilla +// By Scott Andrew +{ + if (elm.addEventListener){ + elm.addEventListener(evType, fn, useCapture); + return true; + } else if (elm.attachEvent){ + var r = elm.attachEvent("on"+evType, fn); + return r; + } else { + elm['on'+evType] = fn; + } +} diff --git a/game_template/server/web/static/admin/js/calendar.js b/game_template/server/web/static/admin/js/calendar.js new file mode 100644 index 000000000..458eece92 --- /dev/null +++ b/game_template/server/web/static/admin/js/calendar.js @@ -0,0 +1,169 @@ +/* +calendar.js - Calendar functions by Adrian Holovaty +depends on core.js for utility functions like removeChildren or quickElement +*/ + +// CalendarNamespace -- Provides a collection of HTML calendar-related helper functions +var CalendarNamespace = { + monthsOfYear: gettext('January February March April May June July August September October November December').split(' '), + daysOfWeek: gettext('S M T W T F S').split(' '), + firstDayOfWeek: parseInt(get_format('FIRST_DAY_OF_WEEK')), + isLeapYear: function(year) { + return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0)); + }, + getDaysInMonth: function(month,year) { + var days; + if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) { + days = 31; + } + else if (month==4 || month==6 || month==9 || month==11) { + days = 30; + } + else if (month==2 && CalendarNamespace.isLeapYear(year)) { + days = 29; + } + else { + days = 28; + } + return days; + }, + draw: function(month, year, div_id, callback, selected) { // month = 1-12, year = 1-9999 + var today = new Date(); + var todayDay = today.getDate(); + var todayMonth = today.getMonth()+1; + var todayYear = today.getFullYear(); + var todayClass = ''; + + // Use UTC functions here because the date field does not contain time + // and using the UTC function variants prevent the local time offset + // from altering the date, specifically the day field. For example: + // + // ``` + // var x = new Date('2013-10-02'); + // var day = x.getDate(); + // ``` + // + // The day variable above will be 1 instead of 2 in, say, US Pacific time + // zone. + var isSelectedMonth = false; + if (typeof selected != 'undefined') { + isSelectedMonth = (selected.getUTCFullYear() == year && (selected.getUTCMonth()+1) == month); + } + + month = parseInt(month); + year = parseInt(year); + var calDiv = document.getElementById(div_id); + removeChildren(calDiv); + var calTable = document.createElement('table'); + quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month-1] + ' ' + year); + var tableBody = quickElement('tbody', calTable); + + // Draw days-of-week header + var tableRow = quickElement('tr', tableBody); + for (var i = 0; i < 7; i++) { + quickElement('th', tableRow, CalendarNamespace.daysOfWeek[(i + CalendarNamespace.firstDayOfWeek) % 7]); + } + + var startingPos = new Date(year, month-1, 1 - CalendarNamespace.firstDayOfWeek).getDay(); + var days = CalendarNamespace.getDaysInMonth(month, year); + + // Draw blanks before first of month + tableRow = quickElement('tr', tableBody); + for (var i = 0; i < startingPos; i++) { + var _cell = quickElement('td', tableRow, ' '); + _cell.className = "nonday"; + } + + // Draw days of month + var currentDay = 1; + for (var i = startingPos; currentDay <= days; i++) { + if (i%7 == 0 && currentDay != 1) { + tableRow = quickElement('tr', tableBody); + } + if ((currentDay==todayDay) && (month==todayMonth) && (year==todayYear)) { + todayClass='today'; + } else { + todayClass=''; + } + + // use UTC function; see above for explanation. + if (isSelectedMonth && currentDay == selected.getUTCDate()) { + if (todayClass != '') todayClass += " "; + todayClass += "selected"; + } + + var cell = quickElement('td', tableRow, '', 'class', todayClass); + + quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));'); + currentDay++; + } + + // Draw blanks after end of month (optional, but makes for valid code) + while (tableRow.childNodes.length < 7) { + var _cell = quickElement('td', tableRow, ' '); + _cell.className = "nonday"; + } + + calDiv.appendChild(calTable); + } +} + +// Calendar -- A calendar instance +function Calendar(div_id, callback, selected) { + // div_id (string) is the ID of the element in which the calendar will + // be displayed + // callback (string) is the name of a JavaScript function that will be + // called with the parameters (year, month, day) when a day in the + // calendar is clicked + this.div_id = div_id; + this.callback = callback; + this.today = new Date(); + this.currentMonth = this.today.getMonth() + 1; + this.currentYear = this.today.getFullYear(); + if (typeof selected != 'undefined') { + this.selected = selected; + } +} +Calendar.prototype = { + drawCurrent: function() { + CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback, this.selected); + }, + drawDate: function(month, year, selected) { + this.currentMonth = month; + this.currentYear = year; + + if(selected) { + this.selected = selected; + } + + this.drawCurrent(); + }, + drawPreviousMonth: function() { + if (this.currentMonth == 1) { + this.currentMonth = 12; + this.currentYear--; + } + else { + this.currentMonth--; + } + this.drawCurrent(); + }, + drawNextMonth: function() { + if (this.currentMonth == 12) { + this.currentMonth = 1; + this.currentYear++; + } + else { + this.currentMonth++; + } + this.drawCurrent(); + }, + drawPreviousYear: function() { + this.currentYear--; + this.drawCurrent(); + }, + drawNextYear: function() { + this.currentYear++; + this.drawCurrent(); + } +} diff --git a/game_template/server/web/static/admin/js/collapse.js b/game_template/server/web/static/admin/js/collapse.js new file mode 100644 index 000000000..3b1f31bd2 --- /dev/null +++ b/game_template/server/web/static/admin/js/collapse.js @@ -0,0 +1,24 @@ +(function($) { + $(document).ready(function() { + // Add anchor tag for Show/Hide link + $("fieldset.collapse").each(function(i, elem) { + // Don't hide if fields in this fieldset have errors + if ($(elem).find("div.errors").length == 0) { + $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + + ')'); + } + }); + // Add toggle to anchor tag + $("fieldset.collapse a.collapse-toggle").click(function(ev) { + if ($(this).closest("fieldset").hasClass("collapsed")) { + // Show + $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); + } else { + // Hide + $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); + } + return false; + }); + }); +})(django.jQuery); diff --git a/game_template/server/web/static/admin/js/collapse.min.js b/game_template/server/web/static/admin/js/collapse.min.js new file mode 100644 index 000000000..0a8c20ea4 --- /dev/null +++ b/game_template/server/web/static/admin/js/collapse.min.js @@ -0,0 +1,2 @@ +(function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){a(b).find("div.errors").length==0&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").click(function(){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", +[a(this).attr("id")]);return false})})})(django.jQuery); diff --git a/game_template/server/web/static/admin/js/core.js b/game_template/server/web/static/admin/js/core.js new file mode 100644 index 000000000..9e735af1e --- /dev/null +++ b/game_template/server/web/static/admin/js/core.js @@ -0,0 +1,222 @@ +// Core javascript helper functions + +// basic browser identification & version +var isOpera = (navigator.userAgent.indexOf("Opera")>=0) && parseFloat(navigator.appVersion); +var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]); + +// Cross-browser event handlers. +function addEvent(obj, evType, fn) { + if (obj.addEventListener) { + obj.addEventListener(evType, fn, false); + return true; + } else if (obj.attachEvent) { + var r = obj.attachEvent("on" + evType, fn); + return r; + } else { + return false; + } +} + +function removeEvent(obj, evType, fn) { + if (obj.removeEventListener) { + obj.removeEventListener(evType, fn, false); + return true; + } else if (obj.detachEvent) { + obj.detachEvent("on" + evType, fn); + return true; + } else { + return false; + } +} + +function cancelEventPropagation(e) { + if (!e) e = window.event; + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); +} + +// quickElement(tagType, parentReference [, textInChildNode, attribute, attributeValue ...]); +function quickElement() { + var obj = document.createElement(arguments[0]); + if (arguments[2]) { + var textNode = document.createTextNode(arguments[2]); + obj.appendChild(textNode); + } + var len = arguments.length; + for (var i = 3; i < len; i += 2) { + obj.setAttribute(arguments[i], arguments[i+1]); + } + arguments[1].appendChild(obj); + return obj; +} + +// "a" is reference to an object +function removeChildren(a) { + while (a.hasChildNodes()) a.removeChild(a.lastChild); +} + +// ---------------------------------------------------------------------------- +// Cross-browser xmlhttp object +// from http://jibbering.com/2002/4/httprequest.html +// ---------------------------------------------------------------------------- +var xmlhttp; +/*@cc_on @*/ +/*@if (@_jscript_version >= 5) + try { + xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) { + try { + xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (E) { + xmlhttp = false; + } + } +@else + xmlhttp = false; +@end @*/ +if (!xmlhttp && typeof XMLHttpRequest != 'undefined') { + xmlhttp = new XMLHttpRequest(); +} + +// ---------------------------------------------------------------------------- +// Find-position functions by PPK +// See http://www.quirksmode.org/js/findpos.html +// ---------------------------------------------------------------------------- +function findPosX(obj) { + var curleft = 0; + if (obj.offsetParent) { + while (obj.offsetParent) { + curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft); + obj = obj.offsetParent; + } + // IE offsetParent does not include the top-level + if (isIE && obj.parentElement){ + curleft += obj.offsetLeft - obj.scrollLeft; + } + } else if (obj.x) { + curleft += obj.x; + } + return curleft; +} + +function findPosY(obj) { + var curtop = 0; + if (obj.offsetParent) { + while (obj.offsetParent) { + curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop); + obj = obj.offsetParent; + } + // IE offsetParent does not include the top-level + if (isIE && obj.parentElement){ + curtop += obj.offsetTop - obj.scrollTop; + } + } else if (obj.y) { + curtop += obj.y; + } + return curtop; +} + +//----------------------------------------------------------------------------- +// Date object extensions +// ---------------------------------------------------------------------------- + +Date.prototype.getTwelveHours = function() { + hours = this.getHours(); + if (hours == 0) { + return 12; + } + else { + return hours <= 12 ? hours : hours-12 + } +} + +Date.prototype.getTwoDigitMonth = function() { + return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1); +} + +Date.prototype.getTwoDigitDate = function() { + return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate(); +} + +Date.prototype.getTwoDigitTwelveHour = function() { + return (this.getTwelveHours() < 10) ? '0' + this.getTwelveHours() : this.getTwelveHours(); +} + +Date.prototype.getTwoDigitHour = function() { + return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours(); +} + +Date.prototype.getTwoDigitMinute = function() { + return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes(); +} + +Date.prototype.getTwoDigitSecond = function() { + return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds(); +} + +Date.prototype.getHourMinute = function() { + return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute(); +} + +Date.prototype.getHourMinuteSecond = function() { + return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond(); +} + +Date.prototype.strftime = function(format) { + var fields = { + c: this.toString(), + d: this.getTwoDigitDate(), + H: this.getTwoDigitHour(), + I: this.getTwoDigitTwelveHour(), + m: this.getTwoDigitMonth(), + M: this.getTwoDigitMinute(), + p: (this.getHours() >= 12) ? 'PM' : 'AM', + S: this.getTwoDigitSecond(), + w: '0' + this.getDay(), + x: this.toLocaleDateString(), + X: this.toLocaleTimeString(), + y: ('' + this.getFullYear()).substr(2, 4), + Y: '' + this.getFullYear(), + '%' : '%' + }; + var result = '', i = 0; + while (i < format.length) { + if (format.charAt(i) === '%') { + result = result + fields[format.charAt(i + 1)]; + ++i; + } + else { + result = result + format.charAt(i); + } + ++i; + } + return result; +} + +// ---------------------------------------------------------------------------- +// String object extensions +// ---------------------------------------------------------------------------- +String.prototype.pad_left = function(pad_length, pad_string) { + var new_string = this; + for (var i = 0; new_string.length < pad_length; i++) { + new_string = pad_string + new_string; + } + return new_string; +} + +// ---------------------------------------------------------------------------- +// Get the computed style for and element +// ---------------------------------------------------------------------------- +function getStyle(oElm, strCssRule){ + var strValue = ""; + if(document.defaultView && document.defaultView.getComputedStyle){ + strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); + } + else if(oElm.currentStyle){ + strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){ + return p1.toUpperCase(); + }); + strValue = oElm.currentStyle[strCssRule]; + } + return strValue; +} diff --git a/game_template/server/web/static/admin/js/getElementsBySelector.js b/game_template/server/web/static/admin/js/getElementsBySelector.js new file mode 100644 index 000000000..15b57a190 --- /dev/null +++ b/game_template/server/web/static/admin/js/getElementsBySelector.js @@ -0,0 +1,167 @@ +/* document.getElementsBySelector(selector) + - returns an array of element objects from the current document + matching the CSS selector. Selectors can contain element names, + class names and ids and can be nested. For example: + + elements = document.getElementsBySelect('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + + New in version 0.4: Support for CSS2 and CSS3 attribute selectors: + See http://www.w3.org/TR/css3-selectors/#attribute-selectors + + Version 0.4 - Simon Willison, March 25th 2003 + -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows + -- Opera 7 fails +*/ + +function getAllChildren(e) { + // Returns all children of element. Workaround required for IE5/Windows. Ugh. + return e.all ? e.all : e.getElementsByTagName('*'); +} + +document.getElementsBySelector = function(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document.getElementsByTagName) { + return new Array(); + } + // Split selector in to tokens + var tokens = selector.split(' '); + var currentContext = new Array(document); + for (var i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');; + if (token.indexOf('#') > -1) { + // Token is an ID selector + var bits = token.split('#'); + var tagName = bits[0]; + var id = bits[1]; + var element = document.getElementById(id); + if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) { + // ID not found or tag with that ID not found, return false. + return new Array(); + } + // Set currentContext to contain just this element + currentContext = new Array(element); + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + var bits = token.split('.'); + var tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + try { + elements = currentContext[h].getElementsByTagName(tagName); + } + catch(e) { + elements = []; + } + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) { + currentContext[currentContextIndex++] = found[k]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { + var tagName = RegExp.$1; + var attrName = RegExp.$2; + var attrOperator = RegExp.$3; + var attrValue = RegExp.$4; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); }; + break; + case '^': // Match starts with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); }; + break; + case '*': // Match ends with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); }; + break; + default : + // Just test for existence of attribute + checkFunction = function(e) { return e.getAttribute(attrName); }; + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (checkFunction(found[k])) { + currentContext[currentContextIndex++] = found[k]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements = currentContext[h].getElementsByTagName(tagName); + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = found; + } + return currentContext; +} + +/* That revolting regular expression explained +/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ + \---/ \---/\-------------/ \-------/ + | | | | + | | | The value + | | ~,|,^,$,* or = + | Attribute + Tag +*/ diff --git a/game_template/server/web/static/admin/js/inlines.js b/game_template/server/web/static/admin/js/inlines.js new file mode 100644 index 000000000..0bfcd3412 --- /dev/null +++ b/game_template/server/web/static/admin/js/inlines.js @@ -0,0 +1,272 @@ +/** + * Django admin inlines + * + * Based on jQuery Formset 1.1 + * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) + * @requires jQuery 1.2.6 or later + * + * Copyright (c) 2009, Stanislaus Madueke + * All rights reserved. + * + * Spiced up with Code from Zain Memon's GSoC project 2009 + * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip. + * + * Licensed under the New BSD License + * See: http://www.opensource.org/licenses/bsd-license.php + */ +(function($) { + $.fn.formset = function(opts) { + var options = $.extend({}, $.fn.formset.defaults, opts); + var $this = $(this); + var $parent = $this.parent(); + var updateElementIndex = function(el, prefix, ndx) { + var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); + var replacement = prefix + "-" + ndx; + if ($(el).prop("for")) { + $(el).prop("for", $(el).prop("for").replace(id_regex, replacement)); + } + if (el.id) { + el.id = el.id.replace(id_regex, replacement); + } + if (el.name) { + el.name = el.name.replace(id_regex, replacement); + } + }; + var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off"); + var nextIndex = parseInt(totalForms.val(), 10); + var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off"); + // only show the add button if we are allowed to add more items, + // note that max_num = None translates to a blank string. + var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; + $this.each(function(i) { + $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); + }); + if ($this.length && showAddButton) { + var addButton; + if ($this.prop("tagName") == "TR") { + // If forms are laid out as table rows, insert the + // "add" button in a new table row: + var numCols = this.eq(-1).children().length; + $parent.append('' + options.addText + ""); + addButton = $parent.find("tr:last a"); + } else { + // Otherwise, insert it immediately after the last form: + $this.filter(":last").after('"); + addButton = $this.filter(":last").next().find("a"); + } + addButton.click(function(e) { + e.preventDefault(); + var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); + var template = $("#" + options.prefix + "-empty"); + var row = template.clone(true); + row.removeClass(options.emptyCssClass) + .addClass(options.formCssClass) + .attr("id", options.prefix + "-" + nextIndex); + if (row.is("tr")) { + // If the forms are laid out in table rows, insert + // the remove button into the last table cell: + row.children(":last").append('"); + } else if (row.is("ul") || row.is("ol")) { + // If they're laid out as an ordered/unordered list, + // insert an
  • after the last list item: + row.append('
  • ' + options.deleteText + "
  • "); + } else { + // Otherwise, just insert the remove button as the + // last child element of the form's container: + row.children(":first").append('' + options.deleteText + ""); + } + row.find("*").each(function() { + updateElementIndex(this, options.prefix, totalForms.val()); + }); + // Insert the new form when it has been fully edited + row.insertBefore($(template)); + // Update number of total forms + $(totalForms).val(parseInt(totalForms.val(), 10) + 1); + nextIndex += 1; + // Hide add button in case we've hit the max, except we want to add infinitely + if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { + addButton.parent().hide(); + } + // The delete button of each row triggers a bunch of other things + row.find("a." + options.deleteCssClass).click(function(e) { + e.preventDefault(); + // Remove the parent form containing this button: + var row = $(this).parents("." + options.formCssClass); + row.remove(); + nextIndex -= 1; + // If a post-delete callback was provided, call it with the deleted form: + if (options.removed) { + options.removed(row); + } + // Update the TOTAL_FORMS form count. + var forms = $("." + options.formCssClass); + $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); + // Show add button again once we drop below max + if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { + addButton.parent().show(); + } + // Also, update names and ids for all remaining form controls + // so they remain in sequence: + for (var i=0, formCount=forms.length; i0;i.each(function(){a(this).not("."+ +b.emptyCssClass).addClass(b.formCssClass)});if(i.length&&l){var f;if(i.prop("tagName")=="TR"){i=this.eq(-1).children().length;g.append(''+b.addText+"");f=g.find("tr:last a")}else{i.filter(":last").after('");f=i.filter(":last").next().find("a")}f.click(function(e){e.preventDefault();var k=a("#id_"+b.prefix+"-TOTAL_FORMS");e=a("#"+ +b.prefix+"-empty");var h=e.clone(true);h.removeClass(b.emptyCssClass).addClass(b.formCssClass).attr("id",b.prefix+"-"+d);if(h.is("tr"))h.children(":last").append('");else h.is("ul")||h.is("ol")?h.append('
  • '+b.deleteText+"
  • "):h.children(":first").append(''+b.deleteText+""); +h.find("*").each(function(){m(this,b.prefix,k.val())});h.insertBefore(a(e));a(k).val(parseInt(k.val(),10)+1);d+=1;c.val()!==""&&c.val()-k.val()<=0&&f.parent().hide();h.find("a."+b.deleteCssClass).click(function(j){j.preventDefault();j=a(this).parents("."+b.formCssClass);j.remove();d-=1;b.removed&&b.removed(j);j=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(j.length);if(c.val()===""||c.val()-j.length>0)f.parent().show();for(var n=0,o=j.length;n type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.9.1", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } + }, + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function() { + + var support, all, a, + input, select, fragment, + opt, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
    a"; + + // Support tests won't run in some limited or non-browser environments + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + if ( !all || !a || !all.length ) { + return {}; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + support = { + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + checkOn: !!input.value, + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Tests for enctype support on a form (#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: document.compatMode === "CSS1Compat", + + // Will be defined later + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "
    t
    "; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "
    "; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +})(); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var i, l, thisCache, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( !name.indexOf( "data-" ) ) { + name = jQuery.camelCase( name.slice(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + // Try to fetch any internally stored data first + return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + } + + this.each(function() { + jQuery.data( this, key, value ); + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val, + self = jQuery(this); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, notxml, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + // In IE9+, Flash objects don't have .getAttribute (#12945) + // Support: IE9+ + if ( typeof elem.getAttribute !== core_strundefined ) { + ret = elem.getAttribute( name ); + } + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( rboolean.test( name ) ) { + // Set corresponding property to false for boolean attributes + // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 + if ( !getSetAttribute && ruseDefault.test( name ) ) { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } else { + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + var + // Use .prop to determine if this attribute is understood as boolean + prop = jQuery.prop( elem, name ), + + // Fetch it accordingly + attr = typeof prop === "boolean" && elem.getAttribute( name ), + detail = typeof prop === "boolean" ? + + getSetInput && getSetAttribute ? + attr != null : + // oldIE fabricates an empty string for missing boolean attributes + // and conflates checked/selected into attroperties + ruseDefault.test( name ) ? + elem[ jQuery.camelCase( "default-" + name ) ] : + !!attr : + + // fetch an attribute node for properties not recognized as boolean + elem.getAttributeNode( name ); + + return detail && detail.value !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; + +// fix oldIE value attroperty +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return jQuery.nodeName( elem, "input" ) ? + + // Ignore the value *property* by using defaultValue + elem.defaultValue : + + ret && ret.specified ? ret.value : undefined; + }, + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret == null ? undefined : ret; + } + }); + }); + + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = true; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur != this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + } + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== document.activeElement && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === document.activeElement && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +(function( window, undefined ) { + +var i, + cachedruns, + Expr, + getText, + isXML, + compile, + hasDuplicate, + outermostContext, + + // Local document vars + setDocument, + document, + docElem, + documentIsXML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + sortOrder, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + support = {}, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Array methods + arr = [], + pop = arr.pop, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rsibling = /[\x20\t\r\n\f]*[+~]/, + + rnative = /^[^{]+\{\s*\[native code/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, + funescape = function( _, escaped ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + return high !== high ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Use a stripped-down slice if we can't use a native one +try { + slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, + results = []; + while ( (elem = this[i++]) ) { + results.push( elem ); + } + return results; + }; +} + +/** + * For feature detection + * @param {Function} fn The function to test for native support + */ +function isNative( fn ) { + return rnative.test( fn + "" ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var cache, + keys = []; + + return (cache = function( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + }); +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return fn( div ); + } catch (e) { + return false; + } finally { + // release memory in IE + div = null; + } +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( !documentIsXML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { + push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + return results; + } + } + + // QSA path + if ( support.qsa && !rbuggyQSA.test(selector) ) { + old = true; + nid = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, slice.call( newContext.querySelectorAll( + newSelector + ), 0 ) ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsXML = isXML( doc ); + + // Check if getElementsByTagName("*") returns only elements + support.tagNameNoComments = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if attributes should be retrieved by attribute nodes + support.attributes = assert(function( div ) { + div.innerHTML = ""; + var type = typeof div.lastChild.getAttribute("multiple"); + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + }); + + // Check if getElementsByClassName can be trusted + support.getByClassName = assert(function( div ) { + // Opera can't find a second classname (in 9.6) + div.innerHTML = ""; + if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = "e"; + return div.getElementsByClassName("e").length === 2; + }); + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + support.getByName = assert(function( div ) { + // Inject content + div.id = expando + 0; + div.innerHTML = "
    "; + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = doc.getElementsByName && + // buggy browsers will return fewer than the correct 2 + doc.getElementsByName( expando ).length === 2 + + // buggy browsers will return more than the correct 0 + doc.getElementsByName( expando + 0 ).length; + support.getIdNotName = !doc.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + }); + + // IE6/7 return modified attributes + Expr.attrHandle = assert(function( div ) { + div.innerHTML = ""; + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute("href") === "#"; + }) ? + {} : + { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute("type"); + } + }; + + // ID find and filter + if ( support.getIdNotName ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? + [m] : + undefined : + []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.tagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Name + Expr.find["NAME"] = support.getByName && function( tag, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }; + + // Class + Expr.find["CLASS"] = support.getByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { + return context.getElementsByClassName( className ); + } + }; + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21), + // no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [ ":focus" ]; + + if ( (support.qsa = isNative(doc.querySelectorAll)) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Opera 10-12/IE8 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = ""; + if ( div.querySelectorAll("[i^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + var compare; + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { + if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { + if ( a === doc || contains( preferredDoc, a ) ) { + return -1; + } + if ( b === doc || contains( preferredDoc, b ) ) { + return 1; + } + return 0; + } + return compare & 4 ? -1 : 1; + } + + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + // Always assume the presence of duplicates if sort doesn't + // pass them to our comparison function (as in Google Chrome). + hasDuplicate = false; + [0, 0].sort( sortOrder ); + support.detectDuplicates = hasDuplicate; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyQSA always contains :focus, so no need for an existence check + if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + var val; + + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( !documentIsXML ) { + name = name.toLowerCase(); + } + if ( (val = Expr.attrHandle[ name ]) ) { + return val( elem ); + } + if ( documentIsXML || support.attributes ) { + return elem.getAttribute( name ); + } + return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? + name : + val && val.specified ? val.value : null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + i = 1, + j = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem === results[ i - 1 ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +// Returns a function to use in pseudos for input types +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +// Returns a function to use in pseudos for buttons +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +// Returns a function to use in pseudos for positionals +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[4] ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + + nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifider + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsXML ? + elem.getAttribute("xml:lang") || elem.getAttribute("lang") : + elem.lang) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push( { + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && !documentIsXML && + Expr.relative[ tokens[1].type ] ) { + + context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, slice.call( seed, 0 ) ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + documentIsXML, + results, + rsibling.test( selector ) + ); + return results; +} + +// Deprecated +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Easy API for creating new setFilters +function setFilters() {} +Expr.filters = setFilters.prototype = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// Initialize with the default document +setDocument(); + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, ret, self, + len = this.length; + + if ( typeof selector !== "string" ) { + self = this; + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + ret = []; + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, this[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true) ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[i]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
    ", "
    " ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
    " ], + tr: [ 2, "", "
    " ], + col: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
    ", "
    " ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + var isFunc = jQuery.isFunction( value ); + + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( !isFunc && typeof value !== "string" ) { + value = jQuery( value ).not( this ).detach(); + } + + return this.domManip( [ value ], true, function( elem ) { + var next = this.nextSibling, + parent = this.parentNode; + + if ( parent ) { + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + }); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, table ? self.html() : undefined ); + } + self.domManip( args, table, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : + this[i], + node, + i + ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery.ajax({ + url: node.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + var attr = elem.getAttributeNode("type"); + elem.type = ( attr && attr.specified ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
    " && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + } +}); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("