Beispiel #1
0
    def test_mutation_meta_registry(self):
        """Test having registry information stored in mutation meta."""
        self.assertEqual(ProjectUpdateMutation._meta.registry,
                         get_global_registry())
        self.assertNotEqual(ProjectUpdateMutation._meta.registry,
                            project_name_only_registry)

        self.assertNotEqual(ProjectNameOnlyUpdateMutation._meta.registry,
                            get_global_registry())
        self.assertEqual(ProjectNameOnlyUpdateMutation._meta.registry,
                         project_name_only_registry)
Beispiel #2
0
def convert_serializer_field(field,
                             is_input=True,
                             convert_choices_to_enum=True,
                             force_optional=False):
    """
    Converts a django rest frameworks field to a graphql field
    and marks the field as required if we are creating an input type
    and the field itself is required
    """

    if isinstance(field,
                  serializers.ChoiceField) and not convert_choices_to_enum:
        graphql_type = graphene.String
    elif isinstance(field, serializers.FileField):
        graphql_type = Upload
    else:
        graphql_type = get_graphene_type_from_serializer_field(field)

    args = []
    kwargs = {
        "description": field.help_text,
        "required": is_input and field.required and not force_optional
    }

    # if it is a tuple or a list it means that we are returning
    # the graphql type and the child type
    if isinstance(graphql_type, (list, tuple)):
        kwargs["of_type"] = graphql_type[1]
        graphql_type = graphql_type[0]

    if isinstance(field, serializers.ModelSerializer):
        if is_input:
            graphql_type = convert_serializer_to_input_type(field.__class__)
        else:
            global_registry = get_global_registry()
            field_model = field.Meta.model
            args = [global_registry.get_type_for_model(field_model)]
    elif isinstance(field, serializers.Serializer):
        if is_input:
            graphql_type = convert_serializer_to_input_type(field.__class__)
    elif isinstance(field, serializers.ListSerializer):
        field = field.child
        if is_input:
            # All the serializer items within the list is considered non-nullable
            kwargs["of_type"] = graphene.NonNull(
                convert_serializer_to_input_type(field.__class__))
        else:
            del kwargs["of_type"]
            global_registry = get_global_registry()
            field_model = field.Meta.model
            args = [global_registry.get_type_for_model(field_model)]

    return graphql_type(*args, **kwargs)
Beispiel #3
0
    def __init_subclass_with_meta__(
            cls,
            model=None,
            permissions=None,
            login_required=None,
            only_fields=(),
            exclude_fields=(),
            return_field_name=None,
            **kwargs,
    ):
        registry = get_global_registry()

        if not return_field_name:
            return_field_name = to_snake_case(model.__name__)

        arguments = OrderedDict(id=graphene.ID(required=True))

        output_fields = OrderedDict()
        output_fields["found"] = graphene.Boolean()
        output_fields["deleted_id"] = graphene.ID()

        _meta = DjangoDeleteMutationOptions(cls)
        _meta.model = model
        _meta.fields = yank_fields_from_attrs(output_fields,
                                              _as=graphene.Field)
        _meta.return_field_name = return_field_name
        _meta.permissions = permissions
        _meta.login_required = _meta.login_required or (
            _meta.permissions and len(_meta.permissions) > 0)

        super().__init_subclass_with_meta__(arguments=arguments,
                                            _meta=_meta,
                                            **kwargs)
Beispiel #4
0
    def convert_field(field):

        registry = get_global_registry()
        converted = registry.get_converted_field(field)
        if converted:
            return converted

        _filter_coll = filterset_class()

        def _get_or_make_field(name, filt):
            return convert_form_field(filt.field)

        def _should_include_filter(filt):
            # if we're in ordering mode, we want
            # to return True for all CalumaOrdering types,
            # and if it's false, we want the opposite
            return ordering == isinstance(filt, CalumaOrdering)

        filter_fields = {
            name: _get_or_make_field(name, filt)
            for name, filt in _filter_coll.filters.items()
            # exclude orderBy in our fields. We want only new-style order filters
            if _should_include_filter(filt) and name != "orderBy"
        }

        if ordering:
            filter_fields["direction"] = AscDesc(default=AscDesc.ASC, required=False)
        else:
            filter_fields["invert"] = graphene.Boolean(required=False, default=False)

        filter_type = type(field_type_name, (InputObjectType,), filter_fields)

        converted = List(filter_type)
        registry.register_converted_field(field, converted)
        return converted
    def test_choices_field__choice_field_in_different_locations__accepts_different_parameters(self):
        class MockModel(models.Model):
            field_with_choices = models.CharField(
                max_length=16,
                choices=(
                    ("A", "Choice a"),
                    ("B", "Choice b"),
                    ("C", "Choice c"),
                )
            )

        registry = get_global_registry()
        field = MockModel._meta.get_field("field_with_choices")
        result = convert_django_field_with_choices(
            field,
            registry=registry
        )

        self.assertIsInstance(result, graphene.types.Enum)
        self.assertEqual(result.kwargs.get("required"), True)

        result = convert_django_field_with_choices(
            field,
            registry,
            required=False
        )

        self.assertIsInstance(result, graphene.types.Enum)
        self.assertEqual(result.kwargs.get("required"), False)
    def test__one_to_one_field__is_properly_converted(self):

        class MockSuperModel(models.Model):
            name = models.CharField(max_length=128, default="Heidi Klum")

        class MockModel(models.Model):
            super_model = models.OneToOneField(MockSuperModel, on_delete=models.CASCADE, related_name='modelling')  # Model-ling... Get it? xD

        # Convert primary relation
        registry = get_global_registry()
        field = MockModel._meta.get_field("super_model")
        result = convert_django_field_with_choices(
            field,
            registry=registry
        )

        self.assertIsInstance(result, graphene.types.ID)
        self.assertEqual(result.kwargs.get("required"), True)

        # Convert backward relation
        field = MockSuperModel._meta.get_field("modelling")
        result = convert_django_field_with_choices(
            field,
            registry=registry
        )

        self.assertIsInstance(result, graphene.types.ID)
