Repository: encode/django-rest-framework

Issue:
ManyRelatedField.choices tries to iterate over None
`ManyRelatedField.choices` is a `property` which [iterates over](https://github.com/tomchristie/django-rest-framework/blob/311cad64c998a89b8e8e2394140e0265ff98f53d/rest_framework/relations.py#L376-L383) `self.child_relation.queryset`. This can be `None` if `self.child_relation` is read-only.
I found this when `SimpleMetadata.get_field_info` tried to [check if `field.choices` exists](https://github.com/tomchristie/django-rest-framework/blob/311cad64c998a89b8e8e2394140e0265ff98f53d/rest_framework/metadata.py#L130) and raised `TypeError`.



Discussion:
I'm not sure where the best place to fix this is. A simple fix for my direct issue would be to catch the `TypeError` in `SimpleMetadata.get_field_info`, but I'm not sure `ManyRelatedField.choices` should be raising it in the first place.

I went into similar issue a few days ago. I got this when making an OPTIONS request and it seems the response tries to iterate over field choices, even if field is `read only` (which a simple GET resquest doesn't do).

I fixed this by adding (file: relations.py, line:380)

``` python
       iterable = queryset.all() if (hasattr(queryset, 'all')) else queryset
+      if not iterable:
+          return { }
```

This is perhaps a bad behaviour of the OPTION request that could check if field is not read_only before calling `choices()`. But, on the other hand, the `choice` method is not robust...

Best regards.


Resolving patch:
diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py
--- a/rest_framework/metadata.py
+++ b/rest_framework/metadata.py
@@ -127,7 +127,7 @@ def get_field_info(self, field):
             if value is not None and value != '':
                 field_info[attr] = force_text(value, strings_only=True)
 
-        if hasattr(field, 'choices'):
+        if not field_info.get('read_only') and hasattr(field, 'choices'):
             field_info['choices'] = [
                 {
                     'value': choice_value,


Test patch:
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -1,4 +1,7 @@
 from __future__ import unicode_literals
+from django.db import models
+from django.test import TestCase
+from django.core.validators import MinValueValidator, MaxValueValidator
 from rest_framework import exceptions, metadata, serializers, status, views, versioning
 from rest_framework.request import Request
 from rest_framework.renderers import BrowsableAPIRenderer
@@ -212,3 +215,83 @@ def test_null_boolean_field_info_type(self):
         options = metadata.SimpleMetadata()
         field_info = options.get_field_info(serializers.NullBooleanField())
         assert field_info['type'] == 'boolean'
+
+
+class TestModelSerializerMetadata(TestCase):
+    def test_read_only_primary_key_related_field(self):
+        """
+        On generic views OPTIONS should return an 'actions' key with metadata
+        on the fields that may be supplied to PUT and POST requests. It should
+        not fail when a read_only PrimaryKeyRelatedField is present
+        """
+        class Parent(models.Model):
+            integer_field = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(1000)])
+            children = models.ManyToManyField('Child')
+            name = models.CharField(max_length=100, blank=True, null=True)
+
+        class Child(models.Model):
+            name = models.CharField(max_length=100)
+
+        class ExampleSerializer(serializers.ModelSerializer):
+            children = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
+
+            class Meta:
+                model = Parent
+
+        class ExampleView(views.APIView):
+            """Example view."""
+            def post(self, request):
+                pass
+
+            def get_serializer(self):
+                return ExampleSerializer()
+
+        view = ExampleView.as_view()
+        response = view(request=request)
+        expected = {
+            'name': 'Example',
+            'description': 'Example view.',
+            'renders': [
+                'application/json',
+                'text/html'
+            ],
+            'parses': [
+                'application/json',
+                'application/x-www-form-urlencoded',
+                'multipart/form-data'
+            ],
+            'actions': {
+                'POST': {
+                    'id': {
+                        'type': 'integer',
+                        'required': False,
+                        'read_only': True,
+                        'label': 'ID'
+                    },
+                    'children': {
+                        'type': 'field',
+                        'required': False,
+                        'read_only': True,
+                        'label': 'Children'
+                    },
+                    'integer_field': {
+                        'type': 'integer',
+                        'required': True,
+                        'read_only': False,
+                        'label': 'Integer field',
+                        'min_value': 1,
+                        'max_value': 1000
+                    },
+                    'name': {
+                        'type': 'string',
+                        'required': False,
+                        'read_only': False,
+                        'label': 'Name',
+                        'max_length': 100
+                    }
+                }
+            }
+        }
+
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == expected