From dfb22a3c8c850b81c5c6926a8487bbcaeab7ec02 Mon Sep 17 00:00:00 2001 From: Tomasz Zielinski Date: Fri, 9 Dec 2011 22:35:37 +0100 Subject: Tickets 104, 107: User_stats view optimizations --- askbot/models/post.py | 17 ++ .../default/templates/user_profile/user_stats.html | 250 +++++++++++---------- askbot/views/readers.py | 2 + askbot/views/users.py | 144 ++++-------- 4 files changed, 192 insertions(+), 221 deletions(-) diff --git a/askbot/models/post.py b/askbot/models/post.py index b71d3bab..b3e6770e 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -5,14 +5,25 @@ from askbot.utils import markup from askbot.utils.html import sanitize_html from askbot.models import content +class PostManager(models.Manager): + def get_questions(self): + return self.filter(post_type='question') + + def get_answers(self): + return self.filter(post_type='answer') + class Post(content.Content): post_type = models.CharField(max_length=255) parent = models.ForeignKey('Post', blank=True, null=True) + self_answer = models.ForeignKey('Answer', blank=True, null=True) self_question = models.ForeignKey('Question', blank=True, null=True) + thread = models.ForeignKey('Thread') + objects = PostManager() + class Meta: app_label = 'askbot' db_table = 'askbot_post' @@ -24,6 +35,12 @@ class Post(content.Content): real_post = self.self_answer or self.self_question real_post.delete(*args, **kwargs) + def is_answer_accepted(self): + if not self.is_answer(): + raise NotImplementedError + return self.thread.accepted_answer_id and (self.thread.accepted_answer_id == self.self_answer_id) + + for field in Post._meta.fields: if isinstance(field, models.ForeignKey): # HACK: Patch all foreign keys to not cascade when deleted diff --git a/askbot/skins/default/templates/user_profile/user_stats.html b/askbot/skins/default/templates/user_profile/user_stats.html index 46d5a40d..f850c6ca 100644 --- a/askbot/skins/default/templates/user_profile/user_stats.html +++ b/askbot/skins/default/templates/user_profile/user_stats.html @@ -4,135 +4,137 @@ {% block profilesection %} {% trans %}overview{% endtrans %} {% endblock %} - {% block usercontent %} - {% include "user_profile/user_info.html" %} - - {% spaceless %} -

{% trans counter=question_count %}{{counter}} Question{% pluralize %}{{counter}} Questions{% endtrans %}

- {% endspaceless %} +{% block usercontent %} + {% include "user_profile/user_info.html" %} + + {% spaceless %} +

{% trans counter=question_count %}{{counter}} Question{% pluralize %}{{counter}} Questions{% endtrans %}

+ {% endspaceless %} {% include "user_profile/users_questions.html" %} - - {% spaceless %} -

{% trans counter=answered_questions|length %}{{counter}} Answer{% pluralize %}{{counter}} Answers{% endtrans %}

- {% endspaceless %} -
- {% for answered_question in answered_questions %} -
- - - {{ answered_question.answer_score }} - - - + + {% spaceless %} +

{{counter}} {% trans counter=top_answer_count %}Answer{% pluralize %}Answers{% endtrans %}

+ {% endspaceless %} +
+ {% for top_answer in top_answers %} +
+ + + {{ top_answer.score }} + + + - {% endfor %}
-
- - {% spaceless %} -

{% trans cnt=total_votes %}{{cnt}} Vote{% pluralize %}{{cnt}} Votes {% endtrans %}

- {% endspaceless %} -
- - - - + + +
- {% trans %}thumb up{% endtrans %} - {{up_votes}} - - {% trans %}thumb down{% endtrans %} - {{down_votes}} + {% endfor %} + +
+ + {% spaceless %} +

{% trans cnt=total_votes %}{{cnt}} Vote{% pluralize %}{{cnt}} Votes {% endtrans %}

+ {% endspaceless %} +
+ + + + - -
+ {% trans %}thumb up{% endtrans %} + {{up_votes}} + + {% trans %}thumb down{% endtrans %} + {{down_votes}} -
-
- - {% spaceless %} -

{% trans counter=user_tags|length %}{{counter}} Tag{% pluralize %}{{counter}} Tags{% endtrans %}

