Home Customising Django's Group Model
Post
Cancel

Customising Django's Group Model

If you have used Django to build a web application before, then you likely have interacted with its robust permissions system.

While first and foremost used in the Django admin site, the built-in permissions system allows a developer to limit access to certain parts of an application only to users with the necessary permissions.

By using the permissions system, one gets access to such handy methods as user.groups.add(group_obj, other_group_obj) or group.permissions.set([permissions]), which can really speed up development by abstracting functionality that would be tedious to write and maintain.

There’s just one problem: the Group model that powers one aspect of the permissions system is extremely basic, containing two fields: id (not even included in the model definition) and name.
The model also contains a ManyToManyField that tracks the permissions assigned to a role, but this creates a “through”, or junction, table.

Let’s suppose you want to use the Group model but wish to add a few more fields to match your needs. How would you go about that?

Hat tip to this Stack Overflow answer, which provided the foundation for this answer.

The obvious answer would be to add fields to the model class. But since Group is built in, that’s not an option. So let’s strike this off the list.

The next option would be to subclass the Group model and add the fields to your new class.
Doing so would create an entirely new table in the database and the fields would not reflect in the Group model. Again, let’s strike this option off the list.

This brings us to the third option: Inject your fields into the Group model.
(The complete code is available on GitHub.)

  1. Add the fields to your models.py file.
    Supposing you wish to add the created_at field to the model:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
     # example/models.py
    
     from django.contrib.auth.models import Group
     from django.db import models
     from django.utils import timezone
     from django.utils.translation import gettext_lazy as _
    
     if not hasattr(Group, "created_at"):
         created_at_field = models.DateTimeField(
             _("Created At"),
             blank=True,
             null=True,
             editable=False,
             default=timezone.now,
         )
         created_at_field.contribute_to_class(Group, "created_at")
    
  2. Create your migration files.

    1
    
     python manage.py makemigrations
    

    You will notice, though, that the migration file is created in the site-packages directory, i.e., where your Django installation is. This means that it won’t be included in your source control commits. It also wouldn’t be present should you choose to re-install your dependencies. Hardly ideal.
    Let’s correct that.

  3. Copy the migrations of Django’s built-in auth app to a different directory. The output from the makemigrations command above should show you the location of the migration files. Copy all files from that directory to, for example, auth_migrations.
    Your file structure should look something like:

    1
    2
    3
    4
    5
    
     └── application_root/
         └── auth_migrations/
             └── # All contents of django.contrib.auth.migrations excluding __pycache__
         └── # Other app directories
         └── manage.py
    
  4. In settings.py, add the MIGRATION_MODULES dictionary, a setting which specifies on a per-app basis where the migration files are found.

    1
    2
    3
    
     MIGRATION_MODULES = {
         "auth": "auth_migrations"
     }
    
  5. Run your migrations:

    1
    
     python manage.py migrate
    

Having done all of that, you will find that the auth_group table now has the created_at column that you defined.
This means that you can work with the field in queries like you would with any other field. The one downside to this approach is that you’re not provided with any Intellisense or auto-completion options; not that I know of, at the very least.

Should you wish to add other fields in the future, any new migration file(s) will be added to auth_migrations.

And that’s it for this post. Until the next one.

This post is licensed under CC BY-ND 4.0 by the author.