Apply ruff formatting and fix unused import linting errors

Remove unused imports flagged by ruff (F401), apply ruff format across all
files, and restore members.signals side-effect import with noqa: F401 so the
post_save signal that auto-creates Member profiles continues to fire.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 08:55:28 +02:00
parent 6c0115d4a2
commit ef05a6523d
28 changed files with 194 additions and 204 deletions

View File

@@ -11,6 +11,6 @@ import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TeamForge.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TeamForge.settings")
application = get_asgi_application()

View File

@@ -14,7 +14,6 @@ from pathlib import Path
from decouple import Csv, config
from dj_database_url import parse as db_url
from django.template.context_processors import static
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

View File

@@ -14,10 +14,11 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('backend/', include('backend.urls')),
path('admin/', admin.site.urls),
path("backend/", include("backend.urls")),
path("admin/", admin.site.urls),
]

View File

@@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TeamForge.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TeamForge.settings")
application = get_wsgi_application()

View File

@@ -1,4 +1,4 @@
from django.urls import include, path
from django.urls import path
from .views import MemberAddView, MemberDeleteView, MemberEditView, MemberListView, MemberLoadView

View File

@@ -4,6 +4,7 @@ from django.http import HttpResponse
from django.template.loader import render_to_string
from django_htmx.http import HttpResponseClientRedirect, HttpResponseClientRefresh
class HTMXPartialMixin:
"""Mixin that automatically switches to a partial template when the request is made via HTMX."""

View File

@@ -3,8 +3,4 @@ from django.urls import include, path
from .views import configuration, index
app_name = "backend"
urlpatterns = [
path("", index, name="index"),
path("members/", include("backend.members.urls")),
path("configuration", configuration, name="configuration")
]
urlpatterns = [path("", index, name="index"), path("members/", include("backend.members.urls")), path("configuration", configuration, name="configuration")]

View File

@@ -1,22 +1,19 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TeamForge.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TeamForge.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
raise ImportError("Couldn't import Django. Are you sure it's installed and available on your PYTHONPATH environment variable? Did you forget to activate a virtual environment?") from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -5,5 +5,4 @@ class MembersConfig(AppConfig):
name = "members"
def ready(self):
# noinspection PyUnusedImports
import members.signals
import members.signals # noqa: F401

View File

@@ -8,10 +8,18 @@ class MemberFilter(django_filters.FilterSet):
user__first_name = django_filters.CharFilter(field_name="user__first_name", label=_("First name"), lookup_expr="icontains")
user__last_name = django_filters.CharFilter(field_name="user__last_name", label=_("Last name"), lookup_expr="icontains")
license = django_filters.CharFilter(label=_("License"), lookup_expr="icontains")
user__is_active = django_filters.TypedChoiceFilter( field_name='user__is_active', label=_("Active?"), initial="true", choices=( ('', 'All users'), ('true', 'Active users'), ('false', 'Inactive users'), ), coerce=lambda x: x.lower() == 'true' )
user__is_active = django_filters.TypedChoiceFilter(
field_name="user__is_active",
label=_("Active?"),
initial="true",
choices=(
("", "All users"),
("true", "Active users"),
("false", "Inactive users"),
),
coerce=lambda x: x.lower() == "true",
)
class Meta:
model = Member
fields = ["user__first_name", "user__last_name", "license", "user__is_active"]

View File

@@ -7,7 +7,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [

View File

@@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("members", "0001_initial"),
]

View File

@@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("members", "0002_member_family"),
]
@@ -29,8 +28,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="member",
name="family",
field=models.ManyToManyField(
blank=True, to="members.member", verbose_name="family"
),
field=models.ManyToManyField(blank=True, to="members.member", verbose_name="family"),
),
]

View File

@@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("members", "0003_member_created_member_updated_alter_member_family"),
]
@@ -14,9 +13,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="member",
name="access_token",
field=models.CharField(
blank=True, max_length=255, null=True, verbose_name="access token"
),
field=models.CharField(blank=True, max_length=255, null=True, verbose_name="access token"),
),
migrations.AddField(
model_name="member",
@@ -37,9 +34,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="member",
name="license",
field=models.CharField(
blank=True, max_length=20, null=True, verbose_name="license"
),
field=models.CharField(blank=True, max_length=20, null=True, verbose_name="license"),
),
migrations.AddField(
model_name="member",

View File

@@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("members", "0004_member_access_token_member_birthday_and_more"),
]
@@ -17,8 +16,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="member",
name="family_members",
field=models.ManyToManyField(
blank=True, to="members.member", verbose_name="family members"
),
field=models.ManyToManyField(blank=True, to="members.member", verbose_name="family members"),
),
]

View File

