Search code examples
pythondjangodjango-modelsdjango-file-uploaddjango-modeltranslation

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


I am using django modeltranslation on a FileField.

I would like this file to be uploaded in a path /path/to/file/<lang>/file.ext and I guess the best way is to extract the lang from the fieldname (file_en, file_it, file_fr, ...) where upload_to is operating.

# models.py
def upload_method(instance, filename):
   lang = ""  # how to get this variable? 
   return f"/path/to/file/{lang}/file.ext"

class Obj(models.Model):
   file = models.FileField(upload_to=upload_method)

# translation.py
@register(models.Obj)
class ObjTranslationOptions(TranslationOptions):
    fields = ("file", )

Solution

  • 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