Django

Code

Ticket #1541 (closed: fixed)

Opened 3 years ago

Last modified 1 year ago

Add multipart message capability to django.core.mail

Reported by: bruce@coderseye.com Assigned to: mtredinnick
Milestone: Component: django.core.mail
Version: Keywords:
Cc: rushman@mail.ru, ross@rossp.org, mssnlayam@yahoo.com Triage Stage: Accepted
Has patch: 1 Needs documentation: 0
Needs tests: 1 Patch needs improvement: 0

Description

I wanted to be able to use the integrated mail functionality to send multipart messages. However, in Python 2.4.1, you cannot simply give send_mail a MIMEMultipart message. It errors, trying to encode. The encoding step is really not needed if you've already constructed a MIMEText or MIMEMultipart message. So, I went ahead and added that functionality by making a "SafeMIMEMultipart" class. If the message parameter is either SafeMIMEMultipart or SafeMIMEtext, it is accepted without re-encoding. Another way to do this would've been to put a "skip_reencode" parameter on the method, but I prefer more transparent solutions.

Eample usage:


msg = mail.SafeMIMEMultipart('alternative', charset="utf-8") msg.attach(mail.SafeMIMEText("example text part", "text")) msg.attach(mail.SafeMIMEText("<p>example <b>html</b> part", "html")) mail.send_mail("example multipart mail", msg, "me@example.com", ["you@example.com",])

Attachments

multipart_mail.diff (2.1 kB) - added by bruce@coderseye.com on 03/26/06 16:25:12.
mail.py patch
mail_attachment.diff (5.8 kB) - added by mssnlayam@yahoo.com on 12/19/06 15:50:08.
mail_attachment_as_str.diff (5.3 kB) - added by mssnlayam@yahoo.com on 12/25/06 15:56:29.
Similar to mail_attachment.diff, but accepts str objects instread of file objects
1541_multipart_unified.diff (3.5 kB) - added by sime <simon@quo.com.au> on 06/13/07 20:14:24.
unified #1541 and #3366 - needs improvement to handle other mimetypes
mail.diff (5.2 kB) - added by nick.lane.au@gmail.com on 06/17/07 20:48:03.
Patch to add attachments to django.core.mail
mail.2.diff (5.6 kB) - added by nick.lane.au@gmail.com on 06/26/07 00:20:06.
Slight change to patch, added docs
mail.3.diff (5.6 kB) - added by nick.lane.au@gmail.com on 06/26/07 00:23:25.
Slight change to patch, added docs (last patch broken)
mail.4.diff (5.8 kB) - added by nick.lane.au@gmail.com on 06/26/07 00:27:31.
Slight change to patch, added docs (last patch broken)

Change History

03/26/06 16:25:12 changed by bruce@coderseye.com

  • attachment multipart_mail.diff added.

mail.py patch

03/26/06 16:27:15 changed by bruce@coderseye.com

Sorry, forgot to hit the "code block" button. Here's try two for that example code.

msg = mail.SafeMIMEMultipart('alternative', charset="utf-8") 
msg.attach(mail.SafeMIMEText("example text part", "text"))
msg.attach(mail.SafeMIMEText("<p>example <b>html</b> part", "html")) 
mail.send_mail("example multipart mail", msg, "me@example.com", ["you@example.com",])

03/28/06 17:04:27 changed by bruce@coderseye.com

  • summary changed from Add multipart message capability to django.core.mail to [patch] Add multipart message capability to django.core.mail.

04/10/06 14:11:19 changed by rushman@mail.ru

Maybe someone can integrate this patch into magic-removal branch?

06/07/06 19:24:12 changed by Helga

  • type changed from enhancement to defect.

06/22/06 12:21:12 changed by test@test.com

  • cc set to qhkpnvflz, nildwaq.
  • keywords changed from smtp, MIMEMultipart to qhkpnvflz nildwaq.
  • summary changed from [patch] Add multipart message capability to django.core.mail to qhkpnvflz nildwaq.

uctqpdosy lrcnde yudgorpah yjaqpcxv toujymqd njqpgsl xudpbf

06/23/06 07:08:09 changed by adrian

  • cc deleted.
  • keywords deleted.
  • version deleted.
  • summary changed from qhkpnvflz nildwaq to [patch] Add multipart message capability to django.core.mail.

