--- a/app/taggable/taggable.py Tue Nov 10 14:00:15 2009 +0100
+++ b/app/taggable/taggable.py Tue Nov 10 18:18:06 2009 +0100
@@ -2,9 +2,10 @@
import string
import soc.models.linkable
-
+
class Tag(db.Model):
- "Google AppEngine model for store of tags."
+ """Google AppEngine model for store of tags.
+ """
tag = db.StringProperty(required=True)
"The actual string value of the tag."
@@ -27,9 +28,12 @@
"Each tag is scoped under some linkable model."
@classmethod
- def __key_name(cls, tag_name):
- return cls.__name__ + '_' + tag_name
-
+ def __key_name(cls, scope_path, tag_name):
+ """Create the key_name from program key_name as scope_path and tag_name.
+ """
+
+ return scope_path + '/' + tag_name
+
def remove_tagged(self, key):
def remove_tagged_txn():
if key in self.tagged:
@@ -50,7 +54,7 @@
self.put()
db.run_in_transaction(add_tagged_txn)
self.__class__.expire_cached_tags()
-
+
def clear_tagged(self):
def clear_tagged_txn():
if self.auto_delete:
@@ -61,47 +65,70 @@
self.put()
db.run_in_transaction(clear_tagged_txn)
self.__class__.expire_cached_tags()
-
+
@classmethod
def get_by_name(cls, tag_name):
- return cls.get_by_key_name(cls.__key_name(tag_name))
-
+ """Get the list of tag objects that has the given tag_name.
+ """
+
+ tags = db.Query(cls).filter('tag =', tag_name).fetch(1000)
+ return tags
+
@classmethod
- def get_tags_for_key(cls, key):
- "Set the tags for the datastore object represented by key."
- tags = db.Query(cls).filter('tagged =', key).fetch(1000)
+ def get_by_scope_and_name(cls, scope, tag_name):
+ """Get a tag by scope and name.
+
+ There may be only one such tag.
+ """
+
+ return db.Query(cls).filter(
+ 'scope =', scope).filter('tag =', tag_name).get()
+
+ @classmethod
+ def get_tags_for_key(cls, key, limit=1000):
+ """Get the tags for the datastore object represented by key.
+ """
+
+ tags = db.Query(cls).filter('tagged =', key).fetch(limit)
return tags
-
+
@classmethod
- def get_or_create(cls, tag_name):
- "Get the Tag object that has the tag value given by tag_value."
- tag_key_name = cls.__key_name(tag_name)
+ def get_or_create(cls, scope, tag_name, order=0):
+ """Get the Tag object that has the tag value given by tag_value.
+ """
+
+ tag_key_name = cls.__key_name(scope.key().name(), tag_name)
existing_tag = cls.get_by_key_name(tag_key_name)
if existing_tag is None:
- # The tag does not yet exist, so create it.
+ # the tag does not yet exist, so create it.
+ if not order:
+ order = cls.get_highest_order(scope=scope) + 1
def create_tag_txn():
- new_tag = cls(key_name=tag_key_name, tag=tag_name)
+ new_tag = cls(key_name=tag_key_name, tag=tag_name,
+ scope=scope, order=order)
new_tag.put()
return new_tag
existing_tag = db.run_in_transaction(create_tag_txn)
return existing_tag
-
+
@classmethod
def get_tags_by_frequency(cls, limit=1000):
- """Return a list of Tags sorted by the number of objects to
- which they have been applied, most frequently-used first.
- If limit is given, return only that many tags; otherwise,
- return all."""
+ """Return a list of Tags sorted by the number of objects to which they
+ have been applied, most frequently-used first. If limit is given, return
+ only return only that many tags; otherwise, return all.
+ """
+
tag_list = db.Query(cls).filter('tagged_count >', 0).order(
"-tagged_count").fetch(limit)
-
+
return tag_list
@classmethod
def get_tags_by_name(cls, limit=1000, ascending=True):
"""Return a list of Tags sorted alphabetically by the name of the tag.
If a limit is given, return only that many tags; otherwise, return all.
- If ascending is True, sort from a-z; otherwise, sort from z-a."""
+ If ascending is True, sort from a-z; otherwise, sort from z-a.
+ """
from google.appengine.api import memcache
@@ -127,7 +154,48 @@
return tags
@classmethod
+ def copy_tag(cls, scope, tag_name, new_tag_name):
+ """Copy a tag with a given scope and tag_name to another tag with
+ new tag_name.
+ """
+ tag = cls.get_by_scope_and_name(scope, tag_name)
+
+ if tag:
+ tag_key_name = cls.__key_name(scope.key().name(), new_tag_name)
+ existing_tag = cls.get_by_key_name(tag_key_name)
+
+ if existing_tag is None:
+ new_tag = cls(key_name=tag_key_name, tag=new_tag_name, scope=scope,
+ added=tag.added, tagged=tag.tagged,
+ tagged_count=tag.tagged_count)
+ new_tag.put()
+ tag.delete()
+
+ return new_tag
+
+ return existing_tag
+
+ return None
+
+ @classmethod
+ def delete_tag(cls, scope, tag_name):
+ """Delete a tag with a given scope and tag_name.
+ """
+
+ tag = cls.get_by_scope_and_name(scope, tag_name)
+
+ if tag:
+ tag.delete()
+ return True
+
+ return False
+
+ @classmethod
def popular_tags(cls, limit=5):
+ """Get the most popular tags from memcache, or if they are not defined
+ there, it retrieves them from datastore and sets in memcache.
+ """
+
from google.appengine.api import memcache
tags = memcache.get(cls.__name__ + '_popular_tags')
@@ -139,6 +207,9 @@
@classmethod
def expire_cached_tags(cls):
+ """Expire all tag lists which exist in memcache.
+ """
+
from google.appengine.api import memcache
memcache.delete(cls.__name__ + '_popular_tags')
@@ -151,8 +222,9 @@
return self.tag
+
def tag_property(tag_name):
- """Decorator that creates and returns a tag property to be used
+ """Decorator that creates and returns a tag property to be used
in Google AppEngine model.
Args:
@@ -170,7 +242,7 @@
return self._tags[tag_name]
def set_tags(self, seed):
- """Set a list of Tag objects for all Tags that apply to
+ """Set a list of Tag objects for all Tags that apply to
the specified entity.
"""
@@ -212,10 +284,10 @@
class Taggable(object):
"""A mixin class that is used for making GAE Model classes taggable.
- This is an extended version of Taggable-mixin which allows for
+ This is an extended version of Taggable-mixin which allows for
multiple tag properties in the same AppEngine Model class.
"""
-
+
def __init__(self, **kwargs):
"""The constructor class for Taggable, that creates a dictionary of tags.
@@ -239,7 +311,7 @@
def tags_string(self, tag_name, ret_list=False):
"""Create a formatted string version of this entity's tags.
-
+
Args:
tag_name: the name of the tag which must be formatted
ret_list: if False sends a string, otherwise sends a Python list