Beispiel #7
0
def convert_choice_field_to_enum(field):
    """
    Add support to convert ordering choices to Graphql enum.

    Label is used as enum name.
    """

    registry = get_global_registry()
    converted = registry.get_converted_field(field)
    if converted:
        return converted

    def get_choices(choices):
        for value, help_text in choices:
            if value:
                name = convert_choice_name(value)
                description = help_text
                yield name, value, description

    name = to_camel_case(field.label)
    choices = list(get_choices(field.choices))
    named_choices = [(c[0], c[1]) for c in choices]
    named_choices_descriptions = {c[0]: c[2] for c in choices}

    class EnumWithDescriptionsType(object):
        @property
        def description(self):
            return named_choices_descriptions[self.name]

    enum = Enum(name, list(named_choices), type=EnumWithDescriptionsType)
    converted = enum(description=field.help_text, required=field.required)

    registry.register_converted_field(field, converted)
    return converted
Beispiel #8
0
def get_output_fields(model, return_field_name):
    """Return mutation output field for model instance."""
    model_type = get_global_registry().get_type_for_model(model)
    if not model_type:
        raise ImproperlyConfigured(
            "Unable to find type for model %s in graphene registry" % model.__name__
        )
    fields = {return_field_name: Field(model_type)}
    return fields
Beispiel #9
0
    def convert_meta_value_field(field):
        registry = get_global_registry()
        converted = registry.get_converted_field(field)
        if converted:
            return converted

        converted = List(SearchAnswersFilterType)
        registry.register_converted_field(field, converted)
        return converted
Beispiel #10
0
    def __init_subclass_with_meta__(
        cls,
        form_class=None,
        model=None,
        fields=ALL_FIELDS,
        permission_classes=None,
        return_field_name=None,
        deleting=False,
        is_relay=False,
        **options
    ):
        if form_class and not model:
            model = form_class._meta.model

        if not model:
            raise Exception("model is required for DjangoModelMutation")

        if not form_class:
            form_class = model_forms.modelform_factory(model, fields=fields)

        input_fields = {}
        if not deleting:
            form = form_class()
            input_fields = fields_for_form(form, fields)

        if fields == ALL_FIELDS or "id" in fields or deleting:
            if is_relay:
                input_fields["id"] = graphene.GlobalID()
            else:
                input_fields["id"] = graphene.ID()

        registry = get_global_registry()
        model_type = registry.get_type_for_model(model)
        if not model_type:
            raise Exception("No type registered for model: {}".format(model.__name__))

        if not return_field_name:
            model_name = model.__name__
            return_field_name = model_name[:1].lower() + model_name[1:]

        output_fields = OrderedDict()
        output_fields[return_field_name] = graphene.Field(model_type)

        _meta = DjangoModelMutationOptions(cls)
        _meta.form_class = form_class
        _meta.fields = fields
        _meta.model = model
        _meta.permission_classes = permission_classes or ()
        _meta.return_field_name = return_field_name
        _meta.deleting = deleting
        _meta.is_relay = is_relay
        _meta.fields = yank_fields_from_attrs(output_fields, _as=Field)

        input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
        super(DjangoModelMutation, cls).__init_subclass_with_meta__(
            _meta=_meta, input_fields=input_fields, **options
        )
Beispiel #11
0
    def convert_meta_value_field(field):
        registry = get_global_registry()
        converted = registry.get_converted_field(field)
        if converted:
            return converted

        converted = MetaValueFilterType()
        registry.register_converted_field(field, converted)
        return converted