06/30/06 11:01:22 changed by Sergey Kirillov <rushman@mail.ru>

  • cc set to rushman@mail.ru.

07/11/06 18:27:24 changed by anonymous

  • cc changed from rushman@mail.ru to rushman@mail.ru, ross@rossp.org.

07/27/06 19:06:27 changed by jacob

  • owner changed from adrian to jacob.
  • status changed from new to assigned.

This is great, and I want to check it in. However, it needs docs added to mail.txt first.

11/08/06 02:33:36 changed by prom dresses

11/24/06 17:21:12 changed by Sergey <rushman@mail.ru>

Does anybody knows when this patch will be integrated into trunk?

11/26/06 18:33:02 changed by adrian

  • component changed from Core framework to django.core.mail.

12/19/06 15:49:30 changed by mssnlayam@yahoo.com

  • cc changed from rushman@mail.ru, ross@rossp.org to rushman@mail.ru, ross@rossp.org, mssnlayam@yahoo.com.

I am attaching a patch that adds multipart message capability. This is slightly different from multipart_mail.diff, in that Django creates the individual mime parts and attaches them.

Example

    attachments = [
        ('image.png', open('/home/user/image.png', 'rb')),
        ('audio.mp3', open('/home/user/audio.mp3', 'rb')),
    ]
    send_mail(subject, body, sender, recipients, attachments=attachments)

12/19/06 15:50:08 changed by mssnlayam@yahoo.com

  • attachment mail_attachment.diff added.

12/19/06 15:56:20 changed by mssnlayam@yahoo.com

Forgot to add that send_mass_mail() might not work as expected, when attachments is reused for multiple datatuples.

12/25/06 15:56:29 changed by mssnlayam@yahoo.com

  • attachment mail_attachment_as_str.diff added.

Similar to mail_attachment.diff, but accepts str objects instread of file objects

01/15/07 23:08:31 changed by russellm

#3307 has a competing version of this feature, along with some other details.

01/24/07 23:04:01 changed by Gary Wilson <gary.wilson@gmail.com>

  • stage changed from Unreviewed to Accepted.

Replying to ubernostrum from #3307:

Hmm. I thought I'd searched on this previously and not found anything, but I guess I was wrong. #1541 has a safer implementation of the multipart stuff.

(follow-up: ↓ 18 ) 01/24/07 23:10:07 changed by Gary Wilson <gary.wilson@gmail.com>

Can someone please test the patch. Also with adding all these new features to an email here and in #3307, it seems like the datatuple is becoming smelly. Perhaps some sort of EmailMessage class storing all these different data pieced as attributes?

(in reply to: ↑ 17 ) 01/24/07 23:21:32 changed by Gary Wilson <gary.wilson@gmail.com>

Replying to Gary Wilson <gary.wilson@gmail.com>:

Perhaps some sort of EmailMessage class storing all these different data pieced as attributes?

Created #3366.

01/25/07 14:00:04 changed by mssnlayam@yahoo.com

I would like the third patch to be applied and this bug closed. We can start working on #3366, but that should not keep this bug waiting.

01/30/07 11:45:47 changed by Gary Wilson <gary.wilson@gmail.com>

  • needs_docs set to 1.

Also, maybe the Python bug mentioned in #3104 should be added as a note in the documentation of this new feature.

01/30/07 11:47:27 changed by Gary Wilson <gary.wilson@gmail.com>

  • needs_docs deleted.

Actually, I guess #3104 is only for multipart form uploads, sorry.

02/02/07 23:08:49 changed by Gary Wilson <gary.wilson@gmail.com>

  • stage changed from Accepted to Ready for checkin.

Jacob mentioned that the only thing holding this back was docs, and docs are now here.

(follow-up: ↓ 24 ) 02/03/07 19:19:05 changed by russellm

  • stage changed from Ready for checkin to Design decision needed.

This ticket contains three competing implementations of the same feature, with another option on #3307. The version that was initially accepted by Jacob still isn't documented. There needs to be some public discussion on which option to adopt, and how that choice integrates with #3366.

(in reply to: ↑ 23 ) 02/03/07 19:26:35 changed by mssnlayam@yahoo.com

Replying to russellm:

