Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the accepted practice for Django inter-app imports

Tags:

python

django

It's quite easy to run into import problems with Django and apps which interact with each other. My question is simple:

What is the accepted process for minimizing circular imports or has anyone come up with an accepted coding standard to reduce these which they are willing to share.?

I'm looking for good principles that can be standardized on.

Models

class Program(models.Model):
    group = models.ForeignKey(Group, related_name="%(app_label)s_%(class)s_related") 

vs

class Program(models.Model):
    group = models.ForeignKey('auth.Group', related_name="%(app_label)s_%(class)s_related") 

Views:

class ProgramDetailView(DetailView):
    """Detail view of the EEP Program"""

    def get_queryset(self):
        """Narrow this based on your company"""
        from apps.company.models import Company
        company = Company.objects.get(name="foo")
        return Program.objects.filter(company = company)

vs (which tends to cause the issues..

from apps.company.models import Company
class ProgramDetailView(DetailView):
    """Detail view of the EEP Program"""

    def get_queryset(self):
        """Narrow this based on your company"""
        company = Company.objects.get(name="foo")
        return Program.objects.filter(company = company)

The problem with this is that you tend to do a lot of imports all over the place..

like image 615
rh0dium Avatar asked Sep 03 '12 20:09

rh0dium


People also ask

How import all models in Django?

To do this, create a directory called /«project»/«app_name»/models , inside it put __init__.py (to declare it as a module) and then create your files inside there. You then need to import your file contents into the module in __init__.py . You should read about Python modules to understand this.

What is AppConfig Django?

The AppConfig class used to configure the application has a path class attribute, which is the absolute directory path Django will use as the single base path for the application.


2 Answers

Over the years I standardized on some patterns, based on my observations on how I develop web apps.

I don't know what are your standards about modularity and code re-use but the following simple rules/patterns have helped me greatly with some fairly large projects.

I have noticed that many of my models share some common attributes. For example I prefer to use UUID's instead of simple auto increment integers, as primary keys.

So I have this abstract model.

class UUIDModel(models.Model):
    id = UUIDField(primary_key=True, auto=True)  # There are many implementation of this on the web. Choose your favorite.

    class Meta:
        abstract = True

Many of my models need the the concept of activation. So I have another abstract model, similar to this:

class ActivatedModel(Model):
    is_active = models.BooleanField(default=False)

    def activate(self, save=True):
        if self.is_active:
            raise Exception('Already activated')
        self.is_active = True
        if save:
            self.save()

    class Meta:
        abstract = True

There are many other abstract models I use for tracking creation time and modification, or if something is finalized and can't be further modified, etc.

All these abstract models live in the core app. This is how I call it. Apart from the core app, I have a tasks app. The tasks app offers abstract models that augments any interfacing I have to do with celery which I use a lot.

The tasks app can import models from the core app, but not the other way around.

I have also an mms app, which handles Multimedia creation and transformations(thumbnails, etc). The mms can import models from the previous apps. So the import relationship we have right now is this: core -> tasks -> mms.

Every other app I create is specific to the current project I am working on and builds upon the previous apps. So basically I try to have "one way imports" if you can call it that.

And I end up with models that look similar to this:

# models.py of an app called  "articles"

from core.models import UUIDModel, ActivatedModel
from tasks.models import ManagedTasksModel

class Article(UUIDModel, ActivatedModel, ManagedTasksModel):
    title = models.CharField()
    # blah...

If an app grows too big I "micromanage" the app by breaking the models.py module into smaller modules following the above mentioned rules. I have found out that this covers most of my needs.

I can't comment on class based views, because honestly I don't like them and it almost always make me write more code instead of less. To each his own. I prefer to use helper utility functions and make good use of things like context processors, inside my view functions.

I hope my answer was within the context of your question.

EDIT: I just noticed your usage of related_name which I think misses the point of that option. See the following example:

class Message(models.Model):
    sender   = models.ForeignKey(User, related_name='messages_sent')
    receiver = models.ForeignKey(User, related_name='messages_received')
    body     = models.Textfield()

With the above model we can do this, which is very readable:

u1 = User.objects.get(...)
received = u1.messages_received.all()

... and it depicts the functional purpose of this relationship. So related_name is not only used for having related names which are unique.

like image 104
rantanplan Avatar answered Oct 15 '22 21:10

rantanplan


Regarding View Code

I don't think there is a high probability that the view code from app A would import the view code from app B in such a way that a circular import is caused.
If you happen to be using view code from another app B in app A's views, then that code should belong to a views.py file, but to an utils.py (or similar) file.

It is even more unlikely that the model code from app A would import the view code from app B, so it's totally OK to use a top-level import for app A's model code in app B's view code.

Regarding model code

As you mentioned however, having ForeignKey relationships between models from different apps can lead to circular imports.
Using the string notation for your ForeignKey addresses that challenge, and as the Django docs put it:

If you need to create a relationship on a model that has not yet been defined, you can use the name of the model, rather than the model object itself.

This also works for ManyToMany and OneToOne relationships.

Regarding the existence of an accepted standard

I don't think there really exists one, but I figured that maybe looking at django.contrib apps would be relevant. They use model imports.

You might want to check out:

  • django.contrib.admin.models
  • django.contrib.auth.models
  • django.contrib.comments.models

Popular apps also use the same functionnality:

  • tastypie.models
  • registration.models

You might also want to have a look at the source of social_auth for a variation on this:

  • social_auth.db.django_models.py

I think that a general rule of thumb would be to use model imports when you can, but be ready to use string notation if needed.

like image 42
Thomas Orozco Avatar answered Oct 15 '22 21:10

Thomas Orozco