예제 #1
0
def get_input_fields_for_model(
    model,
    only_fields,
    exclude_fields,
    optional_fields=(),
    required_fields=(),
    many_to_many_extras=None,
    foreign_key_extras=None,
    many_to_one_extras=None,
    one_to_one_extras=None,
    parent_type_name="",
    field_types=None,
    ignore_primary_key=True,
) -> OrderedDict:

    registry = get_global_registry()
    meta_registry = get_type_meta_registry()
    model_fields = get_model_fields(model)

    many_to_many_extras = resolve_many_to_many_extra_auto_field_names(
        many_to_many_extras or {}, model, parent_type_name)
    many_to_one_extras = resolve_many_to_one_extra_auto_field_names(
        many_to_one_extras or {}, model, parent_type_name)
    foreign_key_extras = resolve_foreign_key_extra_auto_field_names(
        foreign_key_extras or {}, model, parent_type_name)
    one_to_one_extras = resolve_one_to_one_extra_auto_field_names(
        one_to_one_extras or {}, model, parent_type_name)

    field_types = field_types or {}
    one_to_one_fields: List[Union[models.OneToOneRel,
                                  models.OneToOneField]] = []

    fields = OrderedDict()
    fields_lookup = {}

    for name, field in model_fields:
        # We ignore the primary key
        if getattr(field, "primary_key", False) and ignore_primary_key:
            continue

        # If the field has an override, use that
        if name in field_types:
            fields[name] = field_types[name]
            continue

        # Save for later
        fields_lookup[name] = field

        is_not_in_only = only_fields and name not in only_fields
        # is_already_created = name in options.fields
        is_excluded = name in exclude_fields  # or is_already_created
        # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name
        is_no_backref = str(name).endswith("+")
        if is_not_in_only or is_excluded or is_no_backref:
            # We skip this field if we specify only_fields and is not
            # in there. Or when we exclude this field in exclude_fields.
            # Or when there is no back reference.
            continue

        required = None
        if name in optional_fields:
            required = False
        elif name in required_fields:
            required = True

        converted = convert_django_field_with_choices(
            field,
            registry,
            required,
            many_to_many_extras.get(name, {}).get("exact"),
            foreign_key_extras.get(name, {}),
            one_to_one_extras.get(name, {}),
        )
        fields[name] = converted

        if type(field) in (models.OneToOneRel, models.OneToOneField):
            one_to_one_fields.append(field)

    # Create the one to one field types here.
    for name, data in one_to_one_extras.items():
        field: Union[models.OneToOneRel,
                     models.OneToOneField] = fields_lookup.get(name)

        if field is None:
            raise ValueError(
                f"Error adding extras for {name} in model f{model}. Field {name} does not exist."
            )

        type_name = data.get("type")

        if type_name == "ID":
            continue

        # On OneToOnerels we can get the reverse field name from "field.field.name", as we have a direct
        # reference to the reverse field that way. For OneToOneFields we need to go through "field.target_field".
        reverse_field_name = (field.field.name if isinstance(
            field, models.OneToOneRel) else field.remote_field.name)

        converted_fields = get_input_fields_for_model(
            field.related_model,
            data.get("only_fields", ()),
            data.get(
                "exclude_fields",
                (reverse_field_name,
                 )),  # Exclude the field referring back to the foreign key
            data.get("optional_fields", ()),
            data.get("required_fields", ()),
            data.get("many_to_many_extras"),
            data.get("foreign_key_extras"),
            data.get("many_to_one_extras"),
            parent_type_name=type_name,
            field_types=data.get("field_types"),
        )

        InputType = type(type_name, (InputObjectType, ), converted_fields)
        registry.register_converted_field(type_name, InputType)
        meta_registry.register(type_name, data)

    # Create extra many_to_many_fields
    for name, extras in many_to_many_extras.items():
        field: Union[models.ManyToManyField,
                     models.ManyToManyRel] = fields_lookup.get(name)
        if field is None:
            raise GraphQLError(
                f"Error adding extras for {name} in model f{model}. Field {name} does not exist."
            )

        for extra_name, data in extras.items():

            # This is handled above
            if extra_name == "exact":
                continue

            if isinstance(data, bool):
                data = {}

            _type_name = data.get("type")
            _field = convert_many_to_many_field(field, registry, False, data,
                                                None)

            # Default to the same as the "exact" version
            if not _field:
                _field = fields[name]

            # operation = data.get('operation') or get_likely_operation_from_name(extra_name)
            fields[name + "_" + extra_name] = _field

    for name, extras in many_to_one_extras.items():
        field: models.ManyToOneRel = fields_lookup.get(name)
        if field is None:
            raise GraphQLError(
                f"Error adding extras for {name} in model f{model}. Field {name} does not exist."
            )

        for extra_name, data in extras.items():

            argument_name = data.get("name", name + "_" + extra_name)

            # Override default
            if extra_name == "exact":
                argument_name = name

            if isinstance(data, bool):
                data = {"type": "ID"}

            type_name = data.get("type")
            if type_name and type_name != "ID":
                # Create new type.
                operation_name = data.get(
                    "operation", get_likely_operation_from_name(extra_name))

                converted_fields = get_input_fields_for_model(
                    field.related_model,
                    data.get("only_fields", ()),
                    data.get(
                        "exclude_fields", (field.field.name, )
                    ),  # Exclude the field referring back to the foreign key
                    data.get("optional_fields", ()),
                    data.get("required_fields", ()),
                    data.get("many_to_many_extras"),
                    data.get("foreign_key_extras"),
                    data.get("many_to_one_extras"),
                    data.get("one_to_one_extras"),
                    parent_type_name=type_name,
                    field_types=data.get("field_types"),
                    # Don't ignore the primary key on updates
                    ignore_primary_key=operation_name != "update",
                )
                InputType = type(type_name, (InputObjectType, ),
                                 converted_fields)
                registry.register_converted_field(field, InputType)
                meta_registry.register(
                    type_name,
                    {
                        "auto_context_fields": data.get(
                            "auto_context_fields", {}),
                        "optional_fields": data.get("optional_fields", ()),
                        "required_fields": data.get("required_fields", ()),
                        "many_to_many_extras": data.get(
                            "many_to_many_extras", {}),
                        "many_to_one_extras": data.get("many_to_one_extras",
                                                       {}),
                        "foreign_key_extras": data.get("auto_context_fields",
                                                       {}),
                        "one_to_one_extras": data.get("one_to_one_extras", {}),
                        "field_types": data.get("field_types", {}),
                    },
                )
                registry.register_converted_field(type_name, InputType)
                _field = graphene.List(
                    type(type_name, (InputObjectType, ), converted_fields),
                    required=False,
                )
            else:
                _field = convert_many_to_many_field(field, registry, False,
                                                    data, None)

            fields[argument_name] = _field

    return fields
