Plug-and-play plan-level count-limit enforcement on resource creation.
A view declares ONE resource tag — its permission_resource (the same
Resources value that drives the RBAC permission) — and this mixin hooks
create() to enforce that resource’s plan count limit automatically. No
separate plan_limited_resource: the resource tag is the single source.
class DatasetsView(PlanLimitMixin, PermissionMapMixin, ListCreateAPIView):
permission_resource = Resources.DATASETS
RBAC perm AND the dataset count cap both derive from this tag.
The “what” (model/field/filters/error per resource) lives in the
utils.plan_limits registry; PLAN_LIMIT_BY_RESOURCE maps the resource
tag → its GatedResource. A tag with no registry entry simply has no cap.
How the hook fires: DRF’s ListCreateAPIView.post() delegates to
self.create(), and views that override post() end with
super().post() — both reach this mixin’s create() (it sits before
the generic view in the MRO). Views that define their own create() just
need to call super().create() and honour its return value.
Org resolution: get_limit_organization() defaults to the view’s
get_organization() (the org the resource is created under, after any
superadmin injection), falling back to the request user’s org. Override it
for bespoke resolution (e.g. a superadmin target org).
Custom non-create() handlers (e.g. an update_or_create upsert) can’t
be hooked; those call enforce_declared_limit() explicitly — still driven
off the same resource tag.
Staff exemption: Respan staff acting on an org’s behalf bypass ALL plan
limits — a cross-cutting policy enforced centrally in
enforce_declared_limit (see is_plan_limit_exempt), not re-stated per
view. Every gated resource inherits it.