<commit_before>"""
This module defines the database models for all in-game objects, that
is, all objects that has an actual existence in-game.

Each database object is 'decorated' with a 'typeclass', a normal
python class that implements all the various logics needed by the game
in question. Objects created of this class transparently communicate
with its related database object for storing all attributes. The
admin should usually not have to deal directly with this database
object layer.

Attributes are separate objects that store values persistently onto
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 src.utils.idmapper.models import SharedMemoryModel
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
from src.typeclasses.models import _get_cache, _set_cache, _del_cache
from src.typeclasses.typeclass import TypeClass
from src.objects.manager import ObjectManager
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_unicode, variable_from_module, inherits_from

from django.utils.translation import ugettext as _

#__all__ = ("ObjAttribute", "Alias", "ObjectNick", "ObjectDB")


_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))

_GA = object.__getattribute__
_SA = object.__setattr__
_DA = object.__delattr__

_ME = _("me")
_SELF = _("self")
_HERE = _("here")

def clean_content_cache(obj):
    "Clean obj's content cache"
    _SA(obj, "_contents_cache", None)

#------------------------------------------------------------
#
# ObjAttribute
#
#------------------------------------------------------------

class ObjAttribute(Attribute):
    "Attributes for ObjectDB objects."
    db_obj = models.ForeignKey("ObjectDB")

    class Meta:
        "Define Django meta options"
        verbose_name = "Object Attribute"
        verbose_name_plural = "Object Attributes"

#------------------------------------------------------------
#
# Alias
#
#------------------------------------------------------------

class Alias(SharedMemoryModel):
    """
    This model holds a range of alternate names for an object.
    These are intrinsic properties of the object. The split
    is so as to allow for effective global searches also by
    alias.
    """
    db_key = models.CharField('alias', max_length=255, db_index=True)
    db_obj = models.ForeignKey("ObjectDB", verbose_name='object')

    class Meta:
        "Define Django meta options"
        verbose_name = "Object alias"
        verbose_name_plural = "Object aliases"
    def __unicode__(self):
        return u"%s" % self.db_key
    def __str__(self):
        return str(self.db_key)



#------------------------------------------------------------
#
# Object Nicks
#
#------------------------------------------------------------

class ObjectNick(TypeNick):
    """

    The default nick types used by Evennia are:
    inputline (default) - match against all input
    player - match against player searches
    obj - match against object searches
    channel - used to store own names for channels
    """
    db_obj = models.ForeignKey("ObjectDB", verbose_name='object')

    class Meta:
        "Define Django meta options"
        verbose_name = "Nickname for Objects"
        verbose_name_plural = "Nicknames for Objects"
        unique_together = ("db_nick", "db_type", "db_obj")

class ObjectNickHandler(TypeNickHandler):
    """
    Handles nick access and setting. Accessed through ObjectDB.nicks
    """
    NickClass = ObjectNick


#------------------------------------------------------------
#
# ObjectDB
#
#------------------------------------------------------------

class ObjectDB(TypedObject):
    """
    All objects in the game use the ObjectDB model to store
    data in the database. This is handled transparently through
    the typeclass system.

    Note that the base objectdb is very simple, with
    few defined fields. Use attributes to extend your
    type class with new database-stored variables.

    The TypedObject supplies the following (inherited) properties:
      key - main name
      name - alias for key
      typeclass_path - the path to the decorating typeclass
      typeclass - auto-linked typeclass
      date_created - time stamp of object creation
      permissions - perm strings
      locks - lock definitions (handler)
      dbref - #id of object
      db - persistent attribute storage
      ndb - non-persistent attribute storage

    The ObjectDB adds the following properties:
      player - optional connected player
      location - in-game location of object
      home - safety location for object (handler)

      scripts - scripts assigned to object (handler from typeclass)
      cmdset - active cmdset on object (handler from typeclass)
      aliases - aliases for this object (property)
      nicks - nicknames for *other* things in Evennia (handler)
      sessions - sessions connected to this object (see also player)
      has_player - bool if an active player is currently connected
      contents - other objects having this object as location
      exits - exits from this object
    """

    #
    # ObjectDB Database model setup
    #
    #
    # inherited fields (from TypedObject):
    # db_key (also 'name' works), db_typeclass_path, db_date_created,
    # db_permissions
    #
    # These databse fields (including the inherited ones) are all set
    # using their corresponding properties, named same as the field,
    # but withtout the db_* prefix.

    # If this is a character object, the player is connected here.
    db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player',
                                  help_text='a Player connected to this object, if any.')
    # The location in the game world. Since this one is likely
    # to change often, we set this with the 'location' property
    # to transparently handle Typeclassing.
    db_location = models.ForeignKey('self', related_name="locations_set",db_index=True,
                                     blank=True, null=True, verbose_name='game location')
    # a safety location, this usually don't change much.
    db_home = models.ForeignKey('self', related_name="homes_set",
                                 blank=True, null=True, verbose_name='home location')
    # destination of this object - primarily used by exits.
    db_destination = models.ForeignKey('self', related_name="destinations_set", db_index=True,
                                       blank=True, null=True, verbose_name='destination',
                                       help_text='a destination, used only by exit objects.')
    # database storage of persistant cmdsets.
    db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True, blank=True,
                                         help_text="optional python path to a cmdset class.")

    # Database manager
    objects = ObjectManager()

    # Add the object-specific handlers

    def __init__(self, *args, **kwargs):
        "Parent must be initialized first."
        TypedObject.__init__(self, *args, **kwargs)
        # handlers
        _SA(self, "cmdset", CmdSetHandler(self))
        _GA(self, "cmdset").update(init_mode=True)
        _SA(self, "scripts", ScriptHandler(self))
        _SA(self, "nicks", ObjectNickHandler(self))
        # store the attribute class

    # Wrapper properties to easily set database fields. These are
    # @property decorators that allows to access these fields using
    # 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).

    # aliases property (wraps (db_aliases)
    #@property
    def __aliases_get(self):
        "Getter. Allows for value = self.aliases"
        try:
            return _GA(self, "_cached_aliases")
        except AttributeError:
            aliases = list(Alias.objects.filter(db_obj=self).values_list("db_key", flat=True))
            _SA(self, "_cached_aliases", aliases)
            return aliases
    <EMAIL>
    def __aliases_set(self, aliases):
        "Setter. Allows for self.aliases = value"
        for alias in make_iter(aliases):
            new_alias = Alias(db_key=alias, db_obj=self)
            new_alias.save()
        _SA(self, "_cached_aliases", aliases)
    <EMAIL>
    def __aliases_del(self):
        "Deleter. Allows for del self.aliases"
        for alias in Alias.objects.filter(db_obj=self):
            alias.delete()
        _DA(self, "_cached_aliases")
    aliases = property(__aliases_get, __aliases_set, __aliases_del)

    # player property (wraps db_player)
    #@property
    def __player_get(self):
        """
        Getter. Allows for value = self.player.
        We have to be careful here since Player is also
        a TypedObject, so as to not create a loop.
        """
        return _get_cache(self, "player")
    <EMAIL>
    def __player_set(self, player):
        "Setter. Allows for self.player = value"
        if inherits_from(player, TypeClass):
            player = player.dbobj
        _set_cache(self, "player", player)
    <EMAIL>
    def __player_del(self):
        "Deleter. Allows for del self.player"
        _del_cache(self, "player")
    player = property(__player_get, __player_set, __player_del)

    # location property (wraps db_location)
    #@property
    def __location_get(self):
        "Getter. Allows for value = self.location."
        loc = _get_cache(self, "location")
        if loc:
            return _GA(loc, "typeclass")
        return None
    <EMAIL>
    def __location_set(self, location):
        "Setter. Allows for self.location = location"
        try:
            old_loc = _GA(self, "location")

            if ObjectDB.objects.dbref(location):
                # dbref search
                loc = ObjectDB.objects.dbref_search(location)
                loc = loc and _GA(loc, "dbobj")
            elif type(location) != ObjectDB:
                loc = _GA(location, "dbobj")
            else:
                loc = location
            _set_cache(self, "location", loc)
            # update the contents of each location
            if old_loc:
                _GA(_GA(old_loc, "dbobj"), "contents_update")(self, remove=True)
            if loc:
                _GA(loc, "contents_update")(_GA(self, "typeclass"))
        except Exception:
            string = "Cannot set location: "
            string += "%s is not a valid location."
            _GA(self, "msg")(_(string) % location)
            logger.log_trace(string)
            raise
    <EMAIL>
    def __location_del(self):
        "Deleter. Allows for del self.location"
        _GA(self, "location").contents_update(self, remove=True)<commit_msg>Fixed a bug with setting a location to None.<commit_after>"""
