Django - Attach PDF generated by a view to an email - django

This question has some elements here, but no final answer.
There is view generating a PDF with easy_pdf
from easy_pdf.views import PDFTemplateResponseMixin
class PostPDFDetailView(PDFTemplateResponseMixin,DetailView):
model = models.Post
template_name = 'post/post_pdf.html'
Then, I want to attach this generated PDF to the following email :
#receiver(post_save, sender=Post)
def first_mail(sender, instance, **kwargs):
if kwargs['created']:
user_email = instance.client.email
subject, from_email, to = 'New account', 'contact#example.com', user_email
post_id = str(instance.id)
domain = Site.objects.get_current().domain
post_pdf = domain + '/post/' + post_id + '.pdf'
text_content = render_to_string('post/mail_post.txt')
html_content = render_to_string('post/mail_post.html')
# create the email, and attach the HTML version as well.
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.attach_file(post_pdf, 'application/pdf')
msg.send()
I also have tried this one:
msg.attach_file(domain + '/post/' + post_id + '.pdf', 'application/pdf')

I was looking for a way to attach an easy_pdf generated PDF without saving a temp file as well. Since I couldn't find a solution elsewhere, I suggest a short and working proposal using easy_pdf.rendering.render_to_pdf:
from easy_pdf.rendering import render_to_pdf
...
post_pdf = render_to_pdf(
'post/post_pdf.html',
{'any_context_item_to_pass_to_the_template': context_value,},
)
...
msg.attach('file.pdf', post_pdf, 'application/pdf')
I hope it'll help if you are still interested by a way to do this.

Not sure if it helps, but I used EmailMessage built-in to attach PDFs I created for reporting to emails I sent out:
from django.core.mail import send_mail, EmailMessage
draft_email = EmailMessage(
#subject,
#body,
#from_email,
#to_email,
)
Option 1:
# attach a file you have saved to the system... expects the path
draft_email.attach_file(report_pdf.name)
Option 2:
# expects the name of the file object
draft_email.attach("Report.pdf")
Then, sends like you already have:
draft_email.send()
Some initial thoughts: it seems like you are trying to use attach_file to attach a file from the system, but it isn't on the system. I would try using attach instead of attach_file, if I am reading your code correctly, since the pdf is in your memory, not in LTS on the system.

Related

Django - Html tags in email template

I' m using Django signals to send emails when a new record is added:
#receiver(post_save, sender=MeetingMember)
def send_invited_emails(sender, instance, **kwargs):
host = instance.meeting.host
subject = "Mizban invition"
# htmly = get_template('sendingemail.html')
domain = Site.objects.get_current().domain
context={'host':host,'meeting': instance,'domain':domain}
html_content = render_to_string('sendingemail.html',context)
send_mail(subject, html_content, settings.EMAIL_HOST_USER,[instance.email])
This code works but it sends html tags in email as well how can I solve the issue?
The second parameter message is the message you want to display as raw text. You can pass the content of a HTML message through the html_message=… parameter of the send_mail(…) function [Django-doc]:
# …
html_content = render_to_string('sendingemail.html',context)
send_mail(
subject,
html_content,
settings.EMAIL_HOST_USER,[instance.email],
html_message=html_content
)
Usually for the message, one also creates a text variant of the message: one without HTML that can be used by simple email clients to display the email on a console terminal for example.

Django send bulk emails

I am working on a service to send bulk emails in django.
I have this method which is working well with celery
#shared_task(bind=True)
def send_mails(self,saved_id):
text = BroadCast.objects.get(id=saved_id)
attendees = EventAttendee.objects.filter(event__id=text.id)
message = text.body
subject = text.subject
document = text.attachment
recipient_list=[]
for attend in attendees:
text_content = render_to_string(template_text, {'name': attend.client_name, 'message':message})
html_content = render_to_string(template_html, {'name': attend.client_name,'message':message})
mail.send(
[attend.client_email],
email_from,
subject=subject,
html_message=html_content,
attachments = {str(document):document}
)
my challenge is that if i have for examples 1000 attendees, I will have to open 1000 connections which I believe is a very bad.
How can I restructure it so that I only open one connection and be able to send 1000 emails..
From Django's docs
django.core.mail.send_mass_mail() is intended to handle mass emailing.
Since you are sending html, you would need an extra step, consider the following piece of code from this stackoverflow answer:
from django.core.mail import get_connection, EmailMultiAlternatives
def send_mass_html_mail(datatuple, fail_silently=False, user=None, password=None,
connection=None):
"""
Given a datatuple of (subject, text_content, html_content, from_email,
recipient_list), sends each message to each recipient list. Returns the
number of emails sent.
If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
If auth_user and auth_password are set, they're used to log in.
If auth_user is None, the EMAIL_HOST_USER setting is used.
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
"""
connection = connection or get_connection(
username=user, password=password, fail_silently=fail_silently)
messages = []
for subject, text, html, from_email, recipient in datatuple:
message = EmailMultiAlternatives(subject, text, from_email, recipient)
message.attach_alternative(html, 'text/html')
messages.append(message)
return connection.send_messages(messages)
Then you probably want to use send_mass_mail
only one connection to the mail server would be opened
So construct a tuple of messages for all the emails you want to send. (The linked official documentation does a good job explaining usage)

