summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--forum/const/__init__.py8
-rw-r--r--forum/middleware/pagesize.py3
-rw-r--r--forum/models/__init__.py205
-rw-r--r--forum/models/answer.py17
-rw-r--r--forum/models/base.py107
-rw-r--r--forum/models/meta.py53
-rw-r--r--forum/models/question.py46
-rw-r--r--forum/models/repute.py8
-rw-r--r--forum/models/signals.py7
-rw-r--r--forum/models/tag.py5
-rw-r--r--forum/models/user.py51
-rw-r--r--forum/utils/markup.py (renamed from forum/models/utils.py)82
-rwxr-xr-xlog/README.TXT1
13 files changed, 342 insertions, 251 deletions
diff --git a/forum/const/__init__.py b/forum/const/__init__.py
index e7328c54..fcba80d1 100644
--- a/forum/const/__init__.py
+++ b/forum/const/__init__.py
@@ -56,6 +56,12 @@ POST_SCOPE_LIST = (
)
DEFAULT_POST_SCOPE = 'all'
PAGE_SIZE_CHOICES = (('10','10',),('30','30',),('50','50',),)
+#todo: remove this duplication
+QUESTIONS_PER_PAGE_USER_CHOICES = (
+ (10, u'10'),
+ (30, u'30'),
+ (50, u'50'),
+)
UNANSWERED_QUESTION_MEANING_CHOICES = (
('NO_ANSWERS', _('Question has no answers')),
@@ -144,7 +150,7 @@ TYPE_RESPONSE = {
'ANSWER_ACCEPTED' : _('answer_accepted'),
}
-CONST = {
+POST_STATUS = {
'closed' : _('[closed]'),
'deleted' : _('[deleted]'),
'default_version' : _('initial version'),
diff --git a/forum/middleware/pagesize.py b/forum/middleware/pagesize.py
index 486193dc..7090a04c 100644
--- a/forum/middleware/pagesize.py
+++ b/forum/middleware/pagesize.py
@@ -30,4 +30,5 @@ class QuestionsPageSizeMiddleware(object):
def process_exception(self,request,exception):
import logging
- logging.debug('have exception %s' % str(exception))
+ import traceback
+ logging.debug(repr(traceback.extract_tb(exc_traceback)))
diff --git a/forum/models/__init__.py b/forum/models/__init__.py
index 6777339a..6085ce5a 100644
--- a/forum/models/__init__.py
+++ b/forum/models/__init__.py
@@ -1,3 +1,4 @@
+import signals
from question import Question ,QuestionRevision, QuestionView, AnonymousQuestion, FavoriteQuestion
from answer import Answer, AnonymousAnswer, AnswerRevision
from tag import Tag, MarkedTag
@@ -5,53 +6,27 @@ from meta import Vote, Comment, FlaggedItem
from user import Activity, ValidationHash, EmailFeedSetting
from user import AuthKeyUserAssociation
from repute import Badge, Award, Repute
-import signals
from django.core.urlresolvers import reverse
from django.core.mail import EmailMessage
from forum.search.indexer import create_fulltext_indexes
-from django.db.models.signals import post_syncdb
+from django.db.models import signals as django_signals
from django.template import loader, Context
from django.utils.translation import ugettext as _
+from django.contrib.auth.models import User
+from django.template.defaultfilters import slugify
+from django.utils.safestring import mark_safe
+from django.db import models
from forum import const
import logging
import re
+import hashlib
-from base import *
import datetime
from django.contrib.contenttypes.models import ContentType
#todo: must go after signals
from forum import auth
-# User extend properties
-QUESTIONS_PER_PAGE_CHOICES = (
- (10, u'10'),
- (30, u'30'),
- (50, u'50'),
-)
-
-def user_is_username_taken(cls,username):
- try:
- cls.objects.get(username=username)
- return True
- except cls.MultipleObjectsReturned:
- return True
- except cls.DoesNotExist:
- return False
-
-def user_get_q_sel_email_feed_frequency(self):
- #print 'looking for frequency for user %s' % self
- try:
- feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel')
- except Exception, e:
- #print 'have error %s' % e.message
- raise e
- #print 'have freq=%s' % feed_setting.frequency
- return feed_setting.frequency
-
-def user_get_absolute_url(self):
- return "/users/%d/%s/" % (self.id, (self.username))
-
User.add_to_class('is_approved', models.BooleanField(default=False))
User.add_to_class('email_isvalid', models.BooleanField(default=False))
User.add_to_class('email_key', models.CharField(max_length=32, null=True))
@@ -70,7 +45,10 @@ User.add_to_class('gold', models.SmallIntegerField(default=0))
User.add_to_class('silver', models.SmallIntegerField(default=0))
User.add_to_class('bronze', models.SmallIntegerField(default=0))
User.add_to_class('questions_per_page',
- models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
+ models.SmallIntegerField(
+ choices=const.QUESTIONS_PER_PAGE_USER_CHOICES,
+ default=10)
+ )
User.add_to_class('last_seen',
models.DateTimeField(default=datetime.datetime.now))
User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
@@ -78,17 +56,37 @@ User.add_to_class('website', models.URLField(max_length=200, blank=True))
User.add_to_class('location', models.CharField(max_length=100, blank=True))
User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
User.add_to_class('about', models.TextField(blank=True))
-User.add_to_class('is_username_taken',classmethod(user_is_username_taken))
-User.add_to_class('get_q_sel_email_feed_frequency',user_get_q_sel_email_feed_frequency)
User.add_to_class('hide_ignored_questions', models.BooleanField(default=False))
User.add_to_class('tag_filter_setting',
models.CharField(
max_length=16,
- choices=TAG_EMAIL_FILTER_CHOICES,
+ choices=const.TAG_EMAIL_FILTER_CHOICES,
default='ignored'
)
)
-User.add_to_class('get_absolute_url', user_get_absolute_url)
+
+def user_is_username_taken(cls,username):
+ try:
+ cls.objects.get(username=username)
+ return True
+ except cls.MultipleObjectsReturned:
+ return True
+ except cls.DoesNotExist:
+ return False
+
+def user_get_q_sel_email_feed_frequency(self):
+ #print 'looking for frequency for user %s' % self
+ try:
+ feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel')
+ except Exception, e:
+ #print 'have error %s' % e.message
+ raise e
+ #print 'have freq=%s' % feed_setting.frequency
+ return feed_setting.frequency
+
+def user_get_absolute_url(self):
+ return "/users/%d/%s/" % (self.id, (self.username))
+
def get_messages(self):
messages = []
@@ -224,14 +222,21 @@ def flag_post(self, post, timestamp=None, cancel=False):
)
auth.onFlaggedItem(flag, post, user, timestamp=timestamp)
-def user_should_receive_instant_notification_about_post(user, post):
+def user_should_receive_instant_notification_about_post(
+ user,
+ post,
+ newly_mentioned_users = []
+ ):
return EmailFeedSetting.objects.exists_match_to_post_and_subscriber(
- subscriber = user,
- post = post,
- frequency = 'i',
- )
-
+ subscriber = user,
+ post = post,
+ frequency = 'i',
+ newly_mentioned_users = newly_mentioned_users
+ )
+User.add_to_class('is_username_taken',classmethod(user_is_username_taken))
+User.add_to_class('get_q_sel_email_feed_frequency',user_get_q_sel_email_feed_frequency)
+User.add_to_class('get_absolute_url', user_get_absolute_url)
User.add_to_class('upvote', upvote)
User.add_to_class('downvote', downvote)
User.add_to_class('accept_answer', accept_answer)
@@ -246,11 +251,11 @@ User.add_to_class(
user_should_receive_instant_notification_about_post
)
-
def send_instant_notifications_about_activity_in_post(
- activity,
- post,
- receiving_users
+ activity = None,
+ post = None,
+ receiving_users = [],
+ newly_mentioned_users = []
):
"""
function called when posts are updated
@@ -265,18 +270,12 @@ def send_instant_notifications_about_activity_in_post(
}
template = loader.get_template('instant_notification.html')
- for u in receiving_users:
- if u.should_receive_instant_notification_about_post(post):
+ for u in set(receiving_users) + set(newly_mentioned_users):
+ if u.should_receive_instant_notification_about_post(
+ post,
+ newly_mentioned_users = newly_mentioned_users
+ ):
- mentions = Activity.objects.get_mentions(
- mentioned_whom = u,
- mentioned_in = post,
- reported = False
- )
- if mentions:
- #todo: find a more semantic way to do this
- mentions.update(is_auditted = True)
-
#get details about update
#todo: is there a way to solve this import issue?
from forum.conf import settings as forum_settings
@@ -358,50 +357,35 @@ def record_answer_event(instance, created, **kwargs):
activity.receiving_users.add(*receiving_users)
-def record_comment_event(instance, **kwargs):
- if isinstance(instance.content_object, Question):
- activity_type = const.TYPE_ACTIVITY_COMMENT_QUESTION
- elif isinstance(instance.content_object, Answer):
- activity_type = const.TYPE_ACTIVITY_COMMENT_ANSWER
- else:
- logging.critical(
- 'recording comment for %s is not implemented'\
- % type(instance.content_object)
- )
+#todo: change to more general post_update_activity
+def record_post_update_activity(post, newly_mentioned_users, **kwargs):
+ #todo: take into account created == True case
+ activity_type = post.get_updated_activity_type()
+ #fields will depend on post type and maybe activity type
+ #post has to be saved already, b/c Activity is in generic relation to post
activity = Activity(
- user = instance.user,
- active_at = instance.added_at,
- content_object = instance,
+ user = post.get_last_author(),
+ active_at = post.added_at,
+ content_object = post,
activity_type = activity_type
)
activity.save()
- receiving_users = set()
- receiving_users.update(
- #get authors of parent object and all associated comments
- instance.content_object.get_author_list(
- include_comments = True,
- )
- )
-
- receiving_users.update(
- instance.get_newly_mentioned_users()
- )
-
- receiving_users -= set([instance.user])#remove activity user
- receiving_users = list(receiving_users)
+ #what users are included depends on the post type
+ #for example for question - all Q&A contributors
+ #are included, for comments only authors of comments and parent
+ #post are included
+ receiving_users = post.get_potentially_interested_users()
activity.receiving_users.add(*receiving_users)
- print 'in post_save handler on comment'
- print receiving_users
-
send_instant_notifications_about_activity_in_post(
- activity,
- instance,
- receiving_users
- )
+ activity = activity,
+ post = post,
+ receiving_users = receiving_users,
+ newly_mentioned_users = newly_mentioned_users
+ )
def record_revision_question_event(instance, created, **kwargs):
@@ -423,10 +407,6 @@ def record_revision_question_event(instance, created, **kwargs):
for a in instance.question.answers.all():
receiving_users.update(a.get_author_list())
- receiving_users.update(
- instance.question.get_newly_mentioned_users()
- )
-
receiving_users -= set([instance.author])#remove activity user
receiving_users = list(receiving_users)
@@ -456,10 +436,6 @@ def record_revision_answer_event(instance, created, **kwargs):
)
receiving_users.update(instance.answer.question.get_author_list())
- receiving_users.update(
- instance.answer.get_newly_mentioned_users()
- )
-
receiving_users -= set([instance.author])
receiving_users = list(receiving_users)
@@ -656,18 +632,18 @@ def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs)
aa.publish(user)
#signal for User model save changes
-pre_save.connect(calculate_gravatar_hash, sender=User)
-post_save.connect(record_ask_event, sender=Question)
-post_save.connect(record_answer_event, sender=Answer)
-post_save.connect(record_revision_question_event, sender=QuestionRevision)
-post_save.connect(record_revision_answer_event, sender=AnswerRevision)
-post_save.connect(record_award_event, sender=Award)
-post_save.connect(notify_award_message, sender=Award)
-post_save.connect(record_answer_accepted, sender=Answer)
-post_save.connect(update_last_seen, sender=Activity)
-post_save.connect(record_vote, sender=Vote)
-post_save.connect(record_favorite_question, sender=FavoriteQuestion)
-post_delete.connect(record_cancel_vote, sender=Vote)
+django_signals.pre_save.connect(calculate_gravatar_hash, sender=User)
+django_signals.post_save.connect(record_ask_event, sender=Question)
+django_signals.post_save.connect(record_answer_event, sender=Answer)
+django_signals.post_save.connect(record_revision_question_event, sender=QuestionRevision)
+django_signals.post_save.connect(record_revision_answer_event, sender=AnswerRevision)
+django_signals.post_save.connect(record_award_event, sender=Award)
+django_signals.post_save.connect(notify_award_message, sender=Award)
+django_signals.post_save.connect(record_answer_accepted, sender=Answer)
+django_signals.post_save.connect(update_last_seen, sender=Activity)
+django_signals.post_save.connect(record_vote, sender=Vote)
+django_signals.post_save.connect(record_favorite_question, sender=FavoriteQuestion)
+django_signals.post_delete.connect(record_cancel_vote, sender=Vote)
#change this to real m2m_changed with Django1.2
signals.delete_post_or_answer.connect(record_delete_question, sender=Question)
@@ -677,7 +653,10 @@ signals.mark_offensive.connect(record_mark_offensive, sender=Answer)
signals.tags_updated.connect(record_update_tags, sender=Question)
signals.user_updated.connect(record_user_full_updated, sender=User)
signals.user_logged_in.connect(post_stored_anonymous_content)
-signals.comment_post_save.connect(record_comment_event, sender=Comment)
+signals.post_updated.connect(
+ record_post_update_activity,
+ sender=Comment
+ )
#post_syncdb.connect(create_fulltext_indexes)
#todo: wtf??? what is x=x about?
diff --git a/forum/models/answer.py b/forum/models/answer.py
index 1c33cad8..9179226f 100644
--- a/forum/models/answer.py
+++ b/forum/models/answer.py
@@ -1,9 +1,12 @@
-from base import *
+from base import Content, AnonymousContent, ContentRevision, DeletableContent
#todo: take care of copy-paste markdowner stuff maybe make html automatic field?
-from forum.const import CONST
+from forum import const
from markdown2 import Markdown
from django.utils.html import strip_tags
from forum.utils.html import sanitize_html
+from django.db import models
+from django.utils.http import urlquote as django_urlquote
+from django.template.defaultfilters import slugify
import datetime
markdowner = Markdown(html4tags=True)
@@ -27,10 +30,10 @@ class AnswerManager(models.Manager):
answer.save()
answer.add_revision(
- revised_by=author,
- revised_at=added_at,
- text=text,
- comment=CONST['default_version'],
+ revised_by = author,
+ revised_at = added_at,
+ text = text,
+ comment = const.POST_STATUS['default_version'],
)
#update question data
@@ -122,7 +125,7 @@ class Answer(Content, DeletableContent):
rev_no = self.revisions.all().count() + 1
if comment in (None, ''):
if rev_no == 1:
- comment = CONST['default_version']
+ comment = const.POST_STATUS['default_version']
else:
comment = 'No.%s Revision' % rev_no
return AnswerRevision.objects.create(
diff --git a/forum/models/base.py b/forum/models/base.py
index c153bb5d..e276c6ee 100644
--- a/forum/models/base.py
+++ b/forum/models/base.py
@@ -1,39 +1,104 @@
import datetime
import hashlib
from urllib import quote_plus, urlencode
-from django.db import models, IntegrityError, connection, transaction
+from django.db import models
from django.utils.http import urlquote as django_urlquote
from django.utils.html import strip_tags
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
-from django.template.defaultfilters import slugify
-from django.db.models.signals import post_delete, post_save, pre_save
from django.utils.translation import ugettext as _
-from django.utils.safestring import mark_safe
from django.contrib.sitemaps import ping_google
import django.dispatch
from django.conf import settings
+from forum.utils import markup
+from django.utils import html
import logging
-#todo: sphinx search import used to be here
+#todo: following methods belong to a future common post class
+def render_post_text_and_get_newly_mentioned_users(post,
+ urlize_content = False):
-from forum.const import *
+ text = post.get_text()
-#todo: this method belongs to a common post class
-def get_newly_mentioned_users_in_post(post):
- from forum.models import Activity
- mentions = Activity.objects.get_mentions(
- mentioned_in = post,
- reported = False
- )
- users = set()
- for m in mentions:
- users.update(m.receiving_users.all())
- return list(users)
+ if urlize_content:
+ text = html.urlize(text)
+
+ if '@' not in text:
+ return list()
+
+ from forum.models.user import Activity
+
+ mentioned_by = post.get_last_author()
+
+ op = post.get_origin_post()
+ anticipated_authors = op.get_author_list( include_comments = True, recursive = True )
+
+ extra_name_seeds = markup.extract_mentioned_name_seeds(text)
+ extra_authors = set()
+ for name_seed in extra_name_seeds:
+ extra_authors.update(User.objects.filter(username__startswith = name_seed))
+
+ #it is important to preserve order here so that authors of post get mentioned first
+ anticipated_authors += list(extra_authors)
+
+ mentioned_authors, post.html = markup.mentionize_text(text, anticipated_authors)
+
+ #maybe delete some previous mentions
+ if self.id != None:
+ #only look for previous mentions if post was already saved before
+ prev_mention_qs = Activity.objects.get_mentions(
+ mentioned_in = post
+ )
+ new_set = set(mentioned_authors)
+ for mention in prev_mention_qs:
+ delta_set = set(mention.receiving_users.all()) - new_set
+ if not delta_set:
+ mention.delete()
+ new_set -= delta_set
+
+ mentioned_authors = list(new_set)
+
+ return mentioned_authors
+
+def save_content(self, urlize_content = False, **kwargs):
+ """generic save method to use with posts
+ """
+
+ new_mentions = self._render_text_and_get_newly_mentioned_users(
+ urlize_content
+ )
+
+ from forum.models.user import Activity
+
+ #this save must precede saving the mention activity
+ super(self.__class__, self).save(**kwargs)
+
+ post_author = self.get_last_author()
+
+ for u in new_mentions:
+ Activity.objects.create_new_mention(
+ mentioned_whom = u,
+ mentioned_in = self,
+ mentioned_by = post_author
+ )
+ #todo: this is handled in signal because models for posts
+ #are too spread out
+ from forum.models import signals
+ signals.post_updated.send(
+ post = self,
+ newly_mentioned_users = new_mentions,
+ sender = self.__class__
+ )
+
+ try:
+ ping_google()
+ except Exception:
+ logging.debug('problem pinging google did you register you sitemap with google?')
+
class UserContent(models.Model):
user = models.ForeignKey(User, related_name='%(class)ss')
@@ -145,6 +210,10 @@ class Content(models.Model):
comments = self.comments.all().order_by('id')
return comments
+ #todo: maybe remove this wnen post models are unified
+ def get_text(self):
+ return self.text
+
def add_comment(self, comment=None, user=None, added_at=None):
if added_at is None:
added_at = datetime.datetime.now()
@@ -184,8 +253,6 @@ class Content(models.Model):
authors -= set(exclude_list)
return list(authors)
- get_newly_mentioned_users = get_newly_mentioned_users_in_post
-
def passes_tag_filter_for_user(self, user):
tags = self.get_origin_post().tags.all()
@@ -202,8 +269,6 @@ class Content(models.Model):
else:
raise Exception('unexpected User.tag_filter_setting %' % self.tag_filter_setting)
-
-
def post_get_last_update_info(self):#todo: rename this subroutine
when = self.added_at
who = self.author
diff --git a/forum/models/meta.py b/forum/models/meta.py
index 0e49064e..c72bd948 100644
--- a/forum/models/meta.py
+++ b/forum/models/meta.py
@@ -1,7 +1,9 @@
-from base import *
+from django.db import models
+from base import MetaContent, UserContent
+from base import render_post_text_and_get_newly_mentioned_users
+from base import save_content
from forum import const
-from django.utils.html import urlize
-from forum.models import signals
+import datetime
class VoteManager(models.Manager):
def get_up_vote_count_from_user(self, user):
@@ -88,22 +90,35 @@ class Comment(MetaContent, UserContent):
def get_origin_post(self):
return self.content_object.get_origin_post()
+ #todo: maybe remove this wnen post models are unified
+ def get_text(self):
+ return self.comment
+
+ _render_text_and_get_newly_mentioned_users = \
+ render_post_text_and_get_newly_mentioned_users
+
+ _save = save_content
+
def save(self,**kwargs):
- print 'before first save'
- super(Comment,self).save(**kwargs)
- print 'after first save'
- from forum.models.utils import mentionize
- self.html = mentionize(urlize(self.comment, nofollow=True), context_object = self)
- print 'mentionized'
- #todo - try post_save to install mentions
- super(Comment,self).save(**kwargs)#have to save twice!!, b/c need id for generic relation
-
- signals.comment_post_save.send(instance = self, sender = Comment)
-
- try:
- ping_google()
- except Exception:
- logging.debug('problem pinging google did you register you sitemap with google?')
+ self._save(urlize_content = True)
+
+ def get_updated_activity_type(self):
+ if self.content_object.__class__.__name__ == 'Question':
+ return const.TYPE_ACTIVITY_COMMENT_QUESTION
+ elif self.content_object.__class__.__name__ == 'Answer':
+ return const.TYPE_ACTIVITY_COMMENT_ANSWER
+
+ def get_potentially_interested_users(self):
+ users = set()
+ users.update(
+ #get authors of parent object and all associated comments
+ comment.content_object.get_author_list(
+ include_comments = True,
+ )
+ )
+
+ users -= set([comment.user])#remove activity user
+ return list(users)
def delete(self, **kwargs):
#todo: not very good import in models of other models
@@ -121,7 +136,5 @@ class Comment(MetaContent, UserContent):
def get_latest_revision_number(self):
return 1
- get_newly_mentioned_users = get_newly_mentioned_users_in_post
-
def __unicode__(self):
return self.comment
diff --git a/forum/models/question.py b/forum/models/question.py
index 0793e601..0f46e55a 100644
--- a/forum/models/question.py
+++ b/forum/models/question.py
@@ -1,8 +1,6 @@
-from base import * #todo maybe remove *
+from base import Content, DeletableContent, AnonymousContent, ContentRevision
from forum.models import signals
from tag import Tag
-#todo: make uniform import for consts
-from forum.const import CONST
from forum import const
from forum.utils.html import sanitize_html
from markdown2 import Markdown
@@ -11,7 +9,11 @@ import datetime
from django.conf import settings
from django.utils.datastructures import SortedDict
from forum.models.tag import MarkedTag
-from django.db.models import Q
+from django.db import models
+from django.contrib.auth.models import User
+from django.utils.http import urlquote as django_urlquote
+from django.template.defaultfilters import slugify
+from django.core.urlresolvers import reverse
markdowner = Markdown(html4tags=True)
@@ -56,7 +58,7 @@ class QuestionManager(models.Manager):
question.add_revision(
author=author,
text=text,
- comment=CONST['default_version'],
+ comment=const.POST_STATUS['default_version'],
revised_at=added_at,
)
return question
@@ -85,11 +87,12 @@ class QuestionManager(models.Manager):
if search_query:
try:
- qs = qs.filter( Q(title__search = search_query) \
- | Q(text__search = search_query) \
- | Q(tagnames__search = search_query) \
- | Q(answers__text__search = search_query)
- )
+ qs = qs.filter(
+ models.Q(title__search = search_query) \
+ | models.Q(text__search = search_query) \
+ | models.Q(tagnames__search = search_query) \
+ | models.Q(answers__text__search = search_query)
+ )
except:
#fallback to dumb title match search
qs = qs.extra(
@@ -117,7 +120,10 @@ class QuestionManager(models.Manager):
if author_selector:
try:
u = User.objects.get(id=int(author_selector))
- qs = qs.filter(Q(author=u, deleted=False) | Q(answers__author=u, answers__deleted=False))
+ qs = qs.filter(
+ models.Q(author=u, deleted=False) \
+ | models.Q(answers__author=u, answers__deleted=False)
+ )
meta_data['author_name'] = u.username
except User.DoesNotExist:
meta_data['author_name'] = None
@@ -180,8 +186,8 @@ class QuestionManager(models.Manager):
for q in question_list:
answer_list.extend(list(q.answers.all()))
return User.objects.filter(
- Q(questions__in=question_list) \
- | Q(answers__in=answer_list)
+ models.Q(questions__in=question_list) \
+ | models.Q(answers__in=answer_list)
).distinct().order_by('?')
def get_author_list(self, **kwargs):
@@ -285,7 +291,11 @@ class Question(Content, DeletableContent):
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=CLOSE_REASONS, null=True, blank=True)
+ close_reason = models.SmallIntegerField(
+ choices=const.CLOSE_REASONS,
+ null=True,
+ blank=True
+ )
followed_by = models.ManyToManyField(User, related_name='followed_questions')
# Denormalised data
@@ -336,7 +346,7 @@ class Question(Content, DeletableContent):
author = retagged_by,
revised_at = retagged_at,
tagnames = tagnames,
- summary = CONST['retagged'],
+ summary = const.POST_STATUS['retagged'],
text = latest_revision.text
)
# send tags updated singal
@@ -402,7 +412,7 @@ class Question(Content, DeletableContent):
rev_no = self.revisions.all().count() + 1
if comment in (None, ''):
if rev_no == 1:
- comment = CONST['default_version']
+ comment = const.POST_STATUS['default_version']
else:
comment = 'No.%s Revision' % rev_no
@@ -458,9 +468,9 @@ class Question(Content, DeletableContent):
def get_question_title(self):
if self.closed:
- attr = CONST['closed']
+ attr = const.POST_STATUS['closed']
elif self.deleted:
- attr = CONST['deleted']
+ attr = const.POST_STATUS['deleted']
else:
attr = None
if attr is not None:
diff --git a/forum/models/repute.py b/forum/models/repute.py
index f71be4db..ad640f59 100644
--- a/forum/models/repute.py
+++ b/forum/models/repute.py
@@ -1,8 +1,10 @@
-from base import *
from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
from django.contrib.auth.models import User
-
+from django.db import models
from django.utils.translation import ugettext as _
+import datetime
+from forum import const
class Badge(models.Model):
"""Awarded for notable actions performed on the site by Users."""
@@ -109,7 +111,7 @@ class Repute(models.Model):
negative = models.SmallIntegerField(default=0)
question = models.ForeignKey('Question')
reputed_at = models.DateTimeField(default=datetime.datetime.now)
- reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
+ reputation_type = models.SmallIntegerField(choices=const.TYPE_REPUTATION)
reputation = models.IntegerField(default=1)
objects = ReputeManager()
diff --git a/forum/models/signals.py b/forum/models/signals.py
index b3850979..f711260b 100644
--- a/forum/models/signals.py
+++ b/forum/models/signals.py
@@ -14,4 +14,9 @@ user_updated = django.dispatch.Signal(providing_args=['instance', 'updated_by'])
#todo: move this to authentication app
user_logged_in = django.dispatch.Signal(providing_args=['session'])
-comment_post_save = django.dispatch.Signal(providing_args=['instance'])
+post_updated = django.dispatch.Signal(
+ providing_args=[
+ 'post',
+ 'newly_mentioned_users'
+ ]
+ )
diff --git a/forum/models/tag.py b/forum/models/tag.py
index e13baf9b..0e585547 100644
--- a/forum/models/tag.py
+++ b/forum/models/tag.py
@@ -1,4 +1,7 @@
-from base import *
+from base import DeletableContent
+from django.db import models
+from django.db import connection, transaction
+from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
diff --git a/forum/models/user.py b/forum/models/user.py
index d7678801..85e22f1d 100644
--- a/forum/models/user.py
+++ b/forum/models/user.py
@@ -1,12 +1,9 @@
-from base import *
#todo: remove this with Django 1.2
+from django.db import models
from django.contrib.contenttypes.models import ContentType
from forum.models import signals
from django.contrib.contenttypes import generic
from django.contrib.auth.models import User
-from forum.models.question import Question, QuestionRevision
-from forum.models.answer import Answer, AnswerRevision
-from forum.models.meta import Comment
from hashlib import md5
import string
from random import Random
@@ -66,7 +63,6 @@ class ActivityManager(models.Manager):
else:
kwargs['is_auditted'] = False
-
mention_activity = Activity(**kwargs)
mention_activity.save()
@@ -78,7 +74,6 @@ class ActivityManager(models.Manager):
return mention_activity
-
def get_mentions(
self,
mentioned_by = None,
@@ -146,26 +141,28 @@ class Activity(models.Model):
class EmailFeedSettingManager(models.Manager):
- def exists_match_to_post_and_subscriber(self, post = None, subscriber = None, **kwargs):
+ def exists_match_to_post_and_subscriber(
+ self,
+ post = None,
+ subscriber = None,
+ newly_mentioned_users = [],
+ **kwargs
+ ):
"""returns list of feeds matching the post
and subscriber
+ newly_mentioned_user parameter is there to save
+ on a database hit looking for mentions of subscriber
+ in the current post
"""
feeds = self.filter(subscriber = subscriber, **kwargs)
for feed in feeds:
if feed.feed_type == 'm_and_c':
- if isinstance(post, Comment):
+ if post.__clas__.__name__ == 'Comment':#isinstance(post, Comment):
return True
else:
- post_content_type = ContentType.objects.get_for_model(post)
- subscriber_mentions = Mention.objects.filter(
- content_type = post_content_type,
- object_id = post.id,
- mentioned_whom = subscriber,
- is_auditted = False
- )
- if subscriber_mentions:
+ if subscriber in newly_mentioned_users:
return True
else:
if feed.feed_type == 'q_all':
@@ -256,7 +253,10 @@ class EmailFeedSetting(models.Model):
def save(self,*args,**kwargs):
type = self.feed_type
subscriber = self.subscriber
- similar = self.__class__.objects.filter(feed_type=type,subscriber=subscriber).exclude(pk=self.id)
+ similar = self.__class__.objects.filter(
+ feed_type=type,
+ subscriber=subscriber
+ ).exclude(pk=self.id)
if len(similar) > 0:
raise IntegrityError('email feed setting already exists')
super(EmailFeedSetting,self).save(*args,**kwargs)
@@ -284,7 +284,14 @@ from forum.utils.time import one_day_from_now
class ValidationHashManager(models.Manager):
def _generate_md5_hash(self, user, type, hash_data, seed):
- return md5("%s%s%s%s" % (seed, "".join(map(str, hash_data)), user.id, type)).hexdigest()
+ return md5(
+ "%s%s%s%s" % (
+ seed,
+ "".join(map(str, hash_data)),
+ user.id,
+ type
+ )
+ ).hexdigest()
def create_new(self, user, type, hash_data=[], expiration=None):
seed = ''.join(Random().sample(string.letters+string.digits, 12))
@@ -314,7 +321,13 @@ class ValidationHashManager(models.Manager):
if obj.user != user:
return False
- valid = (obj.hash_code == self._generate_md5_hash(obj.user, type, hash_data, obj.seed))
+ valid = (obj.hash_code == self._generate_md5_hash(
+ obj.user,
+ type,
+ hash_data,
+ obj.seed
+ )
+ )
if valid:
if obj.expiration < datetime.datetime.now():
diff --git a/forum/models/utils.py b/forum/utils/markup.py
index bfe7b1c9..2b2b8ab1 100644
--- a/forum/models/utils.py
+++ b/forum/utils/markup.py
@@ -1,30 +1,16 @@
from forum import const
-#from forum.models import Comment, Question, Answer
-#from forum.models import QuestionRevision, AnswerRevision
-from forum.models import Activity, User
-#todo: don't like that this file deals with models directly
-def _make_mention(mentioned_whom, context_object = None):
- mentioned_by = context_object.get_last_author()
- if mentioned_whom:
- if mentioned_whom != mentioned_by:
- m = Activity.objects.create_new_mention(
- mentioned_by = mentioned_by,
- mentioned_whom = mentioned_whom,
- mentioned_in = context_object
- )
- url = mentioned_whom.get_profile_url()
- username = mentioned_whom.username
- return '<a href="%s">@%s</a>' % (url, username)
- else:
- return '@'
+def format_mention_in_html(mentioned_user):
+ url = mentioned_user.get_profile_url()
+ username = mentioned_user.username
+ return '<a href="%s">@%s</a>' % (url, username)
-def _extract_matching_mentioned_author(text, authors):
+def extract_first_matching_mentioned_author(text, anticipated_authors):
if len(text) == 0:
return None, ''
- for a in authors:
+ for a in anticipated_authors:
if text.startswith(a.username):
ulen = len(a.username)
if len(text) == ulen:
@@ -38,40 +24,36 @@ def _extract_matching_mentioned_author(text, authors):
return a, text
return None, text
-def mentionize(text, context_object = None):
-
- if '@' not in text:
- return text
-
- op = context_object.get_origin_post()
- authors = op.get_author_list( include_comments = True, recursive = True )
-
- text_copy = text
+def extract_mentioned_name_seeds(text):
extra_name_seeds = set()
- while '@' in text_copy:
- pos = text_copy.index('@')
- text_copy = text_copy[pos+1:]#chop off prefix
+ while '@' in text:
+ pos = text.index('@')
+ text = text[pos+1:]#chop off prefix
name_seed = ''
- for c in text_copy:
+ for c in text:
if c in const.TWITTER_STYLE_MENTION_TERMINATION_CHARS:
extra_name_seeds.add(name_seed)
+ name_seed = ''
break
if len(name_seed) > 10:
extra_name_seeds.add(name_seed)
+ name_seed = ''
break
if c == '@':
- extra_name_seeds.add(name_seed)
+ if len(name_seed) > 0:
+ extra_name_seeds.add(name_seed)
+ name_seed = ''
break
name_seed += c
-
- extra_authors = set()
- for name_seed in extra_name_seeds:
if len(name_seed) > 0:
- extra_authors.update(User.objects.filter(username__startswith = name_seed))
+ #in case we run off the end of text
+ extra_name_seeds.add(name_seed)
- authors += list(extra_authors)
+ return extra_name_seeds
+def mentionize_text(text, anticipated_authors):
output = ''
+ mentioned_authors = list()
while '@' in text:
#the purpose of this loop is to convert any occurance of '@mention ' syntax
#to user account links leading space is required unless @ is the first
@@ -94,8 +76,15 @@ def mentionize(text, context_object = None):
#if there is a termination character before @mention
#indeed try to find a matching person
text = text[pos+1:]
- matching_author, text = _extract_matching_mentioned_author(text, authors)
- output += _make_mention(matching_author, context_object = context_object)
+ mentioned_author, text = extract_first_matching_mentioned_author(
+ text,
+ authors
+ )
+ if mentioned_author:
+ mentioned_authors.append(mentioned_author)
+ output += format_mention_in_html(mentioned_author)
+ else:
+ output += '@'
else:
#if there isn't, i.e. text goes like something@mention, do not look up people
@@ -104,10 +93,13 @@ def mentionize(text, context_object = None):
else:
#do this if @ is the first character
text = text[1:]
- matching_author, text = _extract_matching_mentioned_author(text, authors)
- output += _make_mention(matching_author, context_object = context_object)
+ mentioned_author, text = extract_first_matching_mentioned_name(text, authors)
+ if mentioned_author:
+ mentioned_authors.append(mentioned_author)
+ output += format_mention_in_html(mentioned_author)
+ else:
+ output += '@'
#append the rest of text that did not have @ symbols
output += text
- return output
-
+ return mentioned_authors, output
diff --git a/log/README.TXT b/log/README.TXT
deleted file mode 100755
index 9c51276d..00000000
--- a/log/README.TXT
+++ /dev/null
@@ -1 +0,0 @@
-this file is just a placeholder so the empty directory is not ignored by version control