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(cls
, tag_name
):
21 return cls
.__name
__ + '_' + 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 self
.__class
__.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 self
.__class
__.expire_cached_tags()
41 def clear_tagged(self
):
42 def clear_tagged_txn():
46 db
.run_in_transaction(clear_tagged_txn
)
47 self
.__class
__.expire_cached_tags()
50 def get_by_name(cls
, tag_name
):
51 return cls
.get_by_key_name(cls
.__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(cls
).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
= cls
.__key
_name
(tag_name
)
63 existing_tag
= cls
.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
= cls(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(cls
).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
= cls
.__name
__ + '_tags_by_name'
96 tags
= memcache
.get(cache_name
)
97 if tags
is None or len(tags
) < limit
:
102 tags
= db
.Query(cls
).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(cls
.__name
__ + '_popular_tags')
118 tags
= cls
.get_tags_by_frequency(limit
)
119 memcache
.add(cls
.__name
__ + '_popular_tags', tags
, 3600)
124 def expire_cached_tags(cls
):
125 from google
.appengine
.api
import memcache
127 memcache
.delete(cls
.__name
__ + '_popular_tags')
128 memcache
.delete(cls
.__name
__ + '_tags_by_name_asc')
129 memcache
.delete(cls
.__name
__ + '_tags_by_name_desc')
132 """A mixin class that is used for making Google AppEngine 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)
145 def __init__(self
, tag_model
= Tag
):
147 self
.__tag
_model
= tag_model
148 self
.tag_separator
= ","
149 """The string that is used to separate individual tags in a string
150 representation of a list of tags. Used by tags_string() to join the tags
151 into a string representation and tags setter to split a string into
154 def __get_tags(self
):
155 "Get a List of Tag objects for all Tags that apply to this object."
156 if self
.__tags
is None or len(self
.__tags
) == 0:
157 self
.__tags
= self
.__tag
_model
.get_tags_for_key(self
.key())
160 def __set_tags(self
, tags
):
162 if type(tags
) is types
.UnicodeType
:
163 # Convert unicode to a plain string
165 if type(tags
) is types
.StringType
:
166 # Tags is a string, split it on tag_seperator into a list
167 tags
= string
.split(tags
, self
.tag_separator
)
168 if type(tags
) is types
.ListType
:
170 # Firstly, we will check to see if any tags have been removed.
171 # Iterate over a copy of __tags, as we may need to modify __tags
172 for each_tag
in self
.__tags
[:]:
173 if each_tag
not in tags
:
174 # A tag that was previously assigned to this entity is
175 # missing in the list that is being assigned, so we
176 # disassocaite this entity and the tag.
177 each_tag
.remove_tagged(self
.key())
178 self
.__tags
.remove(each_tag
)
179 # Secondly, we will check to see if any tags have been added.
180 for each_tag
in tags
:
181 each_tag
= string
.strip(each_tag
)
182 if len(each_tag
) > 0 and each_tag
not in self
.__tags
:
183 # A tag that was not previously assigned to this entity
184 # is present in the list that is being assigned, so we
185 # associate this entity with the tag.
186 tag
= self
.__tag
_model
.get_or_create(each_tag
)
187 tag
.add_tagged(self
.key())
188 self
.__tags
.append(tag
)
190 raise Exception, "tags must be either a unicode, a string or a list"
192 tags
= property(__get_tags
, __set_tags
, None, None)
194 def tags_string(self
):
195 "Create a formatted string version of this entity's tags"
197 for each_tag
in self
.tags
:
198 to_str
+= each_tag
.tag
199 if each_tag
!= self
.tags
[-1]:
200 to_str
+= self
.tag_separator