flavors/django-graphql-geojson

View on GitHub
graphql_geojson/types/feature.py

Summary

Maintainability
A
35 mins
Test Coverage
import inspect
from collections import OrderedDict

import graphene
from graphene.types.generic import GenericScalar
from graphene_django.types import DjangoObjectType, DjangoObjectTypeOptions

from .. import resolver

__all__ = ['GeoJSONType']


class OrderedFields(OrderedDict):

    def update(self, other=None, **kwargs):
        if other is not None:
            global_id_field = other.pop('id', None)

            if global_id_field is not None:
                self['id'] = global_id_field

            properties_type = self['properties']._type
            Properties = type(properties_type.__name__, (
                properties_type,), other)

            self['properties'] = graphene.Field(Properties)
        else:
            super().update(**kwargs)


class GeoJSONTypeOptions(DjangoObjectTypeOptions):
    geojson_field = None

    def __setattr__(self, name, value):
        if name == 'fields':
            geometry_field = value.pop(self.geojson_field, None)

            assert geometry_field is not None, (
                'Unrecognized field `{}`'.format(self.geojson_field)
            )

            geometry_field.name = 'geometry'
            bbox_field = graphene.Field(
                GenericScalar,
                default_value=self.geojson_field)

            primary_key = self.model._meta.pk.name
            primary_key_field = value.pop(primary_key, None)
            properties = self.get_properties(value)

            fields = [
                ('type', graphene.Field(graphene.String)),
                (self.geojson_field, geometry_field),
                ('bbox', bbox_field),
                ('properties', graphene.Field(properties)),
            ]

            if primary_key_field is not None:
                fields.insert(1, (primary_key, primary_key_field))

            value = OrderedFields(fields)

        super().__setattr__(name, value)

    def get_properties(self, value):
        methods = inspect.getmembers(
            self.class_type(),
            predicate=inspect.ismethod)

        for method_name, method in methods:
            if method_name.startswith('resolve'):
                value[method_name] = method.__func__

        class_name = '{}Properties'.format(self.model.__name__)
        return type(class_name, (graphene.ObjectType,), value)


class GeoJSONType(DjangoObjectType):

    class Meta:
        abstract = True

    @classmethod
    def __init_subclass_with_meta__(cls, name=None, _meta=None,
                                    geojson_field=None, **options):

        assert geojson_field is not None, (
            '`{}` should either include a `Meta.geojson_field`'
            ' attribute.'.format(name or cls.__name__)
        )

        if _meta is None:
            _meta = GeoJSONTypeOptions(cls)
            _meta.geojson_field = geojson_field

        options.setdefault('default_resolver', resolver.feature_resolver)
        super().__init_subclass_with_meta__(name=name, _meta=_meta, **options)