deschler / django-modeltranslation

Translates Django models using a registration approach.
BSD 3-Clause "New" or "Revised" License
1.38k stars 257 forks source link

Model field translation overrides default langauge for both fields on save #593

Open jacobburrell opened 3 years ago

jacobburrell commented 3 years ago

I have a model called 'Status' with one field, 'name'.

I registered the model:

class Status(Model):
    def __str__(self):
        return self.name
    class Meta:
        verbose_name = _('Status')
        verbose_name_plural = _('Statuses')

    name = CharField(verbose_name=_('Status Name'), max_length=100)
@register(Status)
class StatusTranslationOptions(TranslationOptions):
    fields = ('name', )

I registered with and without admin:

class StatusAdmin(TranslationAdmin):
    pass

admin.site.register(Status, StatusAdmin)

I enabled English and Spanish translation and whichever I set my browser configuration to, that's the language that will override the other.

image

I have other models in the same app that are similar that don't override the same way.

last-partizan commented 3 years ago

Can you explain in steps what's happening?

name_en: English name name_es: Spanish name

You save object, and what overriding what?

jacobburrell commented 3 years ago

The input in Status Name [es] will override the contents of Status Name [en] if Spanish is the default. If English is the default, the [es] field will override the contents of the [en field].

The example of the screenshot upon save will result in both [en] and [es] fields having the value of [en]'s.

Thus after save: Status Name [en]: English override Status Name [es]: English override

last-partizan commented 3 years ago

Now i understand, but have no idea why it happens.

Was it working before last few updates? What versions of django/django-modeltranslation are you using? Are you doing some stuff in signal handlers which may change those fields?

kathmandu777 commented 2 years ago

@last-partizan @jacobburrell Hi, is there any update on the issue? I have the same problem. Thank you!

last-partizan commented 2 years ago

No, there is nothing new.

Please, provide repository with minimal code to reproduce this issue. Try to fix this yourself and make PR.

kathmandu777 commented 2 years ago

@last-partizan I suspect that the cache is affected by this issue.

Assume name is a field that is translated by django model translation.

queryset = queryset.annotate(
    foo=Case(
        When(Q(name__in=["hoge", "fuga"],  then=True),
        default=Value(False),
        output_field=BooleanField(),
    )
)

I define this and call it at some point. Then, when I try to edit a record in the same model as this one from the Django Admin page, model field translation overrides default langauge for both fields. (Looking directly at the DB, we can confirm that it is stored correctly without being overridden)

So I suspect that when the query is executed, the field value is cached and used as is for display on the django admin page.

Is there a function or option to clear the cache?

Thank you!

last-partizan commented 2 years ago

I don't remember any caching (but i just maintainer, not author). Better check source code yourself.

t-mann commented 2 years ago

The problem is in the cache as it was mentioned by @kathmandu777 But the cache is not a cache like redis or memcache. The problem sits in the django.db.models.fields.Field.get_col that returns self.cached_col that is actually decorated by @cached_property from django.utils.functional.

The fast workaround that works for me was to create custom register decorator with cleaning field cached properties. I only tested that admin part works correctly thus there still may be some side effects.

Solution allows to fix an issue as quick as possible, specially for projects that are using old versions of the model translations library. I suppose that there is better way to fix this issue in new versions.

"""
Register Decorator Patch
"""

__all__ = [
    'register'
]

# Standard library imports.
import logging

# Related third party imports.
from django.db.models.base import ModelBase

# Local application/library specific imports.

log = logging.getLogger('modeltranslation_patch')

def register(model_or_iterable, raise_exceptions: bool = False, **options):
    """
    Patched decorator to fix issue https://github.com/deschler/django-modeltranslation/issues/593
    Decorator has 1 more parameter `raise_exceptions` that may be used to control exception raising
    in a case if field retrieve from model meta failed
    """
    from modeltranslation.translator import translator, TranslationOptions

    def wrapper(opts_class):
        if not issubclass(opts_class, TranslationOptions):
            raise ValueError('Wrapped class must subclass TranslationOptions.')

        # >>>>>>>>>>> PATCH BEGIN <<<<<<<<<<<<<<
        if isinstance(model_or_iterable, ModelBase):
            models = [model_or_iterable]
        else:
            models = model_or_iterable

        for cls in models:
            for f_name in opts_class.fields:
                try:
                    f_obj = cls._meta.get_field(f_name)
                except Exception as e:
                    if raise_exceptions:
                        raise e
                    log.exception(f'Failed to get field with name `{f_name}` on model {cls.__name__}:\n{e}')
                    continue

                if 'cached_col' in f_obj.__dict__:
                    del f_obj.__dict__['cached_col']

        # >>>>>>>>>>>  PATCH END  <<<<<<<<<<<<<<

        translator.register(models, opts_class, **options)
        return opts_class

    return wrapper