mirror of
https://github.com/evennia/evennia.git
synced 2026-04-03 06:27:17 +02:00
Reshuffling the Evennia package into the new template paradigm.
This commit is contained in:
parent
2846e64833
commit
2b3a32e447
371 changed files with 17250 additions and 304 deletions
11
lib/objects/__init__.py
Normal file
11
lib/objects/__init__.py
Normal 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
128
lib/objects/admin.py
Normal 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
416
lib/objects/manager.py
Normal 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
|
||||
36
lib/objects/migrations/0001_initial.py
Normal file
36
lib/objects/migrations/0001_initial.py
Normal 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,),
|
||||
),
|
||||
]
|
||||
30
lib/objects/migrations/0002_auto_20140917_0756.py
Normal file
30
lib/objects/migrations/0002_auto_20140917_0756.py
Normal 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,
|
||||
),
|
||||
]
|
||||
0
lib/objects/migrations/__init__.py
Normal file
0
lib/objects/migrations/__init__.py
Normal file
178
lib/objects/models.py
Normal file
178
lib/objects/models.py
Normal 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
1500
lib/objects/objects.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue