angee.spaces.models
Shared groups, their canonical role-bearing roster, and group-thread binding.
Group is a shared tree rather than a user-scoped organising list. Its visibility is a persisted fact whose owner maintains the public reader@auth/user:* tuple. Membership is the one canonical roster edge; confirmed rows whose party resolves to a platform Person.user maintain one direct group-role tuple. The granted platform user is persisted on the membership so later revocation uses the identity that actually received the grant, never a fresh interpretation of a changed Party. A party without a platform user remains a valid roster row and deliberately grants nothing.
ThreadSpace contributes the group audience onto the existing messaging.Thread row through a same-row many-to-many field. It is an abstract extension, never another first-class runtime model.
Pitfalls. Group visibility and membership grants are reconciled by instance save(); bulk APIs that bypass it (bulk_create and QuerySet.update) also bypass tuple upkeep. The spaces-owned Person receiver covers ordinary Person.save() changes to user_id, but a queryset update or a database SET_NULL cascade does not emit that signal; those paths can leave a stale tuple and require an explicit repair pass. Reconciliation is idempotent and runs inside the row transaction, but it does not lock a membership loaded earlier; concurrent saves can interleave and the last committed writer's state wins.
PUBLIC_READER_RELATION
Wildcard-subject relation opening a public group to authenticated actors.
Group
class Group(HierarchyMixin, SqidMixin, AuditMixin, AngeeModel)A shared group with one canonical roster and an unscoped parent tree.
GroupVisibility
class GroupVisibility(models.TextChoices)Whether membership is required to read the group and its threads.
Meta
class Meta(HierarchyMixin.Meta)Django options carrying the hierarchy path index and REBAC identity.
__str__
def __str__() -> strReturn the group name for Django displays.
from_db
@classmethod
def from_db(cls, db: Any, field_names: Any, values: Any) -> GroupLoad a row and snapshot visibility for save-time tuple reconciliation.
save
def save(*args: Any, **kwargs: Any) -> NonePersist the group with a unique slug and reconcile its reader tuple.
revoke_thread_relationships
def revoke_thread_relationships() -> NoneRevoke stored thread audience tuples where this group is the subject.
Membership
class Membership(ScoredLinkMixin, SqidMixin, AuditMixin, AngeeModel)One party's role-bearing roster row in a shared group.
Confirmation resolves the party through the canonical Person.user identity link and grants exactly one matching role on the group. External parties with no platform user are intentionally retained in the roster but grant no REBAC relationship.
MembershipRole
class MembershipRole(models.TextChoices)The access role a confirmed roster row grants on its group.
granted_user
The platform user that received this membership's current role grant.
Meta
class Meta()Django options for the canonical group roster edge.
__str__
def __str__() -> strReturn a readable membership description for Django displays.
from_db
@classmethod
def from_db(cls, db: Any, field_names: Any, values: Any) -> MembershipLoad a row and snapshot only fields that can change its role grant.
save
def save(*args: Any, **kwargs: Any) -> NonePersist the row and reconcile its confirmed role relationship atomically.
Unrelated saves return after the row write. A relevant save resolves the desired user once, persists that derived FK with the row, revokes only the previously stored user, and writes the desired tuple before the surrounding transaction can commit.
revoke_role_relationships
def revoke_role_relationships() -> NoneRevoke every group role from the persisted user that received it.
GROUP_ROLE_RELATIONS
Direct group relations a confirmed roster membership may own.
THREAD_GROUP_RELATION
Stored messaging/thread relation mirroring ThreadSpace.groups.
ThreadSpace
class ThreadSpace(AngeeModel)Group audience contributed onto messaging.Thread as a same-row field.
grant_group_relationship
def grant_group_relationship(group: Group) -> NoneGrant this thread's REBAC group relation to one audience group.
revoke_group_relationship
def revoke_group_relationship(group: Group) -> NoneRevoke this thread's REBAC group relation for one audience group.
revoke_group_relationships
def revoke_group_relationships() -> NoneRevoke every stored group-audience relation for this thread.
Meta
class Meta()Abstract same-row extension composed into messaging.Thread.