summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-03-05 01:25:52 -0300
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-03-05 01:25:52 -0300
commitadbb8d22b4a322fc074b7a4f17805fea6b0bfec6 (patch)
tree12918bc00da44df4f88571748c5c6257c3b06bb6
parent08d0929ae78920e5e92ae7b362a22aac9e5e99b2 (diff)
downloadaskbot-adbb8d22b4a322fc074b7a4f17805fea6b0bfec6.tar.gz
askbot-adbb8d22b4a322fc074b7a4f17805fea6b0bfec6.tar.bz2
askbot-adbb8d22b4a322fc074b7a4f17805fea6b0bfec6.zip
cache invalidation on the question page seems to work
-rw-r--r--askbot/const/__init__.py4
-rw-r--r--askbot/models/__init__.py16
-rw-r--r--askbot/models/question.py63
-rw-r--r--askbot/skins/common/media/js/post.js16
-rw-r--r--askbot/skins/common/templates/question/answer_controls.html17
-rw-r--r--askbot/skins/common/templates/question/answer_vote_buttons.html22
-rw-r--r--askbot/skins/common/templates/question/question_controls.html10
-rw-r--r--askbot/skins/default/media/style/style.less8
-rw-r--r--askbot/skins/default/templates/macros.html5
-rw-r--r--askbot/skins/default/templates/question.html2
-rw-r--r--askbot/views/commands.py11
-rw-r--r--askbot/views/readers.py1
-rw-r--r--askbot/views/writers.py1
13 files changed, 95 insertions, 81 deletions
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index ddbff836..66e20dae 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -50,6 +50,10 @@ POST_SORT_METHODS = (
('votes-asc', _('least voted')),
('relevance-desc', _('relevance')),
)
+
+ANSWER_SORT_METHODS = (#no translations needed here
+ 'latest', 'oldest', 'votes'
+)
#todo: add assertion here that all sort methods are unique
#because they are keys to the hash used in implementations
#of Q.run_advanced_search
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index 60ff5fb3..f6c32618 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -991,6 +991,7 @@ def user_post_comment(
comment = body_text,
added_at = timestamp,
)
+ parent_post.thread.invalidate_cached_data()
award_badges_signal.send(None,
event = 'post_comment',
actor = self,
@@ -1160,6 +1161,7 @@ def user_delete_comment(
):
self.assert_can_delete_comment(comment = comment)
comment.delete()
+ comment.thread.invalidate_cached_data()
@auto_now_timestamp
def user_delete_answer(
@@ -1260,6 +1262,7 @@ def user_delete_post(
self.delete_question(question = post, timestamp = timestamp)
else:
raise TypeError('either Comment, Question or Answer expected')
+ post.thread.invalidate_cached_data()
def user_restore_post(
self,
@@ -1273,6 +1276,7 @@ def user_restore_post(
post.deleted_by = None
post.deleted_at = None
post.save()
+ post.thread.invalidate_cached_data()
if post.post_type == 'answer':
post.thread.update_answer_count()
else:
@@ -1331,10 +1335,13 @@ def user_post_question(
def user_edit_comment(self, comment_post=None, body_text = None):
"""apply edit to a comment, the method does not
change the comments timestamp and no signals are sent
+ todo: see how this can be merged with edit_post
+ todo: add timestamp
"""
self.assert_can_edit_comment(comment_post)
comment_post.text = body_text
comment_post.parse_and_save(author = self)
+ comment_post.thread.invalidate_cached_data()
@auto_now_timestamp
@@ -1363,6 +1370,7 @@ def user_edit_question(
wiki = wiki,
edit_anonymously = edit_anonymously,
)
+ question.thread.invalidate_cached_data()
award_badges_signal.send(None,
event = 'edit_question',
actor = self,
@@ -1389,6 +1397,7 @@ def user_edit_answer(
comment = revision_comment,
wiki = wiki,
)
+ answer.thread.invalidate_cached_data()
award_badges_signal.send(None,
event = 'edit_answer',
actor = self,
@@ -1957,16 +1966,19 @@ def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None):
if vote_type == Vote.VOTE_UP:
if cancel:
auth.onUpVotedCanceled(vote, post, user, timestamp)
- return None
else:
auth.onUpVoted(vote, post, user, timestamp)
elif vote_type == Vote.VOTE_DOWN:
if cancel:
auth.onDownVotedCanceled(vote, post, user, timestamp)
- return None
else:
auth.onDownVoted(vote, post, user, timestamp)
+ post.thread.invalidate_cached_data()
+
+ if cancel:
+ return None
+
event = VOTES_TO_EVENTS.get((vote_type, post.post_type), None)
if event:
award_badges_signal.send(None,
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 0d76a642..6658af9d 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -5,9 +5,10 @@ import re
from django.conf import settings
from django.db import models
from django.contrib.auth.models import User
-from django.utils.translation import ugettext as _
from django.core import cache # import cache, not from cache import cache, to be able to monkey-patch cache.cache in test cases
from django.core.urlresolvers import reverse
+from django.utils.hashcompat import md5_constructor
+from django.utils.translation import ugettext as _
import askbot
import askbot.conf
@@ -421,34 +422,42 @@ class Thread(models.Model):
| models.Q(deleted_by = user)
)
- def get_cached_answer_list(self, sort_method = None):
- """get all answer posts as a list for the Thread, and a given
- user. This list is cached.
+ def invalidate_cached_thread_content_fragment(self):
+ """we do not precache the fragment here, as re-generating
+ the the fragment takes a lot of data, so we just
+ invalidate the cached item
+
+ Note: the cache key generation code is copy-pasted
+ from coffin/template/defaulttags.py no way around
+ that unfortunately
"""
- key = self.ANSWER_LIST_KEY_TPL % self.id
- answer_list = cache.cache.get(key)
- if not answer_list:
- answers = self.get_answers()
- answers = answers.select_related('thread', 'author', 'last_edited_by')
- answers = answers.order_by(
- {
- "latest":"-added_at",
- "oldest":"added_at",
- "votes":"-score"
- }[sort_method]
- )
- answer_list = list(answers)
- cache.cache.set(key, answer_list)
- return answer_list
+ args_md5 = md5_constructor(str(self.id))
+ key = 'template.cache.%s.%s' % ('thread-content-html', args_md5.hexdigest())
+ cache.cache.delete(key)
+
+ def get_post_data_cache_key(self, sort_method = None):
+ return 'thread-data-%s-%s' % (self.id, sort_method)
+
+ def invalidate_cached_post_data(self):
+ """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
+ for sort_method in const.ANSWER_SORT_METHODS:
+ cache.cache.delete(self.get_post_data_cache_key(sort_method))
+
+ def invalidate_cached_data(self):
+ self.invalidate_cached_post_data()
+ self.invalidate_cached_thread_content_fragment()
def get_cached_post_data(self, sort_method = None):
"""returns cached post data, as calculated by
the method get_post_data()"""
- key = 'thread-data-%s-%s' % (self.id, sort_method)
+ key = self.get_post_data_cache_key(sort_method)
post_data = cache.cache.get(key)
if not post_data:
post_data = self.get_post_data(sort_method)
- cache.cache.set(key, post_data)
+ cache.cache.set(key, post_data, const.LONG_TIME)
return post_data
def get_post_data(self, sort_method = None):
@@ -501,14 +510,17 @@ class Thread(models.Model):
except KeyError:
pass#comment to deleted answer - don't want it
- if self.accepted_answer and self.accepted_answer.deleted == False:
+ if self.has_accepted_answer() and self.accepted_answer.deleted == False:
#Put the accepted answer to front
#the second check is for the case when accepted answer is deleted
- answers.remove(self.accepted_answer)
- answers.insert(0, self.accepted_answer)
+ accepted_answer = post_map[self.accepted_answer_id]
+ answers.remove(accepted_answer)
+ answers.insert(0, accepted_answer)
return (question_post, answers, post_to_author)
+ def has_accepted_answer(self):
+ return self.accepted_answer_id != None
def get_similarity(self, other_thread = None):
"""return number of tags in the other question
@@ -565,6 +577,9 @@ class Thread(models.Model):
return similar_threads
def get_cached_data():
+ """similar thread data will expire
+ with the default expiration delay
+ """
key = 'similar-threads-%s' % self.id
data = cache.cache.get(key)
if data is None:
diff --git a/askbot/skins/common/media/js/post.js b/askbot/skins/common/media/js/post.js
index dc3fbfd7..0ad59c69 100644
--- a/askbot/skins/common/media/js/post.js
+++ b/askbot/skins/common/media/js/post.js
@@ -311,7 +311,6 @@ var Vote = function(){
var removeAllOffensiveIdPrefixAnswerFlag = 'answer-offensive-remove-all-flag-';
var offensiveClassFlag = 'offensive-flag';
var questionControlsId = 'question-controls';
- var removeQuestionLinkIdPrefix = 'question-delete-link-';
var removeAnswerLinkIdPrefix = 'answer-delete-link-';
var questionSubscribeUpdates = 'question-subscribe-updates';
var questionSubscribeSidebar= 'question-subscribe-sidebar';
@@ -419,11 +418,6 @@ var Vote = function(){
return $(removeAllOffensiveAnswerFlag);
};
- var getremoveQuestionLink = function(){
- var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']';
- return $(removeQuestionLink);
- };
-
var getquestionSubscribeUpdatesCheckbox = function(){
return $('#' + questionSubscribeUpdates);
};
@@ -464,7 +458,7 @@ var Vote = function(){
var bindEvents = function(){
// accept answers
- var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
+ var acceptedButtons = 'div.'+ voteContainerId +' div[id^='+ imgIdPrefixAccept +']';
$(acceptedButtons).unbind('click').click(function(event){
Vote.accept($(event.target));
});
@@ -520,10 +514,6 @@ var Vote = function(){
Vote.remove_all_offensive(this, VoteType.removeAllOffensiveAnswer);
});
- //getremoveQuestionLink().unbind('click').click(function(event){
- // Vote.remove(this, VoteType.removeQuestion);
- //});
-
getquestionSubscribeUpdatesCheckbox().unbind('click').click(function(event){
//despeluchar esto
if (this.checked){
@@ -578,19 +568,15 @@ var Vote = function(){
showMessage(object, acceptOwnAnswerMessage);
}
else if(data.status == "1"){
- object.attr("src", mediaUrl("media/images/vote-accepted.png"));
$("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
}
else if(data.success == "1"){
- var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
- $(acceptedButtons).attr("src", mediaUrl("media/images/vote-accepted.png"));
var answers = ("div[id^="+answerContainerIdPrefix +"]");
$(answers).removeClass("accepted-answer");
var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
$(commentLinks).removeClass("comment-link-accepted");
- object.attr("src", mediaUrl("media/images/vote-accepted-on.png"));
$("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
}
diff --git a/askbot/skins/common/templates/question/answer_controls.html b/askbot/skins/common/templates/question/answer_controls.html
index 2e8cd4f9..091572af 100644
--- a/askbot/skins/common/templates/question/answer_controls.html
+++ b/askbot/skins/common/templates/question/answer_controls.html
@@ -12,16 +12,14 @@
<a class="question-delete"
>{% if answer.deleted %}{% trans %}undelete{% endtrans %}{% else %}{% trans %}delete{% endtrans %}{% endif %}</a>
</span>
+{% if answer.offensive_flag_count > 0 %}
<span
- id="answer-offensive-flag-{{ answer.id }}"
+ id="answer-offensive-remove-flag-{{ answer.id }}"
class="action-link offensive-flag"
- title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}"
+ title="{% trans %}remove offensive flag{% endtrans %}"
>
- <a class="question-flag">{% trans %}flag offensive{% endtrans %}
- <span class="darkred">{% if answer.offensive_flag_count > 0 %}({{ answer.offensive_flag_count }}){% endif %}</span>
- </a>
+ <a class="question-flag">{% trans %}remove flag{% endtrans %}</a>
</span>
-{% if answer.offensive_flag_count > 0 %}
<span
id="answer-offensive-flag-{{ answer.id }}"
class="action-link offensive-flag"
@@ -30,13 +28,6 @@
<a class="question-flag">{% trans %}flag offensive{% endtrans %} ({{ answer.offensive_flag_count }})</a>
</a>
</span>
-<span
- id="answer-offensive-flag-remove-{{ answer.id }}"
- class="action-link offensive-flag"
- title="{% trans %}remove offensive flag{% endtrans %}"
->
- <a class="question-flag">{% trans %}remove flag{% endtrans %} ({{ answer.offensive_flag_count }})</a>
-</span>
{% else %}
<span
id="answer-offensive-flag-{{ answer.id }}"
diff --git a/askbot/skins/common/templates/question/answer_vote_buttons.html b/askbot/skins/common/templates/question/answer_vote_buttons.html
index 6ac2e07d..242bf2be 100644
--- a/askbot/skins/common/templates/question/answer_vote_buttons.html
+++ b/askbot/skins/common/templates/question/answer_vote_buttons.html
@@ -1,20 +1,10 @@
{{ macros.post_vote_buttons(post = answer) }}
-<img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
+<div
+ id="answer-img-accept-{{ answer.id }}"
+ class="answer-img-accept"
{% if answer.accepted() %}
- src="{{'/images/vote-accepted-on.png'|media}}"
+ title="{% trans %}this answer has been selected as correct{% endtrans %}"
{% else %}
- src="{{'/images/vote-accepted.png'|media}}"
+ title="{% trans %}mark this answer as correct (click again to undo){% endtrans %}"
{% endif %}
- {% if request.user.is_authenticated() and
- (
- request.user == question.author or
- request.user.is_administrator_or_moderator()
- )
- %}
- alt="{% trans %}mark this answer as correct (click again to undo){% endtrans %}"
- title="{% trans %}mark this answer as correct (click again to undo){% endtrans %}"
- {% else %}
- alt="{% trans question_author=question.author.username %}{{question_author}} has selected this answer as correct{% endtrans %}"
- title="{% trans question_author=question.author.username%}{{question_author}} has selected this answer as correct{% endtrans %}"
- {% endif %}
- />
+></div>
diff --git a/askbot/skins/common/templates/question/question_controls.html b/askbot/skins/common/templates/question/question_controls.html
index 4710559d..5eee380a 100644
--- a/askbot/skins/common/templates/question/question_controls.html
+++ b/askbot/skins/common/templates/question/question_controls.html
@@ -15,17 +15,17 @@
{% endif %}
{% if question.offensive_flag_count > 0 %}
<span
- id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
+ id="question-offensive-remove-flag-{{ question.id }}"
+ class="offensive-flag"
title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}"
>
- <a class="question-flag">{% trans %}flag offensive{% endtrans %} {{ question.offensive_flag_count }})</a>
+ <a class="question-flag">{% trans %}remove flag{% endtrans %}</a>
</span>
<span
- id="question-offensive-flag-remove-{{ question.id }}"
- class="offensive-flag"
+ id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}"
>
- <a class="question-flag">{% trans %}remove flag{% endtrans %} ({{ question.offensive_flag_count }})</a>
+ <a class="question-flag">{% trans %}flag offensive{% endtrans %} ({{ question.offensive_flag_count }})</a>
</span>
{% else %}
<span
diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less
index e8e5a5d8..4f096bd0 100644
--- a/askbot/skins/default/media/style/style.less
+++ b/askbot/skins/default/media/style/style.less
@@ -2034,9 +2034,17 @@ ul#related-tags li {
}
}
+ .answer-img-accept {
+ background: url(../images/vote-accepted.png);
+ width: 23px;
+ height: 23px;
+ }
+
+ .accepted-answer .answer-img-accept,
.answer-img-accept:hover {
background: url(../images/vote-accepted-on.png)
}
+
.answer-body a {
color:@link;
}
diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html
index b5c052cc..e0de6bb6 100644
--- a/askbot/skins/default/templates/macros.html
+++ b/askbot/skins/default/templates/macros.html
@@ -303,9 +303,8 @@ for the purposes of the AJAX comment editor #}
{{comment.html}}
<a class="author" href="{{comment.author.get_profile_url()}}">{{comment.author.username}}</a>
<span class="age">&nbsp;({{comment.added_at|diff_date}})</span>
- &nbsp;<a
- id="post-{{comment.id}}-edit"
- class="edit">{% trans %}edit{% endtrans %}</a>
+ <a id="post-{{comment.id}}-edit"
+ class="edit">{% trans %}edit{% endtrans %}</a>
</div>
</div>
<script type="text/javascript">
diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html
index e241b9b2..bc0dbdeb 100644
--- a/askbot/skins/default/templates/question.html
+++ b/askbot/skins/default/templates/question.html
@@ -123,7 +123,7 @@
{% endblock %}
{% block content %}
{% if is_cacheable %}
- {% cache 1000 "thread" thread.id %}
+ {% cache long_time "thread-content-html" thread.id %}
{% include "question/content.html" %}
{% endcache %}
{% else %}
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index a6de376b..5d86d1a1 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -288,10 +288,10 @@ def vote(request, id):
elif vote_type in ['7.6', '8.6']:
#flag question or answer
if vote_type == '7.6':
- post = get_object_or_404(models.Question, id=id)
+ post = get_object_or_404(models.Post, id=id)
if vote_type == '8.6':
id = request.POST.get('postId')
- post = get_object_or_404(models.Answer, id=id)
+ post = get_object_or_404(models.Post, id=id)
request.user.flag_post(post, cancel_all = True)
@@ -360,6 +360,13 @@ def vote(request, id):
response_data['success'] = 0
response_data['message'] = u'Request mode is not supported. Please try again.'
+ if vote_type not in (1, 2, 4, 5, 6, 11, 12):
+ #favorite or subscribe/unsubscribe
+ #upvote or downvote question or answer - those
+ #are handled within user.upvote and user.downvote
+ post = models.Post.objects.get(id = id)
+ post.thread.invalidate_cached_data()
+
data = simplejson.dumps(response_data)
except Exception, e:
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index 00d2fd5a..b6d4fb72 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -526,6 +526,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
data = {
'is_cacheable': is_cacheable,
+ 'long_time': const.LONG_TIME,#"forever" caching
'page_class': 'question-page',
'active_tab': 'questions',
'question' : question_post,
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index b7e0984e..ab7f581e 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -641,6 +641,7 @@ def delete_comment(request):
#attn: recalc denormalized field
parent.comment_count = parent.comment_count - 1
parent.save()
+ parent.thread.invalidate_cached_data()
return __generate_comments_json(parent, request.user)