Django mail attachment is blank - python

I am trying to send a mail with an attachment using Django, the attached file is sent to the server from a user submitted form. My code is shown below
form = RequestForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.info(request, '')
subject = ''
message = ""
attachment = request.FILES['attachment']
mail = EmailMessage(subject, message, '', [''])
mail.attach(filename=attachment.name, mimetype=attachment.content_type, content=attachment.read())
mail.send()
I am receiving the mail, but the attachment in the mail is blank, i.e it doesn't contain any content. What am I missing here?

I have solved the issue, I placed the form.save() at the bottom i.e after sending the mail and the issue resolved. This is because, once we use form.save() the attachment gets stored in its path and we need to open it before we read it.
form = RequestForm(request.POST, request.FILES)
if form.is_valid():
messages.info(request, '')
subject = ''
message = ""
attachment = request.FILES['attachment']
mail = EmailMessage(subject, message, '', [''])
mail.attach(filename=attachment.name, mimetype=attachment.content_type, content=attachment.read())
mail.send()
form.save()

I believe you need to use attach_file instead of attach. attach_file allows you to pass a path, whereas you need to pass the actual data with attach. See docs.
Also, test that your attachment is actually getting uploaded, that you specified the right enctype on your form, etc. For example:
<form enctype="multipart/form-data" action="/whatever/" method="post">

Related

Django mail attachments blank except for 1st email sent, when looping through recipients

I have a newsletter being sent out using Django forms, with multiple files that can be attached.
I'm looping through the list of recipients (if they are verified, and agreed to receive the newsletter, etc.)
It all works fine, emails are received to these users, including 1 or more attached files.
The problem is only the first user gets file\s with content, others get the file/s, but they are empty/blank (Zero bytes).
Is it linked to some temp files or the need to cache these files first? so they're available for the loop to handle? (note: the file name, number of files, etc. received to all recipients is all correct - they are just empty, except the first email)
How can I resolve that? Thanks
forms.py
class NewsletterForm(forms.Form):
message = forms.CharField(widget = forms.Textarea, max_length = 8000)
attach = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), required=False)
views.py
def newsletter(request):
users = User.objects.all()
receipient_list = []
for user in users:
if user.profile.newsletter_agree == True and user.profile.verified_member == True:
receipient_list.append(user.email)
if request.method == 'POST':
form = NewsletterForm(request.POST, request.FILES)
if form.is_valid():
mail_subject = 'Some newsletter subject'
message = form.cleaned_data['message'],
files = request.FILES.getlist('attach')
for contact_name in receipient_list:
try:
email = EmailMessage(mail_subject, message, 'email#email.com', to=[contact_name])
email.encoding = 'utf-8'
for f in files:
email.attach(f.name, f.read(), f.content_type)
email.send()
except BadHeaderError:
return HttpResponse('Invalid header found.')
messages.success(request, f'Newsletter sent successfully')
return redirect('main_page')
form = NewsletterForm()
context = {'form':form}
return render(request, 'my_app/newsletter.html', context)
newsletter.html
<form method="POST" enctype="multipart/form-data">
<fieldset class="form-group">
{% csrf_token %}
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-light">Send</button>
</div>
</form>
The file read function: f.read() reads through the file and returns it as a string but leaves the read cursor at the end of the file. So when you call it a second time it returns nothing.
Some options might be to use attach_file instead of attach:
email.attach_file(f.name, f.content_type)
or reading each file once, creating a list of attachment triples and re-using the list.
files = request.FILES.getlist('attach')
attachments = []
for f in files:
attachments.append((f.name, f.read(), f.content_type))
for contact_name in receipient_list:
try:
email = EmailMessage(mail_subject, message, 'email#email.com', to=[contact_name], attachments=attachments)
email.encoding = 'utf-8'
email.send()

Django Sending Modelform as an email

I created a site where my techs submit their inventory using model forms. Everything is working as intended but I would like to add the function of sending the whole form as an email when they submit their inventory. This would allow for my inventory team to verify counts without having to log in and check the website.
Here is my view.py I know it works if I remove the email bits and saves to my models. Currently returns an error:
'dict' object has no attribute 'splitlines'
form = Inventory_Form()
if request.method == 'POST':
form = Inventory_Form(request.POST)
tech_field = form.save(commit=False)
tech_field.technician = request.user
tech_field.save()
if form.is_valid():
form.save()
name = form.cleaned_data['initials_date']
from_email = 'operations#imbadatthis.com'
subject = 'Weekly Inventory', form.cleaned_data['initials_date']
message = form.cleaned_data
try:
send_mail(subject, message, from_email, ['myemail#n00b.com'], name)
except BadHeaderError:
return HttpResponse('Invalid header found.')
return response, redirect('inventory_submitted')
return render(request, 'inventory.html', {'form': form})
Would it be better to save the form to a csv then attach it as an email? I looked at this and also had issues with that part.
I guess the error is raised at the send_mail because of
message = form.cleaned_data
Because this is a dict and the send_mail from django expects the message to be a string.
You have to convert the dict to a string.
Maybe this helps to make a nice looking email. (documentation)

passing django object context to sendgrid email via sendgrid-python API lib