Beispiel #12
0
def schema_for_field(field, name, registry=None):
    registry = registry or get_global_registry()
    s = get_field_schema(field, registry)

    default_value = getattr(field, "default", None)
    if default_value is NOT_PROVIDED:
        default_value = None
    if default_value is not None and callable(default_value):
        default_value = default_value()
    if default_value is not None:
        if isinstance(default_value, decimal.Decimal):
            default_value = str(default_value)
        if isinstance(default_value, (datetime.datetime, datetime.date, datetime.time)):
            default_value = default_value.isoformat()

    if isinstance(field, (ManyToOneRel, ManyToManyRel)):
        required = not field.null
    else:
        required = not field.blank and field.default is NOT_PROVIDED

    items = getattr(field, "choices", None)
    if items:
        if isinstance(items, dict):  # pragma:nocover
            items = items.items()

        choices = []
        for (_original_v, label), (n, _value, _desc) in zip(items, get_choices(items)):
            choices.append(
                {
                    "label": label,
                    "value": n,
                }
            )
    else:
        choices = None

    s = update_dict_nested(
        s,
        {
            "name": to_camel_case(name),
            # FIXME: Get verbose_name and help_text for m2m
            "label": getattr(field, "verbose_name", None),
            "help_text": getattr(field, "help_text", None),
            "hidden": name == "id",
            "choices": choices,
            "default_value": default_value,
            "validation": {
                "required": required,
                "min_length": getattr(field, "min_length", None),
                "max_length": getattr(field, "max_length", None),
                "min_value": None,
                "max_value": None,
            },
        },
    )

    return s
Beispiel #13
0
    def mutate(cls, root, info, ids):
        cls.before_mutate(root, info, ids)

        if cls._meta.login_required and not info.context.user.is_authenticated:
            raise GraphQLError("Must be logged in to access this mutation.")

        cls.check_permissions(root, info, ids)

        Model = cls._meta.model
        ids = cls.resolve_ids(ids)

        cls.validate(root, info, ids)

        qs_to_delete = cls.get_queryset(root, info, ids).filter(id__in=ids)

        updated_qs = cls.before_save(root, info, ids, qs_to_delete)

        if updated_qs:
            qs_to_delete = updated_qs

        # Find out which (global) ids are deleted, and which were not found.
        deleted_ids = [
            to_global_id(
                get_global_registry().get_type_for_model(Model).__name__, id)
            for id in qs_to_delete.values_list("id", flat=True)
        ]

        all_global_ids = [
            to_global_id(
                get_global_registry().get_type_for_model(Model).__name__, id)
            for id in ids
        ]

        missed_ids = list(set(all_global_ids).difference(deleted_ids))

        deletion_count, _ = qs_to_delete.delete()

        cls.after_mutate(root, info, ids, deletion_count, deleted_ids)

        return cls(
            deletion_count=deletion_count,
            deleted_ids=deleted_ids,
            missed_ids=missed_ids,
        )
Beispiel #14
0
    def convert_meta_value_field(field):
        registry = get_global_registry()
        converted = registry.get_converted_field(field)
        if converted:
            return converted

        # the converted type must be list-of-filter, as we need to apply
        # multiple conditions
        converted = List(HasAnswerFilterType)
        registry.register_converted_field(field, converted)
        return converted
Beispiel #15
0
    def to_enum(field):
        registry = get_global_registry()
        converted = registry.get_converted_field(field)

        if converted:  # pragma: no cover
            return converted

        converted = field_enum()

        registry.register_converted_field(field, converted)
        return converted
Beispiel #16
0
    def convert_meta_value_field(field):
        registry = get_global_registry()
        converted = registry.get_converted_field(field)
        if converted:
            # TODO: can this be removed, as it's never the case
            # anymore with graphene 3.0?
            # Doesn't seem to have ill effects...
            return converted  # pragma: no cover

        converted = List(SearchAnswersFilterType)
        registry.register_converted_field(field, converted)
        return converted
Beispiel #17
0
    def _optimize_field_by_name(self, store, model, selection, field_def):
        name = self._get_name_from_resolver(field_def.resolve)
        if not name:
            return False

        full_name = name
        # "split" here for deep model_field resolver hint if it's not partial
        split_model_field = callable(name) and [name] or full_name.split(
            LOOKUP_SEP, 1)
        name = split_model_field[0]

        model_field = self._get_model_field_from_name(model, name)
        if not model_field:
            return False

        if self._is_foreign_key_id(model_field,
                                   name) or not model_field.is_relation:
            store.only(name)
            return True

        field_type = self._get_type(field_def)
        selection_next = selection

        # if we're dealing with a deep model_field resolver hint,
        # get the type of the first link in the chain
        if len(split_model_field) > 1:
            model_next = model_field.related_model
            model_next_type = get_global_registry().get_type_for_model(
                model_next)
            field_type = GrapheneObjectType(graphene_type=model_next_type,
                                            name=model_next_type._meta.name,
                                            fields=field_type._fields)

            # we're optimizing a deep model_field resolver hint,
            # so introduce synthetic parents in the ast to prompt
            # the optimizer
            selection_next = self._insert_selection_set_parent_nodes_from_deep_field_name(
                selection, full_name)

        field_store = self._optimize_gql_selections(field_type, selection_next)

        if model_field.many_to_one or model_field.one_to_one:
            store.select_related(name, field_store)
            return True
        if model_field.one_to_many or model_field.many_to_many:
            if isinstance(model_field, ManyToOneRel):
                field_store.only(model_field.field.name)

            related_queryset = model_field.related_model.objects.all()
            store.prefetch_related(name, field_store, related_queryset)
            return True
        return False
Beispiel #18
0
def convert_serializer_field_to_enum(field):
    # TODO: could be removed once following issue is fixed
    # https://github.com/graphql-python/graphene-django/issues/517
    model_class = None
    serializer_meta = getattr(field.parent, "Meta", None)
    if serializer_meta:
        model_class = getattr(serializer_meta, "model", None)

    if model_class:
        registry = get_global_registry()
        model_field = model_class._meta.get_field(field.source)
        return type(convert_django_field_with_choices(model_field, registry))
    return graphene.String
Beispiel #19
0
    def __init_subclass_with_meta__(cls,
                                    form_class=None,
                                    model=None,
                                    return_field_name=None,
                                    only_fields=(),
                                    exclude_fields=(),
                                    **options):

        if not form_class:
            raise Exception(
                'form_class is required for DjangoModelFormMutation')

        if not model:
            model = form_class._meta.model

        if not model:
            raise Exception('model is required for DjangoModelFormMutation')

        form = form_class()
        input_fields = fields_for_form(form, only_fields, exclude_fields)
        input_fields['id'] = graphene.ID()

        registry = get_global_registry()
        model_type = registry.get_type_for_model(model)
        return_field_name = return_field_name
        if not return_field_name:
            model_name = model.__name__
            return_field_name = model_name[:1].lower() + model_name[1:]

        output_fields = OrderedDict()
        output_fields[return_field_name] = graphene.Field(model_type)

        _meta = DjangoModelDjangoFormMutationOptions(cls)
        _meta.form_class = form_class
        _meta.model = model
        _meta.return_field_name = return_field_name
        _meta.fields = yank_fields_from_attrs(
            output_fields,
            _as=Field,
        )

        input_fields = yank_fields_from_attrs(
            input_fields,
            _as=InputField,
        )
        super(DjangoModelFormMutation,
              cls).__init_subclass_with_meta__(_meta=_meta,
                                               input_fields=input_fields,
                                               **options)
    def __init_subclass_with_meta__(
            cls,
            _meta=None,
            model=None,
            permissions=None,
            login_required=None,
            filter_fields=(),
            filter_class=None,
            **kwargs,
    ):
        registry = get_global_registry()
        model_type = registry.get_type_for_model(model)

        assert model_type, f"Model type must be registered for model {model}"
        assert (
            len(filter_fields) > 0
        ), f"You must specify at least one field to filter on for deletion."

        input_arguments = get_filter_fields_input_args(filter_fields, model)

        InputType = type(f"BatchDelete{model.__name__}Input",
                         (InputObjectType, ), input_arguments)

        arguments = OrderedDict(input=InputType(required=True))

        output_fields = OrderedDict()
        output_fields["deletion_count"] = graphene.Int()
        output_fields["deleted_ids"] = graphene.List(graphene.ID)

        if _meta is None:
            _meta = DjangoFilterDeleteMutationOptions(cls)

        _meta.model = model
        _meta.fields = yank_fields_from_attrs(output_fields,
                                              _as=graphene.Field)
        _meta.filter_fields = filter_fields
        _meta.permissions = permissions
        _meta.login_required = login_required or (_meta.permissions and
                                                  len(_meta.permissions) > 0)

        super().__init_subclass_with_meta__(arguments=arguments,
                                            _meta=_meta,
                                            **kwargs)
    def mutate(cls, root, info, input):
        updated_input = cls.before_mutate(root, info, input)

        if updated_input:
            input = updated_input

        if cls._meta.login_required and not info.context.user.is_authenticated:
            raise GraphQLError("Must be logged in to access this mutation.")

        cls.check_permissions(root, info, input)

        Model = cls._meta.model
        model_field_values = {}

        for name, value in super(type(input), input).items():
            filter_field_split = name.split("__", 1)
            field_name = filter_field_split[0]

            try:
                field = Model._meta.get_field(field_name)
            except FieldDoesNotExist:
                # This can happen with nested selectors. In this case we set the field to none.
                field = None

            filter_field_is_list = False

            if len(filter_field_split) > 1:
                # If we have an "__in" final part of the filter, we are now dealing with
                # a list of things. Note that all other variants can be coerced directly
                # on the filter-call, so we don't really have to deal with other cases.
                filter_field_is_list = filter_field_split[-1] == "in"

            new_value = value

            value_handle_name = "handle_" + name
            if hasattr(cls, value_handle_name):
                handle_func = getattr(cls, value_handle_name)
                assert callable(
                    handle_func
                ), f"Property {value_handle_name} on {cls.__name__} is not a function."
                new_value = handle_func(value, name, info)

            # On some fields we perform some default conversion, if the value was not transformed above.
            if new_value == value and value is not None:
                if type(field) in (models.ForeignKey, models.OneToOneField):
                    name = getattr(field, "db_column", None) or name + "_id"
                    new_value = cls.resolve_id(value)
                elif (type(field) in (
                        models.ManyToManyField,
                        models.ManyToManyRel,
                        models.ManyToOneRel,
                ) or filter_field_is_list):
                    new_value = cls.resolve_ids(value)

            model_field_values[name] = new_value

        filter_qs = cls.get_queryset(root, info,
                                     input).filter(**model_field_values)
        updated_qs = cls.before_save(root, info, filter_qs)

        if updated_qs:
            filter_qs = updated_qs

        ids = [
            to_global_id(
                get_global_registry().get_type_for_model(Model).__name__, id)
            for id in filter_qs.values_list("id", flat=True)
        ]

        deletion_count, _ = filter_qs.delete()

        cls.after_mutate(root, info, input, deletion_count, ids)

        return cls(deletion_count=deletion_count, deleted_ids=ids)
