Reshuffling the Evennia package into the new template paradigm.

This commit is contained in:
Griatch 2015-01-06 14:53:45 +01:00
parent 2846e64833
commit 2b3a32e447
371 changed files with 17250 additions and 304 deletions

11
lib/objects/__init__.py Normal file
View file

@ -0,0 +1,11 @@
"""
Makes it easier to import by grouping all relevant things already at this level.
You can henceforth import most things directly from src.objects
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

128
lib/objects/admin.py Normal file
View file

@ -0,0 +1,128 @@
#
# This sets up how models are displayed
# in the web admin interface.
#
from django import forms
from django.conf import settings
from django.contrib import admin
from src.typeclasses.admin import AttributeInline, TagInline
from src.objects.models import ObjectDB
class ObjectAttributeInline(AttributeInline):
model = ObjectDB.db_attributes.through
class ObjectTagInline(TagInline):
model = ObjectDB.db_tags.through
class ObjectCreateForm(forms.ModelForm):
"This form details the look of the fields"
class Meta:
model = ObjectDB
fields = '__all__'
db_key = forms.CharField(label="Name/Key",
widget=forms.TextInput(attrs={'size': '78'}),
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",)
db_typeclass_path = forms.CharField(label="Typeclass",
initial=settings.BASE_OBJECT_TYPECLASS,
widget=forms.TextInput(attrs={'size': '78'}),
help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
db_cmdset_storage = forms.CharField(label="CmdSet",
initial="",
required=False,
widget=forms.TextInput(attrs={'size': '78'}),
help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
raw_id_fields = ('db_destination', 'db_location', 'db_home')
class ObjectEditForm(ObjectCreateForm):
"Form used for editing. Extends the create one with more fields"
class Meta:
fields = '__all__'
db_lock_storage = forms.CharField(label="Locks",
required=False,
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
class ObjectDBAdmin(admin.ModelAdmin):
inlines = [ObjectTagInline, ObjectAttributeInline]
list_display = ('id', 'db_key', 'db_player', 'db_typeclass_path')
list_display_links = ('id', 'db_key')
ordering = ['db_player', 'db_typeclass_path', 'id']
search_fields = ['^db_key', 'db_typeclass_path']
raw_id_fields = ('db_destination', 'db_location', 'db_home')
save_as = True
save_on_top = True
list_select_related = True
list_filter = ('db_typeclass_path',)
#list_filter = ('db_permissions', 'db_typeclass_path')
# editing fields setup
form = ObjectEditForm
fieldsets = (
(None, {
'fields': (('db_key','db_typeclass_path'), ('db_lock_storage', ),
('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
)}),
)
#fieldsets = (
# (None, {
# 'fields': (('db_key','db_typeclass_path'), ('db_permissions', 'db_lock_storage'),
# ('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
# )}),
# )
#deactivated temporarily, they cause empty objects to be created in admin
# Custom modification to give two different forms wether adding or not.
add_form = ObjectCreateForm
add_fieldsets = (
(None, {
'fields': (('db_key','db_typeclass_path'),
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
)}),
)
#add_fieldsets = (
# (None, {
# 'fields': (('db_key','db_typeclass_path'), 'db_permissions',
# ('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
# )}),
# )
def get_fieldsets(self, request, obj=None):
if not obj:
return self.add_fieldsets
return super(ObjectDBAdmin, self).get_fieldsets(request, obj)
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during creation
"""
defaults = {}
if obj is None:
defaults.update({
'form': self.add_form,
'fields': admin.util.flatten_fieldsets(self.add_fieldsets),
})
defaults.update(kwargs)
return super(ObjectDBAdmin, self).get_form(request, obj, **defaults)
def save_model(self, request, obj, form, change):
obj.save()
if not change:
# adding a new object
obj.basetype_setup()
obj.basetype_posthook_setup()
obj.at_object_creation()
obj.at_init()
admin.site.register(ObjectDB, ObjectDBAdmin)

416
lib/objects/manager.py Normal file
View file

