Add and use pre-commit hooks

This commit is contained in:
Joakim Hellsén 2025-11-28 04:44:07 +01:00
commit b2eef830d8
No known key found for this signature in database
10 changed files with 66 additions and 18 deletions

4
.gitignore vendored
View file

@ -182,9 +182,9 @@ cython_debug/
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/

39
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,39 @@
repos:
- repo: https://github.com/asottile/add-trailing-comma
rev: v4.0.0
hooks:
- id: add-trailing-comma
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-ast
- id: check-builtin-literals
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: check-toml
- id: check-vcs-permalinks
- id: end-of-file-fixer
- id: mixed-line-ending
- id: name-tests-test
args: [--pytest-test-first]
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.6
hooks:
- id: ruff-check
args: ["--fix", "--exit-non-zero-on-fix"]
- id: ruff-format
- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
hooks:
- id: pyupgrade
args: ["--py311-plus"]
- repo: https://github.com/rhysd/actionlint
rev: v1.7.9
hooks:
- id: actionlint

2
.vscode/launch.json vendored
View file

@ -25,4 +25,4 @@
"program": "${workspaceFolder}/manage.py"
}
]
}
}

View file

@ -38,4 +38,4 @@
"wrongpassword",
"xdist"
]
}
}

View file

@ -11,4 +11,4 @@ uv run python manage.py migrate
uv run python manage.py collectstatic
uv run python manage.py runserver
uv run pytest
```
```

0
manage.py Normal file → Executable file
View file

View file

@ -42,17 +42,17 @@
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; line-height: 1.4; padding: 0; font-size: 115%; max-width: 75%; margin: 0 auto;}
@media (max-width: 900px) { body { max-width: 95%; } }
table { width: 100%; }
th, td { padding: 8px; text-align: left; vertical-align: middle; }
th {background-color: Canvas; color: CanvasText; font-weight: bold; }
tr:nth-child(even) { background-color: color-mix(in srgb, Canvas 95%, CanvasText 5%); }
td > img { display: block; height: 160px; width: 160px; object-fit: cover; border-radius: 4px; }
.campaign-benefits { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 8px; }
.benefit-item { display: inline-flex; align-items: center; padding: 2px 6px; background-color: color-mix(in srgb, Canvas 90%, CanvasText 10%); border-radius: 4px; font-size: 0.9em; }
.benefit-item img { width: 24px !important; height: 24px !important; display: inline-block !important; margin-right: 4px; border-radius: 2px; }
@media (prefers-color-scheme: dark) {
.highlight { background: #0d1117; color: #E6EDF3; }
.highlight .p { color: #E6EDF3; }

View file

@ -132,7 +132,7 @@ class Command(BaseCommand):
f"{len(self._organization_cache)} orgs, "
f"{len(self._drop_campaign_cache)} campaigns, "
f"{len(self._channel_cache)} channels, "
f"{len(self._benefit_cache)} benefits."
f"{len(self._benefit_cache)} benefits.",
)
except (FileNotFoundError, OSError, RuntimeError):
# If preload fails for any reason, continue without it

View file

@ -82,7 +82,10 @@ class Migration(migrations.Migration):
(
"id",
models.TextField(
help_text="The unique Twitch identifier for the organization.", primary_key=True, serialize=False, verbose_name="Organization ID"
help_text="The unique Twitch identifier for the organization.",
primary_key=True,
serialize=False,
verbose_name="Organization ID",
),
),
("name", models.TextField(help_text="Display name of the organization.", unique=True, verbose_name="Name")),
@ -179,7 +182,10 @@ class Migration(migrations.Migration):
(
"benefits",
models.ManyToManyField(
help_text="Benefits unlocked by this drop.", related_name="drops", through="twitch.DropBenefitEdge", to="twitch.dropbenefit"
help_text="Benefits unlocked by this drop.",
related_name="drops",
through="twitch.DropBenefitEdge",
to="twitch.dropbenefit",
),
),
(
@ -200,7 +206,9 @@ class Migration(migrations.Migration):
model_name="dropbenefitedge",
name="drop",
field=models.ForeignKey(
help_text="The time-based drop in this relationship.", on_delete=django.db.models.deletion.CASCADE, to="twitch.timebaseddrop"
help_text="The time-based drop in this relationship.",
on_delete=django.db.models.deletion.CASCADE,
to="twitch.timebaseddrop",
),
),
migrations.CreateModel(

View file

@ -434,8 +434,9 @@ class GameDetailView(DetailView):
.select_related("game__owner")
.prefetch_related(
Prefetch(
"time_based_drops", queryset=TimeBasedDrop.objects.prefetch_related(Prefetch("benefits", queryset=DropBenefit.objects.order_by("name")))
)
"time_based_drops",
queryset=TimeBasedDrop.objects.prefetch_related(Prefetch("benefits", queryset=DropBenefit.objects.order_by("name"))),
),
)
.order_by("-end_at")
)
@ -580,12 +581,12 @@ def debug_view(request: HttpRequest) -> HttpResponse:
# Campaigns with missing or obviously broken images (empty or not starting with http)
broken_image_campaigns: QuerySet[DropCampaign] = DropCampaign.objects.filter(
Q(image_url__isnull=True) | Q(image_url__exact="") | ~Q(image_url__startswith="http")
Q(image_url__isnull=True) | Q(image_url__exact="") | ~Q(image_url__startswith="http"),
).select_related("game")
# Benefits with missing images
broken_benefit_images: QuerySet[DropBenefit] = DropBenefit.objects.annotate(trimmed_url=Trim("image_asset_url")).filter(
Q(image_asset_url__isnull=True) | Q(trimmed_url__exact="") | ~Q(image_asset_url__startswith="http")
Q(image_asset_url__isnull=True) | Q(trimmed_url__exact="") | ~Q(image_asset_url__startswith="http"),
)
# Time-based drops without any benefits
@ -593,7 +594,7 @@ def debug_view(request: HttpRequest) -> HttpResponse:
# Campaigns with invalid dates (start after end or missing either)
invalid_date_campaigns: QuerySet[DropCampaign] = DropCampaign.objects.filter(
Q(start_at__gt=F("end_at")) | Q(start_at__isnull=True) | Q(end_at__isnull=True)
Q(start_at__gt=F("end_at")) | Q(start_at__isnull=True) | Q(end_at__isnull=True),
).select_related("game")
# Duplicate campaign names per game. We retrieve the game's name for user-friendly display.
@ -729,7 +730,7 @@ class ChannelDetailView(DetailView):
queryset=TimeBasedDrop.objects.prefetch_related(
Prefetch("benefits", queryset=DropBenefit.objects.order_by("name")),
),
)
),
)
.order_by("-start_at")
)