[Django]-How to bind multiple reusable Django apps together?

18👍

✅

Introduction talk in the bottom of the answer (more straight to the answer). I will assume that you have one app for text handling called Text and one app for picture handling called Pictures and a third app for blogging called Blog.

Big picture

You will need to study the manual about the template language for python programmers. The idea is that each thing is in its own app and that you have a third app that connects everything. The apps should then supply its models and views as you like them (just remember to keep you focus on what the app should do) and also supply a set of templatetags.

How to make inclusion tags

Make inclusion tags and it is really easy! It will remind you of writing normal views.

Create a directory templatetags in you app folder. Also create a __init__.py file in this templatetags (so the directory becomes a python package).

Then create a python file. The name is important, you will use this in {% load xyz %} in the templates that will use your app. For instance if call the file picturestags.py, you will call
{% load picturestags %} in all templates that will use it.

First in the file add some politics you need not to think much about, just include this before anything else:

from django.template import Library
register = Library()

Then add the tags by defining functions with the same name as your tag. I will call it display_picture in the example and it will take one argument url. The function should create a dictionary that you will use in a template. My example will just display the picture the url is pointing to.

@register.inclusion_tag('pictures/display_picture.html')
def display_picture(url):
    return {'picture': url}

Create the path templates/pictures in your app and create the file display_picture.html inside containing:

<img src="{{ picture }}" />

As you probably understand, the @register makes this a tag, what is inside the dictionary display_picture returns are what you can use in display_picture.html. Very much like your normal view functions.

In the end you will end up with these files:

pictures/
    __init__.py
    models.py
    views.py
    tests.py
    templates/
        pictures/
            display_picture.html
    templatetags/
        picturetags.py

