summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefano Mancini <stefano.mancini@devinterface.com>2012-10-15 14:11:24 +0200
committerStefano Mancini <stefano.mancini@devinterface.com>2012-10-15 14:11:24 +0200
commitfae17ca2a09b6dc24103f998719706427254aa76 (patch)
treefb70e1ce6cbcff71d0324e88091bbf20b0c7e9c3
parent14775aaaa677a5c3c1aa1b00f385d60d99d44f02 (diff)
parentd83006dd9239fa77b95f77e361d15ad8289c7877 (diff)
downloadaskbot-fae17ca2a09b6dc24103f998719706427254aa76.tar.gz
askbot-fae17ca2a09b6dc24103f998719706427254aa76.tar.bz2
askbot-fae17ca2a09b6dc24103f998719706427254aa76.zip
Merge branch 'master' of https://github.com/ASKBOT/askbot-devel
-rw-r--r--askbot/auth.py10
-rw-r--r--askbot/const/__init__.py8
-rw-r--r--askbot/deps/livesettings/views.py2
-rw-r--r--askbot/doc/source/changelog.rst1
-rw-r--r--askbot/doc/source/optional-modules.rst16
-rw-r--r--askbot/media/style/style.less1
-rw-r--r--askbot/models/__init__.py59
-rw-r--r--askbot/models/badges.py30
-rw-r--r--askbot/models/post.py42
-rw-r--r--askbot/models/question.py157
-rw-r--r--askbot/models/repute.py16
-rw-r--r--askbot/models/tag.py1
-rw-r--r--askbot/search/haystack/__init__.py59
-rw-r--r--askbot/setup_templates/settings.py8
-rw-r--r--askbot/setup_templates/settings.py.mustache9
-rw-r--r--askbot/startup_procedures.py21
-rw-r--r--askbot/tests/__init__.py3
-rw-r--r--askbot/tests/__init__.py.orig19
-rw-r--r--askbot/tests/badge_tests.py64
-rw-r--r--askbot/tests/db_api_tests.py31
-rw-r--r--askbot/tests/haystack_search_tests.py101
-rw-r--r--askbot/tests/page_load_tests.py4
-rw-r--r--askbot/tests/post_model_tests.py6
-rw-r--r--askbot/tests/question_views_tests.py207
-rw-r--r--askbot/tests/user_views_tests.py39
-rw-r--r--askbot/views/commands.py7
-rw-r--r--askbot/views/users.py22
-rw-r--r--askbot/views/writers.py22
28 files changed, 753 insertions, 212 deletions
diff --git a/askbot/auth.py b/askbot/auth.py
index c80f5db1..846445b4 100644
--- a/askbot/auth.py
+++ b/askbot/auth.py
@@ -238,7 +238,7 @@ def onAnswerAcceptCanceled(answer, user, timestamp=None):
reputation.save()
if answer.author == question.author and user == question.author:
- #a symmettric measure for the reputation gaming plug
+ #a symmettric measure for the reputation gaming plug
#as in the onAnswerAccept function
#here it protects the user from uwanted reputation loss
return
@@ -263,7 +263,7 @@ def onUpVoted(vote, post, user, timestamp=None):
if post.post_type != 'comment':
post.vote_up_count = int(post.vote_up_count) + 1
- post.score = int(post.score) + 1
+ post.points = int(post.points) + 1
post.save()
if post.post_type == 'comment':
@@ -300,7 +300,7 @@ def onUpVotedCanceled(vote, post, user, timestamp=None):
if post.vote_up_count < 0:
post.vote_up_count = 0
- post.score = int(post.score) - 1
+ post.points = int(post.points) - 1
post.save()
if post.post_type == 'comment':
@@ -333,7 +333,7 @@ def onDownVoted(vote, post, user, timestamp=None):
vote.save()
post.vote_down_count = int(post.vote_down_count) + 1
- post.score = int(post.score) - 1
+ post.points = int(post.points) - 1
post.save()
if not (post.wiki or post.is_anonymous):
@@ -375,7 +375,7 @@ def onDownVotedCanceled(vote, post, user, timestamp=None):
post.vote_down_count = int(post.vote_down_count) - 1
if post.vote_down_count < 0:
post.vote_down_count = 0
- post.score = post.score + 1
+ post.points = post.points + 1
post.save()
if not (post.wiki or post.is_anonymous):
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index 5f47bb79..977cf0c5 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -299,7 +299,7 @@ POST_STATUS = {
'deleted': _('[deleted]'),
'default_version': _('initial version'),
'retagged': _('retagged'),
- 'private': _('[private]')
+ 'private': _('[private]')
}
#choices used in email and display filters
@@ -361,7 +361,7 @@ DEFAULT_USER_STATUS = 'w'
#number of items to show in user views
USER_VIEW_DATA_SIZE = 50
-#not really dependency, but external links, which it would
+#not really dependency, but external links, which it would
#be nice to test for correctness from time to time
DEPENDENCY_URLS = {
'akismet': 'https://akismet.com/signup/',
@@ -411,8 +411,8 @@ SEARCH_ORDER_BY = (
('last_activity_at', _('activity ascendant')),
('-answer_count', _('answers descendant')),
('answer_count', _('answers ascendant')),
- ('-score', _('votes descendant')),
- ('score', _('votes ascendant')),
+ ('-points', _('votes descendant')),
+ ('points', _('votes ascendant')),
)
DEFAULT_QUESTION_WIDGET_STYLE = """
diff --git a/askbot/deps/livesettings/views.py b/askbot/deps/livesettings/views.py
index 918c6602..d12eb602 100644
--- a/askbot/deps/livesettings/views.py
+++ b/askbot/deps/livesettings/views.py
@@ -96,6 +96,6 @@ def export_as_python(request):
pp = pprint.PrettyPrinter(indent=4)
pretty = pp.pformat(work)
- return render_to_response('askbot.deps.livesettings/text.txt', { 'text' : pretty }, mimetype='text/plain')
+ return render_to_response('livesettings/text.txt', { 'text' : pretty }, mimetype='text/plain')
export_as_python = never_cache(staff_member_required(export_as_python))
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index d77e11ab..a971d9d6 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -3,6 +3,7 @@ Changes in Askbot
Development version
-------------------
+* Added support of Haystack for search (Adolfo)
* Added minimum reputation setting to accept any answer as correct (Evgeny)
* Added "VIP" option to groups - if checked, all posts belong to the group and users of that group in the future will be able to moderate those posts. Moderation features for VIP group are in progress (Evgeny)
* Added setting `NOTIFICATION_DELAY_TIME` to use with enabled celery daemon (Adolfo)
diff --git a/askbot/doc/source/optional-modules.rst b/askbot/doc/source/optional-modules.rst
index 3337ef0c..995ed224 100644
--- a/askbot/doc/source/optional-modules.rst
+++ b/askbot/doc/source/optional-modules.rst
@@ -55,6 +55,22 @@ Finally, add lin
.. _embedding-video:
+Haystack search
+=============
+Askbot supports `Haystack <http://haystacksearch.org/>`_, a modular search framework that supports popular search engine backends as
+Solr, Elasticsearch, Whoosh and Xapian.
+
+.. note::
+ Haystack support in Askbot is a new feature,
+ please give us your feedback at ``support@askbot.com``
+ regarding the possible improvements.
+
+To enable:
+
+* add 'haystack' to INSTALLED_APPS
+* add ENABLE_HAYSTACK_SEARCH = True in settings.py
+* Configure your search backend according to your setup following `this guide <http://django-haystack.readthedocs.org/en/latest/tutorial.html#modify-your-settings-py>`_
+
Embedding video
===============
diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less
index 607261ce..a7d035ed 100644
--- a/askbot/media/style/style.less
+++ b/askbot/media/style/style.less
@@ -2695,7 +2695,6 @@ a:hover.medal {
}
.user-profile-page{
- font-size:13px;
color:@info-text-dark;
p{
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index 83e67bb9..db9674e2 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -93,23 +93,28 @@ def get_users_by_text_query(search_query, users_query_set = None):
"""Runs text search in user names and profile.
For postgres, search also runs against user group names.
"""
- import askbot
- if users_query_set is None:
- users_query_set = User.objects.all()
- if 'postgresql_psycopg2' in askbot.get_database_engine_name():
- from askbot.search import postgresql
- return postgresql.run_full_text_search(users_query_set, search_query)
+ if getattr(django_settings, 'ENABLE_HAYSTACK_SEARCH', False):
+ from askbot.search.haystack import AskbotSearchQuerySet
+ qs = AskbotSearchQuerySet().filter(content=search_query).models(User).get_django_queryset(User)
+ return qs
else:
- return users_query_set.filter(
- models.Q(username__icontains=search_query) |
- models.Q(about__icontains=search_query)
- )
- #if askbot.get_database_engine_name().endswith('mysql') \
- # and mysql.supports_full_text_search():
- # return User.objects.filter(
- # models.Q(username__search = search_query) |
- # models.Q(about__search = search_query)
- # )
+ import askbot
+ if users_query_set is None:
+ users_query_set = User.objects.all()
+ if 'postgresql_psycopg2' in askbot.get_database_engine_name():
+ from askbot.search import postgresql
+ return postgresql.run_full_text_search(users_query_set, search_query)
+ else:
+ return users_query_set.filter(
+ models.Q(username__icontains=search_query) |
+ models.Q(about__icontains=search_query)
+ )
+ #if askbot.get_database_engine_name().endswith('mysql') \
+ # and mysql.supports_full_text_search():
+ # return User.objects.filter(
+ # models.Q(username__search = search_query) |
+ # models.Q(about__search = search_query)
+ # )
User.add_to_class(
'status',
@@ -851,7 +856,7 @@ def user_assert_can_delete_question(self, question = None):
#if there are answers by other people,
#then deny, unless user in admin or moderator
answer_count = question.thread.all_answers()\
- .exclude(author=self).exclude(score__lte=0).count()
+ .exclude(author=self).exclude(points__lte=0).count()
if answer_count > 0:
if self.is_administrator() or self.is_moderator():
@@ -881,7 +886,7 @@ def user_assert_can_delete_answer(self, answer = None):
'you can delete only your own posts'
)
low_rep_error_message = _(
- 'Sorry, to deleted other people\' posts, a minimum '
+ 'Sorry, to delete other people\'s posts, a minimum '
'reputation of %(min_rep)s is required'
) % \
{'min_rep': askbot_settings.MIN_REP_TO_DELETE_OTHERS_POSTS}
@@ -1725,14 +1730,15 @@ def user_edit_answer(
):
if force == False:
self.assert_can_edit_answer(answer)
+
answer.apply_edit(
- edited_at = timestamp,
- edited_by = self,
- text = body_text,
- comment = revision_comment,
- wiki = wiki,
- is_private = is_private,
- by_email = by_email
+ edited_at=timestamp,
+ edited_by=self,
+ text=body_text,
+ comment=revision_comment,
+ wiki=wiki,
+ is_private=is_private,
+ by_email=by_email
)
answer.thread.invalidate_cached_data()
@@ -2458,7 +2464,7 @@ def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None):
if post.post_type == 'question':
#denormalize the question post score on the thread
- post.thread.score = post.score
+ post.thread.points = post.points
post.thread.save()
post.thread.update_summary_html()
@@ -2889,7 +2895,6 @@ def format_instant_notification_email(
only update_types in const.RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES
are supported
"""
-
site_url = askbot_settings.APP_URL
origin_post = post.get_origin_post()
#todo: create a better method to access "sub-urls" in user views
diff --git a/askbot/models/badges.py b/askbot/models/badges.py
index 61149df3..244c8e2f 100644
--- a/askbot/models/badges.py
+++ b/askbot/models/badges.py
@@ -43,13 +43,13 @@ class Badge(object):
"""
def __init__(self,
key = '',
- name = '',
+ name = '',
level = None,
description = None,
multiple = False):
#key - must be an ASCII only word
- self.key = key
+ self.key = key
self.name = name
self.level = level
self.description = description
@@ -114,11 +114,11 @@ class Badge(object):
def consider_award(self, actor = None,
context_object = None, timestamp = None):
- """Normally this method should be reimplemented
+ """Normally this method should be reimplemented
in subclass, but some badges are awarded without
checks. Those do no need to override this method
- actor - user who committed some action, context_object -
+ actor - user who committed some action, context_object -
the object related to the award situation, e.g. answer
"""
return self.award(actor, context_object, timestamp)
@@ -141,7 +141,7 @@ class Disciplined(Badge):
if context_object.author != actor:
return False
- if context_object.score >= \
+ if context_object.points>= \
askbot_settings.DISCIPLINED_BADGE_MIN_UPVOTES:
return self.award(actor, context_object, timestamp)
@@ -163,7 +163,7 @@ class PeerPressure(Badge):
if context_object.author != actor:
return False
- if context_object.score <= \
+ if context_object.points<= \
-1 * askbot_settings.PEER_PRESSURE_BADGE_MIN_DOWNVOTES:
return self.award(actor, context_object, timestamp)
return False
@@ -181,12 +181,12 @@ class Teacher(Badge):
multiple = False
)
- def consider_award(self, actor = None,
+ def consider_award(self, actor = None,
context_object = None, timestamp = None):
if context_object.post_type != 'answer':
return False
- if context_object.score >= askbot_settings.TEACHER_BADGE_MIN_UPVOTES:
+ if context_object.points>= askbot_settings.TEACHER_BADGE_MIN_UPVOTES:
return self.award(context_object.author, context_object, timestamp)
return False
@@ -268,7 +268,7 @@ class SelfLearner(Badge):
question = context_object.thread._question_post()
answer = context_object
- if question.author == answer.author and answer.score >= min_upvotes:
+ if question.author == answer.author and answer.points >= min_upvotes:
self.award(context_object.author, context_object, timestamp)
class QualityPost(Badge):
@@ -294,7 +294,7 @@ class QualityPost(Badge):
context_object = None, timestamp = None):
if context_object.post_type not in ('answer', 'question'):
return False
- if context_object.score >= self.min_votes:
+ if context_object.points >= self.min_votes:
return self.award(context_object.author, context_object, timestamp)
return False
@@ -485,7 +485,7 @@ class VotedAcceptedAnswer(Badge):
if context_object.post_type != 'answer':
return None
answer = context_object
- if answer.score >= self.min_votes and answer.accepted():
+ if answer.points >= self.min_votes and answer.accepted():
return self.award(answer.author, answer, timestamp)
class Enlightened(VotedAcceptedAnswer):
@@ -537,7 +537,7 @@ class Necromancer(Badge):
delta = datetime.timedelta(askbot_settings.NECROMANCER_BADGE_MIN_DELAY)
min_score = askbot_settings.NECROMANCER_BADGE_MIN_UPVOTES
if answer.added_at - question.added_at >= delta \
- and answer.score >= min_score:
+ and answer.points >= min_score:
return self.award(answer.author, answer, timestamp)
return False
@@ -723,7 +723,7 @@ class Enthusiast(Badge):
return False
class Commentator(Badge):
- """Commentator is a bronze badge that is
+ """Commentator is a bronze badge that is
awarded once when user posts a certain number of
comments"""
def __init__(self):
@@ -778,7 +778,7 @@ class Expert(Badge):
)
ORIGINAL_DATA = """
-
+
extra badges from stackexchange
* commentator - left n comments (single)
* enthusiast, fanatic - visited site n days in a row (s)
@@ -894,7 +894,7 @@ award_badges_signal = Signal(
#context_object - database object related to the event, e.g. question
@auto_now_timestamp
-def award_badges(event = None, actor = None,
+def award_badges(event = None, actor = None,
context_object = None, timestamp = None, **kwargs):
"""function that is called when signal `award_badges_signal` is sent
"""
diff --git a/askbot/models/post.py b/askbot/models/post.py
index 9984155a..10b3cdc7 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -59,6 +59,15 @@ class PostQuerySet(models.query.QuerySet):
#todo: we may not need this query set class,
#as all methods on this class seem to want to
#belong to Thread manager or Query set.
+ def get_for_user(self, user):
+ if askbot_settings.GROUPS_ENABLED:
+ if user is None or user.is_anonymous():
+ groups = [get_global_group()]
+ else:
+ groups = user.get_groups()
+ return self.filter(groups__in = groups).distinct()
+ else:
+ return self
def get_by_text_query(self, search_query):
"""returns a query set of questions,
@@ -156,24 +165,16 @@ class PostManager(BaseQuerySetManager):
def get_query_set(self):
return PostQuerySet(self.model)
- def get_questions(self):
- return self.filter(post_type='question')
+ def get_questions(self, user=None):
+ questions = self.filter(post_type='question')
+ return questions.get_for_user(user)
- def get_answers(self, user = None):
+ 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():
- groups = [get_global_group()]
- else:
- groups = user.get_groups()
- answers = answers.filter(groups__in = groups).distinct()
-
- return answers
-
+ return answers.get_for_user(user)
def get_comments(self):
return self.filter(post_type='comment')
@@ -358,7 +359,7 @@ class Post(models.Model):
locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_posts')
locked_at = models.DateTimeField(null=True, blank=True)
- score = models.IntegerField(default=0)
+ points = models.IntegerField(default=0, db_column='score')
vote_up_count = models.IntegerField(default=0)
vote_down_count = models.IntegerField(default=0)
@@ -389,6 +390,14 @@ class Post(models.Model):
app_label = 'askbot'
db_table = 'askbot_post'
+ #property to support legacy themes in case there are.
+ @property
+ def score(self):
+ return int(self.points)
+ @score.setter
+ def score(self, number):
+ if number:
+ self.points = int(number)
def parse_post_text(self):
"""typically post has a field to store raw source text
@@ -1717,9 +1726,10 @@ class Post(models.Model):
##it is important to do this before __apply_edit b/c of signals!!!
if self.is_private() != is_private:
if is_private:
- self.make_private(self.author)
+ #todo: make private for author or for the editor?
+ self.thread.make_private(self.author)
else:
- self.make_public()
+ self.thread.make_public(recursive=False)
self.__apply_edit(
edited_at=edited_at,
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 5878500d..6c45f1eb 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -2,7 +2,7 @@ import datetime
import operator
import re
-from django.conf import settings
+from django.conf import settings as django_settings
from django.db import models
from django.contrib.auth.models import User
from django.core import cache # import cache, not from cache import cache, to be able to monkey-patch cache.cache in test cases
@@ -176,28 +176,33 @@ class ThreadManager(BaseQuerySetManager):
"""returns a query set of questions,
matching the full text query
"""
- if not qs:
- qs = self.all()
-# if getattr(settings, 'USE_SPHINX_SEARCH', False):
-# matching_questions = Question.sphinx_search.query(search_query)
-# question_ids = [q.id for q in matching_questions]
-# return qs.filter(posts__post_type='question', posts__deleted=False, posts__self_question_id__in=question_ids)
- if askbot.get_database_engine_name().endswith('mysql') \
- and mysql.supports_full_text_search():
- return qs.filter(
- models.Q(title__search = search_query) |
- models.Q(tagnames__search = search_query) |
- models.Q(posts__deleted=False, posts__text__search = search_query)
- )
- elif 'postgresql_psycopg2' in askbot.get_database_engine_name():
- from askbot.search import postgresql
- return postgresql.run_full_text_search(qs, search_query)
+ if django_settings.ENABLE_HAYSTACK_SEARCH:
+ from askbot.search.haystack import AskbotSearchQuerySet
+ hs_qs = AskbotSearchQuerySet().filter(content=search_query)
+ return hs_qs.get_django_queryset()
else:
- return qs.filter(
- models.Q(title__icontains=search_query) |
- models.Q(tagnames__icontains=search_query) |
- models.Q(posts__deleted=False, posts__text__icontains = search_query)
- )
+ if not qs:
+ qs = self.all()
+ # if getattr(settings, 'USE_SPHINX_SEARCH', False):
+ # matching_questions = Question.sphinx_search.query(search_query)
+ # question_ids = [q.id for q in matching_questions]
+ # return qs.filter(posts__post_type='question', posts__deleted=False, posts__self_question_id__in=question_ids)
+ if askbot.get_database_engine_name().endswith('mysql') \
+ and mysql.supports_full_text_search():
+ return qs.filter(
+ models.Q(title__search = search_query) |
+ models.Q(tagnames__search = search_query) |
+ models.Q(posts__deleted=False, posts__text__search = search_query)
+ )
+ elif 'postgresql_psycopg2' in askbot.get_database_engine_name():
+ from askbot.search import postgresql
+ return postgresql.run_full_text_search(qs, search_query)
+ else:
+ return qs.filter(
+ models.Q(title__icontains=search_query) |
+ models.Q(tagnames__icontains=search_query) |
+ models.Q(posts__deleted=False, posts__text__icontains = search_query)
+ )
def run_advanced_search(self, request_user, search_state): # TODO: !! review, fix, and write tests for this
@@ -211,8 +216,8 @@ class ThreadManager(BaseQuerySetManager):
# TODO: add a possibility to see deleted questions
qs = self.filter(
- posts__post_type='question',
- posts__deleted=False
+ posts__post_type='question',
+ posts__deleted=False,
) # (***) brings `askbot_post` into the SQL query, see the ordering section below
if askbot_settings.ENABLE_CONTENT_MODERATION:
@@ -249,7 +254,7 @@ class ThreadManager(BaseQuerySetManager):
) # 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]
+ #plus any tags added to the query string with #tag or [tag:something]
#syntax.
#run tag search in addition to these unified tags
meta_data = {}
@@ -271,7 +276,7 @@ class ThreadManager(BaseQuerySetManager):
existing_tags.add(tag_record.name)
except Tag.DoesNotExist:
non_existing_tags.add(tag)
-
+
meta_data['non_existing_tags'] = list(non_existing_tags)
tags = existing_tags
else:
@@ -298,7 +303,7 @@ class ThreadManager(BaseQuerySetManager):
elif search_state.scope == 'favorite':
favorite_filter = models.Q(favorited_by=request_user)
- if 'followit' in settings.INSTALLED_APPS:
+ if 'followit' in django_settings.INSTALLED_APPS:
followed_users = request_user.get_followed_users()
favorite_filter |= models.Q(posts__post_type__in=('question', 'answer'), posts__author__in=followed_users)
qs = qs.filter(favorite_filter)
@@ -370,13 +375,21 @@ class ThreadManager(BaseQuerySetManager):
'activity-asc': 'last_activity_at',
'answers-desc': '-answer_count',
'answers-asc': 'answer_count',
- 'votes-desc': '-score',
- 'votes-asc': 'score',
+ 'votes-desc': '-points',
+ 'votes-asc': 'points',
'relevance-desc': '-relevance', # special Postgresql-specific ordering, 'relevance' quaso-column is added by get_for_query()
}
+
orderby = QUESTION_ORDER_BY_MAP[search_state.sort]
- qs = qs.extra(order_by=[orderby])
+
+ if not (
+ getattr(django_settings, 'ENABLE_HAYSTACK_SEARCH', False) \
+ and orderby=='-relevance'
+ ):
+ #FIXME: this does not produces the very same results as postgres.
+ qs = qs.extra(order_by=[orderby])
+
# HACK: We add 'ordering_key' column as an alias and order by it, because when distict() is used,
# qs.extra(order_by=[orderby,]) is lost if only `orderby` column is from askbot_post!
@@ -403,7 +416,7 @@ class ThreadManager(BaseQuerySetManager):
page_questions = Post.objects.filter(
post_type='question', thread__id__in = thread_ids
).only(# pick only the used fields
- 'id', 'thread', 'score', 'is_anonymous',
+ 'id', 'thread', 'points', 'is_anonymous',
'summary', 'post_type', 'deleted'
)
page_question_map = {}
@@ -514,13 +527,23 @@ class Thread(models.Model):
answer_accepted_at = models.DateTimeField(null=True, blank=True)
added_at = models.DateTimeField(default = datetime.datetime.now)
- score = models.IntegerField(default = 0)
+ #db_column will be removed later
+ points = models.IntegerField(default = 0, db_column='score')
objects = ThreadManager()
-
+
class Meta:
app_label = 'askbot'
+ #property to support legacy themes in case there are.
+ @property
+ def score(self):
+ return int(self.points)
+ @score.setter
+ def score(self, number):
+ if number:
+ self.points = int(number)
+
def _question_post(self, refresh=False):
if refresh and hasattr(self, '_question_cache'):
delattr(self, '_question_cache')
@@ -694,7 +717,7 @@ class Thread(models.Model):
def get_answers_by_user(self, user):
"""regardless - deleted or not"""
- return self.posts.filter(post_type = 'answer', author = user)
+ return self.posts.filter(post_type='answer', author=user, deleted=False)
def has_answer_by_user(self, user):
#use len to cache the queryset
@@ -735,14 +758,26 @@ class Thread(models.Model):
if user is None or user.is_anonymous():
return self.posts.get_answers().filter(deleted=False)
else:
- if user.is_administrator() or user.is_moderator():
- return self.posts.get_answers(user = user)
- else:
- return self.posts.get_answers(user = user).filter(
- models.Q(deleted = False) \
- | models.Q(author = user) \
- | models.Q(deleted_by = user)
- )
+ return self.posts.get_answers(
+ user=user
+ ).filter(deleted=False)
+ # return self.posts.get_answers(user=user).filter(
+ # models.Q(deleted=False) \
+ # | models.Q(author=user) \
+ # | models.Q(deleted_by=user)
+ # )
+ #we used to show deleted answers to admins,
+ #users who deleted those answers and answer owners
+ #but later decided to not show deleted answers at all
+ #because it makes caching the post lists for thread easier
+ #if user.is_administrator() or user.is_moderator():
+ # return self.posts.get_answers(user=user)
+ #else:
+ # 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)
@@ -751,7 +786,7 @@ class Thread(models.Model):
return 'thread-data-%s-%s' % (self.id, sort_method)
def invalidate_cached_post_data(self):
- """needs to be called when anything notable
+ """needs to be called when anything notable
changes in the post data - on votes, adding,
deleting, editing content"""
#we can call delete_many() here if using Django > 1.2
@@ -798,7 +833,7 @@ class Thread(models.Model):
{
'latest':'-added_at',
'oldest':'added_at',
- 'votes':'-score'
+ 'votes':'-points'
}[sort_method]
)
#1) collect question, answer and comment posts and list of post id's
@@ -852,17 +887,18 @@ class Thread(models.Model):
#todo: there may be > 1 enquirers
published_answer_ids = list()
if self.is_moderated() and user != question_post.author:
- #if moderated - then author is guaranteed to be the
+ #if moderated - then author is guaranteed to be the
#limited visibility enquirer
published_answer_ids = self.posts.get_answers(
- user=question_post.author#todo: may be > 1
+ user=question_post.author
+ #todo: may be > 1 user
).filter(
deleted=False
).order_by(
{
'latest':'-added_at',
'oldest':'added_at',
- 'votes':'-score'
+ 'votes':'-points'
}[sort_method]
).values_list('id', flat=True)
@@ -870,9 +906,13 @@ class Thread(models.Model):
#now put those answers first
answer_map = dict([(answer.id, answer) for answer in answers])
for answer_id in published_answer_ids:
- answer = answer_map[answer_id]
- answers.remove(answer)
- answers.insert(0, answer)
+ #note that answer map may not contain answers publised
+ #to the question enquirer, because current user may
+ #not have access to that answer, so we use the .get() method
+ answer = answer_map.get(answer_id, None)
+ if answer:
+ answers.remove(answer)
+ answers.insert(0, answer)
return (question_post, answers, post_to_author, published_answer_ids)
@@ -940,8 +980,8 @@ class Thread(models.Model):
url = question_post.get_absolute_url()
title = thread.get_title(question_post)
result.append({'url': url, 'title': title})
-
- return result
+
+ return result
def get_cached_data():
"""similar thread data will expire
@@ -986,7 +1026,7 @@ class Thread(models.Model):
return False
def add_child_posts_to_groups(self, groups):
- """adds questions and answers of the thread to
+ """adds questions and answers of the thread to
given groups, comments are taken care of implicitly
by the underlying ``Post`` methods
"""
@@ -1258,7 +1298,7 @@ class Thread(models.Model):
return last_updated_at, last_updated_by
- def get_summary_html(self, search_state, visitor = None):
+ def get_summary_html(self, search_state=None, visitor = None):
html = self.get_cached_summary_html(visitor)
if not html:
html = self.update_summary_html(visitor)
@@ -1273,6 +1313,9 @@ class Thread(models.Model):
re.UNICODE
)
+ if search_state is None:
+ search_state = DummySearchState()
+
while True:
match = regex.search(html)
if not match:
@@ -1293,6 +1336,12 @@ class Thread(models.Model):
return cache.cache.get(self.SUMMARY_CACHE_KEY_TPL % self.id)
def update_summary_html(self, visitor = None):
+ #todo: it is quite wrong that visitor is an argument here
+ #because we do not include any visitor-related info in the cache key
+ #ideally cache should be shareable between users, so straight up
+ #using the user id for cache is wrong, we could use group
+ #memberships, but in that case we'd need to be more careful with
+ #cache invalidation
context = {
'thread': self,
#fetch new question post to make sure we're up-to-date
diff --git a/askbot/models/repute.py b/askbot/models/repute.py
index 33ec3a42..a6e9d7d1 100644
--- a/askbot/models/repute.py
+++ b/askbot/models/repute.py
@@ -75,14 +75,14 @@ class Vote(models.Model):
"""
#importing locally because of circular dependency
from askbot import auth
- score_before = self.voted_post.score
+ score_before = self.voted_post.points
if self.vote > 0:
# cancel upvote
auth.onUpVotedCanceled(self, self.voted_post, self.user)
else:
# cancel downvote
auth.onDownVotedCanceled(self, self.voted_post, self.user)
- score_after = self.voted_post.score
+ score_after = self.voted_post.points
return score_after - score_before
@@ -94,7 +94,7 @@ class BadgeData(models.Model):
awarded_to = models.ManyToManyField(User, through='Award', related_name='badges')
def _get_meta_data(self):
- """retrieves badge metadata stored
+ """retrieves badge metadata stored
in a file"""
from askbot.models import badges
return badges.get_badge(self.slug)
@@ -171,9 +171,9 @@ class ReputeManager(models.Manager):
tomorrow = today + datetime.timedelta(1)
rep_types = (1,-8)
sums = self.filter(models.Q(reputation_type__in=rep_types),
- user=user,
+ user=user,
reputed_at__range=(today, tomorrow),
- ).aggregate(models.Sum('positive'), models.Sum('negative'))
+ ).aggregate(models.Sum('positive'), models.Sum('negative'))
if sums:
pos = sums['positive__sum']
neg = sums['negative__sum']
@@ -200,7 +200,7 @@ class Repute(models.Model):
#assigned_by_moderator - so that reason can be displayed
#in that case Question field will be blank
comment = models.CharField(max_length=128, null=True)
-
+
objects = ReputeManager()
def __unicode__(self):
@@ -214,7 +214,7 @@ class Repute(models.Model):
"""returns HTML snippet with a link to related question
or a text description for a the reason of the reputation change
- in the implementation description is returned only
+ in the implementation description is returned only
for Repute.reputation_type == 10 - "assigned by the moderator"
part of the purpose of this method is to hide this idiosyncracy
@@ -242,7 +242,7 @@ class Repute(models.Model):
return '<a href="%(url)s" title="%(link_title)s">%(question_title)s</a>' \
% {
- 'url': self.question.get_absolute_url(),
+ 'url': self.question.get_absolute_url(),
'question_title': escape(self.question.thread.title),
'link_title': escape(link_title)
}
diff --git a/askbot/models/tag.py b/askbot/models/tag.py
index 38555e49..647ea5cf 100644
--- a/askbot/models/tag.py
+++ b/askbot/models/tag.py
@@ -3,6 +3,7 @@ import logging
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
+from django.conf import settings
from askbot.models.base import BaseQuerySetManager
from askbot import const
from askbot.conf import settings as askbot_settings
diff --git a/askbot/search/haystack/__init__.py b/askbot/search/haystack/__init__.py
new file mode 100644
index 00000000..71f04d00
--- /dev/null
+++ b/askbot/search/haystack/__init__.py
@@ -0,0 +1,59 @@
+try:
+ from haystack import indexes, site
+ from haystack.query import SearchQuerySet
+ from askbot.models import Post, Thread, User
+
+
+ class ThreadIndex(indexes.SearchIndex):
+ text = indexes.CharField(document=True, use_template=True)
+ title = indexes.CharField(model_attr='title')
+ post_text = indexes.CharField(model_attr='posts__text__search')
+
+ def index_queryset(self):
+ return Thread.objects.filter(posts__deleted=False)
+
+ def prepare(self, obj):
+ self.prepared_data = super(ThreadIndex, self).prepare(object)
+
+ self.prepared_data['tags'] = [tag.name for tag in objects.tags.all()]
+
+ class PostIndex(indexes.SearchIndex):
+ text = indexes.CharField(document=True, use_template=True)
+ post_text = indexes.CharField(model_attr='text')
+ author = indexes.CharField(model_attr='user')
+ thread_id = indexes.CharField(model_attr='thread')
+
+ def index_queryset(self):
+ return Post.objects.filter(deleted=False)
+
+ class UserIndex(indexes.SearchIndex):
+ text = indexes.CharField(document=True, use_template=True)
+
+ def index_queryset(self):
+ return User.objects.all()
+
+ site.register(Post, PostIndex)
+ site.register(Thread, ThreadIndex)
+ site.register(User, UserIndex)
+
+ class AskbotSearchQuerySet(SearchQuerySet):
+
+ def get_django_queryset(self, model_klass=Thread):
+ '''dirty hack because models() method from the
+ SearchQuerySet does not work </3'''
+ id_list = []
+ for r in self:
+ if r.model_name in ['thread','post'] \
+ and model_klass._meta.object_name.lower() == 'thread':
+ if getattr(r, 'thread_id'):
+ id_list.append(r.thread_id)
+ else:
+ id_list.append(r.pk)
+ elif r.model_name == model_klass._meta.object_name.lower():
+ #FIXME: add a highlight here?
+ id_list.append(r.pk)
+
+ return model_klass.objects.filter(id__in=set(id_list))
+
+except:
+ pass
diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py
index 3d24fc5e..94df6f29 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -162,6 +162,7 @@ INSTALLED_APPS = (
'django.contrib.humanize',
'django.contrib.sitemaps',
#'debug_toolbar',
+ #'haystack',
'askbot',
'askbot.deps.django_authopenid',
#'askbot.importers.stackexchange', #se loader
@@ -235,6 +236,13 @@ STATICFILES_DIRS = (
RECAPTCHA_USE_SSL = True
+#HAYSTACK_SETTINGS
+ENABLE_HAYSTACK_SEARCH = False
+HAYSTACK_SITECONF = 'askbot.search.haystack'
+#more information
+#http://django-haystack.readthedocs.org/en/v1.2.7/settings.html
+HAYSTACK_SEARCH_ENGINE = 'simple'
+
TINYMCE_COMPRESSOR = True
TINYMCE_SPELLCHECKER = False
TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, 'common/media/js/tinymce/')
diff --git a/askbot/setup_templates/settings.py.mustache b/askbot/setup_templates/settings.py.mustache
index a800edec..74295513 100644
--- a/askbot/setup_templates/settings.py.mustache
+++ b/askbot/setup_templates/settings.py.mustache
@@ -161,6 +161,8 @@ INSTALLED_APPS = (
'django.contrib.humanize',
'django.contrib.sitemaps',
#'debug_toolbar',
+ #Optional, to enable haystack search
+ #'haystack',
'askbot',
'askbot.deps.django_authopenid',
#'askbot.importers.stackexchange', #se loader
@@ -236,6 +238,13 @@ STATICFILES_DIRS = (
RECAPTCHA_USE_SSL = True
+#HAYSTACK_SETTINGS
+ENABLE_HAYSTACK_SEARCH = False
+HAYSTACK_SITECONF = 'askbot.search.haystack'
+#more information
+#http://django-haystack.readthedocs.org/en/v1.2.7/settings.html
+HAYSTACK_SEARCH_ENGINE = 'simple'
+
TINYMCE_COMPRESSOR = True
TINYMCE_SPELLCHECKER = False
TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, 'common/media/js/tinymce/')
diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py
index 736397fb..706e19ad 100644
--- a/askbot/startup_procedures.py
+++ b/askbot/startup_procedures.py
@@ -531,13 +531,27 @@ def test_avatar():
short_message = True
)
+def test_haystack():
+ if 'haystack' in django_settings.INSTALLED_APPS:
+ try_import('haystack', 'django-haystack', short_message = True)
+ if getattr(django_settings, 'ENABLE_HAYSTACK_SEARCH', False):
+ errors = list()
+ if not hasattr(django_settings, 'HAYSTACK_SEARCH_ENGINE'):
+ message = "Please HAYSTACK_SEARCH_ENGINE to an appropriate value, value 'simple' can be used for basic testing"
+ errors.append(message)
+ if not hasattr(django_settings, 'HAYSTACK_SITECONF'):
+ message = 'Please add HAYSTACK_SITECONF = "askbot.search.haystack"'
+ errors.append(message)
+ footer = 'Please refer to haystack documentation at http://django-haystack.readthedocs.org/en/v1.2.7/settings.html#haystack-search-engine'
+ print_errors(errors, footer=footer)
+
def test_custom_user_profile_tab():
setting_name = 'ASKBOT_CUSTOM_USER_PROFILE_TAB'
tab_settings = getattr(django_settings, setting_name, None)
if tab_settings:
if not isinstance(tab_settings, dict):
print "Setting %s must be a dictionary!!!" % setting_name
-
+
name = tab_settings.get('NAME', None)
slug = tab_settings.get('SLUG', None)
func_name = tab_settings.get('CONTENT_GENERATOR', None)
@@ -691,6 +705,7 @@ def run_startup_tests():
test_new_skins()
test_longerusername()
test_avatar()
+ test_haystack()
settings_tester = SettingsTester({
'CACHE_MIDDLEWARE_ANONYMOUS_ONLY': {
'value': True,
@@ -720,6 +735,10 @@ def run_startup_tests():
'RECAPTCHA_USE_SSL': {
'value': True,
'message': 'Please add: RECAPTCHA_USE_SSL = True'
+ },
+ 'HAYSTACK_SITECONF': {
+ 'value': 'askbot.search.haystack',
+ 'message': 'Please add: HAYSTACK_SITECONF = "askbot.search.haystack"'
}
})
settings_tester.run()
diff --git a/askbot/tests/__init__.py b/askbot/tests/__init__.py
index fcef288b..7e7e3c48 100644
--- a/askbot/tests/__init__.py
+++ b/askbot/tests/__init__.py
@@ -14,9 +14,12 @@ from askbot.tests.markup_test 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.haystack_search_tests import *
from askbot.tests.email_parsing_tests import *
from askbot.tests.widget_tests import *
from askbot.tests.category_tree_tests import CategoryTreeTests
+from askbot.tests.question_views_tests import *
from askbot.tests.user_model_tests import UserModelTests
+from askbot.tests.user_views_tests import *
from askbot.tests.utils_tests import *
from askbot.tests.view_context_tests import *
diff --git a/askbot/tests/__init__.py.orig b/askbot/tests/__init__.py.orig
deleted file mode 100644
index 905c90df..00000000
--- a/askbot/tests/__init__.py.orig
+++ /dev/null
@@ -1,19 +0,0 @@
-from askbot.tests.cache_tests import *
-from askbot.tests.email_alert_tests import *
-from askbot.tests.on_screen_notification_tests import *
-from askbot.tests.page_load_tests import *
-from askbot.tests.permission_assertion_tests import *
-from askbot.tests.db_api_tests import *
-from askbot.tests.skin_tests import *
-from askbot.tests.badge_tests import *
-from askbot.tests.management_command_tests import *
-from askbot.tests.search_state_tests import *
-from askbot.tests.form_tests import *
-from askbot.tests.follow_tests import *
-from askbot.tests.templatefilter_tests import *
-from askbot.tests.markup_test 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
-from askbot.tests.user_model_tests import UserModelTests
diff --git a/askbot/tests/badge_tests.py b/askbot/tests/badge_tests.py
index 0ed4b343..c184db6f 100644
--- a/askbot/tests/badge_tests.py
+++ b/askbot/tests/badge_tests.py
@@ -24,7 +24,7 @@ class BadgeTests(AskbotTestCase):
def assert_accepted_answer_badge_works(self,
badge_key = None,
- min_score = None,
+ min_points = None,
expected_count = 1,
previous_count = 0,
trigger = None
@@ -32,7 +32,7 @@ class BadgeTests(AskbotTestCase):
assert(trigger in ('accept_best_answer', 'upvote_answer'))
question = self.post_question(user = self.u1)
answer = self.post_answer(user = self.u2, question = question)
- answer.score = min_score - 1
+ answer.points = min_points - 1
answer.save()
recipient = answer.author
@@ -47,30 +47,30 @@ class BadgeTests(AskbotTestCase):
self.u1.upvote(answer)
self.assert_have_badge(badge_key, recipient, expected_count)
- def assert_upvoted_answer_badge_works(self,
+ def assert_upvoted_answer_badge_works(self,
badge_key = None,
- min_score = None,
+ min_points = None,
multiple = False
):
"""test answer badge where answer author is the recipient
where badge award is triggered by upvotes
- * min_score - minimum # of upvotes required
+ * min_points - minimum # of upvotes required
* multiple - multiple award or not
* badge_key - key on askbot.models.badges.Badge object
"""
question = self.post_question(user = self.u1)
answer = self.post_answer(user = self.u2, question = question)
- answer.score = min_score - 1
+ answer.points = min_points - 1
answer.save()
self.u1.upvote(answer)
self.assert_have_badge(badge_key, recipient = self.u2)
self.u3.upvote(answer)
self.assert_have_badge(badge_key, recipient = self.u2, expected_count = 1)
-
+
#post another question and check that there are no new badges
question2 = self.post_question(user = self.u1)
answer2 = self.post_answer(user = self.u2, question = question2)
- answer2.score = min_score - 1
+ answer2.score = min_points - 1
answer2.save()
self.u1.upvote(answer2)
@@ -85,28 +85,28 @@ class BadgeTests(AskbotTestCase):
expected_count = expected_count
)
- def assert_upvoted_question_badge_works(self,
+ def assert_upvoted_question_badge_works(self,
badge_key = None,
- min_score = None,
+ min_points = None,
multiple = False
):
"""test question badge where question author is the recipient
where badge award is triggered by upvotes
- * min_score - minimum # of upvotes required
+ * min_points - minimum # of upvotes required
* multiple - multiple award or not
* badge_key - key on askbot.models.badges.Badge object
"""
question = self.post_question(user = self.u1)
- question.score = min_score - 1
+ question.points = min_points - 1
question.save()
self.u2.upvote(question)
self.assert_have_badge(badge_key, recipient = self.u1)
self.u3.upvote(question)
self.assert_have_badge(badge_key, recipient = self.u1, expected_count = 1)
-
+
#post another question and check that there are no new badges
question2 = self.post_question(user = self.u1)
- question2.score = min_score - 1
+ question2.points = min_points - 1
question2.save()
self.u2.upvote(question2)
@@ -123,13 +123,13 @@ class BadgeTests(AskbotTestCase):
def test_disciplined_badge(self):
question = self.post_question(user = self.u1)
- question.score = settings.DISCIPLINED_BADGE_MIN_UPVOTES
+ question.points = settings.DISCIPLINED_BADGE_MIN_UPVOTES
question.save()
self.u1.delete_question(question)
self.assert_have_badge('disciplined', recipient = self.u1)
question2 = self.post_question(user = self.u1)
- question2.score = settings.DISCIPLINED_BADGE_MIN_UPVOTES
+ question2.points = settings.DISCIPLINED_BADGE_MIN_UPVOTES
question2.save()
self.u1.delete_question(question2)
self.assert_have_badge('disciplined', recipient = self.u1, expected_count = 2)
@@ -137,7 +137,7 @@ class BadgeTests(AskbotTestCase):
def test_peer_pressure_badge(self):
question = self.post_question(user = self.u1)
answer = self.post_answer(user = self.u1, question = question)
- answer.score = -1*settings.PEER_PRESSURE_BADGE_MIN_DOWNVOTES
+ answer.points = -1*settings.PEER_PRESSURE_BADGE_MIN_DOWNVOTES
answer.save()
self.u1.delete_answer(answer)
self.assert_have_badge('peer-pressure', recipient = self.u1)
@@ -145,21 +145,21 @@ class BadgeTests(AskbotTestCase):
def test_teacher_badge(self):
self.assert_upvoted_answer_badge_works(
badge_key = 'teacher',
- min_score = settings.TEACHER_BADGE_MIN_UPVOTES,
+ min_points = settings.TEACHER_BADGE_MIN_UPVOTES,
multiple = False
)
def test_nice_answer_badge(self):
self.assert_upvoted_answer_badge_works(
badge_key = 'nice-answer',
- min_score = settings.NICE_ANSWER_BADGE_MIN_UPVOTES,
+ min_points = settings.NICE_ANSWER_BADGE_MIN_UPVOTES,
multiple = True
)
def test_nice_question_badge(self):
self.assert_upvoted_question_badge_works(
badge_key = 'nice-question',
- min_score = settings.NICE_QUESTION_BADGE_MIN_UPVOTES,
+ min_points = settings.NICE_QUESTION_BADGE_MIN_UPVOTES,
multiple = True
)
@@ -227,7 +227,7 @@ class BadgeTests(AskbotTestCase):
question = self.post_question(user = self.u1)
answer = self.post_answer(user = self.u1, question = question)
min_votes = settings.SELF_LEARNER_BADGE_MIN_UPVOTES
- answer.score = min_votes - 1
+ answer.points = min_votes - 1
answer.save()
self.u2.upvote(answer)
self.assert_have_badge('self-learner', recipient = self.u1)
@@ -235,14 +235,14 @@ class BadgeTests(AskbotTestCase):
#copy-paste of the first question, except expect second badge
question = self.post_question(user = self.u1)
answer = self.post_answer(user = self.u1, question = question)
- answer.score = min_votes - 1
+ answer.points = min_votes - 1
answer.save()
self.u2.upvote(answer)
self.assert_have_badge('self-learner', recipient = self.u1, expected_count = 2)
question = self.post_question(user = self.u2)
answer = self.post_answer(user = self.u1, question = question)
- answer.score = min_votes - 1
+ answer.points = min_votes - 1
answer.save()
self.u2.upvote(answer)
#no badge when asker != answerer
@@ -282,13 +282,13 @@ class BadgeTests(AskbotTestCase):
def assert_enlightened_badge_works(self, trigger):
self.assert_accepted_answer_badge_works(
'enlightened',
- min_score = settings.ENLIGHTENED_BADGE_MIN_UPVOTES,
+ min_points = settings.ENLIGHTENED_BADGE_MIN_UPVOTES,
expected_count = 1,
trigger = trigger
)
self.assert_accepted_answer_badge_works(
'enlightened',
- min_score = settings.ENLIGHTENED_BADGE_MIN_UPVOTES,
+ min_points = settings.ENLIGHTENED_BADGE_MIN_UPVOTES,
expected_count = 1,
previous_count = 1,
trigger = trigger
@@ -297,13 +297,13 @@ class BadgeTests(AskbotTestCase):
def assert_guru_badge_works(self, trigger):
self.assert_accepted_answer_badge_works(
'guru',
- min_score = settings.GURU_BADGE_MIN_UPVOTES,
+ min_points = settings.GURU_BADGE_MIN_UPVOTES,
expected_count = 1,
trigger = trigger
)
self.assert_accepted_answer_badge_works(
'guru',
- min_score = settings.GURU_BADGE_MIN_UPVOTES,
+ min_points = settings.GURU_BADGE_MIN_UPVOTES,
previous_count = 1,
expected_count = 2,
trigger = trigger
@@ -330,8 +330,8 @@ class BadgeTests(AskbotTestCase):
user = self.u2,
question = question,
timestamp = future
- )
- answer.score = settings.NECROMANCER_BADGE_MIN_UPVOTES - 1
+ )
+ answer.points = settings.NECROMANCER_BADGE_MIN_UPVOTES - 1
answer.save()
self.assert_have_badge('necromancer', self.u2, expected_count = 0)
self.u1.upvote(answer)
@@ -457,7 +457,7 @@ class BadgeTests(AskbotTestCase):
self.u1.toggle_favorite_question(question)
"""no gaming"""
self.assert_have_badge('stellar-question', self.u1, 0)
-
+
def test_stellar_badge3(self):
question = self.post_question(user = self.u1)
settings.update('STELLAR_QUESTION_BADGE_MIN_STARS', 2)
@@ -480,9 +480,9 @@ class BadgeTests(AskbotTestCase):
self.post_comment(user = self.u1, parent_post = question)
self.assert_have_badge('commentator', self.u1, 0)
- self.post_comment(user = self.u1, parent_post = question)
+ self.post_comment(user = self.u1, parent_post = question)
self.assert_have_badge('commentator', self.u1, 1)
- self.post_comment(user = self.u1, parent_post = question)
+ self.post_comment(user = self.u1, parent_post = question)
self.assert_have_badge('commentator', self.u1, 1)
def test_taxonomist_badge(self):
diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py
index 5ad26e8a..5477990a 100644
--- a/askbot/tests/db_api_tests.py
+++ b/askbot/tests/db_api_tests.py
@@ -9,6 +9,7 @@ from django.test.client import Client
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django import forms
+from askbot import exceptions as askbot_exceptions
from askbot.tests.utils import AskbotTestCase
from askbot.tests.utils import with_settings
from askbot import models
@@ -38,6 +39,7 @@ class DBApiTests(AskbotTestCase):
user = user,
question = question,
)
+ return self.answer
def assert_post_is_deleted(self, post):
self.assertTrue(post.deleted == True)
@@ -82,6 +84,25 @@ class DBApiTests(AskbotTestCase):
)
return self.reload_object(q)
+ def test_user_cannot_post_two_answers(self):
+ question = self.post_question(user=self.user)
+ answer = self.post_answer(question=question, user=self.user)
+ self.assertRaises(
+ askbot_exceptions.AnswerAlreadyGiven,
+ self.post_answer,
+ question=question,
+ user=self.user
+ )
+
+ def test_user_can_post_answer_after_deleting_one(self):
+ question = self.post_question(user=self.user)
+ answer = self.post_answer(question=question, user=self.user)
+ self.user.delete_answer(answer=answer)
+ answer2 = self.post_answer(question=question, user=self.user)
+ answers = question.thread.get_answers(user=self.user)
+ self.assertEqual(answers.count(), 1)
+ self.assertEqual(answers[0], answer2)
+
def test_post_anonymous_question(self):
q = self.ask_anonymous_question()
self.assertTrue(q.is_anonymous)
@@ -173,13 +194,13 @@ class DBApiTests(AskbotTestCase):
count = models.Tag.objects.filter(name='one-tag').count()
self.assertEquals(count, 0)
-
+
@with_settings(MAX_TAG_LENGTH=200, MAX_TAGS_PER_POST=50)
def test_retag_tags_too_long_raises(self):
tags = "aoaoesuouooeueooeuoaeuoeou aostoeuoaethoeastn oasoeoa nuhoasut oaeeots aoshootuheotuoehao asaoetoeatuoasu o aoeuethut aoaoe uou uoetu uouuou ao aouosutoeh"
question = self.post_question(user=self.user)
self.assertRaises(
- exceptions.ValidationError,
+ exceptions.ValidationError,
self.user.retag_question,
question=question, tags=tags
)
@@ -415,10 +436,10 @@ class CommentTests(AskbotTestCase):
def test_other_user_can_cancel_upvote(self):
self.test_other_user_can_upvote_comment()
comment = models.Post.objects.get_comments().get(id = self.comment.id)
- self.assertEquals(comment.score, 1)
+ self.assertEquals(comment.points, 1)
self.other_user.upvote(comment, cancel = True)
comment = models.Post.objects.get_comments().get(id = self.comment.id)
- self.assertEquals(comment.score, 0)
+ self.assertEquals(comment.points, 0)
class GroupTests(AskbotTestCase):
def setUp(self):
@@ -526,7 +547,7 @@ class GroupTests(AskbotTestCase):
#because answer groups always inherit thread groups
self.edit_answer(user=self.u1, answer=answer, is_private=True)
self.assertEqual(answer.groups.count(), 1)
-
+
#here we have a simple case - the comment to answer was posted
#by the answer author!!!
#won't work when comment was by someone else
diff --git a/askbot/tests/haystack_search_tests.py b/askbot/tests/haystack_search_tests.py
new file mode 100644
index 00000000..7a8bfcfd
--- /dev/null
+++ b/askbot/tests/haystack_search_tests.py
@@ -0,0 +1,101 @@
+"""Tests haystack indexes and queries"""
+from django.core import exceptions
+from django.conf import settings
+from django.contrib.auth.models import User
+from askbot.tests.utils import AskbotTestCase, skipIf
+from askbot import models
+import datetime
+
+class HaystackSearchTests(AskbotTestCase):
+ """tests methods on User object,
+ that were added for askbot
+ """
+ def setUp(self):
+ self._old_value = getattr(settings, 'ENABLE_HAYSTACK_SEARCH', False)
+ setattr(settings, "ENABLE_HAYSTACK_SEARCH", True)
+
+ self.user = self.create_user(username='gepeto')
+ self.other_user = self.create_user(username = 'pinocho')
+ self.other_user.location = 'Managua'
+ self.other_user.about = "I'm made of wood, gepeto made me"
+ self.other_user.save()
+ body_1 = '''Lorem turpis purus? Amet mattis eu et sociis phasellus
+ montes elementum proin ut urna enim, velit, tincidunt quis ut,
+ et integer mus? Nunc! Vut sed? Ac tincidunt egestas adipiscing,
+ magna et pulvinar mid est urna ultricies, turpis tristique nisi,
+ cum. Urna. Purus elit porttitor nisi porttitor ridiculus tincidunt
+ amet duis, gepeto'''
+ #from Baldy of Nome by Esther Birdsall Darling
+ body_2 = ''' With unseeing eyes and dragging steps, the boy trudged along the snowy
+ trail, dreading the arrival at Golconda Camp. For there was the House of
+ Judgment, where all of the unfortunate events of that most unhappy day
+ would be reviewed sternly, lorem'''
+ self.question1 = self.post_question(
+ user=self.user,
+ body_text=body_1,
+ title='Test title 1'
+ )
+ self.question2 = self.post_question(
+ user=self.other_user,
+ body_text=body_2,
+ title='Test title 2, Baldy of Nome'
+ )
+ self.answer1 = self.post_answer(
+ user=self.user,
+ question = self.question1,
+ body_text="This is a answer for question 1"
+ )
+ self.answer1 = self.post_answer(
+ user=self.other_user,
+ question = self.question2,
+ body_text="Just a random text to fill the space"
+ )
+
+ def tearDown(self):
+ setattr(settings, "ENABLE_HAYSTACK_SEARCH", self._old_value)
+
+ @skipIf('haystack' not in settings.INSTALLED_APPS,
+ 'Haystack not setup')
+ def test_title_search(self):
+ #title search
+ title_search_qs = models.Thread.objects.get_for_query('title')
+ title_search_qs_2 = models.Thread.objects.get_for_query('Nome')
+ self.assertEquals(title_search_qs.count(), 2)
+ self.assertEquals(title_search_qs_2.count(), 1)
+
+ @skipIf('haystack' not in settings.INSTALLED_APPS,
+ 'Haystack not setup')
+ def test_body_search(self):
+
+ #bodysearch
+ body_search_qs = models.Thread.objects.get_for_query('Lorem')
+ self.assertEquals(body_search_qs.count(), 2)
+ body_search_qs_2 = models.Thread.objects.get_for_query('steps')
+ self.assertEquals(body_search_qs_2.count(), 1)
+
+ @skipIf('haystack' not in settings.INSTALLED_APPS,
+ 'Haystack not setup')
+ def test_user_profile_search(self):
+ #must return pinocho
+ user_profile_qs = models.get_users_by_text_query('wood')
+ self.assertEquals(user_profile_qs.count(), 1)
+
+ #returns both gepeto and pinocho because gepeto nickname
+ #and gepeto name in pinocho's profile
+ user_profile_qs = models.get_users_by_text_query('gepeto')
+ self.assertEquals(user_profile_qs.count(), 2)
+
+ @skipIf('haystack' not in settings.INSTALLED_APPS,
+ 'Haystack not setup')
+ def test_get_django_queryset(self):
+ '''makes a query that can return multiple models and test
+ get_django_queryset() method from AskbotSearchQuerySet'''
+ #gepeto is present in profile and in question
+ from askbot.search.haystack import AskbotSearchQuerySet
+ qs = AskbotSearchQuerySet().filter(content='gepeto').get_django_queryset(User)
+ for instance in qs:
+ self.assertTrue(isinstance(instance, User))
+
+ qs = AskbotSearchQuerySet().filter(content='gepeto').get_django_queryset(models.Thread)
+ for instance in qs:
+ self.assertTrue(isinstance(instance, models.Thread))
diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py
index 3805d012..4efac0f0 100644
--- a/askbot/tests/page_load_tests.py
+++ b/askbot/tests/page_load_tests.py
@@ -166,13 +166,15 @@ class PageLoadTestCase(AskbotTestCase):
group.save()
user = self.create_user('user')
user.join_group(group)
- self.post_question(user=user, title='alibaba', group_id=group.id)
+ question = self.post_question(user=user, title='alibaba', group_id=group.id)
+ #ask for data anonymously - should get nothing
query_data = {'query': 'alibaba'}
response = self.client.get(reverse('api_get_questions'), query_data)
response_data = simplejson.loads(response.content)
self.assertEqual(len(response_data), 0)
+ #log in - should get the question
self.client.login(method='force', user_id=user.id)
response = self.client.get(reverse('api_get_questions'), query_data)
response_data = simplejson.loads(response.content)
diff --git a/askbot/tests/post_model_tests.py b/askbot/tests/post_model_tests.py
index 1a3a9c49..e61fcd2d 100644
--- a/askbot/tests/post_model_tests.py
+++ b/askbot/tests/post_model_tests.py
@@ -618,7 +618,7 @@ class ThreadRenderCacheUpdateTests(AskbotTestCase):
def test_question_upvote_downvote(self):
question = self.post_question()
- question.score = 5
+ question.points = 5
question.vote_up_count = 7
question.vote_down_count = 2
question.save()
@@ -631,7 +631,7 @@ class ThreadRenderCacheUpdateTests(AskbotTestCase):
data = simplejson.loads(response.content)
self.assertEqual(1, data['success'])
- self.assertEqual(6, data['count']) # 6 == question.score(5) + 1
+ self.assertEqual(6, data['count']) # 6 == question.points(5) + 1
thread = Thread.objects.get(id=question.thread.id)
@@ -647,7 +647,7 @@ class ThreadRenderCacheUpdateTests(AskbotTestCase):
data = simplejson.loads(response.content)
self.assertEqual(1, data['success'])
- self.assertEqual(5, data['count']) # 6 == question.score(6) - 1
+ self.assertEqual(5, data['count']) # 6 == question.points(6) - 1
thread = Thread.objects.get(id=question.thread.id)
diff --git a/askbot/tests/question_views_tests.py b/askbot/tests/question_views_tests.py
new file mode 100644
index 00000000..b1836f9e
--- /dev/null
+++ b/askbot/tests/question_views_tests.py
@@ -0,0 +1,207 @@
+from bs4 import BeautifulSoup
+from askbot.conf import settings as askbot_settings
+from askbot import const
+from askbot.tests.utils import AskbotTestCase
+from askbot import models
+from askbot.models.tag import get_global_group
+from django.core.urlresolvers import reverse
+
+
+class PrivateQuestionViewsTests(AskbotTestCase):
+
+ def setUp(self):
+ self._backup = askbot_settings.GROUPS_ENABLED
+ askbot_settings.update('GROUPS_ENABLED', True)
+ self.user = self.create_user('user')
+ self.group = models.Group.objects.create(
+ name='the group', openness=models.Group.OPEN
+ )
+ self.user.join_group(self.group)
+ self.qdata = {
+ 'title': 'test question title',
+ 'text': 'test question text'
+ }
+ self.client.login(user_id=self.user.id, method='force')
+
+ def tearDown(self):
+ askbot_settings.update('GROUPS_ENABLED', self._backup)
+
+ def test_post_private_question(self):
+ data = self.qdata
+ data['post_privately'] = 'checked'
+ response1 = self.client.post(reverse('ask'), data=data)
+ response2 = self.client.get(response1['location'])
+ dom = BeautifulSoup(response2.content)
+ title = dom.find('h1').text
+ self.assertTrue(const.POST_STATUS['private'] in title)
+ question = models.Thread.objects.get(id=1)
+ self.assertEqual(question.title, self.qdata['title'])
+ self.assertFalse(get_global_group() in set(question.groups.all()))
+
+ #private question is not accessible to unauthorized users
+ self.client.logout()
+ response = self.client.get(question._question_post().get_absolute_url())
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.content, '')
+ #private question link is not shown on the main page
+ #to unauthorized users
+ response = self.client.get(reverse('questions'))
+ self.assertFalse(self.qdata['title'] in response.content)
+ #private question link is not shown on the poster profile
+ #to the unauthorized users
+ response = self.client.get(self.user.get_profile_url())
+ self.assertFalse(self.qdata['title'] in response.content)
+
+ def test_publish_private_question(self):
+ question = self.post_question(user=self.user, is_private=True)
+ title = question.thread.get_title()
+ self.assertTrue(const.POST_STATUS['private'] in title)
+ data = self.qdata
+ #data['post_privately'] = 'false'
+ data['select_revision'] = 'false'
+ data['text'] = 'edited question text'
+ response1 = self.client.post(
+ reverse('edit_question', kwargs={'id':question.id}),
+ data=data
+ )
+ response2 = self.client.get(question.get_absolute_url())
+ dom = BeautifulSoup(response2.content)
+ title = dom.find('h1').text
+ self.assertTrue(get_global_group() in set(question.groups.all()))
+ self.assertEqual(title, self.qdata['title'])
+
+ self.client.logout()
+ response = self.client.get(question.get_absolute_url())
+ self.assertTrue('edited question text' in response.content)
+
+ def test_privatize_public_question(self):
+ question = self.post_question(user=self.user)
+ title = question.thread.get_title()
+ self.assertFalse(const.POST_STATUS['private'] in title)
+ data = self.qdata
+ data['post_privately'] = 'checked'
+ data['select_revision'] = 'false'
+ response1 = self.client.post(
+ reverse('edit_question', kwargs={'id':question.id}),
+ data=data
+ )
+ response2 = self.client.get(question.get_absolute_url())
+ dom = BeautifulSoup(response2.content)
+ title = dom.find('h1').text
+ self.assertFalse(get_global_group() in set(question.groups.all()))
+ self.assertTrue(const.POST_STATUS['private'] in title)
+
+ def test_private_checkbox_is_on_when_editing_private_question(self):
+ question = self.post_question(user=self.user, is_private=True)
+ response = self.client.get(
+ reverse('edit_question', kwargs={'id':question.id})
+ )
+ dom = BeautifulSoup(response.content)
+ checkbox = dom.find(
+ 'input', attrs={'type': 'checkbox', 'name': 'post_privately'}
+ )
+ self.assertEqual(checkbox['checked'], 'checked')
+
+ def test_private_checkbox_is_off_when_editing_public_question(self):
+ question = self.post_question(user=self.user)
+ response = self.client.get(
+ reverse('edit_question', kwargs={'id':question.id})
+ )
+ dom = BeautifulSoup(response.content)
+ checkbox = dom.find(
+ 'input', attrs={'type': 'checkbox', 'name': 'post_privately'}
+ )
+ self.assertEqual(checkbox.get('checked', False), False)
+
+
+class PrivateAnswerViewsTests(AskbotTestCase):
+
+ def setUp(self):
+ self._backup = askbot_settings.GROUPS_ENABLED
+ askbot_settings.update('GROUPS_ENABLED', True)
+ self.user = self.create_user('user')
+ group = models.Group.objects.create(
+ name='the group', openness=models.Group.OPEN
+ )
+ self.user.join_group(group)
+ self.question = self.post_question(user=self.user)
+ self.client.login(user_id=self.user.id, method='force')
+
+ def tearDown(self):
+ askbot_settings.update('GROUPS_ENABLED', self._backup)
+
+ def test_post_private_answer(self):
+ response1 = self.client.post(
+ reverse('answer', kwargs={'id': self.question.id}),
+ data={'text': 'some answer text', 'post_privately': 'checked'}
+ )
+ answer = self.question.thread.get_answers(user=self.user)[0]
+ self.assertFalse(get_global_group() in set(answer.groups.all()))
+ self.client.logout()
+
+ user2 = self.create_user('user2')
+ self.client.login(user_id=user2.id, method='force')
+ response = self.client.get(self.question.get_absolute_url())
+ self.assertFalse('some answer text' in response.content)
+
+ self.client.logout()
+ response = self.client.get(self.question.get_absolute_url())
+ self.assertFalse('some answer text' in response.content)
+
+
+ def test_private_checkbox_is_on_when_editing_private_answer(self):
+ answer = self.post_answer(
+ question=self.question, user=self.user, is_private=True
+ )
+ response = self.client.get(
+ reverse('edit_answer', kwargs={'id': answer.id})
+ )
+ dom = BeautifulSoup(response.content)
+ checkbox = dom.find(
+ 'input', attrs={'type': 'checkbox', 'name': 'post_privately'}
+ )
+ self.assertEqual(checkbox['checked'], 'checked')
+
+ def test_privaet_checkbox_is_off_when_editing_public_answer(self):
+ answer = self.post_answer(question=self.question, user=self.user)
+ response = self.client.get(
+ reverse('edit_answer', kwargs={'id': answer.id})
+ )
+ dom = BeautifulSoup(response.content)
+ checkbox = dom.find(
+ 'input', attrs={'type': 'checkbox', 'name': 'post_privately'}
+ )
+ self.assertEqual(checkbox.get('checked', False), False)
+
+ def test_publish_private_answer(self):
+ answer = self.post_answer(
+ question=self.question, user=self.user, is_private=True
+ )
+ self.client.post(
+ reverse('edit_answer', kwargs={'id': answer.id}),
+ data={'text': 'edited answer text', 'select_revision': 'false'}
+ )
+ answer = self.reload_object(answer)
+ self.assertTrue(get_global_group() in answer.groups.all())
+ self.client.logout()
+ response = self.client.get(self.question.get_absolute_url())
+ self.assertTrue('edited answer text' in response.content)
+
+
+ def test_privatize_public_answer(self):
+ answer = self.post_answer(question=self.question, user=self.user)
+ self.client.post(
+ reverse('edit_answer', kwargs={'id': answer.id}),
+ data={
+ 'text': 'edited answer text',
+ 'post_privately': 'checked',
+ 'select_revision': 'false'
+ }
+ )
+ #check that answer is not visible to the "everyone" group
+ answer = self.reload_object(answer)
+ self.assertFalse(get_global_group() in answer.groups.all())
+ #check that countent is not seen by an anonymous user
+ self.client.logout()
+ response = self.client.get(self.question.get_absolute_url())
+ self.assertFalse('edited answer text' in response.content)
diff --git a/askbot/tests/user_views_tests.py b/askbot/tests/user_views_tests.py
new file mode 100644
index 00000000..489cf76a
--- /dev/null
+++ b/askbot/tests/user_views_tests.py
@@ -0,0 +1,39 @@
+from askbot.tests.utils import AskbotTestCase
+from askbot.views.users import owner_or_moderator_required
+from django.contrib.auth.models import AnonymousUser
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from mock import Mock
+import urllib
+import urlparse
+
+class UserViewsTests(AskbotTestCase):
+
+ def test_owner_or_mod_required_passes_url_parameters(self):
+ @owner_or_moderator_required
+ def mock_view(request, user, context):
+ return None
+
+ request = Mock(spec=('path', 'REQUEST', 'user'))
+ request.user = AnonymousUser()
+ request.REQUEST = {'abra': 'cadabra', 'foo': 'bar'}
+ request.path = '/some/path/'
+ user = self.create_user('user')
+ response = mock_view(request, user, {})
+ self.assertEqual(isinstance(response, HttpResponseRedirect), True)
+
+ url = response['location']
+ parsed_url = urlparse.urlparse(url)
+
+ self.assertEqual(parsed_url.path, reverse('user_signin'))
+
+ next = dict(urlparse.parse_qsl(parsed_url.query))['next']
+ next_url = urllib.unquote(next)
+ parsed_url = urlparse.urlparse(next_url)
+
+ self.assertEqual(parsed_url.path, request.path)
+
+ query = dict(urlparse.parse_qsl(parsed_url.query))
+ self.assertEqual(set(query.keys()), set(['foo', 'abra']))
+ self.assertEqual(set(query.values()), set(['bar', 'cadabra']))
+ self.assertEqual(query['abra'], 'cadabra')
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index f02061cd..f7d22f48 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -169,7 +169,7 @@ def process_vote(user = None, vote_direction = None, post = None):
if vote != None:
user.assert_can_revoke_old_vote(vote)
score_delta = vote.cancel()
- response_data['count'] = post.score + score_delta
+ response_data['count'] = post.points+ score_delta
response_data['status'] = 1 #this means "cancel"
else:
@@ -192,7 +192,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.points
response_data['status'] = 0 #this means "not cancel", normal operation
response_data['success'] = 1
@@ -842,7 +842,8 @@ def upvote_comment(request):
)
else:
raise ValueError
- return {'score': comment.score}
+ #FIXME: rename js
+ return {'score': comment.points}
@csrf.csrf_exempt
@decorators.ajax_only
diff --git a/askbot/views/users.py b/askbot/views/users.py
index dbcbda5c..c22fd294 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -12,6 +12,7 @@ import functools
import datetime
import logging
import operator
+import urllib
from django.db.models import Count
from django.conf import settings as django_settings
@@ -54,7 +55,8 @@ def owner_or_moderator_required(f):
elif request.user.is_authenticated() and request.user.can_moderate_user(profile_owner):
pass
else:
- params = '?next=%s' % request.path
+ next_url = request.path + '?' + urllib.urlencode(request.REQUEST)
+ params = '?next=%s' % urllib.quote(next_url)
return HttpResponseRedirect(url_utils.get_login_url() + params)
return f(request, profile_owner, context)
return wrapped_func
@@ -369,9 +371,15 @@ def user_stats(request, user, context):
#
# Questions
#
- questions = user.posts.get_questions().filter(**question_filter).\
- order_by('-score', '-thread__last_activity_at').\
- select_related('thread', 'thread__last_activity_by')[:100]
+ questions = user.posts.get_questions(
+ user=request.user
+ ).filter(
+ **question_filter
+ ).order_by(
+ '-points', '-thread__last_activity_at'
+ ).select_related(
+ 'thread', 'thread__last_activity_by'
+ )[:100]
#added this if to avoid another query if questions is less than 100
if len(questions) < 100:
@@ -391,7 +399,7 @@ def user_stats(request, user, context):
).select_related(
'thread'
).order_by(
- '-score', '-added_at'
+ '-points', '-added_at'
)[:100]
top_answer_count = len(top_answers)
@@ -724,7 +732,7 @@ def user_responses(request, user, context):
and "flags" - moderation items for mods only
"""
- #0) temporary, till urls are fixed: update context
+ #0) temporary, till urls are fixed: update context
# to contain response counts for all sub-sections
context.update(view_context.get_for_inbox(request.user))
@@ -904,7 +912,7 @@ def user_favorites(request, user, context):
favorite_threads = user.user_favorite_questions.values_list('thread', flat=True)
questions = models.Post.objects.filter(post_type='question', thread__in=favorite_threads)\
.select_related('thread', 'thread__last_activity_by')\
- .order_by('-score', '-thread__last_activity_at')[:const.USER_VIEW_DATA_SIZE]
+ .order_by('-points', '-thread__last_activity_at')[:const.USER_VIEW_DATA_SIZE]
data = {
'active_tab':'users',
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index 4024b4b0..db7a24d2 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -473,7 +473,7 @@ def edit_answer(request, id):
if request.POST['select_revision'] == 'true':
# user has changed revistion number
revision_form = forms.RevisionForm(
- answer,
+ answer,
revision,
request.POST
)
@@ -496,13 +496,13 @@ def edit_answer(request, id):
if form.has_changed():
user = form.get_post_user(request.user)
user.edit_answer(
- answer = answer,
- 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
- )
+ answer=answer,
+ 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('post_privately', False)
+ #todo: add wiki field to form
+ )
return HttpResponseRedirect(answer.get_absolute_url())
else:
revision_form = forms.RevisionForm(answer, revision)
@@ -618,7 +618,8 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po
'user_id': comment_owner.id,
'is_deletable': is_deletable,
'is_editable': is_editable,
- 'score': comment.score,
+ 'points': comment.points,
+ 'score': comment.points, #to support js
'upvoted_by_user': getattr(comment, 'upvoted_by_user', False)
}
json_comments.append(comment_data)
@@ -685,7 +686,8 @@ def edit_comment(request):
'user_id': comment_post.author.id,
'is_deletable': is_deletable,
'is_editable': is_editable,
- 'score': comment_post.score,
+ 'score': comment_post.points, #to support unchanged js
+ 'points': comment_post.points,
'voted': comment_post.is_upvoted_by(request.user),
}