This module defines the database models for all in-game objects, that
is, all objects that has an actual existence in-game.

Each database object is 'decorated' with a 'typeclass', a normal
python class that implements all the various logics needed by the game
in question. Objects created of this class transparently communicate
with its related database object for storing all attributes. The
admin should usually not have to deal directly with this database
object layer.

Attributes are separate objects that store values persistently onto
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 src.utils.idmapper.models import SharedMemoryModel
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
from src.typeclasses.models import _get_cache, _set_cache, _del_cache
from src.typeclasses.typeclass import TypeClass
from src.objects.manager import ObjectManager
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_unicode, variable_from_module, inherits_from

from django.utils.translation import ugettext as _

#__all__ = ("ObjAttribute", "Alias", "ObjectNick", "ObjectDB")


_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))

_GA = object.__getattribute__
_SA = object.__setattr__
_DA = object.__delattr__

_ME = _("me")
_SELF = _("self")
_HERE = _("here")

def clean_content_cache(obj):
    "Clean obj's content cache"
    _SA(obj, "_contents_cache", None)

#------------------------------------------------------------
#
# ObjAttribute
#
#------------------------------------------------------------

class ObjAttribute(Attribute):
    "Attributes for ObjectDB objects."
    db_obj = models.ForeignKey("ObjectDB")

    class Meta:
        "Define Django meta options"
        verbose_name = "Object Attribute"
        verbose_name_plural = "Object Attributes"

