Tenancy

Part of the user manual. Related: VISIBILITY · PERMISSIONS · NOTIFICATIONS · ROLES · PROJECTS · WORKFLOWS. Tenancy is layer 1 of the five visibility layers.

REBIM is multi-tenant from the ground up. Every record in the system — documents, workflow events, contacts, transmittals, configuration objects like workflow definitions and document statuses — carries a tenancy. Every user is assigned one or more tenancies. The system only shows a user records whose tenancy overlaps with theirs.

This page covers what tenancy is, the convention for using it well in practice, and the symptoms when it's misconfigured.


TL;DR

  • Tenancy is the first visibility filter. Other filters (project membership, roles, workflow rules) layer on top.
  • The codebase treats all tenants identically. The convention is to run two kinds of tenants:
  • A config tenant every user gets, holding workflow definitions, statuses, disciplines, etc.
  • Data tenants that gate visibility of actual records.
  • A user with no config tenant will get emails about workflows they can't see in their table. This is the classic misconfiguration symptom — fix by assigning the config tenant to that user.
  • "No tenancy" is itself a tenancy: only users with no tenants will see records that have no tenants.

What tenancy actually is

A tenancy (a SystemTenant in the data model) is a label. Records carry zero, one, or many tenant labels. Users carry zero, one, or many tenant labels. The visibility rule is:

A user can see a record only if at least one of the record's tenants is also one of the user's tenants.

(Or both have no tenants — see "No-tenancy is itself a tenancy" below.)

That's the whole rule, applied uniformly across almost everything in the system. The complexity comes from what gets a tenancy and how it composes with other filters.

What carries a tenancy

Almost every record. Specifically, anything inheriting from the internal DataModel base class, which is most of the data layer:

  • Records: EDMS documents, document revisions, workflow events, workflow transmittals, scheduled events, contacts, companies, projects, project people (membership rows).
  • Configuration: workflow definitions, workflow transitions, workflow statuses, document statuses, document classifications, document types, document sequences, document stamps, disciplines (project roles), project systems, project levels, project spaces, distribution groups.
  • Data sources: the per-project bucket that gates which documents belong to which project (see "Data sources" below).

What doesn't carry a tenancy

  • The Django User model. Users carry tenancies (via UserProfile) but aren't filtered by tenancy themselves — anyone authenticated can be referenced as a user FK regardless of tenant.
  • A handful of pure lookup tables (small fixed enumerations that are deliberately org-wide).
  • The Django admin panel itself — admin access is gated by is_superuser, not tenancy. Admins see across all tenants.

The "config tenant + data tenants" convention

This is the part that bites operators when they get it wrong.

The codebase does not distinguish between "configuration" tenants and "data" tenants. They're all just SystemTenant rows. The distinction is operational convention, applied by how you set up the tenants and what you assign to them.

The shape

  1. One config tenant. Examples from real installs: EUS-O, EUS-OS. Every user gets this one. Every configuration object (workflow, transition, status, discipline, document status, document classification, document type, predefined stamp) is assigned to this tenant.

  2. One or more data tenants. Examples: AZL-O, ACA-O, MGS-O. Each user gets the data tenants relevant to them. Actual records (documents, events, transmittals) are assigned to one data tenant each.

A user's visible scope is then:

  • All configuration (via the config tenant), so the frontend can resolve workflow names, status names, role labels, etc.
  • Only the records whose data tenant matches one of theirs.

Why this convention exists

The frontend renders many things by resolving FKs through tenancy. When the workflow table draws a row, it looks up the workflow name, the status name, the assigned role label, the stamp display — all of which are configuration objects with their own tenancy.

If a user can see a workflow event but not the workflow definition that event references (because the event's data tenant matches but the configuration tenant doesn't), the lookup fails and the row gets dropped from the data the frontend can render.

The fix is to make sure every user has access to the configuration they need to render their data. A shared config tenant is the simplest way to guarantee that.

Why not just enforce it in code?

Because the same machinery is genuinely useful for cases that don't fit the "config + data" split — for example, when an install deliberately uses multiple configuration sets per division. Hardcoding the convention into the model would block those cases.

So: convention, not enforcement. Document it, follow it, train operators to spot the misconfiguration symptoms.


The "I get emails but no table rows" failure mode

This is the canonical misconfiguration. A user reports:

"I get email notifications about workflow events, but when I open the workflow table I don't see them."

The cause is almost always a user with data tenants but no config tenant:

  • The signal handler that sends the email walks ProjectPeople → contact → site_login and matches the user. The notification is sent. ✓
  • The user opens the workflow table. The API returns the event (because its data tenant matches the user). ✓
  • The frontend tries to render the row. It needs the workflow definition + transition statuses + role labels. None of those are visible to the user (no config tenant). ✗
  • The row is silently dropped from the rendered table.

The fix: assign the config tenant to the affected user's UserProfile.tenancy. The next page load should show their data.

