Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion main/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
Provider,
Skill,
SkillLevel,
Team,
TeamMembership,
ToolLanguageMethodology,
User,
UserSkill,
Expand All @@ -44,7 +46,7 @@ class CustomUserAdmin(UserAdmin[User]):
readonly_fields = ("agreed_to_tos", "date_agreed")


class CompetencyInline(admin.TabularInline[Competency, Competency]):
class CompetencyInline(admin.TabularInline[Competency, CompetencyDomain]):
"""Inline admin class for the Competency model."""

model = Competency
Expand Down Expand Up @@ -222,3 +224,21 @@ def has_delete_permission(
) -> bool:
"""Do not allow deleting users from this view."""
return False


class TeamMembershipInline(admin.TabularInline[TeamMembership, Team]):
"""Inline admin class for the Competency model."""

model = TeamMembership
extra = 0
show_change_link = False
verbose_name = "Team Member"


@admin.register(Team)
class TeamAdmin(admin.ModelAdmin[Team]):
"""Admin class for the Team model."""

list_display = ("name", "description")
search_fields = ("name", "description")
inlines = (TeamMembershipInline,)
39 changes: 39 additions & 0 deletions main/migrations/0031_team_teammembership.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 6.1.1 on 2026-10-14 16:20

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('main', '0030_event'),
]

operations = [
migrations.CreateModel(
name='Team',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)),
('description', models.TextField()),
('slug', models.SlugField(max_length=200, unique=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TeamMembership',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('manager', models.BooleanField(blank=True, default=False)),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='members', to='main.team')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teams', to=settings.AUTH_USER_MODEL)),
],
options={
'constraints': [models.UniqueConstraint(fields=('user', 'team'), name='unique_user_team')],
},
),
]
1 change: 1 addition & 0 deletions main/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

from .community_models import * # noqa: F403
from .framework_models import * # noqa: F403
from .team_models import * # noqa: F403
from .user_models import * # noqa: F403
2 changes: 1 addition & 1 deletion main/models/framework_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Models module for the main app."""
"""Models for the core framework."""

from collections.abc import Collection
from typing import Any
Expand Down
31 changes: 31 additions & 0 deletions main/models/team_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Models for the team and collaboration elements of the app."""

from django.conf import settings
from django.db import models

from .framework_models import SluggedModel


class Team(SluggedModel):
"""Model representing a team of users working together."""


class TeamMembership(models.Model):
"""Model for mapping Users to Teams and including a manager flag."""

user = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name="teams", on_delete=models.CASCADE
)
team = models.ForeignKey(Team, related_name="members", on_delete=models.CASCADE)
manager = models.BooleanField(blank=True, default=False)

class Meta:
"""Meta options for the TeamMembership model."""

constraints = (
models.UniqueConstraint(fields=["user", "team"], name="unique_user_team"),
)

def __str__(self) -> str:
"""Return the name of the model instance."""
return f"{self.user.username} membership of {self.team.name}"
2 changes: 1 addition & 1 deletion main/models/user_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Models module for the main app."""
"""Models for the user and authentication elements of the app."""

from django.conf import settings
from django.contrib.auth.models import AbstractUser
Expand Down
25 changes: 25 additions & 0 deletions tests/main/test_team_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Tests for the Team related models in the main app."""

import pytest
from django.core.exceptions import ValidationError

from main.models import Team, TeamMembership


@pytest.mark.django_db
def test_team_membership_unique(user) -> None:
"""Test the TeamMemberhip model is unique for combo of team and member."""
team = Team.objects.create(name="Team 1", description="The first team")
team_membership = TeamMembership.objects.create(user=user, team=team, manager=True)

assert team.slug == "team-1"
assert team_membership.user == user
assert team_membership.team == team
assert team_membership.manager

new_team_membership = TeamMembership(user=user, team=team)
with pytest.raises(
ValidationError,
match=r"Team membership with this User and Team already exists.",
):
new_team_membership.full_clean()
Loading