diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-03-16 03:05:58 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-03-16 03:05:58 -0400 |
commit | fb7de73d27d28378028aa2ae5d8e7b969865211b (patch) | |
tree | 23721494c225c19e11ea7a081bfaf392cb84780f | |
parent | 731dfdf5d343f1599597d45fad2128d4da2ea55f (diff) | |
download | askbot-fb7de73d27d28378028aa2ae5d8e7b969865211b.tar.gz askbot-fb7de73d27d28378028aa2ae5d8e7b969865211b.tar.bz2 askbot-fb7de73d27d28378028aa2ae5d8e7b969865211b.zip |
first pass for posting question by email via Lamson
-rw-r--r-- | askbot/lamson_handlers.py | 17 | ||||
-rw-r--r-- | askbot/management/commands/post_emailed_questions.py | 123 | ||||
-rw-r--r-- | askbot/models/reply_by_email.py | 34 | ||||
-rw-r--r-- | askbot/utils/mail.py | 174 |
4 files changed, 183 insertions, 165 deletions
diff --git a/askbot/lamson_handlers.py b/askbot/lamson_handlers.py index 1d2cb220..ee37ff0b 100644 --- a/askbot/lamson_handlers.py +++ b/askbot/lamson_handlers.py @@ -2,9 +2,10 @@ import re from lamson.routing import route, stateless from lamson.server import Relay from django.utils.translation import ugettext as _ -from askbot.models import ReplyAddress -from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile +from django.conf import settings +from askbot.models import ReplyAddress +from askbot.utils import mail #we might end up needing to use something like this @@ -90,9 +91,19 @@ def get_attachments(message): attachments.append(process_attachment(part)) return attachments +@route('ask@(host)') +@stateless +def ASK(message, host = None): + body = get_body(message) + attachments = get_attachments(message) + subject = '[test; one; two;] this is a question title'#get_subject(message) + from_address = message.From + import pdb + pdb.set_trace() + mail.process_emailed_question(from_address, subject, body, attachments) -@route("(address)@(host)", address=".+") +@route('(address)@(host)', address='.+') @stateless def PROCESS(message, address = None, host = None): """handler to process the emailed message diff --git a/askbot/management/commands/post_emailed_questions.py b/askbot/management/commands/post_emailed_questions.py index 0a038b62..cc77b57f 100644 --- a/askbot/management/commands/post_emailed_questions.py +++ b/askbot/management/commands/post_emailed_questions.py @@ -21,75 +21,14 @@ import quopri import base64 from django.conf import settings as django_settings from django.core.management.base import NoArgsCommand, CommandError -from django.core import exceptions -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import string_concat -from django.core.urlresolvers import reverse from askbot.conf import settings as askbot_settings from askbot.utils import mail -from askbot.utils import url_utils -from askbot import models -from askbot.forms import AskByEmailForm - -USAGE = _( -"""<p>To ask by email, please:</p> -<ul> - <li>Format the subject line as: [Tag1; Tag2] Question title</li> - <li>Type details of your question into the email body</li> -</ul> -<p>Note that tags may consist of more than one word, and tags -may be separated by a semicolon or a comma</p> -""" -) - -def bounce_email(email, subject, reason = None, body_text = None): - """sends a bounce email at address ``email``, with the subject - line ``subject``, accepts several reasons for the bounce: - - * ``'problem_posting'``, ``unknown_user`` and ``permission_denied`` - * ``body_text`` in an optional parameter that allows to append - extra text to the message - """ - if reason == 'problem_posting': - error_message = _( - '<p>Sorry, there was an error posting your question ' - 'please contact the %(site)s administrator</p>' - ) % {'site': askbot_settings.APP_SHORT_NAME} - error_message = string_concat(error_message, USAGE) - elif reason == 'unknown_user': - error_message = _( - '<p>Sorry, in order to post questions on %(site)s ' - 'by email, please <a href="%(url)s">register first</a></p>' - ) % { - 'site': askbot_settings.APP_SHORT_NAME, - 'url': url_utils.get_login_url() - } - elif reason == 'permission_denied': - error_message = _( - '<p>Sorry, your question could not be posted ' - 'due to insufficient privileges of your user account</p>' - ) - else: - raise ValueError('unknown reason to bounce an email: "%s"' % reason) - - if body_text != None: - error_message = string_concat(error_message, body_text) - - #print 'sending email' - #print email - #print subject - #print error_message - mail.send_mail( - recipient_list = (email,), - subject_line = 'Re: ' + subject, - body_text = error_message - ) class CannotParseEmail(Exception): """This exception will bounce the email""" - def __init__(self, email, subject): + def __init__(self, email_address, subject): super(CannotParseEmail, self).__init__() - bounce_email(email, subject, reason = 'problem_posting') + mail.bounce_email(email_address, subject, reason = 'problem_posting') def parse_message(msg): """returns a tuple @@ -105,7 +44,7 @@ def parse_message(msg): if isinstance(msg, list): raise CannotParseEmail(sender, subject) - ctype = msg.get_content_type()#text/plain only + #ctype = msg.get_content_type()#text/plain only raw_body = msg.get_payload()#text/plain only encoding = msg.get('Content-Transfer-Encoding') if encoding == 'base64': @@ -118,6 +57,7 @@ def parse_message(msg): class Command(NoArgsCommand): + """the django management command class""" def handle_noargs(self, **options): """reads all emails from the INBOX of imap server and posts questions based on @@ -145,7 +85,7 @@ class Command(NoArgsCommand): imap.select('INBOX') #get message ids - status, ids = imap.search(None, 'ALL') + junk, ids = imap.search(None, 'ALL') if len(ids[0].strip()) == 0: #with empty inbox - close and exit @@ -154,58 +94,19 @@ class Command(NoArgsCommand): return #for each id - read a message, parse it and post a question - for id in ids[0].split(' '): - t, data = imap.fetch(id, '(RFC822)') - message_body = data[0][1] + for msg_id in ids[0].split(' '): + junk, data = imap.fetch(msg_id, '(RFC822)') + #message_body = data[0][1] msg = email.message_from_string(data[0][1]) - imap.store(id, '+FLAGS', '\\Deleted') + imap.store(msg_id, '+FLAGS', '\\Deleted') try: (sender, subject, body) = parse_message(msg) - except CannotParseEmail, e: + except CannotParseEmail: continue - data = { - 'sender': sender, - 'subject': subject, - 'body_text': body - } - form = AskByEmailForm(data) - print data - if form.is_valid(): - email_address = form.cleaned_data['email'] - try: - user = models.User.objects.get( - email__iexact = email_address - ) - except models.User.DoesNotExist: - bounce_email(email_address, subject, reason = 'unknown_user') - except models.User.MultipleObjectsReturned: - bounce_email(email_address, subject, reason = 'problem_posting') - tagnames = form.cleaned_data['tagnames'] - title = form.cleaned_data['title'] - body_text = form.cleaned_data['body_text'] + sender = mail.extract_first_email_address(sender) + mail.process_emailed_question(sender, subject, body) - try: - user.post_question( - title = title, - tags = tagnames, - body_text = body_text - ) - except exceptions.PermissionDenied, e: - bounce_email( - email_address, - subject, - reason = 'permission_denied', - body_text = unicode(e) - ) - else: - email_address = mail.extract_first_email_address(sender) - if email_address: - bounce_email( - email_address, - subject, - reason = 'problem_posting' - ) imap.expunge() imap.close() imap.logout() diff --git a/askbot/models/reply_by_email.py b/askbot/models/reply_by_email.py index 62242367..26c53999 100644 --- a/askbot/models/reply_by_email.py +++ b/askbot/models/reply_by_email.py @@ -1,35 +1,13 @@ from datetime import datetime import random import string -import os from django.db import models from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ from askbot.models.post import Post from askbot.models.base import BaseQuerySetManager -from askbot.utils.file_utils import store_file from askbot.conf import settings as askbot_settings -from django.utils.translation import ugettext_lazy as _ - -def process_attachments(attachments): - """saves file attachments and adds - - cheap way of dealing with the attachments - just insert them inline, however it might - be useful to keep track of the uploaded files separately - and deal with them as with resources of their own value""" - if attachments: - content = '' - for att in attachments: - file_storage, file_name, file_url = store_file(att) - chunk = '[%s](%s) ' % (att.name, file_url) - file_extension = os.path.splitext(att.name) - #todo: this is a hack - use content type - if file_extension.lower() in ('png', 'jpg', 'gif'): - chunk = '\n\n!' + chunk - content += '\n\n' + chunk - return content - else: - return '' +from askbot.utils import mail class ReplyAddressManager(BaseQuerySetManager): @@ -86,7 +64,7 @@ class ReplyAddress(models.Model): """edits the created post upon repeated response to the same address""" assert self.was_used == True - content += process_attachments(attachments) + content += mail.process_attachments(attachments) self.user.edit_post( post = self.response_post, body_text = content, @@ -99,7 +77,7 @@ class ReplyAddress(models.Model): to the user """ result = None - content += process_attachments(attachments) + content += mail.process_attachments(attachments) if self.post.post_type == 'answer': result = self.user.post_comment(self.post, content) @@ -116,7 +94,3 @@ class ReplyAddress(models.Model): self.used_at = datetime.now() self.save() return result - - - - diff --git a/askbot/utils/mail.py b/askbot/utils/mail.py index 005743fc..8b5d2663 100644 --- a/askbot/utils/mail.py +++ b/askbot/utils/mail.py @@ -1,13 +1,19 @@ """functions that send email in askbot these automatically catch email-related exceptions """ +import os import smtplib import logging from django.core import mail from django.conf import settings as django_settings -from askbot.conf import settings as askbot_settings +from django.core.exceptions import PermissionDenied +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import string_concat from askbot import exceptions from askbot import const +from askbot.conf import settings as askbot_settings +from askbot.utils import url_utils +from askbot.utils.file_utils import store_file #todo: maybe send_mail functions belong to models #or the future API def prefix_the_subject_line(subject): @@ -32,38 +38,40 @@ def extract_first_email_address(text): return None def thread_headers(post, orig_post, update): + """modify headers for email messages, so + that emails appear as threaded conversations in gmail""" suffix_id = django_settings.SERVER_EMAIL if update == const.TYPE_ACTIVITY_ASK_QUESTION: - id = "NQ-%s-%s" % (post.id, suffix_id) - headers = {'Message-ID': id} + msg_id = "NQ-%s-%s" % (post.id, suffix_id) + headers = {'Message-ID': msg_id} elif update == const.TYPE_ACTIVITY_ANSWER: - id = "NA-%s-%s" % (post.id, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': id, + msg_id = "NA-%s-%s" % (post.id, suffix_id) + orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) + headers = {'Message-ID': msg_id, 'In-Reply-To': orig_id} elif update == const.TYPE_ACTIVITY_UPDATE_QUESTION: - id = "UQ-%s-%s-%s" % (post.id, post.last_edited_at, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': id, + msg_id = "UQ-%s-%s-%s" % (post.id, post.last_edited_at, suffix_id) + orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) + headers = {'Message-ID': msg_id, 'In-Reply-To': orig_id} elif update == const.TYPE_ACTIVITY_COMMENT_QUESTION: - id = "CQ-%s-%s" % (post.id, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': id, + msg_id = "CQ-%s-%s" % (post.id, suffix_id) + orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) + headers = {'Message-ID': msg_id, 'In-Reply-To': orig_id} elif update == const.TYPE_ACTIVITY_UPDATE_ANSWER: - id = "UA-%s-%s-%s" % (post.id, post.last_edited_at, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': id, + msg_id = "UA-%s-%s-%s" % (post.id, post.last_edited_at, suffix_id) + orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) + headers = {'Message-ID': msg_id, 'In-Reply-To': orig_id} elif update == const.TYPE_ACTIVITY_COMMENT_ANSWER: - id = "CA-%s-%s" % (post.id, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': id, + msg_id = "CA-%s-%s" % (post.id, suffix_id) + orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) + headers = {'Message-ID': msg_id, 'In-Reply-To': orig_id} else: - # Unknown type -> Can't set headers - return {} + # Unknown type -> Can't set headers + return {} return headers @@ -130,8 +138,132 @@ def mail_moderators( try: mail.send_mail(subject_line, body_text, from_email, recipient_list) - pass except smtplib.SMTPException, error: logging.critical(unicode(error)) if raise_on_failure == True: raise exceptions.EmailNotSent(unicode(error)) + +ASK_BY_EMAIL_USAGE = _( +"""<p>To ask by email, please:</p> +<ul> + <li>Format the subject line as: [Tag1; Tag2] Question title</li> + <li>Type details of your question into the email body</li> +</ul> +<p>Note that tags may consist of more than one word, and tags +may be separated by a semicolon or a comma</p> +""" +) + +def bounce_email(email, subject, reason = None, body_text = None): + """sends a bounce email at address ``email``, with the subject + line ``subject``, accepts several reasons for the bounce: + + * ``'problem_posting'``, ``unknown_user`` and ``permission_denied`` + * ``body_text`` in an optional parameter that allows to append + extra text to the message + """ + if reason == 'problem_posting': + error_message = _( + '<p>Sorry, there was an error posting your question ' + 'please contact the %(site)s administrator</p>' + ) % {'site': askbot_settings.APP_SHORT_NAME} + error_message = string_concat(error_message, ASK_BY_EMAIL_USAGE) + elif reason == 'unknown_user': + error_message = _( + '<p>Sorry, in order to post questions on %(site)s ' + 'by email, please <a href="%(url)s">register first</a></p>' + ) % { + 'site': askbot_settings.APP_SHORT_NAME, + 'url': url_utils.get_login_url() + } + elif reason == 'permission_denied': + error_message = _( + '<p>Sorry, your question could not be posted ' + 'due to insufficient privileges of your user account</p>' + ) + else: + raise ValueError('unknown reason to bounce an email: "%s"' % reason) + + if body_text != None: + error_message = string_concat(error_message, body_text) + + #print 'sending email' + #print email + #print subject + #print error_message + send_mail( + recipient_list = (email,), + subject_line = 'Re: ' + subject, + body_text = error_message + ) + +def process_attachments(attachments): + """saves file attachments and adds + + cheap way of dealing with the attachments + just insert them inline, however it might + be useful to keep track of the uploaded files separately + and deal with them as with resources of their own value""" + if attachments: + content = '' + for att in attachments: + file_storage, file_name, file_url = store_file(att) + chunk = '[%s](%s) ' % (att.name, file_url) + file_extension = os.path.splitext(att.name)[1] + #todo: this is a hack - use content type + if file_extension.lower() in ('png', 'jpg', 'gif'): + chunk = '\n\n!' + chunk + content += '\n\n' + chunk + return content + else: + return '' + + + +def process_emailed_question(from_address, subject, body, attachments = None): + """posts question received by email or bounces the message""" + #a bunch of imports here, to avoid potential circular import issues + from askbot.forms import AskByEmailForm + from askbot.models import User + data = { + 'sender': from_address, + 'subject': subject, + 'body_text': body + } + form = AskByEmailForm(data) + if form.is_valid(): + email_address = form.cleaned_data['email'] + try: + user = User.objects.get( + email__iexact = email_address + ) + except User.DoesNotExist: + bounce_email(email_address, subject, reason = 'unknown_user') + except User.MultipleObjectsReturned: + bounce_email(email_address, subject, reason = 'problem_posting') + + tagnames = form.cleaned_data['tagnames'] + title = form.cleaned_data['title'] + body_text = form.cleaned_data['body_text'] + + try: + body_text += process_attachments(attachments) + user.post_question( + title = title, + tags = tagnames, + body_text = body_text + ) + except PermissionDenied, error: + bounce_email( + email_address, + subject, + reason = 'permission_denied', + body_text = unicode(error) + ) + else: + if email_address: + bounce_email( + email_address, + subject, + reason = 'problem_posting' + ) |