What's the best way to rename photos with a unique filename on the server as they are uploaded, using django? I want to make sure each name is used only once. Are there any pinax apps that can do this, perhaps with GUID?
Use uuid. To tie that into your model see Django documentation for FileField upload_to.
For example in your models.py define the following function:
import uuid
import os
def get_file_path(instance, filename):
ext = filename.split('.')[-1]
filename = "%s.%s" % (uuid.uuid4(), ext)
return os.path.join('uploads/logos', filename)
Then, when defining your FileField/ImageField, specify get_file_path as the upload_to value.
file = models.FileField(upload_to=get_file_path,
null=True,
blank=True,
verbose_name=_(u'Contact list'))
A better way could be using a common class in your helpers.py. This way you could reuse the random file generator across your apps.
In your helpers.py:
import os
import uuid
from django.utils.deconstruct import deconstructible
#deconstructible
class RandomFileName(object):
def __init__(self, path):
self.path = os.path.join(path, "%s%s")
def __call__(self, _, filename):
# #note It's up to the validators to check if it's the correct file type in name or if one even exist.
extension = os.path.splitext(filename)[1]
return self.path % (uuid.uuid4(), extension)
And then in your model just import the helper class:
from mymodule.helpers import RandomFileName
And then use it:
logo = models.ImageField(upload_to=RandomFileName('logos'))
Ref: https://coderwall.com/p/hfgoiw/give-imagefield-uploads-a-unique-name-to-avoid-file-overwrites
As of the writing of this answer it seems like you no longer need to do anything special to make this happen. If you set up a FileField with a static upload_to property, the Django storage system will automatically manage naming so that if a duplicate filename is uploaded, Django will randomly generate a new unique filename for the duplicate.
Works on Django 1.10.
Prior to Django 1.6.6, 1.5.9, and 1.4.14, the get_avaialable_name function would automatically give files a unique name by adding an underscore. So, for example, if you save one file "test.jpg" and then another file, "test.jpg" to your server, the first will be called test.jpg, and the second will be called test_1.jpg.
Alas, that turns out to be a vector for DDOSing a machine, by sending it thousands of zero-byte files to store, each one checking thousands of previous files to see what its name should be.
As you'll see in the docs, the new system appends seven random digits after the underscore to fix this problem.
You can write your own FileField and override generate_filename.
For example:
class UniqueNameFileField(FileField):
def generate_filename(self, instance, filename):
_, ext = os.path.splitext(filename)
name = f'{uuid.uuid4().hex}{ext}'
return super().generate_filename(instance, name)
How about concatenating the filename with the date / time the photo was uploaded and then using hashlib to create a message digest? That should give you unique filenames.
Alternatively you could re-use a neat little snippet which creates unique filenames and then use the full path to that file as the input to your hash call. That gives you unique constant length strings which you can map to your files.
django enforce unique filename automatically.
if the file already exists, seven unique characters are appended to the filename
tested on django 2.2
Related
I'm sure this is really simple but I can't figure out how to make a parsed result into its own file then move it to my desktop using python. Here is my code so far. I just want to save the result "names" as its own file then move it to my desktop but I can't find the answer anywhere. Is this an uncommon practice?
from gedcom.element.individual import IndividualElement
from gedcom.parser import Parser
import os
import shutil
import pickle
# Path to your `.ged` file
file_path = '/Users/Justin/Desktop/Lienhard_Line.ged'
# Initialize the parser
gedcom_parser = Parser()
# Parse your file
gedcom_parser.parse_file(file_path, False)
root_child_elements = gedcom_parser.get_root_child_elements()
# Iterate through all root child elements
for element in root_child_elements:
# Is the `element` an actual `IndividualElement`? (Allows usage of extra functions such as `surname_match` and `get_name`.)
if isinstance(element, IndividualElement):
# Get all individuals whose surname matches "Doe"
if element.surname_match('Lienhard'):
# Unpack the name tuple
(first, last) = element.get_name()
names = (first + " " + last)
pickle.dumps(names)
Saving a file to one location and then moving it is to another not how it's usually done, no. Just save to the final location.
from pathlib import Path
pic = Path.home() / 'Desktop' / 'names.pickle'
with pic.open('w') as picklefile:
pickle.dump(names, picklefile)
The pathlib library makes working with file names somewhat easier than the old os.path API, though both are in common use.
Writing and then renaming has some uses; if you need to absolutely make sure your data is saved somewhere, saving it to a temporary file until it can be renamed to its final name is a fairly common technique. But in this case, saving to a different location first seems like it would only introduce brittleness.
The above assumes that you have a directory named Desktop in your home directory, as is commonly the default on beginner-oriented systems. To be perfectly robust, the code should create the directory if it doesn't already exist, or perhaps simply save to your home directory.
Indeed, a much better convention for most purposes is simply to always save in the current directory, and let the user take it from there. Hardcoding paths in another directory just adds confusion and uncertainty.
with open('names.pickle', 'w') as picklefile:
pickle.dump(names, picklefile)
This code creates a file called names.pickle in the invoking user's current working directory. The operating system provides the concept of a current working directory for precisely this purpose. Perhaps see also Difference between ./ and ~/ if you need more details about how this works.
I have a function called get_book which opens requested book by its title argument. Function get_book can receive title in any case (e.g. Foo,FOO,fOo). While this is not a problem in itself, book folder where the function retrieves a book if the title's are matching, also has book titles in a mixed case way. The database folder is dynamic and continuous to receive new book file names (like e.g. Foo.pdf,FOO.pdf,FOo.pdf).
My question is how can I compare titles and followingly retrieve requested book without changing file names in the database? Is there an efficient way to open files without worrying about case matching?
my code:
def get_book(title):
"""
Retrieves a book by its title. If no such
book exists, the function returns None.
"""
try:
f = default_storage.open(f"books/{title}.pdf")
return f.read().decode("utf-8")
except FileNotFoundError:
return None
Start with an empty lower_to_name dict.
Probe it like this, for each new title:
title = title.lower() + ".pdf"
title = lower_to_name.get(title, title)
Upon getting FileNotFoundError for a title,
do a single retry using glob to lookup actual filename:
lower_to_name = {name.lower(): name
for name in glob.glob("*.pdf")}
title = lower_to_name.get(title, "no_such_file")
(You may find that os.chdir("books/") is convenient.)
For finding a case-insensitive file name, fnmatch is a good method.
This example is almost literally the example in the documentation:
import fnmatch
import os
title = 'tEst.txt' # Try several combinations of upper and lower case
for file in os.listdir('.'):
if fnmatch.fnmatch(file, title): # They will be all found here
print(file)
I recently came across this question and answer https://stackoverflow.com/a/44926557/12322095 regarding Flask file uploads.
This worked perfectly fine until I uploaded an image with the same name again. It didn't change the image or overlayed it.
My question here is what if a user uploads an image with the same name, is there any way we could show an error message or maybe automatically change the name to something else.
For automatic changing the name, I researched and it can be done via resolve_conflict but I couldn't understand how to implement it.
my code is ditto as the the reference
You need to create some kind of uniuqe ID to append to the filename, before saving the file.
This can be done with something like:
from uuid import uuid4
def make_unique(string):
ident = uuid4().__str__()
return f"{ident}-{string}"
Which adds an unique UUID to the beginning of the string:
>>> make_unique('something.txt')
'f374b80c-b09e-468f-adc6-3d9df175cee7-something.txt'
To use this in the upload code, just run the filename through that function before you save. Be sure to put the filename through the secure_filename function first though:
if file and allowed_file(file.filename):
original_filename = secure_filename(file.filename)
unique_filename = make_unique(original_filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], unique_filename))
Although this works for the purpose of avoiding duplicates, in a larger application you may wish to extend this approach.
If you store the values of original_filename and unique_filename in the database, then this allows you to do the following in the download route:
from flask import send_file
# ...
f = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
send_file(f, attachment_filename=original_filename)
This has the advantage that the file is stored on your server with a unique identifier, but the user would never know this, as the file is served back to them with the originally uploaded filename.
In fact you may wish to go further, and simply save the file on your end with the UUID string, rather than appending the filename; instead of using the make_unique function above, change that third line to:
unique_filename = uuid4().__str__()
This will still serve the file with the correct mimetype, as send_file guesses the mimetype based on the provided attachment_filename.
Problem Background
I am new to django. I am trying to upload the file from client and save it.
For this pupose i have created below model.
from django.db import models
class UploadFile(models.Model):
uploadfile = models.FileField(upload_to='toProcess/')
I am using this model as below to save the file.
newfile = UploadFile(uploadfile = request.FILES['file'])
newfile.save()
It is saving file. But now I want to process saved file. In django, if the file with the same name alreday exists then it is adding some unique postfix to the original file name. I am happy with this approch and do not want to write a new method to create a unique filename.
Probelm-
How to get the new unique name calculated by django for a file?
Mean If I will upload a same file two times say "abc.pdf" then it will save the first uploaded file as "abc.pdf" and second uploaded file as "abc_somesuffix.pdf". How to know what is the name of saved file?
As far as I know, the filename is stored in the name attribute of the model field, in your case
newfile.uploadfile.name
and the path of the file is stored in
newfile.uploadfile.path
Please see the official Django docs for further reference, as well as many other SO Q&A (e.g. this one)
In case you want to adopt your own format for the file name you can specify a callable in the upload_to parameter of the model field, as explained here
I have a python web form with two options - File upload and textarea. I need to take the values from each and pass them to another command-line program. I can easily pass the file name with file upload options, but I am not sure how to pass the value of the textarea.
I think what I need to do is:
Generate a unique file name
Create a temporary file with that name in the working directory
Save the values passed from textarea into the temporary file
Execute the commandline program from inside my python module and pass it the name of the temporary file
I am not sure how to generate a unique file name. Can anybody give me some tips on how to generate a unique file name? Any algorithms, suggestions, and lines of code are appreciated.
Thanks for your concern
I didn't think your question was very clear, but if all you need is a unique file name...
import uuid
unique_filename = str(uuid.uuid4())
If you want to make temporary files in Python, there's a module called tempfile in Python's standard libraries. If you want to launch other programs to operate on the file, use tempfile.mkstemp() to create files, and os.fdopen() to access the file descriptors that mkstemp() gives you.
Incidentally, you say you're running commands from a Python program? You should almost certainly be using the subprocess module.
So you can quite merrily write code that looks like:
import subprocess
import tempfile
import os
(fd, filename) = tempfile.mkstemp()
try:
tfile = os.fdopen(fd, "w")
tfile.write("Hello, world!\n")
tfile.close()
subprocess.Popen(["/bin/cat", filename]).wait()
finally:
os.remove(filename)
Running that, you should find that the cat command worked perfectly well, but the temporary file was deleted in the finally block. Be aware that you have to delete the temporary file that mkstemp() returns yourself - the library has no way of knowing when you're done with it!
(Edit: I had presumed that NamedTemporaryFile did exactly what you're after, but that might not be so convenient - the file gets deleted immediately when the temp file object is closed, and having other processes open the file before you've closed it won't work on some platforms, notably Windows. Sorry, fail on my part.)
The uuid module would be a good choice, I prefer to use uuid.uuid4().hex as random filename because it will return a hex string without dashes.
import uuid
filename = uuid.uuid4().hex
The outputs should like this:
>>> import uuid
>>> uuid.uuid()
UUID('20818854-3564-415c-9edc-9262fbb54c82')
>>> str(uuid.uuid4())
'f705a69a-8e98-442b-bd2e-9de010132dc4'
>>> uuid.uuid4().hex
'5ad02dfb08a04d889e3aa9545985e304' # <-- this one
Maybe you need unique temporary file?
import tempfile
f = tempfile.NamedTemporaryFile(mode='w+b', delete=False)
print f.name
f.close()
f is opened file. delete=False means do not delete file after closing.
If you need control over the name of the file, there are optional prefix=... and suffix=... arguments that take strings. See https://docs.python.org/3/library/tempfile.html.
You can use the datetime module
import datetime
uniq_filename = str(datetime.datetime.now().date()) + '_' + str(datetime.datetime.now().time()).replace(':', '.')
Note that:
I am using replace since the colons are not allowed in filenames in many operating systems.
That's it, this will give you a unique filename every single time.
In case you need short unique IDs as your filename, try shortuuid, shortuuid uses lowercase and uppercase letters and digits, and removing similar-looking characters such as l, 1, I, O and 0.
>>> import shortuuid
>>> shortuuid.uuid()
'Tw8VgM47kSS5iX2m8NExNa'
>>> len(ui)
22
compared to
>>> import uuid
>>> unique_filename = str(uuid.uuid4())
>>> len(unique_filename)
36
>>> unique_filename
'2d303ad1-79a1-4c1a-81f3-beea761b5fdf'
I came across this question, and I will add my solution for those who may be looking for something similar. My approach was just to make a random file name from ascii characters. It will be unique with a good probability.
from random import sample
from string import digits, ascii_uppercase, ascii_lowercase
from tempfile import gettempdir
from os import path
def rand_fname(suffix, length=8):
chars = ascii_lowercase + ascii_uppercase + digits
fname = path.join(gettempdir(), 'tmp-'
+ ''.join(sample(chars, length)) + suffix)
return fname if not path.exists(fname) \
else rand_fname(suffix, length)
This can be done using the unique function in ufp.path module.
import ufp.path
ufp.path.unique('./test.ext')
if current path exists 'test.ext' file. ufp.path.unique function return './test (d1).ext'.
To create a unique file path if its exist, use random package to generate a new string name for file. You may refer below code for same.
import os
import random
import string
def getUniquePath(folder, filename):
path = os.path.join(folder, filename)
while os.path.exists(path):
path = path.split('.')[0] + ''.join(random.choice(string.ascii_lowercase) for i in range(10)) + '.' + path.split('.')[1]
return path
Now you can use this path to create file accordingly.