14 "A List of db.Key values for the datastore objects that have been tagged with this tag value." |
14 "A List of db.Key values for the datastore objects that have been tagged with this tag value." |
15 |
15 |
16 tagged_count = db.IntegerProperty(default=0) |
16 tagged_count = db.IntegerProperty(default=0) |
17 "The number of entities in tagged." |
17 "The number of entities in tagged." |
18 |
18 |
19 @staticmethod |
19 @classmethod |
20 def __key_name(tag_name): |
20 def __key_name(cls, tag_name): |
21 return "tag_%s" % tag_name |
21 return cls.__name__ + '_' + tag_name |
22 |
22 |
23 def remove_tagged(self, key): |
23 def remove_tagged(self, key): |
24 def remove_tagged_txn(): |
24 def remove_tagged_txn(): |
25 if key in self.tagged: |
25 if key in self.tagged: |
26 self.tagged.remove(key) |
26 self.tagged.remove(key) |
27 self.tagged_count -= 1 |
27 self.tagged_count -= 1 |
28 self.put() |
28 self.put() |
29 db.run_in_transaction(remove_tagged_txn) |
29 db.run_in_transaction(remove_tagged_txn) |
30 Tag.expire_cached_tags() |
30 self.__class__.expire_cached_tags() |
31 |
31 |
32 def add_tagged(self, key): |
32 def add_tagged(self, key): |
33 def add_tagged_txn(): |
33 def add_tagged_txn(): |
34 if key not in self.tagged: |
34 if key not in self.tagged: |
35 self.tagged.append(key) |
35 self.tagged.append(key) |
36 self.tagged_count += 1 |
36 self.tagged_count += 1 |
37 self.put() |
37 self.put() |
38 db.run_in_transaction(add_tagged_txn) |
38 db.run_in_transaction(add_tagged_txn) |
39 Tag.expire_cached_tags() |
39 self.__class__.expire_cached_tags() |
40 |
40 |
41 def clear_tagged(self): |
41 def clear_tagged(self): |
42 def clear_tagged_txn(): |
42 def clear_tagged_txn(): |
43 self.tagged = [] |
43 self.tagged = [] |
44 self.tagged_count = 0 |
44 self.tagged_count = 0 |
45 self.put() |
45 self.put() |
46 db.run_in_transaction(clear_tagged_txn) |
46 db.run_in_transaction(clear_tagged_txn) |
47 Tag.expire_cached_tags() |
47 self.__class__.expire_cached_tags() |
48 |
48 |
49 @classmethod |
49 @classmethod |
50 def get_by_name(cls, tag_name): |
50 def get_by_name(cls, tag_name): |
51 return Tag.get_by_key_name(Tag.__key_name(tag_name)) |
51 return cls.get_by_key_name(cls.__key_name(tag_name)) |
52 |
52 |
53 @classmethod |
53 @classmethod |
54 def get_tags_for_key(cls, key): |
54 def get_tags_for_key(cls, key): |
55 "Set the tags for the datastore object represented by key." |
55 "Set the tags for the datastore object represented by key." |
56 tags = db.Query(Tag).filter('tagged =', key).fetch(1000) |
56 tags = db.Query(cls).filter('tagged =', key).fetch(1000) |
57 return tags |
57 return tags |
58 |
58 |
59 @classmethod |
59 @classmethod |
60 def get_or_create(cls, tag_name): |
60 def get_or_create(cls, tag_name): |
61 "Get the Tag object that has the tag value given by tag_value." |
61 "Get the Tag object that has the tag value given by tag_value." |
62 tag_key_name = Tag.__key_name(tag_name) |
62 tag_key_name = cls.__key_name(tag_name) |
63 existing_tag = Tag.get_by_key_name(tag_key_name) |
63 existing_tag = cls.get_by_key_name(tag_key_name) |
64 if existing_tag is None: |
64 if existing_tag is None: |
65 # The tag does not yet exist, so create it. |
65 # The tag does not yet exist, so create it. |
66 def create_tag_txn(): |
66 def create_tag_txn(): |
67 new_tag = Tag(key_name=tag_key_name, tag = tag_name) |
67 new_tag = cls(key_name=tag_key_name, tag=tag_name) |
68 new_tag.put() |
68 new_tag.put() |
69 return new_tag |
69 return new_tag |
70 existing_tag = db.run_in_transaction(create_tag_txn) |
70 existing_tag = db.run_in_transaction(create_tag_txn) |
71 return existing_tag |
71 return existing_tag |
72 |
72 |
73 @classmethod |
73 @classmethod |
74 def get_tags_by_frequency(cls, limit=1000): |
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, |
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, |
76 most frequently-used first. If limit is given, return only that many tags; otherwise, |
77 return all.""" |
77 return all.""" |
78 tag_list = db.Query(Tag).filter('tagged_count >', 0).order("-tagged_count").fetch(limit) |
78 tag_list = db.Query(cls).filter('tagged_count >', 0).order("-tagged_count").fetch(limit) |
79 |
79 |
80 return tag_list |
80 return tag_list |
81 |
81 |
82 @classmethod |
82 @classmethod |
83 def get_tags_by_name(cls, limit=1000, ascending=True): |
83 def get_tags_by_name(cls, limit=1000, ascending=True): |
111 |
111 |
112 @classmethod |
112 @classmethod |
113 def popular_tags(cls, limit=5): |
113 def popular_tags(cls, limit=5): |
114 from google.appengine.api import memcache |
114 from google.appengine.api import memcache |
115 |
115 |
116 tags = memcache.get('popular_tags') |
116 tags = memcache.get(cls.__name__ + '_popular_tags') |
117 if tags is None: |
117 if tags is None: |
118 tags = Tag.get_tags_by_frequency(limit) |
118 tags = cls.get_tags_by_frequency(limit) |
119 memcache.add('popular_tags', tags, 3600) |
119 memcache.add(cls.__name__ + '_popular_tags', tags, 3600) |
120 |
120 |
121 return tags |
121 return tags |
122 |
122 |
123 @classmethod |
123 @classmethod |
124 def expire_cached_tags(cls): |
124 def expire_cached_tags(cls): |
125 from google.appengine.api import memcache |
125 from google.appengine.api import memcache |
126 |
126 |
127 memcache.delete('popular_tags') |
127 memcache.delete(cls.__name__ + '_popular_tags') |
128 memcache.delete('tags_by_name_asc') |
128 memcache.delete(cls.__name__ + '_tags_by_name_asc') |
129 memcache.delete('tags_by_name_desc') |
129 memcache.delete(cls.__name__ + '_tags_by_name_desc') |
130 |
130 |
131 class Taggable: |
131 class Taggable: |
132 """A mixin class that is used for making Google AppEnigne Model classes taggable. |
132 """A mixin class that is used for making Google AppEngine Model classes taggable. |
133 Usage: |
133 Usage: |
134 class Post(db.Model, taggable.Taggable): |
134 class Post(db.Model, taggable.Taggable): |
135 body = db.TextProperty(required = True) |
135 body = db.TextProperty(required = True) |
136 title = db.StringProperty() |
136 title = db.StringProperty() |
137 added = db.DateTimeProperty(auto_now_add=True) |
137 added = db.DateTimeProperty(auto_now_add=True) |
140 def __init__(self, parent=None, key_name=None, app=None, **entity_values): |
140 def __init__(self, parent=None, key_name=None, app=None, **entity_values): |
141 db.Model.__init__(self, parent, key_name, app, **entity_values) |
141 db.Model.__init__(self, parent, key_name, app, **entity_values) |
142 taggable.Taggable.__init__(self) |
142 taggable.Taggable.__init__(self) |
143 """ |
143 """ |
144 |
144 |
145 def __init__(self): |
145 def __init__(self, tag_model = Tag): |
146 self.__tags = None |
146 self.__tags = None |
|
147 self.__tag_model = tag_model |
147 self.tag_separator = "," |
148 self.tag_separator = "," |
148 """The string that is used to separate individual tags in a string |
149 """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 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 |
151 into a string representation and tags setter to split a string into |
151 individual tags.""" |
152 individual tags.""" |
152 |
153 |
153 def __get_tags(self): |
154 def __get_tags(self): |
154 "Get a List of Tag objects for all Tags that apply to this object." |
155 "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 if self.__tags is None or len(self.__tags) == 0: |
156 self.__tags = Tag.get_tags_for_key(self.key()) |
157 self.__tags = self.__tag_model.get_tags_for_key(self.key()) |
157 return self.__tags |
158 return self.__tags |
158 |
159 |
159 def __set_tags(self, tags): |
160 def __set_tags(self, tags): |
160 import types |
161 import types |
161 if type(tags) is types.UnicodeType: |
162 if type(tags) is types.UnicodeType: |
180 each_tag = string.strip(each_tag) |
181 each_tag = string.strip(each_tag) |
181 if len(each_tag) > 0 and each_tag not in self.__tags: |
182 if len(each_tag) > 0 and each_tag not in self.__tags: |
182 # A tag that was not previously assigned to this entity |
183 # A tag that was not previously assigned to this entity |
183 # is present in the list that is being assigned, so we |
184 # is present in the list that is being assigned, so we |
184 # associate this entity with the tag. |
185 # associate this entity with the tag. |
185 tag = Tag.get_or_create(each_tag) |
186 tag = self.__tag_model.get_or_create(each_tag) |
186 tag.add_tagged(self.key()) |
187 tag.add_tagged(self.key()) |
187 self.__tags.append(tag) |
188 self.__tags.append(tag) |
188 else: |
189 else: |
189 raise Exception, "tags must be either a unicode, a string or a list" |
190 raise Exception, "tags must be either a unicode, a string or a list" |
190 |
191 |