예제 #2
0
def get_input_fields_for_model(
    model,
    only_fields,
    exclude_fields,
    optional_fields=(),
    required_fields=(),
    many_to_many_extras=None,
    foreign_key_extras=None,
    many_to_one_extras=None,
    parent_type_name="",
    field_types=None,
    ignore_primary_key=True,
) -> OrderedDict:

    registry = get_global_registry()
    meta_registry = get_type_meta_registry()
    model_fields = get_model_fields(model)

    many_to_many_extras = many_to_many_extras or {}
    foreign_key_extras = foreign_key_extras or {}
    many_to_one_extras = many_to_one_extras or {}
    field_types = field_types or {}

    fields = OrderedDict()
    fields_lookup = {}
    for name, field in model_fields:
        # We ignore the primary key
        if getattr(field, "primary_key", False) and ignore_primary_key:
            continue

        # If the field has an override, use that
        if name in field_types:
            fields[name] = field_types[name]
            continue

        # Save for later
        fields_lookup[name] = field

        is_not_in_only = only_fields and name not in only_fields
        # is_already_created = name in options.fields
        is_excluded = name in exclude_fields  # or is_already_created
        # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name
        is_no_backref = str(name).endswith("+")
        if is_not_in_only or is_excluded or is_no_backref:
            # We skip this field if we specify only_fields and is not
            # in there. Or when we exclude this field in exclude_fields.
            # Or when there is no back reference.
            continue

        required = None
        if name in optional_fields:
            required = False
        elif name in required_fields:
            required = True

        converted = convert_django_field_with_choices(
            field,
            registry,
            required,
            many_to_many_extras.get(name, {}).get("exact"),
            foreign_key_extras.get(name, {}),
        )
        fields[name] = converted

    # Create extra many_to_many_fields
    for name, extras in many_to_many_extras.items():
        field = fields_lookup.get(name)
        if field is None:
            raise GraphQLError(
                f"Error adding extras for {name} in model f{model}. Field {name} does not exist."
            )

        for extra_name, data in extras.items():

            # This is handled above
            if extra_name == "exact":
                continue

            if isinstance(data, bool):
                data = {}

            _type_name = data.get("type")
            _field = convert_many_to_many_field(field, registry, False, data,
                                                None)

            # Default to the same as the "exact" version
            if not _field:
                _field = fields[name]

            # operation = data.get('operation') or get_likely_operation_from_name(extra_name)
            fields[name + "_" + extra_name] = _field

    for name, extras in many_to_one_extras.items():
        field = fields_lookup.get(name)
        if field is None:
            raise GraphQLError(
                f"Error adding extras for {name} in model f{model}. Field {name} does not exist."
            )

        for extra_name, data in extras.items():

            argument_name = data.get("name", name + "_" + extra_name)

            # Override default
            if extra_name == "exact":
                argument_name = name

            if isinstance(data, bool):
                data = {"type": "ID"}

            _type = data.get("type")
            if not _type or _type == "auto":
                # Create new type.
                operation_name = data.get(
                    "operation", get_likely_operation_from_name(extra_name))
                _type_name = data.get(
                    "type_name",
                    f"{parent_type_name or ''}{operation_name.capitalize()}{model.__name__}{name.capitalize()}",
                )

                converted_fields = get_input_fields_for_model(
                    field.related_model,
                    data.get("only_fields", ()),
                    data.get(
                        "exclude_fields", (field.field.name, )
                    ),  # Exclude the field referring back to the foreign key
                    data.get("optional_fields", ()),
                    data.get("required_fields", ()),
                    data.get("many_to_many_extras"),
                    data.get("foreign_key_extras"),
                    data.get("many_to_one_extras"),
                    parent_type_name=_type_name,
                    field_types=data.get("field_types"),
                    # Don't ignore the primary key on updates
                    ignore_primary_key=operation_name != "update")
                InputType = type(_type_name, (InputObjectType, ),
                                 converted_fields)
                meta_registry.register(
                    _type_name,
                    {
                        "auto_context_fields": data.get(
                            "auto_context_fields", {}),
                        "optional_fields": data.get("optional_fields", ()),
                        "required_fields": data.get("required_fields", ()),
                        "many_to_many_extras": data.get(
                            "many_to_many_extras", {}),
                        "many_to_one_extras": data.get("many_to_one_extras",
                                                       {}),
                        "foreign_key_extras": data.get("auto_context_fields",
                                                       {}),
                        "field_types": data.get("field_types", {}),
                    },
                )
                _field = graphene.List(
                    type(_type_name, (InputObjectType, ), converted_fields),
                    required=False,
                )
            else:
                _field = convert_many_to_many_field(field, registry, False,
                                                    data, None)

            fields[argument_name] = _field

    return fields
