From 308e4fc53433813aab50ae51d466bd03bdc42d0c Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:16:26 -0400 Subject: [PATCH 01/23] Fix type issues and missing imports --- twisted/twisted_site/hackatime.py | 2 +- twisted/twisted_site/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/twisted/twisted_site/hackatime.py b/twisted/twisted_site/hackatime.py index da4f820..44b70b7 100644 --- a/twisted/twisted_site/hackatime.py +++ b/twisted/twisted_site/hackatime.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from datetime import datetime +from datetime import UTC, datetime import requests diff --git a/twisted/twisted_site/models.py b/twisted/twisted_site/models.py index fca2b52..510fe6a 100644 --- a/twisted/twisted_site/models.py +++ b/twisted/twisted_site/models.py @@ -234,7 +234,7 @@ def status(self): if self.in_progress(): return "in progress" - def mins_spent(self, user: User): + def mins_spent(self, user: AbstractBaseUser): pathways = Pathway.objects.order_by("start").values( "id", "start", "end", "min_mins" ) From c91193620531eb7eb9b73804a9c4e6a0cfe65864 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:16:36 -0400 Subject: [PATCH 02/23] Remove unneeded type error ignore comments --- twisted/twisted_site/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twisted/twisted_site/models.py b/twisted/twisted_site/models.py index 510fe6a..24441b8 100644 --- a/twisted/twisted_site/models.py +++ b/twisted/twisted_site/models.py @@ -74,7 +74,7 @@ def time_shipped(self): return time_shipped def __str__(self): - return self.user.username # ty:ignore[unresolved-attribute] + return self.user.username PROJECT_TYPE_CHOICES = {"software": "Software", "hardware": "Hardware"} From fc1b5a7210a3c0897e5337f08f25e3b323c7dfda Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:17:48 -0400 Subject: [PATCH 03/23] Remove more unnecessary type ignore comments --- twisted/twisted_site/views/ari.py | 12 ++++++------ twisted/twisted_site/views/client/auth.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/twisted/twisted_site/views/ari.py b/twisted/twisted_site/views/ari.py index 285dc93..bb87648 100644 --- a/twisted/twisted_site/views/ari.py +++ b/twisted/twisted_site/views/ari.py @@ -183,7 +183,7 @@ def post(self, request): try: project_id = int(external_id.removeprefix("twisted-")) project = Project.objects.get(id=project_id) - except (ValueError, Project.DoesNotExist): # ty:ignore[unresolved-attribute] + except (ValueError, Project.DoesNotExist): return HttpResponseBadRequest("Invalid external_id") event = data.get("event") @@ -214,7 +214,7 @@ def post(self, request): ship: ProjectShip = project.latest_ship() - ship.status = "requested_changes" # ty:ignore[invalid-assignment] + ship.status = "requested_changes" ship.note_to_maker = note_to_maker ship.save() @@ -232,7 +232,7 @@ def post(self, request): justification = review.get("justification") or {} ship: ProjectShip = project.latest_ship() - ship.status = "approved" # ty:ignore[invalid-assignment] + ship.status = "approved" ship.note_to_maker = note_to_maker ship.audit_note = review.get("audit_note", "") ship.technical_features = justification.get("technical_features", "") @@ -253,7 +253,7 @@ def post(self, request): justification = review.get("justification") or {} ship: ProjectShip = project.latest_ship() - ship.status = "rejected" # ty:ignore[invalid-assignment] + ship.status = "rejected" ship.note_to_maker = note_to_maker ship.audit_note = review.get("audit_note", "") ship.technical_features = justification.get("technical_features", "") @@ -270,7 +270,7 @@ def post(self, request): if data["event"] == "review.reverted": ship: ProjectShip = project.latest_ship() - ship.status = "pending" # ty:ignore[invalid-assignment] + ship.status = "pending" ship.save() slack_bot.send_blocks( @@ -283,7 +283,7 @@ def post(self, request): if data["event"] == "review.requeued": ship: ProjectShip = project.latest_ship() - ship.status = "pending" # ty:ignore[invalid-assignment] + ship.status = "pending" ship.save() slack_bot.send_blocks( diff --git a/twisted/twisted_site/views/client/auth.py b/twisted/twisted_site/views/client/auth.py index ed3d5da..a723917 100644 --- a/twisted/twisted_site/views/client/auth.py +++ b/twisted/twisted_site/views/client/auth.py @@ -95,7 +95,7 @@ def get(self, request): display_name = name avatar_url = os.environ["DEFAULT_PFP"] - profile, created = Profile.objects.get_or_create(user=user) # ty:ignore[unresolved-attribute] + profile, created = Profile.objects.get_or_create(user=user) profile.verification_status = verification_status profile.slack_id = slack_id profile.slack_username = display_name From 047abb880240ae2dfb090be166b7b0d9ad8e5630 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:20:35 -0400 Subject: [PATCH 04/23] Improve GET request context parameter typing --- twisted/twisted_site/views/client/journal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/twisted/twisted_site/views/client/journal.py b/twisted/twisted_site/views/client/journal.py index d029549..be72915 100644 --- a/twisted/twisted_site/views/client/journal.py +++ b/twisted/twisted_site/views/client/journal.py @@ -1,5 +1,6 @@ import math import re +from typing import Any from django.shortcuts import get_object_or_404, redirect, render from django.views import View @@ -175,7 +176,7 @@ def post(self, request, id): class DeleteJournal(View): - def get(self, request, id, context=None): + def get(self, request, id, context: dict[str, Any] | None = None): if context is None: context = {"success": False} From d55ee1bee9df9825f7526a010ff384b4a4bdf4d7 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:21:31 -0400 Subject: [PATCH 05/23] Improved variable type hints --- twisted/twisted_site/views/admin/pathways.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/twisted/twisted_site/views/admin/pathways.py b/twisted/twisted_site/views/admin/pathways.py index 5df538c..eea3a1a 100644 --- a/twisted/twisted_site/views/admin/pathways.py +++ b/twisted/twisted_site/views/admin/pathways.py @@ -1,3 +1,6 @@ +from datetime import datetime +from typing import Any + from django.contrib import messages from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone @@ -122,7 +125,7 @@ def get(self, request, id): "profile" ) - participants = [ + participants: list[dict[str, Any]] = [ { "user": user, "mins": mins_per_participant[user.id], From 313a31f5471ea33873ac87143fd5a34ca6ce4a7c Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:21:56 -0400 Subject: [PATCH 06/23] Remove always-true nested if statements --- twisted/twisted_site/views/admin/pathways.py | 29 ++++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/twisted/twisted_site/views/admin/pathways.py b/twisted/twisted_site/views/admin/pathways.py index eea3a1a..a20bff8 100644 --- a/twisted/twisted_site/views/admin/pathways.py +++ b/twisted/twisted_site/views/admin/pathways.py @@ -70,26 +70,25 @@ def post(self, request): "min_mins": min_mins, } - if "form validation": - if not pathway_name: - return self.get(request, "No pathway name typed!", errcontext) + if not pathway_name: + return self.get(request, "No pathway name typed!", errcontext) - if not start_date: - return self.get(request, "No start date selected!", errcontext) + if not start_date: + return self.get(request, "No start date selected!", errcontext) - if not start_time: - return self.get(request, "No start time selected!", errcontext) + if not start_time: + return self.get(request, "No start time selected!", errcontext) - if not end_date: - return self.get(request, "No end date selected!", errcontext) + if not end_date: + return self.get(request, "No end date selected!", errcontext) - if not end_time: - return self.get(request, "No end time selected!", errcontext) + if not end_time: + return self.get(request, "No end time selected!", errcontext) - if min_mins <= 0: - return self.get( - request, "Minimum minutes must be greater than zero!", errcontext - ) + if min_mins <= 0: + return self.get( + request, "Minimum minutes must be greater than zero!", errcontext + ) current_tz_offset = timezone.datetime.now( timezone.get_current_timezone() From 651413271a413f876d5c6882c575506d343dbcd3 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:22:53 -0400 Subject: [PATCH 07/23] Fix timezone time formatting --- twisted/twisted_site/views/admin/pathways.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/twisted/twisted_site/views/admin/pathways.py b/twisted/twisted_site/views/admin/pathways.py index a20bff8..e72b43b 100644 --- a/twisted/twisted_site/views/admin/pathways.py +++ b/twisted/twisted_site/views/admin/pathways.py @@ -90,15 +90,13 @@ def post(self, request): request, "Minimum minutes must be greater than zero!", errcontext ) - current_tz_offset = timezone.datetime.now( - timezone.get_current_timezone() - ).strftime("%z") + current_tz_offset = datetime.now(timezone.get_current_timezone()).strftime("%z") - start = timezone.datetime.strptime( + start = datetime.strptime( f"{start_date} {start_time} {current_tz_offset}", "%Y-%m-%d %H:%M %z" ) - end = timezone.datetime.strptime( + end = datetime.strptime( f"{end_date} {end_time} {current_tz_offset}", "%Y-%m-%d %H:%M %z" ) From 9ac7de36f91c7c698834f59e3b6745aaab2bba46 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:29:44 -0400 Subject: [PATCH 08/23] Correct venv configuration for Pyright --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 0793262..67998af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,3 +31,7 @@ dependencies = [ [tool.ruff.lint.per-file-ignores] "*/migrations/*.py" = ["RUF012"] + +[tool.pyright] +venvPath = "." +venv = ".venv" From dab0db959d7ce0cb4316841854141d1914e78ca7 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:30:35 -0400 Subject: [PATCH 09/23] Improved type hinting and parameters --- twisted/twisted_site/ari.py | 4 ++-- twisted/twisted_site/models.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/twisted/twisted_site/ari.py b/twisted/twisted_site/ari.py index ecfd902..58e72cb 100644 --- a/twisted/twisted_site/ari.py +++ b/twisted/twisted_site/ari.py @@ -3,7 +3,7 @@ import json import time from collections.abc import Iterable -from typing import Literal +from typing import Any, Literal import requests from django.conf import settings @@ -163,7 +163,7 @@ def get_project_status(project: Project): } -def ship_passes_from_status(status: dict | None) -> tuple[str, str]: +def ship_passes_from_status(status: dict[str, Any] | None) -> tuple[str, str]: """Maps an ARI /status response into (first_pass_status, second_pass_status), using the PROJECT_SHIP_STATUSES vocabulary (pending/approved/rejected/requested_changes).""" if not status: diff --git a/twisted/twisted_site/models.py b/twisted/twisted_site/models.py index 24441b8..aa519f3 100644 --- a/twisted/twisted_site/models.py +++ b/twisted/twisted_site/models.py @@ -1,4 +1,5 @@ from django.contrib.auth import get_user_model +from django.contrib.auth.base_user import AbstractBaseUser from django.core.validators import MinValueValidator from django.db import models from django.db.models import TextField From 13e9f690c9da786b5a4b80c7b87b9114ea7d963f Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:31:08 -0400 Subject: [PATCH 10/23] Assertions for ensuring user properties exist --- twisted/twisted_site/slack.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/twisted/twisted_site/slack.py b/twisted/twisted_site/slack.py index 9532d35..4df5340 100644 --- a/twisted/twisted_site/slack.py +++ b/twisted/twisted_site/slack.py @@ -108,7 +108,9 @@ def get_user_profile(self, user: str) -> dict[str, Any]: response = self.client.users_info(user=user) user_data = response["user"] - profile = user_data.get("profile", {}) + assert isinstance(user_data, dict), "Slack users_info missing user" + profile = user_data.get("profile") + assert isinstance(profile, dict), "Slack user missing profile" return { "id": user_data["id"], From bee47039730176499a13c4b89681221afc560c0c Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:32:46 -0400 Subject: [PATCH 11/23] Fix default Ari ship status checking --- twisted/twisted_site/ari.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/twisted/twisted_site/ari.py b/twisted/twisted_site/ari.py index 58e72cb..8e72066 100644 --- a/twisted/twisted_site/ari.py +++ b/twisted/twisted_site/ari.py @@ -170,7 +170,8 @@ def ship_passes_from_status(status: dict[str, Any] | None) -> tuple[str, str]: return "pending", "pending" phase = status.get("phase") - decision = _ARI_DECISION_TO_SHIP_STATUS.get(status.get("decision"), "pending") + raw_decision = status.get("decision", "pending") + decision = _ARI_DECISION_TO_SHIP_STATUS.get(raw_decision, "pending") if phase == "second_pass": return decision, "pending" From 9e117f4107d264cf2741f1007337d42a43e5dbd6 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:33:25 -0400 Subject: [PATCH 12/23] Add `or` to prevent runtime type errors --- twisted/twisted_site/views/admin/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twisted/twisted_site/views/admin/admin.py b/twisted/twisted_site/views/admin/admin.py index 9616a34..4d489a9 100644 --- a/twisted/twisted_site/views/admin/admin.py +++ b/twisted/twisted_site/views/admin/admin.py @@ -78,7 +78,7 @@ def dispatch(self, request, *args, **kwargs): self.audit_log = AuditLog( user=request.user, path=self.request.get_full_path(), - post=(request.method.lower() == "post"), + post=((request.method or "").lower() == "post"), additional_context={}, ) response = super().dispatch(request, *args, **kwargs) From 604eedcaf7f416966ee0baa01c408dae42c65061 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:40:31 -0400 Subject: [PATCH 13/23] Add type ignore directive comments for remaining type errors in both ty and Pyright --- twisted/twisted_site/ari.py | 4 ++-- twisted/twisted_site/models.py | 12 ++++++------ twisted/twisted_site/slack.py | 2 +- twisted/twisted_site/views/admin/admin.py | 4 ++-- twisted/twisted_site/views/admin/pathways.py | 7 ++++--- twisted/twisted_site/views/admin/users.py | 10 +++++----- twisted/twisted_site/views/client/auth.py | 2 ++ twisted/twisted_site/views/client/dashboard.py | 4 ++-- twisted/twisted_site/views/client/project.py | 10 +++++----- 9 files changed, 29 insertions(+), 26 deletions(-) diff --git a/twisted/twisted_site/ari.py b/twisted/twisted_site/ari.py index 8e72066..382059d 100644 --- a/twisted/twisted_site/ari.py +++ b/twisted/twisted_site/ari.py @@ -114,7 +114,7 @@ def send_ship(ship: ProjectShip): journals = [] orm_journals: Iterable[Journal] = ship.project.journals.all() for journal in orm_journals: - content = f"# Journal type: {journal.get_type_display()}\n\n{journal.content}" + content = f"# Journal type: {journal.get_type_display()}\n\n{journal.content}" # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] journals.append( { "at": journal.created_at.isoformat(), @@ -146,7 +146,7 @@ def send_ship(ship: ProjectShip): def get_project_status(project: Project): - r = send_request("GET", endpoint=f"/status?external_id=twisted-{project.id}") + r = send_request("GET", endpoint=f"/status?external_id=twisted-{project.id}") # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] _resp = r.content r.raise_for_status() return r.json() diff --git a/twisted/twisted_site/models.py b/twisted/twisted_site/models.py index aa519f3..b3e438e 100644 --- a/twisted/twisted_site/models.py +++ b/twisted/twisted_site/models.py @@ -111,7 +111,7 @@ def get_hackatime_project(self) -> hackatime.HackatimeProject | None: def time_logged(self, include_all_minutes=False): minutes = 0 - for journal in self.journals.all(): # ty:ignore[unresolved-attribute] + for journal in self.journals.all(): # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] if include_all_minutes: # django-orm-lens-disable-next-line DOL007 minutes += journal.minutes_worked @@ -121,7 +121,7 @@ def time_logged(self, include_all_minutes=False): def hackatime_logged(self, include_all_minutes=False): minutes = 0 - for journal in self.journals.all(): # ty:ignore[unresolved-attribute] + for journal in self.journals.all(): # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] if journal.type != "hackatime": continue if include_all_minutes: @@ -141,7 +141,7 @@ def hackatime_time_unjournaled(self): return self.time_spent() - self.hackatime_logged(include_all_minutes=True) def latest_ship(self): - ship = self.ships.order_by("-created_at").first() + ship = self.ships.order_by("-created_at").first() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] return ship def is_shipped(self): @@ -205,7 +205,7 @@ class ProjectShip(models.Model): final_audit_note = models.TextField(blank=True, default="") def __str__(self): - return f"Ship created at {self.created_at} ({self.get_status_display()})" # ty:ignore[unresolved-attribute] + return f"Ship created at {self.created_at} ({self.get_status_display()})" # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] class Pathway(models.Model): @@ -273,7 +273,7 @@ def mins_spent(self, user: AbstractBaseUser): mins_remaining -= mins_donated pathway_totals[p_id] = mins_completed + mins_donated - return pathway_totals[self.id] + return pathway_totals[self.id] # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] def mins_spent_per_participant(self) -> dict[int, int]: """ @@ -331,7 +331,7 @@ def mins_spent_per_participant(self) -> dict[int, int]: # Extract only this pathway's result for each participant return { - user_id: totals.get(self.id, 0) + user_id: totals.get(self.id, 0) # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] for user_id, totals in user_pathway_totals.items() } diff --git a/twisted/twisted_site/slack.py b/twisted/twisted_site/slack.py index 4df5340..bdd200e 100644 --- a/twisted/twisted_site/slack.py +++ b/twisted/twisted_site/slack.py @@ -35,7 +35,7 @@ def post_message( if blocks is not None: payload["blocks"] = blocks - return self.client.chat_postMessage(**payload) + return self.client.chat_postMessage(**payload) # ty:ignore[invalid-argument-type] # pyright: ignore[reportArgumentType, reportCallIssue] def send_message( self, diff --git a/twisted/twisted_site/views/admin/admin.py b/twisted/twisted_site/views/admin/admin.py index 4d489a9..56f02e0 100644 --- a/twisted/twisted_site/views/admin/admin.py +++ b/twisted/twisted_site/views/admin/admin.py @@ -67,13 +67,13 @@ def get_context_data(self, page, subpage=None) -> dict: href=resolve_url("admin.logs") + "?page=1", ), ] - context["profile"] = self.request.user.profile + context["profile"] = self.request.user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] return context def dispatch(self, request, *args, **kwargs): if request.user.is_anonymous: return redirect("homepage") - if not request.user.profile.is_staff: + if not request.user.profile.is_staff: # pyright: ignore[reportAttributeAccessIssue] return redirect("dashboard") self.audit_log = AuditLog( user=request.user, diff --git a/twisted/twisted_site/views/admin/pathways.py b/twisted/twisted_site/views/admin/pathways.py index e72b43b..6e0f327 100644 --- a/twisted/twisted_site/views/admin/pathways.py +++ b/twisted/twisted_site/views/admin/pathways.py @@ -125,13 +125,14 @@ def get(self, request, id): participants: list[dict[str, Any]] = [ { "user": user, - "mins": mins_per_participant[user.id], + "mins": mins_per_participant[user.id], # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] "percent": min( - 100, round(mins_per_participant[user.id] / pathway.min_mins * 100) + 100, + round(mins_per_participant[user.id] / pathway.min_mins * 100), # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] ) if pathway.min_mins else 0, - "qualified": mins_per_participant[user.id] >= pathway.min_mins, + "qualified": mins_per_participant[user.id] >= pathway.min_mins, # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] } for user in users ] diff --git a/twisted/twisted_site/views/admin/users.py b/twisted/twisted_site/views/admin/users.py index d123791..80c44a1 100644 --- a/twisted/twisted_site/views/admin/users.py +++ b/twisted/twisted_site/views/admin/users.py @@ -43,8 +43,8 @@ def get(self, request, id): context = self.get_context_data(page="users", subpage="detail") user = get_object_or_404(User, id=id) - self.audit_log.additional_context["user_pfp__img"] = user.profile.slack_pfp_url - self.audit_log.additional_context["user"] = user.profile.slack_username + self.audit_log.additional_context["user_pfp__img"] = user.profile.slack_pfp_url # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + self.audit_log.additional_context["user"] = user.profile.slack_username # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] context["user"] = user context["login_maybe"] = os.environ.get("LOGIN_ENABLED") == "maybe" @@ -53,11 +53,11 @@ def get(self, request, id): def post(self, request, id): user = get_object_or_404(User, id=id) - self.audit_log.additional_context["user_pfp__img"] = user.profile.slack_pfp_url - self.audit_log.additional_context["user"] = user.profile.slack_username + self.audit_log.additional_context["user_pfp__img"] = user.profile.slack_pfp_url # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + self.audit_log.additional_context["user"] = user.profile.slack_username # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] if request.POST.get("action") == "toggle_is_allowed": - prof = user.profile + prof = user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] prof.is_allowed = not prof.is_allowed self.audit_log.additional_context["is_allowed"] = ( f"Set to {prof.is_allowed}" diff --git a/twisted/twisted_site/views/client/auth.py b/twisted/twisted_site/views/client/auth.py index a723917..a4c45cd 100644 --- a/twisted/twisted_site/views/client/auth.py +++ b/twisted/twisted_site/views/client/auth.py @@ -83,7 +83,9 @@ def get(self, request): try: slack_user = slack_bot.users_info(user=slack_id)["user"] + assert isinstance(slack_user, dict), "Slack users_info missing user" slack_profile = slack_user["profile"] + assert isinstance(slack_profile, dict), "Slack user missing profile" display_name = slack_profile.get("display_name") or slack_profile.get( "real_name" diff --git a/twisted/twisted_site/views/client/dashboard.py b/twisted/twisted_site/views/client/dashboard.py index 6823095..c37d309 100644 --- a/twisted/twisted_site/views/client/dashboard.py +++ b/twisted/twisted_site/views/client/dashboard.py @@ -9,7 +9,7 @@ class DashboardView(View): def get(self, request): if self.request.user.is_anonymous: return redirect("homepage") - profile = self.request.user.profile + profile = self.request.user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] context = {"profile": profile} @@ -21,7 +21,7 @@ def get(self, request): project = get_object_or_404(Project, id=project_id) startup_windows.append( { - "href": resolve_url("fr.projects.detail", project.id), + "href": resolve_url("fr.projects.detail", project.id), # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] "title": project.project_name, } ) diff --git a/twisted/twisted_site/views/client/project.py b/twisted/twisted_site/views/client/project.py index a3faf5d..6487564 100644 --- a/twisted/twisted_site/views/client/project.py +++ b/twisted/twisted_site/views/client/project.py @@ -23,8 +23,8 @@ def get(self, request, id): project = get_object_or_404(Project, id=id) context["project"] = project - journals = project.journals.all() - ships = project.ships.all() + journals = project.journals.all() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + ships = project.ships.all() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] context["journals"] = list(chain(journals, ships)) context["journals"].sort(key=lambda x: x.created_at, reverse=True) @@ -108,7 +108,7 @@ def post(self, request, id): project.playable_url = request.POST.get("playable_url", "") project.screenshot_url = request.POST.get("screenshot_url", "") project.save() - return redirect("fr.projects.detail", project.id) + return redirect("fr.projects.detail", project.id) # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] class SubmitProject(View): @@ -146,7 +146,7 @@ def post(self, request, id, context=None): project = get_object_or_404(Project, id=id) if project.user != request.user: - return redirect("fr.projects.detail", project.id) + return redirect("fr.projects.detail", project.id) # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] if project.is_shipped(): return self.get( @@ -169,4 +169,4 @@ def post(self, request, id, context=None): except Exception: ship.delete() raise - return redirect("fr.projects.detail", project.id) + return redirect("fr.projects.detail", project.id) # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] From 4e31ee13e328460043d9683bd6712a5e621ad3ae Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:44:12 -0400 Subject: [PATCH 14/23] Configuration for Pyrefly --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 67998af..d4ff9cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,3 +35,6 @@ dependencies = [ [tool.pyright] venvPath = "." venv = ".venv" + +[tool.pyrefly] +infer-with-first-use = false From 8f346ac4559580099976fc7bedcdd1fd420d454f Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:46:44 -0400 Subject: [PATCH 15/23] More variable type hinting --- twisted/twisted_site/views/admin/dashboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twisted/twisted_site/views/admin/dashboard.py b/twisted/twisted_site/views/admin/dashboard.py index de3d177..4591f38 100644 --- a/twisted/twisted_site/views/admin/dashboard.py +++ b/twisted/twisted_site/views/admin/dashboard.py @@ -15,8 +15,8 @@ def get(self, request): return redirect("homepage") hours_logged = 0 hours_logged_chart = {} - logged_project_type = {"Software": 0, "Hardware": 0} - shipped_project_type = {"Software": 0, "Hardware": 0} + logged_project_type: dict[str, float] = {"Software": 0, "Hardware": 0} + shipped_project_type: dict[str, float] = {"Software": 0, "Hardware": 0} hours_shipped = 0 hours_shipped_chart = {} for journal in Journal.objects.all().prefetch_related("project"): From d0472d024f875f66c579453de8cb663ec90cf587 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:47:05 -0400 Subject: [PATCH 16/23] Improve variable default values --- twisted/twisted_site/views/client/auth.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/twisted/twisted_site/views/client/auth.py b/twisted/twisted_site/views/client/auth.py index a4c45cd..204e7b8 100644 --- a/twisted/twisted_site/views/client/auth.py +++ b/twisted/twisted_site/views/client/auth.py @@ -87,10 +87,12 @@ def get(self, request): slack_profile = slack_user["profile"] assert isinstance(slack_profile, dict), "Slack user missing profile" - display_name = slack_profile.get("display_name") or slack_profile.get( - "real_name" + display_name = ( + slack_profile.get("display_name") + or slack_profile.get("real_name") + or name ) - avatar_url = slack_profile.get("image_512") + avatar_url = slack_profile.get("image_512") or os.environ["DEFAULT_PFP"] except Exception: logger.exception("Slack profile fetch failed") From a3ca7965ac7a7785f3ccd35e97c5daf9b73a4889 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:47:25 -0400 Subject: [PATCH 17/23] More assertions for structure checking --- twisted/twisted_site/views/admin/pathways.py | 1 + twisted/twisted_site/views/admin/users.py | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/twisted/twisted_site/views/admin/pathways.py b/twisted/twisted_site/views/admin/pathways.py index 6e0f327..77d874f 100644 --- a/twisted/twisted_site/views/admin/pathways.py +++ b/twisted/twisted_site/views/admin/pathways.py @@ -115,6 +115,7 @@ def get(self, request, id): pathway = get_object_or_404(Pathway, id=id) context["pathway"] = pathway + assert isinstance(self.audit_log.additional_context, dict) self.audit_log.additional_context["pathway_name"] = pathway.name mins_per_participant = pathway.mins_spent_per_participant() diff --git a/twisted/twisted_site/views/admin/users.py b/twisted/twisted_site/views/admin/users.py index 80c44a1..e878754 100644 --- a/twisted/twisted_site/views/admin/users.py +++ b/twisted/twisted_site/views/admin/users.py @@ -32,6 +32,7 @@ def post(self, request): if request.POST.get("action") == "logoutall": session_count = Session.objects.count() Session.objects.all().delete() + assert isinstance(self.audit_log.additional_context, dict) self.audit_log.additional_context["action"] = "logoutall" self.audit_log.additional_context["sessions_deleted"] = session_count @@ -43,8 +44,9 @@ def get(self, request, id): context = self.get_context_data(page="users", subpage="detail") user = get_object_or_404(User, id=id) - self.audit_log.additional_context["user_pfp__img"] = user.profile.slack_pfp_url # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] - self.audit_log.additional_context["user"] = user.profile.slack_username # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + assert isinstance(self.audit_log.additional_context, dict) + self.audit_log.additional_context["user_pfp__img"] = user.profile.slack_pfp_url # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] + self.audit_log.additional_context["user"] = user.profile.slack_username # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] context["user"] = user context["login_maybe"] = os.environ.get("LOGIN_ENABLED") == "maybe" @@ -53,8 +55,9 @@ def get(self, request, id): def post(self, request, id): user = get_object_or_404(User, id=id) - self.audit_log.additional_context["user_pfp__img"] = user.profile.slack_pfp_url # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] - self.audit_log.additional_context["user"] = user.profile.slack_username # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + assert isinstance(self.audit_log.additional_context, dict) + self.audit_log.additional_context["user_pfp__img"] = user.profile.slack_pfp_url # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] + self.audit_log.additional_context["user"] = user.profile.slack_username # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] if request.POST.get("action") == "toggle_is_allowed": prof = user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] From 6a639cdb951ec1a187ae823e757ff5d5664eb2c8 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 13:47:45 -0400 Subject: [PATCH 18/23] More ignore comments for all type checkers Type check ALL THE THINGS!! --- twisted/twisted_site/ari.py | 10 +++++----- twisted/twisted_site/models.py | 18 +++++++++--------- twisted/twisted_site/slack.py | 2 +- twisted/twisted_site/views/admin/admin.py | 2 +- twisted/twisted_site/views/admin/users.py | 2 +- twisted/twisted_site/views/ari.py | 12 ++++++------ twisted/twisted_site/views/client/dashboard.py | 2 +- twisted/twisted_site/views/client/project.py | 8 ++++---- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/twisted/twisted_site/ari.py b/twisted/twisted_site/ari.py index 382059d..367179b 100644 --- a/twisted/twisted_site/ari.py +++ b/twisted/twisted_site/ari.py @@ -83,13 +83,13 @@ def send_ship(ship: ProjectShip): untracked_time = 0 if ship.project.project_type == "hardware": - for journal in ship.project.journals.filter(type="untracked"): + for journal in ship.project.journals.filter(type="untracked"): # pyrefly: ignore[missing-attribute] untracked_time += journal.reduced_minutes maker = { - "email": ship.project.user.email, - "name": ship.project.user.profile.slack_username, - "slack_id": ship.project.user.profile.slack_id, + "email": ship.project.user.email, # pyrefly: ignore[missing-attribute] + "name": ship.project.user.profile.slack_username, # pyrefly: ignore[missing-attribute] + "slack_id": ship.project.user.profile.slack_id, # pyrefly: ignore[missing-attribute] "program_hours": untracked_time / 60, } @@ -112,7 +112,7 @@ def send_ship(ship: ProjectShip): } journals = [] - orm_journals: Iterable[Journal] = ship.project.journals.all() + orm_journals: Iterable[Journal] = ship.project.journals.all() # pyrefly: ignore[missing-attribute] for journal in orm_journals: content = f"# Journal type: {journal.get_type_display()}\n\n{journal.content}" # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] journals.append( diff --git a/twisted/twisted_site/models.py b/twisted/twisted_site/models.py index b3e438e..6bbfe60 100644 --- a/twisted/twisted_site/models.py +++ b/twisted/twisted_site/models.py @@ -24,7 +24,7 @@ class UploadedFile(models.Model): filesize = models.IntegerField() def __str__(self): - return f"{self.cdn_response['filename']} uploaded by {self.uploaded_by.profile.slack_username}" + return f"{self.cdn_response['filename']} uploaded by {self.uploaded_by.profile.slack_username}" # pyrefly: ignore[missing-attribute] # Create your models here. @@ -57,14 +57,14 @@ class Profile(models.Model): def shipped_projects(self): shipped_projects = [] - for project in self.user.projects.all(): + for project in self.user.projects.all(): # pyrefly: ignore[missing-attribute] if project.is_shipped(): shipped_projects.append(project) return shipped_projects def time_logged(self): time_logged = 0 - for project in self.user.projects.all(): + for project in self.user.projects.all(): # pyrefly: ignore[missing-attribute] time_logged += project.time_logged() return time_logged @@ -75,7 +75,7 @@ def time_shipped(self): return time_shipped def __str__(self): - return self.user.username + return self.user.username # pyrefly: ignore[missing-attribute] PROJECT_TYPE_CHOICES = {"software": "Software", "hardware": "Hardware"} @@ -103,7 +103,7 @@ def __str__(self): def get_hackatime_project(self) -> hackatime.HackatimeProject | None: if not self.hackatime_project_name: return - projects = hackatime.projects(self.user.profile.hackatime_access_token) + projects = hackatime.projects(self.user.profile.hackatime_access_token) # pyrefly: ignore[missing-attribute] for project in projects: if project.name == self.hackatime_project_name: return project @@ -111,7 +111,7 @@ def get_hackatime_project(self) -> hackatime.HackatimeProject | None: def time_logged(self, include_all_minutes=False): minutes = 0 - for journal in self.journals.all(): # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + for journal in self.journals.all(): # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] if include_all_minutes: # django-orm-lens-disable-next-line DOL007 minutes += journal.minutes_worked @@ -121,7 +121,7 @@ def time_logged(self, include_all_minutes=False): def hackatime_logged(self, include_all_minutes=False): minutes = 0 - for journal in self.journals.all(): # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + for journal in self.journals.all(): # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] if journal.type != "hackatime": continue if include_all_minutes: @@ -141,7 +141,7 @@ def hackatime_time_unjournaled(self): return self.time_spent() - self.hackatime_logged(include_all_minutes=True) def latest_ship(self): - ship = self.ships.order_by("-created_at").first() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + ship = self.ships.order_by("-created_at").first() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] return ship def is_shipped(self): @@ -357,4 +357,4 @@ class AuditLog(models.Model): additional_context = models.JSONField(null=True, default=None) def __str__(self): - return f"Audit log for {self.user.profile.slack_username}. PII: {self.pii}" + return f"Audit log for {self.user.profile.slack_username}. PII: {self.pii}" # pyrefly: ignore[missing-attribute] diff --git a/twisted/twisted_site/slack.py b/twisted/twisted_site/slack.py index bdd200e..63de174 100644 --- a/twisted/twisted_site/slack.py +++ b/twisted/twisted_site/slack.py @@ -35,7 +35,7 @@ def post_message( if blocks is not None: payload["blocks"] = blocks - return self.client.chat_postMessage(**payload) # ty:ignore[invalid-argument-type] # pyright: ignore[reportArgumentType, reportCallIssue] + return self.client.chat_postMessage(**payload) # ty:ignore[invalid-argument-type] # pyright: ignore[reportArgumentType, reportCallIssue] # pyrefly: ignore[bad-argument-type] def send_message( self, diff --git a/twisted/twisted_site/views/admin/admin.py b/twisted/twisted_site/views/admin/admin.py index 56f02e0..8177c04 100644 --- a/twisted/twisted_site/views/admin/admin.py +++ b/twisted/twisted_site/views/admin/admin.py @@ -67,7 +67,7 @@ def get_context_data(self, page, subpage=None) -> dict: href=resolve_url("admin.logs") + "?page=1", ), ] - context["profile"] = self.request.user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + context["profile"] = self.request.user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] return context def dispatch(self, request, *args, **kwargs): diff --git a/twisted/twisted_site/views/admin/users.py b/twisted/twisted_site/views/admin/users.py index e878754..89a9f29 100644 --- a/twisted/twisted_site/views/admin/users.py +++ b/twisted/twisted_site/views/admin/users.py @@ -60,7 +60,7 @@ def post(self, request, id): self.audit_log.additional_context["user"] = user.profile.slack_username # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] if request.POST.get("action") == "toggle_is_allowed": - prof = user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + prof = user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] prof.is_allowed = not prof.is_allowed self.audit_log.additional_context["is_allowed"] = ( f"Set to {prof.is_allowed}" diff --git a/twisted/twisted_site/views/ari.py b/twisted/twisted_site/views/ari.py index bb87648..e829c28 100644 --- a/twisted/twisted_site/views/ari.py +++ b/twisted/twisted_site/views/ari.py @@ -200,7 +200,7 @@ def post(self, request): project.hackatime_project_name = data["ship"]["hackatime_projects"][0] project.save() slack_bot.send_blocks( - channel=project.user.profile.slack_id, + channel=project.user.profile.slack_id, # pyrefly: ignore[missing-attribute] blocks=_build_ship_update_blocks(project, data["changes"]), text=f"Your ship for {project.project_name} has been updated by a reviewer!", ) @@ -219,7 +219,7 @@ def post(self, request): ship.save() slack_bot.send_blocks( - channel=project.user.profile.slack_id, + channel=project.user.profile.slack_id, # pyrefly: ignore[missing-attribute] blocks=_build_review_changes_blocks(project, note_to_maker), text=f"Your ship for {project.project_name} needs some changes!", ) @@ -240,7 +240,7 @@ def post(self, request): ship.save() slack_bot.send_blocks( - channel=project.user.profile.slack_id, + channel=project.user.profile.slack_id, # pyrefly: ignore[missing-attribute] blocks=_build_review_approved_blocks(project, note_to_maker), text=f"Your ship for {project.project_name} was approved!", ) @@ -261,7 +261,7 @@ def post(self, request): ship.save() slack_bot.send_blocks( - channel=project.user.profile.slack_id, + channel=project.user.profile.slack_id, # pyrefly: ignore[missing-attribute] blocks=_build_review_rejected_blocks(project, note_to_maker), text=f"Your ship for {project.project_name} was rejected.", ) @@ -274,7 +274,7 @@ def post(self, request): ship.save() slack_bot.send_blocks( - channel=project.user.profile.slack_id, + channel=project.user.profile.slack_id, # pyrefly: ignore[missing-attribute] blocks=_build_review_reverted_blocks(project), text=f"The decision on your ship for {project.project_name} was reverted.", ) @@ -287,7 +287,7 @@ def post(self, request): ship.save() slack_bot.send_blocks( - channel=project.user.profile.slack_id, + channel=project.user.profile.slack_id, # pyrefly: ignore[missing-attribute] blocks=_build_review_requeued_blocks(project), text=f"Your ship for {project.project_name} is back in the review queue.", ) diff --git a/twisted/twisted_site/views/client/dashboard.py b/twisted/twisted_site/views/client/dashboard.py index c37d309..22af7c0 100644 --- a/twisted/twisted_site/views/client/dashboard.py +++ b/twisted/twisted_site/views/client/dashboard.py @@ -9,7 +9,7 @@ class DashboardView(View): def get(self, request): if self.request.user.is_anonymous: return redirect("homepage") - profile = self.request.user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + profile = self.request.user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] context = {"profile": profile} diff --git a/twisted/twisted_site/views/client/project.py b/twisted/twisted_site/views/client/project.py index 6487564..6b42b3f 100644 --- a/twisted/twisted_site/views/client/project.py +++ b/twisted/twisted_site/views/client/project.py @@ -23,8 +23,8 @@ def get(self, request, id): project = get_object_or_404(Project, id=id) context["project"] = project - journals = project.journals.all() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] - ships = project.ships.all() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + journals = project.journals.all() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] + ships = project.ships.all() # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] context["journals"] = list(chain(journals, ships)) context["journals"].sort(key=lambda x: x.created_at, reverse=True) @@ -129,7 +129,7 @@ def get(self, request, id, context=None): if not project.screenshot_url: return redirect("fr.projects.detail", id) - if not project.user.profile.ysws_eligible: + if not project.user.profile.ysws_eligible: # pyrefly: ignore[missing-attribute] context["info"] = ( "You are not YSWS eligible yet! Please get IDVd! Get help with it at #identity-help! (if you think this is a mistake, please ask in #twisted-help)" ) @@ -159,7 +159,7 @@ def post(self, request, id, context=None): if not project.screenshot_url: return redirect("fr.projects.detail", id) - if not project.user.profile.ysws_eligible: + if not project.user.profile.ysws_eligible: # pyrefly: ignore[missing-attribute] return self.get(request, id) ship = ProjectShip(project=project) From a302d6b6f7d1687ccea165841c428f3af8bc0ba8 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 8 Sep 2026 14:01:18 -0400 Subject: [PATCH 19/23] More Ruff formatting --- twisted/common/admin.py | 1 - twisted/common/models.py | 1 - twisted/common/tests.py | 1 - twisted/mysite/settings.py | 6 +-- twisted/twisted_site/admin.py | 1 - ...ffpermissions_profile_staff_permissions.py | 28 +++++++++---- ...rmissions_manage_announcements_and_more.py | 39 +++++++++---------- twisted/twisted_site/models.py | 24 ++++++++---- twisted/twisted_site/slack.py | 7 ++-- twisted/twisted_site/tests.py | 1 - twisted/twisted_site/views/admin/admin.py | 5 ++- .../twisted_site/views/admin/audit_logs.py | 6 +-- twisted/twisted_site/views/client/auth.py | 6 ++- .../twisted_site/views/client/dashboard.py | 6 ++- twisted/twisted_site/views/client/project.py | 23 ++++++----- twisted/twisted_site/views/client/projects.py | 12 +++--- 16 files changed, 96 insertions(+), 71 deletions(-) diff --git a/twisted/common/admin.py b/twisted/common/admin.py index b97a94f..846f6b4 100644 --- a/twisted/common/admin.py +++ b/twisted/common/admin.py @@ -1,2 +1 @@ - # Register your models here. diff --git a/twisted/common/models.py b/twisted/common/models.py index 35e0d64..6b20219 100644 --- a/twisted/common/models.py +++ b/twisted/common/models.py @@ -1,2 +1 @@ - # Create your models here. diff --git a/twisted/common/tests.py b/twisted/common/tests.py index 4929020..a39b155 100644 --- a/twisted/common/tests.py +++ b/twisted/common/tests.py @@ -1,2 +1 @@ - # Create your tests here. diff --git a/twisted/mysite/settings.py b/twisted/mysite/settings.py index 21e2b0a..a09c0ac 100644 --- a/twisted/mysite/settings.py +++ b/twisted/mysite/settings.py @@ -205,9 +205,9 @@ ARI_SIGNING_SECRET = os.environ.get("ARI_SIGNING_SECRET") # Separate from ARI_SIGNING_SECRET: signs deliveries Ari sends to us (Settings -> Webhooks), # not requests we send to Ari. -ARI_WEBHOOK_SECRET = os.environ.get('ARI_WEBHOOK_SECRET') +ARI_WEBHOOK_SECRET = os.environ.get("ARI_WEBHOOK_SECRET") # Slack -SLACK_TOKEN = os.environ.get('SLACK_TOKEN') -SLACK_LOG_CHANNEL = os.environ.get('SLACK_LOG_CHANNEL') +SLACK_TOKEN = os.environ.get("SLACK_TOKEN") +SLACK_LOG_CHANNEL = os.environ.get("SLACK_LOG_CHANNEL") diff --git a/twisted/twisted_site/admin.py b/twisted/twisted_site/admin.py index b97a94f..846f6b4 100644 --- a/twisted/twisted_site/admin.py +++ b/twisted/twisted_site/admin.py @@ -1,2 +1 @@ - # Register your models here. diff --git a/twisted/twisted_site/migrations/0031_profilestaffpermissions_profile_staff_permissions.py b/twisted/twisted_site/migrations/0031_profilestaffpermissions_profile_staff_permissions.py index 244d4fa..d661f9e 100644 --- a/twisted/twisted_site/migrations/0031_profilestaffpermissions_profile_staff_permissions.py +++ b/twisted/twisted_site/migrations/0031_profilestaffpermissions_profile_staff_permissions.py @@ -5,22 +5,34 @@ class Migration(migrations.Migration): - dependencies = [ - ('twisted_site', '0030_alter_journal_minutes_worked_and_more'), + ("twisted_site", "0030_alter_journal_minutes_worked_and_more"), ] operations = [ migrations.CreateModel( - name='ProfileStaffPermissions', + name="ProfileStaffPermissions", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('superuser', models.BooleanField(default=False)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("superuser", models.BooleanField(default=False)), ], ), migrations.AddField( - model_name='profile', - name='staff_permissions', - field=models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='twisted_site.profilestaffpermissions'), + model_name="profile", + name="staff_permissions", + field=models.OneToOneField( + default=None, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="twisted_site.profilestaffpermissions", + ), ), ] diff --git a/twisted/twisted_site/migrations/0032_profilestaffpermissions_manage_announcements_and_more.py b/twisted/twisted_site/migrations/0032_profilestaffpermissions_manage_announcements_and_more.py index 358899e..197b069 100644 --- a/twisted/twisted_site/migrations/0032_profilestaffpermissions_manage_announcements_and_more.py +++ b/twisted/twisted_site/migrations/0032_profilestaffpermissions_manage_announcements_and_more.py @@ -4,55 +4,54 @@ class Migration(migrations.Migration): - dependencies = [ - ('twisted_site', '0031_profilestaffpermissions_profile_staff_permissions'), + ("twisted_site", "0031_profilestaffpermissions_profile_staff_permissions"), ] operations = [ migrations.AddField( - model_name='profilestaffpermissions', - name='manage_announcements', + model_name="profilestaffpermissions", + name="manage_announcements", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='profilestaffpermissions', - name='manage_fulfillments', + model_name="profilestaffpermissions", + name="manage_fulfillments", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='profilestaffpermissions', - name='manage_pathways', + model_name="profilestaffpermissions", + name="manage_pathways", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='profilestaffpermissions', - name='manage_review', + model_name="profilestaffpermissions", + name="manage_review", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='profilestaffpermissions', - name='manage_shop', + model_name="profilestaffpermissions", + name="manage_shop", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='profilestaffpermissions', - name='view_auditlogs', + model_name="profilestaffpermissions", + name="view_auditlogs", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='profilestaffpermissions', - name='view_pathways', + model_name="profilestaffpermissions", + name="view_pathways", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='profilestaffpermissions', - name='view_review', + model_name="profilestaffpermissions", + name="view_review", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='profilestaffpermissions', - name='view_users', + model_name="profilestaffpermissions", + name="view_users", field=models.BooleanField(default=False), ), ] diff --git a/twisted/twisted_site/models.py b/twisted/twisted_site/models.py index b537c81..d0d3177 100644 --- a/twisted/twisted_site/models.py +++ b/twisted/twisted_site/models.py @@ -9,6 +9,7 @@ User = get_user_model() + class UploadedFile(models.Model): uploaded_by = models.ForeignKey(User, on_delete=models.PROTECT) link = models.CharField(max_length=500) @@ -33,11 +34,16 @@ class Profile(models.Model): hackatime_state = models.CharField(max_length=100, blank=True, default="") hca_access_token = models.CharField(max_length=2000, blank=True, default="") - + is_allowed = models.BooleanField(default=False) is_staff = models.BooleanField(default=False) - staff_permissions = models.OneToOneField('twisted_site.ProfileStaffPermissions', on_delete=models.PROTECT, default=None, null=True) - + staff_permissions = models.OneToOneField( + "twisted_site.ProfileStaffPermissions", + on_delete=models.PROTECT, + default=None, + null=True, + ) + twists = models.IntegerField(default=0) referred_by = models.ForeignKey( @@ -76,17 +82,17 @@ class ProfileStaffPermissions(models.Model): superuser = models.BooleanField(default=False) view_users = models.BooleanField(default=False) - + view_pathways = models.BooleanField(default=False) manage_pathways = models.BooleanField(default=False) - + manage_fulfillments = models.BooleanField(default=False) - + manage_shop = models.BooleanField(default=False) - + view_review = models.BooleanField(default=False) manage_review = models.BooleanField(default=False) - + manage_announcements = models.BooleanField(default=False) view_auditlogs = models.BooleanField(default=False) @@ -170,12 +176,14 @@ def is_approved(self): return False return latest_ship.status == "approved" + JOURNAL_TYPES = { "hackatime": "Hackatime", "lookout": "Lookout", "untracked": "Untracked", } + class Journal(models.Model): project = models.ForeignKey( Project, on_delete=models.PROTECT, related_name="journals" diff --git a/twisted/twisted_site/slack.py b/twisted/twisted_site/slack.py index db5d36d..6a1000a 100644 --- a/twisted/twisted_site/slack.py +++ b/twisted/twisted_site/slack.py @@ -1,5 +1,3 @@ -from typing import Any - from django.conf import settings from slack_sdk import WebClient @@ -8,8 +6,11 @@ slack_bot = WebClient(token=SLACK_TOKEN) + def log_to_channel(message): if settings.DEBUG: - slack_bot.chat_postMessage(channel=SLACK_LOG_CHANNEL, text=message, username="[DEBUG]") + slack_bot.chat_postMessage( + channel=SLACK_LOG_CHANNEL, text=message, username="[DEBUG]" + ) else: slack_bot.chat_postMessage(channel=SLACK_LOG_CHANNEL, text=message) diff --git a/twisted/twisted_site/tests.py b/twisted/twisted_site/tests.py index 4929020..a39b155 100644 --- a/twisted/twisted_site/tests.py +++ b/twisted/twisted_site/tests.py @@ -1,2 +1 @@ - # Create your tests here. diff --git a/twisted/twisted_site/views/admin/admin.py b/twisted/twisted_site/views/admin/admin.py index 9c475a4..18e202a 100644 --- a/twisted/twisted_site/views/admin/admin.py +++ b/twisted/twisted_site/views/admin/admin.py @@ -6,6 +6,7 @@ from django_htmx.http import trigger_client_event from ...models import AuditLog, ProfileStaffPermissions + @dataclass class SidebarLink: name: str @@ -81,13 +82,13 @@ def dispatch(self, request, *args, **kwargs): post=((request.method or "").lower() == "post"), additional_context={}, ) - + perms = self.request.user.profile.staff_permissions if perms is None: profile = self.request.user.profile profile.staff_permissions = ProfileStaffPermissions.objects.create() profile.save() - + self.perms = perms response = super().dispatch(request, *args, **kwargs) self.audit_log.save() diff --git a/twisted/twisted_site/views/admin/audit_logs.py b/twisted/twisted_site/views/admin/audit_logs.py index 17b90cc..49d3437 100644 --- a/twisted/twisted_site/views/admin/audit_logs.py +++ b/twisted/twisted_site/views/admin/audit_logs.py @@ -23,8 +23,8 @@ def get(self, request): auditlogs = auditlogs.exclude( Q(additional_context__isnull=True) | Q(additional_context={}) ) - + paginator = Paginator(auditlogs, 100, orphans=50) - context['logs'] = paginator.get_page(page_number) - context['log_count'] = auditlogs.count() + context["logs"] = paginator.get_page(page_number) + context["log_count"] = auditlogs.count() return TemplateResponse(request, "admin/logs.html", context=context) diff --git a/twisted/twisted_site/views/client/auth.py b/twisted/twisted_site/views/client/auth.py index 5e6f45a..4fc1206 100644 --- a/twisted/twisted_site/views/client/auth.py +++ b/twisted/twisted_site/views/client/auth.py @@ -137,8 +137,10 @@ def get(self, request): f"https://hackatime.hackclub.com/oauth/authorize?client_id={HACKATIME_CLIENT_ID}&redirect_uri={HACKATIME_REDIRECT_URI}&response_type=code&scope={scopes}&state={profile.hackatime_state}" ) - log_to_channel(f":ms-arrow-up-right: *{profile.slack_username}* just logged in!") - + log_to_channel( + f":ms-arrow-up-right: *{profile.slack_username}* just logged in!" + ) + return redirect("dashboard") diff --git a/twisted/twisted_site/views/client/dashboard.py b/twisted/twisted_site/views/client/dashboard.py index f866a60..1b3a78b 100644 --- a/twisted/twisted_site/views/client/dashboard.py +++ b/twisted/twisted_site/views/client/dashboard.py @@ -26,8 +26,10 @@ def get(self, request): } ) - if request.GET.get('discover') is not None: - startup_windows.append({"href": resolve_url('fr.discover'), "title": "discover"}) + if request.GET.get("discover") is not None: + startup_windows.append( + {"href": resolve_url("fr.discover"), "title": "discover"} + ) context["startup_windows"] = startup_windows diff --git a/twisted/twisted_site/views/client/project.py b/twisted/twisted_site/views/client/project.py index 1f21973..00f19cc 100644 --- a/twisted/twisted_site/views/client/project.py +++ b/twisted/twisted_site/views/client/project.py @@ -10,6 +10,7 @@ from ... import hackatime from ... import ari + # Create your views here. class ProjectDetail(View): def get(self, request, id): @@ -32,7 +33,7 @@ def get(self, request, id): context["first_pass_status"] = "pending" context["second_pass_status"] = "pending" - + if project.latest_ship() is not None: try: status = ari.get_project_status(project) @@ -110,11 +111,12 @@ def post(self, request, id): project.playable_url = request.POST.get("playable_url", "") project.screenshot_url = request.POST.get("screenshot_url", "") project.save() - + project_url = f"{self.request.scheme}://{self.request.get_host()}{resolve_url('dashboard')}?project={project.id}" - log_to_channel(f":settings: Updated settings for *<{project_url}|{project.project_name}>*!\n- *Description*: {project.project_description}\n- *Type*: {project_type}\n- *Hackatime*: {project.hackatime_project_name or 'None'}\n- *Repo*: {project.repo_url or 'None'}\n- *Demo*: {project.playable_url or 'None'}\n- *Screenshot*: {project.screenshot_url}") - - + log_to_channel( + f":settings: Updated settings for *<{project_url}|{project.project_name}>*!\n- *Description*: {project.project_description}\n- *Type*: {project_type}\n- *Hackatime*: {project.hackatime_project_name or 'None'}\n- *Repo*: {project.repo_url or 'None'}\n- *Demo*: {project.playable_url or 'None'}\n- *Screenshot*: {project.screenshot_url}" + ) + return redirect("fr.projects.detail", project.id) # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] @@ -168,7 +170,6 @@ def post(self, request, id, context=None): if not project.user.profile.ysws_eligible: # pyrefly: ignore[missing-attribute] return self.get(request, id) - ship = ProjectShip(project=project) ship.save() @@ -177,8 +178,10 @@ def post(self, request, id, context=None): except Exception: ship.delete() raise - + project_url = f"{self.request.scheme}://{self.request.get_host()}{resolve_url('dashboard')}?project={project.id}" - log_to_channel(f":shipitparrot: Project *<{project_url}|{project.name}> shipped with *{project.time_logged} minutes*") - - return redirect('fr.projects.detail', project.id) # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] + log_to_channel( + f":shipitparrot: Project *<{project_url}|{project.name}> shipped with *{project.time_logged} minutes*" + ) + + return redirect("fr.projects.detail", project.id) # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] diff --git a/twisted/twisted_site/views/client/projects.py b/twisted/twisted_site/views/client/projects.py index fd206e7..d190a27 100644 --- a/twisted/twisted_site/views/client/projects.py +++ b/twisted/twisted_site/views/client/projects.py @@ -5,6 +5,7 @@ from ...models import Project, PROJECT_TYPE_CHOICES from ...slack import log_to_channel + # Create your views here. class ListProjects(View): def get(self, request): @@ -40,16 +41,17 @@ def post(self, request): if project_type not in PROJECT_TYPE_CHOICES: return HttpResponse("naughty! you arent supposed to do this!") - project = Project.objects.create( user=request.user, project_name=project_name, project_description=project_description, project_type=project_type, ) - + project_url = f"{self.request.scheme}://{self.request.get_host()}{resolve_url('dashboard')}?project={project.id}" - - log_to_channel(f"*{request.user.profile.slack_username}* created a <{project_url}|new project>!\n- *Name*: {project_name}\n- *Description*: {project_description}\n- {project_type.title()}") - return redirect('fr.projects.detail', project.id) + log_to_channel( + f"*{request.user.profile.slack_username}* created a <{project_url}|new project>!\n- *Name*: {project_name}\n- *Description*: {project_description}\n- {project_type.title()}" + ) + + return redirect("fr.projects.detail", project.id) From 6868b92854f8fe1d219d3ceb17537ef0652c93ed Mon Sep 17 00:00:00 2001 From: KavyanshKhaitan2 Date: Tue, 8 Sep 2026 23:31:06 +0530 Subject: [PATCH 20/23] Add more buttoms to the homepage footer --- .../twisted_site/templates/client/homepage.html | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/twisted/twisted_site/templates/client/homepage.html b/twisted/twisted_site/templates/client/homepage.html index af9384d..15fed7a 100644 --- a/twisted/twisted_site/templates/client/homepage.html +++ b/twisted/twisted_site/templates/client/homepage.html @@ -85,20 +85,25 @@ -