diff options
Diffstat (limited to 'group_messaging/models.py')
-rw-r--r-- | group_messaging/models.py | 238 |
1 files changed, 213 insertions, 25 deletions
diff --git a/group_messaging/models.py b/group_messaging/models.py index 62f720cf..9cc12786 100644 --- a/group_messaging/models.py +++ b/group_messaging/models.py @@ -1,18 +1,40 @@ """models for the ``group_messaging`` app """ +import copy import datetime +import urllib +from askbot.mail import send_mail #todo: remove dependency? +from coffin.template.loader import get_template from django.db import models +from django.db.models import signals +from django.conf import settings as django_settings from django.contrib.auth.models import Group from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.utils.importlib import import_module +from django.utils.translation import ugettext as _ -MAX_TITLE_LENGTH = 80 +MAX_HEADLINE_LENGTH = 80 MAX_SENDERS_INFO_LENGTH = 64 +MAX_SUBJECT_LINE_LENGTH = 30 #dummy parse message function parse_message = lambda v: v GROUP_NAME_TPL = '_personal_%s' +def get_recipient_names(recipient_groups): + """returns list of user names if groups are private, + or group names, otherwise""" + names = set() + for group in recipient_groups: + if group.name.startswith('_personal_'): + names.add(group.user_set.all()[0].username) + else: + names.add(group.name) + return names + + def get_personal_group_by_user_id(user_id): return Group.objects.get(name=GROUP_NAME_TPL % user_id) @@ -87,7 +109,7 @@ class MessageMemo(models.Model): (ARCHIVED, 'archived') ) user = models.ForeignKey(User) - message = models.ForeignKey('Message') + message = models.ForeignKey('Message', related_name='memos') status = models.SmallIntegerField( choices=STATUS_CHOICES, default=SEEN ) @@ -99,13 +121,68 @@ class MessageMemo(models.Model): class MessageManager(models.Manager): """model manager for the :class:`Message`""" - def get_threads_for_user(self, user): - user_groups = user.groups.all() - return self.filter( - root=None, - message_type=Message.STORED, - recipients__in=user_groups + def get_sent_threads(self, sender=None): + """returns list of threads for the "sent" mailbox + this function does not deal with deleted=True + """ + responses = self.filter(sender=sender) + responded_to = models.Q(descendants__in=responses, root=None) + seen_filter = models.Q( + memos__status=MessageMemo.SEEN, + memos__user=sender ) + seen_responses = self.filter(responded_to & seen_filter) + unseen_responses = self.filter(responded_to & ~models.Q(memos__user=sender)) + return ( + self.get_threads(sender=sender) \ + | seen_responses.distinct() \ + | unseen_responses.distinct() + ).distinct() + + def get_threads(self, recipient=None, sender=None, deleted=False): + """returns query set of first messages in conversations, + based on recipient, sender and whether to + load deleted messages or not""" + + if sender and sender == recipient: + raise ValueError('sender cannot be the same as recipient') + + filter_kwargs = { + 'root': None, + 'message_type': Message.STORED + } + if recipient: + filter_kwargs['recipients__in'] = recipient.groups.all() + else: + #todo: possibly a confusing hack - for this branch - + #sender but no recipient in the args - we need "sent" origin threads + recipient = sender + + user_thread_filter = models.Q(**filter_kwargs) + + filter = user_thread_filter + if sender: + filter = filter & models.Q(sender=sender) + + if deleted: + deleted_filter = models.Q( + memos__status=MessageMemo.ARCHIVED, + memos__user=recipient + ) + return self.filter(filter & deleted_filter) + else: + #rather a tricky query (may need to change the idea to get rid of this) + #select threads that have a memo for the user, but the memo is not ARCHIVED + #in addition, select threads that have zero memos for the user + marked_as_non_deleted_filter = models.Q( + memos__status=MessageMemo.SEEN, + memos__user=recipient + ) + #part1 - marked as non-archived + part1 = self.filter(filter & marked_as_non_deleted_filter) + #part2 - messages for the user without an attached memo + part2 = self.filter(filter & ~models.Q(memos__user=recipient)) + return (part1 | part2).distinct() def create(self, **kwargs): """creates a message""" @@ -120,7 +197,7 @@ class MessageManager(models.Manager): kwargs['root'] = root headline = kwargs.get('headline', kwargs['text']) - kwargs['headline'] = headline[:MAX_TITLE_LENGTH] + kwargs['headline'] = headline[:MAX_HEADLINE_LENGTH] kwargs['html'] = parse_message(kwargs['text']) message = super(MessageManager, self).create(**kwargs) @@ -133,7 +210,6 @@ class MessageManager(models.Manager): ) return message - def create_thread(self, sender=None, recipients=None, text=None): """creates a stored message and adds recipients""" message = self.create( @@ -142,7 +218,13 @@ class MessageManager(models.Manager): senders_info=sender.username, text=text, ) + now = datetime.datetime.now() + LastVisitTime.objects.create(message=message, user=sender, at=now) + names = get_recipient_names(recipients) + message.add_recipient_names_to_senders_info(recipients) + message.save() message.add_recipients(recipients) + message.send_email_alert() return message def create_response(self, sender=None, text=None, parent=None): @@ -155,15 +237,23 @@ class MessageManager(models.Manager): #recipients are parent's recipients + sender #creator of response gets memo in the "read" status recipients = set(parent.recipients.all()) - senders_group = get_personal_group(parent.sender) - recipients.add(senders_group) + + if sender != parent.sender: + senders_group = get_personal_group(parent.sender) + parent.add_recipients([senders_group]) + recipients.add(senders_group) + message.add_recipients(recipients) #add author of the parent as a recipient to parent - parent.add_recipients([senders_group]) + #update headline + message.root.headline = text[:MAX_HEADLINE_LENGTH] #mark last active timestamp for the root message - #so that we know that this thread was most recently - #updated - message.update_root_info() + message.root.last_active_at = datetime.datetime.now() + #update senders info - stuff that is shown in the thread heading + message.root.update_senders_info() + #unarchive the thread for all recipients + message.root.unarchive() + message.send_email_alert() return message @@ -205,7 +295,7 @@ class Message(models.Model): blank=True, related_name='children' ) - headline = models.CharField(max_length=MAX_TITLE_LENGTH) + headline = models.CharField(max_length=MAX_HEADLINE_LENGTH) text = models.TextField( null=True, blank=True, @@ -223,6 +313,12 @@ class Message(models.Model): objects = MessageManager() + def add_recipient_names_to_senders_info(self, recipient_groups): + names = get_recipient_names(recipient_groups) + old_names = set(self.senders_info.split(',')) + names |= old_names + self.senders_info = ','.join(names) + def add_recipients(self, recipients): """adds recipients to the message and updates the sender lists for all recipients @@ -233,17 +329,109 @@ class Message(models.Model): sender_list, created = SenderList.objects.get_or_create(recipient=recipient) sender_list.senders.add(self.sender) - def update_root_info(self): - """Update the last active at timestamp and - the contributors info, if relevant. - Root object will be saved to the database. + def get_absolute_url(self, user=None): + """returns absolute url to the thread""" + assert(user != None) + settings = django_settings.GROUP_MESSAGING + func_path = settings['base_url_getter_function'] + path_bits = func_path.split('.') + url_getter = getattr( + import_module('.'.join(path_bits[:-1])), + path_bits[-1] + ) + params = copy.copy(settings['base_url_params']) + params['thread_id'] = self.id + url = url_getter(user) + '?' + urllib.urlencode(params) + #if include_domain_name: #don't need this b/c + # site = Site.objects.get_current() + # url = 'http://' + site.domain + url + return url + + def get_email_subject_line(self): + """forms subject line based on the root message + and prepends 'Re': if message is non-root + """ + subject = self.get_root_message().text[:MAX_SUBJECT_LINE_LENGTH] + if self.root: + subject = _('Re: ') + subject + return subject + + def get_root_message(self): + """returns root message or self + if current message is root + """ + return self.root or self + + def get_recipients_users(self): + """returns query set of users""" + groups = self.recipients.all() + return User.objects.filter( + groups__in=groups + ).exclude( + id=self.sender.id + ).distinct() + + def get_timeline(self): + """returns ordered query set of messages in the thread + with the newest first""" + root = self.get_root_message() + root_qs = Message.objects.filter(id=root.id) + return (root.descendants.all() | root_qs).order_by('-sent_at') + + + def send_email_alert(self): + """signal handler for the message post-save""" + root_message = self.get_root_message() + data = {'messages': self.get_timeline()} + template = get_template('group_messaging/email_alert.html') + body_text = template.render(data) + subject = self.get_email_subject_line() + for user in self.get_recipients_users(): + #todo change url scheme so that all users have the same + #urls within their personal areas of the user profile + #so that we don't need to have loops like this one + thread_url = root_message.get_absolute_url(user) + thread_url = thread_url.replace('&', '&') + #in the template we have a placeholder to be replaced like this: + body_text = body_text.replace('THREAD_URL_HOLE', thread_url) + send_mail( + recipient_list=[user.email,], + subject_line=subject, + body_text=body_text + ) + + + def update_senders_info(self): + """update the contributors info, + meant to be used on a root message only """ - self.root.last_active_at = datetime.datetime.now() - senders_names = self.root.senders_info.split(',') + senders_names = self.senders_info.split(',') if self.sender.username in senders_names: senders_names.remove(self.sender.username) senders_names.insert(0, self.sender.username) - self.root.senders_info = (','.join(senders_names))[:64] - self.root.save() + self.senders_info = (','.join(senders_names))[:64] + self.save() + + def unarchive(self, user=None): + """unarchive message for all recipients""" + archived_filter = {'status': MessageMemo.ARCHIVED} + if user: + archived_filter['user'] = user + memos = self.memos.filter(**archived_filter) + memos.update(status=MessageMemo.SEEN) + + def set_status_for_user(self, status, user): + """set specific status to the message for the user""" + memo, created = MessageMemo.objects.get_or_create(user=user, message=self) + memo.status = status + memo.save() + + def archive(self, user): + """mark message as archived""" + self.set_status_for_user(MessageMemo.ARCHIVED, user) + + def mark_as_seen(self, user): + """mark message as seen""" + self.set_status_for_user(MessageMemo.SEEN, user) |