Django Filefield with Upload_To Determined at Runtime

Django FileField with upload_to determined at runtime

You've probably read the documentation, so here's an easy example to make it make sense:

def content_file_name(instance, filename):
return '/'.join(['content', instance.user.username, filename])

class Content(models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User)
file = models.FileField(upload_to=content_file_name)

As you can see, you don't even need to use the filename given - you could override that in your upload_to callable too if you liked.

Access upload_to of a Model's FileFIeld in Django?

You can access this with:

Video.file.field.upload_to  # 'videos/'

or through the _meta object:

Video._meta.get_field('file').upload_to  # 'videos/'

The upload_to=… parameter [Django-doc] can however also be given a function that takes two parameters, and thus in that case it will not return a string, but a reference to that function.

Django model with FileField -- dynamic 'upload_to' argument

Instead of a string try passing a function:

def generate_filename(self, filename):
url = "files/users/%s/%s" % (self.user.username, filename)
return url

class UserFiles(models.Model):
user = models.OneToOneField(User)
file = models.FileField(upload_to=generate_filename)

Does the 'upload_to' callback in the FileField hit the db for related content?

No, it will not hit the DB, because you're already passing existing and saved(!) Owner and Chat objects to Attachment.objects.create, so the upload handler does not need to fetch them from the database.

However, extra queries for related objects will be made when you get the attachment fresh from the database:

attachment = Attachment.objects.get(id=some_id)
attachment.file.save(...)

In this case using select_related can remove the extra queries.

You can always verify the SQL queries that are run (only in DEBUG mode):

from django.db import connection

def get_attachment_path(instance, filename):
print('Queries before', connection.queries)
path = f'{instance.owner.name}/{instance.chat.id}/{filename}'
print('Queries after', connection.queries)
return path

Alternatively, Django Debug Toolbar is a great addition to a Django project to optimize SQL queries.

How to get the fieldname of a FileField in the upload_to method, for a field translated with modeltranslation in Django?

Something like this should work.

from modeltranslation.translator import translator
from django.db.models import FileField
import os

class TranslationMeta(type):
def __init__(self, name, bases, attrs):
for attrname, attrvalue in attrs.items():
if self.is_translated_field(name, attrname):
field = attrvalue
if isinstance(field, FileField):
self.update_upload_to(field, attrname)
super().__init__(name, bases, attrs)

def is_translated_field(self, class_name, attr_name):
opts = translator.get_options_for_model(self)
return attr_name in opts.get_field_names()

def update_upload_to(self, field, attr_name):
opts = translator.get_options_for_model(self)
translated_fields = opts.fields[attr_name]
for trans_field in translated_fields:
# print(trans_field.name)
# print(trans_field.language)
trans_field.upload_to = self.custom_upload_to(field.upload_to, trans_field.language)

def custom_upload_to(self, base_upload_to, language):
# If the original upload_to parameter is a callable,
# return a function that calls the original upload_to
# function and inserts the language as the final folder
# in the path
# If the original upload_to function returned /path/to/file.png,
# then the final path will be /path/to/en/file.png for the
# english field
if callable(base_upload_to):
def upload_to(instance, filename):
path = base_upload_to(instance, filename)
return os.path.join(
os.path.dirname(path),
language,
os.path.basename(path))
return upload_to
# If the original upload_to parameter is a path as a string,
# insert the language as the final folder in the path
# /path/to/file.png becomes /path/to/en/file.png for the
# english field
else:
return os.path.join(
os.path.dirname(base_upload_to),
language,
os.path.basename(base_upload_to))

# This is how you would use this class
class MyModel(models.Model, metaclass=TranslationMeta):
field = FileField()

m = MyModel(models.Model)
print(m.field.upload_to)

It uses introspection to dynamically override the upload_to parameter of every language-specific FileField generated by django-modeltranslation behind the scenes.

With this model for instance:

class MyModel(models.Model):
field = FileField(upload_to=...)

if you have defined field as a translatable field by adding

from modeltranslation.translator import register, TranslationOptions
from . import models

@register(models.MyModel)
class MyModelTranslationOptions(TranslationOptions):
fields = ("field",)

in translation.py, django-modeltranslation will generate something like

class MyModel(models.Model):
field = FileField(upload_to=...)
field_en = FileField(upload_to=...)
field_fr = FileField(upload_to=...)

if you have en and fr defined in your LANGUAGES settings.

If the upload_to parameter passed to the FileField was a path as a string, it is overridden with the same path in which a folder for the language is inserted.
If it is a function, then the folder for the language is inserted in the path returned by this function.

For instance if you have

class MyModel(models.Model):
field = FileField(upload_to="/path/to/file.png")

or

def get_upload_path(instance, filename):
return "path/to/file.png"

class MyModel(models.Model):
field = FileField(upload_to=get_upload_path)

then, in both cases:

  • the English version of the file will be stored under /path/to/en/file.png
  • the French version of the file will be stored under /path/to/fr/file.png


Related Topics



Leave a reply



Submit