1

I have three models as follows, models.py:

class Activity(models.Model):
    # ...

    objects = models.Manager()
    activity = ActivityManager()

    kind = models.CharField(max_length=10, choices=KIND_CHOICES)

    # ...

class ActivtyA(models.Model):
    # ...

    activity = models.OneToOneField(
        Activity,
        on_delete=models.CASCADE,
        related_name='A',
    )

    # ...

class ActivtyB(models.Model):
    # ...

    activity = models.OneToOneField(
        Activity,
        on_delete=models.CASCADE,
        related_name='B',
    )

    # ...

and a model manager, managers.py:

class ActivityManager(models.Manager):
    def create_activity(self, validated_data):
        # ...

        data = validated_data.pop(kind)
        act = self.create(**validated_data)
        if act.kind == 'A':
            # Create ActivityA(act, **data)
        elif act.kind == 'B':
            # Create ActivityB(act, **data)

         # ... 
    # ... 

In model manager's create_activity method I wand to create Activty and either ActivityA or ActivtyB based on Activity.kind. If I import these classes in manager then it results in circular import error.
How can I access ActivityA and ActivtyB in manager?

I tried to do this by using signals but couldn't make it.

@receiver(post_save, sender=Activity)
def create_activity_details(sender, instance, using, **kwargs):
    if instance.kind == 'A':
        ActivityA.objects.create(activity=instance, data=????) # Need data to create this object
    elif instance.kind == 'A':
        ActivityB.objects.create(activity=instance, data=????)
djvg
  • 11,722
  • 5
  • 72
  • 103
haccks
  • 104,019
  • 25
  • 176
  • 264
  • What is the exact issue in your `ActivityManager` example? Would that be the circular import mentioned in your answer? – djvg Mar 24 '21 at 16:01
  • @djvg; Yes, it is. I added it to the question. – haccks Mar 24 '21 at 16:03
  • Could you prevent these import issues by defining the manager in your `models.py`? Or do you really want it in a separate `managers.py` module? – djvg Mar 24 '21 at 16:05
  • @djvg; The purpose of this manager is to separate business logic from views and serializers and at the same time avoid making models fat. So, yes it need to be in a separate file. – haccks Mar 24 '21 at 16:06
  • This import trick looks promising: https://stackoverflow.com/a/29744414/ (using `django.db.models.loading.get_model`) – djvg Mar 24 '21 at 16:08
  • 1
    or this seems more up to date: https://stackoverflow.com/a/4881693 – djvg Mar 24 '21 at 16:17
  • @djvg, Interesting! Thanks for the link. I got it now. – haccks Mar 24 '21 at 19:10

1 Answers1

1

So, I went old school and did this to make it work,

data = validated_data.pop(kind)
act = self.create(**validated_data)
if act.kind == 'A':
    from activity.models import ActivityA  # Prevent the circular import
    ActivityA.objects.create(activity=act, **data)
elif act.kind == 'B':
    from activity.models import ActivityB
    ActivityB.objects.create(activity=act, **data)

It works, but doesn't look clean. Any better solution other?


Update:

So, using django.apps.apps.get_model is the answer. Thanks @djvj for pointing me in the right direction.
Doc says that:

apps.get_model(app_label, model_name, require_ready=True)
Returns the Model with the given app_label and model_name. As a shortcut, this method also accepts a single argument in the form app_label.model_name. model_name is case-insensitive.

Example:

from django.apps import apps

class ActivityManager(models.Manager):
def create_activity(self, validated_data):
    # ...

    data = validated_data.pop(kind)
    act = self.create(**validated_data)
    if act.kind == 'A':
        model = apps.get_model(app_label='activity', model_name='ActivityA')
        model.objects.create(activity=act, **data)
    elif act.kind == 'B':
        model = apps.get_model(app_label='activity', model_name='ActivityB')
        model.objects.create(activity=act, **data)

     # ... 
# ... 

apps.get_model(app_label='activity', model_name='ActivityA') can be simply written as

apps.get_model('activity.ActivityA')
haccks
  • 104,019
  • 25
  • 176
  • 264