Make django-hvad compatible with Django 2.0

with the inspiration from django-parler

Posted by silau on September 26, 2019
_clone() got an unexpected keyword argument '_forced_unique_fields'

If you tried to use django-hvad 1.8.0 with Django 2.0, you will probably encounter the above error message.

After some research, you will find out this GitHub issue discussing the Django 2.0 support back to 2017. But the sad story is, django-hvad is not yet officially compatible with Django 2.0. While they have provided a beta version Version 2.0.0-beta, they also stated "As many changes have been introduced, this release might introduce more bugs than usual, which is why it is flagged as beta. "

django-hvad is a very useful open-source package, especially it works really well with Django REST framework.

So depends on your use cases, you will have different options (before the official 2.0 version):

  1. Switch to django-parler painlessly if you don't heavily use Django REST framework
  2. Make django-hvad 1.8.0 play nicely with Django 2.0 by adopting some concepts from django-parler, if you need the awesome hvad serializer
  3. Switch to django-modeltranslation if you want to try another approach, a lot of code rewrite will be needed

Let's talk about option 1 first. By design, django-parler is compatible with django-hvad so you can do a simple plug and play.

# models.py

# from hvad.models import TranslatableModel, TranslatedFields
from parler.models import TranslatableModel, TranslatedFields

# basically same setup as django-hvad
class MyModel(TranslatableModel):
    translations = TranslatedFields(
        title = models.CharField(_("Title"), max_length=200)
    )

    def __str__(self):
        # return self.lazy_translation_getter('title', '')
        return self.title
# admin.py

# from hvad.admin import TranslatableAdmin
from parler.admin import TranslatableAdmin

The above setting will be enough for simple Django use cases. If you use RESTful API in a casual way, you probably want to add the serialized as well.

# serializers.py

# from hvad.contrib.restframework import TranslatableModelSerializer
from parler_rest.serializers import TranslatableModelSerializer
from parler_rest.fields import TranslatedFieldsField
from myapp.models import Country

class CountrySerializer(TranslatableModelSerializer):
    translations = TranslatedFieldsField(shared_model=Country)

    class Meta:
        model = Country
        fields = ('code', 'translations')

But we use RESTful API extensively and django-parler was not been designed for serving this purpose. Thus we need to stay with django-hvad for now until we find out alternative. Below are some snippets you might want to integrate:

"""
AVOID using .language
while hvad provides magic function (.language) to access filtered queryset of translation models, 
it doesn't compatible with Django 2.0 
so we use raw ORM mechanic to retrieve the filtered queryset 
"""

qs = MODEL.objects.filter(**SOME_FILTERS)

# qs_translations = qs.language(language)
qs_translations = MODEL.translations.field.model.objects.filter(language_code=language, master__in=qs)
"""
AVOID using get_translation_aware_manager
use raw ORM mechanic to retrieve foreign key values instead

example below assuming MODEL has a foreign key called campaign which is a TranslatableModel
"""
# from hvad.utils import get_translation_aware_manager
# tMODEL= get_translation_aware_manager(MODEL)
# tMODEL.objects.all().values('id', 'campaign__name')

MODEL.values('id', 'campaign__translations__name')
# filters.py
"""
similar mechanic could use in filters as well
"""
from django_filters import rest_framework as filters

class MODELFilter(filters.FilterSet):
    class Meta:
        model = MODEL

        fields = {
            'campaign': ['exact', ],
            'campaign__translations__name': ['icontains', ],
        }

I haven't really tried option 3 yet as I still think django-hvad works better with RESTful API. You could find out the pros and cons of two different approach on this page.

At least the snippets listed above make django-hvad works normally in Django 2.0 environment except for Django Admin Panel .

Longer-term, either django-hvad will need an official upgrade, or we will want to migrate the awesome serializer feature to django-parler.