- {% endspaceless %} -
- - - - + +
-
    - {% for tag in user_tags %} -
  • - {{ macros.tag_widget( - tag.name, - html_tag = 'div', - url_params = - "author=" ~ view_user.id ~ - "&start_over=true", - extra_content = - '× ' ~ - tag.user_tag_usage_count|intcomma ~ - '' - ) - }} -
  • - {# - {% if loop.index is divisibleby 10 %} -
- {% endif %} - #} - {% endfor %} +
+
+ + {% spaceless %} +

{% trans counter=user_tags|length %}{{counter}} Tag{% pluralize %}{{counter}} Tags{% endtrans %}

+ {% endspaceless %} +
+ + + + + +
+
    + {% for tag in user_tags %} +
  • + {{ macros.tag_widget( + tag.name, + html_tag = 'div', + url_params = + "author=" ~ view_user.id ~ + "&start_over=true", + extra_content = + '× ' ~ + tag.user_tag_usage_count|intcomma ~ + '' + ) + }} +
  • + {# + {% if loop.index is divisibleby 10 %} +
+ {% endif %} + #} + {% endfor %} + +
+
+ + {% spaceless %} +

{% trans counter=total_badges %}{{counter}} Badge{% pluralize %}{{counter}} Badges{% endtrans %}

+ {% endspaceless %} +
+ + + - -
+ {% for badge, badge_user_awards in badges %} +  {% trans name=badge.name %}{{name}}{% endtrans %} +   + × + {{ badge_user_awards|length|intcomma }} + +{# TODO: Uncomment&adjust after Django view can prepare a list of Post-s for each award + -
-
- - {% spaceless %} -

{% trans counter=total_awards %}{{counter}} Badge{% pluralize %}{{counter}} Badges{% endtrans %}

- {% endspaceless %} -
- - - - - -
- {% for badge in badges %}{# todo: translate badge name properly #} -  {% trans name=badge.name %}{{name}}{% endtrans %} -   - × - {{ badge.award_badge.count()|intcomma }} - - - {% if loop.index is divisibleby 3 %} -
- {% endif %} - {% endfor %} -
-
+#} + {% if loop.index is divisibleby 3 %} +
+ {% endif %} + {% endfor %} +
+
{% endblock %} {% block endjs %} {{ super() }} diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 30843e3a..b75eafe2 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -617,6 +617,7 @@ def get_comment(request): @ajax_only @get_only def get_question_body(request): + # TODO: Is this used anywhere? search_state = request.session.get('search_state', SearchState()) view_log = request.session['view_log'] (qs, meta_data, related_tags) = models.Question.objects.run_advanced_search( @@ -634,6 +635,7 @@ def widget_questions(request): """Returns the first x questions based on certain tags. @returns template with those questions listed.""" # make sure this is a GET request with the correct parameters. + # TODO: Is this used anywhere? if request.method != 'GET': raise Http404 questions = models.Question.objects.all() diff --git a/askbot/views/users.py b/askbot/views/users.py index 7ba10b6a..6cb784bc 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -10,7 +10,9 @@ import calendar import functools import datetime import logging -from django.db.models import Count +import operator + +from django.db.models import Count, Sum from django.conf import settings as django_settings from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator, EmptyPage, InvalidPage @@ -276,114 +278,55 @@ def edit_user(request, id): return render_into_skin('user_profile/user_edit.html', data, request) def user_stats(request, user, context): - - question_filter = {'author': user} + question_filter = {} if request.user != user: question_filter['is_anonymous'] = False - questions = models.Question.objects.filter( - **question_filter - ).order_by( - '-score', '-thread__last_activity_at' - ).select_related( - 'thread', - 'thread__last_activity_by__id', - 'thread__last_activity_by__username', - 'thread__last_activity_by__reputation', - 'thread__last_activity_by__gold', - 'thread__last_activity_by__silver', - 'thread__last_activity_by__bronze' - )[:100] - - questions = list(questions) + questions = user.posts.get_questions().filter(**question_filter).\ + order_by('-score', '-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: question_count = len(questions) else: - question_count = models.Question.objects.filter( - **question_filter - ).order_by( - '-score', '-thread__last_activity_at' - ).select_related( - 'thread__last_activity_by__id', - 'thread__last_activity_by__username', - 'thread__last_activity_by__reputation', - 'thread__last_activity_by__gold', - 'thread__last_activity_by__silver', - 'thread__last_activity_by__bronze' - ).count() - -# Commented out, doesn't seem to be used -# threads = [quest.thread for quest in questions] -# favorited_myself = models.FavoriteQuestion.objects.filter( -# thread__in = threads, -# user = user -# ).values_list('thread__id', flat=True) + 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?) - answered_questions = models.Question.objects.extra( - select={ - 'vote_up_count' : 'answer.vote_up_count', - 'vote_down_count' : 'answer.vote_down_count', - 'answer_id' : 'answer.id', - 'answer_accepted' : 'askbot_thread.accepted_answer_id', - 'answer_score' : 'answer.score', - 'comment_count' : 'answer.comment_count', - 'title': 'askbot_thread.title', - }, - tables=['question', 'answer', 'askbot_thread'], - where=['NOT answer.deleted AND NOT question.deleted AND answer.author_id=%s AND answer.question_id=question.id AND question.thread_id=askbot_thread.id'], - params=[user.id], - order_by=['-answer_score', '-answer_id'], - select_params=[user.id] - ).distinct().values('comment_count', - 'id', - 'answer_id', - 'title', - 'author_id', - 'answer_accepted', - 'answer_score', - #'answer_count', Moved from Question to Thread and doesn't seem to be referenced anywhere !? - 'vote_up_count', - 'vote_down_count')[:100] + top_answers = user.posts.get_answers().filter( + deleted=False, + parent__deleted=False, + ).select_related('thread').order_by('-score', '-id')[:100] + top_answer_count = len(top_answers) 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 - question_id_set = set() - #todo: there may be a better way to do these queries - question_id_set.update([q.id for q in questions]) - question_id_set.update([q['id'] for q in answered_questions]) - #user_tags = models.Tag.objects.filter(questions__id__in = question_id_set) - user_tag_ids = models.Question.objects.filter(id__in=question_id_set).values_list('thread__tags__id', flat=True) - user_tags = models.Tag.objects.filter(id__in=user_tag_ids) - - badges = models.BadgeData.objects.filter( - award_badge__user=user - ) - total_awards = badges.count() - badges = badges.order_by('-slug').distinct() - - user_tags = user_tags.annotate( - user_tag_usage_count=Count('name') - ).order_by( - '-user_tag_usage_count' - ) + thread_id_set = set() + thread_id_set.update([qq.thread_id for qq in questions]) + thread_id_set.update([aa.thread_id for aa in top_answers]) + user_tags = models.Tag.objects.filter(threads__id__in=thread_id_set).\ + annotate(user_tag_usage_count=Count('threads')).\ + order_by('-user_tag_usage_count')[:const.USER_VIEW_DATA_SIZE] + user_tags = list(user_tags) # DEBUG: evaluate + + user_awards = models.Award.objects.filter(user=user).select_related('badge') + badges_dict = {} + for award in user_awards: + 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) - if user.is_administrator(): - user_status = _('Site Adminstrator') - elif user.is_moderator(): - user_status = _('Forum Moderator') - elif user.is_suspended(): - user_status = _('Suspended User') - elif user.is_blocked(): - user_status = _('Blocked User') - else: - user_status = _('Registered User') +# badges = models.BadgeData.objects.filter(award_badge__user=user).annotate(user_awarded_times=Count('award_badge')) +# total_badges = len(badges) data = { 'active_tab':'users', @@ -397,19 +340,26 @@ def user_stats(request, user, context): 'question_count': question_count, 'question_type' : ContentType.objects.get_for_model(models.Question), 'answer_type' : ContentType.objects.get_for_model(models.Answer), -# Commented out, doesn't seem to be used -# 'favorited_myself': favorited_myself, - 'answered_questions' : answered_questions, + + #'answered_questions' : answered_questions, + 'top_answers': top_answers, + 'top_answer_count': top_answer_count, + 'up_votes' : up_votes, 'down_votes' : down_votes, 'total_votes': up_votes + down_votes, - 'votes_today_left': votes_total-votes_today, + 'votes_today_left': votes_total - votes_today, 'votes_total_per_day': votes_total, - 'user_tags' : user_tags[:const.USER_VIEW_DATA_SIZE], + + 'user_tags' : user_tags, + 'badges': badges, - 'total_awards' : total_awards, + 'total_badges' : total_badges, } context.update(data) + + len(models.Post.objects.filter(post_type='mikki')) # DEBUG: distinctive query + return render_into_skin('user_profile/user_stats.html', context, request) def user_recent(request, user, context): -- cgit v1.2.3-1-g7c22