Other versions of the same symptom:

  • "I see the event but the workflow name is blank / says 'unknown'." → partial visibility on the workflow definition (some FKs resolve, others don't).
  • "I can see compliance documents but the status column is empty." → same shape, but for document statuses rather than workflow definitions.

The general diagnostic: when something visible has a blank/unrenderable related field, suspect missing config-tenant access first.


How tenancy composes with other filters

Tenancy is the first filter, applied uniformly. Other filters layer on top per record type.

Record Tenancy filter + Additional gates
Project Project's tenants ∩ user's tenants + user is owner OR project member (via Project People)
Workflow event Event's tenants ∩ user's tenants + owner / assignee / role-based visibility (see PERMISSIONS)
Workflow transmittal Transmittal's tenants ∩ user's tenants + project access
EDMS document Document's tenants ∩ user's tenants + data source belongs to a project the user can access
Contact Contact's tenants ∩ user's tenants + (for editing) PERMISSIONS rules

So passing the tenancy check doesn't guarantee visibility — it just means tenancy isn't the reason something is hidden.


Data sources

Within a project, documents are bucketed by data source. Each data source has its own tenancy. A project lists which data sources belong to it (Project.data_sources is a many-to-many).

A user viewing a project sees only documents whose data source is in the project AND whose data source's tenancy intersects the user's tenancy. So the same project, viewed by two users with different data tenants, can show different document subsets.

This is intentional — it's how a project can host documents for multiple parties (each with their own data tenant) while keeping each party's documents private to them.

Symptom of a misconfigured data source: a user can see the project, can see the table, but the document list is empty or missing rows they expect to see. Check the data source's tenancy against the user's.


No-tenancy is itself a tenancy

A record with no tenants assigned at all is visible only to:

  • Users with no tenants assigned, OR
  • Superusers.

A user with one or more tenants assigned will not see records that have no tenants. They've opted into a tenancy view; records outside any tenancy are outside that view.

This is occasionally a footgun. The two scenarios where it bites:

  1. An install that hasn't set up tenancy yet. Everything works because both users and records have no tenants, so visibility is trivially "everyone sees everything." Then a tenancy is added to one user "to test" — suddenly that user can't see most of the data, because the records still have no tenants. The fix is either roll the test back or migrate the existing records to the new tenant.

  2. A record created from the admin panel without a tenant set. The form will accept blank, but the resulting record is invisible to every tenanted user. Reproduce: a doc status added via admin with no tenancy, then nobody can use it in the workflow form because every user is tenanted. Always set tenants on admin-created records.


Single-tenant installs

You can run REBIM with effectively one tenant (or zero — see above). Tenancy still applies but doesn't restrict anything because everyone has the same tenant. The machinery is the cost of being multi-tenant-by- default; the convention compresses to "everything goes in the one tenant."

Single-tenant installs still benefit from the config/data split when the org wants to model internal segmentation — different business units, departments, project portfolios — even though there's only one "customer." Tenancy as in-house segmentation is a perfectly valid use of the model.


Common admin operations

Creating a new tenant

Add via the admin panel: Core → System Tenants → Add. Give it a name (the convention examples — EUS-O, AZL-O, MGS-O — encode the business meaning). No other fields are required.

Assigning a user to tenants

Via the admin panel: Auth → Users → (pick user) → User Profile → Tenancy. Multi-select. Save.

For a new install, the order is typically:

  1. Create the config tenant.
  2. Create whatever data tenants are needed.
  3. For every user, set tenancy = [config tenant, applicable data tenants].
  4. For every configuration object (workflows, statuses, etc.), set tenants = [config tenant].
  5. For every record (documents, events), set tenants = [applicable data tenant].

Reassigning records between tenants

For a small number, edit each record's tenants in the admin. For bulk moves, write a one-off management command or use the admin's bulk actions (where available) — but always check after that the dependent configuration objects are still reachable from the new tenancy.

Reorganising tenancy without surprises

A safe rollout for changing a user's tenancy on a live install:

  1. Confirm what the user can currently see (the baseline).
  2. Add the new tenancy (don't remove the old yet).
  3. Confirm they can still see everything they saw before AND the new scope.
  4. If correct, remove the old tenancy.

The intermediate "both old and new" state is always safe — the user's visible scope is the union, never accidentally less than baseline.


Troubleshooting checklist

Symptom Likely cause First check
Get email notifications but workflow table is empty User missing config tenant User Profile → Tenancy includes the config tenant
See a workflow event but workflow name is blank Same as above (partial config-tenancy mismatch) Same
See a project but no documents Data sources lack a tenancy the user has Each data source's tenancy
New tenant assigned, suddenly user can't see legacy data Legacy records have no tenant (no-tenant is its own tenant) Assign legacy records to the new tenant, or keep the user untenanted
Admin-created status / classification / workflow not selectable in form Admin didn't set tenancy on the record Admin record's tenants
Contractor sees compliance docs but emails about workflows they can't open Config tenant missing on contractor's profile UserProfile.tenancy

For developers

This document is for users and admins. For the developer-facing implementation:

  • core.models.SystemTenant — the model.
  • core.models.UserProfile.tenancy — many-to-many to SystemTenant.
  • core.models.DataModel.tenants — many-to-many on every record type.
  • core.views.BimViewSet.get_queryset — applies the tenancy filter on most viewsets.
  • apps.projects.models.ProjectManager.for_user — encapsulates the project-access filter (tenancy + membership).

See devdocs/ for codebase-level material.