Repository: encode/django-rest-framework

Issue:
`RelatedField`s with required=False needs to create form_field_class with empty_labels
Let's say I'm working on an endpoint to represent `Photo`s, and who have a `CopyrightHolder`. (Meaning, Photo has ForeignKeyField with `null=True` for the CopyrightHolder)

My serializers would be like this:

``` python
class PhotoSerializer(serializers.ModelSerializer):
     copyright_holder = serializers.HyperlinkedRelatedField(view_name='holder-detail', required=False)
     class Meta:
           model = models.Photo
```

I believe that validation works correctly, but unless I'm missing something, the generated form does not allow me to edit a photo without setting a copyright holder.



Discussion:
Just to clarify, this relates just to the browsable API, right?  (Or am I misunderstanding and it also applies to submitting via JSON?)

> unless I'm missing something, the generated form does not allow me to edit a photo without setting a copyright holder.

No I don't think you're missing anything, seems valid to me.

Correct. I'm talking about the browsable API.

Cool, all sounds sensible. Take a look in BrowseableAPIRenderer - shouldn't be too hard to resolve. If its problematic shout and I will try to take a look.

On 25 Feb 2013, at 19:22, Raphael Lullis notifications@github.com wrote:

> Correct. I'm talking about the browsable API.
> 
> —
> Reply to this email directly or view it on GitHub.

Believe this is identical to #725.

As I solved #725, I'm taking this one as well

After inspecting a bit, it's the same issue but with `ChoiceField` with `RelatedField`.


Resolving patch:
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -8,6 +8,7 @@
 from django.core.exceptions import ObjectDoesNotExist, ValidationError
 from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch
 from django import forms
+from django.db.models.fields import BLANK_CHOICE_DASH
 from django.forms import widgets
 from django.forms.models import ModelChoiceIterator
 from django.utils.translation import ugettext_lazy as _
@@ -47,7 +48,7 @@ def __init__(self, *args, **kwargs):
                           DeprecationWarning, stacklevel=2)
             kwargs['required'] = not kwargs.pop('null')
 
-        self.queryset = kwargs.pop('queryset', None)
+        queryset = kwargs.pop('queryset', None)
         self.many = kwargs.pop('many', self.many)
         if self.many:
             self.widget = self.many_widget
@@ -56,6 +57,11 @@ def __init__(self, *args, **kwargs):
         kwargs['read_only'] = kwargs.pop('read_only', self.read_only)
         super(RelatedField, self).__init__(*args, **kwargs)
 
+        if not self.required:
+            self.empty_label = BLANK_CHOICE_DASH[0][1]
+
+        self.queryset = queryset
+
     def initialize(self, parent, field_name):
         super(RelatedField, self).initialize(parent, field_name)
         if self.queryset is None and not self.read_only:


Test patch:
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -1139,6 +1139,63 @@ def test_choices_blank_true_with_default(self):
         )
 
 
+# Regression tests for #675
+class Ticket(models.Model):
+    assigned = models.ForeignKey(
+        Person, related_name='assigned_tickets')
+    reviewer = models.ForeignKey(
+        Person, blank=True, null=True, related_name='reviewed_tickets')
+
+
+class SerializerRelatedChoicesTest(TestCase):
+
+    def setUp(self):
+        super(SerializerRelatedChoicesTest, self).setUp()
+
+        class RelatedChoicesSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = Ticket
+                fields = ('assigned', 'reviewer')
+
+        self.related_fields_serializer = RelatedChoicesSerializer
+
+    def test_empty_queryset_required(self):
+        serializer = self.related_fields_serializer()
+        self.assertEqual(serializer.fields['assigned'].queryset.count(), 0)
+        self.assertEqual(
+            [x for x in serializer.fields['assigned'].widget.choices],
+            []
+        )
+
+    def test_empty_queryset_not_required(self):
+        serializer = self.related_fields_serializer()
+        self.assertEqual(serializer.fields['reviewer'].queryset.count(), 0)
+        self.assertEqual(
+            [x for x in serializer.fields['reviewer'].widget.choices],
+            [('', '---------')]
+        )
+
+    def test_with_some_persons_required(self):
+        Person.objects.create(name="Lionel Messi")
+        Person.objects.create(name="Xavi Hernandez")
+        serializer = self.related_fields_serializer()
+        self.assertEqual(serializer.fields['assigned'].queryset.count(), 2)
+        self.assertEqual(
+            [x for x in serializer.fields['assigned'].widget.choices],
+            [(1, 'Person object - 1'), (2, 'Person object - 2')]
+        )
+
+    def test_with_some_persons_not_required(self):
+        Person.objects.create(name="Lionel Messi")
+        Person.objects.create(name="Xavi Hernandez")
+        serializer = self.related_fields_serializer()
+        self.assertEqual(serializer.fields['reviewer'].queryset.count(), 2)
+        self.assertEqual(
+            [x for x in serializer.fields['reviewer'].widget.choices],
+            [('', '---------'), (1, 'Person object - 1'), (2, 'Person object - 2')]
+        )
+
+
 class DepthTest(TestCase):
     def test_implicit_nesting(self):