25 required=False, |
26 required=False, |
26 collection_name='task_type_tags') |
27 collection_name='task_type_tags') |
27 "Each tag is scoped under some linkable model." |
28 "Each tag is scoped under some linkable model." |
28 |
29 |
29 @classmethod |
30 @classmethod |
30 def __key_name(cls, tag_name): |
31 def __key_name(cls, scope_path, tag_name): |
31 return cls.__name__ + '_' + tag_name |
32 """Create the key_name from program key_name as scope_path and tag_name. |
32 |
33 """ |
|
34 |
|
35 return scope_path + '/' + tag_name |
|
36 |
33 def remove_tagged(self, key): |
37 def remove_tagged(self, key): |
34 def remove_tagged_txn(): |
38 def remove_tagged_txn(): |
35 if key in self.tagged: |
39 if key in self.tagged: |
36 self.tagged.remove(key) |
40 self.tagged.remove(key) |
37 self.tagged_count -= 1 |
41 self.tagged_count -= 1 |
48 self.tagged.append(key) |
52 self.tagged.append(key) |
49 self.tagged_count += 1 |
53 self.tagged_count += 1 |
50 self.put() |
54 self.put() |
51 db.run_in_transaction(add_tagged_txn) |
55 db.run_in_transaction(add_tagged_txn) |
52 self.__class__.expire_cached_tags() |
56 self.__class__.expire_cached_tags() |
53 |
57 |
54 def clear_tagged(self): |
58 def clear_tagged(self): |
55 def clear_tagged_txn(): |
59 def clear_tagged_txn(): |
56 if self.auto_delete: |
60 if self.auto_delete: |
57 self.delete() |
61 self.delete() |
58 else: |
62 else: |
59 self.tagged = [] |
63 self.tagged = [] |
60 self.tagged_count = 0 |
64 self.tagged_count = 0 |
61 self.put() |
65 self.put() |
62 db.run_in_transaction(clear_tagged_txn) |
66 db.run_in_transaction(clear_tagged_txn) |
63 self.__class__.expire_cached_tags() |
67 self.__class__.expire_cached_tags() |
64 |
68 |
65 @classmethod |
69 @classmethod |
66 def get_by_name(cls, tag_name): |
70 def get_by_name(cls, tag_name): |
67 return cls.get_by_key_name(cls.__key_name(tag_name)) |
71 """Get the list of tag objects that has the given tag_name. |
68 |
72 """ |
69 @classmethod |
73 |
70 def get_tags_for_key(cls, key): |
74 tags = db.Query(cls).filter('tag =', tag_name).fetch(1000) |
71 "Set the tags for the datastore object represented by key." |
|
72 tags = db.Query(cls).filter('tagged =', key).fetch(1000) |
|
73 return tags |
75 return tags |
74 |
76 |
75 @classmethod |
77 @classmethod |
76 def get_or_create(cls, tag_name): |
78 def get_by_scope_and_name(cls, scope, tag_name): |
77 "Get the Tag object that has the tag value given by tag_value." |
79 """Get a tag by scope and name. |
78 tag_key_name = cls.__key_name(tag_name) |
80 |
|
81 There may be only one such tag. |
|
82 """ |
|
83 |
|
84 return db.Query(cls).filter( |
|
85 'scope =', scope).filter('tag =', tag_name).get() |
|
86 |
|
87 @classmethod |
|
88 def get_tags_for_key(cls, key, limit=1000): |
|
89 """Get the tags for the datastore object represented by key. |
|
90 """ |
|
91 |
|
92 tags = db.Query(cls).filter('tagged =', key).fetch(limit) |
|
93 return tags |
|
94 |
|
95 @classmethod |
|
96 def get_or_create(cls, scope, tag_name, order=0): |
|
97 """Get the Tag object that has the tag value given by tag_value. |
|
98 """ |
|
99 |
|
100 tag_key_name = cls.__key_name(scope.key().name(), tag_name) |
79 existing_tag = cls.get_by_key_name(tag_key_name) |
101 existing_tag = cls.get_by_key_name(tag_key_name) |
80 if existing_tag is None: |
102 if existing_tag is None: |
81 # The tag does not yet exist, so create it. |
103 # the tag does not yet exist, so create it. |
|
104 if not order: |
|
105 order = cls.get_highest_order(scope=scope) + 1 |
82 def create_tag_txn(): |
106 def create_tag_txn(): |
83 new_tag = cls(key_name=tag_key_name, tag=tag_name) |
107 new_tag = cls(key_name=tag_key_name, tag=tag_name, |
|
108 scope=scope, order=order) |
84 new_tag.put() |
109 new_tag.put() |
85 return new_tag |
110 return new_tag |
86 existing_tag = db.run_in_transaction(create_tag_txn) |
111 existing_tag = db.run_in_transaction(create_tag_txn) |
87 return existing_tag |
112 return existing_tag |
88 |
113 |
89 @classmethod |
114 @classmethod |
90 def get_tags_by_frequency(cls, limit=1000): |
115 def get_tags_by_frequency(cls, limit=1000): |
91 """Return a list of Tags sorted by the number of objects to |
116 """Return a list of Tags sorted by the number of objects to which they |
92 which they have been applied, most frequently-used first. |
117 have been applied, most frequently-used first. If limit is given, return |
93 If limit is given, return only that many tags; otherwise, |
118 only return only that many tags; otherwise, return all. |
94 return all.""" |
119 """ |
|
120 |
95 tag_list = db.Query(cls).filter('tagged_count >', 0).order( |
121 tag_list = db.Query(cls).filter('tagged_count >', 0).order( |
96 "-tagged_count").fetch(limit) |
122 "-tagged_count").fetch(limit) |
97 |
123 |
98 return tag_list |
124 return tag_list |
99 |
125 |
100 @classmethod |
126 @classmethod |
101 def get_tags_by_name(cls, limit=1000, ascending=True): |
127 def get_tags_by_name(cls, limit=1000, ascending=True): |
102 """Return a list of Tags sorted alphabetically by the name of the tag. |
128 """Return a list of Tags sorted alphabetically by the name of the tag. |
103 If a limit is given, return only that many tags; otherwise, return all. |
129 If a limit is given, return only that many tags; otherwise, return all. |
104 If ascending is True, sort from a-z; otherwise, sort from z-a.""" |
130 If ascending is True, sort from a-z; otherwise, sort from z-a. |
|
131 """ |
105 |
132 |
106 from google.appengine.api import memcache |
133 from google.appengine.api import memcache |
107 |
134 |
108 cache_name = cls.__name__ + '_tags_by_name' |
135 cache_name = cls.__name__ + '_tags_by_name' |
109 if ascending: |
136 if ascending: |
125 tags = tags[:limit] |
152 tags = tags[:limit] |
126 |
153 |
127 return tags |
154 return tags |
128 |
155 |
129 @classmethod |
156 @classmethod |
|
157 def copy_tag(cls, scope, tag_name, new_tag_name): |
|
158 """Copy a tag with a given scope and tag_name to another tag with |
|
159 new tag_name. |
|
160 """ |
|
161 tag = cls.get_by_scope_and_name(scope, tag_name) |
|
162 |
|
163 if tag: |
|
164 tag_key_name = cls.__key_name(scope.key().name(), new_tag_name) |
|
165 existing_tag = cls.get_by_key_name(tag_key_name) |
|
166 |
|
167 if existing_tag is None: |
|
168 new_tag = cls(key_name=tag_key_name, tag=new_tag_name, scope=scope, |
|
169 added=tag.added, tagged=tag.tagged, |
|
170 tagged_count=tag.tagged_count) |
|
171 new_tag.put() |
|
172 tag.delete() |
|
173 |
|
174 return new_tag |
|
175 |
|
176 return existing_tag |
|
177 |
|
178 return None |
|
179 |
|
180 @classmethod |
|
181 def delete_tag(cls, scope, tag_name): |
|
182 """Delete a tag with a given scope and tag_name. |
|
183 """ |
|
184 |
|
185 tag = cls.get_by_scope_and_name(scope, tag_name) |
|
186 |
|
187 if tag: |
|
188 tag.delete() |
|
189 return True |
|
190 |
|
191 return False |
|
192 |
|
193 @classmethod |
130 def popular_tags(cls, limit=5): |
194 def popular_tags(cls, limit=5): |
|
195 """Get the most popular tags from memcache, or if they are not defined |
|
196 there, it retrieves them from datastore and sets in memcache. |
|
197 """ |
|
198 |
131 from google.appengine.api import memcache |
199 from google.appengine.api import memcache |
132 |
200 |
133 tags = memcache.get(cls.__name__ + '_popular_tags') |
201 tags = memcache.get(cls.__name__ + '_popular_tags') |
134 if tags is None: |
202 if tags is None: |
135 tags = cls.get_tags_by_frequency(limit) |
203 tags = cls.get_tags_by_frequency(limit) |
210 |
282 |
211 |
283 |
212 class Taggable(object): |
284 class Taggable(object): |
213 """A mixin class that is used for making GAE Model classes taggable. |
285 """A mixin class that is used for making GAE Model classes taggable. |
214 |
286 |
215 This is an extended version of Taggable-mixin which allows for |
287 This is an extended version of Taggable-mixin which allows for |
216 multiple tag properties in the same AppEngine Model class. |
288 multiple tag properties in the same AppEngine Model class. |
217 """ |
289 """ |
218 |
290 |
219 def __init__(self, **kwargs): |
291 def __init__(self, **kwargs): |
220 """The constructor class for Taggable, that creates a dictionary of tags. |
292 """The constructor class for Taggable, that creates a dictionary of tags. |
221 |
293 |
222 The difference from the original taggable in terms of interface is |
294 The difference from the original taggable in terms of interface is |
223 that, tag class is not used as the default tag model, since we don't |
295 that, tag class is not used as the default tag model, since we don't |