From 5b42b31240c40f5097b80ab7c2a57a60a722c3db Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 25 Oct 2014 22:40:38 +0200 Subject: [PATCH] API change. Removed managers for Attributes and Tags - these are instead incoorporated into the TypedObjectManager which all Typeclassed object handlers inherit from. This concludes the refactor started in #529. This means that access to Attributes/Permissions/Aliases and Tags/nicks are done directly through e.g. ObjectDB.objects.get_by_tag() (or ev.managers.objects.get_by_tag) rather than using Tag.get_obj_with_tag and specifying the model type manually. The create_tag() method was also moved into the TypedObjectManager as part of removing the Tagmanager. As part of this change, Tag and Attribute was also removed from the ev interface. --- ev.py | 3 - src/comms/managers.py | 4 +- src/typeclasses/managers.py | 248 ++++++++++++------------------------ src/typeclasses/models.py | 7 +- src/utils/create.py | 13 -- src/utils/search.py | 64 ++++++---- 6 files changed, 123 insertions(+), 216 deletions(-) diff --git a/ev.py b/ev.py index 69f0940c25..af05cb09cc 100644 --- a/ev.py +++ b/ev.py @@ -121,9 +121,6 @@ from src.comms.comms import Channel # objects from src.objects.objects import Object, Character, Room, Exit -# extras -from src.typeclasses.models import Attribute, Tag - # utils from src.utils.search import * diff --git a/src/comms/managers.py b/src/comms/managers.py index 3b57607163..b2cbce4e5d 100644 --- a/src/comms/managers.py +++ b/src/comms/managers.py @@ -4,7 +4,7 @@ These managers handles the from django.db import models from django.db.models import Q -from src.typeclasses.managers import returns_typeclass_list, returns_typeclass +from src.typeclasses.managers import TypedObjectManager, returns_typeclass_list, returns_typeclass _GA = object.__getattribute__ _PlayerDB = None @@ -251,7 +251,7 @@ class MsgManager(models.Manager): # Channel manager # -class ChannelManager(models.Manager): +class ChannelManager(TypedObjectManager): """ This ChannelManager implements methods for searching and manipulating Channels directly from the database. diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index 55429b6be8..c9f62f95bb 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -4,16 +4,13 @@ abstract models in dbobjects.py (and which are thus shared by all Attributes and TypedObjects). """ from functools import update_wrapper -from django.db import models from django.db.models import Q -from django.contrib.contenttypes.models import ContentType from src.utils import idmapper from src.utils.utils import make_iter, variable_from_module -from src.utils.dbserialize import to_pickle __all__ = ("AttributeManager", "TypedObjectManager") _GA = object.__getattribute__ -_ObjectDB = None +_Tag = None # # helper functions for the TypedObjectManager. @@ -50,181 +47,66 @@ def returns_typeclass(method): # Managers -def _attr_pickled(method): - """ - decorator for safely handling attribute searches - - db_value is a pickled field and this is required - in order to be able for pickled django objects directly. - """ - def wrapper(self, *args, **kwargs): - "wrap all queries searching the db_value field in some way" - self.__doc__ = method.__doc__ - for key in (key for key in kwargs if key.startswith('db_value')): - kwargs[key] = to_pickle(kwargs[key]) - return method(self, *args, **kwargs) - return update_wrapper(wrapper, method) - -class AttributeManager(models.Manager): - "Manager for handling Attributes." - @_attr_pickled - def get(self, *args, **kwargs): - return super(AttributeManager, self).get(*args, **kwargs) - - @_attr_pickled - def filter(self,*args, **kwargs): - return super(AttributeManager, self).filter(*args, **kwargs) - - @_attr_pickled - def exclude(self,*args, **kwargs): - return super(AttributeManager, self).exclude(*args, **kwargs) - - @_attr_pickled - def values(self,*args, **kwargs): - return super(AttributeManager, self).values(*args, **kwargs) - - @_attr_pickled - def values_list(self,*args, **kwargs): - return super(AttributeManager, self).values_list(*args, **kwargs) - - @_attr_pickled - def exists(self,*args, **kwargs): - return super(AttributeManager, self).exists(*args, **kwargs) - - def get_attrs_on_obj(self, searchstr, obj, category=None, exact_match=True): - """ - Searches the object's attributes for attribute key matches. - - searchstr: (str) A string to search for. - """ - # Retrieve the list of attributes for this object. - - category_cond = Q(db_category__iexact=category) if category else Q() - if exact_match: - return _GA("obj", "db_attributes").filter(db_key__iexact=searchstr & category_cond) - else: - return _GA("obj", "db_attributes").filter(db_key__icontains=searchstr & category_cond) - - def attr_namesearch(self, *args, **kwargs): - "alias wrapper for backwards compatability" - return self.get_attrs_on_obj(*args, **kwargs) - - def get_attr_by_value(self, searchstr, obj=None): - """ - Searches obj for Attributes with a given value. - searchstr - value to search for. This may be any suitable object. - obj - limit to a given object instance - - If no restraint is given, all Attributes on all types of objects - will be searched. It's highly recommended to at least - supply the objclass argument (DBObject, DBScript or DBPlayer) - to restrict this lookup. - """ - if obj: - return _GA(obj, "db_attributes").filter(db_value=searchstr) - return self.filter(db_value=searchstr) - - def attr_valuesearch(self, *args, **kwargs): - "alias wrapper for backwards compatability" - return self.get_attr_by_value(self, *args, **kwargs) - -# -# TagManager -# - -class TagManager(models.Manager): - """ - Extra manager methods for Tags - """ - def get_tags_on_obj(self, obj, key=None, category=None): - """ - Get all tags on obj, optionally limited by key and/or category - """ - tags = _GA(obj, "db_tags").all() - if key: - tags = tags.filter(db_key__iexact=key.lower().strip()) - if category: - tags = tags.filter(db_category__iexact=category.lower().strip()) - return list(tags) - - def get_tag(self, key=None, category=None, model="objects.objectdb", tagtype=None): - """ - Search and return all tags matching any combination of - the search criteria. - search_key (string) - the tag identifier - category (string) - the tag category - model - the type of object tagged, on naturalkey form, like "objects.objectdb" - tagtype - None, alias or permission - - Returns a single Tag (or None) if both key and category is given, - otherwise it will return a list. - """ - key_cands = Q(db_key__iexact=key.lower().strip()) if key is not None else Q() - cat_cands = Q(db_category__iexact=category.lower().strip()) if category is not None else Q() - tags = self.filter(db_model=model, db_tagtype=tagtype).filter(key_cands & cat_cands) - if key and category: - return tags[0] if tags else None - else: - return list(tags) - - @returns_typeclass_list - def get_objs_with_tag(self, key=None, category=None, model="objects.objectdb", tagtype=None): - """ - Search and return all objects of objclass that has tags matching - the given search criteria. - key (string) - the tag identifier - category (string) - the tag category - model (string) - tag model name. Defaults to "ObjectDB" - tagtype (string) - None, alias or permission - objclass (dbmodel) - the object class to search. If not given, use ObjectDB. - """ - objclass = ContentType.objects.get_by_natural_key(*model.split(".", 1)).model_class() - key_cands = Q(db_tags__db_key__iexact=key.lower().strip()) if key is not None else Q() - cat_cands = Q(db_tags__db_category__iexact=category.lower().strip()) if category is not None else Q() - tag_crit = Q(db_tags__db_model=model, db_tags__db_tagtype=tagtype) - return objclass.objects.filter(tag_crit & key_cands & cat_cands) - - def create_tag(self, key=None, category=None, data=None, model="objects.objectdb", tagtype=None): - """ - Create a tag. This makes sure the create case-insensitive tags. - Note that if the exact same tag configuration (key+category+model+tagtype) - exists, it will be re-used. A data keyword will overwrite existing - data on a tag (it is not part of what makes the tag unique). - - """ - data = str(data) if data is not None else None - - tag = self.get_tag(key=key, category=category, model=model, tagtype=tagtype) - if tag and data is not None: - tag.db_data = data - tag.save() - elif not tag: - tag = self.create(db_key=key.lower().strip() if key is not None else None, - db_category=category.lower().strip() if category and key is not None else None, - db_data=str(data) if data is not None else None, - db_model=model, - db_tagtype=tagtype) - tag.save() - return make_iter(tag)[0] - - - - -#class TypedObjectManager(idmap.CachingManager): -#class TypedObjectManager(models.Manager): class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ Common ObjectManager for all dbobjects. """ # Attribute manager methods + def get_attribute(self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None): + """ + Return Attribute objects by key, by category, by value, by + strvalue, by object (it is stored on) or with a combination of + those criteria. + + attrtype - one of None (normal Attributes) or "nick" + """ + query = [("attribute__db_attrtype", attrtype)] + if obj: + query.append(("%s__id" % self.model.__name__.lower(), obj.id)) + if key: + query.append(("attribute__db_key", key)) + if category: + query.append(("attribute__db_category", category)) + if strvalue: + query.append(("attribute__db_strvalue", value)) + elif value: + # strvalue and value are mutually exclusive + query.append(("attribute__db_value", value)) + return [th.attribute for th in self.model.db_attributes.through.objects.filter(**dict(query))] + + def get_nick(self, key=None, category=None, value=None, strvalue=None, obj=None): + return self.get_attribute(key=key, category=category, value=value, strvalue=strvalue, obj=obj) + + @returns_typeclass_list + def get_by_attribute(self, key=None, category=None, value=None, strvalue=None, attrtype=None): + """ + Return objects having attributes with the given key, category, value, + strvalue or combination of those criteria. + """ + query = [("db_attributes__db_attrtype", attrtype)] + if key: + query.append(("db_attributes__db_key", key)) + if category: + query.append(("db_attributes__db_category", category)) + if strvalue: + query.append(("db_attributes__db_strvalue", value)) + elif value: + # strvalue and value are mutually exclusive + query.append(("db_attributes__db_value", value)) + return self.filter(**dict(query)) + + def get_by_nick(self, key=None, nick=None, category="inputline"): + "Get object based on its key or nick." + return self.get_by_attribute(key=key, category=category, strvalue=nick, attrtype="nick") # Tag manager methods def get_tag(self, key=None, category=None, obj=None, tagtype=None): """ - Return Tag objects by key, by category, by object or - with a combination of those criteria. + Return Tag objects by key, by category, by object (it is + stored on) or with a combination of those criteria. tagtype - one of None (normal tags), "alias" or "permission" """ @@ -235,7 +117,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): query.append(("tag__db_key", key)) if category: query.append(("tag__db_category", category)) - return self.model.db_tags.through.objects.filter(**dict(query)) + return [th.tag for th in self.model.db_tags.through.objects.filter(**dict(query))] def get_permission(self, key=None, category=None, obj=None): return self.get_tag(key=key, category=category, obj=obj, tagtype="permission") @@ -243,7 +125,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): def get_alias(self, key=None, category=None, obj=None): return self.get_tag(key=key, category=category, obj=obj, tagtype="alias") - @returns_typeclass + @returns_typeclass_list def get_by_tag(self, key=None, category=None, tagtype=None): """ Return objects having tags with a given key or category or @@ -264,6 +146,34 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): def get_by_alias(self, key=None, category=None): return self.get_by_tag(key=key, category=category, tagtype="alias") + def create_tag(self, key=None, category=None, data=None, tagtype=None): + """ + Create a new Tag of the base type associated with this typedobject. + This makes sure to create case-insensitive tags. If the exact same + tag configuration (key+category+tagtype) exists on the model, a + new tag will not be created, but an old one returned. A data + keyword is not part of the uniqueness of the tag and setting one + on an existing tag will overwrite the old data field. + """ + data = str(data) if data is not None else None + # try to get old tag + tag = self.get_tag(key=key, category=category, tagtype=tagtype) + if tag and data is not None: + # overload data on tag + tag.db_data = data + tag.save() + elif not tag: + # create a new tag + global _Tag + if not _Tag: + from src.typeclasses.models import Tag as _Tag + tag = _Tag.objects.create( + db_key=key.strip().lower() if key is not None else None, + db_category=category.strip().lower() if category and key is not None else None, + db_data=data, + db_tagtype=tagtype.strip().lower() if tagtype is not None else None) + tag.save() + return make_iter(tag)[0] # object-manager methods diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 03d00db2ca..7757bceda3 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -127,7 +127,7 @@ class Attribute(SharedMemoryModel): 'date_created', editable=False, auto_now_add=True) # Database manager - objects = managers.AttributeManager() + #objects = managers.AttributeManager() @lazy_property def locks(self): @@ -589,11 +589,10 @@ class Tag(models.Model): 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 "objects.objectdb" etc + # 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) - objects = managers.TagManager() class Meta: "Define Django meta options" @@ -652,7 +651,7 @@ class TagHandler(object): # 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 = Tag.objects.create_tag(key=tagstr, category=category, data=data, + 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: diff --git a/src/utils/create.py b/src/utils/create.py index 6a2ac42a88..43e2f04cd4 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -40,7 +40,6 @@ _PlayerDB = None _to_object = None _ChannelDB = None _channelhandler = None -_Tag = None # limit symbol import from API @@ -450,18 +449,6 @@ def create_channel(key, aliases=None, desc=None, channel = create_channel -def create_tag(self, key=None, category=None, data=None): - """ - Create a tag. This makes sure to create case-insensitive tags. - Note that if the exact same tag configuration (key+category) - exists, it will be re-used. A data keyword will overwrite existing - data on a tag (it is not part of what makes the tag unique). - """ - global _Tag - if not _Tag: - from src.typeclasses.models import Tag as _Tag - return _Tag.objects.create_tag(key=key, category=category, data=data) - # # Player creation methods diff --git a/src/utils/search.py b/src/utils/search.py index 51ad3dbb95..71711d44db 100644 --- a/src/utils/search.py +++ b/src/utils/search.py @@ -180,34 +180,48 @@ help_entry_search = search_help_entry help_entries = search_help_entries +# Locate Attributes + +# search_object_attribute(key, category, value, strvalue) (also search_attribute works) +# search_player_attribute(key, category, value, strvalue) (also search_attribute works) +# search_script_attribute(key, category, value, strvalue) (also search_attribute works) +# search_channel_attribute(key, category, value, strvalue) (also search_attribute works) + +# Note that these return the object attached to the Attribute, +# not the attribute object itself (this is usually what you want) + +def search_object_attribute(key=None, category=None, value=None, strvalue=None): + return ObjectDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue) +def search_player_attribute(key=None, category=None, value=None, strvalue=None): + return PlayerDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue) +def search_script_attribute(key=None, category=None, value=None, strvalue=None): + return ScriptDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue) +def search_channel_attribute(key=None, category=None, value=None, strvalue=None): + return Channel.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue) + +# search for attribute objects +search_attribute_object = ObjectDB.objects.get_attribute + # Locate Tags -# search_object_tag(key, category=None) (also search_tag works) -# search_player_tag(key, category=None) -# search_script_tag(key, category=None) -# search_channel_tag(key, category=None) +# search_object_tag(key=None, category=None) (also search_tag works) +# search_player_tag(key=None, category=None) +# search_script_tag(key=None, category=None) +# search_channel_tag(key=None, category=None) -# Note that this returns the object attached to the tag, not the tag itself -# (this is usually what you want) -search_tag = Tag.objects.get_objs_with_tag -def search_object_tag(key, category=None): return Tag.objects.get_objs_with_tag(key, category, model="objects.objectdb") -def search_player_tag(key, category=None): return Tag.objects.get_objs_with_tag(key, category, model="players.playerdb") -def search_script_tag(key, category=None): return Tag.objects.get_objs_with_tag(key, category, model="scripts.scriptdb") -def search_channel_tag(key, category=None): return Tag.objects.get_objs_with_tag(key, category, model="comms.channeldb") +# Note that this returns the object attached to the tag, not the tag +# object itself (this is usually what you want) +def search_object_tag(key=None, category=None): + return ObjectDB.objects.get_by_tag(key=key, category=category) +search_tag = search_object_tag # this is the most common case +def search_player_tag(key=None, category=None): + return PlayerDB.objects.get_by_tag(key=key, category=category) +def search_script_tag(key=None, category=None): + return ScriptDB.objects.get_by_tag(key=key, category=category) +def search_channel_tag(key=None, category=None): + return Channel.objects.get_by_tag(key=key, category=category) -# """ -# Search and return all tags matching any combination of -# the search criteria. -# search_key (string) - the tag identifier -# category (string) - the tag category -# model - one of -# "objects.objectdb" (default), "players.playerdb", -# "scripts.scriptdb" or "comms.channeldb" -# -# Returns a single Tag (or None) if both key and category is given, -# otherwise it will return a list. -# """ -# This returns the tag object itself. -search_tag_object = Tag.objects.get_tag +# search for tag objects +search_tag_object = ObjectDB.objects.get_tag