How to send Email with html page in Django?

I'm new in Django ! I don't know how to send email in Django. I refer Django documentation but it didn't help me . I need to send email with html page to different users .In models.py i have two values Name and Email. When i click button ,the html page should be send to appropriate user's Email
There are a lot of different solutions how to send emails in django.
You can use even php, or any scripting language if you feel it's complicated to use only python/django code.
Just an example of email utility from custom email subscription:
email_utility.py:
import logging, traceback
from django.urls import reverse
import requests
from django.template.loader import get_template
from django.utils.html import strip_tags
from django.conf import settings
def send_email(data):
try:
url = "https://api.mailgun.net/v3/<domain-name>/messages"
status = requests.post(
url,
auth=("api", settings.MAILGUN_API_KEY),
data={"from": "YOUR NAME <admin#domain-name>",
"to": [data["email"]],
"subject": data["subject"],
"text": data["plain_text"],
"html": data["html_text"]}
)
logging.getLogger("info").info("Mail sent to " + data["email"] + ". status: " + str(status))
return status
except Exception as e:
logging.getLogger("error").error(traceback.format_exc())
return False
Don't forget to create a token which we will verify when user clicks the confirmation link. Token will be encrypted so that no one can tamper the data.
token = encrypt(email + constants.SEPARATOR + str(time.time()))
Also check this link and this.
Here is a naive exemple to leverage django send_mail:
import smtplib
from django.core.mail import send_mail
from django.utils.html import strip_tags
from django.template.loader import render_to_string
#user will be a queryset like:
users = User.objects.all() # or more specific query
subject = 'Subject'
from_email = 'from#xxx.com'
def send_email_to_users(users,subject,from_email):
full_traceback = []
for user in users:
to = [user.email] # list of people you want to sent mail to.
html_content = render_to_string('mail_template.html', {'title':'My Awesome email title', 'content' : 'Some email content', 'username':user.username}) # render with dynamic context you can retrieve in the html file
traceback = {}
try:
send_mail(subject,strip_tags(html_content),from_email, to, html_message=html_content, fail_silently=False)
traceback['status'] = True
except smtplib.SMTPException as e:
traceback['error'] = '%s (%s)' % (e.message, type(e))
traceback['status'] = False
full_traceback.append(traceback)
errors_to_return = []
error_not_found = []
for email in full_traceback:
if email['status']:
error_not_found.append(True)
else:
error_not_found.append(False)
errors_to_return.append(email['error'])
if False in error_not_found:
error_not_found = False
else:
error_not_found = True
return (error_not_found, errors_to_return)
#really naive view using the function on top
def my_email_view(request,user_id):
user = get_object_or_404(User, pk=user_id)
subject = 'Subject'
from_email = 'myemail#xxx.com'
email_sent, traceback = send_email_to_users(user, subject, from_email)
if email_sent:
return render(request,'sucess_template.html')
return render(request,'fail_template.html',{'email_errors' : traceback})
In your template mail_template.html:
<h1>{{title}}</h1>
<p>Dear {{username}},</p>
<p>{{content}}</p>
And don't forget to set the email settings in settings.py: https://docs.djangoproject.com/fr/2.2/ref/settings/#email-backend
Send_mail from docs :https://docs.djangoproject.com/fr/2.2/topics/email/#send-mail
Render_to_string from the doc: https://docs.djangoproject.com/fr/2.2/topics/templates/#django.template.loader.render_to_string

Convert Google Doc to PDF and send email with PDF attachment

