First rework of tag category handling by new fields db_model and db_tagtype.
This commit is contained in:
parent
a9ad82d005
commit
17123bf7ed
2 changed files with 60 additions and 51 deletions
|
|
@ -6,6 +6,7 @@ all Attributes and TypedObjects).
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from src.utils import idmapper
|
from src.utils import idmapper
|
||||||
from src.utils.utils import make_iter
|
from src.utils.utils import make_iter
|
||||||
from src.utils.dbserialize import to_pickle
|
from src.utils.dbserialize import to_pickle
|
||||||
|
|
@ -113,60 +114,61 @@ class TagManager(models.Manager):
|
||||||
tags = tags.filter(db_category__iexact=category.lower().strip())
|
tags = tags.filter(db_category__iexact=category.lower().strip())
|
||||||
return list(tags)
|
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
|
Search and return all tags matching any combination of
|
||||||
the search criteria.
|
the search criteria.
|
||||||
search_key (string) - the tag identifier
|
search_key (string) - the tag identifier
|
||||||
category (string) - the tag category
|
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,
|
Returns a single Tag (or None) if both key and category is given,
|
||||||
otherwise it will return a list.
|
otherwise it will return a list.
|
||||||
"""
|
"""
|
||||||
key_cands = Q(db_key__iexact=key.lower().strip()) if key is not None else Q()
|
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()
|
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:
|
if key and category:
|
||||||
return tags[0] if tags else None
|
return tags[0] if tags else None
|
||||||
else:
|
else:
|
||||||
return list(tags)
|
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
|
Search and return all objects of objclass that has tags matching
|
||||||
the given search criteria.
|
the given search criteria.
|
||||||
key (string) - the tag identifier
|
key (string) - the tag identifier
|
||||||
category (string) - the tag category
|
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 (dbmodel) - the object class to search. If not given, use ObjectDB.
|
||||||
"""
|
"""
|
||||||
global _ObjectDB
|
objclass = ContentType.objects.get_by_natural_key(*model.split(".", 1)).model_class()
|
||||||
if not objclass:
|
|
||||||
if not _ObjectDB:
|
|
||||||
from src.objects.models import ObjectDB as _ObjectDB
|
|
||||||
objclass = _ObjectDB
|
|
||||||
key_cands = Q(db_tags__db_key__iexact=key.lower().strip()) if key is not None else Q()
|
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()
|
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.
|
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
|
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 on a tag (it is not part of what makes the tag unique).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
data = str(data) if data is not None else None
|
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:
|
if tag and data is not None:
|
||||||
tag.db_data = data
|
tag.db_data = data
|
||||||
tag.save()
|
tag.save()
|
||||||
elif not tag:
|
elif not tag:
|
||||||
tag = self.create(db_key=key.lower().strip() if key is not None else None,
|
tag = self.create(db_key=key.lower().strip() if key is not None else None,
|
||||||
db_category=category.lower().strip()
|
db_category=category.lower().strip() if category and key is not None else None,
|
||||||
if category and key is not None else None,
|
db_data=str(data) if data is not None else None,
|
||||||
db_data=str(data) if data is not None else None)
|
db_model=model,
|
||||||
|
db_tagtype=tagtype)
|
||||||
tag.save()
|
tag.save()
|
||||||
return make_iter(tag)[0]
|
return make_iter(tag)[0]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from src.utils.idmapper.models import SharedMemoryModel
|
from src.utils.idmapper.models import SharedMemoryModel
|
||||||
from src.server.caches import get_prop_cache, set_prop_cache
|
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.caches import call_ndb_hooks
|
||||||
from src.server.models import ServerConfig
|
from src.server.models import ServerConfig
|
||||||
|
|
@ -462,6 +462,10 @@ class Tag(models.Model):
|
||||||
help_text="tag category", db_index=True)
|
help_text="tag category", db_index=True)
|
||||||
db_data = models.TextField('data', null=True, blank=True,
|
db_data = models.TextField('data', null=True, blank=True,
|
||||||
help_text="optional data field with extra information. This is not searched for.")
|
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()
|
objects = managers.TagManager()
|
||||||
|
|
||||||
|
|
@ -488,62 +492,64 @@ class TagHandler(object):
|
||||||
Generic tag-handler. Accessed via TypedObject.tags.
|
Generic tag-handler. Accessed via TypedObject.tags.
|
||||||
"""
|
"""
|
||||||
_m2m_fieldname = "db_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
|
Tags are stored internally in the TypedObject.db_tags m2m field
|
||||||
using the category <category_prefix><tag_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.obj = obj
|
||||||
self.prefix = "%s%s" % (category_prefix.strip(" _").lower()
|
self._model = "%s.%s" % ContentType.objects.get_for_model(obj).natural_key()
|
||||||
if category_prefix else "", self._base_category)
|
|
||||||
self._cache = None
|
self._cache = None
|
||||||
|
|
||||||
|
|
||||||
def _recache(self):
|
def _recache(self):
|
||||||
self._cache = dict((to_str(p.db_key), p) for p in _GA(self.obj, self._m2m_fieldname).filter(
|
"Update cache from database field"
|
||||||
db_category__startswith=self.prefix))
|
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):
|
def add(self, tag, category=None, data=None):
|
||||||
"Add a new tag to the handler. Tag is a string or a list of strings."
|
"Add a new tag to the handler. Tag is a string or a list of strings."
|
||||||
for tagstr in make_iter(tag):
|
for tagstr in make_iter(tag):
|
||||||
tagstr = tagstr.strip().lower() if tagstr is not None else None
|
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
|
data = str(data) if data is not None else None
|
||||||
# this will only create tag if no matches existed beforehand (it
|
# this will only create tag if no matches existed beforehand (it
|
||||||
# will overload data on an existing tag since that is not
|
# will overload data on an existing tag since that is not
|
||||||
# considered part of making the tag unique)
|
# 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)
|
_GA(self.obj, self._m2m_fieldname).add(tagobj)
|
||||||
if self._cache is None:
|
if self._cache is None:
|
||||||
self._recache()
|
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
|
Get the tag for the given key or list of tags. If
|
||||||
return_data=True, return the matching Tag objects instead.
|
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:
|
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||||
self._recache()
|
self._recache()
|
||||||
ret = []
|
ret = []
|
||||||
category = "%s%s" % (self.prefix, category.strip().lower()
|
category = category.strip().lower() if category is not None else None
|
||||||
if category is not None else "")
|
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(keystr.strip().lower())
|
ret = [val for val in (self._cache.get(searchkey) for keystr in key) if val]
|
||||||
for keystr in make_iter(key)) if val]
|
ret = [to_str(tag.db_data) for tag in ret] if return_tagobj else ret
|
||||||
ret = [to_str(tag.db_data) for tag in ret] if return_data else ret
|
|
||||||
return ret[0] if len(ret) == 1 else ret
|
return ret[0] if len(ret) == 1 else ret
|
||||||
|
|
||||||
def remove(self, tag, category=None):
|
def remove(self, key, category=None):
|
||||||
"Remove a tag from the handler, where tag is the key of the tag to remove"
|
"Remove a tag from the handler based ond key and category."
|
||||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
|
||||||
self._recache()
|
|
||||||
for tag in make_iter(tag):
|
for tag in make_iter(tag):
|
||||||
if not (tag or tag.strip()): # we don't allow empty tags
|
if not (tag or tag.strip()): # we don't allow empty tags
|
||||||
continue
|
continue
|
||||||
tagstr = tag.strip().lower() if tag is not None else None
|
tagstr = tag.strip().lower() if tag is not None else None
|
||||||
category = "%s%s" % (self.prefix, category.strip().lower()
|
category = category.strip().lower() if tag is not None else None
|
||||||
if category is not None else "")
|
|
||||||
|
|
||||||
# This does not delete the tag object itself. Maybe it should do
|
# This does not delete the tag object itself. Maybe it should do
|
||||||
# that when no objects reference the tag anymore (how to check)?
|
# that when no objects reference the tag anymore (how to check)?
|
||||||
|
|
@ -554,7 +560,7 @@ class TagHandler(object):
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"Remove all tags from the handler"
|
"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)
|
_GA(self.obj, self._m2m_fieldname).remove(tag)
|
||||||
self._recache()
|
self._recache()
|
||||||
|
|
||||||
|
|
@ -563,21 +569,22 @@ class TagHandler(object):
|
||||||
Get all tags in this handler.
|
Get all tags in this handler.
|
||||||
If category is given, return only Tags with this category. If
|
If category is given, return only Tags with this category. If
|
||||||
return_keys_and_categories is set, return a list of tuples [(key, category), ...]
|
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:
|
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||||
self._recache()
|
self._recache()
|
||||||
if category:
|
if category:
|
||||||
category = "%s%s" % (self.prefix, category.strip().lower()
|
category = category.strip().lower() if category is not None else None
|
||||||
if category is not None else "")
|
matches = _GA(self.obj, self._m2m_fieldname).filter(db_category=category,
|
||||||
return [to_str(p[0]) for p in _GA(self.obj, self._m2m_fieldname).filter(
|
db_tagtype=self._tagtype,
|
||||||
db_category=category).values_list("db_key") if p[0]]
|
db_model=self._model).values_list("db_key")
|
||||||
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()]
|
|
||||||
else:
|
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]]
|
#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):
|
class AliasHandler(TagHandler):
|
||||||
_base_category = "alias"
|
_tagtype = "alias"
|
||||||
|
|
||||||
|
|
||||||
class PermissionHandler(TagHandler):
|
class PermissionHandler(TagHandler):
|
||||||
_base_category = "permission"
|
_tagtype = "permission"
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue