Exemplo n.º 1
0
    def get_cache_name_enum_name(field):
        name = field.field_name or field.source or "Choices"
        serializer = field.parent
        cache_name = "{}_{}".format(serializer.__class__.__name__, name)

        if isinstance(serializer, serializers.ModelSerializer):
            _model_fields = dict(get_model_fields(serializer.Meta.model))
            _model_field = _model_fields.get(name)
            enum_name = f'{serializer.Meta.model.__name__}{name.capitalize()}'
            return enum_name, _model_field or cache_name

        enum_name = f'{serializer.__class__.__name__}{name}'
        return enum_name, cache_name
Exemplo n.º 2
0
def get_fields_and_properties(cls):
    """
    Return all fields and @property methods for a model.
    """
    from graphene_django.utils import get_model_fields

    # Note: graphene-django use this method to get the model fields
    # cls._meta.get_fields(include_parents=False) includes symmetrical ManyToMany fields, while get_model_fields doesn't
    fields = [field for field, instance in get_model_fields(cls)]

    try:
        properties = [
            method[0] for method in inspect.getmembers(
                cls, lambda o: isinstance(o, property))
        ]
    except BaseException:
        properties = []

    return fields + properties
Exemplo n.º 3
0
    def __init_subclass_with_meta__(cls,
                                    _meta=None,
                                    model=None,
                                    optional_fields=None,
                                    type_name=None,
                                    **kwargs):
        all_field_names = tuple(name for name, _ in get_model_fields(model))

        if optional_fields is None:
            optional_fields = all_field_names

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

        return super().__init_subclass_with_meta__(
            _meta=_meta,
            model=model,
            optional_fields=optional_fields,
            type_name=input_type_name,
            **kwargs)
Exemplo n.º 4
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
Exemplo n.º 5
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)
Exemplo n.º 6
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
Exemplo n.º 7
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=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,
        field_types=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 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 not return_field_name:
            return_field_name = to_snake_case(model.__name__)

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

        all_field_names = (name for name, _ in get_model_fields(model))

        model_fields = get_input_fields_for_model(
            model,
            only_fields,
            exclude_fields,
            optional_fields=all_field_names,
            required_fields=(),
            many_to_many_extras=many_to_many_extras,
            foreign_key_extras=foreign_key_extras,
            many_to_one_extras=many_to_one_extras,
            one_to_one_extras=one_to_one_extras,
            parent_type_name=input_type_name,
            field_types=field_types,
        )

        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 {},
                "one_to_one_extras": one_to_one_extras,
                "field_types": field_types 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)

        if _meta is None:
            _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
        _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)
Exemplo n.º 8
0
    def __init__(
        self,
        api,
        fields=None,  # Fields that can be read and potentially edited
        exclude=None,  # Fields that will be excluded
        read_only=None,  # Fields that are read-only
        write_only=None,  # Fields that are write-only
        properties=None,  # Dynamic views available as fields on a resource
        actions=None,  # Extra actions for graphql and rest views
        filters=None,  # Extra filters for graphql and rest views
        list_display=None,  # The favorite fields to display in a table
        permissions=None,
        inlines=None,
        viewset_class=None,
        filterset_class=None,
        serializer_class=None,
        admin_class=None,
        dump_info=False,  # Print the full resource to the console upon mounting
    ):
        # Part 1: Set attributes
        self.api = api
        self.permissions = permissions or self.permissions
        self.viewset_class = viewset_class or self.viewset_class

        # Step 2: Fields
        self.model_fields = collections.OrderedDict(get_model_fields(self.model))
        self.model_fields_simple = collections.OrderedDict(
            (f.name, f) for f in self.model._meta.fields
        )

        self.relations = self.get_model_fields(
            lambda field: isinstance(field, related.ForeignObject)
        )
        self.reverse_relations = self.get_model_fields(
            lambda field: isinstance(field, reverse_related.ForeignObjectRel)
        )
        self.many_to_many = self.get_model_fields(
            lambda field: isinstance(field, related.ManyToManyField)
        )

        self.fields = list(
            f
            for f in fields
            or self.fields
            or (
                list(self.model_fields.keys()) + self.relations + self.reverse_relations
            )
            if f != "id"
        )

        self.read_only = ["pk",] + (
            read_only
            or self.read_only
            or [f for f in self.reverse_relations if f in self.fields]
        )
        self.write_only = write_only or self.write_only or []

        self.list_display = [
            f
            for f in ["pk",]
            + list(
                list_display
                or self.list_display
                or [
                    f
                    for f in self.fields
                    if not f in self.reverse_relations and not f in self.many_to_many
                ]
            )
            if f not in self.write_only
        ]

        # Part 3: Complex setups
        self.inlines = inlines or self.inlines

        self.filterset_class = (
            filterset_class or self.filterset_class or EasyFilters.Assemble(self)
        )
        self.serializer_class = (
            serializer_class or self.serializer_class or EasySerializable.Assemble(self)
        )
        self.admin_class = admin_class or self.admin_class or admin.ModelAdmin

        self.filterset = self.filterset_class()

        self.gql_fields = {
            field: get_gql_type(self.model_fields, field)
            for field in self.fields + self.inlines + ["id",]
            if self.api.graphql
            and not isinstance(self.model_fields[field], GenericForeignKey)
            and field not in self.write_only
            and field in self.model_fields
        }

        # need to rewrite this with dir() like below
        self.properties = (
            properties
            or self.properties
            or [
                attr
                for attr, value in self.model.__dict__.items()
                if getattr(value, "_APIProperty", False)
            ]
        )
        self.property_map = {
            name: getattr(self.model, name)._APIType
            for name in self.properties
            if getattr(getattr(self.model, name, None), "_APIProperty", False)
        }

        self.actions = (
            actions
            or self.actions
            or {
                attr: getattr(value, "_APIAction", {})
                for attr, value in self.model_attributes.items()
                if getattr(value, "_APIAction", False)
            }
        )

        self.filters = (
            filters
            or self.filters
            or {
                attr: getattr(value, "_APIFilter", {})
                for attr, value in self.model_attributes.items()
                if isinstance(value, django_filters.Filter)
            }
        )