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

Note: A user created with 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.

Next up: Permissions and groups control what a user is allowed to do once a request reaches your view — CSRF & Security covers protecting the request itself from being forged in the first place.