Repository: carltongibson/django-filter

Issue:
Improve get_field_parts error message
```
def get_field_parts(model, field_name):
    """
    Get the field parts that represent the traversable relationships from the
    base ``model`` to the final field, described by ``field_name``.
    ex::
        >>> parts = get_field_parts(Book, 'author__first_name')
        >>> [p.verbose_name for p in parts]
        ['author', 'first name']
    """
    parts = field_name.split(LOOKUP_SEP)
    opts = model._meta
    fields = []

    # walk relationships
    for name in parts:
        try:
            field = opts.get_field(name)
        except FieldDoesNotExist:
            return None

        fields.append(field)
        if isinstance(field, RelatedField):
            opts = field.remote_field.model._meta
        elif isinstance(field, ForeignObjectRel):
            opts = field.related_model._meta

    return fields
```

`field.remote_field.model._meta` Fails when the related model is string imported: `AttributeError: 'str' object has no attribute '_meta'` Because it is trying to import _meta from the string that represents the model.

A fast fix that came to my mind is:
```
            try:
                opts = field.remote_field.model._meta
            except AttributeError:
                app_label, model_name = field.remote_field.model.split(".")
                opts = apps.get_model(app_label, model_name)._meta
```



Discussion:
@pablomerix Can you put a test together for this please? 

> Fails when the related model is string imported

Not sure I understand this. 
> Fails when the related model is string imported

They mean the FK target is a string instead of a model class. e.g., `models.ForeignKey("myapp.RelatedClass")` instead of `models.ForeignKey(RelatedClass)`.

iirc, what's happening here is that they're importing the filters before Django's app registry is ready. If the app registry isn't ready, the models haven't been resolved for string-based FK targets.

This is a bug in their code, but we should probably raise a more helpful error
> > Fails when the related model is string imported
> 
> They mean the FK target is a string instead of a model class. e.g., `models.ForeignKey("myapp.RelatedClass")` instead of `models.ForeignKey(RelatedClass)`.
> 
> iirc, what's happening here is that they're importing the filters before Django's app registry is ready. If the app registry isn't ready, the models haven't been resolved for string-based FK targets.
> 
> This is a bug in their code, but we should probably raise a more helpful error

Yes @rpkilby , you are completely right. I was using the filter in a model init, ultimately I found a workaround to make it work, so yeah, the error was in my code. And I agree with you that `'str' object has no attribute '_meta'` could be raised with a more explanatory error.

Thank guys.

Resolving patch:
diff --git a/django_filters/utils.py b/django_filters/utils.py
--- a/django_filters/utils.py
+++ b/django_filters/utils.py
@@ -165,10 +165,18 @@ def get_field_parts(model, field_name):
             return None
 
         fields.append(field)
-        if isinstance(field, RelatedField):
-            opts = field.remote_field.model._meta
-        elif isinstance(field, ForeignObjectRel):
-            opts = field.related_model._meta
+        try:
+            if isinstance(field, RelatedField):
+                opts = field.remote_field.model._meta
+            elif isinstance(field, ForeignObjectRel):
+                opts = field.related_model._meta
+        except AttributeError:
+            # Lazy relationships are not resolved until registry is populated.
+            raise RuntimeError(
+                "Unable to resolve relationship `%s` for `%s`. Django is most "
+                "likely not initialized, and its apps registry not populated. "
+                "Ensure Django has finished setup before loading `FilterSet`s."
+                % (field_name, model._meta.label))
 
     return fields
 


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -222,6 +222,22 @@ def test_reverse_related_field(self):
         self.assertIsInstance(parts[1], models.ManyToManyField)
         self.assertIsInstance(parts[2], models.CharField)
 
+    def test_lazy_relationship_not_ready(self):
+        """
+        This simulates trying to create a FilterSet before the app registry has
+        been populated. Lazy relationships have not yet been resolved from their
+        strings into their remote model referencess.
+        """
+        class TestModel(models.Model):
+            fk = models.ForeignKey('remote.Model', on_delete=models.CASCADE)
+
+        msg = ("Unable to resolve relationship `fk__f` for `tests.TestModel`. "
+               "Django is most likely not initialized, and its apps registry "
+               "not populated. Ensure Django has finished setup before loading "
+               "`FilterSet`s.")
+        with self.assertRaisesMessage(RuntimeError, msg):
+            get_field_parts(TestModel, 'fk__f')
+
 
 class GetModelFieldTests(TestCase):