I am attempting to convert a Google Doc to a PDF (using the Drive API) and then attaching the file to an email (using the Gmail API).
The script runs, coverts the Google Doc to a PDF, sends an email with the attachment, but the PDF attachment is blank / corrupt.
I suspect the issue is with line: msg.set_payload(fh.read())
The relevant documentation: set_payload and io.Bytes()
Any guidance is greatly appreciated.
import base64
import io
from apiclient.http import MediaIoBaseDownload
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
fileId = '1234'
content_type = 'application/pdf'
response = drive.files().export_media(fileId=fileId, mimeType=content_type)
fh = io.BytesIO()
downloader = MediaIoBaseDownload(fh, response)
done = False
while done is False:
status, done = downloader.next_chunk()
logging.info("Download %d%%." % int(status.progress() * 100))
message = MIMEMultipart()
message['to'] = 'myemail#gmail.com'
message['from'] = 'myemail#gmail.com'
message['subject'] = 'test subject'
msg = MIMEText('test body')
message.attach(msg)
main_type, sub_type = content_type.split('/', 1)
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fh.read()) # i suspect the issue is here
msg.add_header('Content-Disposition', 'attachment', filename='an example file name.pdf')
message.attach(msg)
message_obj = {'raw': base64.urlsafe_b64encode(message.as_string())}
service.users().messages().send(userId="me", body=message_obj).execute()
How about this modification? I think that about the download from Google Drive, your script is correct. So I would like to propose to modify the script for sending an email with the attachment file.
I thought that msg.set_payload(fh.read()) is one of the modification points as you say. So the data retrieved by getvalue() was converted by email.encoders.encode_base64(). And also I modified message_obj.
Modified script:
Please modify as follows.
From:
msg = MIMEText('test body')
message.attach(msg)
main_type, sub_type = content_type.split('/', 1)
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fh.read()) # i suspect the issue is here
msg.add_header('Content-Disposition', 'attachment', filename='an example file name.pdf')
message.attach(msg)
message_obj = {'raw': base64.urlsafe_b64encode(message.as_string())}
service.users().messages().send(userId="me", body=message_obj).execute()
To:
from email import encoders # Please add this.
msg = MIMEText('test body')
message.attach(msg)
main_type, sub_type = content_type.split('/', 1)
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fh.getvalue()) # Modified
encoders.encode_base64(msg) # Added
msg.add_header('Content-Disposition', 'attachment', filename='an example file name.pdf')
message.attach(msg)
message_obj = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()} # Modified
service.users().messages().send(userId="me", body=message_obj).execute()
Note:
This modification supposes that you have already been able to send emails using Gmail API. If you cannot use Gmail API, please confirm the scopes and whether Gmail API is enabled at API console.
In my environment, I could confirm that the modified script worked. But if this didn't work in your environment, I apologize.

Delaying the sending emails with Sendgrid and Django, SMTP api and send_at header

I've spent a lot of time figuring out how to send email at a specified time in Django, so I am posting it with answer here to save others some time.
My use case is sending email during working hours. Using celery for that is a bad idea. But Sendgrid can send emails with a delay of up to 3 days. That's what we need.
That what I made:
from django.core.mail import EmailMultiAlternatives
from django.template.context import Context
from django.template.loader import get_template
from smtpapi import SMTPAPIHeader
def send_email(subject, template_name, context, to, bcc=None, from_email=settings.DEFAULT_FROM_EMAIL, send_at=None):
header = SMTPAPIHeader()
body = get_template(template_name).render(Context(context))
if send_at:
send_at = {"send_at": send_at}
header.set_send_at(send_at)
email = EmailMultiAlternatives(
subject=subject,
body=body,
from_email=from_email,
to=to,
bcc=bcc,
headers={'X-SMTPAPI': header.json_string()}
)
email.attach_alternative(body, 'text/html')
email.send()
Don't forget to set it in header X-SMTPAPI cause I couldn't find it anywhere..
And send_at should be a timestamp
Also here you could see how to add headers or anything but with sendgrid.SendGridClient:
https://sendgrid.com/docs/Utilities/code_workshop.html/scheduling_parameters.html
import sendgrid
...
sg = sendgrid.SendGridClient('apiKey')
message = sendgrid.Mail()
message.add_to('John Doe <example#mailinator.com>')
message.set_subject('Example')
message.set_html('Body')
message.set_text('Body')
message.set_from('Doe John <example#example.com>')
message.smtpapi.set_send_at(timestamp)
sg.send(message)
send_at = {"send_at": send_at} produces X-SMTPAPI: {"send_at": {"send_at": 1643934600}} when you print the headers.
Instead, just use send_at = send_at or you can as well delete the line entirely.