How does this python function get its parameters?
I am implementing amazon S3 in my django app by following this tutorial: http://djangotricks.blogspot.com/2013/12/how-to-store-your-media-files-in-amazon.html
In it, it defines the upload_avatar_to function, which takes two parameters:
def upload_avatar_to(instance, filename):
import os
from django.utils.timezone import now
filename_base, filename_ext = os.path.splitext(filename)
return 'profiles/%s%s' % (
now().strftime("%Y%m%d%H%M%S"),
filename_ext.lower(),
)
However, when it calls it, it doesn't provide any parameters:
class Profile(models.Model):
# ...
avatar = models.ImageField(_("Avatar"), upload_to=upload_avatar_to, blank=True)
# ...
How does the function get the instance and filename if it was not dispatched when called?
I want to add an extra parameter to the function call, like "folder name". How can i do this?
source to share
It's clearly documented in the Django documentation, upload_to can be callable: https://docs.djangoproject.com/en/1.6/ref/models/fields/#django.db.models.FileField.upload_to
According to the official documentation:
It can also be called, for example, a function that will be called to get the download path, including the filename. This callee must be able to take two arguments and return a Unix-style path (with a forward slash) to be passed along with the storage system. The two arguments to be passed are as follows:
Instance The
instance of the model in which the FileField is defined. More specifically, it is the specific instance into which the current file is inserted.
In most cases, this object has not yet been saved to the database, so if it uses the default AutoField, it may not yet have a value for its primary key field.
filename
The name of the file that was originally supplied to the file. This may or may not be taken into account when determining the final destination path.
source to share
What happens is that Django is passed the actual function object in the last line of code - the function is not called at this time. This function will later be called inside the constructor ImageField
, where it will be given arguments instance
and filename
and return a value. This is easy to do because functions are first-class objects in Python.
As petkostas showed in its link, the function you provide must take exactly two arguments, an instance and a filename. We can get around this though!
What you can do is create a wrapper for a function upload_avatar_to
that will allow you to provide an additional parameter that still meets Django's requirements. Check it:
def upload_to_wrapper(foldername):
def wrapper(instance, filename):
upload_avatar_to(instance, filename, foldername)
return wrapper
Then you define your function like this:
def upload_avatar_to(instance, filename, foldername):
# Do your thing.
And finally, this is the last line you would call and the point at which you indicate foldername
which one you want:
avatar = models.ImageField(_("Avatar"), upload_to=upload_to_wrapper(foldername), blank=True)
Essentially, it upload_to_wrapper
returns a modified version of the object upload_avatar_to
that already has foldername
, and only instance
and should be specified filename
. This allows you to work with Django requirements. This function ImageField
requires you to specify a function object that takes values instance
and filename
, but exactly what it returns upload_to_wrapper
, so you're good to go.
source to share