summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/const/__init__.py9
-rw-r--r--askbot/forms.py63
-rw-r--r--askbot/management/commands/send_email_alerts.py4
-rw-r--r--askbot/migrations/0126_add_groups_field__to__thread_and_post.py335
-rw-r--r--askbot/models/__init__.py46
-rw-r--r--askbot/models/post.py77
-rw-r--r--askbot/models/question.py134
-rw-r--r--askbot/models/tag.py7
-rw-r--r--askbot/skins/default/templates/answer_edit.html3
-rw-r--r--askbot/skins/default/templates/main_page/questions_loop.html8
-rw-r--r--askbot/skins/default/templates/question.html3
-rw-r--r--askbot/skins/default/templates/question/new_answer_form.html3
-rw-r--r--askbot/skins/default/templates/question_edit.html3
-rw-r--r--askbot/skins/default/templates/widgets/ask_form.html3
-rw-r--r--askbot/skins/default/templates/widgets/question_summary.html7
-rw-r--r--askbot/tests/__init__.py1
-rw-r--r--askbot/tests/post_model_tests.py2
-rw-r--r--askbot/tests/thread_model_tests.py40
-rw-r--r--askbot/tests/utils.py4
-rw-r--r--askbot/views/commands.py7
-rw-r--r--askbot/views/readers.py30
-rw-r--r--askbot/views/users.py11
-rw-r--r--askbot/views/writers.py19
23 files changed, 751 insertions, 68 deletions
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index 2bed1c31..2633055f 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -290,10 +290,11 @@ TYPE_RESPONSE = {
}
POST_STATUS = {
- 'closed' : _('[closed]'),
- 'deleted' : _('[deleted]'),
- 'default_version' : _('initial version'),
- 'retagged' : _('retagged'),
+ 'closed': _('[closed]'),
+ 'deleted': _('[deleted]'),
+ 'default_version': _('initial version'),
+ 'retagged': _('retagged'),
+ 'private': _('[private]')
}
#choices used in email and display filters
diff --git a/askbot/forms.py b/askbot/forms.py
index 5e4b5a06..5a8d75f1 100644
--- a/askbot/forms.py
+++ b/askbot/forms.py
@@ -626,7 +626,7 @@ class FeedbackForm(forms.Form):
return self.cleaned_data
-class FormWithHideableFields(object):
+class FormWithHideableFields(forms.Form):
"""allows to swap a field widget to HiddenInput() and back"""
def hide_field(self, name):
@@ -647,7 +647,35 @@ class FormWithHideableFields(object):
if name in self.__hidden_fields:
self.fields[name] = self.__hidden_fields.pop(name)
-class AskForm(forms.Form, FormWithHideableFields):
+class PostPrivatelyForm(FormWithHideableFields):
+ """has a single field `post_privately` with
+ two related methods"""
+
+ post_privately = forms.BooleanField(
+ label = _('keep private within your groups'),
+ required = False
+ )
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user', None)
+ self._user = user
+ super(PostPrivatelyForm, self).__init__(*args, **kwargs)
+ if self.allows_post_privately() == False:
+ self.hide_field('post_privately')
+
+ def allows_post_privately(self):
+ user = self._user
+ return (
+ user and user.is_authenticated() and \
+ user.can_make_group_private_posts()
+ )
+
+ def clean_post_privately(self):
+ if self.allows_post_privately() == False:
+ self.cleaned_data['post_privately'] = False
+ return self.cleaned_data['post_privately']
+
+
+class AskForm(PostPrivatelyForm):
"""the form used to askbot questions
field ask_anonymously is shown to the user if the
if ALLOW_ASK_ANONYMOUSLY live setting is True
@@ -667,6 +695,7 @@ class AskForm(forms.Form, FormWithHideableFields):
),
required = False,
)
+
openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
@@ -684,6 +713,7 @@ class AskForm(forms.Form, FormWithHideableFields):
self.cleaned_data['ask_anonymously'] = False
return self.cleaned_data['ask_anonymously']
+
ASK_BY_EMAIL_SUBJECT_HELP = _(
'Subject line is expected in the format: '
'[tag1, tag2, tag3,...] question title'
@@ -766,7 +796,7 @@ class AskByEmailForm(forms.Form):
raise forms.ValidationError(ASK_BY_EMAIL_SUBJECT_HELP)
return self.cleaned_data['subject']
-class AnswerForm(forms.Form):
+class AnswerForm(PostPrivatelyForm):
text = AnswerEditorField()
wiki = WikiField()
openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
@@ -822,7 +852,7 @@ class RevisionForm(forms.Form):
for r in revisions]
self.fields['revision'].initial = latest_revision.revision
-class EditQuestionForm(forms.Form, FormWithHideableFields):
+class EditQuestionForm(PostPrivatelyForm):
title = TitleField()
text = QuestionEditorField()
tags = TagNamesField()
@@ -842,7 +872,7 @@ class EditQuestionForm(forms.Form, FormWithHideableFields):
def __init__(self, *args, **kwargs):
"""populate EditQuestionForm with initial data"""
self.question = kwargs.pop('question')
- self.user = kwargs.pop('user')
+ self.user = kwargs['user']#preserve for superclass
revision = kwargs.pop('revision')
super(EditQuestionForm, self).__init__(*args, **kwargs)
self.fields['title'].initial = revision.title
@@ -853,6 +883,15 @@ class EditQuestionForm(forms.Form, FormWithHideableFields):
if not self.can_stay_anonymous():
self.hide_field('reveal_identity')
+ def has_changed(self):
+ if super(EditQuestionForm, self).has_changed():
+ return True
+ if askbot_settings.GROUPS_ENABLED:
+ return self.question.is_private() \
+ != self.cleaned_data['post_privately']
+ else:
+ return False
+
def can_stay_anonymous(self):
"""determines if the user cat keep editing the question
anonymously"""
@@ -930,16 +969,28 @@ class EditQuestionForm(forms.Form, FormWithHideableFields):
self.cleaned_data['stay_anonymous'] = stay_anonymous
return self.cleaned_data
-class EditAnswerForm(forms.Form):
+class EditAnswerForm(PostPrivatelyForm):
text = AnswerEditorField()
summary = SummaryField()
wiki = WikiField()
def __init__(self, answer, revision, *args, **kwargs):
+ self.answer = answer
super(EditAnswerForm, self).__init__(*args, **kwargs)
self.fields['text'].initial = revision.text
self.fields['wiki'].initial = answer.wiki
+ def has_changed(self):
+ #todo: this function is almost copy/paste of EditQuestionForm.has_changed()
+ if super(EditAnswerForm, self).has_changed():
+ return True
+ if askbot_settings.GROUPS_ENABLED:
+ return self.answer.is_private() \
+ != self.cleaned_data['post_privately']
+ else:
+ return False
+
+
class EditTagWikiForm(forms.Form):
text = forms.CharField(required = False)
tag_id = forms.IntegerField()
diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py
index e890452d..5d58276d 100644
--- a/askbot/management/commands/send_email_alerts.py
+++ b/askbot/management/commands/send_email_alerts.py
@@ -84,7 +84,7 @@ class Command(NoArgsCommand):
finally:
connection.close()
- def get_updated_questions_for_user(self,user):
+ def get_updated_questions_for_user(self, user):
"""
retreive relevant question updates for the user
according to their subscriptions and recorded question
@@ -353,7 +353,7 @@ class Command(NoArgsCommand):
else:
meta_data['new_q'] = False
- new_ans = Post.objects.get_answers().filter(
+ new_ans = Post.objects.get_answers(user).filter(
thread=q.thread,
added_at__gt=emailed_at,
deleted=False,
diff --git a/askbot/migrations/0126_add_groups_field__to__thread_and_post.py b/askbot/migrations/0126_add_groups_field__to__thread_and_post.py
new file mode 100644
index 00000000..ab8bf919
--- /dev/null
+++ b/askbot/migrations/0126_add_groups_field__to__thread_and_post.py
@@ -0,0 +1,335 @@
+# -*- 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 M2M table for field groups on 'Thread'
+ db.create_table('askbot_thread_groups', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('thread', models.ForeignKey(orm['askbot.thread'], null=False)),
+ ('tag', models.ForeignKey(orm['askbot.tag'], null=False))
+ ))
+ db.create_unique('askbot_thread_groups', ['thread_id', 'tag_id'])
+ # Adding M2M table for field groups on 'Post'
+ db.create_table('askbot_post_groups', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('post', models.ForeignKey(orm['askbot.post'], null=False)),
+ ('tag', models.ForeignKey(orm['askbot.tag'], null=False))
+ ))
+ db.create_unique('askbot_post_groups', ['post_id', 'tag_id'])
+
+ def backwards(self, orm):
+
+ # Removing M2M tables for field groups on 'Thread' and 'Post'
+ db.delete_table('askbot_thread_groups')
+ db.delete_table('askbot_post_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'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ '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'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ '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.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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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']"}),
+ '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'}),
+ 'is_private': ('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']"}),
+ 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ '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', [], {}),
+ 'revision_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ '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.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.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'}),
+ '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', 'to': "orm['askbot.Tag']"}),
+ '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']"}),
+ 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ '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.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.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_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': '30'}),
+ '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 4acbbdc7..6c6bb270 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -27,7 +27,7 @@ from askbot.models.question import Thread
from askbot.skins import utils as skin_utils
from askbot.models.question import QuestionView, AnonymousQuestion
from askbot.models.question import FavoriteQuestion
-from askbot.models.tag import Tag, MarkedTag
+from askbot.models.tag import Tag, MarkedTag, get_group_names, get_groups
from askbot.models.user import EmailFeedSetting, ActivityAuditStatus, Activity
from askbot.models.user import GroupMembership, GroupProfile
from askbot.models.post import Post, PostRevision, PostFlagReason, AnonymousAnswer
@@ -188,6 +188,9 @@ def user_get_avatar_url(self, size):
else:
return self.get_default_avatar_url(size)
+def user_get_user_groups(self):
+ """returns query set of groups to which user belongs"""
+
def user_update_avatar_type(self):
"""counts number of custom avatars
and if zero, sets avatar_type to False,
@@ -1405,6 +1408,7 @@ def user_post_question(
tags = None,
wiki = False,
is_anonymous = False,
+ is_private = False,
timestamp = None,
by_email = False,
email_address = None
@@ -1434,6 +1438,7 @@ def user_post_question(
added_at = timestamp,
wiki = wiki,
is_anonymous = is_anonymous,
+ is_private = is_private,
by_email = by_email,
email_address = email_address
)
@@ -1471,7 +1476,8 @@ def user_edit_post(self,
body_text = None,
revision_comment = None,
timestamp = None,
- by_email = False
+ by_email = False,
+ is_private = False
):
"""a simple method that edits post body
todo: unify it in the style of just a generic post
@@ -1498,7 +1504,8 @@ def user_edit_post(self,
body_text = body_text,
timestamp = timestamp,
revision_comment = revision_comment,
- by_email = by_email
+ by_email = by_email,
+ is_private = is_private
)
elif post.post_type == 'tag_wiki':
post.apply_edit(
@@ -1523,6 +1530,7 @@ def user_edit_question(
tags = None,
wiki = False,
edit_anonymously = False,
+ is_private = False,
timestamp = None,
force = False,#if True - bypass the assert
by_email = False
@@ -1540,6 +1548,7 @@ def user_edit_question(
tags = tags,
wiki = wiki,
edit_anonymously = edit_anonymously,
+ is_private = is_private,
by_email = by_email
)
@@ -1559,6 +1568,7 @@ def user_edit_answer(
body_text = None,
revision_comment = None,
wiki = False,
+ is_private = False,
timestamp = None,
force = False,#if True - bypass the assert
by_email = False
@@ -1629,6 +1639,7 @@ def user_post_answer(
body_text = None,
follow = False,
wiki = False,
+ is_private = False,
timestamp = None,
by_email = False
):
@@ -1696,6 +1707,13 @@ def user_post_answer(
wiki = wiki,
by_email = by_email
)
+
+ if self.can_make_group_private_posts():
+ if is_private:
+ answer_post.make_private(self)
+ else:
+ answer_post.make_public(self)
+
answer_post.thread.invalidate_cached_data()
award_badges_signal.send(None,
event = 'post_answer',
@@ -2067,6 +2085,21 @@ def get_profile_link(self):
return mark_safe(profile_link)
+def user_get_groups(self):
+ """returns a query set of groups to which user belongs"""
+ #todo: maybe cache this query
+ return Tag.group_tags.get_for_user(self)
+
+def user_get_foreign_groups(self):
+ """returns a query set of groups to which user does not belong"""
+ #todo: maybe cache this query
+ user_group_ids = self.get_groups().values_list('id', flat = True)
+ return get_groups().exclude(id__in = user_group_ids)
+
+def user_can_make_group_private_posts(self):
+ """simplest implementation: user belongs to at least one group"""
+ return self.get_groups().count() > 0
+
def user_get_groups_membership_info(self, groups):
"""returts a defaultdict with values that are
dictionaries with the following keys and values:
@@ -2483,6 +2516,8 @@ User.add_to_class('get_absolute_url', user_get_absolute_url)
User.add_to_class('get_avatar_url', user_get_avatar_url)
User.add_to_class('get_default_avatar_url', user_get_default_avatar_url)
User.add_to_class('get_gravatar_url', user_get_gravatar_url)
+User.add_to_class('get_groups', user_get_groups)
+User.add_to_class('get_foreign_groups', user_get_foreign_groups)
User.add_to_class('strip_email_signature', user_strip_email_signature)
User.add_to_class('get_groups_membership_info', user_get_groups_membership_info)
User.add_to_class('get_anonymous_name', user_get_anonymous_name)
@@ -2529,6 +2564,7 @@ User.add_to_class('can_create_tags', user_can_create_tags)
User.add_to_class('can_have_strong_url', user_can_have_strong_url)
User.add_to_class('can_post_by_email', user_can_post_by_email)
User.add_to_class('can_post_comment', user_can_post_comment)
+User.add_to_class('can_make_group_private_posts', user_can_make_group_private_posts)
User.add_to_class('is_administrator', user_is_administrator)
User.add_to_class('is_administrator_or_moderator', user_is_administrator_or_moderator)
User.add_to_class('set_admin_status', user_set_admin_status)
@@ -3310,5 +3346,7 @@ __all__ = [
'ReplyAddress',
'get_model',
- 'get_admins_and_moderators'
+ 'get_admins_and_moderators',
+ 'get_group_names',
+ 'get_grous'
]
diff --git a/askbot/models/post.py b/askbot/models/post.py
index 673b4c6f..c895084d 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -24,7 +24,8 @@ import askbot
from askbot.utils.slug import slugify
from askbot import const
from askbot.models.user import EmailFeedSetting
-from askbot.models.tag import Tag, MarkedTag, tags_match_some_wildcard
+from askbot.models.tag import Tag, MarkedTag
+from askbot.models.tag import get_groups, tags_match_some_wildcard
from askbot.conf import settings as askbot_settings
from askbot import exceptions
from askbot.utils import markup
@@ -142,8 +143,21 @@ class PostManager(BaseQuerySetManager):
def get_questions(self):
return self.filter(post_type='question')
- def get_answers(self):
- return self.filter(post_type='answer')
+ def get_answers(self, user = None):
+ """returns query set of answer posts,
+ optionally filtered to exclude posts of groups
+ to which user does not belong"""
+ answers = self.filter(post_type='answer')
+
+ if askbot_settings.GROUPS_ENABLED:
+ if user is None or user.is_anonymous():
+ exclude_groups = get_groups()
+ else:
+ exclude_groups = user.get_foreign_groups()
+ answers = answers.exclude(groups__in = exclude_groups)
+
+ return answers
+
def get_comments(self):
return self.filter(post_type='comment')
@@ -288,6 +302,7 @@ class Post(models.Model):
parent = models.ForeignKey('Post', blank=True, null=True, related_name='comments') # Answer or Question for Comment
thread = models.ForeignKey('Thread', blank=True, null=True, default = None, related_name='posts')
+ groups = models.ManyToManyField('Tag', related_name = 'group_posts')#used for group-private posts
author = models.ForeignKey(User, related_name='posts')
added_at = models.DateTimeField(default=datetime.datetime.now)
@@ -511,6 +526,24 @@ class Post(models.Model):
def is_reject_reason(self):
return self.post_type == 'reject_reason'
+ def make_private(self, user):
+ """makes post private within user's groups"""
+ groups = user.get_groups()
+ self.groups.add(*groups)
+ if self.is_question():
+ self.thread.groups.add(*groups)
+
+ def make_public(self, user):
+ """removes the privacy mark from users groups"""
+ groups = user.get_groups()
+ self.groups.remove(*groups)
+ if self.is_question():
+ self.thread.groups.remove(*groups)
+
+ def is_private(self):
+ """true, if post is private within any groups"""
+ return askbot_settings.GROUPS_ENABLED and self.groups.count() > 0
+
def needs_moderation(self):
return self.approved == False
@@ -900,7 +933,11 @@ class Post(models.Model):
#4) question asked by me (todo: not "edited_by_me" ???)
question_author = origin_post.author
- if EmailFeedSetting.objects.filter(subscriber = question_author, frequency = 'i', feed_type = 'q_ask').exists():
+ if EmailFeedSetting.objects.filter(
+ subscriber = question_author,
+ frequency = 'i',
+ feed_type = 'q_ask'
+ ).exists():
subscriber_set.add(question_author)
#4) questions answered by me -make sure is that people
@@ -1262,8 +1299,32 @@ class Post(models.Model):
)
raise exceptions.AnswerHidden(message)
+ def assert_is_visible_to_user_groups(self, user):
+ """raises permission denied of the post
+ is hidden due to group memberships"""
+ assert(self.is_comment() == False)
+ post_groups = self.groups.all()
+ if post_groups.count() == 0:
+ return
+
+ if self.is_question():#todo maybe merge the "hidden" exceptions
+ exception = exceptions.QuestionHidden
+ elif self.is_answer():
+ exception = exceptions.AnswerHidden
+ else:
+ raise NotImplementedError
+
+ message = _('This post is temporarily not available')
+ if user.is_anonymous():
+ raise exception(message)
+ else:
+ user_groups_ids = user.get_groups().values_list('id', flat = True)
+ if post_groups.filter(id__in = user_groups_ids).count() == 0:
+ raise exception(message)
def assert_is_visible_to(self, user):
+ if self.is_comment() == False and askbot_settings.GROUPS_ENABLED:
+ self.assert_is_visible_to_user_groups(user)
if self.is_question():
return self._question__assert_is_visible_to(user)
elif self.is_answer():
@@ -1371,7 +1432,7 @@ class Post(models.Model):
def _question__apply_edit(self, edited_at=None, edited_by=None, title=None,\
text=None, comment=None, tags=None, wiki=False,\
- edit_anonymously = False,
+ edit_anonymously = False, is_private = False,
by_email = False
):
@@ -1395,6 +1456,12 @@ class Post(models.Model):
self.thread.tagnames = tags
self.thread.save()
+ if self.is_private() != is_private:
+ if is_private:
+ self.make_private(self.author)
+ else:
+ self.make_public(self.author)
+
self.__apply_edit(
edited_at = edited_at,
edited_by = edited_by,
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 13cbf65d..2e8faa06 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -15,8 +15,9 @@ 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.base import AnonymousContent
+from askbot.models.tag import Tag, get_groups
+from askbot.models.base import AnonymousContent, BaseQuerySetManager
+from askbot.models.tag import Tag, get_groups
from askbot.models.post import Post, PostRevision
from askbot.models import signals
from askbot import const
@@ -26,8 +27,21 @@ from askbot.utils.slug import slugify
from askbot.skins.loaders import get_template #jinja2 template loading enviroment
from askbot.search.state_manager import DummySearchState
+class ThreadQuerySet(models.query.QuerySet):
+ def exclude_group_private(self, user):
+ """filters out threads not belonging to the user groups"""
+ if user.is_authenticated():
+ groups = user.get_foreign_groups()
+ else:
+ groups = get_groups()
+ #todo: maybe use is_private field
+ return self.exclude(groups__in = groups)
+
+class ThreadManager(BaseQuerySetManager):
+
+ def get_query_set(self):
+ return ThreadQuerySet(self.model)
-class ThreadManager(models.Manager):
def get_tag_summary_from_threads(self, threads):
"""returns a humanized string containing up to
five most frequently used
@@ -73,6 +87,7 @@ class ThreadManager(models.Manager):
text,
tagnames = None,
is_anonymous = False,
+ is_private = False,
by_email = False,
email_address = None
):
@@ -122,6 +137,9 @@ class ThreadManager(models.Manager):
email_address = email_address
)
+ if is_private:#add groups to thread and question
+ thread.make_private(author)
+
# INFO: Question has to be saved before update_tags() is called
thread.update_tags(tagnames = tagnames, user = author, timestamp = added_at)
@@ -175,15 +193,39 @@ class ThreadManager(models.Manager):
meta_data = {}
+ #run text search while excluding any modifier in the search string
+ #like #tag [title: something] @user
if search_state.stripped_query:
qs = self.get_for_query(search_query=search_state.stripped_query, qs=qs)
+
+ #we run other things after full text search, because
+ #FTS may break the chain of the query set calls,
+ #since it might go into an external asset, like Solr
+
+ #if groups feature is enabled, filter out threads
+ #that are private in groups to which current user does not belong
+ if askbot_settings.GROUPS_ENABLED:
+ #get group names
+ qs = self.exclude_group_private(user = request_user)
+
+ #search in titles, if necessary
if search_state.query_title:
qs = qs.filter(title__icontains = search_state.query_title)
+
+ #search user names if @user is added to search string
+ #or if user name exists in the search state
if search_state.query_users:
query_users = User.objects.filter(username__in=search_state.query_users)
if query_users:
- qs = qs.filter(posts__post_type='question', posts__author__in=query_users) # TODO: unify with search_state.author ?
-
+ qs = qs.filter(
+ posts__post_type='question',
+ posts__author__in=query_users
+ ) # TODO: unify with search_state.author ?
+
+ #unified tags - is list of tags taken from the tag selection
+ #plus any tags added to the query string with #tag or [tag:something]
+ #syntax.
+ #run tag search in addition to these unified tags
tags = search_state.unified_tags()
if len(tags) > 0:
@@ -214,6 +256,8 @@ class ThreadManager(models.Manager):
if search_state.scope == 'unanswered':
qs = qs.filter(closed = False) # Do not show closed questions in unanswered section
if askbot_settings.UNANSWERED_QUESTION_MEANING == 'NO_ANSWERS':
+ # todo: this will introduce a problem if there are private answers
+ # which are counted here
qs = qs.filter(answer_count=0) # TODO: expand for different meanings of this
elif askbot_settings.UNANSWERED_QUESTION_MEANING == 'NO_ACCEPTED_ANSWERS':
qs = qs.filter(accepted_answer__isnull=True)
@@ -319,8 +363,13 @@ class ThreadManager(models.Manager):
# Precache data only for non-cached threads - only those will be rendered
#threads = [thread for thread in threads if not thread.summary_html_cached()]
- page_questions = Post.objects.filter(post_type='question', thread__in=[obj.id for obj in threads])\
- .only('id', 'thread', 'score', 'is_anonymous', 'summary', 'post_type', 'deleted') # pick only the used fields
+ thread_ids = [obj.id for obj in threads]
+ page_questions = Post.objects.filter(
+ post_type='question', thread__id__in = thread_ids
+ ).only(# pick only the used fields
+ 'id', 'thread', 'score', 'is_anonymous',
+ 'summary', 'post_type', 'deleted'
+ )
page_question_map = {}
for pq in page_questions:
page_question_map[pq.thread_id] = pq
@@ -374,6 +423,7 @@ class Thread(models.Model):
title = models.CharField(max_length=300)
tags = models.ManyToManyField('Tag', related_name='threads')
+ groups = models.ManyToManyField('Tag', related_name='group_threads')
# Denormalised data, transplanted from Question
tagnames = models.CharField(max_length=125)
@@ -425,6 +475,15 @@ class Thread(models.Model):
#question_id = self._question_post().id
#return reverse('question', args = [question_id]) + slugify(self.title)
+ def get_answer_count(self, user = None):
+ """returns answer count depending on who the user is.
+ When user groups are enabled and some answers are hidden,
+ the answer count to show must be reflected accordingly"""
+ if askbot_settings.GROUPS_ENABLED == False or user is None:
+ return self.answer_count
+ else:
+ return self.get_answers(user).count()
+
def update_favorite_count(self):
self.favourite_count = FavoriteQuestion.objects.filter(thread=self).count()
self.save()
@@ -474,10 +533,13 @@ class Thread(models.Model):
def get_title(self, question=None):
if not question:
question = self._question_post() # allow for optimization if the caller has already fetched the question post for this thread
- if self.closed:
+ if self.is_private():
+ attr = const.POST_STATUS['private']
+ elif self.closed:
attr = const.POST_STATUS['closed']
elif question.deleted:
attr = const.POST_STATUS['deleted']
+
else:
attr = None
if attr is not None:
@@ -514,12 +576,12 @@ class Thread(models.Model):
return self.posts.get_answers().filter(deleted=False)
else:
if user.is_administrator() or user.is_moderator():
- return self.posts.get_answers()
+ return self.posts.get_answers(user = user)
else:
- return self.posts.get_answers().filter(
- models.Q(deleted = False) | models.Q(author = user) \
- | models.Q(deleted_by = user)
- )
+ return self.posts.get_answers(user = user).filter(
+ models.Q(deleted = False) | models.Q(author = user) \
+ | models.Q(deleted_by = user)
+ )
def invalidate_cached_thread_content_fragment(self):
cache.cache.delete(self.SUMMARY_CACHE_KEY_TPL % self.id)
@@ -540,9 +602,12 @@ class Thread(models.Model):
#self.invalidate_cached_thread_content_fragment()
self.update_summary_html()
- def get_cached_post_data(self, sort_method = 'votes'):
+ def get_cached_post_data(self, user = None, sort_method = 'votes'):
"""returns cached post data, as calculated by
the method get_post_data()"""
+ if askbot_settings.GROUPS_ENABLED:
+ #temporary plug: bypass cache where groups are enabled
+ return self.get_post_data(sort_method = sort_method, user = user)
key = self.get_post_data_cache_key(sort_method)
post_data = cache.cache.get(key)
if not post_data:
@@ -550,14 +615,22 @@ class Thread(models.Model):
cache.cache.set(key, post_data, const.LONG_TIME)
return post_data
- def get_post_data(self, sort_method = 'votes'):
+ def get_post_data(self, sort_method = 'votes', user = None):
"""returns question, answers as list and a list of post ids
for the given thread
the returned posts are pre-stuffed with the comments
all (both posts and the comments sorted in the correct
order)
"""
- thread_posts = self.posts.all().order_by(
+ thread_posts = self.posts.all()
+ if askbot_settings.GROUPS_ENABLED:
+ if user.is_anonymous():
+ exclude_groups = get_groups()
+ else:
+ exclude_groups = user.get_foreign_groups()
+ thread_posts = thread_posts.exclude(groups__in = exclude_groups)
+
+ thread_posts = thread_posts.order_by(
{
'latest':'-added_at',
'oldest':'added_at',
@@ -700,6 +773,14 @@ class Thread(models.Model):
return self.followed_by.filter(id = user.id).count() > 0
return False
+ def make_private(self, user):
+ groups = list(user.get_groups())
+ self.groups.add(*groups)
+ self._question_post().groups.add(*groups)
+
+ def is_private(self):
+ return askbot_settings.GROUPS_ENABLED and self.groups.count() > 0
+
def update_tags(self, tagnames = None, user = None, timestamp = None):
"""
Updates Tag associations for a thread to match the given
@@ -865,11 +946,13 @@ class Thread(models.Model):
return last_updated_at, last_updated_by
- def get_summary_html(self, search_state):
- html = self.get_cached_summary_html()
+ def get_summary_html(self, search_state, visitor = None):
+ html = self.get_cached_summary_html(visitor)
if not html:
- html = self.update_summary_html()
+ html = self.update_summary_html(visitor)
+ # todo: this work may be pushed onto javascript we post-process tag names
+ # in the snippet so that tag urls match the search state
# use `<<<` and `>>>` because they cannot be confused with user input
# - if user accidentialy types <<<tag-name>>> into question title or body,
# then in html it'll become escaped like this: &lt;&lt;&lt;tag-name&gt;&gt;&gt;
@@ -889,14 +972,21 @@ class Thread(models.Model):
return html
- def get_cached_summary_html(self):
+ def get_cached_summary_html(self, visitor = None):
+ #todo: remove this plug by adding cached foreign user group
+ #parameter to the key. Now with groups on caching is turned off
+ #parameter visitor is there to get summary out by the user groups
+ if askbot_settings.GROUPS_ENABLED:
+ return None
return cache.cache.get(self.SUMMARY_CACHE_KEY_TPL % self.id)
- def update_summary_html(self):
+ def update_summary_html(self, visitor = None):
context = {
'thread': self,
- 'question': self._question_post(refresh=True), # fetch new question post to make sure we're up-to-date
+ #fetch new question post to make sure we're up-to-date
+ 'question': self._question_post(refresh=True),
'search_state': DummySearchState(),
+ 'visitor': visitor
}
html = get_template('widgets/question_summary.html').render(context)
# INFO: Timeout is set to 30 days:
diff --git a/askbot/models/tag.py b/askbot/models/tag.py
index 0c3af52f..0546ab50 100644
--- a/askbot/models/tag.py
+++ b/askbot/models/tag.py
@@ -185,3 +185,10 @@ class MarkedTag(models.Model):
class Meta:
app_label = 'askbot'
+
+def get_groups():
+ return Tag.group_tags.get_all()
+
+def get_group_names():
+ #todo: cache me
+ return get_groups().values_list('name', flat = True)
diff --git a/askbot/skins/default/templates/answer_edit.html b/askbot/skins/default/templates/answer_edit.html
index bbc00420..f19ae016 100644
--- a/askbot/skins/default/templates/answer_edit.html
+++ b/askbot/skins/default/templates/answer_edit.html
@@ -20,6 +20,9 @@
{% if settings.WIKI_ON and answer.wiki == False %}
{{ macros.checkbox_in_div(form.wiki) }}
{% endif %}
+ {% if request.user.is_authenticated() and request.user.can_make_group_private_posts() %}
+ {{ macros.checkbox_in_div(form.post_privately) }}
+ {% endif %}
<div class="after-editor">
<input type="submit" value="{% trans %}Save edit{% endtrans %}" class="submit" />&nbsp;
<input type="button" value="{% trans %}Cancel{% endtrans %}" class="submit" onclick="history.back(-1);" />
diff --git a/askbot/skins/default/templates/main_page/questions_loop.html b/askbot/skins/default/templates/main_page/questions_loop.html
index 6a5e5e3d..ad7bf683 100644
--- a/askbot/skins/default/templates/main_page/questions_loop.html
+++ b/askbot/skins/default/templates/main_page/questions_loop.html
@@ -1,12 +1,10 @@
{% import "macros.html" as macros %}
-{# cache 0 "questions" questions search_tags scope sort query context.page language_code #}
-{% for thread in threads.object_list %}
- {# {{macros.question_summary(thread, thread._question_post(), search_state=search_state)}} #}
- {{ thread.get_summary_html(search_state=search_state) }}
-{% endfor %}
{% if threads.object_list|length == 0 %}
{% include "main_page/nothing_found.html" %}
{% else %}
+ {% for thread in threads.object_list %}
+ {{ thread.get_summary_html(search_state=search_state, visitor = request.user) }}
+ {% endfor %}
<div class="evenMore">
{% trans %}Did not find what you were looking for?{% endtrans %}
<a href="{% url ask %}">{% trans %}Please, post your question!{% endtrans %}</a>
diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html
index 5403a644..511c5086 100644
--- a/askbot/skins/default/templates/question.html
+++ b/askbot/skins/default/templates/question.html
@@ -56,6 +56,9 @@
if (data['userIsAdminOrMod']){
return;//all functions on
}
+ if (data['user_posts'] === undefined) {
+ return;
+ }
if (post_id in data['user_posts']){
//todo: remove edit button from older comments
return;//same here
diff --git a/askbot/skins/default/templates/question/new_answer_form.html b/askbot/skins/default/templates/question/new_answer_form.html
index 68af8afb..347973bf 100644
--- a/askbot/skins/default/templates/question/new_answer_form.html
+++ b/askbot/skins/default/templates/question/new_answer_form.html
@@ -47,6 +47,9 @@
{% if settings.WIKI_ON %}
{{ macros.checkbox_in_div(answer.wiki) }}
{% endif %}
+ {% if request.user.is_authenticated() and request.user.can_make_group_private_posts() %}
+ {{ macros.checkbox_in_div(answer.post_privately) }}
+ {% endif %}
{% endif %}
{% endif %}
</form>
diff --git a/askbot/skins/default/templates/question_edit.html b/askbot/skins/default/templates/question_edit.html
index 2576a1f1..a4ffa424 100644
--- a/askbot/skins/default/templates/question_edit.html
+++ b/askbot/skins/default/templates/question_edit.html
@@ -34,6 +34,9 @@
{% if form.can_stay_anonymous() %}
{{ macros.checkbox_in_div(form.reveal_identity) }}
{% endif %}
+ {% if request.user.is_authenticated() and request.user.can_make_group_private_posts() %}
+ {{ macros.checkbox_in_div(form.post_privately) }}
+ {% endif %}
</div>
<input type="submit" value="{% trans %}Save edit{% endtrans %}" class="submit" />&nbsp;
<input type="button" value="{% trans %}Cancel{% endtrans %}" class="submit" onclick="history.back(-1);" />
diff --git a/askbot/skins/default/templates/widgets/ask_form.html b/askbot/skins/default/templates/widgets/ask_form.html
index 94b5e309..ae71b230 100644
--- a/askbot/skins/default/templates/widgets/ask_form.html
+++ b/askbot/skins/default/templates/widgets/ask_form.html
@@ -37,6 +37,9 @@
{% if settings.ALLOW_ASK_ANONYMOUSLY %}
{{ macros.checkbox_in_div(form.ask_anonymously) }}
{% endif %}
+ {% if request.user.is_authenticated() and request.user.can_make_group_private_posts() %}
+ {{ macros.checkbox_in_div(form.post_privately) }}
+ {% endif %}
</div>
{% if not request.user.is_authenticated() %}
<input type="submit" name="post_anon" value="{% trans %}Login/Signup to Post{% endtrans %}" class="submit" />
diff --git a/askbot/skins/default/templates/widgets/question_summary.html b/askbot/skins/default/templates/widgets/question_summary.html
index c6e7bc5d..22f52a42 100644
--- a/askbot/skins/default/templates/widgets/question_summary.html
+++ b/askbot/skins/default/templates/widgets/question_summary.html
@@ -12,8 +12,9 @@
{% trans cnt=thread.view_count %}view{% pluralize %}views{% endtrans %}
</div>
</div>
+ {% set answer_count = thread.get_answer_count(visitor) %}
<div class="answers
- {% if thread.answer_count == 0 -%}
+ {% if answer_count == 0 -%}
no-answers
{% else -%}
{%- if thread.accepted_answer_id -%} {# INFO: Use _id to not fetch the whole answer post #}
@@ -24,9 +25,9 @@
{%- endif -%}">
<span
class="item-count"
- >{{thread.answer_count|humanize_counter}}{% if thread.accepted_answer_id %}{% endif %}</span>
+ >{{ answer_count|humanize_counter }}{% if thread.accepted_answer_id %}{% endif %}</span>
<div>
- {% trans cnt=thread.answer_count %}answer{% pluralize %}answers{% endtrans %}
+ {% trans cnt = answer_count %}answer{% pluralize %}answers{% endtrans %}
</div>
</div>
<div class="votes
diff --git a/askbot/tests/__init__.py b/askbot/tests/__init__.py
index 198bf9e8..fbf32726 100644
--- a/askbot/tests/__init__.py
+++ b/askbot/tests/__init__.py
@@ -14,5 +14,6 @@ from askbot.tests.templatefilter_tests import *
from askbot.tests.markup_test import *
from askbot.tests.misc_tests import *
from askbot.tests.post_model_tests import *
+from askbot.tests.thread_model_tests import *
from askbot.tests.reply_by_email_tests import *
from askbot.tests.category_tree_tests import CategoryTreeTests
diff --git a/askbot/tests/post_model_tests.py b/askbot/tests/post_model_tests.py
index 9a4d47c8..ac09c667 100644
--- a/askbot/tests/post_model_tests.py
+++ b/askbot/tests/post_model_tests.py
@@ -418,7 +418,7 @@ class ThreadRenderLowLevelCachingTests(AskbotTestCase):
###
cache.cache.delete(key)
- thread.update_summary_html = lambda: "Monkey-patched <<<tag2>>>"
+ thread.update_summary_html = lambda dummy: "Monkey-patched <<<tag2>>>"
self.assertFalse(thread.summary_html_cached())
self.assertIsNone(thread.get_cached_summary_html())
diff --git a/askbot/tests/thread_model_tests.py b/askbot/tests/thread_model_tests.py
new file mode 100644
index 00000000..634a4b0a
--- /dev/null
+++ b/askbot/tests/thread_model_tests.py
@@ -0,0 +1,40 @@
+from askbot.tests.utils import AskbotTestCase
+from askbot.conf import settings as askbot_settings
+from askbot import models
+
+class ThreadModelTestsWithGroupsEnabled(AskbotTestCase):
+
+ def setUp(self):
+ self.groups_enabled_backup = askbot_settings.GROUPS_ENABLED
+ askbot_settings.update('GROUPS_ENABLED', True)
+ self.admin = self.create_user('admin', status = 'd')
+ self.user = self.create_user('user')
+ self.group = models.Tag.group_tags.get_or_create(
+ group_name = 'jockeys', user = self.admin
+ )
+ self.admin.edit_group_membership(
+ group = self.group,
+ user = self.admin,
+ action = 'add'
+ )
+
+ def tearDown(self):
+ askbot_settings.update('GROUPS_ENABLED', self.groups_enabled_backup)
+
+ def test_private_answer(self):
+ # post question, answer, add answer to the group
+ self.question = self.post_question(self.user)
+
+ self.answer = self.post_answer(
+ user = self.admin,
+ question = self.question,
+ is_private = True
+ )
+
+ thread = self.question.thread
+
+ #test answer counts
+ self.assertEqual(thread.get_answer_count(self.user), 0)
+ self.assertEqual(thread.get_answer_count(self.admin), 1)
+
+ #test mail outbox
diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py
index 4bc69ac4..7b4134d1 100644
--- a/askbot/tests/utils.py
+++ b/askbot/tests/utils.py
@@ -118,6 +118,7 @@ class AskbotTestCase(TestCase):
by_email = False,
wiki = False,
is_anonymous = False,
+ is_private = False,
follow = False,
timestamp = None,
):
@@ -139,6 +140,7 @@ class AskbotTestCase(TestCase):
by_email = by_email,
wiki = wiki,
is_anonymous = is_anonymous,
+ is_private = is_private,
timestamp = timestamp
)
@@ -160,6 +162,7 @@ class AskbotTestCase(TestCase):
by_email = False,
follow = False,
wiki = False,
+ is_private = False,
timestamp = None
):
@@ -171,6 +174,7 @@ class AskbotTestCase(TestCase):
by_email = by_email,
follow = follow,
wiki = wiki,
+ is_private = is_private,
timestamp = timestamp
)
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index a1c13708..979309ba 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -15,6 +15,7 @@ from django.forms import ValidationError, IntegerField, CharField
from django.shortcuts import get_object_or_404
from django.views.decorators import csrf
from django.utils import simplejson
+from django.utils.html import escape
from django.utils.translation import ugettext as _
from django.utils.translation import string_concat
from askbot import models
@@ -496,7 +497,7 @@ def get_tag_list(request):
).values_list(
'name', flat = True
)
- output = '\n'.join(tag_names)
+ output = '\n'.join(map(escape, tag_names))
return HttpResponse(output, mimetype = 'text/plain')
@decorators.get_only
@@ -670,7 +671,7 @@ def api_get_questions(request):
thread_list = [{
'url': thread.get_absolute_url(),
'title': thread.title,
- 'answer_count': thread.answer_count
+ 'answer_count': thread.get_answer_count(request.user)
} for thread in threads]
json_data = simplejson.dumps(thread_list)
return HttpResponse(json_data, mimetype = "application/json")
@@ -767,7 +768,7 @@ def swap_question_with_answer(request):
"""
if request.user.is_authenticated():
if request.user.is_administrator() or request.user.is_moderator():
- answer = models.Post.objects.get_answers().get(id = request.POST['answer_id'])
+ answer = models.Post.objects.get_answers(request.user).get(id = request.POST['answer_id'])
new_question = answer.swap_with_question(new_title = request.POST['new_title'])
return {
'id': new_question.id,
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index d27846e3..7782c7e6 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -73,10 +73,15 @@ def questions(request, **kwargs):
if request.method != 'GET':
return HttpResponseNotAllowed(['GET'])
- search_state = SearchState(user_logged_in=request.user.is_authenticated(), **kwargs)
+ search_state = SearchState(
+ user_logged_in=request.user.is_authenticated(),
+ **kwargs
+ )
page_size = int(askbot_settings.DEFAULT_QUESTIONS_PAGE_SIZE)
- qs, meta_data = models.Thread.objects.run_advanced_search(request_user=request.user, search_state=search_state)
+ qs, meta_data = models.Thread.objects.run_advanced_search(
+ request_user=request.user, search_state=search_state
+ )
if meta_data['non_existing_tags']:
search_state = search_state.remove_tags(meta_data['non_existing_tags'])
@@ -85,19 +90,25 @@ def questions(request, **kwargs):
if paginator.num_pages < search_state.page:
search_state.page = 1
page = paginator.page(search_state.page)
-
- page.object_list = list(page.object_list) # evaluate queryset
+ page.object_list = list(page.object_list) # evaluate the queryset
# INFO: Because for the time being we need question posts and thread authors
# down the pipeline, we have to precache them in thread objects
models.Thread.objects.precache_view_data_hack(threads=page.object_list)
- related_tags = Tag.objects.get_related_to_search(threads=page.object_list, ignored_tag_names=meta_data.get('ignored_tag_names', []))
+ related_tags = Tag.objects.get_related_to_search(
+ threads=page.object_list,
+ ignored_tag_names=meta_data.get('ignored_tag_names',[])
+ )
tag_list_type = askbot_settings.TAG_LIST_FORMAT
if tag_list_type == 'cloud': #force cloud to sort by name
related_tags = sorted(related_tags, key = operator.attrgetter('name'))
- contributors = list(models.Thread.objects.get_thread_contributors(thread_list=page.object_list).only('id', 'username', 'gravatar'))
+ contributors = list(
+ models.Thread.objects.get_thread_contributors(
+ thread_list=page.object_list
+ ).only('id', 'username', 'gravatar')
+ )
paginator_context = {
'is_paginated' : (paginator.count > page_size),
@@ -433,8 +444,11 @@ def question(request, id):#refactor - long subroutine. display question body, an
#posts are pre-stuffed with the correctly ordered comments
updated_question_post, answers, post_to_author = thread.get_cached_post_data(
sort_method = answer_sort_method,
+ user = request.user
)
- question_post.set_cached_comments(updated_question_post.get_cached_comments())
+ question_post.set_cached_comments(
+ updated_question_post.get_cached_comments()
+ )
#Post.objects.precache_comments(for_posts=[question_post] + answers, visitor=request.user)
@@ -539,7 +553,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
'thread': thread,
'answer' : answer_form,
'answers' : page_objects.object_list,
- 'answer_count': len(answers),
+ 'answer_count': thread.get_answer_count(request.user),
'category_tree_data': askbot_settings.CATEGORY_TREE,
'user_votes': user_votes,
'user_post_id_list': user_post_id_list,
diff --git a/askbot/views/users.py b/askbot/views/users.py
index 9d9419e1..d8e0fb6e 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -341,14 +341,19 @@ def user_stats(request, user, context):
#
# Top answers
#
- top_answers = user.posts.get_answers().filter(
+ top_answers = user.posts.get_answers(
+ request.user
+ ).filter(
deleted=False,
thread__posts__deleted=False,
thread__posts__post_type='question',
- ).select_related('thread').order_by('-score', '-added_at')[:100]
+ ).select_related(
+ 'thread'
+ ).order_by(
+ '-score', '-added_at'
+ )[:100]
top_answer_count = len(top_answers)
-
#
# Votes
#
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index b5beb65b..dee1ee7b 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -217,6 +217,7 @@ def ask(request):#view used to ask a new question
tagnames = form.cleaned_data['tags']
text = form.cleaned_data['text']
ask_anonymously = form.cleaned_data['ask_anonymously']
+ post_privately = form.cleaned_data['post_privately']
if request.user.is_authenticated():
try:
@@ -226,6 +227,7 @@ def ask(request):#view used to ask a new question
tags = tagnames,
wiki = wiki,
is_anonymous = ask_anonymously,
+ is_private = post_privately,
timestamp = timestamp
)
return HttpResponseRedirect(question.get_absolute_url())
@@ -258,7 +260,8 @@ def ask(request):#view used to ask a new question
'text': request.REQUEST.get('text', ''),
'tags': request.REQUEST.get('tags', ''),
'wiki': request.REQUEST.get('wiki', False),
- 'is_anonymous': request.REQUEST.get('is_anonymous', False),
+ 'ask_anonymously': request.REQUEST.get('ask_anonymousy', False),
+ 'post_privately': request.REQUEST.get('post_privately', False)
}
data = {
@@ -379,6 +382,7 @@ def edit_question(request, id):
is_anon_edit = form.cleaned_data['stay_anonymous']
is_wiki = form.cleaned_data.get('wiki', question.wiki)
+ post_privately = form.cleaned_data['post_privately']
request.user.edit_question(
question = question,
@@ -388,15 +392,21 @@ def edit_question(request, id):
tags = form.cleaned_data['tags'],
wiki = is_wiki,
edit_anonymously = is_anon_edit,
+ is_private = post_privately
)
return HttpResponseRedirect(question.get_absolute_url())
else:
#request type was "GET"
revision_form = forms.RevisionForm(question, latest_revision)
+ initial = {
+ 'post_privately': question.is_private(),
+ 'wiki': question.wiki
+ }
form = forms.EditQuestionForm(
question = question,
revision = latest_revision,
- user = request.user
+ user = request.user,
+ initial = initial
)
data = {
@@ -458,12 +468,15 @@ def edit_answer(request, id):
body_text = form.cleaned_data['text'],
revision_comment = form.cleaned_data['summary'],
wiki = form.cleaned_data.get('wiki', answer.wiki),
+ is_private = form.cleaned_data.get('is_private', False)
#todo: add wiki field to form
)
return HttpResponseRedirect(answer.get_absolute_url())
else:
revision_form = forms.RevisionForm(answer, latest_revision)
form = forms.EditAnswerForm(answer, latest_revision)
+ if request.user.can_make_group_private_posts():
+ form.initial['post_privately'] = answer.is_private()
data = {
'page_class': 'edit-answer-page',
'active_tab': 'questions',
@@ -499,11 +512,13 @@ def answer(request, id):#process a new answer
if request.user.is_authenticated():
try:
follow = form.cleaned_data['email_notify']
+ is_private = form.cleaned_data['post_privately']
answer = request.user.post_answer(
question = question,
body_text = text,
follow = follow,
wiki = wiki,
+ is_private = is_private,
timestamp = update_time,
)
return HttpResponseRedirect(answer.get_absolute_url())