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.)
Add the fields to your
models.pyfile.
Supposing you wish to add thecreated_atfield 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")
Create your migration files.
1
python manage.py makemigrations
You will notice, though, that the migration file is created in the
site-packagesdirectory, 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.Copy the migrations of Django’s built-in auth app to a different directory. The output from the
makemigrationscommand 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.pyIn
settings.py, add theMIGRATION_MODULESdictionary, a setting which specifies on a per-app basis where the migration files are found.1 2 3
MIGRATION_MODULES = { "auth": "auth_migrations" }
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.