Manage Groups and Generate Group Migrations
Use the Group Management Page to add groups to permissions, rename groups, and remove permissions from groups. VUEDA records each change. Then run makegroupmigrations to write a Group Permission Migration, which applies the same changes in your other environments.
A group's permissions feed each member's Baseline Permission. To audit which groups hold each permission without editing anything, use the Permissions and Workflow Overview, described in Use the Permissions and Workflow Overview.
Before You Begin
- The server runs with Django: DEBUG on. The group management page and the URLs that it saves through exist only in debug mode.
vueda.useris in Django: INSTALLED_APPS.get_defaultsincludes it.- The project includes the
vueda.userURLs underroutes/, as the template app does. The page's script sends its save and remove requests to paths under/routes/. - Your account holds these permissions, directly or through a group:
auth.list_permissionto open the page.auth.create_groupandauth.update_groupto add or rename a group.auth.delete_permissionto remove a permission from a group.
Grant these permissions outside the page, for example in python manage.py shell. You need auth.list_permission before you can open the page, and the page hides the other three permissions.
Group Management UI
Open the Page
- Sign in to the server. In debug mode,
/routes/vueda.user/dev-login/serves a Django login form. Add?next=/routes/vueda.user/permissions/overview/to that URL to land on the page after you sign in. - Open
/routes/vueda.user/permissions/overview/. On a local template app this ishttp://localhost:8000/routes/vueda.user/permissions/overview/.
An anonymous request redirects to the Django: LOGIN_URL setting. A signed-in user without auth.list_permission gets 403.
The page groups permissions by app, then by model. Apps in LOCAL_APPS appear under My App Permissions, and the rest under Other App Permissions. Historical models stay hidden until you select Toggle historical. Each permission shows its codename, its name, and the groups that hold it.
The page hides some permissions. For auth.Group, auth.Permission, contenttypes.ContentType, and the VDQ queue models, it hides the create, update, and delete permissions and shows read and list. For models such as sessions.Session and vueda_user.GroupChange, it hides every permission. The vueda.user.globals module lists each hidden model.
Add a Group to a Permission
- Find the permission and select Add. A name field appears with Save and X buttons.
- Type the group name, then select Save or press Enter.
When no group has that name, VUEDA creates the group, adds the permission, and records an added change. When the group exists, VUEDA adds the permission to it and records an associated change. When the group already holds the permission, the page shows an error and records nothing.
Save changes color while its name field has unsaved edits.
Rename a Group
- Find any permission that the group holds.
- Edit the group name, then select Save.
VUEDA renames the group and keeps its permissions and members. The page shows the new name on every row for that group. VUEDA records a changed change with the old and new names, so the migration can run forward and backward.
Remove a Permission from a Group
- Find the permission.
- Select X next to the group name.
The page removes the permission at once, with no confirmation step. VUEDA records an unassociated change. The group and its members stay, even when the group holds no permissions.
WARNING
The group management page has no action that deletes a group; #423 tracks one. Do not delete a group through the Django admin or a shell. VUEDA records no change for that deletion, so every other environment keeps the group.
Generating Group Migrations
Write the migration:
consolepython manage.py makegroupmigrationsThe command writes the migration into the app of Django: AUTH_USER_MODEL, with a name like
0005_group_permission_migrations_2026_04_21. When there is nothing new to write, it printsNo group changes detected.Fake the migration in your own database, which already has the changes. The command prints a note that tells you to fake it.
consolepython manage.py migrate myapp 0005_group_permission_migrations_2026_04_21 --fakeShip the migration with your code. Other environments apply it with
python manage.py migrate.
To preview the migration name without writing a file, add --dry-run. The command passes --dry-run to Django's makemigrations, and still prints the note to fake the migration.
python manage.py makegroupmigrations --dry-runWhich Changes a Migration Includes
VUEDA stores each recorded change as a GroupChange row. makegroupmigrations skips the rows that an existing group migration in the same app already holds. It writes the rest in the order that VUEDA recorded them.
A change names its group by name and its permission by codename, app label, and model name. It holds no primary keys, because keys differ between databases.
When a group migration runs forward, it adds a GroupChange row for each of its changes that has none. A later makegroupmigrations run in that environment then skips those changes.
Change Types
Each change has one of five types, listed in GroupChangeTypes. Rolling back a migration applies its changes in reverse order, each with the backward effect below.
| Type | Recorded when | Forward | Backward |
|---|---|---|---|
added | You save a name that no group has | Creates the group if it is missing and adds the permission | Removes the permission and keeps the group |
associated | You save the name of an existing group | Adds the permission | Removes the permission |
changed | You rename a group | Renames the group | Renames the group back |
unassociated | You remove a permission from a group | Removes the permission and keeps the group | Adds the permission |
deleted | Earlier releases, when you removed a group's last permission | Removes the permission and keeps the group | Creates the group if it is missing and adds the permission |
No change type deletes a group, in either direction.
Each group migration carries its own copy of the code that applies these changes. A group migration generated by an earlier VUEDA release still deletes a group when it replays a deleted change or rolls back an added change. Review those migrations before you run them in another environment. The updategroupmigrations command, described in Updating Existing Group Migrations, replaces their copy with the current code.
What the Migration File Contains
A group migration is a standard Django migration. makegroupmigrations adds:
changed_data, the list of changes that the migration applies. Howchanged_dataIs Formatted describes its layout.- Copies of
migrate_step,forwards_migrate_groups,backwards_migrate_groups,make_sure_permissions_exist,GroupChangeTypes, and their helpers. The migration runsmake_sure_permissions_existfirst, so every permission it names exists. - A comment near the top that marks the file as a group migration. VUEDA's group migration commands find group migrations by this comment, so keep it.
How changed_data Is Formatted
makegroupmigrations formats changed_data with the Python formatter that your project uses, black or ruff, when one is installed. Projects usually install these as development dependencies. makeworkflowmigrations and updateworkflowmigrations format their changed_data the same way, as How changed_data Is Formatted in the workflow guide describes.
The command uses the first formatter in this list that applies:
- black, when the
pyproject.tomlat your project root has a[tool.black]section, even an empty one. A project can install ruff only to lint, so black configuration comes first. A black configuration in your home directory, such as~/.config/black, does not count. One developer's file therefore cannot choose the formatter for the whole project. - ruff, when it is installed.
- black with its default settings, when it is installed but not configured.
- Python's
pprintwith a narrow width, when neither is installed. Its output uses single quotes and ignores your settings.
If the chosen formatter fails, the command does not switch to another formatter that would ignore your settings. A formatter fails, for example, when your pyproject.toml cannot be read or a setting has the wrong type. The command prints the formatter's error and writes changed_data with pprint. It then asks you to fix the problem and format that migration yourself.
With black or ruff, the output has these properties:
- Your project's settings apply. black reads
[tool.black]from thepyproject.tomlat your project root. ruff reads the configuration that covers the migration file, such as yourpyproject.tomlorruff.toml, even when that configuration excludes migrations. The list follows your line length and quote style. - Every change and every key gets its own line. Each dictionary and list ends in a trailing comma, which keeps the formatter from joining the items onto one line. The command turns the magic trailing comma on even when your configuration turns it off.
- Keys are sorted, as earlier versions of the command wrote them.
The command formats only changed_data this way. It writes the rest of the migration, including the copied functions, in its own layout.
Fill In Missing Change Records
sync_group_changes reads changed_data from the group migrations in your project's apps. It creates a GroupChange row for each change that has none, and prints how many it created. The vueda_user migration 0005_sync_group_changes runs it once. You can run it again at any time:
python manage.py sync_group_changesUpdating Existing Group Migrations
A group migration runs without updates, because it carries its own copy of the code. Run updategroupmigrations when a VUEDA release changes that code and you want existing group migrations to use the new version.
python manage.py updategroupmigrationsAdd --dry-run to list the files that the command would update without writing them.
python manage.py updategroupmigrations --dry-runThe command finds group migrations in every installed app by their marker comment. In each file it:
- Replaces each copied function, and
GroupChangeTypes, with the current version, matched by name. It adds any that the file lacks. - Updates the import lines that it recognizes, and leaves other imports in place.
- Keeps
changed_dataand theMigrationclass. In the class'soperationslist, it renames references to functions that earlier releases named differently.
Code outside the copied functions stays as it is. The command overwrites edits inside a copied function the next time it runs. To add behavior to a group migration, write a separate function and add it to the operations list.
An environment that already applied a migration does not run it again. An update affects only rollbacks and environments that have not applied the migration. Running the command when nothing has changed rewrites the same code.