[Django]-Django: how to fully decouple apps when it seems they are coupled?

5đź‘Ť

âś…

Question 0

A “module” in the Python context is simply a file that contains definitions and statements. So “related modules under a package” really just means “split your code into separate files based on what the code is doing”.

Describing it as “an app made of other apps” is to start confusing Django’s concept of an app with Python’s concept of a module (which, as stated above is just a file that houses some code).

Question 1

What should I do when apps share models?

You should still try and stick to the “apps do one thing and do it well” maxim. In this case separate profile and registration apps seems like a good idea – because they have quite different functions. A registration app is going to contain the logic for allowing users to register on your site. A profile app is all about what information you will store about a user.

There is nothing wrong with these two apps having a relationship to each other – see below.

Question 2

Let’s say that my main app (or some other app that is used by the main app) utilizes some aspect of the user model (e.g. recently active members if it was a chat site). Clearly my main app gets this information from the user model. Does my main app now get bundled under the user-app?

No. It should still be a separate app, with links to the other app.

The user model is actually a good example. Django allows you to specify a custom user model that lets you store whatever additional data you want about a user.

Now, there are loads of third party apps out there that do things like registration, authentication, etc for users. They are designed to work with any user model, not just Django’s default one. The way they do that is to use get_user_model() wherever they need to reference the User model, instead of directly importing django.contrib.auth.models.User.

This means that you can use those third party apps with whatever user model you have defined for your own project.

Django’s get_user_model() utility is there to serve a very common use case. However the same principle can be extended to your own apps. If there is a dependency between your apps that you think should be swappable, then you can provide a way to swap it out – e.g., a setting/configuration that allows any other project using your app to specify an alternative.

There are hundreds of examples of this kind of configurability in the Django ecosystem. For example, Django itself ships with its own django.contrib.auth authentication app. However if you want to implement your own authentication logic, you don’t have to reimplement the entire auth app again yourself (that would be a huge pain). Instead you specify an an authentication backend that it’s auth app will use to authenticate. The auth app is designed to allow any project to swap out a core piece of its functionality with minimal effort.

So in your main app, you might define a setting that controls which profile model to use. This means that if someone else wants to use a different profile model, they simply change this setting and they’re all set. They are no longer tied to your profile app.

For example – let’s say you have a main app that has a view that displays some user data, but also provides a link to a registration view that is provided by a different app. You want anyone else to be able to use that app regardless of what registration app they are using. So you can make this view resuable like so:

In main/views.py:

from django.contrib.auth import get_user_model
from django.conf import settings
from django.urls import reverse

class UserDetailView(DetailView):

    # First of all, we're using get_user_model so that a project
    # can specify whatever user model it wants, and still use this
    # view.
    model = get_user_model()

    def get_context_data(self, *args, *kwargs):
        ctx = super().get_context_data(*args, **kwargs)
        # We want to add a link to a registration view into this template context.
        # But we want this to be configurable.
        # Your REGISTRATION_URL would be something like 'profile:registration'
        ctx['registration_link'] = reverse(settings.REGISTRATION_URL)
        return ctx

Question 3

The main app serves as the landing page/ overview in this case. I want all my other apps to use / inherit the static and template files of the main app. Where do I store all the static files and templates?

You should store the templates in each respective app. If your main app is providing the base templates, then those should reside in the main app.

If your profile app is then providing a registration view, then the template for that should live in the profile app. There is nothing wrong with it extending the base template from the main app – this can easily be overridden by a project that wants to.

It’s fine to make assumptions about how two apps are related to each other – as long as you’re careful to allow overriding of those assumptions.

👤solarissmoke

2đź‘Ť

I have to admit your question is not a technical one but rather a conceptual and dogmatic one.
No answer is absolute and universally valid and every detail about how you project is structured and should behave can change the perspective.
As you wrote, each Django app does one thing and it does it well.

I would extend that to the point that each app should contain no more than one Model and at most, it’s closets dependents.

Ex: the Product with it’s Category, Color, Image

"What Changes together, stay together"

You will have plenty of logic to cover inside that app with only these ones.

Try to look at Django framework as a tool to create your project..this is the final goal…but if you want also to create reusable apps try to create them as independent as possible, or at least dependent to some Django features:

ex: a reusable app and totally independent would be an app that only requires User Model, Sessions, Groups included in Django. You get the idea of dependent but still autonomous app.

An app is part of a project after all…either here or in other part after you build it. Look at it as if it would be a simple function…can run alone or can depend on other functions result…at what point you keep everything inside one function and when you decide to split them in 2 separate ones.
So:

  • Question 0:

An app is the smallest piece that can run by it’s own…having models, views, templates, urls, static files.

It can depend also on other apps…so answer is YES

  • Question 1:

Always keep things separate by functionality…
User Auth is dealing with user creation and their authentication

User Profile is dealing with personal data of the User

  • Question 2:

Nothing gets bundled. Everything stays at the same level as 2 different but dependents apps

  • Question 3:

You can do as you wish.

You can do static as a central place and templates specific for each app or everything central.
No right answer here but only what scales well for your project.

👤Catalin

1đź‘Ť

This is a great question and it covers all the questions associated to structuring the project I asked myself when i started working with Django.

Question 0:

Yes, in that case, a module is an app which consists of serveral apps (ellington.events, ellington.podcasts).

Question 1, Question 2, Question 3:

Django is a general purpose, full stack web framework. Since it is general purpose, a lot of it depends on your particular use case. You need not have an entire Django project follow a particular structure (if you want to achieve code reuse AND functional decoupling AND relational decoupling).

With that said, if you can prioritize what you want to achieve, you can go with one pattern over the other.

Let’s take the example of Blog.

Code Reuse:

For achieving maximum code reuse, you have to identify what parts of your project is worthy of reuse. Once you have done that, you can set your project structure accordingly.

Project Structure:

BlogProject

-CommonApps

–AbstractUser(abstract class (just like it’s java counterpart) )

–AbstractActivity

–AbstractComment

–AbstractArticle

-ProjectApps

–BlogUser (extends AbstractUser)

–BlogActivity (extends AbstractActivity)

–BlogComment (extends AbstractComment)

–BlogArticle (extends AbstractArticle)

The functionalities that can be shared across multiple projects should be implemented in abstract apps, and the ones specific to project can be implemented in Project apps.

Relational Decoupling:

You can create apps to represent the relations between two other apps, and implement all the functionality involving two different apps in that relation.

Project Structure:

BlogProject

-User

-UserActivityRelation

-Activity

-Article

-ArticleCommentRelation

-Comment

-UserCommentRelation

-and so on

Functional Decoupling:

This is the most common practice – create apps for particular functionality.

Project Structure:

BlogProject

-Article

-Activity

-User

-Comment

The point I am trying to make here is that the choice is yours. In more complex projects, it won’t be so white and black.

You, depending on what an “app” means to you, and what you want it to do in a particular project and other projects, can decide on a particular structure.

Django keeps it abstract to give you the ability to do that.

I always go for an app setup that makes sense to that particular project. In a project not all apps are reusable. And having flexible/reusable apps does not make sense in all the cases.

As a general rule of thumb, Django devs say an App should be something whose functionality can be described with one sentence. But Django is designed so that you can bend the rules for your projects if you have to.

DISCLAIMER: Functional decoupling and relational decoupling aren’t textbook terms. I just used them to describe what I meant here.

Leave a comment