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

Issue:
Attributes aren't inherited by subclasses
I have a base model and a set of subclasses of the model. As you might expect, when I create the import/export ModelResources for those subclass models there is a lot of duplicated code related to the base model. I'd like to move that code into a base ModelResource and then subclass it. For example:

``` python
class BaseResource(resources.ModelResource):
    parent_field = fields.Field(attribute='parent__base_field', readonly=True)

    class Meta:
        model = BaseModel
        use_transactions = True 
        skip_unchanged = True
        report_skipped = True
        abstract = True

class SubclassResource(BaseResource)

    class Meta:
        model = SubclassModel
```

Unfortunately, when I tried this the `fields.Field` attributes on the parent `BaseResource` aren't inherited on the `SubclassResource`. I think this is because the `DeclarativeMetaclass.__new__()` only copies attributes passed in directly and doesn't loop through `bases` and add them from there. However, I'm not familiar enough with metaclass programming or django-import-export to know the best way to fix it.



Discussion:
I think there is an PR that I think related to this:

https://github.com/bmihelac/django-import-export/pull/123

Specific code:
https://github.com/bmihelac/django-import-export/pull/123/files#diff-c4f0017c20379df2e96867bdcc1a42b7R114

However for that pull request misses tests and have some unrelated commits.


Resolving patch:
diff --git a/import_export/resources.py b/import_export/resources.py
--- a/import_export/resources.py
+++ b/import_export/resources.py
@@ -94,22 +94,26 @@ class ResourceOptions(object):
     skip_unchanged = False
     report_skipped = True
 
-    def __new__(cls, meta=None):
-        overrides = {}
-
-        if meta:
-            for override_name in dir(meta):
-                if not override_name.startswith('_'):
-                    overrides[override_name] = getattr(meta, override_name)
-
-        return object.__new__(type(str('ResourceOptions'), (cls,), overrides))
-
 
 class DeclarativeMetaclass(type):
 
     def __new__(cls, name, bases, attrs):
         declared_fields = []
-
+        meta = ResourceOptions()
+
+        # If this class is subclassing another Resource, add that Resource's fields.
+        # Note that we loop over the bases in *reverse*. This is necessary in
+        # order to preserve the correct order of fields.
+        for base in bases[::-1]:
+            if hasattr(base, 'fields'):
+                declared_fields = list(six.iteritems(base.fields)) + declared_fields
+                # Collect the Meta options
+                options = getattr(base, 'Meta', None)
+                for option in [option for option in dir(options)
+                               if not option.startswith('_')]:
+                    setattr(meta, option, getattr(options, option))
+
+        # Add direct fields
         for field_name, obj in attrs.copy().items():
             if isinstance(obj, Field):
                 field = attrs.pop(field_name)
@@ -120,8 +124,13 @@ def __new__(cls, name, bases, attrs):
         attrs['fields'] = OrderedDict(declared_fields)
         new_class = super(DeclarativeMetaclass, cls).__new__(cls, name,
                 bases, attrs)
-        opts = getattr(new_class, 'Meta', None)
-        new_class._meta = ResourceOptions(opts)
+
+        # Add direct options
+        options = getattr(new_class, 'Meta', None)
+        for option in [option for option in dir(options) 
+                       if not option.startswith('_')]:
+            setattr(meta, option, getattr(options, option))
+        new_class._meta = meta
 
         return new_class
 


Test patch:
diff --git a/tests/core/tests/resources_tests.py b/tests/core/tests/resources_tests.py
--- a/tests/core/tests/resources_tests.py
+++ b/tests/core/tests/resources_tests.py
@@ -60,6 +60,41 @@ def test_get_export_order(self):
         self.assertEqual(self.my_resource.get_export_headers(),
                 ['email', 'name', 'extra'])
 
+    # 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 BookResource(resources.ModelResource):
     published = fields.Field(column_name='published_date')