Repository: encode/django-rest-framework

Issue:
DRF generates incorrect attributes for serializers.ListField
## Checklist

- [x] I have verified that that issue exists against the `master` branch of Django REST framework.
- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [x] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce

```
# view.py
from rest_framework import generics
from rest_framework import serializers
from django.db import models

class Book(models.Model):
  title = models.CharField(max_length=200)

class BookSerializer(serializers.ModelSerializer):
  tags = serializers.ListField(min_length=2, max_length=5)
  class Meta:
    model = Book
    fields = ('tags',)

class BooksView(generics.ListAPIView):
  queryset = Book.objects.all()   
  serializer_class = BookSerializer

# urls.py
urlpatterns = [
...
path('api/books/', views.BooksView.as_view()),
...
]
```

## Expected behavior

```
$ ./manage.py generateschema

openapi: 3.0.2
info:
  title: ''
  version: TODO
paths:
  /api/books/:
    get:
      operationId: ListBooks
      parameters: []
      responses:
        '200':
          content:
            application/json:
              schema:
                required:
                - tags
                properties:
                  tags:
                    type: array
                    items: {}
                    maxItems: 5
                    minItems: 2
```

## Actual behavior

```
$ ./manage.py generateschema

openapi: 3.0.2
info:
  title: ''
  version: TODO
paths:
  /api/books/:
    get:
      operationId: ListBooks
      parameters: []
      responses:
        '200':
          content:
            application/json:
              schema:
                required:
                - tags
                properties:
                  tags:
                    type: array
                    items: {}
                    maxLength: 5
                    minLength: 2
```

https://swagger.io/docs/specification/data-models/data-types/#array-length


Discussion:
@carltongibson please confirm whether this is something you'd like me to work on 🙂 
There's one more https://github.com/encode/django-rest-framework/issues/6863
Hi @knivets I’m struggling to spot the difference between the actual and expected here. Could you ping it out for me please? (Sorry)
Oh, items vs length. Right. 
Yes, I guess we need an adjustment here, after mapping validators. (Grrrr. Messy spec. Grrrr. 🙂)

Resolving patch:
diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py
--- a/rest_framework/schemas/openapi.py
+++ b/rest_framework/schemas/openapi.py
@@ -378,7 +378,7 @@ def _map_serializer(self, serializer):
                 schema['default'] = field.default
             if field.help_text:
                 schema['description'] = str(field.help_text)
-            self._map_field_validators(field.validators, schema)
+            self._map_field_validators(field, schema)
 
             properties[field.field_name] = schema
 
@@ -390,13 +390,11 @@ def _map_serializer(self, serializer):
 
         return result
 
-    def _map_field_validators(self, validators, schema):
+    def _map_field_validators(self, field, schema):
         """
         map field validators
-        :param list:validators: list of field validators
-        :param dict:schema: schema that the validators get added to
         """
-        for v in validators:
+        for v in field.validators:
             # "Formats such as "email", "uuid", and so on, MAY be used even though undefined by this specification."
             # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types
             if isinstance(v, EmailValidator):
@@ -406,9 +404,15 @@ def _map_field_validators(self, validators, schema):
             if isinstance(v, RegexValidator):
                 schema['pattern'] = v.regex.pattern
             elif isinstance(v, MaxLengthValidator):
-                schema['maxLength'] = v.limit_value
+                attr_name = 'maxLength'
+                if isinstance(field, serializers.ListField):
+                    attr_name = 'maxItems'
+                schema[attr_name] = v.limit_value
             elif isinstance(v, MinLengthValidator):
-                schema['minLength'] = v.limit_value
+                attr_name = 'minLength'
+                if isinstance(field, serializers.ListField):
+                    attr_name = 'minItems'
+                schema[attr_name] = v.limit_value
             elif isinstance(v, MaxValueValidator):
                 schema['maximum'] = v.limit_value
             elif isinstance(v, MinValueValidator):


Test patch:
diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py
--- a/tests/schemas/test_openapi.py
+++ b/tests/schemas/test_openapi.py
@@ -395,6 +395,9 @@ def test_serializer_validators(self):
         assert properties['string']['minLength'] == 2
         assert properties['string']['maxLength'] == 10
 
+        assert properties['lst']['minItems'] == 2
+        assert properties['lst']['maxItems'] == 10
+
         assert properties['regex']['pattern'] == r'[ABC]12{3}'
         assert properties['regex']['description'] == 'must have an A, B, or C followed by 1222'
 
diff --git a/tests/schemas/views.py b/tests/schemas/views.py
--- a/tests/schemas/views.py
+++ b/tests/schemas/views.py
@@ -85,6 +85,12 @@ class ExampleValidatedSerializer(serializers.Serializer):
         ),
         help_text='must have an A, B, or C followed by 1222'
     )
+    lst = serializers.ListField(
+        validators=(
+            MaxLengthValidator(limit_value=10),
+            MinLengthValidator(limit_value=2),
+        )
+    )
     decimal1 = serializers.DecimalField(max_digits=6, decimal_places=2)
     decimal2 = serializers.DecimalField(max_digits=5, decimal_places=0,
                                         validators=(DecimalValidator(max_digits=17, decimal_places=4),))