Beispiel #22
0
    def __init_subclass_with_meta__(
        cls,
        model=None,
        permissions=None,
        login_required=None,
        only_fields=(),
        exclude_fields=(),
        return_field_name=None,
        auto_context_fields={},
        many_to_one_extras=None,
        many_to_many_extras=None,
        foreign_key_extras=None,
        type_name=None,
        **kwargs,
    ):
        registry = get_global_registry()
        meta_registry = get_type_meta_registry()
        model_type = registry.get_type_for_model(model)

        assert model_type, f"Model type must be registered for model {model}"

        if not return_field_name:
            return_field_name = to_snake_case(model.__name__)

        if many_to_one_extras is None:
            many_to_one_extras = {}

        if foreign_key_extras is None:
            foreign_key_extras = {}

        if many_to_many_extras is None:
            many_to_many_extras = {}

        input_type_name = type_name or f"Patch{model.__name__}Input"

        model_fields = get_all_optional_input_fields_for_model(
            model,
            only_fields,
            exclude_fields,
            many_to_many_extras=many_to_many_extras,
            foreign_key_extras=foreign_key_extras,
            many_to_one_extras=many_to_one_extras,
            parent_type_name=type_name,
        )

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

        # Register meta-data
        meta_registry.register(
            input_type_name, {
                'auto_context_fields': auto_context_fields or {},
                'many_to_many_extras': many_to_many_extras or {},
                'many_to_one_extras': many_to_one_extras or {},
                'foreign_key_extras': foreign_key_extras or {}
            })

        registry.register_converted_field(input_type_name, InputType)

        arguments = OrderedDict(id=graphene.ID(required=True),
                                input=InputType(required=True))

        output_fields = OrderedDict()
        output_fields[return_field_name] = graphene.Field(model_type)

        _meta = DjangoPatchMutationOptions(cls)
        _meta.model = model
        _meta.fields = yank_fields_from_attrs(output_fields,
                                              _as=graphene.Field)
        _meta.return_field_name = return_field_name
        _meta.permissions = permissions
        _meta.auto_context_fields = auto_context_fields or {}
        _meta.InputType = InputType
        _meta.input_type_name = input_type_name
        _meta.many_to_many_extras = many_to_many_extras
        _meta.many_to_one_extras = many_to_one_extras
        _meta.foreign_key_extras = foreign_key_extras
        _meta.login_required = _meta.login_required or (
            _meta.permissions and len(_meta.permissions) > 0)

        super().__init_subclass_with_meta__(arguments=arguments,
                                            _meta=_meta,
                                            **kwargs)
Beispiel #23
0
from itertools import chain

import graphene
from django.core.exceptions import ImproperlyConfigured, ValidationError
from graphene.types.mutation import MutationOptions
from graphene_django.registry import get_global_registry
from graphql_jwt import ObtainJSONWebToken, Verify
from graphql_jwt.exceptions import GraphQLJWTError, PermissionDenied

