diff options
-rw-r--r-- | askbot/admin.py | 5 | ||||
-rw-r--r-- | askbot/forms.py | 15 | ||||
-rw-r--r-- | askbot/media/js/tag_selector.js | 7 | ||||
-rw-r--r-- | askbot/media/style/style.css | 75 | ||||
-rw-r--r-- | askbot/media/style/style.less | 28 | ||||
-rw-r--r-- | askbot/migrations/0159_add_model_BulkTagSubscription.py | 424 | ||||
-rw-r--r-- | askbot/models/__init__.py | 16 | ||||
-rw-r--r-- | askbot/models/question.py | 12 | ||||
-rw-r--r-- | askbot/models/user.py | 70 | ||||
-rw-r--r-- | askbot/templates/tags/form_bulk_tag_subscription.html | 21 | ||||
-rw-r--r-- | askbot/templates/tags/list_bulk_tag_subscription.html | 51 | ||||
-rw-r--r-- | askbot/templates/user_profile/user_email_subscriptions.html | 28 | ||||
-rw-r--r-- | askbot/tests/db_api_tests.py | 4 | ||||
-rw-r--r-- | askbot/tests/user_model_tests.py | 22 | ||||
-rw-r--r-- | askbot/urls.py | 21 | ||||
-rw-r--r-- | askbot/views/commands.py | 110 | ||||
-rw-r--r-- | askbot/views/users.py | 1 |
17 files changed, 882 insertions, 28 deletions
diff --git a/askbot/admin.py b/askbot/admin.py index 8e97a89d..85c0d75c 100644 --- a/askbot/admin.py +++ b/askbot/admin.py @@ -4,7 +4,7 @@ To make more models accessible in the Django admin interface, add more classes subclassing ``django.contrib.admin.Model`` -Names of the classes must be like `SomeModelAdmin`, where `SomeModel` must +Names of the classes must be like `SomeModelAdmin`, where `SomeModel` must exactly match name of the model used in the project """ from django.contrib import admin @@ -33,7 +33,7 @@ class ReputeAdmin(admin.ModelAdmin): class ActivityAdmin(admin.ModelAdmin): """ admin class""" - + admin.site.register(models.Post) admin.site.register(models.Tag, TagAdmin) admin.site.register(models.Vote, VoteAdmin) @@ -42,3 +42,4 @@ admin.site.register(models.PostRevision, PostRevisionAdmin) admin.site.register(models.Award, AwardAdmin) admin.site.register(models.Repute, ReputeAdmin) admin.site.register(models.Activity, ActivityAdmin) +admin.site.register(models.BulkTagSubscription) diff --git a/askbot/forms.py b/askbot/forms.py index 5168e354..dc0057fc 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -388,8 +388,8 @@ class TagNamesField(forms.CharField): 'We ran out of space for recording the tags. ' 'Please shorten or delete some of them.' ) - self.label = _('tags') - self.help_text = ungettext_lazy( + self.label = kwargs.get('label') or _('tags') + self.help_text = kwargs.get('help_text') or ungettext_lazy( 'Tags are short keywords, with no spaces within. ' 'Up to %(max_tags)d tag can be used.', 'Tags are short keywords, with no spaces within. ' @@ -1652,3 +1652,14 @@ class ModerateTagForm(forms.Form): class ShareQuestionForm(forms.Form): thread_id = forms.IntegerField() recipient_name = forms.CharField() + +class BulkTagSubscriptionForm(forms.Form): + date_added = forms.DateField(required=False, widget=forms.HiddenInput()) + tags = TagNamesField(label=_("Tags"), help_text=' ') + + def __init__(self, *args, **kwargs): + from askbot.models import BulkTagSubscription, Tag, Group + super(BulkTagSubscriptionForm, self).__init__(*args, **kwargs) + self.fields['users'] = forms.ModelMultipleChoiceField(queryset=User.objects.all()) + if askbot_settings.GROUPS_ENABLED: + self.fields['groups'] = forms.ModelMultipleChoiceField(queryset=Group.objects.exclude_personal()) diff --git a/askbot/media/js/tag_selector.js b/askbot/media/js/tag_selector.js index f6e698f3..c1ccd691 100644 --- a/askbot/media/js/tag_selector.js +++ b/askbot/media/js/tag_selector.js @@ -123,7 +123,8 @@ function pickedTags(){ var data = JSON.stringify({ tagnames: tagnames, - reason: reason + reason: reason, + user: askbot['data']['viewUserId'] }); var call_settings = { type:'POST', @@ -150,7 +151,9 @@ function pickedTags(){ 'remove', function(){ deleteTagLocally(); - askbot['controllers']['fullTextSearch'].refresh(); + if ($('body').hasClass('main-page')) { + askbot['controllers']['fullTextSearch'].refresh(); + } } ); } diff --git a/askbot/media/style/style.css b/askbot/media/style/style.css index 3ca80614..b4a41402 100644 --- a/askbot/media/style/style.css +++ b/askbot/media/style/style.css @@ -3046,6 +3046,39 @@ a:hover.medal { padding: 10px 0px 10px 0px; font-family: 'Open Sans Condensed', Arial, sans-serif; } +.user-profile-page .inputs { + margin-top: 10px; + margin-bottom: 10px; +} +.user-profile-page .inputs input[type='submit'] { + height: 26px; + font-size: 15px; + text-align: center; + text-decoration: none; + cursor: pointer; + color: #4a757f; + font-family: 'Open Sans Condensed', Arial, sans-serif; + text-shadow: 0px 1px 0px #c6d9dd; + -moz-text-shadow: 0px 1px 0px #c6d9dd; + -webkit-text-shadow: 0px 1px 0px #c6d9dd; + border-top: #eaf2f3 1px solid; + background-color: #d1e2e5; + background-repeat: no-repeat; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7)); + background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7); + background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7); + background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7); + background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7); + background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7); + border-radius: 4px; + -ms-border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + -webkit-box-shadow: 1px 1px 2px #636363; + -moz-box-shadow: 1px 1px 2px #636363; + box-shadow: 1px 1px 2px #636363; +} .user-details { font-size: 13px; } @@ -4401,3 +4434,45 @@ textarea.tipped-input { width: 100%; } } +.tag-subscriptions { + border-spacing: 10px; + border-collapse: separate; +} +.tag-subscriptions button { + height: 27px; + font-size: 14px; + text-align: center; + text-decoration: none; + cursor: pointer; + color: #4a757f; + font-family: 'Open Sans Condensed', Arial, sans-serif; + text-shadow: 0px 1px 0px #c6d9dd; + -moz-text-shadow: 0px 1px 0px #c6d9dd; + -webkit-text-shadow: 0px 1px 0px #c6d9dd; + border-top: #eaf2f3 1px solid; + background-color: #d1e2e5; + background-repeat: no-repeat; + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7)); + background-image: -webkit-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7); + background-image: -moz-linear-gradient(top, #d1e2e5, #d1e2e5 25%, #a9c2c7); + background-image: -ms-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7); + background-image: -o-linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7); + background-image: linear-gradient(#d1e2e5, #d1e2e5 25%, #a9c2c7); + border-radius: 4px; + -ms-border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + -webkit-box-shadow: 1px 1px 2px #636363; + -moz-box-shadow: 1px 1px 2px #636363; + box-shadow: 1px 1px 2px #636363; +} +.tag-subscriptions form { + display: inline-block; +} +.tag-subscriptions .action { + cursor: pointer; + color: #4A757F; + font-family: 'Open Sans Condensed', Arial, sans-serif; + text-decoration: none; +} diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less index 76a5c0cf..93902c1a 100644 --- a/askbot/media/style/style.less +++ b/askbot/media/style/style.less @@ -2843,6 +2843,14 @@ a:hover.medal { padding:10px 0px 10px 0px; font-family:@main-font; } + + .inputs{ + margin-top: 10px; + margin-bottom: 10px; + input[type='submit']{ + .button-style(26px,15px); + } + } } .user-details { @@ -4286,3 +4294,23 @@ textarea.tipped-input { width: 100%; } } + +.tag-subscriptions{ + border-spacing: 10px; + border-collapse: separate; + + button{ + .button-style(27px, 14px); + } + + form{ + display:inline-block; + } + + .action{ + cursor: pointer; + color: #4A757F; + font-family: 'Open Sans Condensed', Arial, sans-serif; + text-decoration:none; + } +} diff --git a/askbot/migrations/0159_add_model_BulkTagSubscription.py b/askbot/migrations/0159_add_model_BulkTagSubscription.py new file mode 100644 index 00000000..11d78108 --- /dev/null +++ b/askbot/migrations/0159_add_model_BulkTagSubscription.py @@ -0,0 +1,424 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'BulkTagSubscription' + db.create_table('askbot_bulktagsubscription', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('date_added', self.gf('django.db.models.fields.DateField')(auto_now_add=True, blank=True)), + )) + db.send_create_signal('askbot', ['BulkTagSubscription']) + + # Adding M2M table for field tags on 'BulkTagSubscription' + db.create_table('askbot_bulktagsubscription_tags', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('bulktagsubscription', models.ForeignKey(orm['askbot.bulktagsubscription'], null=False)), + ('tag', models.ForeignKey(orm['askbot.tag'], null=False)) + )) + db.create_unique('askbot_bulktagsubscription_tags', ['bulktagsubscription_id', 'tag_id']) + + # Adding M2M table for field users on 'BulkTagSubscription' + db.create_table('askbot_bulktagsubscription_users', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('bulktagsubscription', models.ForeignKey(orm['askbot.bulktagsubscription'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('askbot_bulktagsubscription_users', ['bulktagsubscription_id', 'user_id']) + + # Adding M2M table for field groups on 'BulkTagSubscription' + db.create_table('askbot_bulktagsubscription_groups', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('bulktagsubscription', models.ForeignKey(orm['askbot.bulktagsubscription'], null=False)), + ('group', models.ForeignKey(orm['askbot.group'], null=False)) + )) + db.create_unique('askbot_bulktagsubscription_groups', ['bulktagsubscription_id', 'group_id']) + + + def backwards(self, orm): + # Deleting model 'BulkTagSubscription' + db.delete_table('askbot_bulktagsubscription') + + # Removing M2M table for field tags on 'BulkTagSubscription' + db.delete_table('askbot_bulktagsubscription_tags') + + # Removing M2M table for field users on 'BulkTagSubscription' + db.delete_table('askbot_bulktagsubscription_users') + + # Removing M2M table for field groups on 'BulkTagSubscription' + db.delete_table('askbot_bulktagsubscription_groups') + + + models = { + 'askbot.activity': { + 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"}, + 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True'}), + 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'incoming_activity'", 'symmetrical': 'False', 'through': "orm['askbot.ActivityAuditStatus']", 'to': "orm['auth.User']"}), + 'summary': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.activityauditstatus': { + 'Meta': {'unique_together': "(('user', 'activity'),)", 'object_name': 'ActivityAuditStatus'}, + 'activity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Activity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.anonymousanswer': { + 'Meta': {'object_name': 'AnonymousAnswer'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_answers'", 'to': "orm['askbot.Post']"}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.anonymousquestion': { + 'Meta': {'object_name': 'AnonymousQuestion'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.askwidget': { + 'Meta': {'object_name': 'AskWidget'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'include_text_field': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'inner_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'outer_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'askbot.award': { + 'Meta': {'object_name': 'Award', 'db_table': "u'award'"}, + 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"}) + }, + 'askbot.badgedata': { + 'Meta': {'ordering': "('slug',)", 'object_name': 'BadgeData'}, + 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "orm['askbot.Award']", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) + }, + 'askbot.bulktagsubscription': { + 'Meta': {'object_name': 'BulkTagSubscription'}, + 'date_added': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['askbot.Group']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['askbot.Tag']", 'symmetrical': 'False'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) + }, + 'askbot.draftanswer': { + 'Meta': {'object_name': 'DraftAnswer'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['askbot.Thread']"}) + }, + 'askbot.draftquestion': { + 'Meta': {'object_name': 'DraftQuestion'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125', 'null': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'unique_together': "(('subscriber', 'feed_type'),)", 'object_name': 'EmailFeedSetting'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"}) + }, + 'askbot.favoritequestion': { + 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + 'askbot.group': { + 'Meta': {'object_name': 'Group', '_ormbases': ['auth.Group']}, + 'description': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'described_group'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}), + 'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}), + 'is_vip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}), + 'moderate_answers_to_enquirers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'openness': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'preapproved_email_domains': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'preapproved_emails': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}) + }, + 'askbot.groupmembership': { + 'Meta': {'object_name': 'GroupMembership', '_ormbases': ['auth.AuthUserGroups']}, + 'authusergroups_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.AuthUserGroups']", 'unique': 'True', 'primary_key': 'True'}), + 'level': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}) + }, + 'askbot.markedtag': { + 'Meta': {'object_name': 'MarkedTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"}) + }, + 'askbot.post': { + 'Meta': {'object_name': 'Post'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_posts'", 'symmetrical': 'False', 'through': "orm['askbot.PostToGroup']", 'to': "orm['askbot.Group']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'points': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_column': "'score'"}), + 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'summary': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'posts'", 'null': 'True', 'blank': 'True', 'to': "orm['askbot.Thread']"}), + 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'askbot.postflagreason': { + 'Meta': {'object_name': 'PostFlagReason'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'details': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_reject_reasons'", 'to': "orm['askbot.Post']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'askbot.postrevision': { + 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'}, + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'approved_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'approved_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}), + 'by_email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisions'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '125', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '300', 'blank': 'True'}) + }, + 'askbot.posttogroup': { + 'Meta': {'unique_together': "(('post', 'group'),)", 'object_name': 'PostToGroup', 'db_table': "'askbot_post_groups'"}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']"}) + }, + 'askbot.questionview': { + 'Meta': {'object_name': 'QuestionView'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Post']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"}) + }, + 'askbot.questionwidget': { + 'Meta': {'object_name': 'QuestionWidget'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order_by': ('django.db.models.fields.CharField', [], {'default': "'-added_at'", 'max_length': '18'}), + 'question_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '7'}), + 'search_query': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'style': ('django.db.models.fields.TextField', [], {'default': '"\\n@import url(\'http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700\');\\nbody {\\n overflow: hidden;\\n}\\n\\n#container {\\n width: 200px;\\n height: 350px;\\n}\\nul {\\n list-style: none;\\n padding: 5px;\\n margin: 5px;\\n}\\nli {\\n border-bottom: #CCC 1px solid;\\n padding-bottom: 5px;\\n padding-top: 5px;\\n}\\nli:last-child {\\n border: none;\\n}\\na {\\n text-decoration: none;\\n color: #464646;\\n font-family: \'Yanone Kaffeesatz\', sans-serif;\\n font-size: 15px;\\n}\\n"', 'blank': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'askbot.replyaddress': { + 'Meta': {'object_name': 'ReplyAddress'}, + 'address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '25'}), + 'allowed_from_email': ('django.db.models.fields.EmailField', [], {'max_length': '150'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reply_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'reply_action': ('django.db.models.fields.CharField', [], {'default': "'auto_answer_or_comment'", 'max_length': '32'}), + 'response_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.repute': { + 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + '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']"}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + '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'}) + }, + 'askbot.thread': { + 'Meta': {'object_name': 'Thread'}, + 'accepted_answer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'unused_favorite_threads'", 'symmetrical': 'False', 'through': "orm['askbot.FavoriteQuestion']", 'to': "orm['auth.User']"}), + 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_threads'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_threads'", 'symmetrical': 'False', 'through': "orm['askbot.ThreadToGroup']", 'to': "orm['askbot.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'unused_last_active_in_threads'", 'to': "orm['auth.User']"}), + 'points': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_column': "'score'"}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.threadtogroup': { + 'Meta': {'unique_together': "(('thread', 'group'),)", 'object_name': 'ThreadToGroup', 'db_table': "'askbot_thread_groups'"}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}), + 'visibility': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('user', 'voted_post'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}), + 'vote': ('django.db.models.fields.SmallIntegerField', [], {}), + 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'voted_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['askbot.Post']"}) + }, + 'auth.authusergroups': { + 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'AuthUserGroups', 'db_table': "'auth_user_groups'", 'managed': 'False'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_fake': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 7838cab3..acc59568 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -47,6 +47,7 @@ from askbot.models.tag import format_personal_group_name from askbot.models.user import EmailFeedSetting, ActivityAuditStatus, Activity from askbot.models.user import GroupMembership from askbot.models.user import Group +from askbot.models.user import BulkTagSubscription from askbot.models.post import Post, PostRevision from askbot.models.post import PostFlagReason, AnonymousAnswer from askbot.models.post import PostToGroup @@ -3544,6 +3545,20 @@ def add_missing_subscriptions(sender, instance, created, **kwargs): if created: instance.add_missing_askbot_subscriptions() +def add_missing_tag_subscriptions(sender, instance, created, **kwargs): + '''``sender` is instance of `User``. When the user is created + it add the tag subscriptions to the user via BulkTagSubscription + and MarkedTags. + ''' + if created: + if askbot_settings.SUBSCRIBED_TAG_SELECTOR_ENABLED and \ + askbot_settings.GROUPS_ENABLED: + user_groups = instance.get_groups() + for subscription in BulkTagSubscription.objects.filter(groups__in = user_groups): + tag_list = subscription.tag_list() + instance.mark_tags(tagnames = tag_list, + reason='subscribed', action='add') + def post_anonymous_askbot_content( sender, request, @@ -3596,6 +3611,7 @@ django_signals.pre_save.connect(calculate_gravatar_hash, sender=User) django_signals.post_save.connect(add_missing_subscriptions, sender=User) django_signals.post_save.connect(add_user_to_global_group, sender=User) django_signals.post_save.connect(add_user_to_personal_group, sender=User) +django_signals.post_save.connect(add_missing_tag_subscriptions, sender=User) django_signals.post_save.connect(record_award_event, sender=Award) django_signals.post_save.connect(notify_award_message, sender=Award) django_signals.post_save.connect(record_answer_accepted, sender=Post) diff --git a/askbot/models/question.py b/askbot/models/question.py index f1fb9a0f..e0521f69 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -22,7 +22,7 @@ from askbot.mail import messages from askbot.models.tag import Tag from askbot.models.tag import 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.tag import separate_unused_tags from askbot.models.base import DraftContent, BaseQuerySetManager from askbot.models.post import Post, PostRevision from askbot.models.post import PostToGroup @@ -1164,8 +1164,6 @@ class Thread(models.Model): """ Updates Tag associations for a thread to match the given tagname string. - When tags are removed and their use count hits 0 - the tag is - automatically deleted. When an added tag does not exist - it is created If tag moderation is on - new tags are placed on the queue @@ -1217,10 +1215,6 @@ class Thread(models.Model): else: added_tags = Tag.objects.none() - #this is odd: in sqlite you have to delete after creating new tags - #somehow sqlite does not continue the id sequence, like postgrjs&mysql do - delete_tags(unused_tags)#tags with used_count == 0 are deleted - #Save denormalized tag names on thread. Preserve order from user input. accepted_added_tags = filter_accepted_tags(added_tags) added_tagnames = set([tag.name for tag in accepted_added_tags]) @@ -1255,7 +1249,7 @@ class Thread(models.Model): self.update_summary_html() # regenerate question/thread summary html #################################################################### #if there are any modified tags, update their use counts - modified_tags = set(modified_tags) - set(unused_tags) + modified_tags = set(modified_tags) if modified_tags: Tag.objects.update_use_counts(modified_tags) signals.tags_updated.send(None, @@ -1381,7 +1375,7 @@ class Thread(models.Model): #because we do not include any visitor-related info in the cache key #ideally cache should be shareable between users, so straight up #using the user id for cache is wrong, we could use group - #memberships, but in that case we'd need to be more careful with + #memberships, but in that case we'd need to be more careful with #cache invalidation context = { 'thread': self, diff --git a/askbot/models/user.py b/askbot/models/user.py index 6601fc7e..5fb5b991 100644 --- a/askbot/models/user.py +++ b/askbot/models/user.py @@ -18,6 +18,7 @@ from askbot.utils import functions from askbot.models.base import BaseQuerySetManager from askbot.models.tag import Tag from askbot.models.tag import clean_group_name#todo - delete this +from askbot.models.tag import get_tags_by_names from askbot.forms import DomainNameField from askbot.utils.forms import email_is_allowed @@ -56,7 +57,7 @@ class ActivityManager(models.Manager): mentioned_at = None, mentioned_in = None, reported = None - ): + ): #todo: automate this using python inspect module kwargs = dict() @@ -95,7 +96,7 @@ class ActivityManager(models.Manager): return mention_activity def get_mentions( - self, + self, mentioned_by = None, mentioned_whom = None, mentioned_at = None, @@ -317,8 +318,8 @@ class EmailFeedSetting(models.Model): else: reported_at = '%s' % self.reported_at.strftime('%d/%m/%y %H:%M') return 'Email feed for %s type=%s, frequency=%s, reported_at=%s' % ( - self.subscriber, - self.feed_type, + self.subscriber, + self.feed_type, self.frequency, reported_at ) @@ -425,7 +426,7 @@ class GroupQuerySet(models.query.QuerySet): class GroupManager(BaseQuerySetManager): """model manager for askbot groups""" - + def get_query_set(self): return GroupQuerySet(self.model) @@ -592,3 +593,62 @@ class Group(AuthGroup): def save(self, *args, **kwargs): self.clean() super(Group, self).save(*args, **kwargs) + +class BulkTagSubscriptionManager(BaseQuerySetManager): + + def create(self, tag_names=[], + user_list=[], group_list=[], + tag_author=None, **kwargs): + + new_object = super(BulkTagSubscriptionManager, self).create(**kwargs) + tag_name_list = [] + + if tag_names: + tags, new_tag_names = get_tags_by_names(tag_names) + if new_tag_names: + assert(tag_author) + + tags_id_list= [tag.id for tag in tags] + tag_name_list = [tag.name for tag in tags] + + for tag_name in new_tag_names: + new_tag = Tag.objects.create(name=tagname, created_by=tag_author) + tags_id_list.append(new_tag.id) + tag_name_list.append(new_tag.name) + + new_object.tags.add(*tags_id_list) + + if user_list: + user_ids = [] + for user in user_list: + user_ids.append(user.id) + user.mark_tags(tagnames=tag_name_list, + reason='subscribed', + action='add') + + new_object.users.add(*user_ids) + + if group_list: + group_ids = [] + for group in group_list: + #TODO: do the group marked tag thing here + group_ids.append(group.id) + new_object.groups.add(*group_ids) + + return new_object + + +class BulkTagSubscription(models.Model): + date_added = models.DateField(auto_now_add=True) + tags = models.ManyToManyField(Tag) + users = models.ManyToManyField(User) + groups = models.ManyToManyField(Group) + + objects = BulkTagSubscriptionManager() + + def tag_list(self): + return [tag.name for tag in self.tags.all()] + + class Meta: + app_label = 'askbot' + ordering = ['-date_added'] diff --git a/askbot/templates/tags/form_bulk_tag_subscription.html b/askbot/templates/tags/form_bulk_tag_subscription.html new file mode 100644 index 00000000..d588cfaf --- /dev/null +++ b/askbot/templates/tags/form_bulk_tag_subscription.html @@ -0,0 +1,21 @@ +{% extends "one_column_body.html" %} +{% import "macros.html" as macros %} +<!-- template form_bulk_tag_subscription.html --> +{% block title %}{% spaceless %}{{action}} {% trans %}Tag subscriptions{% endtrans %}{% endspaceless %}{% endblock %} +{% block content %} +<h1 class="section-title">{{action}} {% trans %}Tag Subscriptions{% endtrans %}</h1> +<p> +</p> +<form action="." method="POST" accept-charset="utf-8"> +<table border="0"> +{{form.as_table()}} +<tr><td colspan='2' style='text-align: right;'><input type="submit" class="submit" value="Save"></td></tr> +</table> +</form> +{% endblock %} +{%block endjs%} + <script type="text/javascript" charset="utf-8"> + {{macros.tag_autocomplete_js()}} + </script> +{%endblock%} +<!-- end template form_bulk_tag_subscription.html --> diff --git a/askbot/templates/tags/list_bulk_tag_subscription.html b/askbot/templates/tags/list_bulk_tag_subscription.html new file mode 100644 index 00000000..d75323a9 --- /dev/null +++ b/askbot/templates/tags/list_bulk_tag_subscription.html @@ -0,0 +1,51 @@ +{% extends "one_column_body.html" %} +{% import "macros.html" as macros %} +<!-- template list_bulk_tag_subscription.html --> +{% block title %}{% spaceless %}{% trans %}Manage Tag subscriptions{% endtrans %}{% endspaceless %}{% endblock %} +{% block content %} + <h1 class="section-title">{% trans %}Manage Tag subscription</a> {% endtrans %}(<a href="{% url create_bulk_tag_subscription %}">{%trans%}Create New{%endtrans%}</a>)</h1> +<p> +</p> +<table class='tag-subscriptions'> + <thead> + <th>{% trans %}Date{%endtrans%} </th> + <th>{% trans %}Tags{%endtrans%} </th> + <th>{% trans %}Users{%endtrans%}</th> + {% if settings.GROUPS_ENABLED %} + <th>{%trans%}Groups{%endtrans%} </th> + {%endif%} + <th>{%trans%}Action{%endtrans%}</th> + </thead> + <tbody> + {% for subscription in object_list %} + <tr> + <td>{{subscription.date_added}}</td> + <td> + {{ + macros.tag_list_widget( + subscription.tag_list(), + deletable=False, + css_class='subscribed marked-tags' + ) + }} + </td> + <td>{%for user in subscription.users.all() %} <a href='{% url user_profile user.id, user.username|slugify %}'>{{user.username}},</a>{%endfor%} <a href='#'> </td> + {% if settings.GROUPS_ENABLED %} + <td>{%for group in subscription.groups.all() %} <a href='{% url users_by_group group.id, group.name|slugify %}'>{{group.name}}</a>{%endfor%} </td> + {%endif%} + <td> + <button type="button"><a class='action' href='{% url edit_bulk_tag_subscription subscription.id %}'>{%trans%}Edit{%endtrans%}</a></button> + <form action="{%url delete_bulk_tag_subscription %}" method="POST" accept-charset="utf-8"> + <input type="hidden" name="pk" value="{{subscription.id}}" /> + <button type="submit">Delete</button> + </form> + </td> + </tr> + {%endfor%} + </tbody> +</table> +{% endblock %} +{#block endjs%} + <script type='text/javascript' src='{{"/js/tag_selector.js"|media}}'></script> +{%endblock#} +<!-- end template list_bulk_tag_subscription.html --> diff --git a/askbot/templates/user_profile/user_email_subscriptions.html b/askbot/templates/user_profile/user_email_subscriptions.html index f44e8a1e..16ab2208 100644 --- a/askbot/templates/user_profile/user_email_subscriptions.html +++ b/askbot/templates/user_profile/user_email_subscriptions.html @@ -1,4 +1,5 @@ {% extends "user_profile/user.html" %} +{% import "macros.html" as macros %} <!-- user_email_subscriptions.html --> {% block profilesection %} {% trans %}subscriptions{% endtrans %} @@ -24,5 +25,32 @@ </div> </form> </div> + + {%if settings.SUBSCRIBED_TAG_SELECTOR_ENABLED %} + <h2>{% trans %}Subscribed Tags{% endtrans %}</h2> + {{ + macros.tag_list_widget( + subscribed_tag_names, + deletable = True, + css_class = 'subscribed marked-tags special', + ) + }} + <br/> + <div class="inputs"> + <input id="subscribedTagInput" autocomplete="off" type="text"/> + <input id="subscribedTagAdd" type="submit" value="{% trans %}add{% endtrans%}"/> + </div> + </div> + {%endif%} +{% endblock %} +{%block userjs%} + <script type='text/javascript'> + search = new FullTextSearch(); + askbot['controllers'] = askbot['controllers'] || {} + askbot['controllers']['fullTextSearch'] = search; + askbot['urls']['mark_subscribed_tag'] = '{% url mark_subscribed_tag %}'; + askbot['urls']['unmark_tag'] = '{% url unmark_tag %}'; + </script> + <script type='text/javascript' src='{{"/js/tag_selector.js"|media}}'></script> {% endblock %} <!-- end user_email_subscriptions.html --> diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py index 0af6d955..91c25867 100644 --- a/askbot/tests/db_api_tests.py +++ b/askbot/tests/db_api_tests.py @@ -184,7 +184,7 @@ class DBApiTests(AskbotTestCase): saved_question = models.Post.objects.get_questions().get(id = self.question.id) self.assertTrue(saved_question.thread.answer_count == 1) - def test_unused_tag_is_auto_deleted(self): + def test_unused_tag_is_not_auto_deleted(self): self.user.retag_question(self.question, tags='one-tag') tag = models.Tag.objects.get(name='one-tag') self.assertEquals(tag.used_count, 1) @@ -192,7 +192,7 @@ class DBApiTests(AskbotTestCase): self.user.retag_question(self.question, tags='two-tag') count = models.Tag.objects.filter(name='one-tag').count() - self.assertEquals(count, 0) + self.assertEquals(count, 1) @with_settings(MAX_TAG_LENGTH=200, MAX_TAGS_PER_POST=50) def test_retag_tags_too_long_raises(self): diff --git a/askbot/tests/user_model_tests.py b/askbot/tests/user_model_tests.py index e46cdb77..42c040b0 100644 --- a/askbot/tests/user_model_tests.py +++ b/askbot/tests/user_model_tests.py @@ -1,6 +1,7 @@ from askbot.tests.utils import AskbotTestCase from django.contrib.auth.models import User from askbot import models +from askbot.conf import settings from askbot.models.tag import format_personal_group_name class UserModelTests(AskbotTestCase): @@ -16,6 +17,27 @@ class UserModelTests(AskbotTestCase): ) self.assertEqual(memberships.count(), 1) + def test_new_user_has_subscriptions(self): + old_value = settings.SUBSCRIBED_TAG_SELECTOR_ENABLED + old_group_value = settings.GROUPS_ENABLED + settings.SUBSCRIBED_TAG_SELECTOR_ENABLED = True + settings.GROUPS_ENABLED = True + one_tag = self.create_tag('one-tag') + another_tag = self.create_tag('another_tag') + + global_group = models.Group.objects.get_global_group() + + bulk_subscription = models.BulkTagSubscription.objects.create( + tag_names=[one_tag.name, another_tag.name], + group_list = [global_group] + ) + user = User.objects.create_user('someone', 'someone@example.com') + marked_tags = user.get_marked_tags('subscribed') + self.assertTrue(one_tag in marked_tags) + self.assertTrue(another_tag in marked_tags) + settings.SUBSCRIBED_TAG_SELECTOR_ENABLED = old_value + settings.GROUPS_ENABLED = old_group_value + def test_delete_user(self): user = self.create_user('user') user.delete() diff --git a/askbot/urls.py b/askbot/urls.py index 9bb00406..f5519578 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -215,6 +215,27 @@ urlpatterns = patterns('', name='tags' ), url( + r'^%s$' % _('tags/subscriptions/'), + views.commands.list_bulk_tag_subscription, + name='list_bulk_tag_subscription' + ), + url(#post only + r'^%s$' % _('tags/subscriptions/delete/'), + views.commands.delete_bulk_tag_subscription, + name='delete_bulk_tag_subscription' + ), + url( + r'^%s$' % _('tags/subscriptions/create/'), + views.commands.create_bulk_tag_subscription, + name='create_bulk_tag_subscription' + ), + url( + r'^%s(?P<pk>\d+)/$' % _('tags/subscriptions/edit/'), + views.commands.edit_bulk_tag_subscription, + name='edit_bulk_tag_subscription' + ), + + url( r'^%s$' % _('suggested-tags/'), views.meta.list_suggested_tags, name = 'list_suggested_tags' diff --git a/askbot/views/commands.py b/askbot/views/commands.py index e524b81f..6153a0ca 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -41,6 +41,8 @@ from askbot.utils.forms import get_db_object_or_404 from django.template import RequestContext from askbot.skins.loaders import render_into_skin_as_string from askbot.skins.loaders import render_text_into_skin +from askbot.models.tag import get_tags_by_names + @csrf.csrf_exempt @@ -437,13 +439,18 @@ def mark_tag(request, **kwargs):#tagging system reason = post_data['reason'] assert reason in ('good', 'bad', 'subscribed') #separate plain tag names and wildcard tags - tagnames, wildcards = forms.clean_marked_tagnames(raw_tagnames) - cleaned_tagnames, cleaned_wildcards = request.user.mark_tags( - tagnames, - wildcards, - reason = reason, - action = action + + if request.user.is_administrator() and post_data['user'] != request.user.id: + user = get_object_or_404(models.User, pk=post_data['user']) + else: + user = request.user + + cleaned_tagnames, cleaned_wildcards = user.mark_tags( + tagnames, + wildcards, + reason = reason, + action = action ) #lastly - calculate tag usage counts @@ -694,6 +701,97 @@ def subscribe_for_tags(request): request.session['subscribe_for_tags'] = (pure_tag_names, wildcards) return HttpResponseRedirect(url_utils.get_login_url()) +@decorators.admins_only +def list_bulk_tag_subscription(request): + object_list = models.BulkTagSubscription.objects.all() + + data = { + 'object_list': object_list + } + return render(request, 'tags/list_bulk_tag_subscription.html', data) + +@decorators.admins_only +def create_bulk_tag_subscription(request): + data = {'action': _('Create')} + if request.method == "POST": + form = forms.BulkTagSubscriptionForm(request.POST) + if form.is_valid(): + tag_names = form.cleaned_data['tags'].split(' ') + user_list = form.cleaned_data.get('users') + group_list = form.cleaned_data.get('groups') + + bulk_subscription = models.BulkTagSubscription.objects.create( + tag_names=tag_names, + tag_author=request.user, + user_list=user_list, + group_list=group_list + ) + return HttpResponseRedirect(reverse('list_bulk_tag_subscription')) + else: + data['form'] = form + else: + data['form'] = forms.BulkTagSubscriptionForm() + + return render(request, 'tags/form_bulk_tag_subscription.html', data) + +@decorators.admins_only +def edit_bulk_tag_subscription(request, pk): + bulk_subscription = get_object_or_404(models.BulkTagSubscription, + pk=pk) + data = {'action': _('Edit')} + if request.method == "POST": + form = forms.BulkTagSubscriptionForm(request.POST) + if form.is_valid(): + bulk_subscription.tags.clear() + bulk_subscription.users.clear() + bulk_subscription.groups.clear() + + if 'groups' in form.cleaned_data: + group_ids = [user.id for user in form.cleaned_data['groups']] + bulk_subscription.groups.add(*group_ids) + + tags, new_tag_names = get_tags_by_names(form.cleaned_data['tags'].split(' ')) + tag_id_list = [tag.id for tag in tags] + + for new_tag_name in new_tag_names: + new_tag = models.Tag.objects.create(name=new_tag_name, + created_by=request.user) + tag_id_list.append(new_tag.id) + + bulk_subscription.tags.add(*tag_id_list) + + user_ids = [] + for user in form.cleaned_data['users']: + user_ids.append(user) + user.mark_tags(bulk_subscription.tag_list(), + reason='subscribed', action='add') + + bulk_subscription.users.add(*user_ids) + + return HttpResponseRedirect(reverse('list_bulk_tag_subscription')) + else: + form_initial = { + 'users': bulk_subscription.users.all(), + 'groups': bulk_subscription.groups.all(), + 'tags': ' '.join([tag.name for tag in bulk_subscription.tags.all()]), + } + data.update({ + 'bulk_subscription': bulk_subscription, + 'form': forms.BulkTagSubscriptionForm(initial=form_initial), + }) + + return render(request, 'tags/form_bulk_tag_subscription.html', data) + +@decorators.admins_only +@decorators.post_only +def delete_bulk_tag_subscription(request): + pk = request.POST.get('pk') + if pk: + bulk_subscription = get_object_or_404(models.BulkTagSubscription, pk=pk) + bulk_subscription.delete() + return HttpResponseRedirect(reverse('list_bulk_tag_subscription')) + else: + return HttpResponseRedirect(reverse('list_bulk_tag_subscription')) @decorators.get_only def title_search(request): diff --git a/askbot/views/users.py b/askbot/views/users.py index 8763c3ec..eac79fe0 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -994,6 +994,7 @@ def user_email_subscriptions(request, user, context): data = { 'active_tab': 'users', + 'subscribed_tag_names': user.get_marked_tag_names('subscribed'), 'page_class': 'user-profile-page', 'tab_name': 'email_subscriptions', 'tab_description': _('email subscription settings'), |