diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b8cebe4f..877a6c31 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,24 +15,24 @@ jobs: - name: Run ruff check uses: astral-sh/ruff-action@v2 with: - version: 0.15.20 + version: 0.17.2 - name: Run ruff format --check uses: astral-sh/ruff-action@v2 with: - version: 0.15.20 + version: 0.17.2 args: "format --check" - name: Setup Biome uses: biomejs/setup-biome@v2 with: - version: 1.9.4 + version: 2.6.3 - name: Run Biome run: biome ci . - name: Install djhtml - run: pip install djhtml + run: pip install djhtml==3.0.11 - name: Run djhtml run: djhtml pgcommitfest/*/templates/*.html pgcommitfest/*/templates/*.inc --tabwidth=1 --check diff --git a/.gitignore b/.gitignore index e293a5a4..aef2100b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /.idea/ /venv/ *.egg-info +/build/ diff --git a/Makefile b/Makefile index 566bf874..c0c0f4ba 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,18 @@ format: ruff format - npx @biomejs/biome@1.9.4 format --write + npx @biomejs/biome@2.6.3 format --write djhtml pgcommitfest/*/templates/*.html pgcommitfest/*/templates/*.inc --tabwidth=1 lint: ruff check - npx @biomejs/biome@1.9.4 check + npx @biomejs/biome@2.6.3 check lint-fix: ruff check --fix - npx @biomejs/biome@1.9.4 check --fix + npx @biomejs/biome@2.6.3 check --fix lint-fix-unsafe: ruff check --fix --unsafe-fixes - npx @biomejs/biome@1.9.4 check --fix --unsafe + npx @biomejs/biome@2.6.3 check --fix --unsafe fix: format lint-fix-unsafe diff --git a/biome.json b/biome.json index 4e121e69..97ea151c 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.6.3/schema.json", "vcs": { "enabled": false, "clientKind": "git", @@ -7,12 +7,11 @@ }, "files": { "ignoreUnknown": true, - "ignore": [], - "include": [ - "media/commitfest/js/commitfest.js", - "media/commitfest/js/change_tag.js", - "media/commitfest/css/commitfest.css", - "biome.json" + "includes": [ + "**/media/commitfest/js/commitfest.js", + "**/media/commitfest/js/change_tag.js", + "**/media/commitfest/css/commitfest.css", + "**/biome.json" ] }, "formatter": { @@ -20,13 +19,11 @@ "indentStyle": "space", "indentWidth": 4 }, - "organizeImports": { - "enabled": true - }, + "assist": { "actions": { "source": { "organizeImports": "on" } } }, "linter": { "enabled": true, "rules": { - "recommended": true + "preset": "recommended" } }, "javascript": { diff --git a/media/commitfest/css/commitfest.css b/media/commitfest/css/commitfest.css index 21cbb2d6..a26eee89 100644 --- a/media/commitfest/css/commitfest.css +++ b/media/commitfest/css/commitfest.css @@ -4,11 +4,13 @@ body { --bs-body-font-size: 14px; - --bs-body-font-family: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", - "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", + --bs-body-font-family: + -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", + "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --bs-font-sans-serif: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", - "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", + --bs-font-sans-serif: + -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", + "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } @@ -127,19 +129,19 @@ nav[aria-label="breadcrumb"] .float-end { /* Remove underlines from tag links */ a:has(.badge) { - text-decoration: none !important; + text-decoration: none; } a:has(.badge):hover { - text-decoration: none !important; + text-decoration: none; } a .badge { - text-decoration: none !important; + text-decoration: none; } a:hover .badge { - text-decoration: none !important; + text-decoration: none; } /* Simple close button without float */ @@ -183,13 +185,18 @@ a:hover .badge { } /* Mobile: stack actions vertically, tablet+: horizontal */ +/* The !important is needed to override Bootstrap's gap-2 and + * align-items-center utility classes, which use !important themselves. */ .navbar-actions { flex-direction: column; + /* biome-ignore lint/complexity/noImportantStyles: see above */ gap: 0.5rem !important; + /* biome-ignore lint/complexity/noImportantStyles: see above */ align-items: stretch !important; } .navbar-actions .input-group { + /* biome-ignore lint/complexity/noImportantStyles: overrides the inline width in base.html */ width: 100% !important; } @@ -252,7 +259,7 @@ a:hover .badge { to bottom, rgba(248, 249, 250, 0) 0%, rgba(248, 249, 250, 1) 50% - ) !important; - border-top: none !important; + ); + border-top: none; pointer-events: none; } diff --git a/media/commitfest/js/commitfest.js b/media/commitfest/js/commitfest.js index a353aef2..c6b826de 100644 --- a/media/commitfest/js/commitfest.js +++ b/media/commitfest/js/commitfest.js @@ -1,23 +1,21 @@ +// biome-ignore lint/correctness/noUnusedVariables: called from templates function verify_reject() { return confirm( 'Are you sure you want to close this patch as Rejected?\n\nThis should only be done when a patch will never be applied - if more work is needed, it should instead be set to "Returned with Feedback" or "Moved to next CF".\n\nSo - are you sure?', ); } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function verify_withdrawn() { return confirm( "Are you sure you want to close this patch as Withdrawn?\n\nThis should only be done when the author voluntarily withdraws the patch.\n\nSo - are you sure?", ); } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function verify_returned() { return confirm( 'Are you sure you want to close this patch as Returned with Feedback?\n\nThis should be done if the patch is expected to be finished at some future time, but not necessarily in the next commitfest. If work is undergoing and expected in the next commitfest, it should instead be set to "Moved to next CF".\n\nSo - are you sure?', ); } -function verify_next() { - return confirm( - 'Are you sure you want to move this patch to the next commitfest?\n\nThis means the patch will be marked as closed in this commitfest, but will automatically be moved to the next one. If no further work is expected on this patch, it should be closed with "Rejected" or "Returned with Feedback" instead.\n\nSo - are you sure?', - ); -} function findLatestThreads() { $("#attachThreadListWrap").addClass("loading"); $("#attachThreadSearchButton").addClass("disabled"); @@ -28,7 +26,7 @@ function findLatestThreads() { .success((data) => { sel = $("#attachThreadList"); sel.find("option").remove(); - $.each(data, (m, i) => { + $.each(data, (_m, i) => { sel.append( $("") .text(`${i.from}: ${i.subj} (${i.date})`) @@ -49,7 +47,7 @@ function browseThreads(attachfunc, closefunc) { $("#attachThreadList").find("option").remove(); $("#attachThreadMessageId").val(""); $("#attachModal").off("hidden.bs.modal"); - $("#attachModal").on("hidden.bs.modal", (e) => { + $("#attachModal").on("hidden.bs.modal", () => { if (closefunc) closefunc(); }); const attachModal = new bootstrap.Modal( @@ -88,6 +86,7 @@ function browseThreads(attachfunc, closefunc) { }); } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function attachThread(cfid, patchid, closefunc) { browseThreads( (msgid) => { @@ -103,6 +102,7 @@ function attachThread(cfid, patchid, closefunc) { ); } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function detachThread(cfid, patchid, msgid) { if ( confirm( @@ -114,10 +114,10 @@ function detachThread(cfid, patchid, msgid) { p: patchid, msg: msgid, }) - .success((data) => { + .success(() => { location.reload(); }) - .fail((data) => { + .fail(() => { alert("Failed to detach thread!"); }); } @@ -166,7 +166,7 @@ function updateAnnotationMessages(threadid) { sel = $("#annotateMessageList"); sel.find("option").remove(); sel.append(''); - $.each(data, (i, m) => { + $.each(data, (_i, m) => { sel.append( ``, ); @@ -176,6 +176,7 @@ function updateAnnotationMessages(threadid) { $("#annotateMessageBody").removeClass("loading"); }); } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function addAnnotation(threadid) { $("#annotateThreadList").find("option").remove(); $("#annotateMessage").val(""); @@ -209,13 +210,14 @@ function addAnnotation(threadid) { location.reload(); } }) - .fail((data) => { + .fail(() => { alert("Failed to annotate message"); $("#annotateMessageBody").removeClass("loading"); }); }); } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function annotateMsgPicked() { const val = $("#annotateMessageList").val(); if (val) { @@ -233,20 +235,22 @@ function annotateChanged() { } } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function deleteAnnotation(annid) { if (confirm("Are you sure you want to delete this annotation?")) { $.post("/ajax/deleteAnnotation/", { id: annid, }) - .success((data) => { + .success(() => { location.reload(); }) - .fail((data) => { + .fail(() => { alert("Failed to delete annotation!"); }); } } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function flagCommitted(committer) { const modal = new bootstrap.Modal(document.getElementById("commitModal")); modal.show(); @@ -267,8 +271,9 @@ function flagCommitted(committer) { return false; } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function sortpatches(sortby) { - const sortkey = Number.parseInt($("#id_sortkey").val()); + const sortkey = Number.parseInt($("#id_sortkey").val(), 10); if (sortkey === sortby) { $("#id_sortkey").val(-sortby); } else if (-sortkey === sortby) { @@ -285,11 +290,12 @@ function toggleButtonCollapse(buttonId, collapseId) { const button = document.getElementById(buttonId); const collapse = document.getElementById(collapseId); button.classList.toggle("active"); - const bsCollapse = new bootstrap.Collapse(collapse, { + new bootstrap.Collapse(collapse, { toggle: true, }); } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function togglePatchFilterButton(buttonId, collapseId) { const collapse = document.getElementById(collapseId); @@ -308,6 +314,7 @@ function togglePatchFilterButton(buttonId, collapseId) { /* * Upstream user search dialog */ +// biome-ignore lint/correctness/noUnusedVariables: called from templates function search_and_store_user() { const modal = new bootstrap.Modal( document.getElementById("searchUserModal"), @@ -330,7 +337,7 @@ function search_and_store_user() { alert(`Failed to import user: ${data}`); } }) - .fail((data, statustxt) => { + .fail((_data, statustxt) => { alert(`Failed to import user: ${statustxt}`); }); @@ -340,6 +347,7 @@ function search_and_store_user() { modal.show(); } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function findUsers() { if (!$("#searchUserSearchField").val()) { alert("No search term specified"); @@ -353,7 +361,7 @@ function findUsers() { .success((data) => { sel = $("#searchUserList"); sel.find("option").remove(); - $.each(data, (i, u) => { + $.each(data, (_i, u) => { sel.append( ``, ); @@ -375,6 +383,7 @@ function searchUserListChanged() { } } +// biome-ignore lint/correctness/noUnusedVariables: called from templates function addGitCheckoutToClipboard(patchId) { navigator.clipboard.writeText(`git remote add commitfest https://github.com/postgresql-cfbot/postgresql.git git fetch commitfest cf/${patchId} @@ -388,11 +397,11 @@ $(document).ready(() => { const tooltipTriggerList = [].slice.call( document.querySelectorAll('[data-bs-toggle="tooltip"]'), ); - const tooltipList = tooltipTriggerList.map( - (tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl), - ); + for (const tooltipTriggerEl of tooltipTriggerList) { + new bootstrap.Tooltip(tooltipTriggerEl); + } - $("button.attachThreadButton").each((i, o) => { + $("button.attachThreadButton").each((_i, o) => { const b = $(o); b.click(() => { $("#attachThreadAttachOnly").val("1"); diff --git a/pgcommitfest/commitfest/admin.py b/pgcommitfest/commitfest/admin.py index cb9ac9f6..caaf2d39 100644 --- a/pgcommitfest/commitfest/admin.py +++ b/pgcommitfest/commitfest/admin.py @@ -1,6 +1,8 @@ from django.contrib import admin from django.forms import widgets +from typing import ClassVar + from .models import ( CfbotBranch, CfbotTask, @@ -63,7 +65,7 @@ class ColorInput(widgets.Input): class TagAdmin(admin.ModelAdmin): # Customize the Tag form with a color picker and soft validation. change_form_template = "change_tag_form.html" - formfield_overrides = { + formfield_overrides: ClassVar[dict] = { ColorField: {"widget": ColorInput}, } diff --git a/pgcommitfest/commitfest/ajax.py b/pgcommitfest/commitfest/ajax.py index 9e0d92e4..09ddf62c 100644 --- a/pgcommitfest/commitfest/ajax.py +++ b/pgcommitfest/commitfest/ajax.py @@ -49,35 +49,32 @@ def mockArchivesAPI(path): def _archivesAPI(suburl, params=None): - if getattr(settings, "MOCK_ARCHIVES", False) and getattr( - settings, "MOCK_ARCHIVE_DATA" - ): + if getattr(settings, "MOCK_ARCHIVES", False) and settings.MOCK_ARCHIVE_DATA: return mockArchivesAPI(suburl) try: resp = requests.get( - "http{0}://{1}:{2}{3}".format( - settings.ARCHIVES_PORT == 443 and "s" or "", - settings.ARCHIVES_SERVER, - settings.ARCHIVES_PORT, - suburl, - ), + f"{'https' if settings.ARCHIVES_PORT == 443 else 'http'}://" + f"{settings.ARCHIVES_SERVER}:{settings.ARCHIVES_PORT}{suburl}", params=params, headers={ "Host": settings.ARCHIVES_HOST, }, timeout=settings.ARCHIVES_TIMEOUT, ) + if resp.status_code == 404: + raise Http404() if resp.status_code != 200: - if resp.status_code == 404: - raise Http404() - raise Exception("JSON call failed: %s" % resp.status_code) + raise Http503( + "Failed to communicate with archives backend: " + f"JSON call failed: {resp.status_code}" + ) return resp.json() - except Http404: - raise - except Exception as e: - raise Http503("Failed to communicate with archives backend: %s" % e) + except requests.RequestException as e: + # This includes invalid JSON, because requests' JSONDecodeError is + # also a RequestException. + raise Http503(f"Failed to communicate with archives backend: {e}") from e def getThreads(request): @@ -95,7 +92,7 @@ def getThreads(request): messages = {} for listname in ("pgsql-hackers", "pgsql-bugs"): - for m in _archivesAPI("/list/%s/latest.json" % listname, params): + for m in _archivesAPI(f"/list/{listname}/latest.json", params): messages.setdefault(m["msgid"], m) r = sorted(messages.values(), key=lambda x: x["date"], reverse=True) @@ -110,13 +107,13 @@ def getMessages(request): thread = MailThread.objects.get(pk=threadid) # Always make a call over to the archives api - r = _archivesAPI("/message-id.json/%s" % thread.messageid) + r = _archivesAPI(f"/message-id.json/{thread.messageid}") return sorted(r, key=lambda x: x["date"], reverse=True) def refresh_single_thread(thread): r = sorted( - _archivesAPI("/message-id.json/%s" % thread.messageid), key=lambda x: x["date"] + _archivesAPI(f"/message-id.json/{thread.messageid}"), key=lambda x: x["date"] ) if thread.latestmsgid != r[-1]["msgid"]: # There is now a newer mail in the thread! @@ -148,7 +145,7 @@ def annotateMessage(request): # Get the subject, author and date from the archives # We only have an API call to get the whole thread right now, so # do that, and then find our entry in it. - r = _archivesAPI("/message-id.json/%s" % thread.messageid) + r = _archivesAPI(f"/message-id.json/{thread.messageid}") for m in r: if m["msgid"] == msgid: annotation = MailThreadAnnotation( @@ -166,8 +163,7 @@ def annotateMessage(request): PatchHistory( patch=p, by=request.user, - what='Added annotation "%s" to %s' - % (textwrap.shorten(msg, 100), msgid), + what=f'Added annotation "{textwrap.shorten(msg, 100)}" to {msgid}', ).save_and_notify() p.set_modified() p.save() @@ -184,8 +180,7 @@ def deleteAnnotation(request): PatchHistory( patch=p, by=request.user, - what='Deleted annotation "%s" from %s' - % (annotation.annotationtext, annotation.msgid), + what=f'Deleted annotation "{annotation.annotationtext}" from {annotation.msgid}', ).save_and_notify() p.set_modified() p.save() @@ -226,7 +221,7 @@ def attachThread(request): def doAttachThread(cf, patch, msgid, user): # Note! Must be called in an open transaction! - r = sorted(_archivesAPI("/message-id.json/%s" % msgid), key=lambda x: x["date"]) + r = sorted(_archivesAPI(f"/message-id.json/{msgid}"), key=lambda x: x["date"]) # We have the full thread metadata - using the first and last entry, # construct a new mailthread in our own model. # First, though, check if it's already there. @@ -264,7 +259,7 @@ def doAttachThread(cf, patch, msgid, user): parse_and_add_attachments(r, m) PatchHistory( - patch=patch, by=user, what="Attached mail thread %s" % r[0]["msgid"] + patch=patch, by=user, what=f"Attached mail thread {r[0]['msgid']}" ).save_and_notify() patch.update_lastmail() patch.set_modified() @@ -283,7 +278,7 @@ def detachThread(request): PatchHistory( patch=patch, by=request.user, - what="Detached mail thread %s" % request.POST["msg"], + what=f"Detached mail thread {request.POST['msg']}", ).save_and_notify() patch.update_lastmail() patch.set_modified() diff --git a/pgcommitfest/commitfest/apiv1.py b/pgcommitfest/commitfest/apiv1.py index 9d6109ad..d5c19fec 100644 --- a/pgcommitfest/commitfest/apiv1.py +++ b/pgcommitfest/commitfest/apiv1.py @@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404 import json -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from .models import ( CommitFest, @@ -18,7 +18,7 @@ def datetime_serializer(obj): # datetime must be checked before date, since datetime is a subclass of date if isinstance(obj, datetime): - return obj.replace(tzinfo=timezone.utc).isoformat() + return obj.replace(tzinfo=UTC).isoformat() if isinstance(obj, date): return obj.isoformat() @@ -51,7 +51,7 @@ def commitfestst_that_need_ci(request): # We continue to run CI on the previous commitfest for a week after it ends # to give people some time to move patches over to the next one. - if cfs["previous"].enddate <= datetime.now(timezone.utc).date() - timedelta(days=7): + if cfs["previous"].enddate <= datetime.now(UTC).date() - timedelta(days=7): del cfs["previous"] del cfs["next_open"] diff --git a/pgcommitfest/commitfest/feeds.py b/pgcommitfest/commitfest/feeds.py index 83c47bfc..ef71f345 100644 --- a/pgcommitfest/commitfest/feeds.py +++ b/pgcommitfest/commitfest/feeds.py @@ -18,12 +18,12 @@ class ActivityFeed(Feed): link = "https://commitfest.postgresql.org/" def __init__(self, activity, cf, *args, **kwargs): - super(ActivityFeed, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.activity = activity if cf: self.cfid = cf.id self.title = self.description = ( - "PostgreSQL Commitfest {0} Activity Log".format(cf.name) + f"PostgreSQL Commitfest {cf.name} Activity Log" ) else: self.cfid = None diff --git a/pgcommitfest/commitfest/forms.py b/pgcommitfest/commitfest/forms.py index 444905b1..fa62bada 100644 --- a/pgcommitfest/commitfest/forms.py +++ b/pgcommitfest/commitfest/forms.py @@ -4,13 +4,15 @@ from django.forms.widgets import HiddenInput from django.http import Http404 -from .ajax import _archivesAPI +from typing import ClassVar + +from .ajax import Http503, _archivesAPI from .models import Patch, PatchOnCommitFest, Tag, TargetVersion from .widgets import ThreadPickWidget class CommitFestFilterForm(forms.Form): - selectize_fields = { + selectize_fields: ClassVar[dict] = { "author": "/lookups/user", "reviewer": "/lookups/user", "tag": None, @@ -25,7 +27,7 @@ class CommitFestFilterForm(forms.Form): sortkey = forms.IntegerField(required=False) def __init__(self, data, commitfest=None, *args, **kwargs): - super(CommitFestFilterForm, self).__init__(data, *args, **kwargs) + super().__init__(data, *args, **kwargs) self.commitfest = commitfest # Update selectize_fields with cf parameter if commitfest is provided @@ -85,7 +87,7 @@ def __init__(self, data, commitfest=None, *args, **kwargs): class PatchForm(forms.ModelForm): - selectize_fields = { + selectize_fields: ClassVar[dict] = { "authors": "/lookups/user", "reviewers": "/lookups/user", "tags": None, @@ -104,16 +106,11 @@ class Meta: ) def __init__(self, *args, **kwargs): - super(PatchForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields["authors"].help_text = "Enter part of name to see list" self.fields["reviewers"].help_text = "Enter part of name to see list" self.fields["committer"].label_from_instance = lambda x: ( - "%s %s (%s)" - % ( - x.user.first_name, - x.user.last_name, - x.user.username, - ) + f"{x.user.first_name} {x.user.last_name} ({x.user.username})" ) self.fields["authors"].widget.attrs["class"] = "add-user-picker" @@ -162,7 +159,7 @@ def __init__(self, *args, **kwargs): class NewPatchForm(PatchForm): # Put threadmsgid first - field_order = ["threadmsgid"] + field_order: ClassVar[list] = ["threadmsgid"] threadmsgid = forms.CharField( max_length=200, @@ -180,7 +177,7 @@ class NewPatchForm(PatchForm): def __init__(self, *args, **kwargs): request = kwargs.pop("request", None) - super(NewPatchForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if request: self.fields["authors"].queryset = User.objects.filter(pk=request.user.id) @@ -206,10 +203,10 @@ def clean(self): def clean_threadmsgid(self): try: - _archivesAPI("/message-id.json/%s" % self.cleaned_data["threadmsgid"]) + _archivesAPI(f"/message-id.json/{self.cleaned_data['threadmsgid']}") except Http404: raise ValidationError("Message not found in archives") - except Exception: + except Http503: raise ValidationError("Error in API call to validate thread") return self.cleaned_data["threadmsgid"] @@ -222,4 +219,4 @@ class BulkEmailForm(forms.Form): confirm = forms.BooleanField(required=True, label="Check to confirm sending") def __init__(self, *args, **kwargs): - super(BulkEmailForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) diff --git a/pgcommitfest/commitfest/migrations/0001_initial.py b/pgcommitfest/commitfest/migrations/0001_initial.py index 3577d436..851da57a 100644 --- a/pgcommitfest/commitfest/migrations/0001_initial.py +++ b/pgcommitfest/commitfest/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models @@ -318,10 +315,10 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name="patchoncommitfest", - unique_together=set([("patch", "commitfest")]), + unique_together={("patch", "commitfest")}, ), migrations.AlterUniqueTogether( name="mailthreadattachment", - unique_together=set([("mailthread", "messageid")]), + unique_together={("mailthread", "messageid")}, ), ] diff --git a/pgcommitfest/commitfest/migrations/0002_notifications.py b/pgcommitfest/commitfest/migrations/0002_notifications.py index 450ddfbe..b85bac82 100644 --- a/pgcommitfest/commitfest/migrations/0002_notifications.py +++ b/pgcommitfest/commitfest/migrations/0002_notifications.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff --git a/pgcommitfest/commitfest/migrations/0003_withdrawn_status.py b/pgcommitfest/commitfest/migrations/0003_withdrawn_status.py index 2f6a5f7d..abdc9afe 100644 --- a/pgcommitfest/commitfest/migrations/0003_withdrawn_status.py +++ b/pgcommitfest/commitfest/migrations/0003_withdrawn_status.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/pgcommitfest/commitfest/migrations/0004_target_version.py b/pgcommitfest/commitfest/migrations/0004_target_version.py index ad546109..45919fe7 100644 --- a/pgcommitfest/commitfest/migrations/0004_target_version.py +++ b/pgcommitfest/commitfest/migrations/0004_target_version.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.17 on 2019-02-06 19:43 -from __future__ import unicode_literals import django.db.models.deletion from django.db import migrations, models diff --git a/pgcommitfest/commitfest/migrations/0005_history_dateindex.py b/pgcommitfest/commitfest/migrations/0005_history_dateindex.py index 4316f212..e58cf9d3 100644 --- a/pgcommitfest/commitfest/migrations/0005_history_dateindex.py +++ b/pgcommitfest/commitfest/migrations/0005_history_dateindex.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.17 on 2019-02-06 20:36 -from __future__ import unicode_literals from django.db import migrations, models diff --git a/pgcommitfest/commitfest/models.py b/pgcommitfest/commitfest/models.py index c7b8fb4f..6bb02844 100644 --- a/pgcommitfest/commitfest/models.py +++ b/pgcommitfest/commitfest/models.py @@ -4,7 +4,8 @@ from django.db.models import Exists, OuterRef, Q from django.shortcuts import get_object_or_404 -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta +from typing import ClassVar from pgcommitfest.mailqueue.util import send_template_mail from pgcommitfest.userprofile.models import UserProfile @@ -30,11 +31,7 @@ def __str__(self): @property def fullname(self): - return "%s %s (%s)" % ( - self.user.first_name, - self.user.last_name, - self.user.username, - ) + return f"{self.user.first_name} {self.user.last_name} ({self.user.username})" class Meta: ordering = ("user__last_name", "user__first_name") @@ -64,11 +61,11 @@ class CommitFest(models.Model): @property def statusstring(self): - return [v for k, v in self._STATUS_CHOICES if k == self.status][0] + return next(v for k, v in self._STATUS_CHOICES if k == self.status) @property def periodstring(self): - return "{0} – {1}".format(self.startdate, self.enddate) + return f"{self.startdate} – {self.enddate}" @property def last_open_date(self): @@ -83,7 +80,7 @@ def dev_cycle(self) -> int: @property def title(self): - return "Commitfest %s" % self.name + return f"Commitfest {self.name}" @property def is_closed(self): @@ -249,15 +246,12 @@ def _are_relevant_commitfests_up_to_date(cfs, current_date): if cfs["open"].startdate <= current_date: return False - if not cfs["draft"] or cfs["draft"].enddate < current_date: - return False - - return True + return bool(cfs["draft"]) and cfs["draft"].enddate >= current_date @classmethod def _refresh_relevant_commitfests(cls, for_update): cfs = CommitFest.relevant_commitfests(for_update=for_update, refresh=False) - current_date = datetime.now(timezone.utc).date() + current_date = datetime.now(UTC).date() if cls._are_relevant_commitfests_up_to_date(cfs, current_date): return cfs @@ -442,7 +436,7 @@ class TargetVersion(models.Model): version = models.CharField(max_length=8, blank=False, null=False, unique=True) class Meta: - ordering = [ + ordering: ClassVar[list] = [ "-version", ] @@ -534,7 +528,7 @@ class Patch(models.Model, DiffableModel): # that's attached to this message. lastmail = models.DateTimeField(blank=True, null=True) - map_manytomany_for_diff = { + map_manytomany_for_diff: ClassVar[dict] = { "authors": "authors_string", "reviewers": "reviewers_string", } @@ -558,17 +552,14 @@ def can_remove_all_reviewers(self): @property def authors_string(self): return ", ".join( - [ - "%s %s (%s)" % (a.first_name, a.last_name, a.username) - for a in self.authors.all() - ] + [f"{a.first_name} {a.last_name} ({a.username})" for a in self.authors.all()] ) @property def reviewers_string(self): return ", ".join( [ - "%s %s (%s)" % (a.first_name, a.last_name, a.username) + f"{a.first_name} {a.last_name} ({a.username})" for a in self.reviewers.all() ] ) @@ -635,11 +626,11 @@ def move( new_poc, _ = PatchOnCommitFest.objects.update_or_create( patch=current_poc.patch, commitfest=to_cf, - defaults=dict( - status=old_status, - enterdate=now, - leavedate=now if was_closed else None, - ), + defaults={ + "status": old_status, + "enterdate": now, + "leavedate": now if was_closed else None, + }, ) new_poc.save() self.set_modified() @@ -702,7 +693,7 @@ class PatchOnCommitFest(models.Model): (STATUS_RETURNED, "danger"), (STATUS_WITHDRAWN, "danger"), ) - OPEN_STATUSES = [STATUS_REVIEW, STATUS_AUTHOR, STATUS_COMMITTER] + OPEN_STATUSES: ClassVar[list] = [STATUS_REVIEW, STATUS_AUTHOR, STATUS_COMMITTER] @classmethod def OPEN_STATUS_CHOICES(cls): @@ -737,7 +728,7 @@ def needs_committer(self): @property def statusstring(self): - return [v for k, v in self._STATUS_CHOICES if k == self.status][0] + return next(v for k, v in self._STATUS_CHOICES if k == self.status) def to_json(self): return { @@ -791,14 +782,14 @@ def by_string(self): if self.by_cfbot: return "CFbot" - return "%s %s (%s)" % (self.by.first_name, self.by.last_name, self.by.username) + return f"{self.by.first_name} {self.by.last_name} ({self.by.username})" def __str__(self): - return "%s - %s" % (self.patch.name, self.date) + return f"{self.patch.name} - {self.date}" class Meta: ordering = ("-date",) - constraints = [ + constraints: ClassVar[list] = [ models.CheckConstraint( check=(models.Q(by_cfbot=True) & models.Q(by__isnull=True)) | (models.Q(by_cfbot=False) & models.Q(by__isnull=False)), @@ -952,11 +943,7 @@ class MailThreadAnnotation(models.Model): @property def user_string(self): - return "%s %s (%s)" % ( - self.user.first_name, - self.user.last_name, - self.user.username, - ) + return f"{self.user.first_name} {self.user.last_name} ({self.user.username})" class Meta: ordering = ("date",) @@ -976,7 +963,7 @@ class PendingNotification(models.Model): class CfbotBranch(models.Model): - STATUS_CHOICES = [ + STATUS_CHOICES: ClassVar[list] = [ ("testing", "Testing"), ("finished", "Finished"), ("failed", "Failed"), @@ -1015,11 +1002,11 @@ def save(self, *args, **kwargs): if not self.commit_id: self.commit_id = None - super(CfbotBranch, self).save(*args, **kwargs) + super().save(*args, **kwargs) class CfbotTask(models.Model): - STATUS_CHOICES = [ + STATUS_CHOICES: ClassVar[list] = [ ("CREATED", "Created"), ("PAUSED", "Paused"), ("NEEDS_APPROVAL", "Needs Approval"), @@ -1056,7 +1043,7 @@ class CfbotTask(models.Model): modified = models.DateTimeField(auto_now=True) class Meta: - constraints = [ + constraints: ClassVar[list] = [ models.UniqueConstraint( fields=["branch_id", "position"], name="commitfest_cfbottask_branch_position_unique", diff --git a/pgcommitfest/commitfest/reports.py b/pgcommitfest/commitfest/reports.py index e4191e16..fe176b33 100644 --- a/pgcommitfest/commitfest/reports.py +++ b/pgcommitfest/commitfest/reports.py @@ -50,7 +50,7 @@ def authorstats(request, cfid): "report": cursor.fetchall(), "title": "Author stats", "breadcrumbs": [ - {"title": cf.title, "href": "/%s/" % cf.pk}, + {"title": cf.title, "href": f"/{cf.pk}/"}, ], }, ) diff --git a/pgcommitfest/commitfest/templates/selectize_js.html b/pgcommitfest/commitfest/templates/selectize_js.html index 6da34847..3c0e1fb8 100644 --- a/pgcommitfest/commitfest/templates/selectize_js.html +++ b/pgcommitfest/commitfest/templates/selectize_js.html @@ -34,7 +34,7 @@ // Add footer hint if there are more options than visible (due to search filtering or scrolling) if (totalOptions > 6 && !$dropdown.find('.selectize-scroll-hint').length) { - $dropdown.append('