Repository: django-json-api/django-rest-framework-json-api

Issue:
Invalid detail route rendering when using results as field name
I have a basic django app with the following structure:
(omitted a few other models to make it more readable)

```
# models.py
from django.contrib.postgres.fields import JSONField

class Recording(TimeStampedModel):
    title = models.CharField(max_length=255, blank=True, default="")
    user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
    status = models.CharField(default="new", max_length=64)
    audio = models.ForeignKey("Audio", null=True, on_delete=models.SET_NULL)
    language = models.CharField(max_length=255, blank=True)
    results = JSONField(default=dict)

# serializers.py
from rest_framework_json_api import serializers
from .models import Recording
class RecordingSerializer(serializers.ModelSerializer):
    class Meta:
        model = Recording
        fields = ("id", "language", "audio", "status", "results")
        read_only_fields = ("status",)

# views.py
class RecordingViewSet(viewsets.ModelViewSet):
    model = Recording
    serializer_class = RecordingSerializer
    queryset = Recording.objects.all().order_by("-id")
    
    # explicit renderer to show the problem
    renderer_classes = (renderers.JSONRenderer,)

# urls.py
router = routers.DefaultRouter(trailing_slash=False)
router.register("recording", RecordingViewSet)
urlpatterns = [
path("api/", include(router.urls)),
]
```

When I visit: `/api/recording`, I get a correct output structure:
```
{
  "links": {
    "first": "http://transkriptor.test/api/recording?page%5Bnumber%5D=1",
    "last": "http://transkriptor.test/api/recording?page%5Bnumber%5D=1",
    "next": null,
    "prev": null
  },
  "data": [
    {
      "type": "recording",
      "id": "16",
      "attributes": {
        "language": "",
        "status": "new",
        "results": {
          
        }
      },
      "relationships": {
        "audio": {
          "data": {
            "type": "audio",
            "id": "82"
          }
        }
      }
    },
    {
      "type": "recording",
      "id": "15",
      "attributes": {
        "language": "",
        "status": "new",
        "results": {
          
        }
      },
      "relationships": {
        "audio": {
          "data": {
            "type": "audio",
            "id": "81"
          }
        }
      }
    },
...
}
```

But when I try to view a single record: `/api/recording/16`, I get fallback JSON structure instead of JSON API:

```
{
  "data": {
    "id": 16,
    "language": "",
    "audio": {
      "type": "audio",
      "id": "82"
    },
    "status": "new",
    "results": {
      
    }
  }
}
```

If I remove `results` from serializer (such as: `fields = ("id", "language", "audio", "status")`), then I get expected result structure:

```
{
  "data": {
    "type": "recording",
    "id": "16",
    "attributes": {
      "language": "",
      "status": "new"
    },
    "relationships": {
      "audio": {
        "data": {
          "type": "audio",
          "id": "82"
        }
      }
    }
  }
}
```

I would expect one of two things to happen:
A) There would be exception triggered somewhere if DJA doesn't know how to correctly format the data
or B) The output would be the same between listing and detail view

Versions:
```
djangorestframework==3.9.0
djangorestframework-jsonapi==2.6.0
Django==2.1.3
```

