diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index c1a4f47236..e0a9a314d4 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -6,6 +6,7 @@ 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 from src.utils.dbserialize import to_pickle @@ -113,60 +114,61 @@ class TagManager(models.Manager): tags = tags.filter(db_category__iexact=category.lower().strip()) return list(tags) - def get_tag(self, key=None, category=None): + 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(key_cands & cat_cands) + 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) - def get_objs_with_tag(self, key=None, category=None, objclass=None): + 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. """ - global _ObjectDB - if not objclass: - if not _ObjectDB: - from src.objects.models import ObjectDB as _ObjectDB - objclass = _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() - return objclass.objects.filter(key_cands & cat_cands) + return objclass.objects.filter(db_model=model, db_tagtype=tagtype).filter(key_cands & cat_cands) - def create_tag(self, key=None, category=None, data=None): + 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) + 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) + 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_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] diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 4b51b1d704..d77198b3da 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -41,7 +41,7 @@ from django.contrib.contenttypes.models import ContentType from src.utils.idmapper.models import SharedMemoryModel from src.server.caches import get_prop_cache, set_prop_cache -from src.server.caches import get_attr_cache, set_attr_cache +from src.server.caches import set_attr_cache #from src.server.caches import call_ndb_hooks from src.server.models import ServerConfig @@ -462,6 +462,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 + 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() @@ -488,62 +492,64 @@ class TagHandler(object): Generic tag-handler. Accessed via TypedObject.tags. """ _m2m_fieldname = "db_tags" - _base_category = "" + _tagtype = None - def __init__(self, obj, category_prefix=""): + def __init__(self, obj): """ Tags are stored internally in the TypedObject.db_tags m2m field - using the category + with an tag.db_model based on the obj the taghandler is stored on + and with a tagtype given by self.handlertype """ self.obj = obj - self.prefix = "%s%s" % (category_prefix.strip(" _").lower() - if category_prefix else "", self._base_category) + self._model = "%s.%s" % ContentType.objects.get_for_model(obj).natural_key() self._cache = None + def _recache(self): - self._cache = dict((to_str(p.db_key), p) for p in _GA(self.obj, self._m2m_fieldname).filter( - db_category__startswith=self.prefix)) + "Update cache from database field" + self._cache = dict(("%s-%s" % (p.db_key, p.db_category), p) + for p in _GA(self.obj, self._m2m_fieldname).filter( + db_model=self._model, db_tagtype=self._tagtype)) def add(self, tag, category=None, data=None): "Add a new tag to the handler. Tag is a string or a list of strings." for tagstr in make_iter(tag): tagstr = tagstr.strip().lower() if tagstr is not None else None - categ = "%s%s" % (self.prefix, category.strip().lower() if category is not None else "") + category = category().lower() if category is not None else None data = str(data) if data is not None else None # this will only create tag if no matches existed beforehand (it # will overload data on an existing tag since that is not # considered part of making the tag unique) - tagobj = Tag.objects.create_tag(key=tagstr, category=categ, data=data) + tagobj = Tag.objects.create_tag(key=tagstr, category=category, data=data, + model=self._model, tagtype=self._tagtype) _GA(self.obj, self._m2m_fieldname).add(tagobj) if self._cache is None: self._recache() - self._cache[tagstr] = tagobj + cachestring = "%s-%s" % (tagstr, category) + self._cache[cachestring] = tagobj - def get(self, key, category="", return_data=False): + def get(self, key, category="", return_tagobj=False): """ Get the tag for the given key or list of tags. If return_data=True, return the matching Tag objects instead. + Returns a single tag if a unique match, otherwise a list """ if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: self._recache() ret = [] - category = "%s%s" % (self.prefix, category.strip().lower() - if category is not None else "") - ret = [val for val in (self._cache.get(keystr.strip().lower()) - for keystr in make_iter(key)) if val] - ret = [to_str(tag.db_data) for tag in ret] if return_data else ret + category = category.strip().lower() if category is not None else None + searchkey = ["%s-%s" % (key.strip().lower(), category) if key is not None else None for key in make_iter(key)] + ret = [val for val in (self._cache.get(searchkey) for keystr in key) if val] + ret = [to_str(tag.db_data) for tag in ret] if return_tagobj else ret return ret[0] if len(ret) == 1 else ret - def remove(self, tag, category=None): - "Remove a tag from the handler, where tag is the key of the tag to remove" - if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: - self._recache() + def remove(self, key, category=None): + "Remove a tag from the handler based ond key and category." for tag in make_iter(tag): if not (tag or tag.strip()): # we don't allow empty tags continue tagstr = tag.strip().lower() if tag is not None else None - category = "%s%s" % (self.prefix, category.strip().lower() - if category is not None else "") + category = category.strip().lower() if tag is not None else None # This does not delete the tag object itself. Maybe it should do # that when no objects reference the tag anymore (how to check)? @@ -554,7 +560,7 @@ class TagHandler(object): def clear(self): "Remove all tags from the handler" - for tag in _GA(self.obj, self._m2m_fieldname).filter(db_category__startswith=self.prefix): + for tag in _GA(self.obj, self._m2m_fieldname).filter(db_model=self._model, db_tagtype=self._tagtype): _GA(self.obj, self._m2m_fieldname).remove(tag) self._recache() @@ -563,21 +569,22 @@ class TagHandler(object): Get all tags in this handler. If category is given, return only Tags with this category. If return_keys_and_categories is set, return a list of tuples [(key, category), ...] - where the category is stripped of the category prefix (this is ignored - if category keyword is given). """ if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE: self._recache() if category: - category = "%s%s" % (self.prefix, category.strip().lower() - if category is not None else "") - return [to_str(p[0]) for p in _GA(self.obj, self._m2m_fieldname).filter( - db_category=category).values_list("db_key") if p[0]] - elif return_key_and_category: - # return tuple (key, category) - return [(to_str(p.db_key), to_str(p.db_category).lstrip(self.prefix)) for p in self._cache.values()] + category = category.strip().lower() if category is not None else None + matches = _GA(self.obj, self._m2m_fieldname).filter(db_category=category, + db_tagtype=self._tagtype, + db_model=self._model).values_list("db_key") else: - return self._cache.keys() + matches = self._cache.values() + if matches: + if return_key_and_category: + # return tuple (key, category) + return [(to_str(p.db_key), to_str(p.db_category)) for p in matches] + else: + return [to_str(p.db_key) for p in matches] #return [to_str(p[0]) for p in _GA(self.obj, self._m2m_fieldname).filter(db_category__startswith=self.prefix).values_list("db_key") if p[0]] @@ -589,11 +596,11 @@ class TagHandler(object): class AliasHandler(TagHandler): - _base_category = "alias" + _tagtype = "alias" class PermissionHandler(TagHandler): - _base_category = "permission" + _tagtype = "permission" #------------------------------------------------------------