That is all you need to add to your Picture app. To use this in your Blog app, you need to add Pictures to your INSTALLED_APPS. Then in the templates, where you need to use your own newly home baked tag first load it: {% load picturestags %} then just add the tag {% display_picture https://www.google.com/intl/sv_ALL/images/logos/images_logo_lg.gif %} like this:

{% load picturestags %}
<html>
    <body>
        {% display_picture https://www.google.com/intl/sv_ALL/images/logos/images_logo_lg.gif %}
    </body>
</html>

Results

This is just a small example but you can see that it is very easy to expand this. Your blog could connect the Text and Pictures app by importing their models and foreign key them. There is you connection Text and Pictures for a certain blog post. Your blog_post.html-template could look like (simplified):

{% load picturestags %}
{% load texttags %}
<html>
    <body>
        <h1>{{ subject }}</h1>
        <div class="article">{% article_to_html articleid %}</div>
        <div class="article_picture">{% display_picture %}</div>
    </body>
</html>

Notice that only the Blog has dependencies and it is dependencies it should have (no blog without text and pictures
but pictures can live without text). The look and placement should be and can be controlled by CSS and DIV/SPAN-tags. In this way you can take your Picture app and give it to someone who has no idea about Text app and use it, displaying pictures in different ways probably without ever touching your code!

Inclusion tags is the only thing I know of since I just learned this yesterday. I think it is a convenience provided by Django to make life simple. On the documentation page there are a whole lot more (including how to make “real” tags the hard way without “shortcuts”). So if you find this method to limited, read the documentation
it has lots of examples. It also discusses how to make filters, simple_tags, thread considerations and other advanced stuff.

Introduction

I had this exactly this problem as you and I also wanted something different than the answers I read (I don’t say the answers was bad, they helped me learn a lot and gave me insights, but I wanted this I am writing now). I managed to figure something out that is not very obvious, thanks to your question and definitely thanks to Stack Overflow so this is my contribution back even to a half year old question that is probably abandoned (might help a googler or two)!

I also got a lot of inspiration from Google Tech Talk Reusable Apps. In the end (43 minutes) he mentions some good examples like django-tagging which is what he says a model for how to write reusable apps. That gave me the idea for all this, because that is the way django-tagging solves this very problem we had/have.

Now after I written all this (took an hour), I feel for the first time that I might contribute instead of just google and follow what others are doing or complaining that others are not writing how I need to do things. For the first time I am taking my responsibility of writing my view so others can google this (just had to write this paragraph 🙂 because it feels really great, even if it might be shredded to pieces or ignored and forgotten).

đŸ‘€Aquaplanet

6👍

Think of it in the same way that you would use any 3rd-party app in your project. “Re-usable” doesn’t mean “without dependencies”. On the contrary, you’d be hard-pressed to find an app that doesn’t have at least one dependency, even if it’s just dependent on Django or core Python libraries. (While core Python libraries are usually thought of as “safe” dependencies, i.e. everyone will have it, things do sometimes change between versions of Python, so you’re still locking your app into a specific point in time).

The goal of re-usuable is the same as that of DRY: you don’t want to write the same code over and over again. As a result, it makes sense to break out functionality like a picture app, because you can then use it over and over again in other apps and projects, but your picture app will have dependencies and other packages will depend on it, as long as there are no circular dependencies, you’re good (a circular dependency would mean that you haven’t actually separated the functionality).

đŸ‘€Chris Pratt

5👍

This is a good question, and something I find quite difficult to manage also. But – do you imagine these applications being released publicly, or are you only using them yourself? If you’re not releasing, I wouldn’t worry too much about it.

The other thing is, dependencies are fine to have. The pictures app in your example sounds like a good candidate to be a ‘reusable’ app. It’s simple, does one thing, and can be used by other apps.

A blog app on the other hand usually needs to consume other apps like a picture app or a tagging app. I find this dependency fine to have. You could try to abstract it a little, by simply linking to a media resource that was put there by your picture app.

It’s all just a little bit of common sense. Can you make your apps slim? If yes, then try to create them so they can be reused. But don’t be afraid to take dependencies when they make sense. Also, try to allow extension points so you can potentially swap out dependencies for other ones. A direct foreign key isn’t going to help here, but perhaps something like signals or Restful APIs can.

đŸ‘€Josh Smeaton

4👍

I am a Django newbie and I faced the same problem. It is shameful how there’s so much buzz about reusable apps in the Django community, but not a single authoritative reference on how to connect them
in a project without hard coded dependencies.

I have been working on a new Django project and I wanted to set up my models to avoid hard coding as much as possible. This is the pattern that I used.

Layout

project_root/
    core/
        models/
            mixinmodels1.py
            mixinmodels2.py
            ...
        utils.py
        ...
    app1/
        models/
            __init__.py
            base.py
            basemixins.py
            mixins.py
            concrete.py
        /signals
            __init__.py
            handlers.py
        utils.py
        ...
    app2/
        ...
    ...

App models

base.py : This module implements only the ‘reason for existence’ of that app in abstract classes.
Rules are:

  • This module usually only imports from the core app. It usually does not import anything from other apps in the same project.

  • An exception to the above rule is when the ‘reason for existence’ assumes the existence of another app. For example, a group app assumes that there is an user app somewhere. In this case, the way to link them is:

    # project_root/settings.py
    
    AUTH_USER_MODEL = 'app_label.UserModel'
    
    # project_root/groups/models/base.py
    
    from django.conf import settings
    

    and then using settings.AUTH_USER_MODEL to refer to the user model

  • Use this pattern for all apps, not just the user app. For example, you should also do

    # project_root/settings.py
    
    GROUP_MODEL = 'app_label.GroupModel'
    
  • If using the above pattern, only assume the functionality provided by base.py of the other app
    to which you are linking. Do not assume the functionality of elaborate concrete classes (I will discuss where to put concrete classes shortly)

  • Of course, importing from django, third party apps and python packages is allowed.

  • Make sure that the assumptions that you make in base.py of any app is rock solid and would not
    change much in the future. A good example is django-registration by James Bennett. Its an old app, but its appeal did not wane because it made rock solid assumptions. Since good reusable apps do one thing well, it is not difficult to find that set of assumptions.

basemixins.py: This module should implements plugs to the concrete models of that app. A ‘plug’ to a model M is any model that contains a foreign key to the model M. For example:

# project_root/groups/models/basemixins.py

from django.conf import settings
from django.db import models

class BaseOwnedByGroup(models.Model):
    """
    This is a plug to the group model. Use this
    to implement ownership like relations with 
    the group model
    """
    owner = models.ForeignKey(settings.GROUP_MODEL,
        related_name = '%(app_label)s_%(class)s_owner',
        verbose_name = 'owner')

    # functionality and manager definitions go here.

    class Meta:
        abstract = True
        app_label = 'groups'

BaseOwnedByGroup is a ‘plug’ to the group model. The rules here are the same as ‘base.py`

  • While defining ‘plugs’ in basemixins.py, only assume functionality provided by base.py.
  • Import only from core, django, third party apps and python packages.

mixins.py : This module should be used for two purposes

  • To define elaborate ‘plugs’, which assumes the functionality of the elaborate concrete classes but not the relationships with other apps. The elaborate plugs should ideally inherit one of the ‘base plugs’ defined in basemixins.py.

  • To define mixin models (that are not ‘plugs’) which can be used by the concrete classes of that app.

concrete.py : This module should be used to define (you guessed it) concrete classes of that apps and to set up relationships with other apps. In short, this module assumes your project and all the
functionality you want to provide in it.

Relationships to other apps should be set up as follows:

  • To establish a one to one or many to one relationship with model M of app another_app, do the following:

    # project_root/another_app/utils.py
    
    def plug_to_M_factory(version_label):
        """
        This is a factory method which returns
        the plug to model M specified by 
        version_label
        """
        if version_label == 'first_version':
            from another_app.models.basemixins import BasePlugToM
            return BasePlugToM
        if version_label == 'second_version':
            from another_app.models.mixins import PlugToM
            return PlugToM
        ...
    
    # project_root/groups/models/concrete.py
    
    from groups.models.base import BaseGroup
    from another_app.utils import plug_to_M_factory
    
    PlugToMClass = plug_to_M_factory(version_label = 'second_version')
    
    class ConcreteGroup(BaseGroup, PlugToMClass):
        # define your concrete model
    
        class Meta:
            app_label = 'groups'
    
  • To establish a many to many relationship, the recommended way is to use a through model. Inherit the correct plug in the through model in the exact same way (as we did in the ConcreteGroup model)

signals : While setting up relationships, often we have to perform operations on a model M of app app1, when model N of app app2 changes. You can use signals to handle that. Your handlers can assume the functionality of the concrete sender, but often they don’t need to. Assumption of the base version of the sender in base.py is enough. This is why it is a good idea to always use settings.ModelName to refer to a concrete model. You can extract out the model class from the settings.ModelName string by either using ContentType or using a project wide get_model_for_settings function as follows:

# project_root/project/utils.py

from django.db.models import get_model
from django.core.exceptions import ImproperlyConfigured

def get_model_from_settings(model_string):
    """
    Takes a string of the form 'app_label.model' as input, returns the 
    appropriate model class if it can find it.
    """
    try:
        app_label, model_name = model_string.split('.')
    except ValueError:
        raise ImproperlyConfigured("function argument must be of the " 
            "form 'app_label.model_name', got '%s'" % model_string)

    model = get_model(app_label, model_name)

    if model is None:
        raise ImproperlyConfigured("function argument refers to model "
            "'%s' that has not been installed" % model_string)

    return model

core : The core app is a special app which stores project wide mixin functions.

  • These mixins should not assume anything about any other app. Only exception to this rule is mixins that rely on base functionality of settings.AUTH_USER_MODEL. This is because you can safely assume that most projects will have an user model.

  • Of course imports from django, third party and python packages are still allowed

  • Remember that all base.py and basemixins.py modules are allowed to import from core.

Finally for everything to work as intended, import your models in models/__init__.py of every app.

The advantages that I find from following this scheme is:

  • The models are reusable. Anyone can use the abstract base models and mixins to design their own concrete model. base.py, basemixins.py and related factory methods can be bundled together with a bare bones concrete model and shipped in a reusable app.

  • The apps are extendable. All mixins are versioned and there is a clear inheritance scheme.

  • The apps are loosely coupled. External mixins are accessed via factory methods and external models are referred to using django.conf.settings.

  • The apps are self contained. Any changes in an app will most likely break that app and that app only. Other apps will most likely remain unscathed. Even if external apps break, the place where this
    could happen is clearly marked.

I have been using this scheme to reduce coupling between my apps. I am not an experienced Django programmer, and I have a lot to learn, so any feedback is appreciated.

đŸ‘€user4013889

Leave a comment