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 invitedsend_role_change_email— notifies a user when their role changesexpire_team_memberships— runs daily at 2:00 AM, deletes memberships pastaccess_expires_atexpire_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.