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

Issue:
Resource._meta doesn't get params from parent's parent's Meta
**Describe the bug**
`Resource._meta` does get params from own `Meta` and from parent's `Meta`. But it doesn't get params from `Meta` of parent's parent's `Meta`

**To Reproduce**
Execute the following code:
```
from import_export.resources import Resource

class MyBaseResource(Resource):
    class Meta:
        # random parameter to show error
        batch_size = 123456789


class MyResource(MyBaseResource):
    class Meta:
        pass


class MyChildResource(MyResource):
    class Meta:
        pass

print(f'MyResource: {MyResource()._meta.batch_size}')
print(f'MyChildResource: {MyChildResource()._meta.batch_size}')
```
Output:
```
MyResource: 123456789 # right
MyChildResource: 1000 # wrong, default value of `ResourceOptions.batch_size`
```

**Versions (please complete the following information):**
 - Django Import Export: 2.7.0
 - Python 3.9
 - Django 4.0 

**Expected behavior**
`MyChildResource` should get params from `MyBaseResource`

**Possible Solution**
`import_export.resources.DeclarativeMetaclass` should copy params from parent's `_meta` instead of `Meta`.



Discussion:
thanks - I have seen this before and we will look into when we can.
Just ran into this as well

Resolving patch:
diff --git a/import_export/declarative.py b/import_export/declarative.py
--- a/import_export/declarative.py
+++ b/import_export/declarative.py
@@ -40,6 +40,9 @@ def _load_meta_options(base_, meta_):
             if hasattr(base, "fields"):
                 declared_fields = list(base.fields.items()) + declared_fields
                 # Collect the Meta options
+                # #1363 If there are any parent classes, set those options first
+                for parent in base.__bases__:
+                    _load_meta_options(parent, meta)
                 _load_meta_options(base, meta)
 
         # Add direct fields


Test patch:
diff --git a/tests/core/tests/admin_integration/test_action.py b/tests/core/tests/admin_integration/test_action.py
--- a/tests/core/tests/admin_integration/test_action.py
+++ b/tests/core/tests/admin_integration/test_action.py
@@ -152,6 +152,14 @@ def test_export_button_on_change_form(self):
         )
         self.assertIn("Export 1 selected item", str(response.content))
 
+    def test_save_button_on_change_form(self):
+        # test default behavior is retained when saving an instance ChangeForm
+        response = self.client.post(
+            self.change_url, data={"_save": "Save", "name": self.cat1.name}, follow=True
+        )
+        target_str = f"The category.*{self.cat1.name}.*was changed successfully."
+        self.assertRegex(str(response.content), target_str)
+
     def test_export_button_on_change_form_disabled(self):
         class MockCategoryAdmin(CategoryAdmin):
             show_change_form_export = True
