Skip to content

Commit

Permalink
Add field name mappings functionality
Browse files Browse the repository at this point in the history
This commit allows users to specify aliases for the automatically
extracted field names of models, called "field_name_mappings".

For instance, if a model has a field name "name", but you actually want
the API the expose this field as "tag", you might supply a field name
mapping as follows:

    class Test(DjangoCreateMutation):
        class Meta:
            field_name_mappings = {
                "name": "tag"
            }

The mappings are always `db-name` => `api-name`.

In addition, two extra flags are added to the meta attributes of most
mutations:

- `use_id_suffixes_for_fk`
- `use_id_suffixes_for_m2m`

Supplying these, respectively, will add field name mappings for all
foreign keys and m2m fields (and also for the opposite side of fks) such
that "_id" are appended to the foreign key names, and "_ids" to m2m
names.

For instance, if you have a model with an FK named "user", this field
would by default be exposed as an ID field with the name "user". If you
set `use_id_suffixes_for_fk=True`, the field would now be exposed in the
API as `userId` (since snake_case is auto-converted to camelCase by
graphene).

Similarly for m2ms (or the other side of fks, m2os): Say you have a m2m
relation with a field named `groups`, setting `use_id_suffixes_for_m2m`
would make the input field be `groupsIds`. The double pluralization is
intentional (we don't accept the responsibility of handling all the ways
one might name a m2m field and converting it into proper pluralized
English). You can override this explicitly using `field_name_mappings`.

Finally, both of these settings can be set globally by using the
following two django settings:

- `GRAPHENE_DJANGO_CUD_USE_ID_SUFFIXES_FOR_FK`
- `GRAPHENE_DJANGO_CUD_USE_ID_SUFFIXES_FOR_M2M`
  • Loading branch information
tOgg1 committed Sep 1, 2024
1 parent e06c670 commit 60f624e
Show file tree
Hide file tree
Showing 12 changed files with 1,117 additions and 109 deletions.
5 changes: 5 additions & 0 deletions graphene_django_cud/consts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from django.conf import settings

OPERATION_UPDATE = "update"
OPERATION_DELETE = "delete"
OPERATION_CREATE = "create"
OPERATION_UPSERT = "upsert"

USE_ID_SUFFIXES_FOR_FK_SETTINGS_KEY = "GRAPHENE_DJANGO_CUD_USE_ID_SUFFIXES_FOR_FK"
USE_ID_SUFFIXES_FOR_M2M_SETTINGS_KEY = "GRAPHENE_DJANGO_CUD_USE_ID_SUFFIXES_FOR_M2M"
30 changes: 26 additions & 4 deletions graphene_django_cud/mutations/batch_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@
from typing import Iterable

import graphene
from django.conf import settings
from django.db import transaction
from graphene import InputObjectType
from graphene.types.utils import yank_fields_from_attrs
from graphene.utils.str_converters import to_snake_case
from graphene_django.registry import get_global_registry
from graphene_django.utils import get_model_fields
from graphql import GraphQLError

from graphene_django_cud.consts import USE_ID_SUFFIXES_FOR_FK, USE_ID_SUFFIXES_FOR_M2M, \
USE_ID_SUFFIXES_FOR_FK_SETTINGS_KEY, USE_ID_SUFFIXES_FOR_M2M_SETTINGS_KEY
from graphene_django_cud.mutations.core import DjangoCudBase, DjangoCudBaseOptions
from graphene_django_cud.registry import get_type_meta_registry
from graphene_django_cud.util import get_input_fields_for_model
from graphene_django_cud.util import get_input_fields_for_model, apply_field_name_mappings