#------------------------------------------------------------
#
# Alias
#
#------------------------------------------------------------

class Alias(SharedMemoryModel):
    """
    This model holds a range of alternate names for an object.
    These are intrinsic properties of the object. The split
    is so as to allow for effective global searches also by
    alias.
    """
    db_key = models.CharField('alias', max_length=255, db_index=True)
    db_obj = models.ForeignKey("ObjectDB", verbose_name='object')

    class Meta:
        "Define Django meta options"
        verbose_name = "Object alias"
        verbose_name_plural = "Object aliases"
    def __unicode__(self):
        return u"%s" % self.db_key
    def __str__(self):
        return str(self.db_key)



#------------------------------------------------------------
#
# Object Nicks
#
#------------------------------------------------------------

class ObjectNick(TypeNick):
    """

    The default nick types used by Evennia are:
    inputline (default) - match against all input
    player - match against player searches
    obj - match against object searches
    channel - used to store own names for channels
    """
    db_obj = models.ForeignKey("ObjectDB", verbose_name='object')

    class Meta:
        "Define Django meta options"
        verbose_name = "Nickname for Objects"
        verbose_name_plural = "Nicknames for Objects"
        unique_together = ("db_nick", "db_type", "db_obj")

class ObjectNickHandler(TypeNickHandler):
    """
    Handles nick access and setting. Accessed through ObjectDB.nicks
    """
    NickClass = ObjectNick


#------------------------------------------------------------
#
# ObjectDB
#
#------------------------------------------------------------