This ticket contains three competing implementations of the same feature, with another option on #3307. The version that was initially accepted by Jacob still isn't documented. There needs to be some public discussion on which option to adopt, and how that choice integrates with #3366.

Mails to the developers mailing list have not induced a discussion. What should be the plan of action if there is no or insufficient response?

(follow-up: ↓ 27 ) 02/03/07 19:47:22 changed by bruce@coderseye.com

I am the original author of this patch, which has now grown into a tangle. I'd be willing to consolidate the various patches, as I don't think they are really conflicting all that much.

The combined version would:

1) Use the idea from #3366, making an EmailMessage? class, which is really just an extension of the "SafeMimeMultipart?" I made in the original patch.

2) Add the bcc_list idea from #3307

3) Add the convenience method for adding a list of attachments at once.

How does that sound?

02/03/07 19:54:40 changed by russellm

@bruce

Sounds like a good approach to me. Show me the code! :-)

(in reply to: ↑ 25 ; follow-up: ↓ 28 ) 02/03/07 19:58:32 changed by anonymous

Replying to bruce@coderseye.com:

How does that sound?

Sounds good. I have a few concerns/comments. I prefer an approach where I do not have to know what SafeMIMEMultipart is. It is not clear how the EmailMessage? class will maintain backward compatibility. We will be spending a lot of time accepting an interface and implementation for EmailMessage?, and have to disturb everyone when that patch is applied. Not many people are interested, this is not newforms ;) I'd recommend applying patch 3 now and after that, working on #3366.

(in reply to: ↑ 27 ) 02/07/07 22:15:58 changed by Gary Wilson <gary.wilson@gmail.com>

Replying to anonymous:

It is not clear how the EmailMessage? class will maintain backward compatibility.

Well, it's not completely necessary to maintain backward compatibility, as it's not guaranteed until 1.0.

We will be spending a lot of time accepting an interface and implementation for EmailMessage, and have to disturb everyone when that patch is applied. Not many people are interested, this is not newforms ;) I'd recommend applying patch 3 now and after that, working on #3366.

The patch for this ticket maintains backwards compatibility since it allows for a variable length datatuple, but what happens when it's time to add the features from other tickets? The interface would probably have to be changed anyway. If the interface is fixed first (with EmailMessage in #3366?), then all these features from the various tickets could be added afterwards (separately or together) without breaking anything.

06/13/07 20:12:57 changed by sime <simon@quo.com.au>

  • needs_better_patch set to 1.
  • stage changed from Design decision needed to Unreviewed.
  • needs_tests set to 1.
  • needs_docs set to 1.

Well folks here is a quick mash of Bruce's #1541 and Gary Wilson's #3366 patch, against rev 5468. Plus a convenience parameter for sending HTML.

I think all we need now, is to make it handle images and other mime types transparently too. I haven't played with that stuff in python before and have no immediate need right now - anyone else keen?

06/13/07 20:14:24 changed by sime <simon@quo.com.au>

  • attachment 1541_multipart_unified.diff added.

unified #1541 and #3366 - needs improvement to handle other mimetypes

06/13/07 20:32:30 changed by lukeplant

Using the new EmailMessage? system, we can already implement attachments without much duplication and without patching the source. I've had to do it just now, and below is the code I came up with (based on the code in the patch). Only EmailWithAttachments?.message() has any significant duplication of core code.

I'm not sure what further work on changing the EmailMessage? class and send_mail function is planned. In case nothing further is done, or in case it takes a while, the code below may help other people. (NB, I haven't tested it massively, it works for my usage).

from django.core.mail import EmailMessage, SMTPConnection, SafeMIMEText, Header, BadHeaderError, formatdate, make_msgid
from email import Encoders
from email.MIMEBase import MIMEBase
from email.MIMEMultipart import MIMEMultipart
from django.conf import settings

"""
Utilities for sending email with attachments
"""

class SafeMIMEMultipart(MIMEMultipart): 
    def __setitem__(self, name, val): 
        "Forbids multi-line headers, to prevent header injection." 
        if '\n' in val or '\r' in val: 
            raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name) 
        if name == "Subject": 
            val = Header(val, settings.DEFAULT_CHARSET) 
        MIMEMultipart.__setitem__(self, name, val) 
 
    def attachFile(self, filename, content, mimetype): 
        maintype, subtype = mimetype.split('/', 1) 
        msg = MIMEBase(maintype, subtype) 
        msg.set_payload(content) 
        Encoders.encode_base64(msg) 
        msg.add_header('Content-Disposition', 'attachment', filename=filename) 
        MIMEMultipart.attach(self, msg) 

