Retrieving default fallback avatar image using python and GAE - python

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.

Related

How to attach S3 file to email in Django

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()

Django/Dropbox uploaded file is 0 bytes

I CAN'T figure out why my uploaded file to Dropbox via Django is always zero bytes.
Outsize Django (using raw Python), the files get uploaded normally.
My HTML has enctype="multipart/form-data" declared.
VIEWS.PY:
if cv_form.is_valid():
cv_form.save(commit=True)
# print(request.FILES['cv'].size) == 125898
DOC = request.FILES['cv']
PATH = f'/CV/{DOC}'
upload_handler(DOC, PATH)
#http_response_happens_here()
HANDLER:
def upload_handler(DOC, PATH):
dbx = dropbox.Dropbox(settings.DROPBOX_APP_ACCESS_TOKEN)
dbx.files_upload(DOC.file.read(), PATH)
#file gets uploaded but always 0bytes in size
My bad! How did I not see it.
The solution incase anyone else runs into the same issue. Battled it for a while myself.
if cv_form.is_valid():
cv_form.save(commit=False) # commit should be False before upload
# print(request.FILES['cv'].size) == 125898
DOC = request.FILES['cv']
PATH = f'/CV/{DOC}'
upload_handler(DOC, PATH)
cv_form.save(commit=True) # now you can save the file.
#http_response_happens_here()

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!

Flask-Uploads always throwing 'UploadNotAllowed' error even with no constraints

Flask-uploads has something called UploadSet which is described as a "single collection of files". I can use this upload set to save my file to a predefined location. I've defined my setup:
app = Flask(__name__)
app.config['UPLOADS_DEFAULT_DEST'] = os.path.realpath('.') + '/uploads'
app.config['UPLOADED_PHOTOS_ALLOW'] = set(['png', 'jpg', 'jpeg'])
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
# setup flask-uploads
photos = UploadSet('photos')
configure_uploads(app, photos)
#app.route('/doit', method=["POST"])
def doit():
myfile = request.files['file']
photos.save(myfile, 'subfolder_test', 'filename_test')
return ''' blah '''
This should save to ./uploads/photos/subfolder_test/filename_test.png
My test image is: 2.6MB and is a png file. When I upload this file, I get the error:
...
File "/home/btw/flask/app.py", line 57, in doit
photos.save(myfile, 'subfolder_test', 'filename_test')
File "/usr/local/lib/python2.7/dist-packages/flaskext/uploads.py", line 388, in save
raise UploadNotAllowed()
UploadNotAllowed
However it doesn't say exactly what is not allowed. I have also tried removing all constraints, but the app still throws this error. Why?
EDIT:
Okay, so I figured out that it's not actually the constraints that is causing the problem. It is the subfolder and/or the filename that is causing the problem:
# This works
# saves to: ./uploads/photos/filename_test.png
photos.save(myfile)
But I want to save to my custom location ./uploads/photos/<custom_subdir>/<custom_filename>. What is the correct way of doing this?
You need to give your filename_test the extension as well
photos.save(myfile, 'subfolder_test', 'filename_test.png')
The UploadSet checks the extension on the new file name and will throw the exception if the new extension is not allowed.
Since you are not giving the new file an extension, it does not recognize it.
You can add a dot to file's name, then the file's extension will be appended.
photos.save(myfile, 'subfolder_test', 'filename_test' + '.')
save(storage, folder=None, name=None)
Parameters:
storage – The uploaded file to save.
folder – The subfolder within the upload set to save to.
name – The name to save the file as. If it ends with a dot, the file’s extension will be appended to the end.

Flask-strange routing issue

So I am trying to test out serving some user uploaded files in Flask. For images I am simply renaming them with a shortened UUID and putting them in a folder, but for other file types I would like to retain the original filename, so I devised the convoluted method of saving each file in a subfolder named with a UUID. Everything works fine, both the images and files upload and are in the directories they should be in. Just to test I made a template for a download page to just display the filename(planned to implement a download button later). However, when I plug the generated URL in for an uploaded file, I get a 404, and the function that url is supposed to be bound to doesn't even appear to execute(I had it print the filename in console and it doesnt even print), and in console the url is displayed: "GET /xVgePgj2Y HTTP/1.1" 404 -
My code for uploading the files and making the URL:
else:
new_folder_name = shortuuid.uuid()[:9]
os.mkdir(os.path.join(app.config['FILE_FOLDER'], new_folder_name))
file.save(os.path.join(os.path.join(app.config['FILE_FOLDER'], new_folder_name), filename))
new_folder_path = os.path.join(app.config['FILE_FOLDER'], new_folder_name)
return url_for('uploaded_file', new_folder_name=new_folder_name)
My code for serving the files:
#app.route('/<new_folder_name>', methods=['GET'])
def uploaded_file(new_folder_name):
filename = subfolder_fetch(new_folder_name)
return render_template("download.html", filename=filename)
and finally my code for fetching the filename from the subdirectory (called in the serving function - didn't pass the filename to the url_for function because that would make it ugly and complicated):
def subfolder_fetch(new_folder_name):
stuff = os.listdir(os.path.join(app.config['FILE_FOLDER'], new_folder_name))
for name in folder:
print (name)
return name
I'm puzzled as to what is even going on and why my uploaded_file function isn't even being called.
Thanks in advance.

Categories