@@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0005_remove_member_family_member_family_members'),
("members", "0005_remove_member_family_member_family_members"),
]
operations = [
migrations.AddField(
model_name='member',
name='notes',
field=models.TextField(blank=True, null=True, verbose_name='notes'),
model_name="member",
name="notes",
field=models.TextField(blank=True, null=True, verbose_name="notes"),
),
]

View File

@@ -66,14 +66,7 @@ class Member(RulesModel):
else:
# First check to see if a user already exists in the system
user, created = get_user_model().objects.get_or_create(
username=email,
defaults={
"first_name": first_name,
"last_name": last_name,
"email": email
}
)
user, created = get_user_model().objects.get_or_create(username=email, defaults={"first_name": first_name, "last_name": last_name, "email": email})
if not created:
user.first_name = first_name

View File

@@ -6,4 +6,4 @@ from django.contrib.auth.models import AbstractUser
@rules.predicate
def is_member_manager(user: Optional[AbstractUser]) -> bool:
return user.has_perm('members.member_manager')
return user.has_perm("members.member_manager")

View File

@@ -1,3 +1 @@
from django.contrib import admin
# Register your models here.

View File

@@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []

View File

@@ -9,95 +9,94 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0006_member_notes'),
('teams', '0001_initial'),
("members", "0006_member_notes"),
("teams", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='Team',
name="Team",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
('short_name', models.CharField(blank=True, help_text='An optional short name for the team', max_length=255, null=True, unique=True, verbose_name='short name')),
('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, max_length=255, populate_from='name', unique=True, verbose_name='slug')),
('logo', models.ImageField(blank=True, null=True, upload_to='teams/logo/', verbose_name='logo')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("name", models.CharField(max_length=255, unique=True, verbose_name="name")),
("short_name", models.CharField(blank=True, help_text="An optional short name for the team", max_length=255, null=True, unique=True, verbose_name="short name")),
("slug", django_extensions.db.fields.AutoSlugField(blank=True, editable=False, max_length=255, populate_from="name", unique=True, verbose_name="slug")),
("logo", models.ImageField(blank=True, null=True, upload_to="teams/logo/", verbose_name="logo")),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'team',
'verbose_name_plural': 'teams',
'ordering': ['name'],
"verbose_name": "team",
"verbose_name_plural": "teams",
"ordering": ["name"],
},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.CreateModel(
name='TeamRole',
name="TeamRole",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
('abbreviation', models.CharField(help_text='An abbreviated version of the role name', max_length=255, unique=True, verbose_name='abbreviation')),
('staff_role', models.BooleanField(default=False, help_text='A staff role is any supporting function for a given team (e.g., coach, team manager, ...)', verbose_name='staff role')),
('admin_role', models.BooleanField(default=False, help_text='An admin role is a role that has administrative privileges and can change team information and settings', verbose_name='admin role')),
('sort_order', models.PositiveIntegerField(default=10, help_text='The order in which the role should be displayed (low to high)', verbose_name='sort order')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("name", models.CharField(max_length=255, unique=True, verbose_name="name")),
("abbreviation", models.CharField(help_text="An abbreviated version of the role name", max_length=255, unique=True, verbose_name="abbreviation")),
("staff_role", models.BooleanField(default=False, help_text="A staff role is any supporting function for a given team (e.g., coach, team manager, ...)", verbose_name="staff role")),
("admin_role", models.BooleanField(default=False, help_text="An admin role is a role that has administrative privileges and can change team information and settings", verbose_name="admin role")),
("sort_order", models.PositiveIntegerField(default=10, help_text="The order in which the role should be displayed (low to high)", verbose_name="sort order")),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'team role',
'verbose_name_plural': 'team roles',
'ordering': ['sort_order'],
"verbose_name": "team role",
"verbose_name_plural": "team roles",
"ordering": ["sort_order"],
},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.CreateModel(
name='TeamMembership',
name="TeamMembership",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(99)], verbose_name='number')),
('captain', models.BooleanField(default=False, verbose_name='captain')),
('alternate_captain', models.BooleanField(default=False, verbose_name='alternate captain')),
('created', models.DateTimeField(auto_now_add=True)),
('modified', models.DateTimeField(auto_now=True)),
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team_memberships', to='members.member', verbose_name='member')),
('season', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team_memberships', to='teams.season', verbose_name='season')),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teams.team', verbose_name='team')),
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='team_memberships', to='teams.teamrole', verbose_name='role')),
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("number", models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(99)], verbose_name="number")),
("captain", models.BooleanField(default=False, verbose_name="captain")),
("alternate_captain", models.BooleanField(default=False, verbose_name="alternate captain")),
("created", models.DateTimeField(auto_now_add=True)),
("modified", models.DateTimeField(auto_now=True)),
("member", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="team_memberships", to="members.member", verbose_name="member")),
("season", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="team_memberships", to="teams.season", verbose_name="season")),
("team", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="teams.team", verbose_name="team")),
("role", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="team_memberships", to="teams.teamrole", verbose_name="role")),
],
options={
'verbose_name': 'team membership',
'verbose_name_plural': 'team memberships',
'ordering': ['team__name', 'role__sort_order', 'number', 'member__user__last_name', 'member__user__first_name'],
"verbose_name": "team membership",
"verbose_name_plural": "team memberships",
"ordering": ["team__name", "role__sort_order", "number", "member__user__last_name", "member__user__first_name"],
},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.AddField(
model_name='team',
name='members',
field=models.ManyToManyField(through='teams.TeamMembership', to='members.member', verbose_name='members'),
model_name="team",
name="members",
field=models.ManyToManyField(through="teams.TeamMembership", to="members.member", verbose_name="members"),
),
migrations.CreateModel(
name='TeamPicture',
name="TeamPicture",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('picture', models.ImageField(upload_to=teams.models.team_season_file_path, verbose_name='picture')),
('created', models.DateTimeField(auto_now_add=True)),
('modified', models.DateTimeField(auto_now=True)),
('season', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teams.season', verbose_name='season')),
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='teams.team', verbose_name='team')),
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("picture", models.ImageField(upload_to=teams.models.team_season_file_path, verbose_name="picture")),
("created", models.DateTimeField(auto_now_add=True)),
("modified", models.DateTimeField(auto_now=True)),
("season", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="teams.season", verbose_name="season")),
("team", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="teams.team", verbose_name="team")),
],
options={
'verbose_name': 'team picture',
'verbose_name_plural': 'team pictures',
'ordering': ['team__name', 'season__start_date'],
"verbose_name": "team picture",
"verbose_name_plural": "team pictures",
"ordering": ["team__name", "season__start_date"],
},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.AddConstraint(
model_name='teammembership',
constraint=models.UniqueConstraint(fields=('team', 'season', 'number'), name='unique_team_membership_number', violation_error_message='A team can only have one member for a given season and number.'),
model_name="teammembership",
constraint=models.UniqueConstraint(fields=("team", "season", "number"), name="unique_team_membership_number", violation_error_message="A team can only have one member for a given season and number."),
),
]

