Comments (6)
What exactly should be wrapped in TranslatedFields()?
Well, the fields you want to have translated. They can be empty, to have you an empty TranslatedFieldsModel
that only has a "master" and "language_code" field.
(that was actually used as a workaround in the early days, because inlines required the parent model to be translatable too)
In this case the contents of TranslatedFields is provided; but then, having to redefine a field in the untranslated model and/or rename it (tr_title) strikes me as less than ideal. Am I missing something?
No I missed something there, I think it can be named "title" after all. I did this to avoid clashes with the original "title" field that the AbstractProduct already provides. If you can confirm that the Oscar integration withs fine with the tr_
part removed, I'd be happy to update the gist.
Could parler provide a function that moves fields across classes to avoid field redefinition
Wow, that is a nice idea. If you can cook up some code which does this in a sensible way, I'm all for it. I'm not sure about moving the original (there may be more classes extending from the abstract model). If you can redefine the field, that would even be better. The sementics may have to change, e.g. something like this:
class Product(AbstractProduct, TranslatableModel):
title = TranslatedField(redefine_field=True)
translations = TranslatedFields(
title = models.CharField(...),
description = models.TextField(...),
)
from django-parler.
Thank you for commenting.
After toying a bit with the idea of allowing TranslatedFields wrapping references to fields from a parent model without the need of field redefinition, I seem to have found a relatively simple approach that does the trick. Actually I tried it patching Parler 1.1 and running with Python 3.4 / Django 1.7. All the use cases in the "Quick Start Guide" work.
(The code below should be seen as a proof of concept and the design almost certainly could use of further refinement. Unfortunately my django-model-metaclass-fu is not that great and I don't have the time to polish the solution. So, sorry, no pull requests. But I suspect that for the well versed it shouldn't take long to do that ;-)
The basic idea came from this observation: in the simple use case of fields wrapped in TranslatedFields that don't have a corresponding field in a parent model, Parler generates a TranslatedFieldDescriptor for them in the TranslatableModel class, but no field entries in _meta.fields. Everything is good. But when there is a field in a parent model with the same name as a field wrapped in TranslatedFields, both a TranslatedFieldDescriptor and a field entry in _meta.fields get created, and that breaks the API in several ways.
So the real issue was never a matter of allowing TranslatedFields to contain references to fields from a parent model but rather how to allow TranslatedFields to contain fields with the same name of inherited ones and have Parler generating only a TranslatedFieldDescriptor but not an entry in _meta.fields.
The main points in this proof of concept implementation to achieve what was described above are:
- a new metaclass TranslatedFieldsOverride extends ModelBase;
- TranslatedFieldsOverride.new registers all TranslatedFields and delegates to ModelBase.new;
- TranslatedFieldsOverride.add_to_class looks up the current field in the registry; if it finds it, it returns without calling ModelBase.add_to_class, so no field gets added to _meta.fields;
- user defined TranslatableModel classes that have translation fields that "override" (same name) a field from a parent model should be marked with the TranslatedFieldsOverride metaclass.
# add this to parler/models.py
class TranslatedFieldsOverride(ModelBase):
"""
Use in conjunction with a TranslatableModel containing TranslatedFields
intended to "override" fields from a parent model (abstract or concrete).
"Overriding" a field can be done by either referring to it or redefining it.
"""
# Registry of all translated fields as tuples of (model_name, field_name).
translated_fields = set()
def __new__(mcl, name, bases, attrs):
"""
For any TranslatedFields in class to be created: register its fields
for lookup in the add_to_class override.
"""
for obj in attrs.values():
if isinstance(obj, TranslatedFields):
trans_fields = set((name, field_name) for field_name in obj.fields)
mcl.translated_fields.update(trans_fields)
return super(TranslatedFieldsOverride, mcl).__new__(mcl, name, bases, attrs)
def add_to_class(model, field_name, value):
"""
We override this method to prevent translated fields from being added to
`model` as regular fields: they should be just TranslatedFieldDescriptor.
"""
if (model.__name__, field_name) in TranslatedFieldsOverride.translated_fields:
return
# Can't get this thru `super` since there's no reference to `mcl` available here.
ModelBase.add_to_class(model, field_name, value)
# example of model with translated fields "overriding" inherited ones
from django.db import models
from parler.models import TranslatableModel, TranslatedFields, TranslatedFieldsOverride
class W_(models.Model):
class Meta:
abstract = True
t = models.CharField(max_length=80, null=1) # translate this field...
u = models.CharField(max_length=80, null=1) # this one as well...
v = models.CharField(max_length=80, default='abc') # but not this one
class W(W_, TranslatableModel, metaclass=TranslatedFieldsOverride):
translations = TranslatedFields(
t = W_._meta.get_field('t'), # refer to field from parent model...
u = models.CharField(max_length=90, default='xyz'), # or completely redefine it...
w = models.CharField(max_length=50, null=1), # or create a new one unrelated to parent model
)
x = models.CharField(max_length=80, null=1) # non-translated field
# now the W model looks like this:
...
In [1]: W._meta.fields
Out[1]:
[<django.db.models.fields.AutoField: id>,
<django.db.models.fields.CharField: v>,
<django.db.models.fields.CharField: x>]
In [2]: W.t, W.u, W.w
Out[2]:
(<TranslatedFieldDescriptor for W.t>,
<TranslatedFieldDescriptor for W.u>,
<TranslatedFieldDescriptor for W.w>)
Just a couple of final thoughts:
Obviously, making a metaclass part of the public API is not an ideal alternative and probably it should be hidden behind a new specialized TranslatableModel for this particular use case (that's where my django-model-metaclass-fu fails me and I'll leave it as an exercise for the reader :-).
The metaclass name (TranslatedFieldsOverride) is admittedly not great. (Technically, there is no field "override" going on, just a mechanism to prevent fields from being added to _meta.fields.) But from the user's POV, probably the name makes some sense, as conceptually this could be thought as a translated field overriding an untranslated one.
from django-parler.
Great work!
Maybe this add_to_class()
trick could even be part of a standard meta class in the regular TranslatableModel
model. It does require some tinkering to deal with manually constructed TranslatedFieldsModel
models, but nothing that can't be fixed in some way.
from django-parler.
You are probably right: there's no need to introduce any new entities to the code base (read TranslatedFieldsOverride). But, again, unfortunately right now I won't have the time to go thru the effort of hooking it safely into TranslatableModel. So please feel free to take the idea and run with it in any direction you'd like to.
from django-parler.
I just want to let you know that having multiple TranslatedFields
objects per model / proxy / inherited model are now supported in v1.2.1
I've also been playing with overriding existing fields. This is complicated however, due to the following:
- When using
TranslatedFields(..)
, the translated model is constructed during the construction of the regular model. - At that point, it's Meta class is not fully initialized yet; it doesn't know it's parents, etc.. (these are only known in the
_prepare()
function). - Hence, the code can't check whether a parent field exists.
- Secondly, since the original field name exists, Django will request it from the DB, and pass it to the translated fields in the
Model(*row)
function. This causes an earlysetattr(self, field, val)
on aTranslatedFieldDescriptor
before theQuerySet.iterator()
code even changedmodel._state.adding = False
. Hence, it's impossible to detect whether objects should be queried, or can be read from the cache.
Changing this requires:
- delaying the processing of
TranslatedFields
, by adding a metaclass to theTranslatableModel
. - removing the original field from the
_meta
options (if that's even possible), so Django no longer requests it.
from django-parler.
I've opened a new issue, to keep the issue tracker clean. (this discussion is no longer about clarifying things ;))
from django-parler.
Related Issues (20)
- django-hvad migration: How to modify old migrations?
- django-treebeard
- Clearing FileField don't work
- dumpdata & loaddata manage.py command with django parler HOT 1
- "wrong number of arguments for 'del' command" when deleting TranslatableModel using Redis cache
- Django Parler shows translated object one time per translation on the admin list HOT 5
- Store translated model to/from more than one database fails
- get_or_create not working HOT 2
- (Bug) `bulk_create` on TranslatableModel HOT 1
- (Bug) Redis cache "wrong number of arguments for 'del' command"
- Async and parler
- Is the package still maintained? HOT 2
- django-parler does not honor validators attribute -- validation possible at all?
- how to translate a form with parler_tags.
- django-parler with unique field raise IntegrityError
- how do I translate a easy_thumbnails.fields.ThumbnailerImageField
- Tags are not being saved when using django-taggit with django-parler.
- Creation of the parent, although max length has been exceeded
- Custom manager is not working
- What's the status of the project? HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from django-parler.