summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdolfo Fitoria <adolfo.fitoria@gmail.com>2013-06-05 09:06:51 -0600
committerAdolfo Fitoria <adolfo.fitoria@gmail.com>2013-06-05 09:06:51 -0600
commit7f319d67eecf2295829c3e97ce04bf29a7da437e (patch)
tree18d1ac073b35874c8c4b576ee8c1659fe5282f22
parent3cbe22172952c2d43160aac6f2eeb9d2af7a6ebd (diff)
parent27772070e816f979e3d6d6d101e98c7258bfdb65 (diff)
downloadaskbot-7f319d67eecf2295829c3e97ce04bf29a7da437e.tar.gz
askbot-7f319d67eecf2295829c3e97ce04bf29a7da437e.tar.bz2
askbot-7f319d67eecf2295829c3e97ce04bf29a7da437e.zip
Merge branch 'master' of github.com:ASKBOT/askbot-devel into solrmultilang
-rw-r--r--askbot/deps/django_authopenid/forms.py2
-rw-r--r--askbot/deps/django_authopenid/views.py1
-rw-r--r--askbot/doc/source/changelog.rst1
-rw-r--r--askbot/doc/source/contributors.rst1
-rw-r--r--askbot/doc/source/management-commands.rst5
-rw-r--r--askbot/management/commands/create_tag_synonyms.py147
-rw-r--r--askbot/management/commands/rename_tags_id.py40
-rw-r--r--askbot/management/commands/send_email_alerts.py49
-rw-r--r--askbot/media/js/user.js3
-rw-r--r--askbot/media/style/style.css30
-rw-r--r--askbot/media/style/style.less44
-rw-r--r--askbot/migrations/0169_auto__add_tagsynonym.py413
-rw-r--r--askbot/models/__init__.py3
-rw-r--r--askbot/models/question.py17
-rw-r--r--askbot/models/tag.py17
-rw-r--r--askbot/templates/authopenid/email_validation.html12
-rw-r--r--askbot/templates/user_profile/user_info.html24
-rw-r--r--askbot/tests/cache_tests.py15
-rw-r--r--askbot/tests/email_parsing_tests.py7
-rw-r--r--askbot/tests/management_command_tests.py78
-rw-r--r--askbot/tests/post_model_tests.py3
-rw-r--r--askbot/views/commands.py25
22 files changed, 830 insertions, 107 deletions
diff --git a/askbot/deps/django_authopenid/forms.py b/askbot/deps/django_authopenid/forms.py
index 4ce7242b..8328da4d 100644
--- a/askbot/deps/django_authopenid/forms.py
+++ b/askbot/deps/django_authopenid/forms.py
@@ -394,7 +394,7 @@ class AccountRecoveryForm(forms.Form):
try:
user = User.objects.filter(email__iexact=email)[0]
self.cleaned_data['user'] = user
- except KeyError:
+ except IndexError:
del self.cleaned_data['email']
message = _('Sorry, we don\'t have this email address in the database')
raise forms.ValidationError(message)
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index bea67d2f..8f6ee101 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -1256,6 +1256,7 @@ def send_email_key(email, key, handler_url_name='user_account_recover'):
{'site': askbot_settings.APP_SHORT_NAME}
data = {
+ 'site_name': askbot_settings.APP_SHORT_NAME,
'validation_link': site_url(reverse(handler_url_name)) + \
'?validation_code=' + key
}
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index 48afb5ea..2e300278 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -3,6 +3,7 @@ Changes in Askbot
Development version
-------------------
+* Added backend support for the tag synonyms
* Added management command `apply_hinted_tags` to batch-apply tags from a list
* Added hovercard on the user's karma display in the header
* Added option to hide ad blocks from logged in users
diff --git a/askbot/doc/source/contributors.rst b/askbot/doc/source/contributors.rst
index 85d252bc..04a93e40 100644
--- a/askbot/doc/source/contributors.rst
+++ b/askbot/doc/source/contributors.rst
@@ -48,6 +48,7 @@ Programming, bug fixes and documentation
* `Zafer Cakmak <https://github.com/xaph>`_
* `Kevin Porterfield <http://www.shotgunsoftware.com>_`
* `Robert Martin <https://github.com/bobbydavid>_`
+* `pcompassion https://github.com/pcompassion`_
Translations
------------
diff --git a/askbot/doc/source/management-commands.rst b/askbot/doc/source/management-commands.rst
index da93dcb5..a56aa47a 100644
--- a/askbot/doc/source/management-commands.rst
+++ b/askbot/doc/source/management-commands.rst
@@ -30,6 +30,11 @@ The bulk of the management commands fall into this group and will probably be th
| | one per line. If many tags match - only the most frequent |
| | will be selected. |
+---------------------------------+-------------------------------------------------------------+
+| `create_tag_synonyms --from | Creates tag synonym record from one name to another, |
+| <from_name> --to <to_name> | creates the tag named as given with the value of `--to` |
+| --user-id <user_id>` | if not existing, and the tag creator will be user with id |
+| | equal to the value of `--user-id` parameter. |
++---------------------------------+-------------------------------------------------------------+
| `remove_admin <user_id>` | Remove admin status from a user account - the opposite of |
| | the `add_admin` command |
+---------------------------------+-------------------------------------------------------------+
diff --git a/askbot/management/commands/create_tag_synonyms.py b/askbot/management/commands/create_tag_synonyms.py
new file mode 100644
index 00000000..e4324639
--- /dev/null
+++ b/askbot/management/commands/create_tag_synonyms.py
@@ -0,0 +1,147 @@
+"""management command that creates a tag synonym
+all corresponding questions are retagged
+"""
+
+import sys
+from optparse import make_option
+from django.core import management
+from django.core.management.base import BaseCommand, CommandError
+from askbot import models
+from askbot.management.commands.rename_tags import get_admin
+from askbot.utils import console
+import datetime
+
+
+
+def decode_input(input):
+ decoded_input = input.decode(sys.stdin.encoding)
+ decoded_input = decoded_input.strip()
+ return decoded_input
+
+
+class Command(BaseCommand):
+
+ help = """create TagSynonym,
+retags questions from source_tag_name to target_tag_name,
+remove source_tag"""
+
+ option_list = BaseCommand.option_list + (
+ make_option('--from',
+ action = 'store',
+ type = 'str',
+ dest = 'from',
+ default = None,
+ help = 'a source tag name which needs to be replaced'
+ ),
+ make_option('--to',
+ action = 'store',
+ type = 'str',
+ dest = 'to',
+ default = None,
+ help = 'a target tag name that are to be used instead'
+ ),
+ make_option('--user-id',
+ action = 'store',
+ type = 'int',
+ dest = 'user_id',
+ default = None,
+ help = 'id of the user who will be marked as a performer of this operation'
+ ),
+ )
+
+ def handle(self, *args, **options):
+ """command handle function. reads tag names, decodes
+ them using the standard input encoding and attempts to find
+ the matching tags
+
+ If "from" tag is not resolved, command fails
+ if "to" tag is not resolved, a new tag is created
+ """
+
+ if options['from'] is None:
+ raise CommandError('the --from argument is required')
+ if options['to'] is None:
+ raise CommandError('the --to argument is required')
+
+ source_tag_name = decode_input(options['from'])
+ target_tag_name = decode_input(options['to'])
+
+ if source_tag_name == target_tag_name:
+ raise CommandError("source and target tags appear to be the same")
+
+ admin = get_admin(seed_user_id = options['user_id'])
+
+ source_tag = None
+ is_source_tag_created = False
+
+ try:
+ source_tag = models.Tag.objects.get(name=source_tag_name)
+ except models.Tag.DoesNotExist:
+ if not options.get('is_force', False):
+ prompt = """source tag %s doesn't exist, are you sure you want to create a TagSynonym
+ %s ==> %s?""" % (source_tag_name, source_tag_name, target_tag_name)
+ choice = console.choice_dialog(prompt, choices=('yes', 'no'))
+ if choice == 'no':
+ print 'Cancled'
+ sys.exit()
+ source_tag = models.Tag.objects.create(name=source_tag_name,
+ created_by=admin
+ )
+ is_source_tag_created = True
+
+
+ # test if target_tag is actually synonym for yet another tag
+ # when user asked tag2->tag3, we already had tag3->tag4.
+ try:
+ tag_synonym_tmp = models.TagSynonym.objects.get(source_tag_name = target_tag_name)
+ if not options.get('is_force', False):
+ prompt = """There exists a TagSynonym %s ==> %s,
+ hence we will create a tag synonym %s ==> %s instead. Proceed?""" % (tag_synonym_tmp.source_tag_name, tag_synonym_tmp.target_tag_name,
+ source_tag_name, tag_synonym_tmp.target_tag_name)
+ choice = console.choice_dialog(prompt, choices=('yes', 'no'))
+ if choice == 'no':
+ print 'Cancled'
+ sys.exit()
+ target_tag_name = tag_synonym_tmp.target_tag_name
+ options['to'] = target_tag_name
+ except models.TagSynonym.DoesNotExist:
+ pass
+
+ try:
+ models.Tag.objects.get(name=target_tag_name)
+ except models.Tag.DoesNotExist:
+ # we are creating a target tag, let's copy source tag's info
+ # used_count are updated later
+ models.Tag.objects.create(name=target_tag_name,
+ created_by = admin,
+ status = source_tag.status,
+ tag_wiki = source_tag.tag_wiki
+ )
+
+ tag_synonym_tmp, created = models.TagSynonym.objects.get_or_create(source_tag_name = source_tag_name,
+ target_tag_name = target_tag_name,
+ owned_by = admin
+ )
+
+ management.call_command('rename_tags', *args, **options)
+
+ # When source_tag_name is a target_tag_name of already existing TagSynonym.
+ # ie. if tag1->tag2 exists when user asked tag2->tag3
+ # we are going to convert all tag1->tag2 to tag1->tag3 as well
+ existing_tag_synonyms = models.TagSynonym.objects.filter(target_tag_name=source_tag_name)
+ for existing_tag_synonym in existing_tag_synonyms:
+ new_options = options.copy()
+ new_options['from'] = existing_tag_synonym.source_tag_name
+ new_options['user_id'] = admin.id
+ new_options['is_force'] = True # this is mandatory conversion
+ new_options['timestamp'] = datetime.datetime.now()
+ existing_tag_synonym.delete() # no longer needed
+ self.handle(*args, **new_options)
+
+ # delete source Tag
+ if is_source_tag_created:
+ source_tag.delete()
+ else:
+ source_tag.deleted = True
+ source_tag.deleted_at = options.get('timestamp', datetime.datetime.now())
+ source_tag.deleted_by = admin
diff --git a/askbot/management/commands/rename_tags_id.py b/askbot/management/commands/rename_tags_id.py
index f6fe05ae..1da22868 100644
--- a/askbot/management/commands/rename_tags_id.py
+++ b/askbot/management/commands/rename_tags_id.py
@@ -123,21 +123,33 @@ or repost a bug, if that does not help"""
for question in questions[:10]:
print '* %s' % question.title.strip()
- from_tag_names = format_tag_name_list(from_tags)
- to_tag_names = format_tag_name_list(to_tags)
-
- prompt = 'Rename tags %s --> %s?' % (from_tag_names, to_tag_names)
- choice = console.choice_dialog(prompt, choices=('yes', 'no'))
- if choice == 'no':
- print 'Canceled'
- sys.exit()
+ formatted_from_tag_names = format_tag_name_list(from_tags)
+ formatted_to_tag_names = format_tag_name_list(to_tags)
+
+ if not options.get('is_force', False):
+ prompt = 'Rename tags %s --> %s?' % (formatted_from_tag_names, formatted_to_tag_names)
+ choice = console.choice_dialog(prompt, choices=('yes', 'no'))
+ if choice == 'no':
+ print 'Canceled'
+ sys.exit()
else:
- sys.stdout.write('Processing:')
+ print 'Renaming tags %s --> %s' % (formatted_from_tag_names, formatted_to_tag_names)
+ sys.stdout.write('Processing:')
- #actual processing stage, only after this point we start to
- #modify stuff in the database, one question per transaction
from_tag_names = get_tag_names(from_tags)
to_tag_names = get_tag_names(to_tags)
+
+ #if user provided tag1 as to_tag, and tagsynonym tag1->tag2 exists.
+ for to_tag_name in to_tag_names:
+ try:
+ tag_synonym = models.TagSynonym.objects.get(source_tag_name = to_tag_name)
+ raise CommandError(u'You gave %s as --to argument, but TagSynonym: %s -> %s exists, probably you want to provide %s as --to argument' % (to_tag_name, tag_synonym.source_tag_name, tag_synonym.target_tag_name, tag_synonym.target_tag_name))
+ except models.TagSynonym.DoesNotExist:
+ pass
+
+
+ #actual processing stage, only after this point we start to
+ #modify stuff in the database, one question per transaction
i = 0
for question in questions:
tag_names = set(question.get_tag_names())
@@ -174,3 +186,9 @@ or repost a bug, if that does not help"""
# print "None found."
#print "Done."
#transaction.commit()
+
+ # A user wants to rename tag2->tag3 and tagsynonym tag1->tag2 exists.
+ # we want to update tagsynonym (tag1->tag2) to (tag1->tag3)
+ for from_tag_name in from_tag_names:
+ # we need db_index for target_tag_name as well for this
+ models.TagSynonym.objects.filter(target_tag_name = from_tag_name).update(target_tag_name = to_tag_name)
diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py
index b7432296..06f40c65 100644
--- a/askbot/management/commands/send_email_alerts.py
+++ b/askbot/management/commands/send_email_alerts.py
@@ -29,7 +29,8 @@ def get_all_origin_posts(mentions):
def extend_question_list(
src, dst, cutoff_time = None,
limit=False, add_mention=False,
- add_comment = False
+ add_comment = False,
+ languages=None
):
"""src is a query set with questions
or None
@@ -48,6 +49,8 @@ def extend_question_list(
raise ValueError('cutoff_time is a mandatory parameter')
for q in src:
+ if languages and src.language_code not in languages:
+ continue
if q in dst:
meta_data = dst[q]
else:
@@ -162,6 +165,11 @@ class Command(NoArgsCommand):
Q_set_A = not_seen_qs
Q_set_B = seen_before_last_mod_qs
+ if django_settings.ASKBOT_MULTILINGUAL:
+ languages = user.languages.split()
+ else:
+ languages = None
+
for feed in user_feeds:
if feed.feed_type == 'm_and_c':
#alerts on mentions and comments are processed separately
@@ -216,8 +224,8 @@ class Command(NoArgsCommand):
q_list = SortedDict()
#todo: refactor q_list into a separate class?
- extend_question_list(q_sel_A, q_list)
- extend_question_list(q_sel_B, q_list)
+ extend_question_list(q_sel_A, q_list, languages=languages)
+ extend_question_list(q_sel_B, q_list, languages=languages)
#build list of comment and mention responses here
#it is separate because posts are not marked as changed
@@ -247,8 +255,9 @@ class Command(NoArgsCommand):
extend_question_list(
q_commented,
q_list,
- cutoff_time = cutoff_time,
- add_comment = True
+ cutoff_time=cutoff_time,
+ add_comment=True,
+ languages=languages
)
mentions = Activity.objects.get_mentions(
@@ -267,27 +276,37 @@ class Command(NoArgsCommand):
q_mentions_A = Q_set_A.filter(id__in = q_mentions_id)
q_mentions_A.cutoff_time = cutoff_time
- extend_question_list(q_mentions_A, q_list, add_mention=True)
+ extend_question_list(
+ q_mentions_A,
+ q_list,
+ add_mention=True,
+ languages=languages
+ )
q_mentions_B = Q_set_B.filter(id__in = q_mentions_id)
q_mentions_B.cutoff_time = cutoff_time
- extend_question_list(q_mentions_B, q_list, add_mention=True)
+ extend_question_list(
+ q_mentions_B,
+ q_list,
+ add_mention=True,
+ languages=languages
+ )
except EmailFeedSetting.DoesNotExist:
pass
if user.email_tag_filter_strategy == const.INCLUDE_INTERESTING:
- extend_question_list(q_all_A, q_list)
- extend_question_list(q_all_B, q_list)
+ extend_question_list(q_all_A, q_list, languages=languages)
+ extend_question_list(q_all_B, q_list, languages=languages)
- extend_question_list(q_ask_A, q_list, limit=True)
- extend_question_list(q_ask_B, q_list, limit=True)
+ extend_question_list(q_ask_A, q_list, limit=True, languages=languages)
+ extend_question_list(q_ask_B, q_list, limit=True, languages=languages)
- extend_question_list(q_ans_A, q_list, limit=True)
- extend_question_list(q_ans_B, q_list, limit=True)
+ extend_question_list(q_ans_A, q_list, limit=True, languages=languages)
+ extend_question_list(q_ans_B, q_list, limit=True, languages=languages)
if user.email_tag_filter_strategy == const.EXCLUDE_IGNORED:
- extend_question_list(q_all_A, q_list, limit=True)
- extend_question_list(q_all_B, q_list, limit=True)
+ extend_question_list(q_all_A, q_list, limit=True, languages=languages)
+ extend_question_list(q_all_B, q_list, limit=True, languages=languages)
ctype = ContentType.objects.get_for_model(Post)
EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_EMAIL_UPDATE_SENT
diff --git a/askbot/media/js/user.js b/askbot/media/js/user.js
index 957bfb4c..b5a999a5 100644
--- a/askbot/media/js/user.js
+++ b/askbot/media/js/user.js
@@ -804,7 +804,7 @@ UserGroup.prototype.decorate = function(element){
this._name = $.trim(element.find('a').html());
var deleter = new DeleteIcon();
deleter.setHandler(this.getDeleteHandler());
- deleter.setContent(gettext('Remove'));
+ //deleter.setContent(gettext('Remove'));
this._element.find('td:last').append(deleter.getElement());
this._delete_icon = deleter;
};
@@ -946,6 +946,7 @@ GroupAdderWidget.prototype.toggleState = function(){
GroupAdderWidget.prototype.decorate = function(element){
this._element = element;
var input = this.makeElement('input');
+ input.attr('type', 'text');
this._input = input;
var groupsAc = new AutoCompleter({
diff --git a/askbot/media/style/style.css b/askbot/media/style/style.css
index fbbd206a..55769a17 100644
--- a/askbot/media/style/style.css
+++ b/askbot/media/style/style.css
@@ -3013,6 +3013,7 @@ a:hover.medal {
}
.user-details td {
padding-right: 10px;
+ vertical-align: top;
}
.user-about {
background-color: #EEEEEE;
@@ -3852,6 +3853,9 @@ p.signup_p {
padding: 0;
margin-top: -3px;
}
+.user-profile-page .vote-notification h3 {
+ padding: 10px;
+}
.user-profile-page ul.tags {
margin-left: 5px;
}
@@ -4006,24 +4010,23 @@ body.anon.lang-es #searchBar {
}
/* user groups */
+#user-groups input[type="text"] {
+ height: 21px;
+ width: 100px;
+}
#user-groups ul {
margin-bottom: 0px;
}
#user-groups .delete-icon {
- float: none;
- display: inline;
- color: #525252;
- padding: 0 3px 0 3px;
- background: #ccc;
- border-radius: 4px;
- line-height: inherit;
- -moz-border-radius: 4px;
- -khtml-border-radius: 4px;
- -webkit-border-radius: 4px;
+ background: url(../images/close-small.png) no-repeat;
+ border: none;
+ display: inline-block;
+ height: 14px;
+ margin-top: 4px;
+ width: 14px;
}
#user-groups .delete-icon:hover {
- color: white;
- background: #b32f2f;
+ background: url(../images/close-small-hover.png) no-repeat;
}
.question-page .post-update-info a.primary-group-name,
a.primary-group-name {
@@ -4058,9 +4061,6 @@ img.group-logo {
#groups-list .group-name {
padding-right: 20px;
}
-#groups-list td {
- padding-bottom: 5px;
-}
.groups-page #groups-list th,
.groups-page #groups-list td {
padding-right: 20px;
diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less
index 4a957dd6..83be895f 100644
--- a/askbot/media/style/style.less
+++ b/askbot/media/style/style.less
@@ -3133,6 +3133,7 @@ a:hover.medal {
}
td {
padding-right: 10px;
+ vertical-align: top;
}
}
@@ -4099,6 +4100,9 @@ p.signup_p {
padding: 0;
margin-top: -3px;
}
+ .vote-notification h3 {
+ padding: 10px;
+ }
ul.tags {
margin-left: 5px;
}
@@ -4169,24 +4173,25 @@ body.anon.lang-es {
}
/* user groups */
-#user-groups ul {
- margin-bottom: 0px;
-}
-#user-groups .delete-icon {
- float: none;
- display: inline;
- color: #525252;
- padding: 0 3px 0 3px;
- background: #ccc;
- border-radius: 4px;
- line-height:inherit;
- -moz-border-radius: 4px;
- -khtml-border-radius: 4px;
- -webkit-border-radius: 4px;
-}
-#user-groups .delete-icon:hover {
- color: white;
- background: #b32f2f;
+#user-groups {
+ input[type="text"] {
+ height: 21px;
+ width: 100px;
+ }
+ ul {
+ margin-bottom: 0px;
+ }
+ .delete-icon {
+ background: url(../images/close-small.png) no-repeat;
+ border: none;
+ display: inline-block;
+ height: 14px;
+ margin-top: 4px;
+ width: 14px;
+ }
+ .delete-icon:hover {
+ background: url(../images/close-small-hover.png) no-repeat;
+ }
}
.question-page .post-update-info a.primary-group-name,
@@ -4229,9 +4234,6 @@ img.group-logo {
.group-name {
padding-right: 20px;
}
- td {
- padding-bottom: 5px;
- }
}
.groups-page #groups-list {
diff --git a/askbot/migrations/0169_auto__add_tagsynonym.py b/askbot/migrations/0169_auto__add_tagsynonym.py
new file mode 100644
index 00000000..bd217056
--- /dev/null
+++ b/askbot/migrations/0169_auto__add_tagsynonym.py
@@ -0,0 +1,413 @@
+# -*- 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 'TagSynonym'
+ db.create_table('askbot_tagsynonym', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('source_tag_name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
+ ('target_tag_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('owned_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='tag_synonyms', to=orm['auth.User'])),
+ ('auto_rename_count', self.gf('django.db.models.fields.IntegerField')(default=0)),
+ ('last_auto_rename_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+ ))
+ db.send_create_signal('askbot', ['TagSynonym'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'TagSynonym'
+ db.delete_table('askbot_tagsynonym')
+
+
+ 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': {'ordering': "['-date_added']", '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'}),
+ 'language_code': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '16'}),
+ '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.tagsynonym': {
+ 'Meta': {'object_name': 'TagSynonym'},
+ 'auto_rename_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_auto_rename_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'owned_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_synonyms'", 'to': "orm['auth.User']"}),
+ 'source_tag_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'target_tag_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ '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'}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': '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'}),
+ 'language_code': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '16'}),
+ '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'}),
+ 'languages': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '128'}),
+ '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'}),
+ 'social_sharing_mode': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
+ 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'twitter_access_token': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
+ 'twitter_handle': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32'}),
+ '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'] \ No newline at end of file
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index 78a68487..797aa6a3 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -42,7 +42,7 @@ from askbot.mail import messages
from askbot.models.question import QuestionView, AnonymousQuestion
from askbot.models.question import DraftQuestion
from askbot.models.question import FavoriteQuestion
-from askbot.models.tag import Tag, MarkedTag
+from askbot.models.tag import Tag, MarkedTag, TagSynonym
from askbot.models.tag import format_personal_group_name
from askbot.models.user import EmailFeedSetting, ActivityAuditStatus, Activity
from askbot.models.user import GroupMembership
@@ -3781,6 +3781,7 @@ __all__ = [
'Vote',
'PostFlagReason',
'MarkedTag',
+ 'TagSynonym',
'BadgeData',
'Award',
diff --git a/askbot/models/question.py b/askbot/models/question.py
index ed30c5f5..0f4c4a85 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -19,7 +19,7 @@ import askbot
from askbot.conf import settings as askbot_settings
from askbot import mail
from askbot.mail import messages
-from askbot.models.tag import Tag
+from askbot.models.tag import Tag, TagSynonym
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 separate_unused_tags
@@ -1264,6 +1264,8 @@ class Thread(models.Model):
Tag use counts are recalculated
A signal tags updated is sent
+ TagSynonym is used to replace tag names
+
*IMPORTANT*: self._question_post() has to
exist when update_tags() is called!
"""
@@ -1273,9 +1275,20 @@ class Thread(models.Model):
previous_tags = list(self.tags.filter(status = Tag.STATUS_ACCEPTED))
ordered_updated_tagnames = [t for t in tagnames.strip().split(' ')]
+ updated_tagnames_tmp = set(ordered_updated_tagnames)
+
+ #apply TagSynonym
+ updated_tagnames = set()
+ for tag_name in updated_tagnames_tmp:
+ try:
+ tag_synonym = TagSynonym.objects.get(source_tag_name=tag_name)
+ updated_tagnames.add(tag_synonym.target_tag_name)
+ tag_synonym.auto_rename_count += 1
+ tag_synonym.save()
+ except TagSynonym.DoesNotExist:
+ updated_tagnames.add(tag_name)
previous_tagnames = set([tag.name for tag in previous_tags])
- updated_tagnames = set(ordered_updated_tagnames)
removed_tagnames = previous_tagnames - updated_tagnames
#remove tags from the question's tags many2many relation
diff --git a/askbot/models/tag.py b/askbot/models/tag.py
index 455995e0..bf054627 100644
--- a/askbot/models/tag.py
+++ b/askbot/models/tag.py
@@ -318,3 +318,20 @@ class MarkedTag(models.Model):
class Meta:
app_label = 'askbot'
+
+
+class TagSynonym(models.Model):
+
+ source_tag_name = models.CharField(max_length=255, unique=True)
+ target_tag_name = models.CharField(max_length=255, db_index=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+ owned_by = models.ForeignKey(User, related_name='tag_synonyms')
+ auto_rename_count = models.IntegerField(default=0)
+ last_auto_rename_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ app_label = 'askbot'
+
+ def __unicode__(self):
+ return u'%s -> %s' % (self.source_tag_name, self.target_tag_name)
+
diff --git a/askbot/templates/authopenid/email_validation.html b/askbot/templates/authopenid/email_validation.html
index 616b285f..4a74b23f 100644
--- a/askbot/templates/authopenid/email_validation.html
+++ b/askbot/templates/authopenid/email_validation.html
@@ -1,20 +1,20 @@
{% extends "email/base_mail.html"%}
-{% block title %}{% trans %}Greetings from the Q&A forum{% endtrans %},{%endblock%}
-{% block headline%}{% trans %}Greetings from the Q&A forum{% endtrans %},{%endblock%}
+{% block title %}{% trans %}Greetings from the {{ site_name }}{% endtrans %},{%endblock%}
+{% block headline %}{% trans %}Greetings from the {{ site_name }}{% endtrans %},{%endblock%}
-{%block content%}
+{% block content %}
<p>{% trans %}To make use of the Forum, please follow the link below:{% endtrans %}</p>
-<p><a href="{{validation_link}}" >{{validation_link}}</a></p>
+<p><a href="{{ validation_link }}" >{{ validation_link }}</a></p>
<p>{% trans %}Following the link above will help us verify your email address.{% endtrans %}</p>
<p>{% trans %}If you believe that this message was sent in mistake -
no further action is needed. Just ignore this email, we apologize
for any inconvenience{% endtrans %}</p>
-{%endblock%}
+{% endblock %}
-{%block footer %}
+{% block footer %}
{% include "email/footer.html" %}
{% endblock %}
diff --git a/askbot/templates/user_profile/user_info.html b/askbot/templates/user_profile/user_info.html
index 95baad20..6c20f1f4 100644
--- a/askbot/templates/user_profile/user_info.html
+++ b/askbot/templates/user_profile/user_info.html
@@ -58,19 +58,21 @@
{% endif %}
{% if settings.GROUPS_ENABLED %}
<tr>
- <td>{% if user_groups %}{% trans %}groups{% endtrans %}{% endif %}</td>
+ <td>{% trans %}groups{% endtrans %}</td>
<td>
- <table id="groups-list">
- {% for group in user_groups %}
- <tr>
- {{ macros.user_group(group, groups_membership_info[group.id]) }}
- </tr>
- {% endfor %}
- </table>
- <div class="clearfix"></div>
- <a id="add-group">{% trans %}add group{% endtrans %}</a>
+ <div id="user-groups">
+ <table id="groups-list">
+ {% for group in user_groups %}
+ <tr>
+ {{ macros.user_group(group, groups_membership_info[group.id]) }}
+ </tr>
+ {% endfor %}
+ </table>
+ <div class="clearfix"></div>
+ <a id="add-group">{% trans %}add group{% endtrans %}</a>
+ </div>
</td>
- </div>
+ </tr>
{% endif %}
<tr>
<td>{% trans %}member since{% endtrans %}</td>
diff --git a/askbot/tests/cache_tests.py b/askbot/tests/cache_tests.py
index 5740cc2a..e0703d08 100644
--- a/askbot/tests/cache_tests.py
+++ b/askbot/tests/cache_tests.py
@@ -11,16 +11,19 @@ class CacheTests(AskbotTestCase):
self.post_answer(user=user, question=self.question)
settings.DEBUG = True # because it's forsed to False
+ def tearDown(self):
+ settings.DEBUG = False
+
def visit_question(self):
self.client.get(self.question.get_absolute_url(), follow=True)
-
+
def test_anonymous_question_cache(self):
self.visit_question()
- counter = len(connection.queries)
- print 'we have %d queries' % counter
+ before_count = len(connection.queries)
self.visit_question()
-
#second hit to the same question should give fewer queries
- self.assertTrue(counter > len(connection.queries))
- settings.DEBUG = False
+ after_count = len(connection.queries)
+ self.assertTrue(before_count > after_count,
+ ('Expected fewer queries after calling visit_question. ' +
+ 'Before visit: %d. After visit: %d.') % (before_count, after_count))
diff --git a/askbot/tests/email_parsing_tests.py b/askbot/tests/email_parsing_tests.py
index 3ed0908a..9a5ff126 100644
--- a/askbot/tests/email_parsing_tests.py
+++ b/askbot/tests/email_parsing_tests.py
@@ -18,12 +18,7 @@ class EmailParsingTests(utils.AskbotTestCase):
def test_clean_email_body(self):
cleaned_body = mail.clean_html_email(self.rendered_template)
- print "EXPECTED BODY"
- print self.expected_output
- print '=================================================='
- print cleaned_body
- print "CLEANED BODY"
- self.assertEqual(cleaned_body, self.expected_output)
+ self.assertEqual(self.expected_output, cleaned_body)
def test_gmail_rich_text_response_stripped(self):
text = u'\n\nthis is my reply!\n\nOn Wed, Oct 31, 2012 at 1:45 AM, <kp@kp-dev.askbot.com> wrote:\n\n> **\n> '
diff --git a/askbot/tests/management_command_tests.py b/askbot/tests/management_command_tests.py
index f1c6728d..a44bb792 100644
--- a/askbot/tests/management_command_tests.py
+++ b/askbot/tests/management_command_tests.py
@@ -2,6 +2,7 @@ from django.core import management
from django.contrib import auth
from askbot.tests.utils import AskbotTestCase
from askbot import models
+from django.contrib.auth.models import User
class ManagementCommandTests(AskbotTestCase):
def test_add_askbot_user(self):
@@ -50,3 +51,80 @@ class ManagementCommandTests(AskbotTestCase):
user_two = models.User.objects.get(pk=user_two_pk)
self.assertEqual(user_two.gold, number_of_gold)
self.assertEqual(user_two.reputation, reputation)
+
+ def test_create_tag_synonym(self):
+
+ admin = User.objects.create_superuser('test_admin', 'admin@admin.com', 'admin_pass')
+
+ options = {
+ 'from': 'tag1', # ok.. 'from' is a bad keyword argument name..
+ 'to': 'tag2',
+ 'user_id': admin.id,
+ 'is_force': True
+ }
+ management.call_command(
+ 'create_tag_synonyms',
+ **options
+ )
+
+ options['from'] = 'tag3'
+ options['to'] = 'tag4'
+ management.call_command(
+ 'create_tag_synonyms',
+ **options
+ )
+
+ options['from']='tag5'
+ options['to']='tag4'
+ management.call_command(
+ 'create_tag_synonyms',
+ **options
+ )
+
+ options['from']='tag2'
+ options['to']='tag3'
+ management.call_command(
+ 'create_tag_synonyms',
+ **options
+ )
+
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag1',
+ target_tag_name = 'tag4'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag2',
+ target_tag_name = 'tag4'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag3',
+ target_tag_name = 'tag4'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag5',
+ target_tag_name = 'tag4'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.count(), 4)
+
+ options['from']='tag4'
+ options['to']='tag6'
+ management.call_command(
+ 'create_tag_synonyms',
+ **options
+ )
+
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag1',
+ target_tag_name = 'tag6'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag2',
+ target_tag_name = 'tag6'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag3',
+ target_tag_name = 'tag6'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag4',
+ target_tag_name = 'tag6'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.filter(source_tag_name = 'tag5',
+ target_tag_name = 'tag6'
+ ).count(), 1)
+ self.assertEqual(models.TagSynonym.objects.count(), 5)
+
+ print 'done create_tag_synonym_test'
+
diff --git a/askbot/tests/post_model_tests.py b/askbot/tests/post_model_tests.py
index 703603c3..2e785802 100644
--- a/askbot/tests/post_model_tests.py
+++ b/askbot/tests/post_model_tests.py
@@ -546,7 +546,8 @@ class ThreadRenderCacheUpdateTests(AskbotTestCase):
})
self.assertEqual(2, Post.objects.count())
answer = Post.objects.get_answers()[0]
- self.assertRedirects(response=response, expected_url=answer.get_absolute_url())
+ expected_url=answer.get_absolute_url()
+ self.assertRedirects(response=response, expected_url=expected_url)
thread = answer.thread
self.assertEqual(1, thread.answer_count)
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index b2c8a788..d072cc00 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -1028,18 +1028,23 @@ def edit_group_membership(request):
action = form.cleaned_data['action']
#warning: possible race condition
if action == 'add':
- group_params = {'name': group_name, 'user': user}
- group = models.Group.objects.get_or_create(**group_params)
- request.user.edit_group_membership(user, group, 'add')
- template = get_template('widgets/group_snippet.html')
- return {
- 'name': group.name,
- 'description': getattr(group.tag_wiki, 'text', ''),
- 'html': template.render({'group': group})
- }
+ try:
+ group = models.Group.objects.get(name=group_name)
+ request.user.edit_group_membership(user, group, 'add')
+ template = get_template('widgets/group_snippet.html')
+ return {
+ 'name': group.name,
+ 'description': getattr(group.description, 'text', ''),
+ 'html': template.render({'group': group})
+ }
+ except models.Group.DoesNotExist:
+ raise exceptions.PermissionDenied(
+ _('Group %(name)s does not exist') % {'name': group_name}
+ )
+
elif action == 'remove':
try:
- group = models.Group.objects.get(group_name = group_name)
+ group = models.Group.objects.get(name = group_name)
request.user.edit_group_membership(user, group, 'remove')
except models.Group.DoesNotExist:
raise exceptions.PermissionDenied()