summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/admin.py5
-rw-r--r--askbot/forms.py15
-rw-r--r--askbot/media/js/tag_selector.js7
-rw-r--r--askbot/media/style/style.css75
-rw-r--r--askbot/media/style/style.less28
-rw-r--r--askbot/migrations/0159_add_model_BulkTagSubscription.py424
-rw-r--r--askbot/models/__init__.py16
-rw-r--r--askbot/models/question.py12
-rw-r--r--askbot/models/user.py70
-rw-r--r--askbot/templates/tags/form_bulk_tag_subscription.html21
-rw-r--r--askbot/templates/tags/list_bulk_tag_subscription.html51
-rw-r--r--askbot/templates/user_profile/user_email_subscriptions.html28
-rw-r--r--askbot/tests/db_api_tests.py4
-rw-r--r--askbot/tests/user_model_tests.py22
-rw-r--r--askbot/urls.py21
-rw-r--r--askbot/views/commands.py110
-rw-r--r--askbot/views/users.py1
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'),