diff --git a/tests/core/tests/test_declarative.py b/tests/core/tests/test_declarative.py
new file mode 100644
--- /dev/null
+++ b/tests/core/tests/test_declarative.py
@@ -0,0 +1,123 @@
+from django.test import TestCase
+
+from import_export import fields
+from import_export.resources import Resource
+
+from .resources import MyResource
+
+
+class TestInheritance(TestCase):
+    # Issue 140 Attributes aren't inherited by subclasses
+    def test_inheritance(self):
+        class A(MyResource):
+            inherited = fields.Field()
+
+            class Meta:
+                import_id_fields = ("email",)
+
+        class B(A):
+            local = fields.Field()
+
+            class Meta:
+                export_order = ("email", "extra")
+
+        resource = B()
+        self.assertIn("name", resource.fields)
+        self.assertIn("inherited", resource.fields)
+        self.assertIn("local", resource.fields)
+        self.assertEqual(
+            resource.get_export_headers(),
+            ["email", "extra", "name", "inherited", "local"],
+        )
+        self.assertEqual(resource._meta.import_id_fields, ("email",))
+
+    def test_inheritance_with_custom_attributes(self):
+        class A(MyResource):
+            inherited = fields.Field()
+
+            class Meta:
+                import_id_fields = ("email",)
+                custom_attribute = True
+
+        class B(A):
+            local = fields.Field()
+
+        resource = B()
+        self.assertEqual(resource._meta.custom_attribute, True)
+
+
+class TestMultiInheritance(TestCase):
+    def test_meta_inheritance_3_levels(self):
+        # issue 1363
+        class GrandparentResource(Resource):
+            class Meta:
+                batch_size = 666
+
+        class ParentResource(GrandparentResource):
+            class Meta:
+                pass
+
+        class ChildResource(ParentResource):
+            class Meta:
+                pass
+
+        parent_resource = ParentResource()
+        child_resource = ChildResource()
+        self.assertEqual(666, parent_resource._meta.batch_size)
+        self.assertEqual(666, child_resource._meta.batch_size)
+
+    def test_meta_inheritance_2_levels(self):
+        class GrandparentResource(Resource):
+            class Meta:
+                batch_size = 666
+
+        class ParentResource(GrandparentResource):
+            class Meta:
+                batch_size = 333
+
+        class ChildResource(ParentResource):
+            class Meta:
+                pass
+
+        parent_resource = ParentResource()
+        child_resource = ChildResource()
+        self.assertEqual(333, parent_resource._meta.batch_size)
+        self.assertEqual(333, child_resource._meta.batch_size)
+
+    def test_meta_inheritance_1_level(self):
+        class GrandparentResource(Resource):
+            class Meta:
+                batch_size = 666
+
+        class ParentResource(GrandparentResource):
+            class Meta:
+                batch_size = 333
+
+        class ChildResource(ParentResource):
+            class Meta:
+                batch_size = 111
+
+        parent_resource = ParentResource()
+        child_resource = ChildResource()
+        self.assertEqual(333, parent_resource._meta.batch_size)
+        self.assertEqual(111, child_resource._meta.batch_size)
+
+    def test_meta_inheritance_default(self):
+        class GrandparentResource(Resource):
+            class Meta:
+                pass
+
+        class ParentResource(GrandparentResource):
+            class Meta:
+                pass
+
+        class ChildResource(ParentResource):
+            class Meta:
+                pass
+
+        grandparent_resource = GrandparentResource()
+        parent_resource = ParentResource()
+        child_resource = ChildResource()
+        self.assertEqual(1000, grandparent_resource._meta.batch_size)
+        self.assertEqual(1000, parent_resource._meta.batch_size)
+        self.assertEqual(1000, child_resource._meta.batch_size)
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
@@ -114,44 +114,6 @@ def test_default_after_import(self):
             )
         )
 
-    # Issue 140 Attributes aren't inherited by subclasses
-    def test_inheritance(self):
-        class A(MyResource):
-            inherited = fields.Field()
-
-            class Meta:
-                import_id_fields = ("email",)
-
-        class B(A):
-            local = fields.Field()
-
-            class Meta:
-                export_order = ("email", "extra")
-
-        resource = B()
-        self.assertIn("name", resource.fields)
-        self.assertIn("inherited", resource.fields)
-        self.assertIn("local", resource.fields)
-        self.assertEqual(
-            resource.get_export_headers(),
-            ["email", "extra", "name", "inherited", "local"],
-        )
-        self.assertEqual(resource._meta.import_id_fields, ("email",))
-
-    def test_inheritance_with_custom_attributes(self):
-        class A(MyResource):
-            inherited = fields.Field()
-
-            class Meta:
-                import_id_fields = ("email",)
-                custom_attribute = True
-
-        class B(A):
-            local = fields.Field()
-
-        resource = B()
-        self.assertEqual(resource._meta.custom_attribute, True)
-
     def test_get_use_transactions_defined_in_resource(self):
         class A(MyResource):
             class Meta: