Repository: paulocheque/django-dynamic-fixture

Issue:
Proxy models ignore lessons of their base models
In the [Django docs](https://docs.djangoproject.com/en/4.2/topics/db/models/#proxy-models) it sounds like proxy models and regular models are basically equivalent when it comes to database access. The proxy models just add extra methods or managers that the regular models don't have.

Based on this I was also expecting that DDF reuses the lessons for concrete models for any proxy models they might have. But this doesn't seem to be the case 😞 

`G()`etting a proxy model works just fine, but DDF ignores any lessons that I have taught for the concrete base model.

```python
### models.py
from django.db import models


class Thing(models.Model):
    value = models.IntegerField()


class ProxyThing(Thing):
    class Meta:
        proxy = True


### tests.py
from django.test import TestCase
from django_dynamic_fixture import G, teach

from demo.models import ProxyThing, Thing


teach(Thing, value=1234)


class ProxyThingTest(TestCase):
    def test_stuff(self):
        self.assertEqual(1234, G(ProxyThing).value)
```

When I run the tests using `python manage.py test`, the test fails:
```
Found 1 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_stuff (demo.tests.ThingTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\demo\proxy-lessons\tests.py", line 13, in test_stuff
    self.assertEqual(1234, proxy.value)
AssertionError: 1234 != 1

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)
Destroying test database for alias 'default'...
```

I currently work aroung this by specifically copying the lessons for proxy models from their concrete base models. But it would be nicer if DDF supported this out of the box 😸. Maybe if `DDFLibrary.get_configuration()` and `DDFLibrary.add_configuration()` checked for proxy models themselves?
```python
from django.apps import apps
from django_dynamic_fixture import DDFLibrary


def _copy_lessons_for_proxy_models():
    library = DDFLibrary.get_instance()

    for model in apps.get_models():
        concrete_model = model._meta.concrete_model
        if not model._meta.proxy or not concrete_model or concrete_model == model:
            continue

        config = library.get_configuration(concrete_model)
        if config:
            library.add_configuration(model, config)
```



Comments:

Patch:
diff --git a/django_dynamic_fixture/ddf.py b/django_dynamic_fixture/ddf.py
index 1e7f345..dd2d038 100644
--- a/django_dynamic_fixture/ddf.py
+++ b/django_dynamic_fixture/ddf.py
@@ -239,6 +239,7 @@ class DDFLibrary:
         import warnings
         if name in [None, True]:
             name = self.DEFAULT_KEY
+        model_class = self._get_concrete_model(model_class)
         if model_class in self.configs and name in self.configs[model_class]:
             if not os.getenv('DDF_SHELL_MODE'):
                 msg = "Override a lesson is an anti-pattern and will turn your test suite very hard to understand."
@@ -250,12 +251,19 @@ class DDFLibrary:
     def get_configuration(self, model_class, name=None):
         if name is None:
             name = self.DEFAULT_KEY
+        model_class = self._get_concrete_model(model_class)
         # copy is important because this dict will be updated every time in the algorithm.
         config = self.configs.get(model_class, {})
         if name != self.DEFAULT_KEY and name not in config.keys():
             raise InvalidConfigurationError('There is no lesson for model {} with the name "{}"'.format(get_unique_model_name(model_class), name))
         return config.get(name, {}).copy() # default configuration never raises an error
 
+    def _get_concrete_model(self, model_class):
+        if hasattr(model_class, '_meta') and model_class._meta.proxy:
+            return model_class._meta.concrete_model or model_class
+        else:
+            return model_class
+
     def clear(self):
         '''Remove all lessons of the library. Util for the DDF tests.'''
         self.configs = {}


Test patch:
diff --git a/django_dynamic_fixture/models_test.py b/django_dynamic_fixture/models_test.py
index 17d1dde..b36b618 100644
--- a/django_dynamic_fixture/models_test.py
+++ b/django_dynamic_fixture/models_test.py
@@ -352,6 +352,13 @@ class ModelForLibrary(models.Model):
         app_label = 'django_dynamic_fixture'
 
 
+class ProxyModelForLibrary(ModelForLibrary):
+    class Meta:
+        proxy = True
+        verbose_name = 'Proxy Library'
+        app_label = 'django_dynamic_fixture'
+
+
 class ModelWithUniqueCharField(models.Model):
     text_unique = models.CharField(max_length=20, unique=True)
 
diff --git a/django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py b/django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py
index d15639b..95d7961 100644
--- a/django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py
+++ b/django_dynamic_fixture/tests/test_ddf_teaching_and_lessons.py
@@ -65,6 +65,24 @@ class TeachAndLessonsTest(DDFTestCase):
         assert instance.integer == 1000
         assert instance.foreignkey.integer == 1001
 
+    def test_it_uses_lessons_for_base_model_when_creating_a_proxy_model(self):
+        self.ddf.teach(ModelForLibrary, integer=123)
+        instance = self.ddf.get(ProxyModelForLibrary)
+        assert instance.__class__ is ProxyModelForLibrary
+        assert instance.integer == 123
+
+    def test_it_uses_lessons_for_proxy_models_when_creating_the_base_model(self):
+        self.ddf.teach(ProxyModelForLibrary, integer=456)
+        instance = self.ddf.get(ModelForLibrary)
+        assert instance.__class__ is ModelForLibrary
+        assert instance.integer == 456
+
+    def test_it_uses_lessons_for_proxy_models_when_creating_the_proxy_model(self):
+        self.ddf.teach(ProxyModelForLibrary, integer=789)
+        instance = self.ddf.get(ProxyModelForLibrary)
+        assert instance.__class__ is ProxyModelForLibrary
+        assert instance.integer == 789
+
     # Not implemented yet
     # def test_teaching_must_store_ddf_configs_too(self):
     #     self.ddf.teach(ModelForLibrary, fill_nullable_fields=False)