@ -0,0 +1,416 @@
"""
Custom manager for Objects.
"""
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, 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
__all__ = ("ObjectManager",)
_GA = object.__getattribute__
# delayed import
_ATTR = None
# Try to use a custom way to parse id-tagged multimatches.
_AT_MULTIMATCH_INPUT = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1))
class ObjectDBManager(TypedObjectManager):
"""
This ObjectManager implementes methods for searching
and manipulating Objects directly from the database.
Evennia-specific search methods (will return Typeclasses or
lists of Typeclasses, whereas Django-general methods will return
Querysets or database objects).
dbref (converter)
get_id (alias: dbref_search)
get_dbref_range
object_totals
typeclass_search
get_object_with_player
get_objs_with_key_and_typeclass
get_objs_with_attr
get_objs_with_attr_match
get_objs_with_db_property
get_objs_with_db_property_match
get_objs_with_key_or_alias
get_contents
object_search (interface to many of the above methods,
equivalent to ev.search_object)
copy_object
"""
#
# ObjectManager Get methods
#
# player related
@returns_typeclass
def get_object_with_player(self, ostring, exact=True, candidates=None):
"""
Search for an object based on its player's name or dbref.
This search
is sometimes initiated by appending a * to the beginning of
the search criterion (e.g. in local_and_global_search).
search_string: (string) The name or dbref to search for.
"""
ostring = to_unicode(ostring).lstrip('*')
# simplest case - search by dbref
dbref = self.dbref(ostring)
if dbref:
return dbref
# not a dbref. Search by name.
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
if exact:
return self.filter(cand_restriction & Q(db_player__username__iexact=ostring))
else: # fuzzy matching
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)).values_list("db_key", flat=True)
if candidates:
index_matches = string_partial_matching(ply_cands, ostring, ret_index=True)
return [obj for ind, obj in enumerate(make_iter(candidates)) if ind in index_matches]
else:
return string_partial_matching(ply_cands, ostring, ret_index=False)
@returns_typeclass_list
def get_objs_with_key_and_typeclass(self, oname, otypeclass_path, candidates=None):
"""
Returns objects based on simultaneous key and typeclass match.
"""
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
return self.filter(cand_restriction & Q(db_key__iexact=oname, db_typeclass_path__exact=otypeclass_path))
# attr/property related
@returns_typeclass_list
def get_objs_with_attr(self, attribute_name, candidates=None):
"""
Returns all objects having the given attribute_name defined at all.
Location should be a valid location object.
"""
cand_restriction = candidates != None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)))
@returns_typeclass_list
def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None):
"""
Returns all objects having the valid attrname set to the given value.
candidates - list of candidate objects to search
typeclasses - list of typeclass-path strings to restrict matches with
This uses the Attribute's PickledField to transparently search the database by matching
the internal representation. This is reasonably effective but since Attribute values
cannot be indexed, searching by Attribute key is to be preferred whenever possible.
"""
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()
## This doesn't work if attribute_value is an object. Workaround below
if isinstance(attribute_value, (basestring, int, float, bool, long)):
return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name, db_attributes__db_value=attribute_value))
else:
# We have to loop for safety since the referenced lookup gives deepcopy error if attribute value is an object.
global _ATTR
if not _ATTR:
from src.typeclasses.models import Attribute as _ATTR
cands = list(self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name)))
results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands, db_value=attribute_value)]
return chain(*results)
@returns_typeclass_list
def get_objs_with_db_property(self, property_name, candidates=None):
"""
Returns all objects having a given db field property.
property_name = search string
candidates - list of candidate objects to search
"""
property_name = "db_%s" % property_name.lstrip('db_')
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
querykwargs = {property_name:None}
try:
return list(self.filter(cand_restriction).exclude(Q(**querykwargs)))
except exceptions.FieldError:
return []
@returns_typeclass_list
def get_objs_with_db_property_value(self, property_name, property_value, candidates=None, typeclasses=None):
"""
Returns all objects having a given db field property.
candidates - list of objects to search
typeclasses - list of typeclass-path strings to restrict matches with
"""
if isinstance(property_value, basestring):
property_value = to_unicode(property_value)
if isinstance(property_name, basestring):
if not property_name.startswith('db_'):
property_name = "db_%s" % property_name
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()
try:
return list(self.filter(cand_restriction & type_restriction & Q(**querykwargs)))
except exceptions.FieldError:
return []
except ValueError:
from src.utils import logger
logger.log_errmsg("The property '%s' does not support search criteria of the type %s." % (property_name, type(property_value)))
return []
@returns_typeclass_list
def get_contents(self, location, excludeobj=None):
"""
Get all objects that has a location
set to this one.
excludeobj - one or more object keys to exclude from the match
"""
exclude_restriction = Q(pk__in=[_GA(obj, "id") for obj in make_iter(excludeobj)]) if excludeobj else Q()
return self.filter(db_location=location).exclude(exclude_restriction)
@returns_typeclass_list
def get_objs_with_key_or_alias(self, ostring, exact=True,
candidates=None, typeclasses=None):
"""
Returns objects based on key or alias match. Will also do fuzzy
matching based on the utils.string_partial_matching function.
candidates - list of candidate objects to restrict on
typeclasses - list of typeclass path strings to restrict on
"""
if not isinstance(ostring, basestring):
if hasattr(ostring, "key"):
ostring = ostring.key
else:
return []
if is_iter(candidates) and not len(candidates):
# if candidates is an empty iterable there can be no matches
# Exit early.
return []
# build query objects
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj]
cand_restriction = candidates != None and Q(pk__in=make_iter(candidates_id)) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
if exact:
# exact match - do direct search
return self.filter(cand_restriction & type_restriction & (Q(db_key__iexact=ostring) |
Q(db_tags__db_key__iexact=ostring) & Q(db_tags__db_tagtype__iexact="alias"))).distinct()
elif candidates:
# fuzzy with candidates
key_candidates = self.filter(cand_restriction & type_restriction)
else:
# fuzzy without supplied candidates - we select our own candidates
key_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct()
candidates_id = [_GA(obj, "id") for obj in key_candidates]
# fuzzy matching
key_strings = key_candidates.values_list("db_key", flat=True).order_by("id")
index_matches = string_partial_matching(key_strings, ostring, ret_index=True)
if index_matches:
return [obj for ind, obj in enumerate(key_candidates) if ind in index_matches]
else:
alias_candidates = self.filter(id__in=candidates_id, db_tags__db_tagtype__iexact="alias")
alias_strings = alias_candidates.values_list("db_key", flat=True)
index_matches = string_partial_matching(alias_strings, ostring, ret_index=True)
if index_matches:
return [alias.db_obj for ind, alias in enumerate(alias_candidates) if ind in index_matches]
return []
# main search methods and helper functions
@returns_typeclass_list
def object_search(self, searchdata,
attribute_name=None,
typeclass=None,
candidates=None,
exact=True):
"""
Search as an object globally or in a list of candidates and return
results. The result is always an Object. Always returns a list.
Arguments:
searchdata: (str or obj) The entity to match for. This is usually a
key string but may also be an object itself. By default (if
not attribute_name is set), this will search object.key and
object.aliases in order. Can also be on the form #dbref,
which will, if exact=True be matched against primary key.
attribute_name: (str): Use this named ObjectAttribute to match
searchdata against, instead of the defaults. If this is
the name of a database field (with or without the db_ prefix),
that will be matched too.
typeclass (str or TypeClass): restrict matches to objects having this
typeclass. This will help speed up global searches.
candidates (list obj ObjectDBs): If supplied, search will only be
performed among the candidates in this list. A common list
of candidates is the contents of the current location
searched.
exact (bool): Match names/aliases exactly or partially. Partial
matching matches the beginning of words in the names/aliases,
using a matching routine to separate multiple matches in
names with multiple components (so "bi sw" will match
"Big sword"). Since this is more expensive than exact
matching, it is recommended to be used together with the
objlist keyword to limit the number of possibilities. This
value has no meaning if searching for attributes/properties.
Returns:
A list of matching objects (or a list with one unique match)
"""
def _searcher(searchdata, candidates, typeclass, exact=False):
"""
Helper method for searching objects. typeclass is only used
for global searching (no candidates)
"""
if attribute_name:
# attribute/property search (always exact).
matches = self.get_objs_with_db_property_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
if matches:
return matches
return self.get_objs_with_attr_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
else:
# normal key/alias search
return self.get_objs_with_key_or_alias(searchdata, exact=exact, candidates=candidates, typeclasses=typeclass)
if not searchdata and searchdata != 0:
return []
if typeclass:
# typeclass may also be a list
typeclasses = make_iter(typeclass)
for i, typeclass in enumerate(make_iter(typeclasses)):
if callable(typeclass):
typeclasses[i] = u"%s.%s" % (typeclass.__module__, typeclass.__name__)
else:
typeclasses[i] = u"%s" % typeclass
typeclass = typeclasses
if candidates:
# Convenience check to make sure candidates are really dbobjs
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]
dbref = not attribute_name and exact and self.dbref(searchdata)
if dbref is not None:
# Easiest case - dbref matching (always exact)
dbref_match = self.dbref_search(dbref)
if dbref_match:
if not candidates or dbref_match in candidates:
return [dbref_match]
else:
return []
# Search through all possibilities.
match_number = None
# always run first check exact - we don't want partial matches
# if on the form of 1-keyword etc.
matches = _searcher(searchdata, candidates, typeclass, exact=True)
if not matches:
# no matches found - check if we are dealing with N-keyword
# query - if so, strip it.
match_number, searchdata = _AT_MULTIMATCH_INPUT(searchdata)
# run search again, with the exactness set by call
if match_number is not None or not exact:
matches = _searcher(searchdata, candidates, typeclass, exact=exact)
# deal with result
if len(matches) > 1 and match_number is not None:
# multiple matches, but a number was given to separate them
try:
matches = [matches[match_number]]
except IndexError:
pass
# return a list (possibly empty)
return matches
#
# ObjectManager Copy method
#
def copy_object(self, original_object, new_key=None,
new_location=None, new_home=None,
new_permissions=None, new_locks=None,
new_aliases=None, new_destination=None):
"""
Create and return a new object as a copy of the original object. All
will be identical to the original except for the arguments given
specifically to this method.
original_object (obj) - the object to make a copy from
new_key (str) - name the copy differently from the original.
new_location (obj) - if not None, change the location
new_home (obj) - if not None, change the Home
new_aliases (list of strings) - if not None, change object aliases.
new_destination (obj) - if not None, change destination
"""
# get all the object's stats
typeclass_path = original_object.typeclass_path
if not new_key:
new_key = original_object.key
if not new_location:
new_location = original_object.location
if not new_home:
new_home = original_object.home
if not new_aliases:
new_aliases = original_object.aliases.all()
if not new_locks:
new_locks = original_object.db_lock_storage
if not new_permissions:
new_permissions = original_object.permissions.all()
if not new_destination:
new_destination = original_object.destination
# create new object
from src.utils import create
from src.scripts.models import ScriptDB
new_object = create.create_object(typeclass_path,
key=new_key,
location=new_location,
home=new_home,
permissions=new_permissions,
locks=new_locks,
aliases=new_aliases,
destination=new_destination)
if not new_object:
return None
# copy over all attributes from old to new.
for attr in original_object.attributes.all():
new_object.attributes.add(attr.key, attr.value)
# copy over all cmdsets, if any
for icmdset, cmdset in enumerate(original_object.cmdset.all()):
if icmdset == 0:
new_object.cmdset.add_default(cmdset)
else:
new_object.cmdset.add(cmdset)
# copy over all scripts, if any
for script in original_object.scripts.all():
ScriptDB.objects.copy_script(script, new_obj=new_object)
return new_object
def clear_all_sessids(self):
"""
Clear the db_sessid field of all objects having also the db_player field
set.
"""
self.filter(db_sessid__isnull=False).update(db_sessid=None)
class ObjectManager(ObjectDBManager, TypeclassManager):
pass

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('typeclasses', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ObjectDB',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_key', models.CharField(max_length=255, verbose_name=b'key', db_index=True)),
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
('db_lock_storage', models.TextField(help_text=b"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.", verbose_name=b'locks', blank=True)),
('db_sessid', models.CommaSeparatedIntegerField(help_text=b'csv list of session ids of connected Player, if any.', max_length=32, null=True, verbose_name=b'session id')),
('db_cmdset_storage', models.CharField(help_text=b'optional python path to a cmdset class.', max_length=255, null=True, verbose_name=b'cmdset', blank=True)),
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
('db_destination', models.ForeignKey(related_name=b'destinations_set', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='objects.ObjectDB', help_text=b'a destination, used only by exit objects.', null=True, verbose_name=b'destination')),
('db_home', models.ForeignKey(related_name=b'homes_set', on_delete=django.db.models.deletion.SET_NULL, verbose_name=b'home location', blank=True, to='objects.ObjectDB', null=True)),
('db_location', models.ForeignKey(related_name=b'locations_set', on_delete=django.db.models.deletion.SET_NULL, verbose_name=b'game location', blank=True, to='objects.ObjectDB', null=True)),
],
options={
'verbose_name': 'Object',
'verbose_name_plural': 'Objects',
},
bases=(models.Model,),
),
]

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('objects', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('typeclasses', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='objectdb',
name='db_player',
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, verbose_name=b'player', to=settings.AUTH_USER_MODEL, help_text=b'a Player connected to this object, if any.', null=True),
preserve_default=True,
),
migrations.AddField(
model_name='objectdb',
name='db_tags',
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True),
preserve_default=True,
),
]