class EmailWithAttachments(EmailMessage):
    def __init__(self, *args, **kwargs):
        attachments = kwargs.pop('attachments', None)
        super(EmailWithAttachments, self).__init__(*args, **kwargs)
        self.attachments = attachments

    def message(self):
        simple_msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
        if self.attachments:
            # This is a multipart mail
            msg = SafeMIMEMultipart()
            # First the body
            msg.attach(simple_msg)
            # Then the various files to be attached.
            for (filename, content, mimetype) in self.attachments:
                msg.attachFile(filename, content, mimetype)
        else:
            msg = simple_msg
        
        msg['Subject'] = self.subject
        msg['From'] = self.from_email
        msg['To'] = ', '.join(self.to)
        msg['Date'] = formatdate()
        msg['Message-ID'] = make_msgid()
        if self.bcc:
            msg['Bcc'] = ', '.join(self.bcc)
        return msg

def send_mail_with_attachments(subject, message, from_email, recipient_list, 
                              fail_silently=False, auth_user=None, auth_password=None, attachments=None): 
    connection = SMTPConnection(username=auth_user, password=auth_password,
                                 fail_silently=fail_silently)
    return EmailWithAttachments(subject, message, from_email, recipient_list,
                                 connection=connection, attachments=attachments).send()

06/13/07 21:07:09 changed by sime <simon@quo.com.au>

Looks good, just need to get this stuff into core. Multipart, file attachments, transparent HTML, and possibly even a convenience function for template/context merging.

06/17/07 20:48:03 changed by nick.lane.au@gmail.com

  • attachment mail.diff added.

Patch to add attachments to django.core.mail

06/17/07 20:50:12 changed by nick.lane.au@gmail.com

I have a similar patch, which I improved a bit using lukeplant's example, which allows you to add attachments to a EmailMessage? object. I also added a render_to_mail shortcut:

def render_to_mail(*args, **kwargs):
    return EmailMessage(body=loader.render_to_string(*args, **kwargs))

So my typical usage is:

m = render_to_mail('emails/foo.html', {'var': 'bar'})
m.attach_file('files/some_file.pdf')
m.subject = 'Here is your file'
m.to = ['nick.lane.au@gmail.com']
m.send()

06/20/07 21:51:18 changed by SmileyChris

  • stage changed from Unreviewed to Design decision needed.

06/26/07 00:20:06 changed by nick.lane.au@gmail.com

  • attachment mail.2.diff added.

Slight change to patch, added docs

06/26/07 00:23:25 changed by nick.lane.au@gmail.com

  • attachment mail.3.diff added.

Slight change to patch, added docs (last patch broken)

06/26/07 00:27:31 changed by nick.lane.au@gmail.com

  • attachment mail.4.diff added.

Slight change to patch, added docs (last patch broken)

06/26/07 00:35:00 changed by nick.lane.au@gmail.com

Sorry about the bad patches... I swore the last one would work. I'll have to wait a week till I'm back at home and can do a proper patch against SVN. I can also add in the tests I have and make sure they work against SVN too.

Feel free to remove mail.2.diff -> mail.4.diff.

06/26/07 00:52:41 changed by mtredinnick

  • status changed from assigned to new.
  • needs_better_patch deleted.
  • summary changed from [patch] Add multipart message capability to django.core.mail to Add multipart message capability to django.core.mail.
  • owner changed from jacob to mtredinnick.
  • needs_docs deleted.
  • stage changed from Design decision needed to Accepted.

No need to worry about updating the patches any further, Nick. I'm partway through merging them into trunk. Using most of your design, with only a few small tweaks. Should have something committed in the next day or two.

06/27/07 04:44:57 changed by mtredinnick

  • status changed from new to closed.
  • resolution set to fixed.

(In [5547]) Fixed #1541 -- Added ability to create multipart email messages. Thanks, Nick Lane.


Add/Change #1541 (Add multipart message capability to django.core.mail)




Change Properties
Action