Teams
Overview¶
SpeedPy includes a full multi-tenancy system based on teams. Teams can be enabled or disabled via the SPEEDPY_TEAMS_ENABLED setting.
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:
Add your plans here and use team.get_plan_config() to check plan features and limits.