Advanced Parent Resolution: The Model Property Fallback
When securing POST (creation) endpoints, the ReBAC validation phase happens before the Django model is instantiated and saved. By default, RebacViewConfig expects the frontend to provide the parent's ID directly in the JSON payload (via create_scope_field).
However, for Singletons, Tenant Roots, or Global Platform objects, forcing the frontend to send a hardcoded ID (e.g., {"platform_id": "global"}) leaks backend architecture to the client.
To solve this cleanly, django-rebac supports the Model Property Fallback. If the parent field is missing from the payload, the package will automatically instantiate a dummy instance of your model, read the field as a property, and use it for authorization.
The ReBAC Architecture
In this scenario, we have a global platform node. Platform Admins have the "God Mode" ability to create new companies.
Here is the ReBAC schema (for OpenFGA is DSL) we are targeting:
model
schema 1.1
type user
# ==========================================
# LEVEL 0: THE SINGLETON ROOT
# ==========================================
type platform
relations
# 1. Role
define admin: [user]
# 2. Permission(s)
define can_create_company: admin
# ==========================================
# LEVEL 1: THE COMPANY
# ==========================================
type company
relations
# 1. Structural Link to the Root
define platform: [platform]
# 2. Roles (Platform Admins automatically inherit Company Admin rights!)
define admin: [user] or admin from platform
define viewer: [user] or admin
# 3. Permissions
define can_read_company: viewer
define can_update_company: admin
define can_delete_company: admin
# ==========================================
# LEVEL 2: THE PROJECT
# ==========================================
type project
relations
# 1. Structural Link to the Root
define company: [company]
# 2. Roles (Platform Admins automatically inherit Company Admin rights!)
define admin: [user] or admin from company
define viewer: [user] or admin
# 3. Permissions
define can_read_project: viewer
define can_update_project: viewer
define can_delete_project: admin
How to implement the Fallback
1. Define the Property on the Model
Instead of a database field, define a @property on your model that returns the static ID (e.g., "global"). The RebacModelSyncMixin will use this property to automatically link every new Company to platform:global.
from django.db import models
from rebac.mixins import RebacModelSyncMixin
from rebac.structs import RebacModelConfig, RebacParentConfig
class Company(RebacModelSyncMixin, models.Model):
name = models.CharField(max_length=255)
creator_id = models.CharField(max_length=100)
# THE TRICK: Provide a static property for the ReBAC Mixin to read
@property
def platform_id(self) -> str:
return "global"
rebac_config = RebacModelConfig(
object_type="company",
parents=[
RebacParentConfig(
relation="platform",
parent_type="platform",
local_field="platform_id", # Maps to the property above!
)
],
)
Automated ReBAC Linking
This model configuration serves a dual purpose! When a new Company is successfully saved to the database, the RebacModelSyncMixin automatically reads the platform_id property and queues an Outbox task to write the structural link to ReBAC (object: company:{id}, relation: platform, user: platform:global). You do not need to write any custom signals or service code to build the hierarchy!
2. Configure the View
In your ViewSet, point create_scope_field to the exact name of your model property.
from rest_framework import viewsets
from rebac.mixins import RebacViewMixin
from rebac.structs import RebacViewConfig
class CompanyViewSet(RebacViewMixin, viewsets.ModelViewSet):
queryset = Company.objects.all()
serializer_class = CompanySerializer
rebac_config = RebacViewConfig(
object_type="company",
read_relation="can_read_company",
update_relation="can_update_company",
delete_relation="can_delete_company",
# 🛡️ Lock down creation to Platform Admins
create_scope_type="platform",
create_scope_field="platform_id", # The library will auto-resolve this from the Model!
create_relation="can_create_company",
)
Zero Frontend Hacks Required
Your frontend developers can now send a clean payload: {"name": "Acme Corp"}. They do not need to know about platform_id. The library intercepts the POST request, spins up an empty Company model in memory, reads platform_id="global", and securely validates it against the ReBAC network.