View File

@@ -18,6 +18,7 @@ def team_season_file_path(instance: "TeamPicture", filename: str) -> str:
"""Generates the file path for a team picture based on the team name and season."""
return f"teams/picture/{instance.team.slug}/{instance.season.start_date.year}/{filename}"
class Season(RulesModel):
start_date = models.DateField(_("start date"))
end_date = models.DateField(_("end date"))
@@ -52,7 +53,6 @@ class Season(RulesModel):
if season is None:
raise cls.DoesNotExist(f"No Season covers date {current_date}")
if values_only:
return season.date_range
return season
@@ -87,6 +87,7 @@ class Season(RulesModel):
return date_value.replace(year=year, month=month, day=day)
class TeamRole(RulesModel):
name = models.CharField(_("name"), max_length=255, unique=True)
abbreviation = models.CharField(_("abbreviation"), max_length=255, unique=True, help_text=_("An abbreviated version of the role name"))
@@ -108,6 +109,7 @@ class TeamRole(RulesModel):
def __str__(self):
return f"{self.name} ({self.abbreviation})"
class Team(RulesModel):
name = models.CharField(_("name"), max_length=255, unique=True)
short_name = models.CharField(_("short name"), max_length=255, unique=True, blank=True, null=True, help_text=_("An optional short name for the team"))
@@ -145,6 +147,7 @@ class Team(RulesModel):
"""Returns the short name of the team, or the name if no short name is set."""
return self.short_name if self.short_name and self.short_name != "" else self.name
class TeamMembership(RulesModel):
team = models.ForeignKey(Team, on_delete=models.CASCADE, verbose_name=_("team"))
member = models.ForeignKey("members.Member", on_delete=models.CASCADE, related_name="team_memberships", verbose_name=_("member"))
@@ -170,6 +173,7 @@ class TeamMembership(RulesModel):
def __str__(self):
return f"{self.team.name} - {self.member.user.get_full_name()} ({self.role.abbreviation})"
class TeamPicture(RulesModel):
team = models.ForeignKey(Team, on_delete=models.CASCADE, verbose_name=_("team"))
season = models.ForeignKey(Season, on_delete=models.CASCADE, verbose_name=_("season"))

View File

@@ -4,7 +4,7 @@ import rules
from django.contrib.auth.models import AbstractUser
if TYPE_CHECKING:
from .models import TeamMembership, Season
from .models import TeamMembership
@rules.predicate