class ObjectDB(TypedObject):
    """
    All objects in the game use the ObjectDB model to store
    data in the database. This is handled transparently through
    the typeclass system.

    Note that the base objectdb is very simple, with
    few defined fields. Use attributes to extend your
    type class with new database-stored variables.

    The TypedObject supplies the following (inherited) properties:
      key - main name
      name - alias for key
      typeclass_path - the path to the decorating typeclass
      typeclass - auto-linked typeclass
      date_created - time stamp of object creation
      permissions - perm strings
      locks - lock definitions (handler)
      dbref - #id of object
      db - persistent attribute storage
      ndb - non-persistent attribute storage

    The ObjectDB adds the following properties:
      player - optional connected player
      location - in-game location of object
      home - safety location for object (handler)

      scripts - scripts assigned to object (handler from typeclass)
      cmdset - active cmdset on object (handler from typeclass)
      aliases - aliases for this object (property)
      nicks - nicknames for *other* things in Evennia (handler)
      sessions - sessions connected to this object (see also player)
      has_player - bool if an active player is currently connected
      contents - other objects having this object as location
      exits - exits from this object
    """

    #
    # ObjectDB Database model setup
    #
    #
    # inherited fields (from TypedObject):
    # db_key (also 'name' works), db_typeclass_path, db_date_created,
    # db_permissions
    #
    # These databse fields (including the inherited ones) are all set
    # using their corresponding properties, named same as the field,
    # but withtout the db_* prefix.

    # If this is a character object, the player is connected here.
    db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player',
                                  help_text='a Player connected to this object, if any.')
    # The location in the game world. Since this one is likely
    # to change often, we set this with the 'location' property
    # to transparently handle Typeclassing.
    db_location = models.ForeignKey('self', related_name="locations_set",db_index=True,
                                     blank=True, null=True, verbose_name='game location')
    # a safety location, this usually don't change much.
    db_home = models.ForeignKey('self', related_name="homes_set",
                                 blank=True, null=True, verbose_name='home location')
    # destination of this object - primarily used by exits.
    db_destination = models.ForeignKey('self', related_name="destinations_set", db_index=True,
                                       blank=True, null=True, verbose_name='destination',
                                       help_text='a destination, used only by exit objects.')
    # database storage of persistant cmdsets.
    db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True, blank=True,
                                         help_text="optional python path to a cmdset class.")

    # Database manager
    objects = ObjectManager()

    # Add the object-specific handlers

    def __init__(self, *args, **kwargs):
        "Parent must be initialized first."
        TypedObject.__init__(self, *args, **kwargs)
        # handlers
        _SA(self, "cmdset", CmdSetHandler(self))
        _GA(self, "cmdset").update(init_mode=True)
        _SA(self, "scripts", ScriptHandler(self))
        _SA(self, "nicks", ObjectNickHandler(self))
        # store the attribute class

    # Wrapper properties to easily set database fields. These are
    # @property decorators that allows to access these fields using
    # 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).

    # aliases property (wraps (db_aliases)
    #@property
    def __aliases_get(self):
        "Getter. Allows for value = self.aliases"
        try:
            return _GA(self, "_cached_aliases")
        except AttributeError:
            aliases = list(Alias.objects.filter(db_obj=self).values_list("db_key", flat=True))
            _SA(self, "_cached_aliases", aliases)
            return aliases
    #@aliases.setter
    def __aliases_set(self, aliases):
        "Setter. Allows for self.aliases = value"
        for alias in make_iter(aliases):
            new_alias = Alias(db_key=alias, db_obj=self)
            new_alias.save()
        _SA(self, "_cached_aliases", aliases)
    #<EMAIL>
    def __aliases_del(self):
        "Deleter. Allows for del self.aliases"
        for alias in Alias.objects.filter(db_obj=self):
            alias.delete()
        _DA(self, "_cached_aliases")
    aliases = property(__aliases_get, __aliases_set, __aliases_del)

    # player property (wraps db_player)
    #@property
    def __player_get(self):
        """
        Getter. Allows for value = self.player.
        We have to be careful here since Player is also
        a TypedObject, so as to not create a loop.
        """
        return _get_cache(self, "player")
    <EMAIL>
    def __player_set(self, player):
        "Setter. Allows for self.player = value"
        if inherits_from(player, TypeClass):
            player = player.dbobj
        _set_cache(self, "player", player)
    <EMAIL>
    def __player_del(self):
        "Deleter. Allows for del self.player"
        _del_cache(self, "player")
    player = property(__player_get, __player_set, __player_del)

    # location property (wraps db_location)
    #@property
    def __location_get(self):
        "Getter. Allows for value = self.location."
        loc = _get_cache(self, "location")
        if loc:
            return _GA(loc, "typeclass")
        return None
    <EMAIL>
    def __location_set(self, location):
        "Setter. Allows for self.location = location"
        try:
            old_loc = _GA(self, "location")

            if ObjectDB.objects.dbref(location):
                # dbref search
                loc = ObjectDB.objects.dbref_search(location)
                loc = loc and _GA(loc, "dbobj")
            elif location and type(location) != ObjectDB:
                loc = _GA(location, "dbobj")
            else:
                loc = location
            _set_cache(self, "location", loc)
            # update the contents of each location
            if old_loc:
                _GA(_GA(old_loc, "dbobj"), "contents_update")(self, remove=True)
            if loc:
                _GA(loc, "contents_update")(_GA(self, "typeclass"))
        except Exception:
            string = "Cannot set location: "
            string += "%s is not a valid location."
            _GA(self, "msg")(_(string) % location)
            logger.log_trace(string)
            raise
    <EMAIL>
    def __location_del(self):
        "Deleter. Allows for del self.location"
        _GA(self, "location").contents_update(self, remove=True)
{'fraction_non_alphanumeric': 0.12197841405762198, 'fraction_numerical': 0.0006243867630006244, 'mean_word_length': 2.3754327863916904, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1963696', 'n_tokens_mistral': 6045, 'n_tokens_neox': 5895, 'n_words': 2015}