summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Zielinski <tomasz.zielinski@pyconsultant.eu>2011-12-08 14:53:23 +0100
committerTomasz Zielinski <tomasz.zielinski@pyconsultant.eu>2011-12-08 14:53:23 +0100
commit4e28a1547fa1d90965be32e841a3bebb02fdb79e (patch)
treeeb621d3744f894785a10476ac5d55fe61baf937f
parent3ad431fc8967632d08cc382d02e013c3c5ebc5a1 (diff)
downloadaskbot-4e28a1547fa1d90965be32e841a3bebb02fdb79e.tar.gz
askbot-4e28a1547fa1d90965be32e841a3bebb02fdb79e.tar.bz2
askbot-4e28a1547fa1d90965be32e841a3bebb02fdb79e.zip
Tickets 104, 107: transplant of methods
-rw-r--r--askbot/management/commands/send_accept_answer_reminders.py1
-rw-r--r--askbot/management/commands/send_email_alerts.py7
-rw-r--r--askbot/management/commands/send_unanswered_question_reminders.py6
-rw-r--r--askbot/models/question.py624
-rw-r--r--askbot/tests/email_alert_tests.py55
5 files changed, 344 insertions, 349 deletions
diff --git a/askbot/management/commands/send_accept_answer_reminders.py b/askbot/management/commands/send_accept_answer_reminders.py
index 537e8814..e53dbcdc 100644
--- a/askbot/management/commands/send_accept_answer_reminders.py
+++ b/askbot/management/commands/send_accept_answer_reminders.py
@@ -52,7 +52,6 @@ class Command(NoArgsCommand):
if question_count == 0:
continue
- #tag_summary = get_tag_summary_from_questions(final_question_list)
subject_line = _(
'Accept the best answer for %(question_count)d of your questions'
) % {'question_count': question_count}
diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py
index 994d1209..9f600126 100644
--- a/askbot/management/commands/send_email_alerts.py
+++ b/askbot/management/commands/send_email_alerts.py
@@ -3,10 +3,9 @@ from django.core.management.base import NoArgsCommand
from django.core.urlresolvers import reverse
from django.db import connection
from django.db.models import Q, F
-from askbot.models import User, Question, Answer, Tag, PostRevision
+from askbot.models import User, Question, Answer, Tag, PostRevision, Thread
from askbot.models import Activity, EmailFeedSetting
from askbot.models import Comment
-from askbot.models.question import get_tag_summary_from_questions
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.conf import settings as django_settings
@@ -404,7 +403,9 @@ class Command(NoArgsCommand):
if num_q > 0:
url_prefix = askbot_settings.APP_URL
- tag_summary = get_tag_summary_from_questions(q_list.keys())
+ threads = Thread.objects.filter(id__in=[qq.thread_id for qq in q_list.keys()])
+ tag_summary = Thread.objects.get_tag_summary_from_threads(threads)
+
question_count = len(q_list.keys())
subject_line = ungettext(
diff --git a/askbot/management/commands/send_unanswered_question_reminders.py b/askbot/management/commands/send_unanswered_question_reminders.py
index 3d5236dc..a57f79b8 100644
--- a/askbot/management/commands/send_unanswered_question_reminders.py
+++ b/askbot/management/commands/send_unanswered_question_reminders.py
@@ -5,7 +5,7 @@ from askbot.conf import settings as askbot_settings
from django.utils.translation import ungettext
from askbot.utils import mail
from askbot.utils.classes import ReminderSchedule
-from askbot.models.question import get_tag_summary_from_questions
+from askbot.models.question import Thread
DEBUG_THIS_COMMAND = False
@@ -51,7 +51,9 @@ class Command(NoArgsCommand):
if question_count == 0:
continue
- tag_summary = get_tag_summary_from_questions(final_question_list)
+ threads = Thread.objects.filter(id__in=[qq.thread_id for qq in final_question_list])
+ tag_summary = Thread.objects.get_tag_summary_from_threads(threads)
+
subject_line = ungettext(
'%(question_count)d unanswered question about %(topics)s',
'%(question_count)d unanswered questions about %(topics)s',
diff --git a/askbot/models/question.py b/askbot/models/question.py
index e4602aeb..0e47bcf4 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -34,40 +34,315 @@ QUESTION_ORDER_BY_MAP = {
'relevance-desc': None#this is a special case for postges only
}
-def get_tag_summary_from_questions(questions):
- """returns a humanized string containing up to
- five most frequently used
- unique tags coming from the ``questions``.
- Variable ``questions`` is an iterable of
- :class:`~askbot.models.Question` model objects.
-
- This is not implemented yet as a query set method,
- because it is used on a list.
- """
- #todo: in python 2.6 there is collections.Counter() thing
- #which would be very useful here
- tag_counts = dict()
- for question in questions:
- tag_names = question.get_tag_names()
- for tag_name in tag_names:
- if tag_name in tag_counts:
- tag_counts[tag_name] += 1
+class ThreadManager(models.Manager):
+ def get_tag_summary_from_threads(self, threads):
+ """returns a humanized string containing up to
+ five most frequently used
+ unique tags coming from the ``threads``.
+ Variable ``threads`` is an iterable of
+ :class:`~askbot.models.Thread` model objects.
+
+ This is not implemented yet as a query set method,
+ because it is used on a list.
+ """
+ # TODO: In Python 2.6 there is collections.Counter() thing which would be very useful here
+ # TODO: In Python 2.5 there is `defaultdict` which already would be an improvement
+ tag_counts = dict()
+ for thread in threads:
+ for tag_name in thread.get_tag_names():
+ if tag_name in tag_counts:
+ tag_counts[tag_name] += 1
+ else:
+ tag_counts[tag_name] = 1
+ tag_list = tag_counts.keys()
+ tag_list.sort(key=lambda t: tag_counts[t], reverse=True)
+
+ #note that double quote placement is important here
+ if len(tag_list) == 1:
+ last_topic = '"'
+ elif len(tag_list) <= 5:
+ last_topic = _('" and "%s"') % tag_list.pop()
+ else:
+ tag_list = tag_list[:5]
+ last_topic = _('" and more')
+
+ return '"' + '", "'.join(tag_list) + last_topic
+
+
+class Thread(models.Model):
+ title = models.CharField(max_length=300)
+
+ tags = models.ManyToManyField('Tag', related_name='threads')
+
+ # Denormalised data, transplanted from Question
+ tagnames = models.CharField(max_length=125)
+ view_count = models.PositiveIntegerField(default=0)
+ favourite_count = models.PositiveIntegerField(default=0)
+ answer_count = models.PositiveIntegerField(default=0)
+ last_activity_at = models.DateTimeField(default=datetime.datetime.now)
+ last_activity_by = models.ForeignKey(User, related_name='unused_last_active_in_threads')
+
+ followed_by = models.ManyToManyField(User, related_name='followed_threads')
+ favorited_by = models.ManyToManyField(User, through='FavoriteQuestion', related_name='unused_favorite_threads')
+
+ closed = models.BooleanField(default=False)
+ closed_by = models.ForeignKey(User, null=True, blank=True) #, related_name='closed_questions')
+ closed_at = models.DateTimeField(null=True, blank=True)
+ close_reason = models.SmallIntegerField(
+ choices=const.CLOSE_REASONS,
+ null=True,
+ blank=True
+ )
+
+ accepted_answer = models.ForeignKey('askbot.Answer', null=True, blank=True)
+ answer_accepted_at = models.DateTimeField(null=True, blank=True)
+
+ objects = ThreadManager()
+
+ class Meta:
+ app_label = 'askbot'
+
+ def _question(self):
+ return Question.objects.get(thread=self)
+
+ def update_favorite_count(self):
+ self.favourite_count = FavoriteQuestion.objects.filter(thread=self).count()
+ self.save()
+
+ def update_answer_count(self):
+ self.answer_count = self.get_answers().count()
+ self.save()
+
+ def increase_view_count(self, increment=1):
+ qset = Thread.objects.filter(id=self.id)
+ qset.update(view_count=models.F('view_count') + increment)
+ self.view_count = qset.values('view_count')[0]['view_count'] # get the new view_count back because other pieces of code relies on such behaviour
+
+ def set_closed_status(self, closed, closed_by, closed_at, close_reason):
+ self.closed = closed
+ self.closed_by = closed_by
+ self.closed_at = closed_at
+ self.close_reason = close_reason
+ self.save()
+
+ def set_accepted_answer(self, answer, timestamp):
+ if answer and answer.question.thread != self:
+ raise ValueError("Answer doesn't belong to this thread")
+ self.accepted_answer = answer
+ self.answer_accepted_at = timestamp
+ self.save()
+
+ def set_last_activity(self, last_activity_at, last_activity_by):
+ self.last_activity_at = last_activity_at
+ self.last_activity_by = last_activity_by
+ self.save()
+
+ def get_tag_names(self):
+ "Creates a list of Tag names from the ``tagnames`` attribute."
+ return self.tagnames.split(u' ')
+
+ def get_answers(self, user=None):
+ """returns query set for answers to this question
+ that may be shown to the given user
+ """
+ thread_question = self._question()
+
+ if user is None or user.is_anonymous():
+ return thread_question.answers.filter(deleted=False)
+ else:
+ if user.is_administrator() or user.is_moderator():
+ return thread_question.answers.all()
+ else:
+ return thread_question.answers.filter(
+ models.Q(deleted = False) | models.Q(author = user) \
+ | models.Q(deleted_by = user)
+ )
+
+
+ def get_similarity(self, other_question = None):
+ """return number of tags in the other question
+ that overlap with the current question (self)
+ """
+ my_tags = set(self._question().get_tag_names())
+ others_tags = set(other_question.get_tag_names())
+ return len(my_tags & others_tags)
+
+ def get_similar_questions(self):
+ """
+ Get 10 similar questions for given one.
+ Questions with the individual tags will be added to list if above questions are not full.
+
+ This function has a limitation that it will
+ retrieve only 100 records then select 10 most similar
+ from that list as querying entire database may
+ be very expensive - this function will benefit from
+ some sort of optimization
+ """
+
+ def get_data():
+ thread_question = self._question()
+
+ tags_list = self.tags.all()
+ similar_questions = Question.objects.filter(thread__tags__in=tags_list).\
+ exclude(id = self.id).exclude(deleted = True).distinct()[:100]
+
+ similar_questions = list(similar_questions)
+ for question in similar_questions:
+ question.similarity = self.get_similarity(other_question=question)
+
+ similar_questions.sort(key=operator.attrgetter('similarity'), reverse=True)
+ return similar_questions[:10]
+
+ return LazyList(get_data)
+
+ def remove_author_anonymity(self):
+ """removes anonymous flag from the question
+ and all its revisions
+ the function calls update method to make sure that
+ signals are not called
+ """
+ #note: see note for the is_anonymous field
+ #it is important that update method is called - not save,
+ #because we do not want the signals to fire here
+ thread_question = self._question()
+ Question.objects.filter(id=thread_question.id).update(is_anonymous=False)
+ thread_question.revisions.all().update(is_anonymous=False)
+
+ def update_tags(self, tagnames = None, user = None, timestamp = None):
+ """
+ Updates Tag associations for a question to match the given
+ tagname string.
+
+ When tags are removed and their use count hits 0 - the tag is
+ automatically deleted.
+
+ When an added tag does not exist - it is created
+
+ Tag use counts are recalculated
+
+ A signal tags updated is sent
+ """
+ thread_question = self._question()
+
+ previous_tags = list(self.tags.all())
+
+ previous_tagnames = set([tag.name for tag in previous_tags])
+ updated_tagnames = set(t for t in tagnames.split(' '))
+
+ removed_tagnames = previous_tagnames - updated_tagnames
+ added_tagnames = updated_tagnames - previous_tagnames
+
+ modified_tags = list()
+ #remove tags from the question's tags many2many relation
+ if removed_tagnames:
+ removed_tags = [tag for tag in previous_tags if tag.name in removed_tagnames]
+ self.tags.remove(*removed_tags)
+
+ #if any of the removed tags reached use count == 1 that means they must be deleted
+ for tag in removed_tags:
+ if tag.used_count == 1:
+ #we won't modify used count b/c it's done below anyway
+ removed_tags.remove(tag)
+ #todo - do we need to use fields deleted_by and deleted_at?
+ tag.delete()#auto-delete tags whose use count dwindled
+
+ #remember modified tags, we'll need to update use counts on them
+ modified_tags = removed_tags
+
+ #add new tags to the relation
+ if added_tagnames:
+ #find reused tags
+ reused_tags = Tag.objects.filter(name__in = added_tagnames)
+ #undelete them, because we are using them
+ reused_count = reused_tags.update(
+ deleted = False,
+ deleted_by = None,
+ deleted_at = None
+ )
+ #if there are brand new tags, create them and finalize the added tag list
+ if reused_count < len(added_tagnames):
+ added_tags = list(reused_tags)
+
+ reused_tagnames = set([tag.name for tag in reused_tags])
+ new_tagnames = added_tagnames - reused_tagnames
+ for name in new_tagnames:
+ new_tag = Tag.objects.create(
+ name = name,
+ created_by = user,
+ used_count = 1
+ )
+ added_tags.append(new_tag)
else:
- tag_counts[tag_name] = 1
- tag_list = tag_counts.keys()
- #sort in descending order
- tag_list.sort(lambda x, y: cmp(tag_counts[y], tag_counts[x]))
+ added_tags = reused_tags
+
+ #finally add tags to the relation and extend the modified list
+ self.tags.add(*added_tags)
+ modified_tags.extend(added_tags)
+
+ #if there are any modified tags, update their use counts
+ if modified_tags:
+ Tag.objects.update_use_counts(modified_tags)
+ signals.tags_updated.send(None,
+ question = thread_question,
+ tags = modified_tags,
+ user = user,
+ timestamp = timestamp
+ )
+ return True
+
+ return False
+
+ def retag(self, retagged_by=None, retagged_at=None, tagnames=None, silent=False):
+ if None in (retagged_by, retagged_at, tagnames):
+ raise Exception('arguments retagged_at, retagged_by and tagnames are required')
+
+ thread_question = self._question()
+
+ self.tagnames = tagnames
+ self.save()
+
+ # Update the Question itself
+ if silent == False:
+ thread_question.last_edited_at = retagged_at
+ #thread_question.thread.last_activity_at = retagged_at
+ thread_question.last_edited_by = retagged_by
+ #thread_question.thread.last_activity_by = retagged_by
+ thread_question.save()
+
+ # Update the Question's tag associations
+ self.update_tags(tagnames=tagnames, user=retagged_by, timestamp=retagged_at)
+
+ # Create a new revision
+ latest_revision = thread_question.get_latest_revision()
+ PostRevision.objects.create_question_revision(
+ question = thread_question,
+ title = latest_revision.title,
+ author = retagged_by,
+ revised_at = retagged_at,
+ tagnames = tagnames,
+ summary = const.POST_STATUS['retagged'],
+ text = latest_revision.text
+ )
- #note that double quote placement is important here
- if len(tag_list) == 1:
- last_topic = '"'
- elif len(tag_list) <= 5:
- last_topic = _('" and "%s"') % tag_list.pop()
- else:
- tag_list = tag_list[:5]
- last_topic = _('" and more')
+ def has_favorite_by_user(self, user):
+ if not user.is_authenticated():
+ return False
+
+ return FavoriteQuestion.objects.filter(thread=self, user=user).exists()
+
+ def get_last_update_info(self):
+ thread_question = self._question()
+
+ when, who = thread_question.post_get_last_update_info()
+
+ answers = thread_question.answers.all()
+ for a in answers:
+ a_when, a_who = a.post_get_last_update_info()
+ if a_when > when:
+ when = a_when
+ who = a_who
- return '"' + '", "'.join(tag_list) + last_topic
+ return when, who
class QuestionQuerySet(models.query.QuerySet):
@@ -119,16 +394,16 @@ class QuestionQuerySet(models.query.QuerySet):
return question
def get_by_text_query(self, search_query):
- """returns a query set of questions,
+ """returns a query set of questions,
matching the full text query
"""
#todo - goes to thread - we search whole threads
if getattr(settings, 'USE_SPHINX_SEARCH', False):
matching_questions = Question.sphinx_search.query(search_query)
- question_ids = [q.id for q in matching_questions]
+ question_ids = [q.id for q in matching_questions]
return Question.objects.filter(deleted = False, id__in = question_ids)
if settings.DATABASE_ENGINE == 'mysql' and mysql.supports_full_text_search():
- return self.filter(
+ return self.filter(
models.Q(thread__title__search = search_query) \
| models.Q(text__search = search_query) \
| models.Q(thread__tagnames__search = search_query) \
@@ -170,7 +445,7 @@ class QuestionQuerySet(models.query.QuerySet):
author_selector = search_state.author
sort_method = getattr(
- search_state,
+ search_state,
'sort',
const.DEFAULT_POST_SORT_METHOD
)
@@ -201,7 +476,7 @@ class QuestionQuerySet(models.query.QuerySet):
if len(query_users) > 0:
qs = qs.filter(author__in = query_users)
- if tag_selector:
+ if tag_selector:
for tag in tag_selector:
qs = qs.filter(thread__tags__name = tag)
@@ -227,7 +502,7 @@ class QuestionQuerySet(models.query.QuerySet):
favorite_filter |= models.Q(author__in = followed_users)
favorite_filter |= models.Q(answers__author__in = followed_users)
qs = qs.filter(favorite_filter)
-
+
#user contributed questions & answers
if author_selector:
try:
@@ -268,7 +543,7 @@ class QuestionQuerySet(models.query.QuerySet):
#filter by interesting tags only
interesting_tag_filter = models.Q(thread__tags__in = interesting_tags)
if request_user.has_interesting_wildcard_tags():
- interesting_wildcards = request_user.interesting_tags.split()
+ interesting_wildcards = request_user.interesting_tags.split()
extra_interesting_tags = Tag.objects.get_by_wildcards(
interesting_wildcards
)
@@ -300,7 +575,7 @@ class QuestionQuerySet(models.query.QuerySet):
#exclude ignored tags if the user wants to
qs = qs.exclude(thread__tags__in=ignored_tags)
if request_user.has_ignored_wildcard_tags():
- ignored_wildcards = request_user.ignored_tags.split()
+ ignored_wildcards = request_user.ignored_tags.split()
extra_ignored_tags = Tag.objects.get_by_wildcards(
ignored_wildcards
)
@@ -446,282 +721,13 @@ class QuestionQuerySet(models.query.QuerySet):
class QuestionManager(BaseQuerySetManager):
- """chainable custom query set manager for
+ """chainable custom query set manager for
questions
"""
#todo: becomes thread manager
def get_query_set(self):
return QuestionQuerySet(self.model)
-class Thread(models.Model):
- title = models.CharField(max_length=300)
-
- tags = models.ManyToManyField('Tag', related_name='threads')
-
- # Denormalised data, transplanted from Question
- tagnames = models.CharField(max_length=125)
- view_count = models.PositiveIntegerField(default=0)
- favourite_count = models.PositiveIntegerField(default=0)
- answer_count = models.PositiveIntegerField(default=0)
- last_activity_at = models.DateTimeField(default=datetime.datetime.now)
- last_activity_by = models.ForeignKey(User, related_name='unused_last_active_in_threads')
-
- followed_by = models.ManyToManyField(User, related_name='followed_threads')
- favorited_by = models.ManyToManyField(User, through='FavoriteQuestion', related_name='unused_favorite_threads')
-
- closed = models.BooleanField(default=False)
- closed_by = models.ForeignKey(User, null=True, blank=True) #, related_name='closed_questions')
- closed_at = models.DateTimeField(null=True, blank=True)
- close_reason = models.SmallIntegerField(
- choices=const.CLOSE_REASONS,
- null=True,
- blank=True
- )
-
- accepted_answer = models.ForeignKey('askbot.Answer', null=True, blank=True)
- answer_accepted_at = models.DateTimeField(null=True, blank=True)
-
- class Meta:
- app_label = 'askbot'
-
- def _question(self):
- return Question.objects.get(thread=self)
-
- def update_favorite_count(self):
- self.favourite_count = FavoriteQuestion.objects.filter(thread=self).count()
- self.save()
-
- def update_answer_count(self):
- self.answer_count = self.get_answers().count()
- self.save()
-
- def increase_view_count(self, increment=1):
- qset = Thread.objects.filter(id=self.id)
- qset.update(view_count=models.F('view_count') + increment)
- self.view_count = qset.values('view_count')[0]['view_count'] # get the new view_count back because other pieces of code relies on such behaviour
-
- def set_closed_status(self, closed, closed_by, closed_at, close_reason):
- self.closed = closed
- self.closed_by = closed_by
- self.closed_at = closed_at
- self.close_reason = close_reason
- self.save()
-
- def set_accepted_answer(self, answer, timestamp):
- if answer and answer.question.thread != self:
- raise ValueError("Answer doesn't belong to this thread")
- self.accepted_answer = answer
- self.answer_accepted_at = timestamp
- self.save()
-
- def set_last_activity(self, last_activity_at, last_activity_by):
- self.last_activity_at = last_activity_at
- self.last_activity_by = last_activity_by
- self.save()
-
- def get_answers(self, user=None):
- """returns query set for answers to this question
- that may be shown to the given user
- """
- thread_question = self._question()
-
- if user is None or user.is_anonymous():
- return thread_question.answers.filter(deleted=False)
- else:
- if user.is_administrator() or user.is_moderator():
- return thread_question.answers.all()
- else:
- return thread_question.answers.filter(
- models.Q(deleted = False) | models.Q(author = user) \
- | models.Q(deleted_by = user)
- )
-
-
- def get_similarity(self, other_question = None):
- """return number of tags in the other question
- that overlap with the current question (self)
- """
- my_tags = set(self._question().get_tag_names())
- others_tags = set(other_question.get_tag_names())
- return len(my_tags & others_tags)
-
- def get_similar_questions(self):
- """
- Get 10 similar questions for given one.
- Questions with the individual tags will be added to list if above questions are not full.
-
- This function has a limitation that it will
- retrieve only 100 records then select 10 most similar
- from that list as querying entire database may
- be very expensive - this function will benefit from
- some sort of optimization
- """
-
- def get_data():
- thread_question = self._question()
-
- tags_list = self.tags.all()
- similar_questions = Question.objects.filter(thread__tags__in=tags_list).\
- exclude(id = self.id).exclude(deleted = True).distinct()[:100]
-
- similar_questions = list(similar_questions)
- for question in similar_questions:
- question.similarity = self.get_similarity(other_question=question)
-
- similar_questions.sort(key=operator.attrgetter('similarity'), reverse=True)
- return similar_questions[:10]
-
- return LazyList(get_data)
-
- def remove_author_anonymity(self):
- """removes anonymous flag from the question
- and all its revisions
- the function calls update method to make sure that
- signals are not called
- """
- #note: see note for the is_anonymous field
- #it is important that update method is called - not save,
- #because we do not want the signals to fire here
- thread_question = self._question()
- Question.objects.filter(id=thread_question.id).update(is_anonymous=False)
- thread_question.revisions.all().update(is_anonymous=False)
-
- def update_tags(self, tagnames = None, user = None, timestamp = None):
- """
- Updates Tag associations for a question to match the given
- tagname string.
-
- When tags are removed and their use count hits 0 - the tag is
- automatically deleted.
-
- When an added tag does not exist - it is created
-
- Tag use counts are recalculated
-
- A signal tags updated is sent
- """
- thread_question = self._question()
-
- previous_tags = list(self.tags.all())
-
- previous_tagnames = set([tag.name for tag in previous_tags])
- updated_tagnames = set(t for t in tagnames.split(' '))
-
- removed_tagnames = previous_tagnames - updated_tagnames
- added_tagnames = updated_tagnames - previous_tagnames
-
- modified_tags = list()
- #remove tags from the question's tags many2many relation
- if removed_tagnames:
- removed_tags = [tag for tag in previous_tags if tag.name in removed_tagnames]
- self.tags.remove(*removed_tags)
-
- #if any of the removed tags reached use count == 1 that means they must be deleted
- for tag in removed_tags:
- if tag.used_count == 1:
- #we won't modify used count b/c it's done below anyway
- removed_tags.remove(tag)
- #todo - do we need to use fields deleted_by and deleted_at?
- tag.delete()#auto-delete tags whose use count dwindled
-
- #remember modified tags, we'll need to update use counts on them
- modified_tags = removed_tags
-
- #add new tags to the relation
- if added_tagnames:
- #find reused tags
- reused_tags = Tag.objects.filter(name__in = added_tagnames)
- #undelete them, because we are using them
- reused_count = reused_tags.update(
- deleted = False,
- deleted_by = None,
- deleted_at = None
- )
- #if there are brand new tags, create them and finalize the added tag list
- if reused_count < len(added_tagnames):
- added_tags = list(reused_tags)
-
- reused_tagnames = set([tag.name for tag in reused_tags])
- new_tagnames = added_tagnames - reused_tagnames
- for name in new_tagnames:
- new_tag = Tag.objects.create(
- name = name,
- created_by = user,
- used_count = 1
- )
- added_tags.append(new_tag)
- else:
- added_tags = reused_tags
-
- #finally add tags to the relation and extend the modified list
- self.tags.add(*added_tags)
- modified_tags.extend(added_tags)
-
- #if there are any modified tags, update their use counts
- if modified_tags:
- Tag.objects.update_use_counts(modified_tags)
- signals.tags_updated.send(None,
- question = thread_question,
- tags = modified_tags,
- user = user,
- timestamp = timestamp
- )
- return True
-
- return False
-
- def retag(self, retagged_by=None, retagged_at=None, tagnames=None, silent=False):
- if None in (retagged_by, retagged_at, tagnames):
- raise Exception('arguments retagged_at, retagged_by and tagnames are required')
-
- thread_question = self._question()
-
- self.tagnames = tagnames
- self.save()
-
- # Update the Question itself
- if silent == False:
- thread_question.last_edited_at = retagged_at
- #thread_question.thread.last_activity_at = retagged_at
- thread_question.last_edited_by = retagged_by
- #thread_question.thread.last_activity_by = retagged_by
- thread_question.save()
-
- # Update the Question's tag associations
- self.update_tags(tagnames=tagnames, user=retagged_by, timestamp=retagged_at)
-
- # Create a new revision
- latest_revision = thread_question.get_latest_revision()
- PostRevision.objects.create_question_revision(
- question = thread_question,
- title = latest_revision.title,
- author = retagged_by,
- revised_at = retagged_at,
- tagnames = tagnames,
- summary = const.POST_STATUS['retagged'],
- text = latest_revision.text
- )
-
- def has_favorite_by_user(self, user):
- if not user.is_authenticated():
- return False
-
- return FavoriteQuestion.objects.filter(thread=self, user=user).exists()
-
- def get_last_update_info(self):
- thread_question = self._question()
-
- when, who = thread_question.post_get_last_update_info()
-
- answers = thread_question.answers.all()
- for a in answers:
- a_when, a_who = a.post_get_last_update_info()
- if a_when > when:
- when = a_when
- who = a_who
-
- return when, who
-
class Question(content.Content):
post_type = 'question'
diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py
index f4f84c00..bd2e5780 100644
--- a/askbot/tests/email_alert_tests.py
+++ b/askbot/tests/email_alert_tests.py
@@ -14,7 +14,7 @@ from askbot import models
from askbot.utils import mail
from askbot.conf import settings as askbot_settings
from askbot import const
-from askbot.models.question import get_tag_summary_from_questions
+from askbot.models.question import Thread
TO_JSON = functools.partial(serializers.serialize, 'json')
@@ -680,39 +680,26 @@ class InstantQAnsEmailAlertTests(EmailAlertTests):
class DelayedAlertSubjectLineTests(TestCase):
def test_topics_in_subject_line(self):
- q1 = models.Question(id=1, thread=models.Thread(tagnames='one two three four five'))
- q2 = models.Question(id=2, thread=models.Thread(tagnames='two three four five'))
- q3 = models.Question(id=3, thread=models.Thread(tagnames='three four five'))
- q4 = models.Question(id=4, thread=models.Thread(tagnames='four five'))
- q5 = models.Question(id=5, thread=models.Thread(tagnames='five'))
- q6 = models.Question(id=6, thread=models.Thread(tagnames='six'))
- q7 = models.Question(id=7, thread=models.Thread(tagnames='six'))
- q8 = models.Question(id=8, thread=models.Thread(tagnames='six'))
- q9 = models.Question(id=9, thread=models.Thread(tagnames='six'))
- q10 = models.Question(id=10, thread=models.Thread(tagnames='six'))
- q11 = models.Question(id=11, thread=models.Thread(tagnames='six'))
- q_dict = {
- q1:'', q2:'', q3:'', q4:'', q5:'', q6:'', q7:'',
- q8:'', q9:'', q10:'', q11:'',
- }
- subject = get_tag_summary_from_questions(q_dict.keys())
-
- self.assertTrue('one' not in subject)
- self.assertTrue('two' in subject)
- self.assertTrue('three' in subject)
- self.assertTrue('four' in subject)
- self.assertTrue('five' in subject)
- self.assertTrue('six' in subject)
- i2 = subject.index('two')
- i3 = subject.index('three')
- i4 = subject.index('four')
- i5 = subject.index('five')
- i6 = subject.index('six')
- order = [i6, i5, i4, i3, i2]
- self.assertEquals(
- order,
- sorted(order)
- )
+ threads = [
+ models.Thread(tagnames='one two three four five'),
+ models.Thread(tagnames='two three four five'),
+ models.Thread(tagnames='three four five'),
+ models.Thread(tagnames='four five'),
+ models.Thread(tagnames='five'),
+ ]
+ subject = Thread.objects.get_tag_summary_from_threads(threads)
+ self.assertEqual('"five", "four", "three", "two" and "one"', subject)
+
+ threads += [
+ models.Thread(tagnames='six'),
+ models.Thread(tagnames='six'),
+ models.Thread(tagnames='six'),
+ models.Thread(tagnames='six'),
+ models.Thread(tagnames='six'),
+ models.Thread(tagnames='six'),
+ ]
+ subject = Thread.objects.get_tag_summary_from_threads(threads)
+ self.assertEqual('"six", "five", "four", "three", "two" and more', subject)
class FeedbackTests(utils.AskbotTestCase):
def setUp(self):