class API(object):

    """ API handler."""

    CONTENT_TYPE = "application/vnd.api+json"

    def __init__(self):
        self._resources = []
        self.base_url = None  # base server url
        self.api_url = None  # api root url

    @property
    def resource_map(self):
        """ Resource map of api.

        .. versionadded:: 0.4.1

        :return: resource name to resource mapping.
        :rtype: dict

        """
        return {r.Meta.name: r for r in self._resources}

    @property
    def model_resource_map(self):
        return {
            resource.Meta.model: resource
            for resource in self.resource_map.values()
            if hasattr(resource.Meta, 'model')
        }

    def register(self, resource=None, **kwargs):
        """ Register resource for currnet API.

        :param resource: Resource to be registered
        :type resource: jsonapi.resource.Resource or None
        :return: resource
        :rtype: jsonapi.resource.Resource

        .. versionadded:: 0.4.1
        :param kwargs: Extra meta parameters

        """
        if resource is None:
            def wrapper(resource):
                return self.register(resource, **kwargs)
            return wrapper

        for key, value in kwargs.items():
            setattr(resource.Meta, key, value)

        if resource.Meta.name in self.resource_map:
            raise ValueError('Resource {} already registered'.format(
                resource.Meta.name))

        if resource.Meta.name_plural in self.resource_map:
            raise ValueError(
                'Resource plural name {} conflicts with registered resource'.
                format(resource.Meta.name))

        resource_plural_names = {
            r.Meta.name_plural for r in self.resource_map.values()
        }
        if resource.Meta.name in resource_plural_names:
            raise ValueError(
                'Resource name {} conflicts with other resource plural name'.
                format(resource.Meta.name)
            )

        resource.Meta.api = self
        self._resources.append(resource)
        return resource

    @property
    def urls(self):
        """ Get all of the api endpoints.

        NOTE: only for django as of now.
        NOTE: urlpatterns are deprecated since Django1.8

        :return list: urls

        """
        from django.conf.urls import url
        urls = [
            url(r'^$', self.documentation),
            url(r'^map$', self.map_view),
        ]

        for resource_name in self.resource_map:
            urls.extend([
                url(r'(?P<resource_name>{})$'.format(
                    resource_name), self.handler_view),
                url(r'(?P<resource_name>{})/(?P<ids>[\w\-\,]+)$'.format(
                    resource_name), self.handler_view),
            ])

        return urls

    def update_urls(self, request, resource_name=None, ids=None):
        """ Update url configuration.

        :param request:
        :param resource_name:
        :type resource_name: str or None
        :param ids:
        :rtype: None

        """
        http_host = request.META.get('HTTP_HOST', None)

        if http_host is None:
            http_host = request.META['SERVER_NAME']
            if request.META['SERVER_PORT'] not in ('80', '443'):
                http_host = "{}:{}".format(
                    http_host, request.META['SERVER_PORT'])

        self.base_url = "{}://{}".format(
            request.META['wsgi.url_scheme'],
            http_host
        )
        self.api_url = "{}{}".format(self.base_url, request.path)
        self.api_url = self.api_url.rstrip("/")

        if ids is not None:
            self.api_url = self.api_url.rsplit("/", 1)[0]

        if resource_name is not None:
            self.api_url = self.api_url.rsplit("/", 1)[0]

    def map_view(self, request):
        """ Show information about available resources.

        .. versionadded:: 0.5.7
            Content-Type check

        :return django.http.HttpResponse

        """
        self.update_urls(request)
        resource_info = {
            "resources": [{
                "id": index + 1,
                "href": "{}/{}".format(self.api_url, resource_name),
            } for index, (resource_name, resource) in enumerate(
                sorted(self.resource_map.items()))
                if not resource.Meta.authenticators or
                resource.authenticate(request) is not None
            ]
        }
        response = json.dumps(resource_info)
        return HttpResponse(response, content_type="application/vnd.api+json")

    def documentation(self, request):
        """ Resource documentation.

        .. versionadded:: 0.7.2
            Content-Type check

        :return django.http.HttpResponse

        """
        self.update_urls(request)
        context = {
            "resources": sorted(self.resource_map.items())
        }
        return render(request, "jsonapi/index.html", context)

    def handler_view_get(self, resource, **kwargs):
        items = json.dumps(
            resource.get(**kwargs),
            cls=resource.Meta.encoder
        )
        return HttpResponse(items, content_type=self.CONTENT_TYPE)

    def handler_view_post(self, resource, **kwargs):
        data = resource.post(**kwargs)
        if "errors" in data:
            response = HttpResponse(
                json.dumps(data, cls=DatetimeDecimalEncoder),
                content_type=self.CONTENT_TYPE, status=400)
            return response

        response = HttpResponse(
            json.dumps(data, cls=DatetimeDecimalEncoder),
            content_type=self.CONTENT_TYPE, status=201)

        items = data["data"]
        items = items if isinstance(items, list) else [items]

        response["Location"] = "{}/{}".format(
            resource.Meta.name,
            ",".join([str(x["id"]) for x in items])
        )
        return response

    def handler_view_put(self, resource, **kwargs):
        if 'ids' not in kwargs:
            return HttpResponse("Request SHOULD have resource ids", status=400)

        data = resource.put(**kwargs)
        if "errors" in data:
            response = HttpResponse(
                json.dumps(data, cls=DatetimeDecimalEncoder),
                content_type=self.CONTENT_TYPE, status=400)
            return response

        response = HttpResponse(
            json.dumps(data, cls=DatetimeDecimalEncoder),
            content_type=self.CONTENT_TYPE, status=200)
        return response

    def handler_view_delete(self, resource, **kwargs):
        if 'ids' not in kwargs:
            return HttpResponse("Request SHOULD have resource ids", status=400)

        response = resource.delete(**kwargs)
        return HttpResponse(
            response, content_type=self.CONTENT_TYPE, status=204)

    def handler_view(self, request, resource_name, ids=None):
        """ Handler for resources.

        .. versionadded:: 0.5.7
            Content-Type check

        :return django.http.HttpResponse

        """
        signal_request.send(sender=self, request=request)
        time_start = time.time()
        self.update_urls(request, resource_name=resource_name, ids=ids)
        resource = self.resource_map[resource_name]

        allowed_http_methods = resource.Meta.allowed_methods
        if request.method not in allowed_http_methods:
            response = HttpResponseNotAllowed(
                permitted_methods=allowed_http_methods)
            signal_response.send(
                sender=self, request=request, response=response,
                duration=time.time() - time_start)
            return response

        if resource.Meta.authenticators and not (
                request.method == "GET" and
                resource.Meta.disable_get_authentication):
            user = resource.authenticate(request)
            if user is None or not user.is_authenticated():
                response = HttpResponse("Not Authenticated", status=401)
                signal_response.send(
                    sender=self, request=request, response=response,
                    duration=time.time() - time_start)
                return response

        kwargs = dict(request=request)
        if ids is not None:
            kwargs['ids'] = ids.split(",")

        try:
            if request.method == "GET":
                response = self.handler_view_get(resource, **kwargs)
            elif request.method == "POST":
                response = self.handler_view_post(resource, **kwargs)
            elif request.method == "PUT":
                response = self.handler_view_put(resource, **kwargs)
            elif request.method == "DELETE":
                response = self.handler_view_delete(resource, **kwargs)
        except JSONAPIError as e:
            response = HttpResponse(
                json.dumps({"errors": [e.data]}, cls=DatetimeDecimalEncoder),
                content_type=self.CONTENT_TYPE, status=e.status)

        signal_response.send(sender=self, request=request, response=response,
                             duration=time.time() - time_start)
        return response class Resource(Serializer, Authenticator):

    """ Base JSON:API resource class."""

    class Meta:
        name = None
        # fieldnames_include = None  # NOTE: moved to Serializer.
        # fieldnames_exclude = None
        page_size = None
        allowed_methods = 'GET',
        form = None

        @classproperty
        def name_plural(cls):
            return "{0}s".format(cls.name)

    @classmethod
    def get_queryset(cls, user=None, **kwargs):
        """ Get objects queryset.

        Method is used to generate objects queryset for resource operations.
        It is aimed to:
            * Filter objects based on user. Object could be in queryset only if
            there is attribute-ForeignKey-ManyToMany path from current resource
            to current auth_user.
            * Select related objects (or prefetch them) based on requested
            requested objects to include

        NOTE: use user from parameters, it could be authenticated not with
            session, so request.user might not work

        """
        queryset = cls.Meta.model.objects
        if cls.Meta.authenticators:
            queryset = cls.update_user_queryset(queryset, user, **kwargs)
        return queryset

    @classmethod
    def update_user_queryset(cls, queryset, user=None, **kwargs):
        """ Update queryset based on given user.

        .. versionadded:: 0.6.9

        Method is used to control permissions during resource management.

        """
        user_filter = models.Q()
        for path in cls.Meta.model_info.auth_user_paths:
            querydict = {path: user} if path else {"id": user.id}
            user_filter = user_filter | models.Q(**querydict)

        queryset = queryset.filter(user_filter)

        return queryset

    @classmethod
    def get_filters(cls, filters):
        """ Filter given queryset.

        .. note:: Method is used to define custom filters.

        Parameters
        ----------
        filters : list
            list of strings in form 'a=b' to apply to queryset

        Returns
        -------
        dict
            key is django filter expression, value is a filter value.

        """
        result = dict()

        for f in filters:
            if not isinstance(f, six.string_types):
                msg = "Value {} is not supported for filtering".format(f)
                raise ValueError(msg)
            result.update(dict([f.split('=', 1)]))

        return result

    @classmethod
    def update_get_queryset(cls, queryset, **kwargs):
        """ Update permission queryset for GET operations."""
        return queryset

    @classmethod
    def update_post_queryset(cls, queryset, **kwargs):
        """ Update permission queryset for POST operations."""
        return queryset

    @classmethod
    def update_put_queryset(cls, queryset, **kwargs):
        """ Update permission queryset for PUT operations."""
        return queryset

    @classmethod
    def update_delete_queryset(cls, queryset, **kwargs):
        """ Update permission queryset for delete operations."""
        return queryset

    @classmethod
    def get_form(cls):
        """ Create Partial Form based on given fields."""
        if cls.Meta.form:
            return cls.Meta.form

        meta_attributes = {"model": cls.Meta.model, "fields": '__all__'}
        Form = type('Form', (ModelForm,), {
            "Meta": type('Meta', (object,), meta_attributes)
        })
        return Form

    @classmethod
    def get_partial_form(cls, Form, fields):
        """ Get partial form based on original Form and fields set.

        :param Form: django.forms.ModelForm
        :param list fields: list of field names.

        """
        if not fields:
            return Form

        if not set(fields) <= set(Form.base_fields.keys()):
            # Set of requested fields is not subset of original form fields
            # Django itself does not raise exception here.
            fields = set(fields) & set(Form.base_fields.keys())
        meta_attributes = dict(fields=list(fields))

        # NOTE: if Form was created automatically, it's Meta is inherited from
        # object already, double inheritance raises error. If form is general
        # ModelForm created by user, it's Meta is not inherited from object and
        # PartialForm creation raises error.
        meta_bases = (Form.Meta,)
        if not issubclass(Form.Meta, object):
            meta_bases += (object,)

        PartialForm = type('PartialForm', (Form,), {
            "Meta": type('Meta', meta_bases, meta_attributes)
        })
        return PartialForm

    @classmethod
    def _get_include_structure(cls, include=None):
        result = []
        include = include or []

        for include_path in include:
            current_model = cls.Meta.model
            field_path = []

            for include_name in include_path.split('.'):
                model_info = model_inspector.models[current_model]
                field = model_info.field_resource_map[include_name]
                field_path.append(field)
                current_model = field.related_model

            result.append({
                "field_path": field_path,
                "model_info": model_inspector.models[current_model],
                "resource": cls.Meta.api.model_resource_map[current_model],
                "type": field_path[-1].related_resource_name,
                "query": "__".join([f.name for f in field_path])
            })

        return result

    @classmethod
    def get(cls, request=None, **kwargs):
        """ Get resource http response.

        :return str: resource

        """
        user = cls.authenticate(request)
        queryset = cls.get_queryset(user=user, **kwargs)
        queryargs = RequestParser.parse(request.GET)

        # Filters
        if 'ids' in kwargs:
            queryset = queryset.filter(id__in=kwargs['ids'])

        queryset = queryset.filter(**cls.get_filters(queryargs.filter))

        # Distinct
        if queryargs.distinct:
            queryset = queryset.distinct(*queryargs.distinct)

        # Sort
        if queryargs.sort:
            queryset = queryset.order_by(*queryargs.sort)

        include = queryargs.include
        include_structure = cls._get_include_structure(include)

        # Update queryset based on include parameters.
        for include_resource in include_structure:
            field = include_resource['field_path'][-1]
            related_query_type = 'select_related' \
                if field.category == field.CATEGORIES.TO_ONE \
                else 'prefetch_related'

            queryset = getattr(queryset, related_query_type)(
                include_resource['query'])

        # Fields serialisation
        # NOTE: currently filter only own fields
        model_info = cls.Meta.model_info
        fields_own = model_info.fields_own
        if queryargs.fields:
            fieldnames = queryargs.fields
            fields_own = [f for f in fields_own if f.name in fieldnames]

        objects = queryset.all()
        meta = {}
        if cls.Meta.page_size is not None:
            paginator = Paginator(queryset, cls.Meta.page_size)
            page = int(queryargs.page or 1)
            meta["count"] = paginator.count
            meta["num_pages"] = paginator.num_pages
            meta["page_size"] = cls.Meta.page_size
            meta["page"] = page
            objects = paginator.page(page)

            meta["page_next"] = objects.next_page_number() \
                if objects.has_next() else None
            meta["page_prev"] = objects.previous_page_number() \
                if objects.has_previous() else None

        response = cls.dump_documents(
            cls,
            objects,
            fields_own=fields_own,
            include_structure=include_structure
        )
        if meta:
            response["meta"] = meta
        return response

    @classmethod
    def extract_resource_items(cls, request):
        """ Extract resources from django request.

        :param: request django.HttpRequest
        :return: (items, is_collection)
        is_collection is True if items is list, False if object
        items is list of items

        """
        jdata = request.body.decode('utf8')
        try:
            data = json.loads(jdata)
        except ValueError:
            raise JSONAPIParseError(detail=jdata)

        try:
            items = data["data"]
        except KeyError:
            raise JSONAPIInvalidRequestDataMissingError()
        is_collection = isinstance(items, list)

        if not is_collection:
            items = [items]

        return (items, is_collection)

    @classmethod
    def clean_resources(cls, resources, request=None, **kwargs):
        """ Clean resources before models management.

        If models management requires resources, such as database calls or
        external services communication, one may possible to clean resources
        before it.
        It is also possible to validate user permissions to do operation. Use
        form validation if validation does not require user access and Resource
        validation (this method) if it requires to access user object.

        Parameters
        ----------
        resources : list
            List of dictionaries - serialized objects.

        Returns
        -------
        resources : list
            List of cleaned resources

        Raises
        ------
        django.forms.ValidationError in case of validation errors

        """
        return resources

    @classmethod
    def _post_put(cls, request=None, **kwargs):
        """ General method for post and put requests."""
        items, is_collection = cls.extract_resource_items(request)

        try:
            items = cls.clean_resources(items, request=request, **kwargs)
        except ValidationError as e:
            raise JSONAPIResourceValidationError(detail=e.message)

        if request.method == "PUT":
            ids_set = set([int(_id) for _id in kwargs['ids']])
            item_ids_set = {item["id"] for item in items}
            if ids_set != item_ids_set:
                msg = "ids set in url and request body are not matched"
                raise JSONAPIError(detail=msg)

            user = cls.authenticate(request)
            queryset = cls.get_queryset(user=user, **kwargs)
            queryset = cls.update_put_queryset(queryset, **kwargs)
            objects_map = queryset.in_bulk(kwargs["ids"])

            if len(objects_map) < len(kwargs["ids"]):
                msg = "You do not have access to objects {}".format(
                    list(ids_set - set(objects_map.keys()))
                )
                raise JSONAPIForbiddenError(detail=msg)

        forms = []
        attributes_include = []
        fieldnames_to_many = []
        for item in items:
            if 'links' in item:
                fieldnames_to_many = [
                    k for k, v in item['links'].items() if isinstance(v, list)]
                item.update(item.pop('links'))

            # Split resource data into original resource and attributes dict
            # with keys from resource.Meta.fieldnames_include. Included fields
            # could be of two types: 1) included in resource, but not model; 2)
            # included in model (properties). In both cases fields could not be
            # saved with form. Set those fields as attributes. In case 1)
            # nothing would happed, because model does not have such attribute.
            # In case 2) if model has setter for property, it would be set, if
            # not, catch AttributeError and do nothing with it.

            attribute_keys = set(item.keys()) & set(cls.Meta.fieldnames_include)
            attributes_include.append({
                k: v for k, v in item.items() if k in attribute_keys})

            for key in attribute_keys:
                del item[key]

            # Prepare forms
            if request.method == "POST":
                Form = cls.get_form()
                form = Form(item)
            elif request.method == "PUT":
                Form = cls.get_partial_form(cls.get_form(), item.keys())
                instance = objects_map[item["id"]]
                form = Form(item, instance=instance)

            forms.append(form)

        for index, form in enumerate(forms):
            if not form.is_valid():
                raise JSONAPIFormValidationError(
                    links=["/data/{}".format(index)],
                    paths=["/{}".format(attr) for attr in form.errors],
                    data=form.errors
                )

        data = []
        try:
            with transaction.atomic():
                for form, instance_attributes in zip(forms, attributes_include):
                    instance = form.save()

                    # Set instance attributes: resource attributes or model
                    # properties with setters if exist.
                    for key, value in instance_attributes.items():
                        try:
                            setattr(instance, key, value)
                        except AttributeError:
                            # Do nothing if model's property does not have
                            # setter
                            pass

                    # save model only if there are attributes set.
                    if instance_attributes:
                        instance.save()

                    dumped_resource = cls.dump_document(instance)

                    for fieldname_to_many in fieldnames_to_many:
                        dumped_resource['links'][fieldname_to_many] = [
                            x.id for x in form.cleaned_data[fieldname_to_many]]

                    data.append(dumped_resource)
        except IntegrityError as e:
            raise JSONAPIIntegrityError(detail=str(e))
        except Exception as e:
            raise JSONAPIFormSaveError(detail=str(e))

        if not is_collection:
            data = data[0]

        response = dict(data=data)
        return response

    @classmethod
    def post(cls, request=None, **kwargs):
        return cls._post_put(request=request, **kwargs)

    @classmethod
    def put(cls, request=None, **kwargs):
        return cls._post_put(request=request, **kwargs)

    @classmethod
    def delete(cls, request=None, **kwargs):
        user = cls.authenticate(request)
        queryset = cls.get_queryset(user=user, **kwargs)\
            .filter(id__in=kwargs['ids'])

        if len(kwargs['ids']) > queryset.count():
            raise JSONAPIForbiddenError()
        queryset.delete()
        return "" class ModelInspector(object):

    """ Inspect Django models."""

    def inspect(self):
        user_model = get_user_model()

        self.models = {
            model: ModelInfo(
                get_model_name(model),
                fields_own=self._get_fields_own(model),
                fields_to_one=self._get_fields_self_foreign_key(model),
                fields_to_many=self._get_fields_others_foreign_key(model) +
                self._get_fields_self_many_to_many(model) +
                self._get_fields_others_many_to_many(model),
                is_user=(model is user_model or issubclass(model, user_model))
            ) for model in get_models()
        }

        for model, model_info in self.models.items():
            if model_info.is_user:
                model_info.auth_user_paths = ['']
            else:
                self._update_auth_user_paths_model(model)

            model_info.field_resource_map = {
                f.related_resource_name: f
                for f in model_info.fields_to_one + model_info.fields_to_many
            }

    @classmethod
    def _filter_child_model_fields(cls, fields):
        """ Keep only related model fields.

        Example: Inherited models: A -> B -> C
        B has one-to-many relationship to BMany.
        after inspection BMany would have links to B and C. Keep only B. Parent
        model A could not be used (It would not be in fields)

        :param list fields: model fields.
        :return list fields: filtered fields.

        """
        indexes_to_remove = set([])
        for index1, field1 in enumerate(fields):
            for index2, field2 in enumerate(fields):
                if index1 < index2 and index1 not in indexes_to_remove and\
                        index2 not in indexes_to_remove:
                    if issubclass(field1.related_model, field2.related_model):
                        indexes_to_remove.add(index1)

                    if issubclass(field2.related_model, field1.related_model):
                        indexes_to_remove.add(index2)

        fields = [field for index, field in enumerate(fields)
                  if index not in indexes_to_remove]

        return fields

    @classmethod
    def _get_fields_own(cls, model):
        fields = [
            Field(
                name=field.name,
                related_model=None,
                django_field=field,
                category=Field.CATEGORIES.OWN
            ) for field in model._meta.fields
            if field.rel is None and (field.serialize or field.name == 'id')
        ]
        return fields

    @classmethod
    def _get_fields_self_foreign_key(cls, model):
        fields = [
            Field(
                name=field.name,
                related_model=field.rel.to,
                django_field=field,
                category=Field.CATEGORIES.TO_ONE
            ) for field in model._meta.fields
            if field.rel and field.rel.multiple
        ]
        return fields

    @classmethod
    def _get_fields_others_foreign_key(cls, model):
        """ Get to-namy related field.

        If related model has children, link current model only to related.
        Child links make relationship complicated.

        """
        fields = [
            Field(
                name=field.rel.related_name or "{}_set".format(
                    get_model_name(related_model)),
                related_model=related_model,
                django_field=field,
                category=Field.CATEGORIES.TO_MANY
            ) for related_model in get_models()
            if not related_model._meta.proxy
            for field in related_model._meta.fields
            if field.rel and field.rel.to is model._meta.concrete_model and
            field.rel.multiple
        ]
        fields = cls._filter_child_model_fields(fields)
        return fields

    @classmethod
    def _get_fields_self_many_to_many(cls, model):
        fields = [
            Field(
                name=field.name,
                related_model=field.rel.to,
                django_field=field,
                category=Field.CATEGORIES.TO_MANY
            ) for field in model._meta.many_to_many
        ]
        return fields

    @classmethod
    def _get_fields_others_many_to_many(cls, model):
        fields = [
            Field(
                name=field.rel.related_name or "{}_set".format(
                    get_model_name(related_model)),
                related_model=related_model,
                django_field=field,
                category=Field.CATEGORIES.TO_MANY
            ) for related_model in get_models()
            if related_model is not model
            for field in related_model._meta.many_to_many
            if field.rel.to is model._meta.concrete_model
        ]
        fields = cls._filter_child_model_fields(fields)
        return fields

    def _update_auth_user_paths_model(self, model):
        # (field from previous model, related field of this model, model)
        paths = [[(None, None, model)]]

        while paths:
            current_paths = paths
            paths = []

            for current_path in current_paths:
                current_model = current_path[-1][-1]
                current_model_info = self.models[current_model]

                # NOTE: calculate used models links. Link is defined by model
                # and field used.
                used_links = set()
                for node1, node2 in zip(current_path[:-1], current_path[1:]):
                    used_links.add((node1[2], node1[1]))
                    used_links.add((node2[2], node2[1]))
                    used_links.add((node1[2], node2[0]))

                for field in current_model_info.relation_fields:
                    related_model = field.related_model
                    related_model_info = self.models[related_model]

                    for related_field in related_model_info.relation_fields:
                        related_related_model = related_field.related_model
                        if (related_related_model is current_model or
                            issubclass(current_model, related_related_model)) \
                            and (current_model, field) not in used_links \
                            and (related_model, related_field) not in \
                                used_links:

                            path = current_path + [
                                (field, related_field, related_model)]

                            if related_model_info.is_user:
                                self.models[model].auth_user_paths.append(
                                    "__".join([
                                        get_model_name(p[2]) for p in path[1:]
                                    ]))
                            else:
                                paths.append(path) class Field(object):

    """ Field information.

    is_bidirectional is True if related model has reference to current model as
    well.

    Example:
    A -@ B -> BChild

    A has reference to B, but not BChild, but both B and BChild have reference
    to A. A to B fields are bidirectional, BChild to A field is not
    bidirectional.

    """

    CATEGORIES = Choices(
        ('own', 'OWN'),
        ('to_one', 'TO_ONE'),
        ('to_many', 'TO_MANY'),
    )

    def __init__(self, name, category, related_model=None, django_field=None):
        self.name = name
        self.related_model = related_model
        self.category = category
        self.is_bidirectional = None
        self.django_field = django_field

    @property
    def query_name(self):
        """ Get field name used in queries."""
        return get_model_name(get_parent(self.related_model))

    @property
    def related_resource_name(self):
        name = self.query_name
        name += "s" if self.category == self.CATEGORIES.TO_MANY else ""
        return name

    def __repr__(self):
        suffix = "({})".format(get_model_name(self.related_model))\
            if self.related_model else ""
        return "<Field: {}{}>".format(self.name, suffix)

    def __hash__(self):
        return hash((self.name, self.related_model, self.category))

    def __eq__(self, other):
        return hash(self) == hash(other) class AAbstractOne(models.Model):
    field = models.IntegerField() class AAbstractManyToMany(models.Model):
    field = models.IntegerField() class AAbstract(models.Model):
    class Meta:
        abstract = True

    field_abstract = models.IntegerField()
    user = models.ForeignKey(User)
    a_abstract_one = models.ForeignKey(AAbstractOne)
    a_abstract_many_to_manys = models.ManyToManyField(
        AAbstractManyToMany,
        related_name="%(app_label)s_%(class)s_related"
    ) class AA(AAbstract):
    pass class AOne(models.Model):
    field = models.IntegerField() class AManyToMany(models.Model):
    field = models.IntegerField() class A(AA):
    field_a = models.IntegerField()
    a_one = models.ForeignKey(AOne)
    a_many_to_manys = models.ManyToManyField(AManyToMany) class B(A):
    field_b = models.IntegerField() class BMany(models.Model):
    field = models.IntegerField()
    b = models.ForeignKey(B, related_name="bmanys") class BManyToMany(models.Model):
    field = models.IntegerField()
    bs = models.ManyToManyField(B, related_name="bmanytomanys") class BProxy(B):
    class Meta:
        proxy = True class Post(models.Model):
    title = models.CharField(max_length=100)
    author = models.ForeignKey(Author)
    user = models.ForeignKey(User, null=True, blank=True)

    @property
    def title_uppercased(self):
        return self.title.upper()

    @title_uppercased.setter
    def title_uppercased(self, value):
        if value != value.upper():
            raise ValueError("Value of title_uppercased should be uppercased")
        self.title = value class BManyToManyChild(BManyToMany):
    pass
from django.test import TestCase
from django.contrib.auth import get_user_model
from jsonapi.api import API
from jsonapi.resource import Resource
from jsonapi.model_inspector import ModelInspector, Field
from ..models import (
    AAbstractOne, AAbstractManyToMany, AAbstract, AA, AOne, AManyToMany,
    A, B, BMany, BManyToMany, BProxy, Post, BManyToManyChild
)



class TestResourceRelationship(TestCase):
    def setUp(self):
        self.api = API()
        self.classes = dict(
            AAbstractOne=AAbstractOne,
            AAbstractManyToMany=AAbstractManyToMany,
            AAbstract=AAbstract,
            AA=AA,
            AOne=AOne,
            AManyToMany=AManyToMany,
            A=A,
            B=B,

            BMany=BMany,