Open Alkalit opened 8 years ago
Hello,
ManyToManyField
are not directly supported as translated fields in hvad. There is no check, because some projects do use them, but this involves digging through some internals. The central paradigm of hvad is making a TranslatableModel
behave like it is a regular model. Which means, from other models, translations are not normally visible as a separate entity, they are just drop-in replacements for field values.
There is inherent ambiguity in “translating a m2m field”. Depending on context, this could mean:
If you are in case 2, what this actually means is you are not just adding translations to your database structure. Instead, your database actually interacts with language in a complex way, which you need to design by yourself. This goes beyond the scope of model translation packages that I know of, hvad included.
You may still use hvad to create the structure, but most of the syntactic sugar will be mostly useless for this specific relation. If you choose to follow that path, there are two ways you can achieve this:
1) Store a relation to the main model, and add a language field.
class ProductSpecification(models.Model):
product = models.ForeignKey(Product)
product_language = models.CharField(max_length=8)
specification = models.ForeignKey(
File,
related_name='spec_product_spec',
)
class Meta:
unique_together = ['product', 'product_language', 'specification']
Advantage: does not break hvad
's paradigm.
Drawback: database integrity cannot be strictly enforced. There may be ProductSpecification
entries that reference an Product
in a language it is not translated in. You'll have to check how your code reacts when this occurs.
2) Break through hvad abstraction layer to reference the translation directly. Basically what you did:
class ProductSpecification(models.Model):
product_translation = models.ForeignKey(Product._meta.translations_model)
specification = models.ForeignKey(
File,
related_name='spec_product_spec',
)
class Meta:
unique_together = ['product_translation', 'specification']
(Notice how we create a foreign key to the translations model)
Advantage: integrity is guaranteed. Drawback: queries that come from the through model will bypass hvad. This means your code will have to know about hvad internals, which could mean harder upgrade path if they change in the future.
As you cannot use hvad's syntactic sugar, you'll have to know that the translation model has the following internal fields, which you will need to build your queries:
master
is a ForeignKey
to the Product
model.language_code
is a CharField
that stores the language of the translation.TranslatedFields
.As you have seen, TranslatableAdmin
will not handle those correctly, because the M2M relation actually points back to ProductTranslation
not to Product
. Therefore ProductSetForm(TranslatableModelForm)
should actually be ProductTranslationSetForm(ModelForm)
. Depending on how you want it to work, you may want to filter the inline queryset so it only shows one language. You may also want to allow the creation of a full Product
and not just new translations of existing products. You will also have to rule whether deleting an entry in one language should remove just that entry or the whole product.
There are numerous edge cases, and appropriate behaviors depend on application specifications. This is why it is out of the scope of a general module such as hvad. Still, if you do this and need help with the hvad side of the things, feel free to ask, I'll do my best.
Thank you for quick response. Under "make translatable" I just mean that, depending on language, Product should have its own set of files. For example:
product_en = Product.objects.language('en').get(id=1)
product_en.specification_file.all()
# [<File: file_name_1>, <File: file_name_2>]
product_ru = Product.objects.language('ru').get(id=1)
product_ru.specification_file.all()
# [<File: file_name_3>, <File: file_name_4>]
In this specific example, would it be correct to assume the language is not a “display language” like te on hvad uses, but is part of the properties of the specification file? It would seem logical, as a specification for another country is not just a matter of translating, but a completely different document.
In this case you could represent it like this:
class File(models.Model):
# other fields
applicable_language = models.CharField(max_length=10, db_index=True)
class Product(TranslatableModel):
specification_file = models.ManyToManyField(File, blank=True, related_name='file_products')
translations = TranslatedFields(
title=models.CharField(max_length=255, db_index=True),
)
You would then do this:
product_en = Product.objects.language('en').get(pk=1)
product_en.specification_file.filter(applicable_language='en')
# [<File: file_name_1>, <File: file_name_2>]
product_ru = Product.objects.language('ru').get(pk=1)
product_ru.specification_file.filter(applicable_language='ru')
# [<File: file_name_3>, <File: file_name_4>]
It might look like the language is repeated, but in fact it is not, because display language and applicable language are not the same thing. In fact, you could even do this:
product_en = Product.objects.language('en').get(pk=1)
product_en.specification_file.filter(applicable_language='ru')
# [<File: file_name_3>, <File: file_name_4>]
… this would make sense, for instance if you have a product manager who speaks English, yet wants to check which files are applicable for Russia.
Ok, so I solve this with adding language field on File
class File(models.Model):
# other fields
language_code = models.CharField(max_length=10, default='ru', db_index=True)
and then in admin:
class SpecificationAdmin(SortableTabularInline):
model = ProductSpecification
extra = 1
def formfield_for_foreignkey(self, db_field, request, **kwargs):
lang = get_language()
if db_field.name == "specification":
kwargs["queryset"] = File.objects.filter(language_code=lang)
return super(SpecificationAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
def get_queryset(self, request):
lang = get_language()
qs = super(SpecificationAdmin, self).get_queryset(request)
qs = qs.filter(specification__language_code=lang)
return qs
PS: I also looked deeper into doc and didn't find any notes about improper m2m fields support. Maybe it should to note?
I'm glad you could solve your problem.
You're right, the explanation I wrote here should be included somewhere in the documentation. It's not that it's unsupported per se — you can actually create them. Rather, it's the inherent ambiguity in what they should mean that requires handling them explicitly and manually. Perhaps hvad should expose a bit more of its internals through a formal API as well. But there is the usual trade-off, the more it exposes, the less it can evolve and improve freely.
Hi, I have a tricky problem with m2m field and admin intergration. I want to make m2m field translatable. Let say I have a model Product:
and model ProductSpecification that looks like this:
Now I want to make Product.specification_file translatable and I do:
wich gives me:
on makemigrations. Then I do
Wich is probably works, but when I go to admin page after migrations it gives me:
Wich I dont know how to resolve.
Also admin.py (with some cuts)