Groups & Permissions
The previous lesson checked permissions one at a time — taskflow.delete_task,
taskflow.can_archive_project — assigned directly to individual users. That works, but
imagine TaskFlow grows to fifty users, and ten of them are project managers who all need the exact
same set of five or six permissions. Assigning each permission to each of those ten users
individually is tedious and error-prone — miss one assignment and that manager quietly can't do
part of their job. Groups solve exactly this: bundle a set of permissions once, then
assign the whole bundle to as many users as needed.
A Real Scenario for TaskFlow
Picture two kinds of TaskFlow users: regular members, who should only manage tasks inside their own
projects, and project managers, who additionally need to create new projects, delete projects, and
use the custom can_archive_project permission from the previous lesson. Rather than
granting those three permissions to every manager by hand, you create one group —
"Project Managers" — attach the permissions to it once, and then simply add each
manager to that group.
Creating a Group and Assigning Permissions
Groups can be created and configured through the Django admin's UI — a form dedicated to exactly this, covered once the admin itself is introduced in The Django Admin: Basics — or programmatically wherever it's more convenient, such as a one-off setup script or a data migration:
Example: Creating the "Project Managers" Group
# One-time setup, e.g. run from the Django shell
from django.contrib.auth.models import Group, Permission
group = Group.objects.create(name='Project Managers')
can_archive = Permission.objects.get(codename='can_archive_project')
can_delete_project = Permission.objects.get(codename='delete_project')
group.permissions.add(can_archive, can_delete_project)
group.permissions is a many-to-many field, so .add() works exactly like
adding tags to a TaskFlow task — you can pass one permission or several at once, and the group ends
up holding the full combined set.
Adding a User to a Group
A user's groups field is also many-to-many, so putting someone in the "Project Managers"
group is a single .add() call:
user.groups.add(group)
The moment that runs, the user immediately has every permission attached to the group — nothing
else needs to change. Remove them from the group later with user.groups.remove(group)
and those permissions disappear just as cleanly.
Checking Group Membership in a View
Sometimes the check you actually want isn't "does this user have permission X" but "is this user one of our project managers" as a role, independent of any specific permission. Group membership can be checked directly:
Example: Gating a View by Group Membership
def create_project(request):
is_manager = request.user.groups.filter(name='Project Managers').exists()
if not is_manager:
raise PermissionDenied
# ... proceed with project creation
.exists() is the efficient choice here — it asks the database a yes/no question rather
than pulling back a full QuerySet just to check whether it's empty.
has_perm() Already Checks Groups For You
Here's the detail worth remembering from the previous lesson: request.user.has_perm(...)
doesn't only check permissions assigned directly to that user — it automatically checks permissions
granted through any group the user belongs to as well, and returns True
if either source grants it. That means every @permission_required decorator and every
has_perm() call from the previous lesson already works correctly for group-based
permissions with no extra code — you never need separate logic for "direct" versus "via a group."
Superusers Bypass All of This
is_superuser=True — such as the account created by
createsuperuser, covered next — automatically passes every
has_perm() check and every @permission_required decorator, regardless of
what permissions or groups are actually assigned to them. This is why a superuser account appears to
be able to do "everything" in TaskFlow even without a single explicit permission ever being granted
to it — the check is short-circuited entirely for superusers, by design.
As with every code example on this page, these are correct, real Django patterns that don't execute live here — this website's own project doesn't run a Django server or database. Try them against your own TaskFlow project.