my django app has a view where accounts can send out newsletter emails to its contacts and subscribers using Sendgrid's API. sending is working with a plaintext email:
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import (Mail, Subject, To, ReplyTo, SendAt, Content, From, CustomArg, Header)
def compose_email(request, send_to, *args, **kwargs):
...
if request.method == 'POST':
subject = request.POST.get('subject')
from_name = request.POST.get('from_name')
body = request.POST.get('body')
reply_to = request.POST.get('reply_to')
test_address = [request.POST.get('test_address')]
# send test email
if request.POST.get('do_test'):
if form.is_valid():
message = AccountEmailMessage(account=account, subject=subject,
from_name=from_name, destination=destination, body=body, reply_to=reply_to,
is_draft=True, is_sent=False)
message.save()
email = Mail(
subject=subject,
from_email=hi#app.foo,
html_content=body,
to_emails=test_address,
)
email.reply_to = ReplyTo(reply_to)
try:
sendgrid_client = SendGridAPIClient(settings.SENDGRID_API_KEY)
response = sendgrid_client.send(email)
message.sendgrid_id = response.headers['X-Message-Id']
message.save()
except Exception as e:
log.error(e)
messages.success(request, 'Test message has been successfully sent')
else:
messages.error(request, 'Please, check for errors')
this works. but we want to render django object properties (model fields via template tags) in an html email template from Account (account) [assume it's just a vanilla obj req query account = Account.objects.get(id=selected_account) in the view], and I'm not clear what's the recommended docs approach.
the attempt:
if request.method == 'POST':
subject = request.POST.get('subject')
from_name = request.POST.get('from_name')
body = request.POST.get('body')
reply_to = request.POST.get('reply_to')
if request.POST.get('send'):
if form.is_valid():
message = AccountEmailMessage(account=account, subject=subject,
from_name=from_name, destination=destination, body=body, reply_to=reply_to,
is_draft=False, is_sent=True)
message.save()
rendered = render_to_string('email/newsletter.html', {
'account': account,
'protocol': settings.DEFAULT_PROTOCOL,
'domain': settings.DOMAIN,
'message_body': body
})
email = Mail(
subject=subject,
from_email=hi#app.foo,
html_content=rendered,
to_emails=recipients,
mime_type='text/html'
)
email.reply_to = ReplyTo(reply_to)
try:
sendgrid_client = SendGridAPIClient(settings.SENDGRID_API_KEY)
response = sendgrid_client.send(email)
message.sendgrid_id = response.headers['X-Message-Id']
message.save()
except Exception as e:
log.error(e)
but on submit, this throws an err: NoReverseMatch: Reverse for 'account' not found. 'account' is not a valid view function or pattern name when I try to pass account as a kwarg to the context and render it as a string.
looking at the docs (https://github.com/sendgrid/sendgrid-python#use-cases) I see Mail() has a .dynamic_template_data property. that's very inefficient to process a large number of fields from the same obj, as well as attributes like image urls, and also requires use of legacy transactional templates (https://sendgrid.com/docs/ui/sending-email/create-and-edit-legacy-transactional-templates/). I see Sendgrid has a Personalization obj (https://sendgrid.com/docs/for-developers/sending-email/personalizations/) - is that the recommended way to implement this?
thanks to Iain on further testing realized we had two issues:
was attempting to encode a url in the template via {% url %} tag, that threw the NoReverseMatch
mime_type='text/html' isn't a valid kwarg for Mail(), removed that as well.
after (1) and (2) everything's working properly, no need for personalization

Django e-mail template error

I'm trying to setup a HTML template that takes in some field forms for the subject header, and for part of the content.
views.py
if len(recipient) > 0:
messages.success(request, "Receipt sent successfully!")
subject = "Your Booking Reference: "
to = [recipient]
from_email = 'orders#demo.net'
template = get_template('booking/email/booking_reference.html')
message = EmailMessage(subject, template, from_email, ['test#test.com'])
message.content_subtype = 'html'
message.send()
return HttpResponse("Sent!")
else:
return index(request)
Whenever I request an email to be sent, I get the following error:
'Template' object has no attribute 'encode'
If I comment out message.content_subtype = 'html', I get the desired HttpResponse, but with no e-mail sent. I've added this setting to my settings.py file so that all e-mails get output to the console, but nothing is displayed
settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Try to write this :
message = EmailMultiAlternatives (subject, template, from_email, [test#test.com])
message.attach_alternative(template, "text/html")
Ok I think the problem is that you don't add a context in your template so try this:
if len(recipient) > 0:
messages.success(request, "Receipt sent successfully!")
subject = "Your Booking Reference: "
to = [recipient]
from_email = 'orders#demo.net'
template = loader.get_template('booking/email/booking_reference.html')
context = RequestContext(request, locals())
template = template.render(context)
message = EmailMessage(subject, template, from_email, ['test#test.com'])
message.content_subtype = 'html'
message.send(True)
return HttpResponse("Sent!")
else:
return index(request)

Django, redirect to another view with data

I'm sending a form. So if it's valid i'm setting a variable message with a message. So if the form is valid, I would like to redirect to another view but also pass the message variable. It should be a syntax issue.
On successful submission, it redirects to a view with a url membership/enroll/studies.views.dashboard which of course is wrong.
views.py
def enroll(request):
user = request.user
if request.method == 'POST':
form = SelectCourseYear(request.POST)
if form.is_valid():
student = form.save(commit=False)
student.user = request.user
student.save()
message = 'Successfully Enrolled'
return redirect('studies.views.dashboard', {'message': message,})
else:
form = SelectCourseYear()
return render(request, 'registration/step3.html',)
Consider making use of sessions to store arbitrary data between requests: https://docs.djangoproject.com/en/dev/topics/http/sessions/
request.session['message'] = 'Successfully Enrolled'
Alternatively, if you just want to display a message to the user, you might be happy with the contrib.messages framework: https://docs.djangoproject.com/en/dev/ref/contrib/messages/
from django.contrib import messages
messages.success(request, 'Successfully Enrolled')
Based on your use case above, I'm guessing that contrib.messages is more appropriate for your scenario.

Categories