How to attach S3 file to email in Django - python

I'm trying to attach a media file saved in an S3 bucket to an email, which I'm doing with this line of code:
email.attach_file(standard.download.url)
The model is defined as follows:
class Standard(models.Model):
name = models.CharField(max_length = 51)
download = models.FileField(upload_to="standard_downloads/", null=True, blank=True)
def __str__(self):
return self.name
Within settings.py I have defined my media files as follows:
AWS_DEFAULT_ACL = 'public-read'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400',
}
DEFAULT_FILE_STORAGE = 'sme.storage_backends.MediaStorage'
MEDIA_ROOT = 'https://%s.s3.amazonaws.com/media/' % AWS_STORAGE_BUCKET_NAME
When trying to run the code I'm getting
No such file or directory:
'https:/bucket-name.s3.amazonaws.com/media/standard_downloads/filename.ext
Please note it is showing as https:/ (a single /). How do I correct this?

Here's the source code of attach_file from Django. It clearly says - to attach file from the filesystem. It does not work with remote urls. And when you give it a url it thinks you are referring to local file, so it escapes all double slashes to single slashes.
def attach_file(self, path, mimetype=None):
"""
Attach a file from the filesystem.
Set the mimetype to DEFAULT_ATTACHMENT_MIME_TYPE if it isn't specified
and cannot be guessed.
For a text/* mimetype (guessed or specified), decode the file's content
as UTF-8. If that fails, set the mimetype to
DEFAULT_ATTACHMENT_MIME_TYPE and don't decode the content.
"""
path = Path(path)
with path.open('rb') as file:
content = file.read()
self.attach(path.name, content, mimetype)
Django does not provide anything built-in for that. You will have to write something custom on the lines of above code also using libraries like request or boto. Basically the idea is to fetch from remote url save as temp and then use attach on that.
Here's one example on how you could get the file on the fly:
from django.core.mail.message import attach
import requests
response = requests.get("http://yoururl/somefile.pdf")
email.attach('My file',response.read(),mimetype="application/pdf")

A better way to do this would be to leverage default_storage which will work whether you are using local file storage, S3 or any other storage backend.
from django.core.files.storage import default_storage
msg = EmailMessage(
subject="Your subject",
body="Your Message",
from_email="email#yourdomain.com",
to=["email#anotherdomain.com"],
)
filename = "standard_downloads/filename.ext"
with default_storage.open(filename, "r") as fh:
msg.attach(filename, fh.read())
msg.send()

Related

Django - User uploaded S3 files in the view

I have a page where users can upload PDF / image files to their profile. The model for these files is relativly straightforward:
class ResumeItemFile(models.Model):
item = models.ForeignKey(ResumeItem, related_name='attachment_files')
file = models.FileField(
max_length=255, upload_to=RandomizedFilePath('resume_attachments'),
verbose_name=_('Attachment'))
name = models.CharField(max_length=255, verbose_name=_('Naam'), blank=True)
I am creating a view where all files linked to a profile (item) are gathered in a .zip file. I've got this working locally, but in production I run in the following error NotImplementedError: This backend doesn't support absolute paths.
The main difference is that on production the mediafiles are served through S3
MEDIA_URL = 'https://******.s3.amazonaws.com/'
STATIC_URL = MEDIA_URL
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
In my view I created a list of the ResumeItemFile in the attachments variable, which is a list of dicts that look like this: {'filename', ResumeItemFileObject}
for file in attachments:
storage = DefaultStorage()
filename = file[1]
file_extension = str(file[0].file).split('.')[-1]
file_object = storage.open(file[0].file.path, mode='rb')
filename, file_object.read())
file_object.close()
Though this works fine locally, on staging it crashes on the file_object = storage.open(file[0].file.path, mode='rb') line.
If the backend does not support absolute paths, how I am to select the correct file? Does anyone have an idea of what I am doing wrong?
I think that problem comes because in the s3boto storage class, the path() method is not implemented. As per the Django documentation,
For storage systems that aren’t accessible from the local filesystem,
this will raise NotImplementedError instead.
Instead of file.path use file.name in your code.
# file_object = storage.open(file[0].file.path, mode='rb')
file_object = storage.open(file[0].file.name, mode='rb')
You may want to look into the File object. It allows you to manipulate files in a largely Pythonic manner, but leverages the Django project's storage settings. In my case, this allows me to use local, on-disk storage locally and S3 in production:
https://docs.djangoproject.com/en/2.0/ref/files/file/
This will abstract away a lot of the boilerplate you're writing. There is an example here:
https://docs.djangoproject.com/en/2.0/topics/files/#the-file-object
Good luck!

Retrieving default fallback avatar image using python and GAE

