Skip to main content

Teams

Overview

SpeedPy includes a full multi-tenancy system based on teams. Teams can be enabled or disabled via the SPEEDPY_TEAMS_ENABLED setting.

SPEEDPY_TEAMS_ENABLED = env.bool("SPEEDPY_TEAMS_ENABLED", default=True)

Models

All team models live in mainapp/models/teams.py.

Team

The core Team model stores team info, subscription plan, and limits:

class Team(BaseModel):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=100, unique=True)
logo = models.ImageField(upload_to="team_logos/", blank=True, null=True)
plan = models.CharField(max_length=50, default="free", choices=SUBSCRIPTION_PLANS_CHOICES)
is_active = models.BooleanField(default=True)
limits_max_team_members = models.PositiveIntegerField(blank=True, null=True)

Inherits id (UUID), created_at, and updated_at from BaseModel.

TeamMembership

Connects users to teams with roles:

class TeamMembership(TeamModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
role = models.CharField(choices=[
("owner", "Owner"), # Full control, billing, delete team
("admin", "Admin"), # Manage team, invite members
("member", "Member"), # Create/edit, view data
("viewer", "Viewer"), # Read-only access
], default="member")
invited_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
invite_accepted_at = models.DateTimeField(null=True, blank=True)
access_expires_at = models.DateTimeField(null=True, blank=True)

Permission rules:

  • Owner can manage anyone
  • Admin can manage members and viewers (not owners or other admins)
  • Members and Viewers cannot manage anyone

TeamInvitation

Handles inviting users to teams:

class TeamInvitation(TeamModel):
invited_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
email = models.EmailField()
role = models.CharField(choices=[("admin", "Admin"), ("member", "Member"), ("viewer", "Viewer")])
token = models.CharField(max_length=64, unique=True)
status = models.CharField(choices=[
("pending", "Pending"), ("accepted", "Accepted"),
("declined", "Declined"), ("expired", "Expired"), ("revoked", "Revoked"),
], default="pending")
expires_at = models.DateTimeField(null=True, blank=True)

Invitations auto-generate a secure token and expire after 7 days by default.

TeamModel (Abstract)

For your own multi-tenant models, inherit from TeamModel:

from mainapp.models.teams import TeamModel

class Project(TeamModel):
name = models.CharField(max_length=255)
# Gets: id, created_at, updated_at, team (FK to Team)

URL Routes

Team URLs are conditionally included based on SPEEDPY_TEAMS_ENABLED:

URLViewPurpose
/teams/create/TeamCreateViewCreate a new team
/teams/<team_id>/dashboard/TeamDashboardViewTeam dashboard
/teams/<team_id>/settings/TeamSettingsViewTeam settings
/teams/<team_id>/members/TeamMembersListViewList members
/teams/<team_id>/members/invite/InviteMemberViewInvite a member
/teams/<team_id>/members/<id>/update-role/UpdateMemberRoleViewChange member role
/teams/<team_id>/members/<id>/remove/RemoveMemberViewRemove a member
/teams/invitations/<token>/accept/AcceptInvitationViewAccept invitation
/teams/invitations/<token>/decline/DeclineInvitationViewDecline invitation
/teams/<team_id>/invitations/<id>/revoke/RevokeInvitationViewRevoke invitation

Background Tasks

Team-related Celery tasks are in mainapp/tasks/teams.py:

  • send_team_invitation_email — sends the invitation email when a member is invited
  • send_role_change_email — notifies a user when their role changes
  • expire_team_memberships — runs daily at 2:00 AM, deletes memberships past access_expires_at
  • expire_team_memberships_invitations — runs daily at 2:30 AM, deletes expired pending invitations

Subscription Plans

Plans are defined in mainapp/subscription_plans.py:

SUBSCRIPTION_PLANS = {
'free': {
'name': 'Free',
}
}

Add your plans here and use team.get_plan_config() to check plan features and limits.