# HG changeset patch # User Daniel Hans # Date 1257873486 -3600 # Node ID ded7a67e7e0acf85d5c0c1d21de413d5fe63aae9 # Parent cac43a6cb98678fe7b14ec671ea0b7b8a5a29e8a Some functions which applies to scoped tags in general moved from TaskTag to Task model. Also, some stylish and whitespace changes and docstrings added. diff -r cac43a6cb986 -r ded7a67e7e0a app/soc/modules/ghop/models/task.py --- a/app/soc/modules/ghop/models/task.py Tue Nov 10 14:00:15 2009 +0100 +++ b/app/soc/modules/ghop/models/task.py Tue Nov 10 18:18:06 2009 +0100 @@ -19,6 +19,7 @@ __authors__ = [ '"Madhusudan.C.S" ', + '"Daniel Hans" ', '"Lennard de Rijk" ', ] @@ -43,27 +44,14 @@ """Model for storing all Task tags. """ - #: Each task_type tag is scoped under the program. - order = db.IntegerProperty(required=True, default=0) @classmethod - def __key_name(cls, scope_path, tag_name): - """Create the key_name from program key_name as scope_path and tag_name. + def get_by_scope(cls, scope): + """Get the list of tag objects that has the given scope and sorts the + result by order values. """ - return scope_path + '/' + tag_name - @classmethod - def get_by_name(cls, tag_name): - """Get the list of tag objects that has the given tag_name. - """ - tags = db.Query(cls).filter('tag =', tag_name).fetch(1000) - return tags - - @classmethod - def get_by_scope(cls, scope): - """Get the list of tag objects that has the given scope. - """ tags = db.Query(cls).filter('scope =', scope).order('order').fetch(1000) return tags @@ -71,27 +59,14 @@ def get_highest_order(cls, scope): """Get a tag with highest order. """ - tags = db.Query(cls).filter('scope =', scope).order('-order').fetch(1) - if tags: - return tags[0].order + + tag = db.Query(cls).filter('scope =', scope).order('-order').get() + if tag: + return tag.order else: return -1 @classmethod - def get_by_scope_and_name(cls, scope, tag_name): - """Get a tag by scope and name. - - There can be only one such tag. - """ - - tags = db.Query(cls).filter( - 'scope =', scope).filter('tag =', tag_name).fetch(1) - if tags: - return tags[0] - else: - return None - - @classmethod def update_order(cls, scope, tag_name, order): """Updates the order of the tag. """ @@ -103,64 +78,6 @@ return tag - @classmethod - def copy_tag(cls, scope, tag_name, new_tag_name): - """Copy a tag with a given scope and tag_name to another tag with - new tag_name. - """ - tag = cls.get_by_scope_and_name(scope, tag_name) - - if tag: - tag_key_name = cls.__key_name(scope.key().name(), new_tag_name) - existing_tag = cls.get_by_key_name(tag_key_name) - - if existing_tag is None: - new_tag = cls(key_name=tag_key_name, tag=new_tag_name, scope=scope, - added=tag.added, tagged=tag.tagged, - tagged_count=tag.tagged_count) - new_tag.put() - tag.delete() - - return new_tag - - return existing_tag - - return None - - @classmethod - def delete_tag(cls, scope, tag_name): - """Delete a tag with a given scope and tag_name. - """ - tag = cls.get_by_scope_and_name(scope, tag_name) - - if tag: - tag.delete() - return True - - return False - - @classmethod - def get_or_create(cls, scope, tag_name, order=0): - """Get the Tag object that has the tag value given by tag_value. - """ - - if not scope: - return None - - tag_key_name = cls.__key_name(scope.key().name(), tag_name) - existing_tag = cls.get_by_key_name(tag_key_name) - if existing_tag is None: - # the tag does not yet exist, so create it. - if not order: - order = cls.get_highest_order(scope=scope) + 1 - def create_tag_txn(): - new_tag = cls(key_name=tag_key_name, tag=tag_name, - scope=scope, order=order) - new_tag.put() - return new_tag - existing_tag = db.run_in_transaction(create_tag_txn) - return existing_tag - class TaskTypeTag(TaskTag): """Model for storing of task type tags. @@ -202,12 +119,12 @@ title.help_text = ugettext('Title of the task') #: Required field containing the description of the task - description = db.TextProperty(required=True, + description = db.TextProperty(required=True, verbose_name=ugettext('Description')) description.help_text = ugettext('Complete description of the task') #: Field indicating the difficulty level of the Task. This is not - #: mandatory so the it can be assigned at any later stage. + #: mandatory so the it can be assigned at any later stage. #: The options are configured by a Program Admin. difficulty = tag_property('difficulty') @@ -260,7 +177,7 @@ #: reopened. #: ClaimRequested: A Student has requested to claim this task. #: Claimed: This Task has been claimed and someone is working on it. - #: ActionNeeded: Work on this Task must be submitted for review within + #: ActionNeeded: Work on this Task must be submitted for review within #: 24 hours. #: Closed: Work on this Task has been completed to the org's content. #: AwaitingRegistration: Student has completed work on this task, but @@ -272,8 +189,8 @@ #: Invalid: The Task is deleted either by an Org Admin/Mentor status = db.StringProperty( required=True, verbose_name=ugettext('Status'), - choices=['Unapproved', 'Unpublished', 'Open', 'Reopened', - 'ClaimRequested', 'Claimed', 'ActionNeeded', + choices=['Unapproved', 'Unpublished', 'Open', 'Reopened', + 'ClaimRequested', 'Claimed', 'ActionNeeded', 'Closed', 'AwaitingRegistration', 'NeedsWork', 'NeedsReview', 'Invalid'], default='Unapproved') @@ -318,7 +235,7 @@ #: "state": "Unapproved" #: ... #: "edited_by": Role reference - #: + #: #: } #: timestamp2: { #: "state": "Unpublished" @@ -330,10 +247,10 @@ #: Reference properties will be stored by calling str() on their Key. history = db.TextProperty(required=False, default='') - def __init__(self, parent=None, key_name=None, + def __init__(self, parent=None, key_name=None, app=None, **entity_values): """Constructor for GHOPTask Model. - + Args: See Google App Engine APIs. """ @@ -343,6 +260,6 @@ # call the Taggable constructor to initialize the tags specified as # keyword arguments - Taggable.__init__(self, task_type=TaskTypeTag, + Taggable.__init__(self, task_type=TaskTypeTag, difficulty=TaskDifficultyTag, arbit_tag=TaskArbitraryTag) diff -r cac43a6cb986 -r ded7a67e7e0a app/taggable/taggable.py --- a/app/taggable/taggable.py Tue Nov 10 14:00:15 2009 +0100 +++ b/app/taggable/taggable.py Tue Nov 10 18:18:06 2009 +0100 @@ -2,9 +2,10 @@ import string import soc.models.linkable - + class Tag(db.Model): - "Google AppEngine model for store of tags." + """Google AppEngine model for store of tags. + """ tag = db.StringProperty(required=True) "The actual string value of the tag." @@ -27,9 +28,12 @@ "Each tag is scoped under some linkable model." @classmethod - def __key_name(cls, tag_name): - return cls.__name__ + '_' + tag_name - + def __key_name(cls, scope_path, tag_name): + """Create the key_name from program key_name as scope_path and tag_name. + """ + + return scope_path + '/' + tag_name + def remove_tagged(self, key): def remove_tagged_txn(): if key in self.tagged: @@ -50,7 +54,7 @@ self.put() db.run_in_transaction(add_tagged_txn) self.__class__.expire_cached_tags() - + def clear_tagged(self): def clear_tagged_txn(): if self.auto_delete: @@ -61,47 +65,70 @@ self.put() db.run_in_transaction(clear_tagged_txn) self.__class__.expire_cached_tags() - + @classmethod def get_by_name(cls, tag_name): - return cls.get_by_key_name(cls.__key_name(tag_name)) - + """Get the list of tag objects that has the given tag_name. + """ + + tags = db.Query(cls).filter('tag =', tag_name).fetch(1000) + return tags + @classmethod - def get_tags_for_key(cls, key): - "Set the tags for the datastore object represented by key." - tags = db.Query(cls).filter('tagged =', key).fetch(1000) + def get_by_scope_and_name(cls, scope, tag_name): + """Get a tag by scope and name. + + There may be only one such tag. + """ + + return db.Query(cls).filter( + 'scope =', scope).filter('tag =', tag_name).get() + + @classmethod + def get_tags_for_key(cls, key, limit=1000): + """Get the tags for the datastore object represented by key. + """ + + tags = db.Query(cls).filter('tagged =', key).fetch(limit) return tags - + @classmethod - def get_or_create(cls, tag_name): - "Get the Tag object that has the tag value given by tag_value." - tag_key_name = cls.__key_name(tag_name) + def get_or_create(cls, scope, tag_name, order=0): + """Get the Tag object that has the tag value given by tag_value. + """ + + tag_key_name = cls.__key_name(scope.key().name(), tag_name) existing_tag = cls.get_by_key_name(tag_key_name) if existing_tag is None: - # The tag does not yet exist, so create it. + # the tag does not yet exist, so create it. + if not order: + order = cls.get_highest_order(scope=scope) + 1 def create_tag_txn(): - new_tag = cls(key_name=tag_key_name, tag=tag_name) + new_tag = cls(key_name=tag_key_name, tag=tag_name, + scope=scope, order=order) new_tag.put() return new_tag existing_tag = db.run_in_transaction(create_tag_txn) return existing_tag - + @classmethod def get_tags_by_frequency(cls, limit=1000): - """Return a list of Tags sorted by the number of objects to - which they have been applied, most frequently-used first. - If limit is given, return only that many tags; otherwise, - return all.""" + """Return a list of Tags sorted by the number of objects to which they + have been applied, most frequently-used first. If limit is given, return + only return only that many tags; otherwise, return all. + """ + tag_list = db.Query(cls).filter('tagged_count >', 0).order( "-tagged_count").fetch(limit) - + return tag_list @classmethod def get_tags_by_name(cls, limit=1000, ascending=True): """Return a list of Tags sorted alphabetically by the name of the tag. If a limit is given, return only that many tags; otherwise, return all. - If ascending is True, sort from a-z; otherwise, sort from z-a.""" + If ascending is True, sort from a-z; otherwise, sort from z-a. + """ from google.appengine.api import memcache @@ -127,7 +154,48 @@ return tags @classmethod + def copy_tag(cls, scope, tag_name, new_tag_name): + """Copy a tag with a given scope and tag_name to another tag with + new tag_name. + """ + tag = cls.get_by_scope_and_name(scope, tag_name) + + if tag: + tag_key_name = cls.__key_name(scope.key().name(), new_tag_name) + existing_tag = cls.get_by_key_name(tag_key_name) + + if existing_tag is None: + new_tag = cls(key_name=tag_key_name, tag=new_tag_name, scope=scope, + added=tag.added, tagged=tag.tagged, + tagged_count=tag.tagged_count) + new_tag.put() + tag.delete() + + return new_tag + + return existing_tag + + return None + + @classmethod + def delete_tag(cls, scope, tag_name): + """Delete a tag with a given scope and tag_name. + """ + + tag = cls.get_by_scope_and_name(scope, tag_name) + + if tag: + tag.delete() + return True + + return False + + @classmethod def popular_tags(cls, limit=5): + """Get the most popular tags from memcache, or if they are not defined + there, it retrieves them from datastore and sets in memcache. + """ + from google.appengine.api import memcache tags = memcache.get(cls.__name__ + '_popular_tags') @@ -139,6 +207,9 @@ @classmethod def expire_cached_tags(cls): + """Expire all tag lists which exist in memcache. + """ + from google.appengine.api import memcache memcache.delete(cls.__name__ + '_popular_tags') @@ -151,8 +222,9 @@ return self.tag + def tag_property(tag_name): - """Decorator that creates and returns a tag property to be used + """Decorator that creates and returns a tag property to be used in Google AppEngine model. Args: @@ -170,7 +242,7 @@ return self._tags[tag_name] def set_tags(self, seed): - """Set a list of Tag objects for all Tags that apply to + """Set a list of Tag objects for all Tags that apply to the specified entity. """ @@ -212,10 +284,10 @@ class Taggable(object): """A mixin class that is used for making GAE Model classes taggable. - This is an extended version of Taggable-mixin which allows for + This is an extended version of Taggable-mixin which allows for multiple tag properties in the same AppEngine Model class. """ - + def __init__(self, **kwargs): """The constructor class for Taggable, that creates a dictionary of tags. @@ -239,7 +311,7 @@ def tags_string(self, tag_name, ret_list=False): """Create a formatted string version of this entity's tags. - + Args: tag_name: the name of the tag which must be formatted ret_list: if False sends a string, otherwise sends a Python list