app/taggable-mixin/taggable.py
changeset 2376 feec28b50f1b
parent 2368 e07c425c7135
equal deleted inserted replaced
2375:b9b203dc96d8 2376:feec28b50f1b
    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):
    85         If a limit is given, return only that many tags; otherwise, return all.
    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."""
    86         If ascending is True, sort from a-z; otherwise, sort from z-a."""
    87 
    87 
    88         from google.appengine.api import memcache
    88         from google.appengine.api import memcache
    89 
    89 
    90         cache_name = 'tags_by_name'
    90         cache_name = cls.__name__ + '_tags_by_name'
    91         if ascending:
    91         if ascending:
    92             cache_name += '_asc'
    92             cache_name += '_asc'
    93         else:
    93         else:
    94             cache_name += '_desc'
    94             cache_name += '_desc'
    95             
    95             
    97         if tags is None or len(tags) < limit:
    97         if tags is None or len(tags) < limit:
    98             order_by = "tag"
    98             order_by = "tag"
    99             if not ascending:
    99             if not ascending:
   100                 order_by = "-tag"
   100                 order_by = "-tag"
   101             
   101             
   102             tags = db.Query(Tag).order(order_by).fetch(limit)
   102             tags = db.Query(cls).order(order_by).fetch(limit)
   103             memcache.add(cache_name, tags, 3600)
   103             memcache.add(cache_name, tags, 3600)
   104         else:
   104         else:
   105             if len(tags) > limit:
   105             if len(tags) > limit:
   106                 # Return only as many as requested.
   106                 # Return only as many as requested.
   107                 tags = tags[:limit]
   107                 tags = tags[:limit]
   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