I've used `renderer_classes` to remove the variability of any default renderers or request headers. I've experimented with appending `format=vnd.api%2Bjson` but it doesn't change the output.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api) Report
> Merging [#987](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api) (7502b2d) into [master](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/commit/29971b45aba199c290b0a7e789421642c8f95d30?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api) (29971b4) will **decrease** coverage by `0.03%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/graphs/tree.svg?width=650&height=150&src=pr&token=kJ4T4hcVh8&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api)](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api)

```diff
@@            Coverage Diff             @@
##           master     #987      +/-   ##
==========================================
- Coverage   96.82%   96.79%   -0.04%     
==========================================
  Files          65       65              
  Lines        3906     3866      -40     
==========================================
- Hits         3782     3742      -40     
  Misses        124      124              
```


| [Impacted Files](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api) | Coverage Δ | |
|---|---|---|
| [tests/test\_serializers.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-dGVzdHMvdGVzdF9zZXJpYWxpemVycy5weQ==) | `100.00% <100.00%> (ø)` | |
| [rest\_framework\_json\_api/renderers.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-cmVzdF9mcmFtZXdvcmtfanNvbl9hcGkvcmVuZGVyZXJzLnB5) | `89.96% <0.00%> (-0.20%)` | :arrow_down: |
| [rest\_framework\_json\_api/relations.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-cmVzdF9mcmFtZXdvcmtfanNvbl9hcGkvcmVsYXRpb25zLnB5) | `88.34% <0.00%> (-0.12%)` | :arrow_down: |
| [rest\_framework\_json\_api/parsers.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-cmVzdF9mcmFtZXdvcmtfanNvbl9hcGkvcGFyc2Vycy5weQ==) | `98.52% <0.00%> (-0.07%)` | :arrow_down: |
| [tests/conftest.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-dGVzdHMvY29uZnRlc3QucHk=) | `100.00% <0.00%> (ø)` | |
| [tests/test\_views.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-dGVzdHMvdGVzdF92aWV3cy5weQ==) | `100.00% <0.00%> (ø)` | |
| [example/factories.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-ZXhhbXBsZS9mYWN0b3JpZXMucHk=) | `100.00% <0.00%> (ø)` | |
| [example/tests/test\_errors.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-ZXhhbXBsZS90ZXN0cy90ZXN0X2Vycm9ycy5weQ==) | `100.00% <0.00%> (ø)` | |
| [example/tests/test\_openapi.py](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api#diff-ZXhhbXBsZS90ZXN0cy90ZXN0X29wZW5hcGkucHk=) | `100.00% <0.00%> (ø)` | |
| ... and [10 more](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api). Last update [29971b4...7502b2d](https://codecov.io/gh/django-json-api/django-rest-framework-json-api/pull/987?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=django-json-api).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97ef872..30ef91e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,8 +12,9 @@ any parts of the framework not mentioned in the documentation should generally b
 
 ### Fixed
 
-* Adjusted error messages to correctly use capitial "JSON:API" abbreviation as used in the specification.
+* Adjusted error messages to correctly use capital "JSON:API" abbreviation as used in the specification.
 * Avoid error when `parser_context` is `None` while parsing.
+* Raise comprehensible error when reserved field names `meta` and `results` are used.
 
 ### Changed
 
diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py
index ce80874..ee6d15b 100644
--- a/rest_framework_json_api/serializers.py
+++ b/rest_framework_json_api/serializers.py
@@ -184,6 +184,25 @@ class LazySerializersDict(Mapping):
 
 
 class SerializerMetaclass(SerializerMetaclass):
+
+    _reserved_field_names = {"meta", "results"}
+
+    @classmethod
+    def _get_declared_fields(cls, bases, attrs):
+        fields = super()._get_declared_fields(bases, attrs)
+
+        found_reserved_field_names = cls._reserved_field_names.intersection(
+            fields.keys()
+        )
+        if found_reserved_field_names:
+            raise AttributeError(
+                f"Serializer class {attrs['__module__']}.{attrs['__qualname__']} uses "
+                f"following reserved field name(s) which is not allowed: "
+                f"{', '.join(sorted(found_reserved_field_names))}"
+            )
+
+        return fields
+
     def __new__(cls, name, bases, attrs):
         serializer = super().__new__(cls, name, bases, attrs)
 


Test patch:
diff --git a/tests/test_serializers.py b/tests/test_serializers.py
index 6726fa8..505111c 100644
--- a/tests/test_serializers.py
+++ b/tests/test_serializers.py
@@ -1,3 +1,4 @@
+import pytest
 from django.db import models
 
 from rest_framework_json_api import serializers
@@ -33,3 +34,17 @@ def test_get_included_serializers():
     }
 
     assert included_serializers == expected_included_serializers
+
+
+def test_reserved_field_names():
+    with pytest.raises(AttributeError) as e:
+
+        class ReservedFieldNamesSerializer(serializers.Serializer):
+            meta = serializers.CharField()
+            results = serializers.CharField()
+
+    assert str(e.value) == (
+        "Serializer class tests.test_serializers.test_reserved_field_names.<locals>."
+        "ReservedFieldNamesSerializer uses following reserved field name(s) which is "
+        "not allowed: meta, results"
+    )