View File

@@ -4,7 +4,6 @@ from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone
from members.models import Member
from teams.models import Season, Team, TeamMembership, TeamPicture, TeamRole
User = get_user_model()
@@ -14,6 +13,7 @@ User = get_user_model()
# Helpers
# ---------------------------------------------------------------------------
def make_member(email, first="Test", last="User"):
user = User.objects.create_user(username=email, email=email, first_name=first, last_name=last)
return user.member
@@ -35,6 +35,7 @@ def make_team(name="Test Team"):
# Season
# ---------------------------------------------------------------------------
class SeasonStrTest(TestCase):
def test_str(self):
season = Season(start_date=datetime.date(2025, 9, 1), end_date=datetime.date(2026, 8, 31))
@@ -127,6 +128,7 @@ class SeasonAddMonthsTest(TestCase):
# TeamRole
# ---------------------------------------------------------------------------
class TeamRoleStrTest(TestCase):
def test_str(self):
role = TeamRole(name="Coach", abbreviation="C")
@@ -143,6 +145,7 @@ class TeamRoleStrTest(TestCase):
# Team
# ---------------------------------------------------------------------------
class TeamStrTest(TestCase):
def test_str(self):
team = Team(name="Red Dragons")
@@ -199,6 +202,7 @@ class TeamMemberCountTest(TestCase):
# TeamMembership
# ---------------------------------------------------------------------------
class TeamMembershipStrTest(TestCase):
def setUp(self):
today = timezone.now().date()
@@ -209,15 +213,14 @@ class TeamMembershipStrTest(TestCase):
self.team = make_team("Blue Hawks")
self.role = make_role("Forward", "F")
self.member = make_member("player@test.com", "John", "Doe")
self.membership = TeamMembership.objects.create(
team=self.team, member=self.member, season=self.season, role=self.role
)
self.membership = TeamMembership.objects.create(team=self.team, member=self.member, season=self.season, role=self.role)
def test_str(self):
self.assertEqual(str(self.membership), "Blue Hawks - John Doe (F)")
def test_unique_number_constraint(self):
from django.db import IntegrityError
member2 = make_member("player2@test.com", "Jane", "Doe")
TeamMembership.objects.create(team=self.team, member=member2, season=self.season, role=self.role, number=7)
with self.assertRaises(IntegrityError):
@@ -232,6 +235,7 @@ class TeamMembershipStrTest(TestCase):
# TeamPicture
# ---------------------------------------------------------------------------
class TeamPictureStrTest(TestCase):
def setUp(self):
self.season = Season.objects.create(

View File

@@ -2,4 +2,4 @@ from django.apps import AppConfig
class ThemeConfig(AppConfig):
name = 'theme'
name = "theme"

View File

@@ -5,6 +5,7 @@ from django import template
register = template.Library()
def calculate_brightness(background_color: dict) -> float:
"""Calculates the brightness of a background image."""
r_coefficient = 0.241
@@ -13,6 +14,7 @@ def calculate_brightness(background_color: dict) -> float:
return sqrt(r_coefficient**2 * background_color["R"] + g_coefficient**2 * background_color["G"] + b_coefficient**2 * background_color["B"]) * 100
def foreground(background_color: dict) -> dict:
"""Calculates the foreground color based on the background."""
black = {"R": 0, "G": 0, "B": 0}
@@ -20,6 +22,7 @@ def foreground(background_color: dict) -> dict:
return black if calculate_brightness(background_color) > 210 else white
def background(text: str) -> dict:
"""Calculates the background color based on the text."""
hash_value = md5(text.encode("utf-8")).hexdigest()
@@ -28,6 +31,7 @@ def background(text: str) -> dict:
return {"R": background_color[0], "G": background_color[1], "B": background_color[2]}
@register.inclusion_tag("templatetags/avatar.html")
def avatar(first_name: str = "", last_name: str = "", initials: str = "", width: str = "md", button: bool = False) -> dict:
if initials:

View File

@@ -4,6 +4,7 @@ from typing import Optional
register = template.Library()
@register.inclusion_tag("templatetags/field.html")
def form_field(field: BoundField, label: Optional[str] = None, help_text: Optional[str] = None, show_label: bool = True, show_help_text: bool = True, show_placeholder: bool = True, show_as_toggle: bool = False, size: str = "full") -> dict:
if label is not None:

View File

@@ -5,6 +5,7 @@ from django.http import HttpRequest
register = template.Library()
@register.simple_tag
def url_replace(request: HttpRequest, field: str, value: str | int, default_field: Optional[str] = None, default_value: Optional[str | int] = None) -> str:
"""Updates the given field in the GET parameters with the supplied field. If it does not exist, the field is added."""