View file

178
lib/objects/models.py Normal file
View file

@ -0,0 +1,178 @@
"""
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.
"""
from django.db import models
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from src.typeclasses.models import TypedObject
from src.objects.manager import ObjectDBManager
from src.utils import logger
from src.utils.utils import (make_iter, dbref)
#------------------------------------------------------------
#
# 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 (always together with sessid)
sessid - optional connection session id (always together with 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) should normally be
# managed by their corresponding wrapper properties, named same as the
# field, but without the db_* prefix (e.g. the db_key field is set with
# self.key instead). The wrappers are created at the metaclass level and
# will automatically save and cache the data more efficiently.
# If this is a character object, the player is connected here.
db_player = models.ForeignKey("players.PlayerDB", null=True, verbose_name='player', on_delete=models.SET_NULL,
help_text='a Player connected to this object, if any.')
# the session id associated with this player, if any
db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id",
help_text="csv list of session ids of connected Player, 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, on_delete=models.SET_NULL,
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", on_delete=models.SET_NULL,
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, on_delete=models.SET_NULL,
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 = ObjectDBManager()
# cmdset_storage property handling
def __cmdset_storage_get(self):
"getter"
storage = self.db_cmdset_storage
return [path.strip() for path in storage.split(',')] if storage else []
def __cmdset_storage_set(self, value):
"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"
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
def __location_get(self):
"Get location"
return self.db_location
def __location_set(self, location):
"Set location, checking for loops and allowing dbref"
if isinstance(location, (basestring, int)):
# allow setting of #dbref
dbid = dbref(location, reqhash=False)
if dbid:
try:
location = ObjectDB.objects.get(id=dbid)
except ObjectDoesNotExist:
# maybe it is just a name that happens to look like a dbid
pass
try:
def is_loc_loop(loc, depth=0):
"Recursively traverse target location, trying to catch a loop."
if depth > 10:
return
elif loc == self:
raise RuntimeError
elif loc == None:
raise RuntimeWarning
return is_loc_loop(loc.db_location, depth + 1)
try:
is_loc_loop(location)
except RuntimeWarning:
pass
# actually set the field
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)
raise RuntimeError(errmsg)
except Exception, e:
errmsg = "Error (%s): %s is not a valid location." % (str(e), location)
logger.log_errmsg(errmsg)
raise Exception(errmsg)
def __location_del(self):
"Cleanly delete the location reference"
self.db_location = None
self.save(update_fields=["db_location"])
location = property(__location_get, __location_set, __location_del)
class Meta:
"Define Django meta options"
verbose_name = "Object"
verbose_name_plural = "Objects"

1500
lib/objects/objects.py Normal file

File diff suppressed because it is too large Load diff