Repository: encode/django-rest-framework

Issue:
M2M model field without `blank=True` should map to a serializer relationship with `allow_empty=False`.
The Django 1.8 system check framework displays the warning [fields.W340](https://docs.djangoproject.com/en/1.8/ref/checks/#related-fields) when the `null` parameter is defined on a `ManyToManyField`, since it has no effect.

This `null` field affects on whether a related field in a model serializer is considered required or not. We can see it [here](https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/utils/field_mapping.py#L226-L227).

From my point of view, this behavior is correct for a `ForeignKey` but not for a `ManyToManyField`, that should be considered not required by default.

Adding the var `to_many` to the condition on the aforementioned code might do the trick.



Discussion:
See test in #2674.

+1

> should be considered not required by default.

For both this and #2674 I'm unsure _exactly_ what behavior you're expecting.
M2M fields default to required, because the input field itself is required, although _it may be an empty list_.

Are you expecting to be able to provide `null` as the input for the M2M field, or are you seeing an issue when providing an empty list?

As currently described the existing behavior seems correct to me - require the input, but allow it to be an empty list.

See my answer in #2674. If `required=True` (the default) the framework should not accept empty lists/arrays. This is because the framework should behave like Django forms and here we got an error if we try to send an empty list to a required form field.

(The other point (not this issue) is that the required flag should be controlled by the model field option `blank` which is not the case at the moment.)

I agree with https://github.com/tomchristie/django-rest-framework/pull/2674#issuecomment-105479180

+1

Retitled more appropriately.
The behavior that's being requested here as far as I understand it is to mirror Django's blank model field -> required input.

I'm not 100% convinced that we want `ManyToManyField(Something)` to always require >= 1 entry and `ManyToManyField(Something, blank=True)` to allow empty lists, and I'd really like to see that discussion pushed to the mailing list, but _certainly_ we should at least allow either case to be specified.

If we do change the behavior we should do so in a major version bump and call it out prominently.

Requires #2250 as a prerequisite.

+1

Accepting this, though given the potential for backwards incompatibility it's probably a good idea if we push it in 3.2.0, to allow us to call it out.

Also note that `allow_empty` is now a valid flag.
You can workaround the behavior here by using `extra_kwargs = {'my_field': {'allow_empty': False}}`.


Resolving patch:
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -64,6 +64,7 @@ def __init__(self, **kwargs):
             'when setting read_only=`True`.'
         )
         kwargs.pop('many', None)
+        kwargs.pop('allow_empty', None)
         super(RelatedField, self).__init__(**kwargs)
 
     def __new__(cls, *args, **kwargs):
diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py
--- a/rest_framework/utils/field_mapping.py
+++ b/rest_framework/utils/field_mapping.py
@@ -225,6 +225,7 @@ def get_relation_kwargs(field_name, relation_info):
             # If this field is read-only, then return early.
             # No further keyword arguments are valid.
             return kwargs
+
         if model_field.has_default() or model_field.null:
             kwargs['required'] = False
         if model_field.null:
@@ -234,6 +235,8 @@ def get_relation_kwargs(field_name, relation_info):
         if getattr(model_field, 'unique', False):
             validator = UniqueValidator(queryset=model_field.model._default_manager)
             kwargs['validators'] = kwargs.get('validators', []) + [validator]
+        if to_many and not model_field.blank:
+            kwargs['allow_empty'] = False
 
     return kwargs
 


Test patch:
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -395,7 +395,7 @@ class Meta:
                 id = IntegerField(label='ID', read_only=True)
                 foreign_key = PrimaryKeyRelatedField(queryset=ForeignKeyTargetModel.objects.all())
                 one_to_one = PrimaryKeyRelatedField(queryset=OneToOneTargetModel.objects.all(), validators=[<UniqueValidator(queryset=RelationalModel.objects.all())>])
-                many_to_many = PrimaryKeyRelatedField(many=True, queryset=ManyToManyTargetModel.objects.all())
+                many_to_many = PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=ManyToManyTargetModel.objects.all())
                 through = PrimaryKeyRelatedField(many=True, read_only=True)
         """)
         self.assertEqual(unicode_repr(TestSerializer()), expected)
@@ -434,7 +434,7 @@ class Meta:
                 url = HyperlinkedIdentityField(view_name='relationalmodel-detail')
                 foreign_key = HyperlinkedRelatedField(queryset=ForeignKeyTargetModel.objects.all(), view_name='foreignkeytargetmodel-detail')
                 one_to_one = HyperlinkedRelatedField(queryset=OneToOneTargetModel.objects.all(), validators=[<UniqueValidator(queryset=RelationalModel.objects.all())>], view_name='onetoonetargetmodel-detail')
-                many_to_many = HyperlinkedRelatedField(many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail')
+                many_to_many = HyperlinkedRelatedField(allow_empty=False, many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail')
                 through = HyperlinkedRelatedField(many=True, read_only=True, view_name='throughtargetmodel-detail')
         """)
         self.assertEqual(unicode_repr(TestSerializer()), expected)