summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDejan Noveski <dr.mote@gmail.com>2011-12-04 15:48:58 +0100
committerDejan Noveski <dr.mote@gmail.com>2011-12-04 15:48:58 +0100
commit50568a43725c632e744b81b4085781c01f80804c (patch)
tree3aa4d613897f2cf9550a46936fad725b4b4d165f
parentfe6b5c03cd2cc2972c8a953aafd94b798881115d (diff)
downloadaskbot-50568a43725c632e744b81b4085781c01f80804c.tar.gz
askbot-50568a43725c632e744b81b4085781c01f80804c.tar.bz2
askbot-50568a43725c632e744b81b4085781c01f80804c.zip
Added - ability to remove own flags to questions and answers
-rw-r--r--askbot/auth.py89
-rw-r--r--askbot/models/__init__.py216
-rw-r--r--askbot/models/signals.py6
-rw-r--r--askbot/skins/common/media/js/post.js217
-rw-r--r--askbot/skins/common/templates/question/answer_controls.html12
-rw-r--r--askbot/skins/common/templates/question/question_controls.html10
-rw-r--r--askbot/templatetags/extra_filters_jinja.py11
-rw-r--r--askbot/views/commands.py29
8 files changed, 446 insertions, 144 deletions
diff --git a/askbot/auth.py b/askbot/auth.py
index e9a93ec5..15044452 100644
--- a/askbot/auth.py
+++ b/askbot/auth.py
@@ -48,8 +48,8 @@ def onFlaggedItem(post, user, timestamp=None):
reputation.save()
signals.flag_offensive.send(
- sender=post.__class__,
- instance=post,
+ sender=post.__class__,
+ instance=post,
mark_by=user
)
@@ -102,6 +102,91 @@ def onFlaggedItem(post, user, timestamp=None):
#post.deleted_by = Admin
post.save()
+
+@transaction.commit_on_success
+def onUnFlaggedItem(post, user, timestamp=None):
+ if timestamp is None:
+ timestamp = datetime.datetime.now()
+
+ post.offensive_flag_count = post.offensive_flag_count - 1
+ post.save()
+
+ if post.post_type == 'comment':#todo: fix this
+ flagged_user = post.user
+ else:
+ flagged_user = post.author
+
+ flagged_user.receive_reputation(
+ - askbot_settings.REP_LOSS_FOR_RECEIVING_FLAG
+ )
+ flagged_user.save()
+
+ question = post.get_origin_post()
+
+ reputation = Repute(
+ user=flagged_user,
+ positive=askbot_settings.REP_LOSS_FOR_RECEIVING_FLAG,
+ question=question,
+ reputed_at=timestamp,
+ reputation_type=-4,#todo: clean up magic number
+ reputation=flagged_user.reputation
+ )
+ reputation.save()
+
+ signals.remove_flag_offensive.send(
+ sender=post.__class__,
+ instance=post,
+ mark_by=user
+ )
+
+ if post.post_type == 'comment':
+ #do not hide or delete comments automatically yet,
+ #because there is no .deleted field in the comment model
+ return
+
+ #todo: These should be updated to work on same revisions.
+ # The post fell below HIDE treshold - unhide it.
+ if post.offensive_flag_count == askbot_settings.MIN_FLAGS_TO_HIDE_POST - 1:
+ #todo: strange - are we supposed to hide the post here or the name of
+ #setting is incorrect?
+ flagged_user.receive_reputation(
+ - askbot_settings.REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION
+ )
+
+ flagged_user.save()
+
+ reputation = Repute(
+ user=flagged_user,
+ positive=\
+ askbot_settings.REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION,
+ question=question,
+ reputed_at=timestamp,
+ reputation_type=-6,
+ reputation=flagged_user.reputation
+ )
+ reputation.save()
+ # The post fell below DELETE treshold, undelete it
+ elif post.offensive_flag_count == askbot_settings.MIN_FLAGS_TO_DELETE_POST-1 :
+ flagged_user.receive_reputation(
+ - askbot_settings.REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION
+ )
+
+ flagged_user.save()
+
+ reputation = Repute(
+ user=flagged_user,
+ positive =\
+ askbot_settings.REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION,
+ question=question,
+ reputed_at=timestamp,
+ reputation_type=-7,
+ reputation=flagged_user.reputation
+ )
+ reputation.save()
+
+ post.deleted = False
+ post.save()
+
@transaction.commit_on_success
def onAnswerAccept(answer, user, timestamp=None):
answer.accepted = True
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index d63acc61..4c5cfdc5 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -44,7 +44,7 @@ def get_model(model_name):
return models.get_model('askbot', model_name)
User.add_to_class(
- 'status',
+ 'status',
models.CharField(
max_length = 2,
default = const.DEFAULT_USER_STATUS,
@@ -55,14 +55,14 @@ User.add_to_class(
User.add_to_class('email_isvalid', models.BooleanField(default=False))
User.add_to_class('email_key', models.CharField(max_length=32, null=True))
#hardcoded initial reputaion of 1, no setting for this one
-User.add_to_class('reputation',
+User.add_to_class('reputation',
models.PositiveIntegerField(default=const.MIN_REPUTATION)
)
User.add_to_class('gravatar', models.CharField(max_length=32))
#User.add_to_class('has_custom_avatar', models.BooleanField(default=False))
User.add_to_class(
- 'avatar_type',
- models.CharField(max_length=1,
+ 'avatar_type',
+ models.CharField(max_length=1,
choices=const.AVATAR_STATUS_CHOICE,
default='n')
)
@@ -91,7 +91,7 @@ User.add_to_class('about', models.TextField(blank=True))
User.add_to_class('interesting_tags', models.TextField(blank = True))
User.add_to_class('ignored_tags', models.TextField(blank = True))
User.add_to_class(
- 'email_tag_filter_strategy',
+ 'email_tag_filter_strategy',
models.SmallIntegerField(
choices=const.TAG_FILTER_STRATEGY_CHOICES,
default=const.EXCLUDE_IGNORED
@@ -158,7 +158,7 @@ def user_update_avatar_type(self):
if 'avatar' in django_settings.INSTALLED_APPS:
if self.avatar_set.count() > 0:
- self.avatar_type = 'a'
+ self.avatar_type = 'a'
else:
self.avatar_type = _check_gravatar(self.gravatar)
else:
@@ -195,7 +195,7 @@ def user_get_old_vote_for_post(self, post):
def user_has_affinity_to_question(self, question = None, affinity_type = None):
- """returns True if number of tag overlap of the user tag
+ """returns True if number of tag overlap of the user tag
selection with the question is 0 and False otherwise
affinity_type can be either "like" or "dislike"
"""
@@ -228,7 +228,7 @@ def user_has_affinity_to_question(self, question = None, affinity_type = None):
def user_has_ignored_wildcard_tags(self):
- """True if wildcard tags are on and
+ """True if wildcard tags are on and
user has some"""
return (
askbot_settings.USE_WILDCARD_TAGS \
@@ -247,7 +247,7 @@ def user_has_interesting_wildcard_tags(self):
def user_can_have_strong_url(self):
- """True if user's homepage url can be
+ """True if user's homepage url can be
followed by the search engine crawlers"""
return (self.reputation >= askbot_settings.MIN_REP_TO_HAVE_STRONG_URL)
@@ -327,14 +327,14 @@ def user_assert_can_unaccept_best_answer(self, answer = None):
error_message = suspended_error_message
elif self == answer.question.get_owner():
if self == answer.get_owner():
- if not self.is_administrator():
+ if not self.is_administrator():
#check rep
min_rep_setting = askbot_settings.MIN_REP_TO_ACCEPT_OWN_ANSWER
low_rep_error_message = _(
">%(points)s points required to accept or unaccept "
" your own answer to your own question"
) % {'points': min_rep_setting}
-
+
_assert_user_can(
user = self,
blocked_error_message = blocked_error_message,
@@ -346,7 +346,7 @@ def user_assert_can_unaccept_best_answer(self, answer = None):
elif self.is_administrator() or self.is_moderator():
will_be_able_at = (
- answer.added_at +
+ answer.added_at +
datetime.timedelta(
days=askbot_settings.MIN_DAYS_FOR_STAFF_TO_ACCEPT_ANSWER)
)
@@ -372,7 +372,7 @@ def user_assert_can_accept_best_answer(self, answer = None):
self.assert_can_unaccept_best_answer(answer)
def user_assert_can_vote_for_post(
- self,
+ self,
post = None,
direction = None,
):
@@ -657,7 +657,7 @@ def user_assert_can_delete_question(self, question = None):
def user_assert_can_delete_answer(self, answer = None):
"""intentionally use "post" word in the messages
- instead of "answer", because this logic also applies to
+ instead of "answer", because this logic also applies to
assert on deleting question (in addition to some special rules)
"""
blocked_error_message = _(
@@ -791,6 +791,54 @@ def user_assert_can_flag_offensive(self, post = None):
}
raise django_exceptions.PermissionDenied(flags_exceeded_error_message)
+def user_assert_can_remove_flag_offensive(self, post = None):
+
+ assert(post is not None)
+
+ non_existing_flagging_error_message = _('cannot remove non-existing flag')
+
+ if self.get_flags_for_post(post).count() < 1:
+ raise django_exceptions.PermissionDenied(non_existing_flagging_error_message)
+
+ blocked_error_message = _('blocked users cannot remove flags')
+
+ suspended_error_message = _('suspended users cannot remove flags')
+
+ low_rep_error_message = _('need > %(min_rep)s points to remove flag') % \
+ {'min_rep': askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE}
+ min_rep_setting = askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE
+
+ _assert_user_can(
+ user = self,
+ post = post,
+ blocked_error_message = blocked_error_message,
+ suspended_error_message = suspended_error_message,
+ low_rep_error_message = low_rep_error_message,
+ min_rep_setting = min_rep_setting
+ )
+ #one extra assertion
+ if self.is_administrator() or self.is_moderator():
+ return
+
+def user_assert_can_remove_all_flags_offensive(self, post = None):
+ assert(post is not None)
+ permission_denied_message = _("you don't have the permission to remove all flags")
+ non_existing_flagging_error_message = _('no flags for this entry')
+
+ # Check if the post is flagged by anyone
+ post_content_type = ContentType.objects.get_for_model(post)
+ all_flags = Activity.objects.filter(
+ activity_type = const.TYPE_ACTIVITY_MARK_OFFENSIVE,
+ content_type = post_content_type, object_id=post.id
+ )
+ if all_flags.count() < 1:
+ raise django_exceptions.PermissionDenied(non_existing_flagging_error_message)
+ #one extra assertion
+ if self.is_administrator() or self.is_moderator():
+ return
+ else:
+ raise django_exceptions.PermissionDenied(permission_denied_message)
+
def user_assert_can_retag_question(self, question = None):
@@ -859,7 +907,7 @@ def user_assert_can_delete_comment(self, comment = None):
def user_assert_can_revoke_old_vote(self, vote):
- """raises exceptions.PermissionDenied if old vote
+ """raises exceptions.PermissionDenied if old vote
cannot be revoked due to age of the vote
"""
if (datetime.datetime.now().day - vote.voted_at.day) \
@@ -874,7 +922,7 @@ def user_get_unused_votes_today(self):
one_day_interval = (today, today + datetime.timedelta(1))
used_votes = Vote.objects.filter(
- user = self,
+ user = self,
voted_at__range = one_day_interval
).count()
@@ -914,7 +962,7 @@ def user_post_comment(
return comment
def user_post_anonymous_askbot_content(user, session_key):
- """posts any posts added just before logging in
+ """posts any posts added just before logging in
the posts are identified by the session key, thus the second argument
this function is used by the signal handler with a similar name
@@ -953,7 +1001,7 @@ def user_mark_tags(
):
"""subscribe for or ignore a list of tags
- * ``tagnames`` and ``wildcards`` are lists of
+ * ``tagnames`` and ``wildcards`` are lists of
pure tags and wildcard tags, respectively
* ``reason`` - either "good" or "bad"
* ``action`` - eitrer "add" or "remove"
@@ -1083,7 +1131,7 @@ def user_delete_answer(
):
self.assert_can_delete_answer(answer = answer)
answer.deleted = True
- answer.deleted_by = self
+ answer.deleted_by = self
answer.deleted_at = timestamp
answer.save()
@@ -1112,17 +1160,17 @@ def user_delete_question(
self.assert_can_delete_question(question = question)
question.deleted = True
- question.deleted_by = self
+ question.deleted_by = self
question.deleted_at = timestamp
question.save()
for tag in list(question.tags.all()):
if tag.used_count == 1:
tag.deleted = True
- tag.deleted_by = self
+ tag.deleted_by = self
tag.deleted_at = timestamp
else:
- tag.used_count = tag.used_count - 1
+ tag.used_count = tag.used_count - 1
tag.save()
signals.delete_question_or_answer.send(
@@ -1192,20 +1240,20 @@ def user_restore_post(
self.assert_can_restore_post(post)
if isinstance(post, Question) or isinstance(post, Answer):
post.deleted = False
- post.deleted_by = None
- post.deleted_at = None
+ post.deleted_by = None
+ post.deleted_at = None
post.save()
if isinstance(post, Answer):
post.question.update_answer_count()
elif isinstance(post, Question):
#todo: make sure that these tags actually exist
- #some may have since been deleted for good
+ #some may have since been deleted for good
#or merged into others
for tag in list(post.tags.all()):
if tag.used_count == 1 and tag.deleted:
tag.deleted = False
tag.deleted_by = None
- tag.deleted_at = None
+ tag.deleted_at = None
tag.save()
else:
raise NotImplementedError()
@@ -1323,9 +1371,9 @@ def user_post_answer(
if self == question.author and not self.is_administrator():
# check date and rep required to post answer to own question
-
+
delta = datetime.timedelta(askbot_settings.MIN_DAYS_TO_ANSWER_OWN_QUESTION)
-
+
now = datetime.datetime.now()
asked = question.added_at
if (now - asked < delta and self.reputation < askbot_settings.MIN_REP_TO_ANSWER_OWN_QUESTION):
@@ -1348,7 +1396,7 @@ def user_post_answer(
left = ungettext('in %(hr)d hour','in %(hr)d hours',hours) % {'hr':hours}
else:
left = ungettext('in %(min)d min','in %(min)d mins',minutes) % {'min':minutes}
- day = ungettext('%(days)d day','%(days)d days',askbot_settings.MIN_DAYS_TO_ANSWER_OWN_QUESTION) % {'days':askbot_settings.MIN_DAYS_TO_ANSWER_OWN_QUESTION}
+ day = ungettext('%(days)d day','%(days)d days',askbot_settings.MIN_DAYS_TO_ANSWER_OWN_QUESTION) % {'days':askbot_settings.MIN_DAYS_TO_ANSWER_OWN_QUESTION}
error_message = _(
'New users must wait %(days)s before answering their own question. '
' You can post an answer %(left)s'
@@ -1384,7 +1432,7 @@ def user_visit_question(self, question = None, timestamp = None):
on behalf of the user represented by the self object
and mark it as taking place at timestamp time
- and remove pending on-screen notifications about anything in
+ and remove pending on-screen notifications about anything in
the post - question, answer or comments
"""
if not isinstance(question, Question):
@@ -1399,7 +1447,7 @@ def user_visit_question(self, question = None, timestamp = None):
)
except QuestionView.DoesNotExist:
question_view = QuestionView(
- who = self,
+ who = self,
question = question
)
question_view.when = timestamp
@@ -1520,7 +1568,7 @@ def get_name_of_anonymous_user():
def user_get_anonymous_name(self):
"""Returns name of anonymous user
- - convinience method for use in the template
+ - convinience method for use in the template
macros that accept user as parameter
"""
return get_name_of_anonymous_user()
@@ -1535,10 +1583,10 @@ def user_set_status(self, new_status):
there is a slight aberration - administrator status
can be removed, but not added yet
- if new status is applied to user, then the record is
+ if new status is applied to user, then the record is
committed to the database
"""
- #d - administrator
+ #d - administrator
#m - moderator
#s - suspended
#b - blocked
@@ -1556,8 +1604,8 @@ def user_set_status(self, new_status):
self.set_admin_status()
else:
#This was the old method, kept in the else clause when changing
- #to admin, so if you change the status to another thing that
- #is not Administrator it will simply remove admin if the user have
+ #to admin, so if you change the status to another thing that
+ #is not Administrator it will simply remove admin if the user have
#that permission, it will mostly be false.
if self.is_administrator():
self.remove_admin_status()
@@ -1589,7 +1637,7 @@ def user_moderate_user_reputation(
user.save()
#any question. This is necessary because reputes are read in the
- #user_reputation view with select_related('question__title') and it fails if
+ #user_reputation view with select_related('question__title') and it fails if
#ForeignKey is nullable even though it should work (according to the manual)
#probably a bug in the Django ORM
#fake_question = Question.objects.all()[:1][0]
@@ -1717,7 +1765,7 @@ def delete_messages(self):
def get_profile_url(self):
"""Returns the URL for this User's profile."""
return reverse(
- 'user_profile',
+ 'user_profile',
kwargs={'id':self.id, 'slug':slugify(self.username)}
)
@@ -1790,7 +1838,7 @@ def toggle_favorite_question(
):
"""cancel has no effect here, but is important for the SE loader
it is hoped that toggle will work and data will be consistent
- but there is no guarantee, maybe it's better to be more strict
+ but there is no guarantee, maybe it's better to be more strict
about processing the "cancel" option
another strange thing is that this function unlike others below
returns a value
@@ -1931,17 +1979,25 @@ def downvote(self, post, timestamp=None, cancel=False, force = False):
@auto_now_timestamp
def flag_post(user, post, timestamp=None, cancel=False, force = False):
if cancel:#todo: can't unflag?
- return
-
- if force == False:
- user.assert_can_flag_offensive(post = post)
- auth.onFlaggedItem(post, user, timestamp=timestamp)
- award_badges_signal.send(None,
- event = 'flag_post',
- actor = user,
- context_object = post,
- timestamp = timestamp
- )
+ if force == False:
+ user.assert_can_remove_flag_offensive(post = post)
+ auth.onUnFlaggedItem(post, user, timestamp=timestamp)
+ award_badges_signal.send(None,
+ event = 'flag_post',
+ actor = user,
+ context_object = post,
+ timestamp = timestamp
+ )
+ else:
+ if force == False:
+ user.assert_can_flag_offensive(post = post)
+ auth.onFlaggedItem(post, user, timestamp=timestamp)
+ award_badges_signal.send(None,
+ event = 'flag_post',
+ actor = user,
+ context_object = post,
+ timestamp = timestamp
+ )
def user_get_flags(self):
"""return flag Activity query set
@@ -2042,7 +2098,7 @@ User.add_to_class(
user_get_followed_question_alert_frequency
)
User.add_to_class(
- 'subscribe_for_followed_question_alerts',
+ 'subscribe_for_followed_question_alerts',
user_subscribe_for_followed_question_alerts
)
User.add_to_class('get_absolute_url', user_get_absolute_url)
@@ -2132,6 +2188,8 @@ User.add_to_class('assert_can_edit_answer', user_assert_can_edit_answer)
User.add_to_class('assert_can_close_question', user_assert_can_close_question)
User.add_to_class('assert_can_reopen_question', user_assert_can_reopen_question)
User.add_to_class('assert_can_flag_offensive', user_assert_can_flag_offensive)
+User.add_to_class('assert_can_remove_flag_offensive', user_assert_can_remove_flag_offensive)
+User.add_to_class('assert_can_remove_all_flags_offensive', user_assert_can_remove_all_flags_offensive)
User.add_to_class('assert_can_retag_question', user_assert_can_retag_question)
#todo: do we need assert_can_delete_post
User.add_to_class('assert_can_delete_post', user_assert_can_delete_post)
@@ -2294,7 +2352,7 @@ def calculate_gravatar_hash(instance, **kwargs):
def record_post_update_activity(
post,
- newly_mentioned_users = None,
+ newly_mentioned_users = None,
updated_by = None,
timestamp = None,
created = False,
@@ -2332,7 +2390,7 @@ def record_post_update_activity(
def record_award_event(instance, created, **kwargs):
"""
- After we awarded a badge to user, we need to
+ After we awarded a badge to user, we need to
record this activity and notify user.
We also recaculate awarded_count of this badge and user information.
"""
@@ -2372,15 +2430,15 @@ def notify_award_message(instance, created, **kwargs):
msg = _(u"Congratulations, you have received a badge '%(badge_name)s'. "
u"Check out <a href=\"%(user_profile)s\">your profile</a>.") \
% {
- 'badge_name':badge.name,
+ 'badge_name':badge.name,
'user_profile':user.get_profile_url()
- }
+ }
user.message_set.create(message=msg)
def record_answer_accepted(instance, created, **kwargs):
"""
- when answer is accepted, we record this for question author
+ when answer is accepted, we record this for question author
- who accepted it.
"""
if not created and instance.accepted:
@@ -2440,9 +2498,9 @@ def record_cancel_vote(instance, **kwargs):
when user canceled vote, the vote will be deleted.
"""
activity = Activity(
- user=instance.user,
- active_at=datetime.datetime.now(),
- content_object=instance,
+ user=instance.user,
+ active_at=datetime.datetime.now(),
+ content_object=instance,
activity_type=const.TYPE_ACTIVITY_CANCEL_VOTE
)
#todo: same problem - cannot access receiving user here
@@ -2461,9 +2519,9 @@ def record_delete_question(instance, delete_by, **kwargs):
activity_type = const.TYPE_ACTIVITY_DELETE_ANSWER
activity = Activity(
- user=delete_by,
- active_at=datetime.datetime.now(),
- content_object=instance,
+ user=delete_by,
+ active_at=datetime.datetime.now(),
+ content_object=instance,
activity_type=activity_type,
question = instance.get_origin_post()
)
@@ -2472,9 +2530,9 @@ def record_delete_question(instance, delete_by, **kwargs):
def record_flag_offensive(instance, mark_by, **kwargs):
activity = Activity(
- user=mark_by,
- active_at=datetime.datetime.now(),
- content_object=instance,
+ user=mark_by,
+ active_at=datetime.datetime.now(),
+ content_object=instance,
activity_type=const.TYPE_ACTIVITY_MARK_OFFENSIVE,
question=instance.get_origin_post()
)
@@ -2488,6 +2546,20 @@ def record_flag_offensive(instance, mark_by, **kwargs):
)
activity.add_recipients(recipients)
+def remove_flag_offensive(instance, mark_by, **kwargs):
+ "Remove flagging activity"
+ content_type = ContentType.objects.get_for_model(instance)
+
+ activity = Activity.objects.filter(
+ user=mark_by,
+ content_type = content_type,
+ object_id = instance.id,
+ activity_type=const.TYPE_ACTIVITY_MARK_OFFENSIVE,
+ question=instance.get_origin_post()
+ )
+ activity.delete()
+
+
def record_update_tags(question, tags, user, timestamp, **kwargs):
"""
This function sends award badges signal on each updated tag
@@ -2516,9 +2588,9 @@ def record_favorite_question(instance, created, **kwargs):
"""
if created:
activity = Activity(
- user=instance.user,
- active_at=datetime.datetime.now(),
- content_object=instance,
+ user=instance.user,
+ active_at=datetime.datetime.now(),
+ content_object=instance,
activity_type=const.TYPE_ACTIVITY_FAVORITE,
question=instance.question
)
@@ -2530,9 +2602,9 @@ def record_favorite_question(instance, created, **kwargs):
def record_user_full_updated(instance, **kwargs):
activity = Activity(
- user=instance,
- active_at=datetime.datetime.now(),
- content_object=instance,
+ user=instance,
+ active_at=datetime.datetime.now(),
+ content_object=instance,
activity_type=const.TYPE_ACTIVITY_USER_FULL_UPDATED
)
activity.save()
@@ -2614,6 +2686,8 @@ signals.delete_question_or_answer.connect(record_delete_question, sender=Questio
signals.delete_question_or_answer.connect(record_delete_question, sender=Answer)
signals.flag_offensive.connect(record_flag_offensive, sender=Question)
signals.flag_offensive.connect(record_flag_offensive, sender=Answer)
+signals.remove_flag_offensive.connect(remove_flag_offensive, sender=Question)
+signals.remove_flag_offensive.connect(remove_flag_offensive, sender=Answer)
signals.tags_updated.connect(record_update_tags)
signals.user_updated.connect(record_user_full_updated, sender=User)
signals.user_logged_in.connect(complete_pending_tag_subscriptions)#todo: add this to fake onlogin middleware
diff --git a/askbot/models/signals.py b/askbot/models/signals.py
index 8802fcf7..baa4c149 100644
--- a/askbot/models/signals.py
+++ b/askbot/models/signals.py
@@ -19,13 +19,14 @@ delete_question_or_answer = django.dispatch.Signal(
providing_args=['instance', 'deleted_by']
)
flag_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by'])
+remove_flag_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by'])
user_updated = django.dispatch.Signal(providing_args=['instance', 'updated_by'])
#todo: move this to authentication app
user_logged_in = django.dispatch.Signal(providing_args=['session'])
post_updated = django.dispatch.Signal(
providing_args=[
- 'post',
+ 'post',
'updated_by',
'newly_mentioned_users'
]
@@ -61,6 +62,7 @@ def pop_all_db_signal_receivers():
edit_question_or_answer,
delete_question_or_answer,
flag_offensive,
+ remove_flag_offensive,
user_updated,
user_logged_in,
post_updated,
@@ -83,7 +85,7 @@ def pop_all_db_signal_receivers():
def set_all_db_signal_receivers(receiver_data):
"""takes receiver data as an argument
- where the argument is as returned by the
+ where the argument is as returned by the
pop_all_db_signal_receivers() call
and sets the receivers back to the signals
"""
diff --git a/askbot/skins/common/media/js/post.js b/askbot/skins/common/media/js/post.js
index e35608fa..09c6c74d 100644
--- a/askbot/skins/common/media/js/post.js
+++ b/askbot/skins/common/media/js/post.js
@@ -38,8 +38,8 @@ function removeLoader() {
$("img.ajax-loader").remove();
}
-function setSubmitButtonDisabled(form, isDisabled) {
- form.find("input[type='submit']").attr("disabled", isDisabled ? "true" : "");
+function setSubmitButtonDisabled(form, isDisabled) {
+ form.find("input[type='submit']").attr("disabled", isDisabled ? "true" : "");
}
function enableSubmitButton(form) {
@@ -72,10 +72,10 @@ function setupFormValidation(form, validationRules, validationMessages, onSubmit
},
submitHandler: function(form_dom) {
disableSubmitButton($(form_dom));
-
+
if (onSubmitCallback){
onSubmitCallback();
- }
+ }
else{
form_dom.submit();
}
@@ -110,7 +110,7 @@ var CPValidator = function(){
maxlength: 105,
limit_tag_count: true,
limit_tag_length: true
- },
+ },
text: {
required: true,
minlength: 10
@@ -132,11 +132,11 @@ var CPValidator = function(){
},
text: {
required: " " + gettext('content cannot be empty'),
- minlength: interpolate(gettext('%s content minchars'), [chars])
+ minlength: interpolate(gettext('%s content minchars'), [chars])
},
title: {
required: " " + gettext('please enter title'),
- minlength: interpolate(gettext('%s title minchars'), [chars])
+ minlength: interpolate(gettext('%s title minchars'), [chars])
}
};
}
@@ -146,7 +146,7 @@ var CPValidator = function(){
/**
* @constructor
* @extends {SimpleControl}
- * @param {Comment} comment to upvote
+ * @param {Comment} comment to upvote
*/
var CommentVoteButton = function(comment){
SimpleControl.call(this);
@@ -271,14 +271,18 @@ var Vote = function(){
var commentLinkIdPrefix = 'comment-';
var voteNumberClass = "vote-number";
var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-';
+ var removeOffensiveIdPrefixQuestionFlag = 'question-offensive-flag-remove-';
+ var removeAllOffensiveIdPrefixQuestionFlag = 'question-offensive-flag-remove-all-';
var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-';
+ var removeOffensiveIdPrefixAnswerFlag = 'answer-offensive-flag-remove-';
+ var removeAllOffensiveIdPrefixAnswerFlag = 'answer-offensive-flag-remove-all';
var offensiveClassFlag = 'offensive-flag';
var questionControlsId = 'question-controls';
var removeQuestionLinkIdPrefix = 'question-delete-link-';
var removeAnswerLinkIdPrefix = 'answer-delete-link-';
var questionSubscribeUpdates = 'question-subscribe-updates';
var questionSubscribeSidebar= 'question-subscribe-sidebar';
-
+
var acceptAnonymousMessage = gettext('insufficient privilege');
var acceptOwnAnswerMessage = gettext('cannot pick own answer as best');
@@ -292,12 +296,13 @@ var Vote = function(){
var voteAnonymousMessage = gettext('anonymous users cannot vote') + pleaseLogin;
//there were a couple of more messages...
var offensiveConfirmation = gettext('please confirm offensive');
+ var removeOffensiveConfirmation = gettext('please confirm removal of offensive flag');
var offensiveAnonymousMessage = gettext('anonymous users cannot flag offensive posts') + pleaseLogin;
var removeConfirmation = gettext('confirm delete');
var removeAnonymousMessage = gettext('anonymous users cannot delete/undelete') + pleaseLogin;
var recoveredMessage = gettext('post recovered');
var deletedMessage = gettext('post deleted');
-
+
var VoteType = {
acceptAnswer : 0,
questionUpVote : 1,
@@ -306,7 +311,11 @@ var Vote = function(){
answerUpVote: 5,
answerDownVote:6,
offensiveQuestion : 7,
+ removeOffensiveQuestion : 7.5,
+ removeAllOffensiveQuestion : 7.6,
offensiveAnswer:8,
+ removeOffensiveAnswer:8.5,
+ removeAllOffensiveAnswer:8.6,
removeQuestion: 9,
removeAnswer:10,
questionSubscribeUpdates:11,
@@ -346,17 +355,37 @@ var Vote = function(){
var answerVoteDownButton = 'div.'+ voteContainerId +' div[id='+ imgIdPrefixAnswerVotedown + id + ']';
return $(answerVoteDownButton);
};
-
+
var getOffensiveQuestionFlag = function(){
- var offensiveQuestionFlag = '#question-table span[class='+ offensiveClassFlag +']';
+ var offensiveQuestionFlag = '#question-table span[id^="'+ offensiveIdPrefixQuestionFlag +'"]';
return $(offensiveQuestionFlag);
};
-
+
+ var getRemoveOffensiveQuestionFlag = function(){
+ var removeOffensiveQuestionFlag = '#question-table span[id^="'+ removeOffensiveIdPrefixQuestionFlag +'"]';
+ return $(removeOffensiveQuestionFlag);
+ };
+
+ var getRemoveAllOffensiveQuestionFlag = function(){
+ var removeAllOffensiveQuestionFlag = '#question-table span[id^="'+ removeAllOffensiveIdPrefixQuestionFlag +'"]';
+ return $(removeAllOffensiveQuestionFlag);
+ };
+
var getOffensiveAnswerFlags = function(){
- var offensiveQuestionFlag = 'div.answer span[class='+ offensiveClassFlag +']';
+ var offensiveQuestionFlag = 'div.answer span[id^="'+ offensiveIdPrefixAnswerFlag +'"]';
return $(offensiveQuestionFlag);
};
-
+
+ var getRemoveOffensiveAnswerFlag = function(){
+ var removeOffensiveAnswerFlag = 'div.answer span[id^="'+ removeOffensiveIdPrefixAnswerFlag +'"]';
+ return $(removeOffensiveAnswerFlag);
+ };
+
+ var getRemoveAllOffensiveAnswerFlag = function(){
+ var removeAllOffensiveAnswerFlag = 'div.answer span[id^="'+ removeAllOffensiveIdPrefixAnswerFlag +'"]';
+ return $(removeAllOffensiveAnswerFlag);
+ };
+
var getremoveQuestionLink = function(){
var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']';
return $(removeQuestionLink);
@@ -369,12 +398,12 @@ var Vote = function(){
var getquestionSubscribeSidebarCheckbox= function(){
return $('#' + questionSubscribeSidebar);
};
-
+
var getremoveAnswersLinks = function(){
var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']';
return $(removeAnswerLinks);
};
-
+
var setVoteImage = function(voteType, undo, object){
var flag = undo ? false : true;
if (object.hasClass("on")) {
@@ -382,7 +411,7 @@ var Vote = function(){
}else{
object.addClass("on");
}
-
+
if(undo){
if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
$(getQuestionVoteUpButton()).removeClass("on");
@@ -394,12 +423,12 @@ var Vote = function(){
}
}
};
-
+
var setVoteNumber = function(object, number){
var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass);
$(voteNumber).text(number);
};
-
+
var bindEvents = function(){
// accept answers
var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
@@ -412,36 +441,52 @@ var Vote = function(){
//Vote.favorite($(event.target));
Vote.favorite(favoriteButton);
});
-
+
// question vote up
var questionVoteUpButton = getQuestionVoteUpButton();
questionVoteUpButton.unbind('click').click(function(event){
Vote.vote($(event.target), VoteType.questionUpVote);
});
-
+
var questionVoteDownButton = getQuestionVoteDownButton();
questionVoteDownButton.unbind('click').click(function(event){
Vote.vote($(event.target), VoteType.questionDownVote);
});
-
+
var answerVoteUpButton = getAnswerVoteUpButtons();
answerVoteUpButton.unbind('click').click(function(event){
Vote.vote($(event.target), VoteType.answerUpVote);
});
-
+
var answerVoteDownButton = getAnswerVoteDownButtons();
answerVoteDownButton.unbind('click').click(function(event){
Vote.vote($(event.target), VoteType.answerDownVote);
});
-
+
getOffensiveQuestionFlag().unbind('click').click(function(event){
Vote.offensive(this, VoteType.offensiveQuestion);
});
-
+
+ getRemoveOffensiveQuestionFlag().unbind('click').click(function(event){
+ Vote.remove_offensive(this, VoteType.removeOffensiveQuestion);
+ });
+
+ getRemoveAllOffensiveQuestionFlag().unbind('click').click(function(event){
+ Vote.remove_offensive(this, VoteType.removeAllOffensiveQuestion);
+ });
+
getOffensiveAnswerFlags().unbind('click').click(function(event){
Vote.offensive(this, VoteType.offensiveAnswer);
});
-
+
+ getRemoveOffensiveAnswerFlag().unbind('click').click(function(event){
+ Vote.remove_offensive(this, VoteType.removeOffensiveAnswer);
+ });
+
+ getRemoveAllOffensiveAnswerFlag().unbind('click').click(function(event){
+ Vote.remove_offensive(this, VoteType.removeAllOffensiveAnswer);
+ });
+
getremoveQuestionLink().unbind('click').click(function(event){
Vote.remove(this, VoteType.removeQuestion);
});
@@ -473,7 +518,7 @@ var Vote = function(){
Vote.remove(this, VoteType.removeAnswer);
});
};
-
+
var submit = function(object, voteType, callback) {
//this function submits votes
$.ajax({
@@ -486,7 +531,7 @@ var Vote = function(){
success: function(data){callback(object, voteType, data);}
});
};
-
+
var handleFail = function(xhr, msg){
alert("Callback invoke error: " + msg);
};
@@ -511,7 +556,7 @@ var Vote = function(){
$(answers).removeClass("accepted-answer");
var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
$(commentLinks).removeClass("comment-link-accepted");
-
+
object.attr("src", mediaUrl("media/images/vote-accepted-on.png"));
$("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
@@ -524,12 +569,12 @@ var Vote = function(){
var callback_favorite = function(object, voteType, data){
if(data.allowed == "0" && data.success == "0"){
showMessage(
- object,
+ object,
favoriteAnonymousMessage.replace(
- '{{QuestionID}}',
+ '{{QuestionID}}',
questionId).replace(
'{{questionSlug}}',
- ''
+ ''
)
);
}
@@ -560,7 +605,7 @@ var Vote = function(){
showMessage(object, data.message);
}
};
-
+
var callback_vote = function(object, voteType, data){
if (data.success == '0'){
showMessage(object, data.message);
@@ -583,7 +628,7 @@ var Vote = function(){
if (data.status == "1"){
setVoteImage(voteType, true, object);
setVoteNumber(object, data.count);
- }
+ }
else if (data.success == "1"){
setVoteImage(voteType, false, object);
setVoteNumber(object, data.count);
@@ -592,20 +637,58 @@ var Vote = function(){
}
}
};
-
+
var callback_offensive = function(object, voteType, data){
//todo: transfer proper translations of these from i18n.js
//to django.po files
//_('anonymous users cannot flag offensive posts') + pleaseLogin;
if (data.success == "1"){
$(object).children('span[class=darkred]').text("("+ data.count +")");
+
+ // Change the link text and rebind events
+ $(object).find("a.question-flag").html(gettext("remove flag"));
+ var obj_id = $(object).attr("id");
+ $(object).attr("id", obj_id.replace("flag-", "flag-remove-"));
+
+ getRemoveOffensiveQuestionFlag().unbind('click').click(function(event){
+ Vote.remove_offensive(this, VoteType.removeOffensiveQuestion);
+ });
+
+ getRemoveOffensiveAnswerFlag().unbind('click').click(function(event){
+ Vote.remove_offensive(this, VoteType.removeOffensiveAnswer);
+ });
+ }
+ else {
+ object = $(object);
+ showMessage(object, data.message)
+ }
+ };
+
+ var callback_remove_offensive = function(object, voteType, data){
+ //todo: transfer proper translations of these from i18n.js
+ //to django.po files
+ //_('anonymous users cannot flag offensive posts') + pleaseLogin;
+ if (data.success == "1"){
+ $(object).children('span[class=darkred]').text("("+ data.count +")");
+ // Change the link text and rebind events
+ $(object).find("a.question-flag").html(gettext("flag offensive"));
+ var obj_id = $(object).attr("id");
+ $(object).attr("id", obj_id.replace("flag-remove-", "flag-"));
+
+ getOffensiveQuestionFlag().unbind('click').click(function(event){
+ Vote.offensive(this, VoteType.offensiveQuestion);
+ });
+
+ getOffensiveAnswerFlags().unbind('click').click(function(event){
+ Vote.offensive(this, VoteType.offensiveAnswer);
+ });
}
else {
object = $(object);
showMessage(object, data.message)
}
};
-
+
var callback_remove = function(object, voteType, data){
if (data.success == "1"){
if (removeActionType == 'delete'){
@@ -623,7 +706,7 @@ var Vote = function(){
showMessage(object, data.message)
}
};
-
+
return {
init : function(qId, qSlug, questionAuthor, userId){
questionId = qId;
@@ -632,7 +715,7 @@ var Vote = function(){
currentUserId = userId;
bindEvents();
},
-
+
//accept answer
accept: function(object){
postId = object.attr("id").substring(imgIdPrefixAccept.length);
@@ -642,7 +725,7 @@ var Vote = function(){
favorite: function(object){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage(
- object,
+ object,
favoriteAnonymousMessage.replace(
"{{QuestionID}}",
questionId
@@ -655,7 +738,7 @@ var Vote = function(){
}
submit(object, VoteType.favorite, callback_favorite);
},
-
+
vote: function(object, voteType){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
if (voteType == VoteType.questionSubscribeUpdates || voteType == VoteType.questionUnsubscribeUpdates){
@@ -683,7 +766,7 @@ var Vote = function(){
else if (voteType == VoteType.answerDownVote){
postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
}
-
+
submit(object, voteType, callback_vote);
},
//flag offensive
@@ -699,13 +782,33 @@ var Vote = function(){
questionSlug
)
);
- return false;
+ return false;
}
if (confirm(offensiveConfirmation)){
postId = object.id.substr(object.id.lastIndexOf('-') + 1);
submit(object, voteType, callback_offensive);
}
},
+ //remove flag offensive
+ remove_offensive: function(object, voteType){
+ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage(
+ $(object),
+ offensiveAnonymousMessage.replace(
+ "{{QuestionID}}",
+ questionId
+ ).replace(
+ '{{questionSlug}}',
+ questionSlug
+ )
+ );
+ return false;
+ }
+ if (confirm(removeOffensiveConfirmation)){
+ postId = object.id.substr(object.id.lastIndexOf('-') + 1);
+ submit(object, voteType, callback_remove_offensive);
+ }
+ },
//delete question or answer (comments are deleted separately)
remove: function(object, voteType){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
@@ -719,13 +822,13 @@ var Vote = function(){
questionSlug
)
);
- return false;
+ return false;
}
bits = object.id.split('-');
postId = bits.pop();/* this seems to be used within submit! */
postType = bits.shift();
- var do_proceed = false;
+ var do_proceed = false;
if (postType == 'answer'){
postNode = $('#answer-container-' + postId);
}
@@ -976,7 +1079,7 @@ EditCommentForm.prototype.getCounterUpdater = function(){
if (length2 < 0){
length2 = Math.round(0.9*maxCommentLength);
}
-
+
//todo:
//1) use class instead of color - move color def to css
var color = 'maroon';
@@ -1017,7 +1120,7 @@ EditCommentForm.prototype.getCancelHandler = function(){
return function(){
if (form.canCancel()){
form.detach();
- }
+ }
return false;
};
};
@@ -1047,7 +1150,7 @@ EditCommentForm.prototype.createDom = function(){
/*
this._help_text = $('<span></span>').attr('class', 'help-text');
- this._help_text.html(gettext('Markdown is allowed in the comments'));
+ this._help_text.html(gettext('Markdown is allowed in the comments'));
div.append(this._help_text);
this._help_text = $('<div></div>').attr('class', 'clearfix');
@@ -1257,7 +1360,7 @@ Comment.prototype.setContent = function(data){
var votes = this.makeElement('div');
votes.addClass('comment-votes');
-
+
var vote = new CommentVoteButton(this);
if (this._data['upvoted_by_user']){
vote.setVoted(true);
@@ -1274,7 +1377,7 @@ Comment.prototype.setContent = function(data){
this._comment_delete.append(this._delete_icon.getElement());
}
this._element.append(this._comment_delete);
-
+
this._comment_body = $('<div class="comment-body"></div>');
this._comment_body.html(this._data['html']);
//this._comment_body.append(' &ndash; ');
@@ -1296,7 +1399,7 @@ Comment.prototype.setContent = function(data){
this._comment_body.append(this._edit_link.getElement());
}
this._element.append(this._comment_body);
-
+
this._blank = false;
};
@@ -1306,7 +1409,7 @@ Comment.prototype.dispose = function(){
}
if (this._comment_delete){
this._comment_delete.remove();
- }
+ }
if (this._user_link){
this._user_link.remove();
}
@@ -1386,11 +1489,11 @@ Comment.prototype.getDeleteHandler = function(){
comment.getElement().hide();
$.ajax({
type: 'POST',
- url: askbot['urls']['deleteComment'],
- data: { comment_id: comment.getId() },
+ url: askbot['urls']['deleteComment'],
+ data: { comment_id: comment.getId() },
success: function(json, textStatus, xhr) {
comment.dispose();
- },
+ },
error: function(xhr, textStatus, exception) {
comment.getElement().show()
showMessage(del_icon.getElement(), xhr.responseText);
@@ -1600,7 +1703,7 @@ var socialSharing = function(){
setupButtonEventHandlers(ica, function(){share_page("identica")});
}
}
-}();
+}();
/**
* @constructor
@@ -1635,7 +1738,7 @@ QASwapper.prototype.startSwapping = function(){
success: function(data){
var url_template = askbot['urls']['question_url_template'];
new_question_url = url_template.replace(
- '{{QuestionID}}',
+ '{{QuestionID}}',
data['id']
).replace(
'{{questionSlug}}',
diff --git a/askbot/skins/common/templates/question/answer_controls.html b/askbot/skins/common/templates/question/answer_controls.html
index 4d26ffb9..0dddc72d 100644
--- a/askbot/skins/common/templates/question/answer_controls.html
+++ b/askbot/skins/common/templates/question/answer_controls.html
@@ -1,7 +1,7 @@
{% set pipe=joiner('<span class="sep">|</span>') %}
<span class="linksopt">{{ pipe() }}
<a class="permant-link"
- href="{{ answer.get_absolute_url() }}"
+ href="{{ answer.get_absolute_url() }}"
title="{% trans %}answer permanent link{% endtrans %}">
{% trans %}permanent link{% endtrans %}
</a>
@@ -10,13 +10,21 @@
<span class="action-link"><a class="question-edit" href="{% url edit_answer answer.id %}">{% trans %}edit{% endtrans %}</a></span>
{% endif %}
{% if request.user|can_flag_offensive(answer) %}{{ pipe() }}
-<span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag"
+<span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag"
title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}">
<a class="question-flag">{% trans %}flag offensive{% endtrans %}</a>
{% if request.user|can_see_offensive_flags(answer) %}
<span class="darkred">{% if answer.offensive_flag_count > 0 %}({{ answer.offensive_flag_count }}){% endif %}</span>
{% endif %}
</span>
+{% elif request.user|can_remove_flag_offensive(answer)%}{{ pipe() }}
+<span id="answer-offensive-flag-remove-{{ answer.id }}" class="offensive-flag"
+ title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}">
+ <a class="question-flag">{% trans %}remove flag{% endtrans %}</a>
+ {% if request.user|can_see_offensive_flags(answer) %}
+ <span class="darkred">{% if answer.offensive_flag_count > 0 %}({{ answer.offensive_flag_count }}){% endif %}</span>
+ {% endif %}
+</span>
{% endif %}
{% if request.user|can_delete_post(answer) %}{{ pipe() }}
{% spaceless %}
diff --git a/askbot/skins/common/templates/question/question_controls.html b/askbot/skins/common/templates/question/question_controls.html
index 9de54526..ea112aa6 100644
--- a/askbot/skins/common/templates/question/question_controls.html
+++ b/askbot/skins/common/templates/question/question_controls.html
@@ -18,13 +18,21 @@
{% endif %}
{% endif %}
{% if request.user|can_flag_offensive(question) %}{{ pipe() }}
-<span id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
+<span id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}">
<a class="question-flag">{% trans %}flag offensive{% endtrans %}</a>
{% if request.user|can_see_offensive_flags(question) %}
<span class="darkred">{% if question.offensive_flag_count > 0 %}({{ question.offensive_flag_count }}){% endif %}</span>
{% endif %}
</span>
+{% elif request.user|can_remove_flag_offensive(question)%}{{ pipe() }}
+<span id="question-offensive-flag-remove-{{ question.id }}" class="offensive-flag"
+ title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}">
+ <a class="question-flag">{% trans %}remove flag{% endtrans %}</a>
+ {% if request.user|can_see_offensive_flags(question) %}
+ <span class="darkred">{% if question.offensive_flag_count > 0 %}({{ question.offensive_flag_count }}){% endif %}</span>
+ {% endif %}
+</span>
{% endif %}
{% if request.user|can_delete_post(question) %}{{ pipe() }}
<a id="question-delete-link-{{question.id}}" class="question-delete">{% if question.deleted %}{% trans %}undelete{% endtrans %}{% else %}{% trans %}delete{% endtrans %}{% endif %}</a>
diff --git a/askbot/templatetags/extra_filters_jinja.py b/askbot/templatetags/extra_filters_jinja.py
index 2228eed4..8486d934 100644
--- a/askbot/templatetags/extra_filters_jinja.py
+++ b/askbot/templatetags/extra_filters_jinja.py
@@ -163,7 +163,16 @@ def can_moderate_user(user, other_user):
can_flag_offensive = make_template_filter_from_permission_assertion(
assertion_name = 'assert_can_flag_offensive',
filter_name = 'can_flag_offensive',
- allowed_exception = askbot_exceptions.DuplicateCommand
+ )
+
+can_remove_flag_offensive = make_template_filter_from_permission_assertion(
+ assertion_name = 'assert_can_remove_flag_offensive',
+ filter_name = 'can_remove_flag_offensive',
+ )
+
+can_remove_all_flags_offensive = make_template_filter_from_permission_assertion(
+ assertion_name = 'assert_can_remove_all_flags_offensive',
+ filter_name = 'can_remove_all_flags_offensive',
)
can_post_comment = make_template_filter_from_permission_assertion(
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index 04d4ef1b..2eb562ed 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -1,7 +1,7 @@
"""
:synopsis: most ajax processors for askbot
-This module contains most (but not all) processors for Ajax requests.
+This module contains most (but not all) processors for Ajax requests.
Not so clear if this subdivision was necessary as separation of Ajax and non-ajax views
is not always very clean.
"""
@@ -39,7 +39,7 @@ def process_vote(user = None, vote_direction = None, post = None):
raise exceptions.PermissionDenied(_('anonymous users cannot vote'))
user.assert_can_vote_for_post(
- post = post,
+ post = post,
direction = vote_direction
)
@@ -71,7 +71,7 @@ def process_vote(user = None, vote_direction = None, post = None):
else:
vote = user.downvote(post = post)
- response_data['count'] = post.score
+ response_data['count'] = post.score
response_data['status'] = 0 #this means "not cancel", normal operation
response_data['success'] = 1
@@ -217,7 +217,7 @@ def vote(request, id):
vote_direction = 'down'
if vote_type in ('5', '6'):
- #todo: fix this weirdness - why postId here
+ #todo: fix this weirdness - why postId here
#and not with question?
id = request.POST.get('postId')
post = get_object_or_404(models.Answer, id=id)
@@ -245,9 +245,22 @@ def vote(request, id):
response_data['count'] = post.offensive_flag_count
response_data['success'] = 1
+ elif vote_type in ['7.5', '8.5']:
+ #flag question or answer
+ if vote_type == '7.5':
+ post = get_object_or_404(models.Question, id=id)
+ if vote_type == '8.5':
+ id = request.POST.get('postId')
+ post = get_object_or_404(models.Answer, id=id)
+
+ request.user.flag_post(post, cancel = True)
+
+ response_data['count'] = post.offensive_flag_count
+ response_data['success'] = 1
+
elif vote_type in ['9', '10']:
#delete question or answer
- post = get_object_or_404(models.Question, id = id)
+ post = get_object_or_404(models.Question, id = id)
if vote_type == '10':
id = request.POST.get('postId')
post = get_object_or_404(models.Answer, id = id)
@@ -460,7 +473,7 @@ def set_tag_filter_strategy(request):
@login_required
@csrf.csrf_protect
def close(request, id):#close question
- """view to initiate and process
+ """view to initiate and process
question close
"""
question = get_object_or_404(models.Question, id=id)
@@ -490,7 +503,7 @@ def close(request, id):#close question
@login_required
@csrf.csrf_protect
def reopen(request, id):#re-open question
- """view to initiate and process
+ """view to initiate and process
question close
this is not an ajax view
@@ -512,7 +525,7 @@ def reopen(request, id):#re-open question
'closed_by_username': closed_by_username,
}
return render_into_skin('reopen.html', data, request)
-
+
except exceptions.PermissionDenied, e:
request.user.message_set.create(message = unicode(e))
return HttpResponseRedirect(question.get_absolute_url())