I've seen a lot of post about this problem without really understanding how to solve it.
I have this model:
class Project(TimeStampedModel):
name = models.TextField(max_length=100, default='no name')
logo = models.ImageField()
I'd like to have my image saved to media root following this template:
<name>/logo/<filename>
At first glance, I would like to do:
logo = models.ImageField(upload_to="{}/logo/".format(name))
But it raises this error: AttributeError: 'TextField' object has no attribute 'model'
Using a callable would be fine, partially though:
def upload_to_project(self, filename):
url = ("%s/%s") % (self.name, filename)
return url
and using:
logo = models.ImageField(upload_to=upload_to_project)
at least I have: <name>/<filename>
But how to pass the argument in this case? I'd like to reuse my function to upload in other subfolders, not only logo
as:
<name>/logo/<filename>
<name>/history/<filename>
<name>/whatever/<filename>
Any idea on what I could do?
It looks like (re-reading your post it's not 100% clear) what you want is a partial application. Good news, it's part of Python's stdlib:
import os
from functools import partial
def generic_upload_to(instance, filename, folder):
return os.path.join(instance.name, folder, filename)
class Project(TimeStampedModel):
name = models.TextField(max_length=100, default='no name')
logo = models.ImageField(
upload_to=partial(generic_upload_to, folder="logo")
)
Note that this implementation assumes instance
has a name
attribute... if the instance attribute you want to use as first part has to be configurable too you can rewrite your upload_to
as:
def generic_upload_to(instance, filename, folder, attrname):
return os.path.join(getattr(instance, attrname), folder, filename)
then use it as
class Project(TimeStampedModel):
name = models.TextField(max_length=100, default='no name')
logo = models.ImageField(
upload_to=partial(generic_upload_to, attrname="name", folder="logo")
)
And if you have more than one FileField
or ImageField
in your model and don't want to repeat the attrname
part:
class Something(TimeStampedModel):
my_upload_to = partial(generic_upload_to, attrname="label")
label = models.CharField(max_length=100, default='no label')
logo = models.ImageField(
upload_to=partial(my_upload_to, folder="logo")
)
attachment = models.FileField(
upload_to=partial(my_upload_to, folder="attachment")
)