Git Product home page Git Product logo

Comments (6)

vdboor avatar vdboor commented on May 22, 2024

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.

kaleb-moreland avatar kaleb-moreland commented on May 22, 2024

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.

vdboor avatar vdboor commented on May 22, 2024

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.

kaleb-moreland avatar kaleb-moreland commented on May 22, 2024

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.

vdboor avatar vdboor commented on May 22, 2024

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 early setattr(self, field, val) on a TranslatedFieldDescriptor before the QuerySet.iterator() code even changed model._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 the TranslatableModel.
  • removing the original field from the _meta options (if that's even possible), so Django no longer requests it.

from django-parler.

vdboor avatar vdboor commented on May 22, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.