Skip to 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:

URL View Purpose
/teams/create/ TeamCreateView Create a new team
/teams/<team_id>/dashboard/ TeamDashboardView Team dashboard
/teams/<team_id>/settings/ TeamSettingsView Team settings
/teams/<team_id>/members/ TeamMembersListView List members
/teams/<team_id>/members/invite/ InviteMemberView Invite a member
/teams/<team_id>/members/<id>/update-role/ UpdateMemberRoleView Change member role
/teams/<team_id>/members/<id>/remove/ RemoveMemberView Remove a member
/teams/invitations/<token>/accept/ AcceptInvitationView Accept invitation
/teams/invitations/<token>/decline/ DeclineInvitationView Decline invitation
/teams/<team_id>/invitations/<id>/revoke/ RevokeInvitationView Revoke 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.