class DjangoBatchCreateMutationOptions(DjangoCudBaseOptions):
Expand Down Expand Up @@ -46,6 +50,9 @@ def __init_subclass_with_meta__(
use_type_name=None,
field_types=None,
custom_fields=None,
use_id_suffixes_for_fk=getattr(settings, USE_ID_SUFFIXES_FOR_FK_SETTINGS_KEY, None),
use_id_suffixes_for_m2m=getattr(settings, USE_ID_SUFFIXES_FOR_M2M_SETTINGS_KEY, None),
field_name_mappings=None,
**kwargs,
):
registry = get_global_registry()
Expand Down Expand Up @@ -101,7 +108,14 @@ def __init_subclass_with_meta__(
else:
input_type_name = type_name or f"BatchCreate{model.__name__}Input"

model_fields = get_input_fields_for_model(
field_name_mappings = apply_field_name_mappings(
get_model_fields(model),
use_id_suffixes_for_fk,
use_id_suffixes_for_m2m,
field_name_mappings
)

input_fields = get_input_fields_for_model(
model,
fields,
exclude,
Expand All @@ -113,12 +127,13 @@ def __init_subclass_with_meta__(
one_to_one_extras=one_to_one_extras,
parent_type_name=input_type_name,
field_types=field_types,
field_name_mappings=field_name_mappings,
)

for name, field in custom_fields.items():
model_fields[name] = field
input_fields[name] = field

InputType = type(input_type_name, (InputObjectType,), model_fields)
InputType = type(input_type_name, (InputObjectType,), input_fields)

# Register meta-data
meta_registry.register(
Expand All @@ -132,6 +147,9 @@ def __init_subclass_with_meta__(
"foreign_key_extras": foreign_key_extras,
"one_to_one_extras": one_to_one_extras,
"field_types": field_types or {},
"use_id_suffixes_for_fk": use_id_suffixes_for_fk,
"use_id_suffixes_for_m2m": use_id_suffixes_for_m2m,
"field_name_mappings": field_name_mappings,
},
)

Expand All @@ -156,6 +174,9 @@ def __init_subclass_with_meta__(
_meta.foreign_key_extras = foreign_key_extras
_meta.many_to_one_extras = many_to_one_extras
_meta.one_to_one_extras = one_to_one_extras
_meta.use_id_suffixes_for_fk = use_id_suffixes_for_fk
_meta.use_id_suffixes_for_m2m = use_id_suffixes_for_m2m
_meta.field_name_mappings = field_name_mappings

_meta.field_types = field_types or {}
_meta.InputType = InputType
Expand Down Expand Up @@ -219,6 +240,7 @@ def mutate(cls, root, info, input):
cls._meta.foreign_key_extras,
cls._meta.many_to_one_extras,
cls._meta.one_to_one_extras,
cls._meta.field_name_mappings,
Model,
)

Expand Down
73 changes: 47 additions & 26 deletions graphene_django_cud/mutations/batch_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
from typing import Iterable

import graphene
from django.conf import settings
from django.db import transaction
from graphene import InputObjectType
from graphene.types.utils import yank_fields_from_attrs
from graphene.utils.str_converters import to_snake_case
from graphene_django.registry import get_global_registry
from graphene_django.utils import get_model_fields
from graphql import GraphQLError

from graphene_django_cud.consts import USE_ID_SUFFIXES_FOR_M2M_SETTINGS_KEY, USE_ID_SUFFIXES_FOR_FK_SETTINGS_KEY
from graphene_django_cud.mutations.core import DjangoCudBase, DjangoCudBaseOptions
from graphene_django_cud.registry import get_type_meta_registry
from graphene_django_cud.util import get_input_fields_for_model
from graphene_django_cud.util import get_input_fields_for_model, apply_field_name_mappings


class DjangoBatchUpdateMutationOptions(DjangoCudBaseOptions):
Expand All @@ -25,28 +28,31 @@ class Meta:

@classmethod
def __init_subclass_with_meta__(
cls,
_meta=None,
model=None,
permissions=None,
login_required=None,
fields=(),
only_fields=(), # Deprecated in favor of `fields`
exclude=(),
exclude_fields=(), # Deprecated in favor of `exclude`
optional_fields=(),
required_fields=(),
auto_context_fields=None,
return_field_name=None,
many_to_many_extras=None,
foreign_key_extras=None,
many_to_one_extras=None,
one_to_one_extras=None,
type_name=None,
use_type_name=None,
field_types=None,
custom_fields=None,
**kwargs,
cls,
_meta=None,
model=None,
permissions=None,
login_required=None,
fields=(),
only_fields=(), # Deprecated in favor of `fields`
exclude=(),
exclude_fields=(), # Deprecated in favor of `exclude`
optional_fields=(),
required_fields=(),
auto_context_fields=None,
return_field_name=None,
many_to_many_extras=None,
foreign_key_extras=None,
many_to_one_extras=None,
one_to_one_extras=None,
type_name=None,
use_type_name=None,
field_types=None,
use_id_suffixes_for_fk=getattr(settings, USE_ID_SUFFIXES_FOR_FK_SETTINGS_KEY, None),
use_id_suffixes_for_m2m=getattr(settings, USE_ID_SUFFIXES_FOR_M2M_SETTINGS_KEY, None),
field_name_mappings=None,
custom_fields=None,
**kwargs,
):
registry = get_global_registry()
meta_registry = get_type_meta_registry()
Expand Down Expand Up @@ -101,7 +107,14 @@ def __init_subclass_with_meta__(
else:
input_type_name = type_name or f"BatchUpdate{model.__name__}Input"

model_fields = get_input_fields_for_model(
field_name_mappings = apply_field_name_mappings(
get_model_fields(model),
use_id_suffixes_for_fk,
use_id_suffixes_for_m2m,
field_name_mappings
)

input_fields = get_input_fields_for_model(
model,
fields,
exclude,
Expand All @@ -114,12 +127,13 @@ def __init_subclass_with_meta__(
parent_type_name=input_type_name,
field_types=field_types,
ignore_primary_key=False,
field_name_mappings=field_name_mappings,
)

for name, field in custom_fields.items():
model_fields[name] = field
input_fields[name] = field

InputType = type(input_type_name, (InputObjectType,), model_fields)
InputType = type(input_type_name, (InputObjectType,), input_fields)

# Register meta-data
meta_registry.register(
Expand All @@ -133,6 +147,9 @@ def __init_subclass_with_meta__(
"foreign_key_extras": foreign_key_extras,
"one_to_one_extras": one_to_one_extras,
"field_types": field_types or {},
"use_id_suffixes_for_fk": use_id_suffixes_for_fk,
"use_id_suffixes_for_m2m": use_id_suffixes_for_m2m,
"field_name_mappings": field_name_mappings,
},
)

Expand All @@ -157,6 +174,9 @@ def __init_subclass_with_meta__(
_meta.foreign_key_extras = foreign_key_extras
_meta.many_to_one_extras = many_to_one_extras
_meta.one_to_one_extras = one_to_one_extras
_meta.use_id_suffixes_for_fk = use_id_suffixes_for_fk
_meta.use_id_suffixes_for_m2m = use_id_suffixes_for_m2m
_meta.field_name_mappings = field_name_mappings

_meta.field_types = field_types or {}
_meta.InputType = InputType
Expand Down Expand Up @@ -232,6 +252,7 @@ def mutate(cls, root, info, input):
cls._meta.foreign_key_extras,
cls._meta.many_to_one_extras,
cls._meta.one_to_one_extras,
cls._meta.field_name_mappings,
Model,
)

Expand Down
Loading

0 comments on commit 60f624e

Please sign in to comment.