Repository: django-import-export/django-import-export

Issue:
Reference the model in the Resource meta via string
Guys, it is possible to reference the model in the Resource meta class via string without having to import the model specifically?

- How is today
```py
from core.books import Book

class BookResource(ModelResource):
    class Meta:
        model = Book
```

- How I wish
```py
class BookResource(ModelResource):
    class Meta:
        model = "books.Book"
```

I know that the way I said it doesn't work, but I wanted to know if there is any other way. This is useful to prevent imports from looping.



Discussion:
As an alternative, I made a copy paste of the `import_from_str` function that exists in the package [model_bakery](https://github.com/model-bakers/model_bakery/blob/75735ff68341e2a030d1381b25907e3a4377a76f/model_bakery/utils.py#L16) and it is working as follows form:

```python
from core.utils import import_from_str

class BookResource(ModelResource):
    class Meta:
        model = import_from_str("books.Book")
```

It would be interesting if this already exists as a default in the project.
Hi Carlos - thanks for the update.  I'll leave this open as a potential enhancement though it might be a while before we look at it (we are a small team).  If you wanted to submit a PR to support this, you would be very welcome to do so.
@matthewhegarty Assign me this
Thanks @EricOuma - please can you take a branch from `release-4`
@EricOuma, Django has a function that does this, in this case you could even use it...

```
from django.apps import apps

User = apps.get_model('auth', 'User')
User = apps.get_model('auth.User')
```


Resolving patch:
diff --git a/import_export/resources.py b/import_export/resources.py
--- a/import_export/resources.py
+++ b/import_export/resources.py
@@ -7,6 +7,7 @@
 
 import tablib
 from diff_match_patch import diff_match_patch
+from django.apps import apps
 from django.conf import settings
 from django.core.exceptions import (
     FieldDoesNotExist,
@@ -58,8 +59,8 @@ class ResourceOptions:
 
     model = None
     """
-    Django Model class. It is used to introspect available
-    fields.
+    Django Model class or full application label string. It is used to introspect
+    available fields.
 
     """
     fields = None
@@ -250,6 +251,20 @@ class ResourceOptions:
 
 class DeclarativeMetaclass(type):
     def __new__(cls, name, bases, attrs):
+        def _load_meta_options(base_, meta_):
+            options = getattr(base_, "Meta", None)
+
+            for option in [
+                option
+                for option in dir(options)
+                if not option.startswith("_") and hasattr(options, option)
+            ]:
+                option_value = getattr(options, option)
+                if option == "model" and isinstance(option_value, str):
+                    option_value = apps.get_model(option_value)
+
+                setattr(meta_, option, option_value)
+
         declared_fields = []
         meta = ResourceOptions()
 
@@ -260,13 +275,7 @@ def __new__(cls, name, bases, attrs):
             if hasattr(base, "fields"):
                 declared_fields = list(base.fields.items()) + declared_fields
                 # Collect the Meta options
-                options = getattr(base, "Meta", None)
-                for option in [
-                    option
-                    for option in dir(options)
-                    if not option.startswith("_") and hasattr(options, option)
-                ]:
-                    setattr(meta, option, getattr(options, option))
+                _load_meta_options(base, meta)
 
         # Add direct fields
         for field_name, obj in attrs.copy().items():
@@ -278,15 +287,8 @@ def __new__(cls, name, bases, attrs):
 
         attrs["fields"] = OrderedDict(declared_fields)
         new_class = super().__new__(cls, name, bases, attrs)
-
-        # Add direct options
-        options = getattr(new_class, "Meta", None)
-        for option in [
-            option
-            for option in dir(options)
-            if not option.startswith("_") and hasattr(options, option)
-        ]:
-            setattr(meta, option, getattr(options, option))
+        # add direct fields
+        _load_meta_options(new_class, meta)
         new_class._meta = meta
 
         return new_class


Test patch:
diff --git a/tests/core/tests/test_resources/test_resources.py b/tests/core/tests/test_resources/test_resources.py
--- a/tests/core/tests/test_resources/test_resources.py
+++ b/tests/core/tests/test_resources/test_resources.py
@@ -1715,3 +1715,15 @@ def test_sets_json_data_when_model_field_is_empty(self):
 
             self.book.refresh_from_db()
             self.assertEqual(self.book.data, self.data)
+
+
+class BookResourceWithStringModelTest(TestCase):
+    def setUp(self):
+        class BookResourceWithStringModel(resources.ModelResource):
+            class Meta:
+                model = "core.Book"
+
+        self.resource = BookResourceWithStringModel()
+
+    def test_resource_gets_correct_model_from_string(self):
+        self.assertEqual(self.resource._meta.model, Book)