summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-03-15 01:31:29 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-03-15 01:31:29 -0400
commit6e44222a68bc9edb506ab6598621532cbe6f5ced (patch)
tree91f5a2f1606640dfa7304858c159445f009eaa88
parentf7c79a660e63955c05ddc32f1dfb3e175487b4f4 (diff)
downloadaskbot-6e44222a68bc9edb506ab6598621532cbe6f5ced.tar.gz
askbot-6e44222a68bc9edb506ab6598621532cbe6f5ced.tar.bz2
askbot-6e44222a68bc9edb506ab6598621532cbe6f5ced.zip
a first pass on accepting file attachments in email responses
-rw-r--r--askbot/lamson_handlers.py73
-rw-r--r--askbot/models/reply_by_email.py20
-rw-r--r--askbot/tests/reply_by_email_tests.py4
-rw-r--r--askbot/utils/file_utils.py35
-rw-r--r--askbot/views/writers.py28
5 files changed, 125 insertions, 35 deletions
diff --git a/askbot/lamson_handlers.py b/askbot/lamson_handlers.py
index 899ee47a..8e2d2ce6 100644
--- a/askbot/lamson_handlers.py
+++ b/askbot/lamson_handlers.py
@@ -1,13 +1,10 @@
import re
-import logging
-from lamson.routing import route, route_like, stateless
+from lamson.routing import route, stateless
from lamson.server import Relay
from django.utils.translation import ugettext as _
from askbot.models import ReplyAddress
-from askbot.conf import settings as askbot_settings
from django.conf import settings
-
-
+from StringIO import StringIO
#we might end up needing to use something like this
@@ -39,11 +36,71 @@ def _strip_message_qoute(message_text):
return result
"""
+def get_dispositions(part):
+ """return list of part's content dispositions
+ or an empty list
+ """
+ disposition_hdr = part.get('Content-Disposition', None)
+ if disposition_hdr:
+ dispositions = disposition_hdr.strip().split(';')
+ return [disp.lower() for disp in dispositions]
+ else:
+ return list()
+
+def is_attachment(part):
+ """True if part content disposition is
+ attachment"""
+ dispositions = get_dispositions(part)
+ if len(dispositions) == 0:
+ return False
+
+ if dispositions[0] == 'attachment':
+ return True
+
+ return False
+
+def process_attachment(part):
+ """takes message part and turns it into StringIO object"""
+ file_data = part.get_payload(decode = True)
+ att = StringIO(file_data)
+ att.content_type = part.get_content_type()
+ att.size = len(file_data)
+ att.name = None#todo figure out file name
+ att.create_date = None
+ att.mod_date = None
+ att.read_date = None
+
+ dispositions = get_dispositions(part)[:1]
+ for disp in dispositions:
+ name, value = disp.split('=')
+ if name == 'filename':
+ att.name = value
+ elif name == 'create-date':
+ att.create_date = value
+ elif name == 'modification-date':
+ att.modification_date = value
+ elif name == 'read-date':
+ att.read_date = value
+
+ return att
+
+def get_attachments(message):
+ """returns a list of file attachments
+ represented by StringIO objects"""
+ attachments = list()
+ for part in message.walk():
+ if is_attachment(part):
+ attachments.append(process_attachment(part))
+ return attachments
+
@route("(address)@(host)", address=".+")
@stateless
def PROCESS(message, address = None, host = None):
+ """handler to process the emailed message
+ and make a post to askbot based on the contents of
+ the email, including the text body and the file attachments"""
try:
for rule in settings.LAMSON_FORWARD:
if re.match(rule['pattern'], message.base['to']):
@@ -59,6 +116,7 @@ def PROCESS(message, address = None, host = None):
reply_address = ReplyAddress.objects.get_unused(address, message.From)
separator = _("======= Reply above this line. ====-=-=")
parts = message.body().split(separator)
+ attachments = get_attachments(message)
if len(parts) != 2 :
error = _("Your message was malformed. Please make sure to qoute \
the original notification you received at the end of your reply.")
@@ -66,7 +124,10 @@ def PROCESS(message, address = None, host = None):
reply_part = parts[0]
reply_part = '\n'.join(reply_part.splitlines(True)[:-3])
#the function below actually posts to the forum
- reply_address.create_reply(reply_part.strip())
+ reply_address.create_reply(
+ reply_part.strip(),
+ attachments = attachments
+ )
except ReplyAddress.DoesNotExist:
error = _("You were replying to an email address\
unknown to the system or you were replying from a different address from the one where you\
diff --git a/askbot/models/reply_by_email.py b/askbot/models/reply_by_email.py
index 0653f684..936c3ded 100644
--- a/askbot/models/reply_by_email.py
+++ b/askbot/models/reply_by_email.py
@@ -1,17 +1,13 @@
from datetime import datetime
import random
import string
-
from django.db import models
from django.contrib.auth.models import User
-
-
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
-
-
class ReplyAddressManager(BaseQuerySetManager):
def get_unused(self, address, allowed_from_email):
@@ -42,12 +38,22 @@ class ReplyAddress(models.Model):
app_label = 'askbot'
db_table = 'askbot_replyaddress'
- def create_reply(self, content):
+ def create_reply(self, content, attachments = None):
result = None
+
+ if attachments:
+ #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
+ for att in attachments:
+ file_storage, file_name, file_url = store_file(att)
+ result += '[%s](%s) ' % (att.name, file_url)
+
if self.post.post_type == 'answer':
result = self.user.post_comment(self.post, content)
elif self.post.post_type == 'question':
- wordcount = len(content)/6
+ wordcount = len(content)/6#this is a simplistic hack
if wordcount > askbot_settings.MIN_WORDS_FOR_ANSWER_BY_EMAIL:
result = self.user.post_answer(self.post, content)
else:
diff --git a/askbot/tests/reply_by_email_tests.py b/askbot/tests/reply_by_email_tests.py
index 76097362..5128c9e7 100644
--- a/askbot/tests/reply_by_email_tests.py
+++ b/askbot/tests/reply_by_email_tests.py
@@ -15,6 +15,10 @@ class MockMessage(object):
def body(self):
return self._body
+ def walk(self):
+ """todo: add real file attachment"""
+ return list()
+
class EmailProcessingTests(AskbotTestCase):
def setUp(self):
diff --git a/askbot/utils/file_utils.py b/askbot/utils/file_utils.py
new file mode 100644
index 00000000..daca1522
--- /dev/null
+++ b/askbot/utils/file_utils.py
@@ -0,0 +1,35 @@
+"""file utilities for askbot"""
+import os
+import random
+import time
+import urlparse
+from django.core.files.storage import get_storage_class
+
+def store_file(file_object):
+ """Creates an instance of django's file storage
+ object based on the file-like object,
+ returns the storage object, file name, file url
+ """
+ file_name = str(
+ time.time()
+ ).replace(
+ '.',
+ str(random.randint(0,100000))
+ ) + os.path.splitext(file_object.name)[1].lower()
+
+ file_storage = get_storage_class()()
+ # use default storage to store file
+ file_storage.save(file_name, file_object)
+
+ file_url = file_storage.url(file_name)
+ parsed_url = urlparse.urlparse(file_url)
+ file_url = urlparse.urlunparse(
+ urlparse.ParseResult(
+ parsed_url.scheme,
+ parsed_url.netloc,
+ parsed_url.path,
+ '', '', ''
+ )
+ )
+
+ return file_storage, file_name, file_url
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index 0ee4b7ef..4b1ae744 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -13,7 +13,6 @@ import sys
import tempfile
import time
import urlparse
-from django.core.files.storage import get_storage_class
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
@@ -31,6 +30,7 @@ from askbot.skins.loaders import render_into_skin
from askbot.utils import decorators
from askbot.utils.functions import diff_date
from askbot.utils import url_utils
+from askbot.utils.file_utils import store_file
from askbot.templatetags import extra_filters_jinja as template_filters
from askbot.importers.stackexchange import management as stackexchange#todo: may change
@@ -64,6 +64,9 @@ def upload(request):#ajax upload file to a question or answer
# check file type
f = request.FILES['file-upload']
+
+ #todo: extension checking should be replaced with mimetype checking
+ #and this must be part of the form validation
file_extension = os.path.splitext(f.name)[1].lower()
if not file_extension in settings.ASKBOT_ALLOWED_UPLOAD_FILE_TYPES:
file_types = "', '".join(settings.ASKBOT_ALLOWED_UPLOAD_FILE_TYPES)
@@ -71,17 +74,8 @@ def upload(request):#ajax upload file to a question or answer
{'file_types': file_types}
raise exceptions.PermissionDenied(msg)
- # generate new file name
- new_file_name = str(
- time.time()
- ).replace(
- '.',
- str(random.randint(0,100000))
- ) + file_extension
-
- file_storage = get_storage_class()()
- # use default storage to store file
- file_storage.save(new_file_name, f)
+ # generate new file name and storage object
+ file_storage, new_file_name, file_url = store_file(f)
# check file size
# byte
size = file_storage.size(new_file_name)
@@ -99,16 +93,6 @@ def upload(request):#ajax upload file to a question or answer
if error == '':
result = 'Good'
- file_url = file_storage.url(new_file_name)
- parsed_url = urlparse.urlparse(file_url)
- file_url = urlparse.urlunparse(
- urlparse.ParseResult(
- parsed_url.scheme,
- parsed_url.netloc,
- parsed_url.path,
- '', '', ''
- )
- )
else:
result = ''
file_url = ''