from ...account import models
from ..account.types import User
from ..file_upload.types import Upload
from ..utils import get_node, get_nodes
from .types import Error

registry = get_global_registry()


def get_model_name(model):
    """Return name of the model with first letter lowercase."""
    model_name = model.__name__
    return model_name[:1].lower() + model_name[1:]


def get_output_fields(model, return_field_name):
    """Return mutation output field for model instance."""
    model_type = registry.get_type_for_model(model)
    fields = {return_field_name: graphene.Field(model_type)}
    return fields

Beispiel #24
0
    def __init_subclass_with_meta__(
        cls,
        _meta=None,
        model=None,
        permissions=None,
        login_required=None,
        filter_fields=(),
        filter_class=None,
        type_name=None,
        only_fields=(),
        exclude_fields=(),
        optional_fields=None,
        required_fields=(),
        field_types=None,
        auto_context_fields={},
        **kwargs,
    ):
        registry = get_global_registry()
        model_type = registry.get_type_for_model(model)

        if optional_fields is None:
            optional_fields = tuple(name
                                    for name, _ in get_model_fields(model))

        assert model_type, f"Model type must be registered for model {model}"
        assert (
            len(filter_fields) > 0
        ), f"You must specify at least one field to filter on for deletion."

        input_arguments = get_filter_fields_input_args(filter_fields, model)

        FilterInputType = type(
            f"FilterUpdate{model.__name__}FilterInput",
            (InputObjectType, ),
            input_arguments,
        )

        input_type_name = type_name or f"FilterUpdate{model.__name__}DataInput"

        model_fields = get_input_fields_for_model(
            model,
            only_fields,
            exclude_fields,
            tuple(auto_context_fields.keys()) + optional_fields,
            required_fields,
            None,
            None,
            None,
            one_to_one_extras=None,
            parent_type_name=input_type_name,
            field_types=field_types,
            ignore_primary_key=True,
        )

        DataInputType = type(input_type_name, (InputObjectType, ),
                             model_fields)

        # Register meta-data
        meta_registry.register(
            input_type_name,
            {
                "auto_context_fields": auto_context_fields or {},
                "optional_fields": optional_fields,
                "required_fields": required_fields,
                "many_to_many_extras": {},
                "many_to_one_extras": {},
                "foreign_key_extras": {},
                "one_to_one_extras": {},
                "field_types": field_types or {},
            },
        )

        registry.register_converted_field(input_type_name, DataInputType)

        arguments = OrderedDict(filter=FilterInputType(required=True),
                                data=DataInputType(required=True))

        output_fields = OrderedDict()
        output_fields["updated_count"] = graphene.Int()
        output_fields["updated_objects"] = graphene.List(model_type)

        if _meta is None:
            _meta = DjangoFilterUpdateMutationOptions(cls)

        _meta.model = model
        _meta.fields = yank_fields_from_attrs(output_fields,
                                              _as=graphene.Field)
        _meta.filter_fields = filter_fields
        _meta.permissions = permissions
        _meta.login_required = login_required or (_meta.permissions and
                                                  len(_meta.permissions) > 0)

        super().__init_subclass_with_meta__(arguments=arguments,
                                            _meta=_meta,
                                            **kwargs)
Beispiel #25
0
    def __init_subclass_with_meta__(
        cls,
        model=None,
        permissions=None,
        login_required=None,
        only_fields=(),
        exclude_fields=(),
        optional_fields=(),
        required_fields=(),
        auto_context_fields={},
        return_field_name=None,
        many_to_many_extras=None,
        foreign_key_extras=None,
        many_to_one_extras=None,
        type_name=None,
        use_type_name=None,
        **kwargs,
    ):
        registry = get_global_registry()
        meta_registry = get_type_meta_registry()
        model_type = registry.get_type_for_model(model)

        if many_to_one_extras is None:
            many_to_one_extras = {}

        if foreign_key_extras is None:
            foreign_key_extras = {}

        if many_to_many_extras is None:
            many_to_many_extras = {}

        assert model_type, f"Model type must be registered for model {model}"

        if not return_field_name:
            # Pluralize
            return_field_name = to_snake_case(model.__name__) + "s"

        if use_type_name:
            input_type_name = use_type_name
            InputType = registry.get_converted_field(input_type_name)
            if not InputType:
                raise GraphQLError(
                    f"Could not find input type with name {input_type_name}")
        else:
            input_type_name = type_name or f"BatchCreate{model.__name__}Input"

            model_fields = get_input_fields_for_model(
                model,
                only_fields,
                exclude_fields,
                tuple(auto_context_fields.keys()) + optional_fields,
                required_fields,
                many_to_many_extras,
                foreign_key_extras,
                many_to_one_extras,
                parent_type_name=input_type_name,
            )

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

            # Register meta-data
            meta_registry.register(
                input_type_name, {
                    'auto_context_fields': auto_context_fields or {},
                    'optional_fields': optional_fields,
                    'required_fields': required_fields,
                    'many_to_many_extras': many_to_many_extras or {},
                    'foreign_key_extras': foreign_key_extras or {}
                })

            registry.register_converted_field(input_type_name, InputType)

        arguments = OrderedDict(input=graphene.List(InputType, required=True))

        output_fields = OrderedDict()
        output_fields[return_field_name] = graphene.List(model_type)

        _meta = DjangoBatchCreateMutationOptions(cls)
        _meta.model = model
        _meta.fields = yank_fields_from_attrs(output_fields,
                                              _as=graphene.Field)
        _meta.return_field_name = return_field_name
        _meta.optional_fields = optional_fields
        _meta.required_fields = required_fields
        _meta.permissions = permissions
        _meta.auto_context_fields = auto_context_fields or {}
        _meta.many_to_many_extras = many_to_many_extras or {}
        _meta.foreign_key_extras = foreign_key_extras
        _meta.many_to_one_extras = many_to_one_extras or {}
        _meta.InputType = InputType
        _meta.input_type_name = input_type_name
        _meta.login_required = _meta.login_required or (
            _meta.permissions and len(_meta.permissions) > 0)

        super().__init_subclass_with_meta__(arguments=arguments,
                                            _meta=_meta,
                                            **kwargs)
