5

I have 3 apps products, sales, purchases. each app has a correspondingly named Model class, Product, Sale, and Purchase.

products/models.py
class Product(models.Model):
    Name = models.CharField(max_length=32)

sales/models.py
class Sale(models.Model):
    Product = models.ForeignKey('products.Product', on_delete=models.CASCADE)

purchases/models.py
class Purchase(models.Model):
    Product = models.ForeignKey('products.Product', on_delete=models.CASCADE)

And I decided to make custom managers for the Model classes so that I can keep all the logic in the model files (by overriding objects attr for each class) when I'm writing the methods in the custom manager I imported Sale model In products.models and Product model in sales.models which creates a circular reference, I was able to get away with it by performing the imports in the methods themselves but I remember reading online that circular imports are sign of bad code writing.

So my question is how can I avoid circular imports in this case and have access to the Product Model in sales.models and Sale in products.models.

2 Answers2

8

You can import a model by name to avoid circular imports. When you need to use the model, import it like this:

from django.apps import apps

ModelName = apps.get_model(app_label='app_name', model_name='ModelName')
Josh C
  • 404
  • 3
  • 4
1

Running into circular importing means you are fighting against the separation you've created, or you're not writing appropriately decoupled code. In your case, I think you may be trying too hard to separate these models into separate apps. Surely sales and purchases are part of the same set of functionality and share lots of business logic. Even products could live in the same app, although you might have more of an argument there.

If you're coming from another language where it's customary not to have multiple classes in a single file, you should know that is not a best practice in python. Note in the official Django tutorial, the polls app has both Question and Answer models in the same file.

I think a single app named commerce with all three models in the same models.py file would make sense.

# commerce/models.py

class Product(models.Model):
    name = models.CharField(max_length=32)


class Sale(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)


class Purchase(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
Greg Kaleka
  • 1,942
  • 1
  • 15
  • 32
  • but what if I keep this separation, is there a better way of doing this, like maybe getting the model class through the ForeignKey reference... – Selman Hassen Feb 19 '20 at 19:57
  • Yes, you can use the `get_model` method Josh is suggesting, or you can simply refer to 'products.Product' as you have done. That said, this ignores the fact that "circular imports are a sign of bad code writing." Just fixing the circular import doesn't fix the bad design / organization of your code. – Greg Kaleka Feb 19 '20 at 20:09
  • 1
    I'll look into it, Thanks. sorry I couldn't upvote (rep < 15)... – Selman Hassen Feb 19 '20 at 20:25
  • No problem! I've always thought it was silly that rule also applies to answers on your own question, but :shrug:. Looks like you're over 15 now, though :). Welcome to SO. – Greg Kaleka Feb 19 '20 at 21:07