1 from google
.appengine
.ext
import db
5 "Google AppEngine model for store of tags."
7 tag
= db
.StringProperty(required
=True)
8 "The actual string value of the tag."
10 added
= db
.DateTimeProperty(auto_now_add
=True)
11 "The date and time that the tag was first added to the datastore."
13 tagged
= db
.ListProperty(db
.Key
)
14 "A List of db.Key values for the datastore objects that have been tagged with this tag value."
16 tagged_count
= db
.IntegerProperty(default
=0)
17 "The number of entities in tagged."
20 def __key_name(tag_name
):
21 return "tag_%s" % tag_name
23 def remove_tagged(self
, key
):
24 def remove_tagged_txn():
25 if key
in self
.tagged
:
26 self
.tagged
.remove(key
)
27 self
.tagged_count
-= 1
29 db
.run_in_transaction(remove_tagged_txn
)
30 Tag
.expire_cached_tags()
32 def add_tagged(self
, key
):
34 if key
not in self
.tagged
:
35 self
.tagged
.append(key
)
36 self
.tagged_count
+= 1
38 db
.run_in_transaction(add_tagged_txn
)
39 Tag
.expire_cached_tags()
41 def clear_tagged(self
):
42 def clear_tagged_txn():
46 db
.run_in_transaction(clear_tagged_txn
)
47 Tag
.expire_cached_tags()
50 def get_by_name(cls
, tag_name
):
51 return Tag
.get_by_key_name(Tag
.__key
_name
(tag_name
))
54 def get_tags_for_key(cls
, key
):
55 "Set the tags for the datastore object represented by key."
56 tags
= db
.Query(Tag
).filter('tagged =', key
).fetch(1000)
60 def get_or_create(cls
, tag_name
):
61 "Get the Tag object that has the tag value given by tag_value."
62 tag_key_name
= Tag
.__key
_name
(tag_name
)
63 existing_tag
= Tag
.get_by_key_name(tag_key_name
)
64 if existing_tag
is None:
65 # The tag does not yet exist, so create it.
67 new_tag
= Tag(key_name
=tag_key_name
, tag
= tag_name
)
70 existing_tag
= db
.run_in_transaction(create_tag_txn
)
74 def get_tags_by_frequency(cls
, limit
=1000):
75 """Return a list of Tags sorted by the number of objects to which they have been applied,
76 most frequently-used first. If limit is given, return only that many tags; otherwise,
78 tag_list
= db
.Query(Tag
).filter('tagged_count >', 0).order("-tagged_count").fetch(limit
)
83 def get_tags_by_name(cls
, limit
=1000, ascending
=True):
84 """Return a list of Tags sorted alphabetically by the name of the tag.
85 If a limit is given, return only that many tags; otherwise, return all.
86 If ascending is True, sort from a-z; otherwise, sort from z-a."""
88 from google
.appengine
.api
import memcache
90 cache_name
= 'tags_by_name'
96 tags
= memcache
.get(cache_name
)
97 if tags
is None or len(tags
) < limit
:
102 tags
= db
.Query(Tag
).order(order_by
).fetch(limit
)
103 memcache
.add(cache_name
, tags
, 3600)
105 if len(tags
) > limit
:
106 # Return only as many as requested.
113 def popular_tags(cls
, limit
=5):
114 from google
.appengine
.api
import memcache
116 tags
= memcache
.get('popular_tags')
118 tags
= Tag
.get_tags_by_frequency(limit
)
119 memcache
.add('popular_tags', tags
, 3600)
124 def expire_cached_tags(cls
):
125 from google
.appengine
.api
import memcache
127 memcache
.delete('popular_tags')
128 memcache
.delete('tags_by_name_asc')
129 memcache
.delete('tags_by_name_desc')
132 """A mixin class that is used for making Google AppEnigne Model classes taggable.
134 class Post(db.Model, taggable.Taggable):
135 body = db.TextProperty(required = True)
136 title = db.StringProperty()
137 added = db.DateTimeProperty(auto_now_add=True)
138 edited = db.DateTimeProperty()
140 def __init__(self, parent=None, key_name=None, app=None, **entity_values):
141 db.Model.__init__(self, parent, key_name, app, **entity_values)
142 taggable.Taggable.__init__(self)
147 self
.tag_separator
= ","
148 """The string that is used to separate individual tags in a string
149 representation of a list of tags. Used by tags_string() to join the tags
150 into a string representation and tags setter to split a string into
153 def __get_tags(self
):
154 "Get a List of Tag objects for all Tags that apply to this object."
155 if self
.__tags
is None or len(self
.__tags
) == 0:
156 self
.__tags
= Tag
.get_tags_for_key(self
.key())
159 def __set_tags(self
, tags
):
161 if type(tags
) is types
.UnicodeType
:
162 # Convert unicode to a plain string
164 if type(tags
) is types
.StringType
:
165 # Tags is a string, split it on tag_seperator into a list
166 tags
= string
.split(tags
, self
.tag_separator
)
167 if type(tags
) is types
.ListType
:
169 # Firstly, we will check to see if any tags have been removed.
170 # Iterate over a copy of __tags, as we may need to modify __tags
171 for each_tag
in self
.__tags
[:]:
172 if each_tag
not in tags
:
173 # A tag that was previously assigned to this entity is
174 # missing in the list that is being assigned, so we
175 # disassocaite this entity and the tag.
176 each_tag
.remove_tagged(self
.key())
177 self
.__tags
.remove(each_tag
)
178 # Secondly, we will check to see if any tags have been added.
179 for each_tag
in tags
:
180 each_tag
= string
.strip(each_tag
)
181 if len(each_tag
) > 0 and each_tag
not in self
.__tags
:
182 # A tag that was not previously assigned to this entity
183 # is present in the list that is being assigned, so we
184 # associate this entity with the tag.
185 tag
= Tag
.get_or_create(each_tag
)
186 tag
.add_tagged(self
.key())
187 self
.__tags
.append(tag
)
189 raise Exception, "tags must be either a unicode, a string or a list"
191 tags
= property(__get_tags
, __set_tags
, None, None)
193 def tags_string(self
):
194 "Create a formatted string version of this entity's tags"
196 for each_tag
in self
.tags
:
197 to_str
+= each_tag
.tag
198 if each_tag
!= self
.tags
[-1]:
199 to_str
+= self
.tag_separator