Beispiel #26
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
Beispiel #27
0
    def __init_subclass_with_meta__(
        cls,
        _meta=None,
        model=None,
        permissions=None,
        login_required=None,
        only_fields=(),
        exclude_fields=(),
        optional_fields=(),
        required_fields=(),
        auto_context_fields={},
        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,
        field_types=None,
        ignore_primary_key=True,
        custom_fields=None,
        **kwargs,
    ):
        registry = get_global_registry()
        meta_registry = get_type_meta_registry()
        model_type = registry.get_type_for_model(model)

        if auto_context_fields is None:
            auto_context_fields = {}

        if many_to_one_extras is None:
            many_to_one_extras = {}

        if foreign_key_extras is None:
            foreign_key_extras = {}

        if many_to_many_extras is None:
            many_to_many_extras = {}

        if one_to_one_extras is None:
            one_to_one_extras = {}

        if custom_fields is None:
            custom_fields = {}

        assert model_type, f"Model type must be registered for model {model}"

        if not return_field_name:
            return_field_name = to_snake_case(model.__name__)

        input_type_name = type_name or f"Create{model.__name__}Input"

        model_fields = get_input_fields_for_model(
            model,
            only_fields,
            exclude_fields,
            tuple(auto_context_fields.keys()) + optional_fields,
            required_fields,
            many_to_many_extras,
            foreign_key_extras,
            many_to_one_extras,
            one_to_one_extras=one_to_one_extras,
            parent_type_name=input_type_name,
            field_types=field_types,
            ignore_primary_key=ignore_primary_key,
        )

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

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

        # Register meta-data
        meta_registry.register(
            input_type_name,
            {
                "auto_context_fields": auto_context_fields or {},
                "optional_fields": optional_fields,
                "required_fields": required_fields,
                "many_to_many_extras": many_to_many_extras,
                "many_to_one_extras": many_to_one_extras,
                "foreign_key_extras": foreign_key_extras,
                "one_to_one_extras": one_to_one_extras,
                "field_types": field_types or {},
            },
        )

        registry.register_converted_field(input_type_name, InputType)

        arguments = OrderedDict(input=InputType(required=True))

        output_fields = OrderedDict()
        output_fields[return_field_name] = graphene.Field(model_type)

        if _meta is None:
            _meta = DjangoCreateMutationOptions(cls)

        _meta.model = model
        _meta.fields = yank_fields_from_attrs(output_fields,
                                              _as=graphene.Field)
        _meta.return_field_name = return_field_name
        _meta.optional_fields = optional_fields
        _meta.required_fields = required_fields
        _meta.permissions = permissions
        _meta.auto_context_fields = auto_context_fields
        _meta.many_to_many_extras = many_to_many_extras
        _meta.many_to_one_extras = many_to_one_extras
        _meta.foreign_key_extras = foreign_key_extras
        _meta.one_to_one_extras = one_to_one_extras

        _meta.field_types = field_types or {}
        _meta.InputType = InputType
        _meta.input_type_name = input_type_name
        _meta.login_required = login_required or (_meta.permissions and
                                                  len(_meta.permissions) > 0)

        super().__init_subclass_with_meta__(arguments=arguments,
                                            _meta=_meta,
                                            **kwargs)