my users can upload an image of themselves and use that as an avatar. Now I am struggling how to retrieve a default fallback image if they haven't uploaded an image themselves.
The path to the avatar is "//mysite.com/avatar/username".
So far I have this code, which works fine when the user has uploaded an avatar themselves, but it gives me the following error when I try to retrieve the default image:
raise IOError(errno.EACCES, 'file not accessible', filename)
IOError: [Errno 13] file not accessible: '/Users/myuser/Documents/github/mysite/static/images/profile.png'
def get(self):
path = self.request.path.split('/')
action = self.get_action(path)
if action:
e = employees.filter('username = ', action).get()
if e.avatar:
self.response.headers['Content-Type'] = "image/png"
self.response.out.write(e.avatar)
else:
self.response.headers['Content-Type'] = 'image/png'
path = os.path.join(os.path.split(__file__)[0], 'static/images/profile.png')
with open(path, 'r') as f:
print self.response.out.write(f.read())
I have defined the "/static"-folder as a static_dir in my app.yaml.
I know I can place the profile.png in the root-folder, but I prefer to have it in the "/static/images"-folder.
Any ideas?
If you declared the file itself as a static_file or its directory or any directory in its filepath a static_dir inside your app/service's .yaml config file then, by default, it's not accessible to the application code.
You need to also configure it as application_readable. From Handlers element:
application_readable
Optional. Boolean. By default, files declared in static file handlers
are uploaded as static data and are only served to end users. They
cannot be read by an application. If this field is set to true, the
files are also uploaded as code data so your application can read
them. Both uploads are charged against your code and static data
storage resource quotas.

How to put a JSON file's content in a response

I have a file on my computer that I'm trying to serve up as JSON from a django view.
def serve(request):
file = os.path.join(BASE_DIR, 'static', 'files', 'apple-app-site-association')
response = HttpResponse(content=file)
response['Content-Type'] = 'application/json'
What I get back is the path to the file when navigating to the URL
/Users/myself/Developer/us/www/static/files/apple-app-site-association
What am I doing wrong here?
os.path.join returns a string, it's why you get a path in the content of the response. You need to read the file at that path first.
For a static file
If the file is static and on disk, you could just return it using the webserver and avoid using python and django at all. If the file needs authenticating to be downloaded, you could still handle that with django, and return a X-Sendfile header (this is dependant on the webserver).
Serving static files is a job for a webserver, Nginx and Apache are really good at this, while Python and Django are tools to handle application logic.
Simplest way to read a file
def serve(request):
path = os.path.join(BASE_DIR, 'static', 'files', 'apple-app-site-association')
with open(path , 'r') as myfile:
data=myfile.read()
response = HttpResponse(content=data)
response['Content-Type'] = 'application/json'
This is inspired by How do I read a text file into a string variable in Python
For a more advanced solution
See dhke's answer on StreamingHttpResponse.
Additional information
Reading and writing files
Managing files with Django
If you feed HttpResponse a string a content you tell it to serve that string as HTTP body:
content should be an iterator or a string. If it’s an iterator, it should return strings, and those strings will be joined together to form the content of the response. If it is not an iterator or a string, it will be converted to a string when accessed.
Since you seem to be using your static storage directory, you might as well use staticfiles to handle content:
from django.contrib.staticfiles.storage import staticfiles_storage
from django.http.response import StreamingHttpResponse
file_path = os.path.join('files', 'apple-app-site-association')
response = StreamingHttpResponse(content=staticfiles_storage.open(file_path))
return response
As noted in #Emile Bergeron's answer, for static files, this should already be overkill, since those are supposed to be accessible from outside, anyway. So a simple redirect to static(file_path) should do the trick, too (given your webserver is correctly configured).
To serve an arbitrary file:
from django.contrib.staticfiles.storage import staticfiles_storage
from django.http.response import StreamingHttpResponse
file_path = ...
response = StreamingHttpResponse(content=open(file_path, 'rb'))
return response
Note that from Django 1.10 and on, the file handle will be closed automatically.
Also, if the file is accessible from your webserver, consider using django-sendfile, so that the file's contents don't need to pass through Django at all.

Django download image from ImageField

