diff options
-rw-r--r-- | askbot/migrations/0130_auto__add_field_tag_status.py (renamed from askbot/migrations/0130_auto__add_suggestedtag.py) | 52 | ||||
-rw-r--r-- | askbot/models/__init__.py | 57 | ||||
-rw-r--r-- | askbot/models/question.py | 30 | ||||
-rw-r--r-- | askbot/models/tag.py | 189 | ||||
-rw-r--r-- | askbot/skins/default/templates/moderate_tags.html | 2 | ||||
-rw-r--r-- | askbot/views/commands.py | 3 | ||||
-rw-r--r-- | askbot/views/meta.py | 7 |
7 files changed, 171 insertions, 169 deletions
diff --git a/askbot/migrations/0130_auto__add_suggestedtag.py b/askbot/migrations/0130_auto__add_field_tag_status.py index d769c76d..f5557546 100644 --- a/askbot/migrations/0130_auto__add_suggestedtag.py +++ b/askbot/migrations/0130_auto__add_field_tag_status.py @@ -8,40 +8,25 @@ from django.db import models class Migration(SchemaMigration): def forwards(self, orm): - # Adding model 'SuggestedTag' - db.create_table('askbot_suggestedtag', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), - ('used_count', self.gf('django.db.models.fields.PositiveIntegerField')(default=1)), - ('thread_ids', self.gf('django.db.models.fields.TextField')()), - )) - db.send_create_signal('askbot', ['SuggestedTag']) - - # Adding M2M table for field threads on 'SuggestedTag' - db.create_table('askbot_suggestedtag_threads', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('suggestedtag', models.ForeignKey(orm['askbot.suggestedtag'], null=False)), - ('thread', models.ForeignKey(orm['askbot.thread'], null=False)) - )) - db.create_unique('askbot_suggestedtag_threads', ['suggestedtag_id', 'thread_id']) + # Adding field 'Tag.status' + db.add_column(u'tag', 'status', + self.gf('django.db.models.fields.SmallIntegerField')(default=1), + keep_default=False) - # Adding M2M table for field users on 'SuggestedTag' - db.create_table('askbot_suggestedtag_users', ( + # Adding M2M table for field suggested_by on 'Tag' + db.create_table(u'tag_suggested_by', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('suggestedtag', models.ForeignKey(orm['askbot.suggestedtag'], null=False)), + ('tag', models.ForeignKey(orm['askbot.tag'], null=False)), ('user', models.ForeignKey(orm['auth.user'], null=False)) )) - db.create_unique('askbot_suggestedtag_users', ['suggestedtag_id', 'user_id']) + db.create_unique(u'tag_suggested_by', ['tag_id', 'user_id']) def backwards(self, orm): - # Deleting model 'SuggestedTag' - db.delete_table('askbot_suggestedtag') + # Deleting field 'Tag.status' + db.delete_column(u'tag', 'status') - # Removing M2M table for field threads on 'SuggestedTag' - db.delete_table('askbot_suggestedtag_threads') - - # Removing M2M table for field users on 'SuggestedTag' - db.delete_table('askbot_suggestedtag_users') + # Removing M2M table for field suggested_by on 'Tag' + db.delete_table('tag_suggested_by') models = { 'askbot.activity': { @@ -236,15 +221,6 @@ class Migration(SchemaMigration): 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) }, - 'askbot.suggestedtag': { - 'Meta': {'ordering': "['-used_count', 'name']", 'object_name': 'SuggestedTag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), - 'thread_ids': ('django.db.models.fields.TextField', [], {}), - 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['askbot.Thread']", 'symmetrical': 'False'}), - 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), - 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) - }, 'askbot.tag': { 'Meta': {'ordering': "('-used_count', 'name')", 'object_name': 'Tag', 'db_table': "u'tag'"}, 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}), @@ -253,6 +229,8 @@ class Migration(SchemaMigration): 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'suggested_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'suggested_tags'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), 'tag_wiki': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'described_tag'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}), 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) }, @@ -354,4 +332,4 @@ class Migration(SchemaMigration): } } - complete_apps = ['askbot']
\ No newline at end of file + complete_apps = ['askbot'] diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 046f19a1..166bf0c8 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -29,7 +29,7 @@ from askbot.skins import utils as skin_utils from askbot.mail import messages from askbot.models.question import QuestionView, AnonymousQuestion from askbot.models.question import FavoriteQuestion -from askbot.models.tag import Tag, MarkedTag, SuggestedTag +from askbot.models.tag import Tag, MarkedTag from askbot.models.tag import get_group_names, get_groups from askbot.models.user import EmailFeedSetting, ActivityAuditStatus, Activity from askbot.models.user import GroupMembership, GroupProfile @@ -39,7 +39,6 @@ from askbot.models import signals from askbot.models.badges import award_badges_signal, get_badge, BadgeData from askbot.models.repute import Award, Repute, Vote from askbot import auth -from askbot.utils import category_tree from askbot.utils.decorators import auto_now_timestamp from askbot.utils.slug import slugify from askbot.utils.html import sanitize_html @@ -344,58 +343,6 @@ def user_can_create_tags(self): else: return True -def user_try_creating_tags(self, tag_names = None, thread = None): - created_tags = list() - if self.can_create_tags(): - suggested_tags = SuggestedTag.objects.filter(name__in = tag_names) - suggested_tags_dict = dict([(tag.name, tag) for tag in suggested_tags]) - for name in tag_names: - #todo: keep better track of who creates the tag - suggested_tag = suggested_tags_dict.get(name, None) - if suggested_tag: - creator = suggested_tag.users.all()[0] - else: - creator = self - - new_tag = Tag.objects.create( - name = name, - created_by = creator, - used_count = 1#wrong, but we are recounting downstream - ) - created_tags.append(new_tag) - #remove added tags from the suggested tag list - suggested_tags.delete() - elif tag_names:#notify admins by email about new tags - #maybe remove tags to report based on categories - #todo: maybe move this to tags_updated signal handler - if askbot_settings.TAG_SOURCE == 'category-tree': - category_names = category_tree.get_leaf_names() - #remove category tree tags from creation list - tag_names = tag_names - category_names - - #here we put tags on the moderation queue - if len(tag_names) > 0: - suggested_tags = SuggestedTag.objects.filter(name__in = tag_names) - - previously_suggested_tag_names = set() - for tag in suggested_tags: - tag.used_count += 1 - tag.threads.add(thread) - tag.users.add(self) - tag.save() - previously_suggested_tag_names.add(tag.name) - - tag_names = set(tag_names) - previously_suggested_tag_names - - SuggestedTag.objects.create( - tag_names = tag_names, - user = self, - thread = thread - ) - - return created_tags - - def user_can_have_strong_url(self): """True if user's homepage url can be followed by the search engine crawlers""" @@ -2664,7 +2611,6 @@ User.add_to_class('set_admin_status', user_set_admin_status) User.add_to_class('edit_group_membership', user_edit_group_membership) User.add_to_class('is_group_member', user_is_group_member) User.add_to_class('remove_admin_status', user_remove_admin_status) -User.add_to_class('try_creating_tags', user_try_creating_tags) User.add_to_class('is_moderator', user_is_moderator) User.add_to_class('is_approved', user_is_approved) User.add_to_class('is_watched', user_is_watched) @@ -3424,7 +3370,6 @@ __all__ = [ 'Vote', 'PostFlagReason', 'MarkedTag', - 'SuggestedTag', 'BadgeData', 'Award', diff --git a/askbot/models/question.py b/askbot/models/question.py index 2a5c557d..e4fb7e56 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -16,6 +16,7 @@ from askbot.conf import settings as askbot_settings from askbot import mail from askbot.mail import messages from askbot.models.tag import Tag, get_groups, get_tags_by_names +from askbot.models.tag import filter_accepted_tags, filter_suggested_tags from askbot.models.tag import delete_tags, separate_unused_tags from askbot.models.base import AnonymousContent, BaseQuerySetManager from askbot.models.tag import Tag, get_groups @@ -811,7 +812,7 @@ class Thread(models.Model): *IMPORTANT*: self._question_post() has to exist when update_tags() is called! """ - previous_tags = list(self.tags.all()) + previous_tags = list(self.tags.filter(status = Tag.STATUS_ACCEPTED)) ordered_updated_tagnames = [t for t in tagnames.strip().split(' ')] @@ -822,7 +823,9 @@ class Thread(models.Model): #remove tags from the question's tags many2many relation #used_count values are decremented on all tags removed_tags = self.remove_tags_by_names(removed_tagnames) + #modified tags go on to recounting their use + #todo - this can actually be done asynchronously - not so important modified_tags, unused_tags = separate_unused_tags(removed_tags) delete_tags(unused_tags)#tags with used_count == 0 are deleted @@ -831,7 +834,6 @@ class Thread(models.Model): #add new tags to the relation added_tagnames = updated_tagnames - previous_tagnames - #todo: the part below is too long and needs to be refactored if added_tagnames: #find reused tags reused_tags, new_tagnames = get_tags_by_names(added_tagnames) @@ -839,17 +841,20 @@ class Thread(models.Model): added_tags = list(reused_tags) #tag moderation is in the call below - created_tags = user.try_creating_tags(new_tagnames, thread = self) + created_tags = Tag.objects.create_in_bulk( + tag_names = new_tagnames, user = user + ) + added_tags.extend(created_tags) #todo: not nice that assignment of added_tags is way above self.tags.add(*added_tags) modified_tags.extend(added_tags) - else: added_tags = Tag.objects.none() #Save denormalized tag names on thread. Preserve order from user input. - added_tagnames = set([tag.name for tag in added_tags]) + accepted_added_tags = filter_accepted_tags(added_tags) + added_tagnames = set([tag.name for tag in accepted_added_tags]) final_tagnames = (previous_tagnames - removed_tagnames) | added_tagnames ordered_final_tagnames = list() for tagname in ordered_updated_tagnames: @@ -859,6 +864,21 @@ class Thread(models.Model): self.tagnames = ' '.join(ordered_final_tagnames) self.save()#need to save here? + #todo: factor out - tell author about suggested tags + suggested_tags = filter_suggested_tags(added_tags) + if len(suggested_tags) > 0: + if len(suggested_tags) == 1: + msg = _( + 'Tag %s is new and will be submitted for the ' + 'moderators approval' + ) % suggested_tags[0].name + else: + msg = _( + 'Tags %s are new and will be submitted for the ' + 'moderators approval' + ) % ', '.join([tag.name for tag in suggested_tags]) + user.message_set.create(message = msg) + #################################################################### self.update_summary_html() # regenerate question/thread summary html #################################################################### diff --git a/askbot/models/tag.py b/askbot/models/tag.py index 54c632e7..50edb801 100644 --- a/askbot/models/tag.py +++ b/askbot/models/tag.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _ from askbot.models.base import BaseQuerySetManager from askbot import const from askbot.conf import settings as askbot_settings +from askbot.utils import category_tree def delete_tags(tags): """deletes tags in the list""" @@ -26,6 +27,26 @@ def get_tags_by_names(tag_names): return tags, new_tag_names +def filter_tags_by_status(tags, status = None): + """returns a list or a query set of tags which are accepted""" + if isinstance(tags, models.query.QuerySet): + return tags.filter(status = status) + else: + return [tag for tag in tags if tag.status == status] + +def filter_accepted_tags(tags): + return filter_tags_by_status(tags, status = Tag.STATUS_ACCEPTED) + +def filter_suggested_tags(tags): + return filter_tags_by_status(tags, status = Tag.STATUS_SUGGESTED) + +def is_preapproved_tag_name(tag_name): + """true if tag name is in the category tree + or any other container of preapproved tags""" + #get list of preapproved tags, to make exceptions for + if askbot_settings.TAG_SOURCE == 'category-tree': + return tag_name in category_tree.get_leaf_names() + return False def separate_unused_tags(tags): """returns two lists:: @@ -144,6 +165,94 @@ class TagManager(BaseQuerySetManager): def get_query_set(self): return TagQuerySet(self.model) + def create(self, name = None, created_by = None, **kwargs): + """Creates a new tag""" + if created_by.can_create_tags() or is_preapproved_tag_name(name): + status = Tag.STATUS_ACCEPTED + else: + status = Tag.STATUS_SUGGESTED + + kwargs['created_by'] = created_by + kwargs['name'] = name + kwargs['status'] = status + + return super(TagManager, self).create(**kwargs) + + def create_suggested_tag(self, tag_names = None, user = None): + """This function is not used, and will probably need + to be retired. In the previous version we were sending + email to admins when the new tags were created, + now we have a separate page where new tags are listed. + """ + #todo: stuff below will probably go after + #tag moderation actions are implemented + from askbot import mail + from askbot.mail import messages + body_text = messages.notify_admins_about_new_tags( + tags = tag_names, + user = user, + thread = self + ) + site_name = askbot_settings.APP_SHORT_NAME + subject_line = _('New tags added to %s') % site_name + mail.mail_moderators( + subject_line, + body_text, + headers = {'Reply-To': user.email} + ) + + msg = _( + 'Tags %s are new and will be submitted for the ' + 'moderators approval' + ) % ', '.join(tag_names) + user.message_set.create(message = msg) + + def create_in_bulk(self, tag_names = None, user = None): + """creates tags by names. If user can create tags, + then they are set status ``STATUS_ACCEPTED``, + otherwise the status will be set to ``STATUS_SUGGESTED``. + + One exception: if suggested tag is in the category tree + and source of tags is category tree - then status of newly + created tag is ``STATUS_ACCEPTED`` + """ + + #load suggested tags + pre_suggested_tags = self.filter( + name__in = tag_names, status = Tag.STATUS_SUGGESTED + ) + + #deal with suggested tags + if user.can_create_tags(): + #turn previously suggested tags into accepted + pre_suggested_tags.update(status = Tag.STATUS_ACCEPTED) + else: + #increment use count and add user to "suggested_by" + for tag in pre_suggested_tags: + tag.times_used += 1 + tag.suggested_by.add(user) + tag.save() + + created_tags = list() + pre_suggested_tag_names = list() + for tag in pre_suggested_tags: + pre_suggested_tag_names.append(tag.name) + created_tags.append(tag) + + for tag_name in set(tag_names) - set(pre_suggested_tag_names): + + #status for the new tags is automatically set within the create() + new_tag = Tag.objects.create(name = tag_name, created_by = user) + created_tags.append(new_tag) + + if new_tag.status == Tag.STATUS_SUGGESTED: + new_tag.suggested_by.add(user) + + #todo: here we have a chance to send a signal and notify + #whoever wants about the new tag creation + + return created_tags + class GroupTagQuerySet(TagQuerySet): """Custom query set for the group""" @@ -192,8 +301,20 @@ class GroupTagManager(BaseQuerySetManager): return tag class Tag(models.Model): - name = models.CharField(max_length=255, unique=True) - created_by = models.ForeignKey(User, related_name='created_tags') + #a couple of status constants + STATUS_SUGGESTED = 0 + STATUS_ACCEPTED = 1 + + name = models.CharField(max_length=255, unique=True) + created_by = models.ForeignKey(User, related_name='created_tags') + + suggested_by = models.ManyToManyField( + User, related_name='suggested_tags', + help_text = 'Works only for suggested tags for tag moderation' + ) + + status = models.SmallIntegerField(default = STATUS_ACCEPTED) + # Denormalised data used_count = models.PositiveIntegerField(default=0) @@ -237,67 +358,3 @@ def get_groups(): def get_group_names(): #todo: cache me return get_groups().values_list('name', flat = True) - -class SuggestedTagManager(models.Manager): - def create(self, tag_names = None, user = None, thread = None): - """creates ``SuggestedTag`` records and saves them - in the database""" - suggested_tags = list() - for tag_name in tag_names: - #create new record - suggested_tag = SuggestedTag(name = tag_name) - suggested_tag.save() - #add user and thread - suggested_tag.users.add(user) - suggested_tag.threads.add(thread) - #add to the list that is to be returned - suggested_tags.append(suggested_tag) - - #todo: stuff below will probably go after - #tag moderation actions are implemented - from askbot import mail - from askbot.mail import messages - body_text = messages.notify_admins_about_new_tags( - tags = tag_names, - user = user, - thread = thread - ) - site_name = askbot_settings.APP_SHORT_NAME - subject_line = _('New tags added to %s') % site_name - mail.mail_moderators( - subject_line, - body_text, - headers = {'Reply-To': user.email} - ) - - msg = _( - 'Tags %s are new and will be submitted for the ' - 'moderators approval' - ) % ', '.join(tag_names) - user.message_set.create(message = msg) - - return suggested_tags - -class SuggestedTag(models.Model): - """Suggested tag knows about who suggested it - and in what thread""" - name = models.CharField( - max_length=255, unique=True, help_text = 'Name for the proposed tag' - ) - used_count = models.PositiveIntegerField(default = 1) - #todo: instead these can be associated with revisions - #but the problem is that there would be too many joins - #to pull out threads - #if we used revisions, then we would not need to have - #a separate "user" field - threads = models.ManyToManyField('Thread') - users = models.ManyToManyField(User) - thread_ids = models.TextField( - help_text = 'comma-separated list of thread ids' - ) - - objects = SuggestedTagManager() - - class Meta: - app_label = 'askbot' - ordering = ['-used_count', 'name'] diff --git a/askbot/skins/default/templates/moderate_tags.html b/askbot/skins/default/templates/moderate_tags.html index fbf43d5e..5cd95a87 100644 --- a/askbot/skins/default/templates/moderate_tags.html +++ b/askbot/skins/default/templates/moderate_tags.html @@ -22,7 +22,7 @@ <span class="tag-number">×{{ tag.used_count }}</span> </td> <td class="users-col"> - {% for user in tag.users.all() %} + {% for user in tag.suggested_by.all() %} <p>{{ user.get_profile_link() }}</p> {% endfor %} </td> diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 0f59c122..19684179 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -493,7 +493,8 @@ def get_tag_list(request): function """ tag_names = models.Tag.objects.filter( - deleted = False + deleted = False, + status = models.Tag.STATUS_ACCEPTED ).values_list( 'name', flat = True ) diff --git a/askbot/views/meta.py b/askbot/views/meta.py index fd7cb3e2..418c9cd2 100644 --- a/askbot/views/meta.py +++ b/askbot/views/meta.py @@ -17,7 +17,7 @@ from askbot import skins from askbot.conf import settings as askbot_settings from askbot.forms import FeedbackForm from askbot.mail import mail_moderators -from askbot.models import BadgeData, Award, User, SuggestedTag +from askbot.models import BadgeData, Award, User, Tag from askbot.models import badges as badge_data from askbot.skins.loaders import get_template, render_into_skin, render_text_into_skin from askbot.utils.decorators import admins_only @@ -164,9 +164,10 @@ def moderate_tags(request): or cancel the moderation reuest.""" if askbot_settings.ENABLE_TAG_MODERATION == False: raise Http404 - tags = SuggestedTag.objects.all() + tags = Tag.objects.filter(status = Tag.STATUS_SUGGESTED) + tags = tags.order_by('-used_count', 'name') #paginate moderated tags - paginator = Paginator(SuggestedTag.objects.all(), 20) + paginator = Paginator(tags, 20) page_no = request.GET.get('page', '1') |