예제 #3
0
def convert_many_to_many_like_field(data, name, extra_name, parent_type_name,
                                    field, registry, meta_registry):
    if isinstance(data, bool):
        data = {"type": "ID"}

    type_name = data.get("type")

    if type_name and type_name != "ID":
        # Check if type already exists
        existing_type = registry.get_converted_field(type_name)

        if existing_type:
            return graphene.List(existing_type, required=False)

        is_auto_field = data.get("auto", False)
        if not is_auto_field:
            return create_dynamic_list_type(field, type_name, registry, False)

        # Create new type.
        operation_name = data.get("operation",
                                  get_likely_operation_from_name(extra_name))

        exclude_fields = ()
        if isinstance(field, models.ManyToOneRel):
            exclude_fields = (field.field.name, )

        converted_fields = get_input_fields_for_model(
            field.related_model,
            data.get("only_fields", ()),
            data.get("exclude_fields", exclude_fields
                     ),  # Exclude the field referring back to the foreign key
            data.get("optional_fields", ()),
            data.get("required_fields", ()),
            data.get("many_to_many_extras"),
            data.get("foreign_key_extras"),
            data.get("many_to_one_extras"),
            data.get("one_to_one_extras"),
            parent_type_name=type_name,
            field_types=data.get("field_types"),
            # Don't ignore the primary key on updates
            ignore_primary_key=operation_name != "update",
        )
        InputType = type(type_name, (InputObjectType, ), converted_fields)
        registry.register_converted_field(field, InputType)
        meta_registry.register(
            type_name,
            {
                "auto_context_fields": data.get("auto_context_fields", {}),
                "optional_fields": data.get("optional_fields", ()),
                "required_fields": data.get("required_fields", ()),
                "many_to_many_extras": data.get("many_to_many_extras", {}),
                "many_to_one_extras": data.get("many_to_one_extras", {}),
                "foreign_key_extras": data.get("auto_context_fields", {}),
                "one_to_one_extras": data.get("one_to_one_extras", {}),
                "field_types": data.get("field_types", {}),
            },
        )
        registry.register_converted_field(type_name, InputType)
        _field = graphene.List(
            InputType,
            required=False,
        )
    else:
        _field = convert_many_to_many_field(field, registry, False, data, None)

    return _field