I'm working with Django 1.7 and Python 3.4.
I have a model like this one:
class ImageModel(models.Model):
image = models.ImageField(verbose_name='image', upload_to='uploaded_images/')
Now I want to download image which is saved in /static/uploaded_images/.
For example I have a link like this: www.example.com/image/download/1, where 1 is id of ImageModel object.
Now i have a view:
def download_image(request, image_id):
img = ImageModel.objects.get(id=image_id)
( what I need to do? )
What next? How to create a view that will force download of that image?
You can try this code, maybe need some caveats:
from django.core.servers.basehttp import FileWrapper
import mimetypes
def download_image(request, image_id):
img = ImageModel.objects.get(id=image_id)
wrapper = FileWrapper(open(img.file)) # img.file returns full path to the image
content_type = mimetypes.guess_type(filename)[0] # Use mimetypes to get file type
response = HttpResponse(wrapper,content_type=content_type)
response['Content-Length'] = os.path.getsize(img.file)
response['Content-Disposition'] = "attachment; filename=%s" % img.name
return response
I'm assuming there is a field .name in your ImageModel to get the name of the file in the second-to-last line ...filename=%s" % img.name You should edit the code to fit your project.
There is a field in an ImageField that is file, in the code here I use img.file to get the path to the file, you should change that for img.YOUR_IMAGE_FIELD.file or anything you need to get the path to the image
You need to use Content-Disposition header, take a look here:
Generating file to download with Django
Django Serving a Download File
A class based views-type exampe of this would be like this (i'm using python-magic to get the correct content-type for the file):
import os
import magic
from django.views.generic import View
from django.http import HttpResponse
from .models import ImageModel
class ImageDownloadView(View):
def get(self, request, *args, **kwargs):
image = ImageModel.objects.get(pk=self.kwargs['image_id'])
image_buffer = open(image.file.path, "rb").read()
content_type = magic.from_buffer(image_buffer, mime=True)
response = HttpResponse(image_buffer, content_type=content_type);
response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename(image.file.path)
return response
This works for Django 1.10.7 but shouldn't be that different for Django 1.7
The other two answers are ok, but as advertised in many places using Django to serve an static file is not recommended for performance reasons. It is better to serve it using your web server (nginx/apache...).
You don't need an extra view to serve static files. Simply render a link to the file in your template:
Download this image!
Where object is an instance of ImageModel.
See django.db.models.fields.files.FieldFile.url
If you really want to have a view in an URL like www.example.com/image/download/1 you can simply write a view that redirect to the image URL obtained from the field.
Here is your cross browser safe download for files containing any character type
# Even better as it works in any browser (mobile and desktop)
def safe_name(file_name):
"""
Generates a safe file name, even those containing characters like ? and &
And your Kanji and Cyrillics are supported!
"""
u_file_name = file_name.encode('utf-8')
s_file_name = re.sub('[\x00-\xFF]', lambda c: '%%%02x' % ord(c.group(0)), u_file_name)
return s_file_name
# Handled by url(r'^/image/download/(\d+)/.+$
def safe_download_image(request, image_id):
"""
Safely downloads the file because the filename is part of the URL
"""
img = ImageModel.objects.get(id=image_id)
wrapper = FileWrapper(open(img.file)) # img.file returns full path to the image
content_type = mimetypes.guess_type(filename)[0] # Use mimetypes to get file type
response = HttpResponse(wrapper,content_type=content_type)
response['Content-Length'] = os.path.getsize(img.file)
# This works for most browsers, but IE will complain sometimes
response['Content-Disposition'] = "attachment;"
return response
def download_image(request, image_id):
img = ImageModel.objects.get(id=image_id)
redirect_do = safe_name(img.name)
return HttpResponseRedirect('/image/download/' + img_id + '/' + redirect_to)
It's doesn't work. I did something like this:
wrapper = FileWrapper(img.file) # img.file returns full path to the image
content_type = mimetypes.guess_type(str(img.file))[0] # Use mimetypes to get file type
response = HttpResponse(wrapper, content_type=content_type)
response['Content-Length'] = os.path.getsize(str(img.file))
response['Content-Disposition'] = "attachment; filename=%s" % img.name
where img points to my ImageField field. File is downloaded, but I can't open it. xUbuntu image viewer seys 'Not a JPEG file. starts with 0x89 0x50'

Files Not Downloading Properly In Django/Nginx

I want users to be able to download file from my django web app. I wrote the below codes, but when I download a file, the file will turn into an error file. It won't open and the total size of the file would be 0 bytes.
Models:
class Emov(models.Model):
User=models.ForeignKey(User)
mov_file=models.FileField(upload_to='miiv')
mov_name=models.CharField(max_length=50)
email=models.EmailField() #email of the uploader
download_count=models.PositiveIntegerField(default=0)
#other fields follows
#property
def pretty_name(self):
return "{0}.{1}".format(slugify(self.title),
os.path.splitext(self.mov_name.name)[1])
Views:
def document_view(request,emov_id):
fileload=Emov.objects.get(id=emov_id)
response=HttpResponse()
response["Content-Disposition"]= "attachment; filename={0}".format(fileload.pretty_name)
response['X-Accel-Redirect']="/protected/{0}".format(fileload.mov_name.name)
return response
Nginx
location /protected/ {
internal;
root /C:/Python27/Scripts/env/Scripts/digi/media/miiv/;
}
What am I missing?
Try to do it like this:
fileload = Emov.objects.get(id=emov_id)
filename = fileload.mov_file.name.split('/')[-1]
response = HttpResponse(fileload.mov_file, content_type='text/plain') # your content type
response['Content-Disposition'] = 'attachment; filename=%s' % filename
return response
See:
https://docs.djangoproject.com/en/dev/ref/request-response/#telling-the-browser-to-treat-the-response-as-a-file-attachment
Hope it helps.
EDIT:
FileField.url attribute you should be able to pass a path to your files (like /protected/). https://docs.djangoproject.com/en/dev/ref/models/fields/#filefield-and-fieldfile

Categories