graphql-python / graphene-django

Build powerful, efficient, and flexible GraphQL APIs with seamless Django integration.
http://docs.graphene-python.org/projects/django/en/latest/
MIT License
4.29k stars 768 forks source link

Translatable Model Fields do not get listed in DjangoObjectType #1195

Open edwillys opened 3 years ago

edwillys commented 3 years ago

Currently, if I have a DjangoObjectType node whose model contains django-parler's TranslatedFields, these do not get listed there and cannot be queried, mutated, etc...

import graphene
from graphene_django.types import DjangoObjectType
from parler.models import TranslatableModel, TranslatedFields

class MyModel(TranslatableModel):
    content_translations = TranslatedFields(
        title = models.CharField(max_length=200, default=""),
    )

class MyNode(DjangoObjectType):
    class Meta:
        model = MyModel

class MyQuery(graphene.ObjectType):
    node = graphene.List(MyNode)

I would expect for example, the following query to be valid:

query{
  node{
    title
  }
}

The root-cause seems to lie in the way the model fields are populated inside utils.py:

def get_model_fields(model):
    local_fields = [
        (field.name, field)
        for field in sorted(
            list(model._meta.fields) + list(model._meta.local_many_to_many)
        )
    ]

The following makes the translatable fields available, but it is not enough for the query to succeed:

def get_model_fields(model):
    local_fields = [
        (field.name, field)
        for field in list(model._meta.get_fields()) # sorted() doesn't work for OneToMany relationship. get_fields() has been available for a while in django
    ]

As a workaround I can define each translatable field as an extra field in my query object, though this is not very scalable.

I'm running on Ubuntu 64 bit with the following python packages.

django-parler===2.2 
graphene===2.1.8     
graphene-django===2.15.0

Thanks in advance!

wodCZ commented 3 years ago

Maybe you need to define a converter for the TranslatedFields type, similarly to other fields?

edwillys commented 3 years ago

Hello @wodCZ ,

Thanks for the reply. I had already tried this and unfortunately it doesn't help, since the fields defined in TranslatedFields are not listed as model fields. Otherwise convert_django_field would have raised an exception already.

W1773ND commented 2 years ago

UP

underdoeg commented 5 months ago

I use something like this.

So far it seems to work ok

def resolve_translations(model: Type[TranslatableModel], instance, info: GraphQLResolveInfo):
    data = {}
    for lang, val in info.return_type.fields.items():
        if not instance.has_translation(lang):
            data[lang] = None
            continue
        instance.set_current_language(lang)
        translations = {}
        # data[field] =
        for key in val.type.fields:
            translations[key] = getattr(instance, key)
        data[lang] = translations
    return data

def get_translations_schema(model: Type[TranslatableModel]):
    model_name = model.__name__

    # collect fields
    fields = {}
    for field in model._parler_meta.get_all_fields():
        fields[field] = graphene.String()
    fields_schema = type("%sTranslationFields" % model_name, (graphene.ObjectType,), fields)

    # collect languages
    lang_fields = {}
    all_languages = [lang['code'] for lang in settings.PARLER_LANGUAGES[None]]

    for lang in all_languages:
        lang_fields[lang] = graphene.Field(fields_schema)

    return graphene.Field(type("%sTranslations" % model_name, (graphene.ObjectType,), lang_fields),
                          resolver=lambda instance, info: resolve_translations(model, instance, info))

class PageType(DjangoObjectType):
    translations = get_translations_schema(Page)

    class Meta:
        model = Page