Skip to content

Commit

Permalink
Implement FileField support
Browse files Browse the repository at this point in the history
Adding `get_localized_value` allows us mimic how a "native" FileField
is instantiated:

https://github.com/django/django/blob/629398e55fd260c2ac4c2a9fc8b0c7d8dbda9e56/django/db/models/fields/files.py#L166

`TranslationField.pre_save` is modified to mimic how `pre_save` calls work as well:

https://github.com/django/django/blob/629398e55fd260c2ac4c2a9fc8b0c7d8dbda9e56/django/db/models/fields/files.py#L313

The virtual FileField instances are not considered concrete, so are not processed within the ORM:
https://github.com/django/django/blob/629398e55fd260c2ac4c2a9fc8b0c7d8dbda9e56/django/db/models/sql/compiler.py#L1744
  • Loading branch information
jacobwegner committed May 13, 2024
1 parent f17a9bb commit fe03015
Showing 1 changed file with 46 additions and 2 deletions.
48 changes: 46 additions & 2 deletions modeltrans/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ def get_instance_fallback_chain(self, instance, language):

return default

def get_localized_value(self, instance, field_name):
value = instance.i18n.get(field_name)

if isinstance(self.original_field, fields.files.FileField):
# TODO: Review this versus `descriptor_class`; need to write some additional tests to verify
return self.attr_class(instance, self, value)

return value

def __get__(self, instance, instance_type=None):
# This method is apparently called with instance=None from django.
# django-hstor raises AttributeError here, but that doesn't solve our problem.
Expand All @@ -131,7 +140,7 @@ def __get__(self, instance, instance_type=None):

# Just return the value if this is an explicit field (<name>_<lang>)
if self.language is not None:
return instance.i18n.get(field_name)
return self.get_localized_value(instance, field_name)

# This is the _i18n version of the field, and the current language is not available,
# so we walk the fallback chain:
Expand All @@ -144,7 +153,7 @@ def __get__(self, instance, instance_type=None):

field_name = build_localized_fieldname(self.original_name, fallback_language)
if field_name in instance.i18n and instance.i18n[field_name]:
return instance.i18n.get(field_name)
return self.get_localized_value(instance, field_name)

# finally, return the original field if all else fails.
return getattr(instance, self.original_name)
Expand Down Expand Up @@ -306,8 +315,43 @@ def get_translated_fields(self):
if isinstance(field, TranslatedVirtualField):
yield field

def get_file_fields(self):
"""Return a generator for all translated FileFields."""
for field in self.get_translated_fields():
if isinstance(field.original_field, fields.files.FileField):
yield field

def contribute_to_class(self, cls, name):
if name != "i18n":
raise ImproperlyConfigured('{} must have name "i18n"'.format(self.__class__.__name__))

super().contribute_to_class(cls, name)

def pre_save(self, model_instance, add):
"""Ensure that translated field values are serializable before save"""
data = super().pre_save(model_instance, add)

if data is None:
return data

for field in self.get_file_fields():
localized_file_attname = field.attname
if localized_file_attname in data:
current_value = data[localized_file_attname]

# wrap the value in the descriptor class, which will set
# the value within the model instance, _NOT_ the i18n key
descriptor = field.descriptor_class(field)
descriptor.__set__(model_instance, current_value)

# retrieve the descriptor value, which will check to see if the
# file reference has been committed
file = descriptor.__get__(model_instance)
if file and not file._committed:
# Commit the file to storage prior to saving the model
file.save(file.name, file.file, save=False)

# finally, mimic how `get_prep_value` works
# for "concrete" FileField instances
data[localized_file_attname] = str(file)
return data

0 comments on commit fe03015

Please sign in to comment.