Repository: django-tastypie/django-tastypie

Issue:
TypeError: 'NoneType' object is not iterable
I'm hitting what appears to be a logic error in the `dehydrate` method of `ToManyField` class in _fields.py_:
The code in question starts at line 856: 
```
        if the_m2ms is None:
            if not self.null:
                raise ApiFieldError("The model '%r' has an empty attribute '%s' and doesn't allow a null value." % (previous_obj, attr))

        if isinstance(the_m2ms, models.Manager):
            the_m2ms = the_m2ms.all()

        m2m_dehydrated = [
            self.dehydrate_related(
                Bundle(obj=m2m, request=bundle.request),
                self.get_related_resource(m2m),
                for_list=for_list
            )
            for m2m in the_m2ms
        ]
```
Let's say `the_m2ms` is `None` and `self.null` is `False`. The code will not raise an error, will not execute the `if isinstance` clause and will land on the `for` iteration. At this point a `TypeError: 'NoneType' object is not iterable` is thrown.

To replicate this, create a `ModelResource` subclass  with a self-referencing related field:
```class MyResource(ModelResource):
    children = fields.ToManyField('self', 'children', null=True, full=True)
```





Discussion:
Hello @SKisContent,

I am currently facing the same issue. Did you find a solution ?
I seems it's been introduced in version 0.14.0. Take a look:
https://github.com/django-tastypie/django-tastypie/blob/v0.13.3/tastypie/fields.py#L861
https://github.com/django-tastypie/django-tastypie/blob/v0.14.0/tastypie/fields.py#L859

The question is: why?
> The question is: why?

Because there are no tests covering this situation, so a regression was inevitable.  If you could add a test in your PR that fails without your code change, I'll be happy to merge it and cut a new release (we need one for 'official' Django 2.0 compatibility anyway).

Without a test, this regression will almost certainly happen again.
Hey, toto22450,
I never did. I think I just worked around it.
@toto22450 @SKisContent 
I'm unable to replicate this issue in a unit test.  It appears to only be possible when using a callable attribute or a relationship specified with a string that returns `None`.  Can you post your `Resource` and the request that triggers the error?

Resolving patch:
diff --git a/tastypie/fields.py b/tastypie/fields.py
--- a/tastypie/fields.py
+++ b/tastypie/fields.py
@@ -856,6 +856,7 @@ def dehydrate(self, bundle, for_list=True):
         if the_m2ms is None:
             if not self.null:
                 raise ApiFieldError("The model '%r' has an empty attribute '%s' and doesn't allow a null value." % (previous_obj, attr))
+            return []
 
         if isinstance(the_m2ms, models.Manager):
             the_m2ms = the_m2ms.all()


Test patch:
diff --git a/tests/core/tests/fields.py b/tests/core/tests/fields.py
--- a/tests/core/tests/fields.py
+++ b/tests/core/tests/fields.py
@@ -763,6 +763,10 @@ def test_dehydrate_with_callable(self):
         field_3 = ToOneField(UserResource, lambda bundle: None)
         self.assertRaises(ApiFieldError, field_3.dehydrate, bundle)
 
+        # Regression: dehydrating a field with no related data and null=True should not yield exception
+        field_4 = ToManyField(UserResource, null=True, attribute=lambda bundle: None)
+        self.assertEqual(field_4.dehydrate(bundle), [])
+
     def test_dehydrate_full_detail_list(self):
         note = Note.objects.get(pk=1)
         request = MockRequest()
@@ -778,6 +782,13 @@ def test_dehydrate_full_detail_list(self):
         field_1 = ToOneField(UserResource, 'author', full=True, full_detail=False)
         self.assertEqual(field_1.dehydrate(bundle, for_list=False), '/api/v1/users/1/')
 
+    def test_dehydrate_bad_attribute(self):
+        note = Note.objects.get(pk=1)
+        bundle = Bundle(obj=note)
+        field_1 = ToManyField(UserResource, 'bad_attribute_name')
+        with self.assertRaises(ApiFieldError):
+            field_1.dehydrate(bundle)
+
     def test_hydrate(self):
         note = Note()
         bundle = Bundle(obj=note)