Beispiel #28
0
    def __init_subclass_with_meta__(
        cls,
        _meta=None,
        model=None,
        permissions=None,
        permissions_any=True,
        object_permissions=None,
        object_permissions_any=True,
        object_permissions_with_superuser=True,
        fields_schema=None,
        public=None,
        only_fields=None,
        fields=None,
        exclude_fields=None,
        exclude=None,
        **kwargs,
    ):
        if not _meta:
            _meta = ModelTypeOptions(cls)

        if "allow_unauthenticated" in kwargs:
            raise ImproperlyConfigured("Use 'public' instead of 'allow_unauthenticated'")

        _meta.permissions = permissions or []
        _meta.permissions_any = permissions_any
        _meta.object_permissions = object_permissions or []
        _meta.object_permissions_any = object_permissions_any
        _meta.object_permissions_with_superuser = object_permissions_with_superuser
        _meta.public = public

        _fields_schema = {}
        # graphene will handle the deprecated only_fields/exclude_fields for us
        # We just want to mimic the logic here
        _include = fields if fields is not None else only_fields
        if _include is not None:
            _include = set(_include)
        _exclude = set(exclude or []) or set(exclude_fields or [])
        for name, field in get_model_fields(model):
            if name in _exclude:
                continue
            if _include is not None and name not in _include:
                continue

            registry = kwargs.get("registry", get_global_registry())
            _fields_schema[name] = schema_for_field(field, name, registry)

        fields_schema = update_dict_nested(
            _fields_schema,
            fields_schema or {},
        )
        _meta.fields_schema = fields_schema or {}

        super().__init_subclass_with_meta__(
            _meta=_meta,
            model=model,
            only_fields=only_fields,
            fields=fields,
            exclude_fields=exclude_fields,
            exclude=exclude,
            **kwargs,
        )

        schema_registry[cls._meta.name] = {
            "object_type": cls._meta.name,
            "fields": list(_meta.fields_schema.values()),
        }
Beispiel #29
0
    def __init_subclass_with_meta__(cls,
                                    lookup_field=None,
                                    lookup_input_kwarg=None,
                                    serializer_class=None,
                                    model_class=None,
                                    model_operations=["create", "update"],
                                    fields=(),
                                    exclude=(),
                                    return_field_name=None,
                                    return_field_type=None,
                                    **options):
        if not serializer_class:
            raise Exception("serializer_class is required for the Mutation")

        if "update" not in model_operations and "create" not in model_operations:
            raise Exception(
                'model_operations must contain "create" and/or "update"')

        serializer = serializer_class()
        if model_class is None:
            serializer_meta = getattr(serializer_class, "Meta", None)
            if serializer_meta:
                model_class = getattr(serializer_meta, "model", None)

        if lookup_field is None and model_class:
            lookup_field = model_class._meta.pk.name
        if lookup_input_kwarg is None:
            lookup_input_kwarg = lookup_field

        input_fields = fields_for_serializer(serializer,
                                             fields,
                                             exclude,
                                             is_input=True)

        if return_field_name is None:
            model_name = model_class.__name__
            return_field_name = model_name[:1].lower() + model_name[1:]

        if not return_field_type:
            registry = get_global_registry()
            return_field_type = registry.get_type_for_model(model_class)

        output_fields = OrderedDict()
        if return_field_name:
            output_fields[return_field_name] = graphene.Field(
                return_field_type)

        _meta = MutationOptions(cls)
        _meta.lookup_field = lookup_field
        _meta.lookup_input_kwarg = lookup_input_kwarg
        _meta.model_operations = model_operations
        _meta.serializer_class = serializer_class
        _meta.model_class = model_class
        _meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
        _meta.return_field_name = return_field_name
        _meta.return_field_type = return_field_type

        input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
        super().__init_subclass_with_meta__(_meta=_meta,
                                            input_fields=input_fields,
                                            **options)
Beispiel #30
0
from django.db.models.fields.files import FileField
from graphene import ObjectType
from graphene.types.mutation import MutationOptions
from graphene_django.registry import get_global_registry
from graphql.error import GraphQLError
from graphql_jwt import ObtainJSONWebToken, Verify
from graphql_jwt.exceptions import JSONWebTokenError, PermissionDenied

from ...account import models
from ..account.types import User
from ..utils import get_nodes
from .types import Error, MetaInput, MetaPath, Upload
from .utils import from_global_id_strict_type, snake_to_camel_case
from .utils.error_codes import get_error_code_from_error

registry = get_global_registry()


def get_model_name(model):
    """Return name of the model with first letter lowercase."""
    model_name = model.__name__
    return model_name[:1].lower() + model_name[1:]


def get_output_fields(model, return_field_name):
    """Return mutation output field for model instance."""
    model_type = registry.get_type_for_model(model)
    if not model_type:
        raise ImproperlyConfigured(
            "Unable to find type for model %s in graphene registry" % model.__name__
        )
Beispiel #31
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