Skip to content

Support arbitrary, temporary Entitlement grants#680

Open
allanlasser wants to merge 4 commits into
masterfrom
allanlasser/issue666
Open

Support arbitrary, temporary Entitlement grants#680
allanlasser wants to merge 4 commits into
masterfrom
allanlasser/issue666

Conversation

@allanlasser
Copy link
Copy Markdown
Member

Fixes #666

Adds a new EntitlementGrant model that lets us give Entitlements to Organizations.

Previously, the only way to associate Entitlements with Organizations was through a Subscription to a Plan.

This allows us to grant access to resources in an arbitrary way to any org, and revoke them as needed, too.

This uses our same code path for refreshing entitlements on a monthly cadence.

This also updates our serializer to union entitlements, which will come in handy when we support multiple concurrent subscriptions.

@allanlasser allanlasser temporarily deployed to squarelet-pi-allanlasse-snttlh May 19, 2026 19:57 Inactive
@allanlasser allanlasser temporarily deployed to squarelet-pi-allanlasse-snttlh May 19, 2026 20:04 Inactive
Copy link
Copy Markdown
Member

@mitchelljkotler mitchelljkotler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created another PR with SQL optimizations

# Mirrors org.has_active_subscription() = bool(subscriptions.first())
rule_clauses.append(Q(subscriptions__isnull=False))

if rule_clauses:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking my understanding:

If you check require_verified then this entitlement grant applies to all Organizations which are verified (filtered by individual/group as appropriate)

If you check require_active_subscription then it applies to all Orgs with an active subscription (again filtered by individual/group)

If both are checked, then it applies to all Orgs which are both verified and have an active subscription.

for clause in rule_clauses[1:]:
rule_q &= clause
return eligible.filter(explicit_q | rule_q).distinct()
return eligible.filter(explicit_q).distinct()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there is only explicit_q then distinct is not necessary as you can't add the same org more than once.

return self.name

def save(self, *args, **kwargs):
if self.update_on is None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are just trying to set a default value, you can do this more cleanly by setting the default to a callable:

https://docs.djangoproject.com/en/6.0/ref/models/fields/#django.db.models.Field.default

if not org.individual and not self.for_groups:
return False
# Uses `.all()` so a prefetched `organizations` relation is reused.
if any(o.pk == org.pk for o in self.organizations.all()):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if self.organizations.filter(pk=org.pk).exists():


plan_q = Q(plans__organizations=org)
grant_pks = [g.pk for g in EntitlementGrant.objects.matching(org)]
grant_q = Q(grants__in=grant_pks)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be grant_q = Q(grants__in=EntitlementGrant.objects.matching(org))

Django will automatically use the PK if you pass in a model object

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support arbitrary, temporary Entitlement grants

2 participants