summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/models/content.py2
-rw-r--r--askbot/models/post.py22
-rw-r--r--askbot/models/post_view.sql2
-rw-r--r--askbot/skins/default/templates/user_profile/user_stats.html6
-rw-r--r--askbot/tests/cache_tests.py8
-rw-r--r--askbot/tests/permission_assertion_tests.py5
-rw-r--r--askbot/tests/utils.py17
-rw-r--r--askbot/views/users.py60
8 files changed, 103 insertions, 19 deletions
diff --git a/askbot/models/content.py b/askbot/models/content.py
index 1ec11d72..8424c8f6 100644
--- a/askbot/models/content.py
+++ b/askbot/models/content.py
@@ -84,7 +84,7 @@ class Content(models.Model):
return self.html
raise NotImplementedError
- def get_absolute_url(self, no_slug = False):
+ def get_absolute_url(self, no_slug = False): # OVERRIDEN by Post.get_absolute_url()
if self.is_answer():
return u'%(base)s%(slug)s?answer=%(id)d#answer-container-%(id)d' % \
{
diff --git a/askbot/models/post.py b/askbot/models/post.py
index b3e6770e..11a3f774 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -1,5 +1,7 @@
+from django.core import urlresolvers
from django.db import models
from django.core.exceptions import ValidationError
+from django.utils.http import urlquote as django_urlquote
from askbot.utils import markup
from askbot.utils.html import sanitize_html
@@ -20,6 +22,8 @@ class Post(content.Content):
self_answer = models.ForeignKey('Answer', blank=True, null=True)
self_question = models.ForeignKey('Question', blank=True, null=True)
+ question = property(fget=lambda self: self.self_answer.question) # to simulate Answer model
+
thread = models.ForeignKey('Thread')
objects = PostManager()
@@ -29,6 +33,24 @@ class Post(content.Content):
db_table = 'askbot_post'
managed = False
+ def get_absolute_url(self, no_slug = False): # OVERRIDE for Content.get_absolute_url()
+ from askbot.utils.slug import slugify
+ if self.is_answer():
+ return u'%(base)s%(slug)s?answer=%(id)d#answer-container-%(id)d' % \
+ {
+ 'base': urlresolvers.reverse('question', args=[self.self_answer.question_id]),
+ 'slug': django_urlquote(slugify(self.thread.title)),
+ 'id': self.self_answer_id
+ }
+ elif self.is_question():
+ url = urlresolvers.reverse('question', args=[self.self_question_id])
+ if no_slug == True:
+ return url
+ else:
+ return url + django_urlquote(self.slug)
+ raise NotImplementedError
+
+
def delete(self, *args, **kwargs):
# Redirect the deletion to the relevant Question or Answer instance
# WARNING: This is not called for batch deletions so watch out!
diff --git a/askbot/models/post_view.sql b/askbot/models/post_view.sql
index a3487265..a2218d7d 100644
--- a/askbot/models/post_view.sql
+++ b/askbot/models/post_view.sql
@@ -6,7 +6,7 @@ Tested with: SQLite3
*/
-CREATE VIEW askbot_post AS
+CREATE VIEW IF NOT EXISTS askbot_post AS
SELECT
answer.id + 1000000 AS id, -- fake unique ID
diff --git a/askbot/skins/default/templates/user_profile/user_stats.html b/askbot/skins/default/templates/user_profile/user_stats.html
index f850c6ca..6e6d2008 100644
--- a/askbot/skins/default/templates/user_profile/user_stats.html
+++ b/askbot/skins/default/templates/user_profile/user_stats.html
@@ -112,20 +112,18 @@
<span class="tag-number">&#215;
<span class="badge-context-toggle">{{ badge_user_awards|length|intcomma }}</span>
</span>
-{# TODO: Uncomment&adjust after Django view can prepare a list of Post-s for each award
<ul id="badge-context-{{ badge.id }}" class="badge-context-list" style="display:none">
{% for award in badge_user_awards %}
- {% if award.content_type in (question_type, answer_type) %}
+ {% if award.content_object %}
<li>
<a
title="{{ award.content_object.get_snippet()|collapse }}"
href="{{ award.content_object.get_absolute_url() }}"
- >{% if award.content_type == answer_type %}{% trans %}Answer to:{% endtrans %}{% endif %} {{ award.content_object.thread.title }}</a>
+ >{% if award.content_type.post_type == 'answer' %}{% trans %}Answer to:{% endtrans %}{% endif %} {{ award.content_object.thread.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
-#}
{% if loop.index is divisibleby 3 %}
</td></tr>
<tr><td style="line-height:35px">
diff --git a/askbot/tests/cache_tests.py b/askbot/tests/cache_tests.py
index 5eda8c74..a8416e99 100644
--- a/askbot/tests/cache_tests.py
+++ b/askbot/tests/cache_tests.py
@@ -3,12 +3,12 @@ from django.core.urlresolvers import reverse
from django.conf import settings
from askbot.tests.utils import AskbotTestCase
+
class CacheTests(AskbotTestCase):
def setUp(self):
- self.create_user()
- self.create_user('other_user')
- self.question = self.post_question()
- self.post_answer(question = self.question)
+ user = self.create_user('other_user')
+ self.question = self.post_question(user=user)
+ self.post_answer(user=user, question=self.question)
settings.DEBUG = True # because it's forsed to False
def visit_question(self):
diff --git a/askbot/tests/permission_assertion_tests.py b/askbot/tests/permission_assertion_tests.py
index b83d7827..8d98cbdd 100644
--- a/askbot/tests/permission_assertion_tests.py
+++ b/askbot/tests/permission_assertion_tests.py
@@ -8,10 +8,10 @@ from askbot.tests import utils
from askbot.conf import settings as askbot_settings
from askbot import models
from askbot.templatetags import extra_filters as template_filters
-from askbot.tests.utils import skipIf
+from askbot.tests.utils import skipIf, AskbotTestCase
-class PermissionAssertionTestCase(TestCase):
+class PermissionAssertionTestCase(AskbotTestCase):
"""base TestCase class for permission
assertion tests
@@ -484,6 +484,7 @@ class ReopenQuestionPermissionAssertionTests(utils.AskbotTestCase):
class EditQuestionPermissionAssertionTests(utils.AskbotTestCase):
def setUp(self):
+ super(EditQuestionPermissionAssertionTests, self).setUp()
self.create_user()
self.create_user(username = 'other_user')
self.post = self.post_question()
diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py
index fdeea371..218e9c4d 100644
--- a/askbot/tests/utils.py
+++ b/askbot/tests/utils.py
@@ -62,6 +62,23 @@ class AskbotTestCase(TestCase):
to django TestCase class
"""
+ def _fixture_setup(self):
+ # HACK: Create askbot_post database VIEW for the purpose of performing tests
+ import os.path
+ from django.conf import settings
+ from django.db import connection
+ sql = open(os.path.join(settings.PROJECT_ROOT, 'askbot', 'models', 'post_view.sql'), 'rt').read()
+ cursor = connection.cursor()
+ cursor.execute(sql)
+ super(AskbotTestCase, self)._fixture_setup()
+
+ def _fixture_teardown(self):
+ super(AskbotTestCase, self)._fixture_teardown()
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute('DROP VIEW IF EXISTS askbot_post')
+
+
def create_user(
self,
username = 'user',
diff --git a/askbot/views/users.py b/askbot/views/users.py
index 0d90105d..93ddce92 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -10,9 +10,8 @@ import calendar
import functools
import datetime
import logging
-import operator
-from django.db.models import Count, Sum
+from django.db.models import Count, Q
from django.conf import settings as django_settings
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator, EmptyPage, InvalidPage
@@ -282,6 +281,9 @@ def user_stats(request, user, context):
if request.user != user:
question_filter['is_anonymous'] = False
+ #
+ # Questions
+ #
questions = user.posts.get_questions().filter(**question_filter).\
order_by('-score', '-thread__last_activity_at').\
select_related('thread', 'thread__last_activity_by')[:100]
@@ -292,7 +294,9 @@ def user_stats(request, user, context):
else:
question_count = user.posts.get_questions().filter(**question_filter).count()
- #this is meant for the questions answered by the user (or where answers were edited by him/her?)
+ #
+ # Top answers
+ #
top_answers = user.posts.get_answers().filter(
deleted=False,
parent__deleted=False,
@@ -300,32 +304,74 @@ def user_stats(request, user, context):
top_answer_count = len(top_answers)
+ #
+ # Votes
+ #
up_votes = models.Vote.objects.get_up_vote_count_from_user(user)
down_votes = models.Vote.objects.get_down_vote_count_from_user(user)
votes_today = models.Vote.objects.get_votes_count_today_from_user(user)
votes_total = askbot_settings.MAX_VOTES_PER_USER_PER_DAY
+ #
+ # Tags
+ #
# INFO: There's bug in Django that makes the following query kind of broken (GROUP BY clause is problematic):
# http://stackoverflow.com/questions/7973461/django-aggregation-does-excessive-group-by-clauses
# Fortunately it looks to return correct results for the test data
user_tags = models.Tag.objects.filter(threads__post__author=user).\
annotate(user_tag_usage_count=Count('threads')).\
order_by('-user_tag_usage_count')[:const.USER_VIEW_DATA_SIZE]
+ user_tags = list(user_tags) # evaluate
+
+
+ #
+ # Badges/Awards (TODO: refactor into Managers/QuerySets when a pattern emerges; Simplify when we get rid of Queastion&Answer models)
+ #
+ question_type = ContentType.objects.get_for_model(models.Question)
+ answer_type = ContentType.objects.get_for_model(models.Answer)
user_awards = models.Award.objects.filter(user=user).select_related('badge')
+
+ awarded_answer_ids = []
+ awarded_question_ids = []
+ for award in user_awards:
+ if award.content_type_id == question_type.id:
+ awarded_question_ids.append(award.object_id)
+ elif award.content_type_id == answer_type.id:
+ awarded_answer_ids.append(award.object_id)
+
+ awarded_posts = models.Post.objects.filter(
+ Q(self_answer__in=awarded_answer_ids)|Q(self_question__in=awarded_question_ids)
+ ).select_related('self_answer', 'thread') # select related to avoid additional queries in Post.get_absolute_url()
+ awarded_questions_map = {}
+ awarded_answers_map = {}
+ for post in awarded_posts:
+ if post.self_question_id:
+ awarded_questions_map[post.self_question_id] = post
+ elif post.self_answer_id:
+ awarded_answers_map[post.self_answer_id] = post
+
badges_dict = {}
+
for award in user_awards:
+ # Fetch content object
+ if award.content_type_id == question_type.id:
+ award.content_object = awarded_questions_map[award.object_id]
+ elif award.content_type_id == answer_type.id:
+ award.content_object = awarded_answers_map[award.object_id]
+ else:
+ award.content_object = None
+
+ # "Assign" to its Badge
if award not in badges_dict:
badges_dict[award.badge] = [award]
else:
badges_dict[award.badge].append(award)
+
badges = list(badges_dict.items())
badges.sort(key=lambda badge_tuple: len(badge_tuple[1]), reverse=True)
- # TODO: fetch award.content_object in one query, as a list of Post-s, and pass to template to avoid subsequent queries there
- total_badges = len(badges)
-# INFO: Shorter version for fetching badges, but then it's hard to do the postprocessing (i.e. getting user awards per badge etc.)
-# badges = models.BadgeData.objects.filter(award_badge__user=user).annotate(user_awarded_times=Count('award_badge'))
+ total_badges = len(badges)
data = {
'active_tab':'users',