diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4f3a07e365..c2381a986d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,67 +47,15 @@ }, { "name": "Set up packages", - "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n flake8 pylint pylint-django pylint-plugin-utils isort black autopep8 \\\n coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" + "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" }, { "name": "Copy secret.py", "run": "cp intranet/settings/ci_secret.py intranet/settings/secret.py" }, { - "name": "Run flake8", - "run": "flake8 --max-line-length 150 --exclude=*/migrations/* intranet/ scripts/ docs/ *.py" - }, - { - "name": "Run pylintx", - "run": "pylint --jobs=0 --disable=fixme,broad-exception-caught,broad-exception-raised,unsupported-binary-operation,global-statement,attribute-defined-outside-init,cyclic-import,consider-using-f-string --django-settings-module=intranet.settings intranet/" - } - ] - }, - "formatting": { - "runs-on": "ubuntu-latest", - "strategy": { - "matrix": { - "python-version": [ - 3.8 - ] - }, - "fail-fast": false - }, - "steps": [ - { - "name": "Set up repo", - "uses": "actions/checkout@v2" - }, - { - "name": "Set up Python ${{ matrix.python-version }}", - "uses": "actions/setup-python@v2", - "with": { - "python-version": "${{ matrix.python-version }}" - } - }, - { - "name": "Set up pip cache", - "uses": "actions/cache@v2", - "with": { - "path": "~/.cache/pip", - "key": "pip-${{ matrix.python-version }}" - } - }, - { - "name": "Set up packages", - "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n flake8 pylint pylint-django pylint-plugin-utils isort black autopep8 \\\n coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" - }, - { - "name": "Copy secret.py", - "run": "cp intranet/settings/ci_secret.py intranet/settings/secret.py" - }, - { - "name": "Format code", - "run": "./scripts/build_ensure_no_changes.sh ./scripts/format.sh" - }, - { - "name": "Format static files and templates", - "run": "./scripts/build_ensure_no_changes.sh ./scripts/static_templates_format.sh" + "name": "Run pre-commit", + "run": "pre-commit run --all-files" } ] }, @@ -146,7 +94,7 @@ }, { "name": "Set up packages", - "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n flake8 pylint pylint-django pylint-plugin-utils isort black autopep8 \\\n coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" + "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" }, { "name": "Copy secret.py", @@ -251,7 +199,7 @@ }, { "name": "Set up packages", - "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n flake8 pylint pylint-django pylint-plugin-utils isort black autopep8 \\\n coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" + "run": "set -e\n\npip install -U pip setuptools wheel\npip install -U \\\n pre-commit coveralls pyyaml pytest-django\npip install -U -r requirements.txt\n\necho \"PATH=$PATH\" >> $GITHUB_ENV\n" }, { "name": "Copy secret.py", @@ -328,7 +276,6 @@ "finish_success": { "needs": [ "linting", - "formatting", "build", "tests" ], diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..df754f70ded --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,43 @@ +default_stages: [commit, push] +fail_fast: false +exclude: ^(intranet/middleware|intranet/apps/.*/migrations|intranet/static/.*vendor/.*|intranet/.*/stubs|Ion.egg-info|\.github) + +# This is for pre-commit ci +# After adding pre-commit ci, the "linting" stage of the tests +# can be safely deleted +ci: + autofix_commit_msg: | + chore: auto fixes from pre-commit hooks + + for more information, see https://pre-commit.ci + autoupdate_commit_msg: 'build: pre-commit autoupdate' + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-ast + name: Validate Python + - id: trailing-whitespace + - id: mixed-line-ending + args: ["--fix=lf"] + - id: check-toml + - id: check-yaml + - id: detect-private-key + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.5 + hooks: + - id: ruff + args: [ "--fix", "--exit-non-zero-on-fix" ] + name: ruff lint + files: ^intranet/apps/.* + - id: ruff-format + files: ^intranet/apps/.* + - repo: https://github.com/codespell-project/codespell + rev: v2.2.5 + hooks: + - id: codespell + files: ^.*\.(py|md|rst)$ + # TODO: Remove after python version >= 3.11 + additional_dependencies: + - tomli diff --git a/Ion.egg-info/SOURCES.txt b/Ion.egg-info/SOURCES.txt index e9e6da15f83..d292a1d553a 100644 --- a/Ion.egg-info/SOURCES.txt +++ b/Ion.egg-info/SOURCES.txt @@ -4,6 +4,7 @@ .gitattributes .gitignore .isort.cfg +.pre-commit-config.yaml .pylintrc .style.yapf CONTRIBUTING.md diff --git a/ci/spec.yml b/ci/spec.yml index f12f1670462..7a379b52643 100644 --- a/ci/spec.yml +++ b/ci/spec.yml @@ -57,8 +57,7 @@ env: pip install -U pip setuptools wheel pip install -U \ - flake8 pylint pylint-django pylint-plugin-utils isort black autopep8 \ - coveralls pyyaml pytest-django + pre-commit coveralls pyyaml pytest-django pip install -U -r requirements.txt echo "PATH=$PATH" >> $GITHUB_ENV @@ -83,30 +82,8 @@ jobs: - *setup_packages - *copy_secret - - name: Run flake8 - run: 'flake8 --max-line-length 150 --exclude=*/migrations/* intranet/ scripts/ docs/ *.py' - - name: Run pylintx - run: pylint --jobs=0 --disable=fixme,broad-exception-caught,broad-exception-raised,unsupported-binary-operation,global-statement,attribute-defined-outside-init,cyclic-import,consider-using-f-string --django-settings-module=intranet.settings intranet/ - - formatting: - runs-on: ubuntu-latest - - strategy: - matrix: - python-version: *python_versions - fail-fast: false - - steps: - - *repo_setup - - *python_setup - - *setup_pip_cache - - *setup_packages - - *copy_secret - - - name: Format code - run: ./scripts/build_ensure_no_changes.sh ./scripts/format.sh - - name: Format static files and templates - run: ./scripts/build_ensure_no_changes.sh ./scripts/static_templates_format.sh + - name: Run pre-commit + run: 'pre-commit run --all-files' build: runs-on: ubuntu-latest @@ -256,7 +233,6 @@ jobs: finish_success: needs: - linting - - formatting - build - tests diff --git a/intranet/apps/announcements/notifications.py b/intranet/apps/announcements/notifications.py index 3e8ab893f2f..51e6d486842 100644 --- a/intranet/apps/announcements/notifications.py +++ b/intranet/apps/announcements/notifications.py @@ -3,14 +3,13 @@ import re import requests -from requests_oauthlib import OAuth1 -from sentry_sdk import capture_exception - from django.conf import settings from django.contrib import messages from django.contrib.auth import get_user_model from django.core import exceptions from django.urls import reverse +from requests_oauthlib import OAuth1 +from sentry_sdk import capture_exception from ...utils.date import get_senior_graduation_year from ..notifications.tasks import email_send_task diff --git a/intranet/apps/announcements/views.py b/intranet/apps/announcements/views.py index b0c83c97eb1..b1f92231e0d 100644 --- a/intranet/apps/announcements/views.py +++ b/intranet/apps/announcements/views.py @@ -15,8 +15,13 @@ from ..groups.models import Group from .forms import AnnouncementAdminForm, AnnouncementEditForm, AnnouncementForm, AnnouncementRequestForm from .models import Announcement, AnnouncementRequest -from .notifications import (admin_request_announcement_email, announcement_approved_email, announcement_posted_email, announcement_posted_twitter, - request_announcement_email) +from .notifications import ( + admin_request_announcement_email, + announcement_approved_email, + announcement_posted_email, + announcement_posted_twitter, + request_announcement_email, +) logger = logging.getLogger(__name__) diff --git a/intranet/apps/api/authentication.py b/intranet/apps/api/authentication.py index 98540e01ec4..a6fd99fd524 100644 --- a/intranet/apps/api/authentication.py +++ b/intranet/apps/api/authentication.py @@ -1,7 +1,6 @@ -from rest_framework import authentication, exceptions - from django.contrib import auth from django.views.decorators.debug import sensitive_variables +from rest_framework import authentication, exceptions class ApiBasicAuthentication(authentication.BasicAuthentication): diff --git a/intranet/apps/api/tests.py b/intranet/apps/api/tests.py index 84089ce95e5..0b20474992f 100644 --- a/intranet/apps/api/tests.py +++ b/intranet/apps/api/tests.py @@ -2,12 +2,11 @@ import json import urllib.parse -from oauth2_provider.models import AccessToken, get_application_model -from oauth2_provider.settings import oauth2_settings - from django.contrib.auth import get_user_model from django.urls import reverse from django.utils import timezone +from oauth2_provider.models import AccessToken, get_application_model +from oauth2_provider.settings import oauth2_settings from ...test.ion_test import IonTestCase from ...utils.date import get_senior_graduation_year diff --git a/intranet/apps/api/utils.py b/intranet/apps/api/utils.py index 9fe3abc8afa..6726e24492a 100644 --- a/intranet/apps/api/utils.py +++ b/intranet/apps/api/utils.py @@ -1,12 +1,11 @@ import logging +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist from rest_framework import status from rest_framework.response import Response from rest_framework.views import exception_handler -from django.conf import settings -from django.core.exceptions import ObjectDoesNotExist - from ..eighth import exceptions as eighth_exceptions logger = logging.getLogger(__name__) diff --git a/intranet/apps/api/views.py b/intranet/apps/api/views.py index a31f80714cd..1b29a340a20 100644 --- a/intranet/apps/api/views.py +++ b/intranet/apps/api/views.py @@ -1,11 +1,10 @@ from collections import OrderedDict +from django.urls import reverse from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny from rest_framework.response import Response -from django.urls import reverse - def perma_reverse(request, view, *args, **kwargs): return request.build_absolute_uri(reverse(view, *args, **kwargs)) diff --git a/intranet/apps/auth/backends.py b/intranet/apps/auth/backends.py index d276899b79e..84eadd1767f 100644 --- a/intranet/apps/auth/backends.py +++ b/intranet/apps/auth/backends.py @@ -2,11 +2,10 @@ import logging import pam -from prometheus_client import Counter, Summary - from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.hashers import check_password +from prometheus_client import Counter, Summary logger = logging.getLogger(__name__) diff --git a/intranet/apps/auth/helpers.py b/intranet/apps/auth/helpers.py index d6655b04d6b..f2f52bb5607 100644 --- a/intranet/apps/auth/helpers.py +++ b/intranet/apps/auth/helpers.py @@ -2,7 +2,6 @@ import re import pexpect - from django.conf import settings logger = logging.getLogger(__name__) diff --git a/intranet/apps/auth/management/commands/grant_admin.py b/intranet/apps/auth/management/commands/grant_admin.py index 1305039b771..b20a2e79278 100644 --- a/intranet/apps/auth/management/commands/grant_admin.py +++ b/intranet/apps/auth/management/commands/grant_admin.py @@ -12,6 +12,6 @@ def add_arguments(self, parser): parser.add_argument("admin_group") def handle(self, *args, **options): - g = Group.objects.get_or_create(name="admin_%s" % options["admin_group"])[0] + g = Group.objects.get_or_create(name=f"admin_{options['admin_group']}")[0] get_user_model().objects.get_or_create(username=options["username"])[0].groups.add(g) - self.stdout.write("Added %s to %s" % (options["username"], options["admin_group"])) + self.stdout.write(f"Added {options['username']} to {options['admin_group']}") diff --git a/intranet/apps/auth/views.py b/intranet/apps/auth/views.py index 0adc9d79625..7f684ca51a6 100644 --- a/intranet/apps/auth/views.py +++ b/intranet/apps/auth/views.py @@ -6,7 +6,6 @@ from typing import Container, Tuple from dateutil.relativedelta import relativedelta - from django.conf import settings from django.contrib import messages from django.contrib.auth import authenticate, get_user_model, login, logout @@ -29,6 +28,7 @@ from ..events.models import Event from ..schedule.views import schedule_context from ..sessionmgmt.helpers import trust_session + # Load these so the Prometheus metrics get added from . import backends, signals # pylint: disable=unused-import # noqa F401 from .forms import AuthenticateForm diff --git a/intranet/apps/bus/consumers.py b/intranet/apps/bus/consumers.py index 4f1c1a4f0b3..fe8617f2711 100644 --- a/intranet/apps/bus/consumers.py +++ b/intranet/apps/bus/consumers.py @@ -2,7 +2,6 @@ from asgiref.sync import async_to_sync from channels.generic.websocket import JsonWebsocketConsumer - from django.conf import settings from django.utils import timezone diff --git a/intranet/apps/cslapps/views.py b/intranet/apps/cslapps/views.py index 1742fa200c6..4b6daaa4689 100644 --- a/intranet/apps/cslapps/views.py +++ b/intranet/apps/cslapps/views.py @@ -1,8 +1,7 @@ -from oauth2_provider.models import AccessToken - from django.contrib import messages from django.contrib.auth.decorators import login_required from django.shortcuts import redirect +from oauth2_provider.models import AccessToken from ..auth.decorators import deny_restricted from .models import App diff --git a/intranet/apps/dataimport/management/commands/import_photos.py b/intranet/apps/dataimport/management/commands/import_photos.py index 06d5527f93d..83b2241ba3e 100644 --- a/intranet/apps/dataimport/management/commands/import_photos.py +++ b/intranet/apps/dataimport/management/commands/import_photos.py @@ -4,10 +4,9 @@ import sys from pathlib import Path -from PIL import Image - from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand +from PIL import Image from intranet.apps.users.models import Photo diff --git a/intranet/apps/dataimport/management/commands/import_tj_star.py b/intranet/apps/dataimport/management/commands/import_tj_star.py index 2c7ad84b26e..7ed5d2ad37c 100644 --- a/intranet/apps/dataimport/management/commands/import_tj_star.py +++ b/intranet/apps/dataimport/management/commands/import_tj_star.py @@ -27,7 +27,7 @@ def add_arguments(self, parser): parser.add_argument("filename") def handle(self, *args, **options): - with open(options["filename"], "r", encoding="utf-8") as csv_file: + with open(options["filename"], encoding="utf-8") as csv_file: data = csv.DictReader(csv_file) for idx, row in enumerate(data): diff --git a/intranet/apps/dataimport/tests.py b/intranet/apps/dataimport/tests.py index 9a79b1a6bc8..b4f641d14a5 100755 --- a/intranet/apps/dataimport/tests.py +++ b/intranet/apps/dataimport/tests.py @@ -3,7 +3,6 @@ from unittest.mock import mock_open, patch import pytz - from django.contrib.auth import get_user_model from django.core.management import CommandError, call_command from django.utils import timezone diff --git a/intranet/apps/eighth/admin.py b/intranet/apps/eighth/admin.py index 30d61c75461..21be4a8603e 100644 --- a/intranet/apps/eighth/admin.py +++ b/intranet/apps/eighth/admin.py @@ -1,6 +1,5 @@ -from simple_history.admin import SimpleHistoryAdmin - from django.contrib import admin +from simple_history.admin import SimpleHistoryAdmin from .models import EighthActivity, EighthBlock, EighthRoom, EighthScheduledActivity, EighthSignup, EighthSponsor diff --git a/intranet/apps/eighth/exceptions.py b/intranet/apps/eighth/exceptions.py index 7750f5d4bec..f7bcea8038e 100644 --- a/intranet/apps/eighth/exceptions.py +++ b/intranet/apps/eighth/exceptions.py @@ -1,8 +1,7 @@ from collections import namedtuple -from rest_framework.status import HTTP_403_FORBIDDEN - from django.http import HttpResponse +from rest_framework.status import HTTP_403_FORBIDDEN m = namedtuple("Message", ["regular", "admin"]) diff --git a/intranet/apps/eighth/forms/admin/activities.py b/intranet/apps/eighth/forms/admin/activities.py index 9e839d711e2..0f4d8953252 100644 --- a/intranet/apps/eighth/forms/admin/activities.py +++ b/intranet/apps/eighth/forms/admin/activities.py @@ -107,7 +107,8 @@ def __init__(self, *args, label="Activities", block=None, **kwargs): # pylint: activity_ids = ( EighthScheduledActivity.objects.exclude(activity__deleted=True) # .exclude(cancelled=True) - .filter(block=block).values_list("activity__id", flat=True) + .filter(block=block) + .values_list("activity__id", flat=True) ) queryset = EighthActivity.objects.filter(id__in=activity_ids).order_by("name") else: diff --git a/intranet/apps/eighth/models.py b/intranet/apps/eighth/models.py index 209f4ece0c1..6fca981ebe8 100644 --- a/intranet/apps/eighth/models.py +++ b/intranet/apps/eighth/models.py @@ -5,9 +5,6 @@ from typing import Collection, Iterable, List, Optional, Union from cacheops import invalidate_obj -from sentry_sdk import add_breadcrumb, capture_exception -from simple_history.models import HistoricalRecords - from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.models import Group as DjangoGroup @@ -18,6 +15,8 @@ from django.db.models import Count, Manager, Q, QuerySet from django.http.request import HttpRequest from django.utils import formats, timezone +from sentry_sdk import add_breadcrumb, capture_exception +from simple_history.models import HistoricalRecords from ...utils.date import get_date_range_this_year, is_current_year from ...utils.deletion import set_historical_user @@ -733,9 +732,7 @@ class Meta: class EighthScheduledActivityManager(Manager): """Model Manager for EighthScheduledActivity.""" - def for_sponsor( - self, sponsor: EighthSponsor, include_cancelled: bool = False - ) -> Union[QuerySet, Collection["EighthScheduledActivity"]]: # pylint: disable=unsubscriptable-object + def for_sponsor(self, sponsor: EighthSponsor, include_cancelled: bool = False) -> Union[QuerySet, Collection["EighthScheduledActivity"]]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of EighthScheduledActivities where the given EighthSponsor is sponsoring. If a sponsorship is defined in an EighthActivity, it may be overridden @@ -1003,9 +1000,7 @@ def has_open_passes(self) -> bool: """ return self.eighthsignup_set.filter(after_deadline=True, pass_accepted=False).exists() - def _get_viewable_members( - self, user: "get_user_model()" - ) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def _get_viewable_members(self, user: "get_user_model()") -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object """Get an unsorted QuerySet of the members that you have permission to view. Args: user: The user who is attempting to view the member list. @@ -1020,9 +1015,7 @@ def _get_viewable_members( q |= Q(id=user.id) return self.members.filter(q) - def get_viewable_members( - self, user: "get_user_model()" = None - ) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def get_viewable_members(self, user: "get_user_model()" = None) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of the members that you have permission to view, sorted alphabetically. Args: user: The user who is attempting to view the member list. @@ -1041,9 +1034,7 @@ def get_viewable_members_serializer(self, request) -> Union[QuerySet, Collection """ return self._get_viewable_members(request.user) - def get_hidden_members( - self, user: "get_user_model()" = None - ) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object + def get_hidden_members(self, user: "get_user_model()" = None) -> Union[QuerySet, Collection["get_user_model()"]]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of the members that you do not have permission to view. Args: user: The user who is attempting to view the member list. @@ -1296,7 +1287,7 @@ def add_user( if not existing_signup.scheduled_activity.is_both_blocks(): add_breadcrumb( category="eighth-signup", - message=f"Switching user {user.id} from single-block activity {existing_signup.scheduled_activity.activity.id} to single-block activity {self.activity.id} in block {self.block.id}", # pylint: disable=line-too-long # noqa: E501 + message=f"Switching user {user.id} from single-block activity {existing_signup.scheduled_activity.activity.id} to single-block activity {self.activity.id} in block {self.block.id}", # pylint: disable=line-too-long level="debug", ) @@ -1352,7 +1343,7 @@ def add_user( add_breadcrumb( category="eighth-signup", - message=f"Switching user {user.id} from dual-block activity {existing_signup.scheduled_activity.activity.id} to single-block activity {self.activity.id} in block {self.block.id}", # pylint: disable=line-too-long # noqa: E501 + message=f"Switching user {user.id} from dual-block activity {existing_signup.scheduled_activity.activity.id} to single-block activity {self.activity.id} in block {self.block.id}", # pylint: disable=line-too-long level="debug", ) @@ -1429,7 +1420,7 @@ def add_user( for signup in existing_signups: add_breadcrumb( category="eighth-signup", - message=f"User {user.id}: original activity for block {signup.scheduled_activity.block.id}: {signup.scheduled_activity.activity.id}", # pylint: disable=line-too-long # noqa: E501 + message=f"User {user.id}: original activity for block {signup.scheduled_activity.block.id}: {signup.scheduled_activity.activity.id}", # pylint: disable=line-too-long level="debug", ) @@ -1747,9 +1738,7 @@ class Meta: class EighthWaitlistManager(Manager): """Model manager for EighthWaitlist.""" - def get_next_waitlist( - self, activity: EighthScheduledActivity - ) -> Union[QuerySet, Collection["EighthWaitlist"]]: # pylint: disable=unsubscriptable-object + def get_next_waitlist(self, activity: EighthScheduledActivity) -> Union[QuerySet, Collection["EighthWaitlist"]]: # pylint: disable=unsubscriptable-object """Returns a QuerySet of all the EighthWaitlist objects for the given activity, ordered by signup time. Args: diff --git a/intranet/apps/eighth/serializers.py b/intranet/apps/eighth/serializers.py index d3dc07cfbb4..1e864654a4e 100644 --- a/intranet/apps/eighth/serializers.py +++ b/intranet/apps/eighth/serializers.py @@ -3,12 +3,11 @@ import functools import logging -from rest_framework import serializers -from rest_framework.reverse import reverse - from django.contrib.auth import get_user_model from django.db import transaction from django.db.models import Count +from rest_framework import serializers +from rest_framework.reverse import reverse from .models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor diff --git a/intranet/apps/eighth/tasks.py b/intranet/apps/eighth/tasks.py index 50e97178cf9..20d4326e92c 100644 --- a/intranet/apps/eighth/tasks.py +++ b/intranet/apps/eighth/tasks.py @@ -4,7 +4,6 @@ from celery import shared_task from celery.utils.log import get_task_logger - from django.conf import settings from django.contrib.auth import get_user_model from django.core.mail import EmailMessage diff --git a/intranet/apps/eighth/tests/test_commands.py b/intranet/apps/eighth/tests/test_commands.py index f76e432a80e..1c6a5fbe352 100644 --- a/intranet/apps/eighth/tests/test_commands.py +++ b/intranet/apps/eighth/tests/test_commands.py @@ -4,7 +4,6 @@ from unittest.mock import mock_open, patch import pytz - from django.contrib.auth import get_user_model from django.core.management import CommandError, call_command from django.utils import timezone diff --git a/intranet/apps/eighth/urls.py b/intranet/apps/eighth/urls.py index 1035f215a5a..5652682d0de 100644 --- a/intranet/apps/eighth/urls.py +++ b/intranet/apps/eighth/urls.py @@ -4,9 +4,8 @@ from .views import activities, attendance, monitoring, profile, routers, signup from .views.admin import activities as admin_activities from .views.admin import attendance as admin_attendance -from .views.admin import blocks, general, groups, hybrid +from .views.admin import blocks, general, groups, hybrid, rooms, scheduling, sponsors, users from .views.admin import maintenance as admin_maintenance -from .views.admin import rooms, scheduling, sponsors, users urlpatterns = [ re_path(r"^$", routers.eighth_redirect_view, name="eighth_redirect"), diff --git a/intranet/apps/eighth/views/activities.py b/intranet/apps/eighth/views/activities.py index 834fd65879e..563ec3f2fec 100644 --- a/intranet/apps/eighth/views/activities.py +++ b/intranet/apps/eighth/views/activities.py @@ -4,11 +4,6 @@ from datetime import MAXYEAR, MINYEAR, date, datetime, timedelta from io import BytesIO -from reportlab.lib.pagesizes import letter -from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet -from reportlab.lib.units import inch -from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table - from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.decorators import login_required @@ -16,6 +11,10 @@ from django.http import HttpResponse from django.shortcuts import get_object_or_404, render from django.utils import timezone +from reportlab.lib.pagesizes import letter +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import inch +from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table from ....utils.date import get_date_range_this_year, get_senior_graduation_year from ....utils.helpers import is_entirely_digit @@ -53,7 +52,7 @@ def activity_view(request, activity_id=None): def chunks(items, n): for i in range(0, len(items), n): - yield items[i: i + n] + yield items[i : i + n] def current_school_year(): diff --git a/intranet/apps/eighth/views/admin/activities.py b/intranet/apps/eighth/views/admin/activities.py index eabf4092305..919140edd6d 100644 --- a/intranet/apps/eighth/views/admin/activities.py +++ b/intranet/apps/eighth/views/admin/activities.py @@ -3,7 +3,6 @@ import pickle from cacheops import invalidate_obj - from django import forms, http from django.contrib import messages from django.shortcuts import redirect, render diff --git a/intranet/apps/eighth/views/admin/attendance.py b/intranet/apps/eighth/views/admin/attendance.py index 000c319daaf..f738daf5a7e 100644 --- a/intranet/apps/eighth/views/admin/attendance.py +++ b/intranet/apps/eighth/views/admin/attendance.py @@ -3,7 +3,6 @@ from datetime import MAXYEAR, MINYEAR, date, datetime, timedelta from cacheops import invalidate_obj - from django import http from django.contrib import messages from django.contrib.auth import get_user_model diff --git a/intranet/apps/eighth/views/admin/blocks.py b/intranet/apps/eighth/views/admin/blocks.py index d3526e7021f..7060b604343 100644 --- a/intranet/apps/eighth/views/admin/blocks.py +++ b/intranet/apps/eighth/views/admin/blocks.py @@ -4,7 +4,6 @@ import re from cacheops import invalidate_model - from django import http from django.conf import settings from django.contrib import messages diff --git a/intranet/apps/eighth/views/admin/general.py b/intranet/apps/eighth/views/admin/general.py index add69491d4c..1c0fffd0617 100644 --- a/intranet/apps/eighth/views/admin/general.py +++ b/intranet/apps/eighth/views/admin/general.py @@ -1,7 +1,6 @@ from urllib.parse import unquote from cacheops import invalidate_all - from django.conf import settings from django.contrib import messages from django.contrib.auth import get_user_model diff --git a/intranet/apps/eighth/views/admin/groups.py b/intranet/apps/eighth/views/admin/groups.py index aa10e828be0..55f82c79e51 100644 --- a/intranet/apps/eighth/views/admin/groups.py +++ b/intranet/apps/eighth/views/admin/groups.py @@ -4,8 +4,6 @@ from typing import List, Optional from cacheops import invalidate_model, invalidate_obj -from formtools.wizard.views import SessionWizardView - from django import http from django.contrib import messages from django.contrib.auth import get_user_model @@ -14,6 +12,7 @@ from django.db.models import Q from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse +from formtools.wizard.views import SessionWizardView from ....auth.decorators import eighth_admin_required from ....groups.models import Group @@ -675,7 +674,7 @@ def eighth_admin_distribute_action(request): users_type = "unsigned" if "limit" in request.GET: - users = users[0: int(request.GET.get("limit"))] + users = users[0 : int(request.GET.get("limit"))] # Sort by last name users = sorted(users, key=lambda x: x.last_name) @@ -684,7 +683,8 @@ def eighth_admin_distribute_action(request): sticky_users_and_activities = {} for user in users: sticky_activity_signup = EighthSignup.objects.filter( - user=user, scheduled_activity__block=block if users_type == "unsigned" else schacts[0].block # pylint: disable=used-before-assignment + user=user, + scheduled_activity__block=block if users_type == "unsigned" else schacts[0].block, # pylint: disable=used-before-assignment ).filter(Q(scheduled_activity__activity__sticky=True) | Q(scheduled_activity__sticky=True)) if sticky_activity_signup.exists(): sticky_users_and_activities[user] = sticky_activity_signup[0].scheduled_activity diff --git a/intranet/apps/eighth/views/admin/hybrid.py b/intranet/apps/eighth/views/admin/hybrid.py index 14d063b3eb5..9045ec3a625 100644 --- a/intranet/apps/eighth/views/admin/hybrid.py +++ b/intranet/apps/eighth/views/admin/hybrid.py @@ -1,11 +1,10 @@ import logging from typing import Optional -from formtools.wizard.views import SessionWizardView - from django import http from django.contrib import messages from django.shortcuts import get_object_or_404, redirect, render, reverse +from formtools.wizard.views import SessionWizardView from ....auth.decorators import eighth_admin_required from ....users.models import Group @@ -46,9 +45,7 @@ def activities_without_attendance_view(request): if scheduled_activities is not None: scheduled_activities = scheduled_activities | b.eighthscheduledactivity_set.filter( block__date__gte=start_date, attendance_taken=False - ).order_by( - "-activity__special", "activity__name" - ) # float special to top + ).order_by("-activity__special", "activity__name") # float special to top else: scheduled_activities = b.eighthscheduledactivity_set.filter(block__date__gte=start_date, attendance_taken=False).order_by( "-activity__special", "activity__name" diff --git a/intranet/apps/eighth/views/admin/rooms.py b/intranet/apps/eighth/views/admin/rooms.py index 6a57afbcf02..45b2a98742f 100644 --- a/intranet/apps/eighth/views/admin/rooms.py +++ b/intranet/apps/eighth/views/admin/rooms.py @@ -2,13 +2,12 @@ import logging from collections import defaultdict -from formtools.wizard.views import SessionWizardView - from django import http from django.contrib import messages from django.db.models import Q from django.shortcuts import redirect, render from django.urls import reverse +from formtools.wizard.views import SessionWizardView from ....auth.decorators import eighth_admin_required from ...forms.admin.blocks import BlockSelectionForm diff --git a/intranet/apps/eighth/views/admin/scheduling.py b/intranet/apps/eighth/views/admin/scheduling.py index fb7f3a799ae..abc5b4e7372 100644 --- a/intranet/apps/eighth/views/admin/scheduling.py +++ b/intranet/apps/eighth/views/admin/scheduling.py @@ -1,8 +1,6 @@ import logging from cacheops import invalidate_obj -from formtools.wizard.views import SessionWizardView - from django.contrib import messages from django.core.management import call_command from django.db import transaction @@ -11,6 +9,7 @@ from django.forms.formsets import formset_factory from django.http import Http404 from django.shortcuts import redirect, render +from formtools.wizard.views import SessionWizardView from .....utils.locking import lock_on from .....utils.serialization import safe_json diff --git a/intranet/apps/eighth/views/api.py b/intranet/apps/eighth/views/api.py index 98119a9ffb9..ea150b488b9 100644 --- a/intranet/apps/eighth/views/api.py +++ b/intranet/apps/eighth/views/api.py @@ -1,18 +1,25 @@ import logging from datetime import datetime +from django.contrib.auth import get_user_model +from django.core.exceptions import PermissionDenied +from django.http import Http404 from rest_framework import generics, permissions, status, views from rest_framework.exceptions import ValidationError from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response -from django.contrib.auth import get_user_model -from django.core.exceptions import PermissionDenied -from django.http import Http404 - from ..models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup -from ..serializers import (EighthActivityDetailSerializer, EighthActivityListSerializer, EighthAddSignupSerializer, EighthBlockDetailSerializer, - EighthBlockListSerializer, EighthScheduledActivitySerializer, EighthSignupSerializer, EighthToggleFavoriteSerializer) +from ..serializers import ( + EighthActivityDetailSerializer, + EighthActivityListSerializer, + EighthAddSignupSerializer, + EighthBlockDetailSerializer, + EighthBlockListSerializer, + EighthScheduledActivitySerializer, + EighthSignupSerializer, + EighthToggleFavoriteSerializer, +) logger = logging.getLogger(__name__) diff --git a/intranet/apps/eighth/views/attendance.py b/intranet/apps/eighth/views/attendance.py index 64a78a326ee..2385dd09e5c 100644 --- a/intranet/apps/eighth/views/attendance.py +++ b/intranet/apps/eighth/views/attendance.py @@ -4,14 +4,6 @@ from html import escape from cacheops import invalidate_obj -from formtools.wizard.views import SessionWizardView -from reportlab.lib import colors -from reportlab.lib.enums import TA_CENTER, TA_RIGHT -from reportlab.lib.pagesizes import letter -from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet -from reportlab.lib.units import inch -from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle - from django import http from django.conf import settings from django.contrib import messages @@ -22,6 +14,13 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone +from formtools.wizard.views import SessionWizardView +from reportlab.lib import colors +from reportlab.lib.enums import TA_CENTER, TA_RIGHT +from reportlab.lib.pagesizes import letter +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import inch +from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle from ....utils.date import get_date_range_this_year from ...auth.decorators import attendance_taker_required, deny_restricted, eighth_admin_required diff --git a/intranet/apps/eighth/views/signup.py b/intranet/apps/eighth/views/signup.py index a06ee2be0fa..97be5c56d8a 100644 --- a/intranet/apps/eighth/views/signup.py +++ b/intranet/apps/eighth/views/signup.py @@ -2,8 +2,6 @@ import logging import time -from prometheus_client import Summary - from django import http from django.conf import settings from django.contrib import messages @@ -13,6 +11,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone from django.views.decorators.http import require_POST +from prometheus_client import Summary from ....utils.date import get_date_range_this_year from ....utils.helpers import is_entirely_digit diff --git a/intranet/apps/emailfwd/management/commands/get_senior_forwards.py b/intranet/apps/emailfwd/management/commands/get_senior_forwards.py index bc75948cae1..88e4746be99 100644 --- a/intranet/apps/emailfwd/management/commands/get_senior_forwards.py +++ b/intranet/apps/emailfwd/management/commands/get_senior_forwards.py @@ -10,4 +10,4 @@ class Command(BaseCommand): def handle(self, *args, **options): forwards = SeniorEmailForward.objects.filter(user__graduation_year=get_senior_graduation_year(), user__user_type="student") for forward in forwards: - self.stdout.write("%s:\t\t%s" % (forward.user, forward.email)) + self.stdout.write(f"{forward.user}:\t\t{forward.email}") diff --git a/intranet/apps/emerg/views.py b/intranet/apps/emerg/views.py index 666f0f6ccc0..fd69b3d49da 100644 --- a/intranet/apps/emerg/views.py +++ b/intranet/apps/emerg/views.py @@ -4,7 +4,6 @@ import requests from bs4 import BeautifulSoup - from django.conf import settings from django.core.cache import cache from django.utils import timezone @@ -44,7 +43,7 @@ def check_emerg(): bad_strings = [ "There are no emergency announcements at this time", "There are no emergency messages at this time", - "There are no emeregency annoncements at this time", + "There are no emeregency annoncements at this time", # codespell: ignore "There are no major announcements at this time.", "There are no major emergency announcements at this time.", "There are no emergencies at this time.", @@ -87,7 +86,7 @@ def check_emerg(): soup = BeautifulSoup(desc, "html.parser") text = soup.find_all(["p", "hr"]) - desc = text[2: len(text) - 5] + desc = text[2 : len(text) - 5] a = { "title": f"{issue['title']}", "body": "".join(d.prettify() for d in desc), diff --git a/intranet/apps/enrichment/views.py b/intranet/apps/enrichment/views.py index fbaa2817352..81ad5fbb6d0 100644 --- a/intranet/apps/enrichment/views.py +++ b/intranet/apps/enrichment/views.py @@ -2,7 +2,6 @@ from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta - from django import http from django.contrib import messages from django.contrib.auth.decorators import login_required diff --git a/intranet/apps/events/tasks.py b/intranet/apps/events/tasks.py index 5b7c39acdcf..d68e73fcb75 100644 --- a/intranet/apps/events/tasks.py +++ b/intranet/apps/events/tasks.py @@ -4,7 +4,6 @@ import requests from celery import shared_task from celery.utils.log import get_task_logger - from django.utils import timezone from .models import Event @@ -74,7 +73,7 @@ def pull_sports_schedules(month=None) -> None: assert time is not None Event.objects.get_or_create( title=line.get("Subject").split("(")[0].strip(), - description=line.get("Description")[line.get("Description").index("Opponent:") + 10:], + description=line.get("Description")[line.get("Description").index("Opponent:") + 10 :], location=line.get("Location"), show_attending=False, show_on_dashboard=False, diff --git a/intranet/apps/events/views.py b/intranet/apps/events/views.py index af71741cc51..38f710c7452 100644 --- a/intranet/apps/events/views.py +++ b/intranet/apps/events/views.py @@ -2,7 +2,6 @@ from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta - from django import http from django.contrib import messages from django.contrib.auth.decorators import login_required diff --git a/intranet/apps/features/views.py b/intranet/apps/features/views.py index ba720d2f0b7..0a3c2b47bdf 100644 --- a/intranet/apps/features/views.py +++ b/intranet/apps/features/views.py @@ -1,5 +1,4 @@ from cacheops import invalidate_obj - from django import http from django.contrib.auth.decorators import login_required from django.views.decorators.http import require_POST diff --git a/intranet/apps/files/views.py b/intranet/apps/files/views.py index 9453d1a37a9..183de9a4079 100644 --- a/intranet/apps/files/views.py +++ b/intranet/apps/files/views.py @@ -12,8 +12,6 @@ import pysftp from Crypto import Random from Crypto.Cipher import AES -from paramiko import SFTPError, SSHException - from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required @@ -22,6 +20,7 @@ from django.urls import reverse from django.utils.text import slugify from django.views.decorators.debug import sensitive_post_parameters, sensitive_variables +from paramiko import SFTPError, SSHException from ..auth.decorators import deny_restricted from .forms import UploadFileForm diff --git a/intranet/apps/logs/models.py b/intranet/apps/logs/models.py index a262e9470ed..9da957e5ea6 100644 --- a/intranet/apps/logs/models.py +++ b/intranet/apps/logs/models.py @@ -33,7 +33,7 @@ def request_json_obj(self): return json.loads(self.request) def __str__(self): - return f'{self.timestamp.astimezone(settings.PYTZ_TIME_ZONE).strftime("%b %d %Y %H:%M:%S")} - {self.username} - {self.ip} - {self.method} "{self.path}"' # pylint: disable=line-too-long # noqa: E501 + return f'{self.timestamp.astimezone(settings.PYTZ_TIME_ZONE).strftime("%b %d %Y %H:%M:%S")} - {self.username} - {self.ip} - {self.method} "{self.path}"' # pylint: disable=line-too-long class Meta: ordering = ["-timestamp"] diff --git a/intranet/apps/notifications/emails.py b/intranet/apps/notifications/emails.py index dd0ff536271..662051382fa 100644 --- a/intranet/apps/notifications/emails.py +++ b/intranet/apps/notifications/emails.py @@ -49,7 +49,7 @@ def email_send( logger.debug("Email list is empty; not sending") return EmailMultiAlternatives(subject, text_content, settings.EMAIL_FROM, emails, headers=headers) - email_groups = [emails[i: i + 800] for i in range(0, len(emails), 800)] + email_groups = [emails[i : i + 800] for i in range(0, len(emails), 800)] email_msg = [] for group in email_groups: diff --git a/intranet/apps/notifications/views.py b/intranet/apps/notifications/views.py index 33a19300fbc..0e8c6da9cae 100644 --- a/intranet/apps/notifications/views.py +++ b/intranet/apps/notifications/views.py @@ -2,7 +2,6 @@ import logging import requests - from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required diff --git a/intranet/apps/oauth/models.py b/intranet/apps/oauth/models.py index 9b7783027f2..a552676e0f7 100644 --- a/intranet/apps/oauth/models.py +++ b/intranet/apps/oauth/models.py @@ -1,7 +1,6 @@ -from oauth2_provider.models import AbstractApplication - from django.db import models from django.utils.translation import gettext_lazy as _ +from oauth2_provider.models import AbstractApplication class CSLApplication(AbstractApplication): diff --git a/intranet/apps/oauth/views.py b/intranet/apps/oauth/views.py index 238934fbd61..aef24ce2fcb 100644 --- a/intranet/apps/oauth/views.py +++ b/intranet/apps/oauth/views.py @@ -1,8 +1,7 @@ -from oauth2_provider.models import get_application_model -from oauth2_provider.views.application import ApplicationDelete, ApplicationRegistration, ApplicationUpdate - from django.forms.models import modelform_factory from django.shortcuts import render +from oauth2_provider.models import get_application_model +from oauth2_provider.views.application import ApplicationDelete, ApplicationRegistration, ApplicationUpdate from .models import BlankModel diff --git a/intranet/apps/polls/views.py b/intranet/apps/polls/views.py index 50c0696516e..f9274628039 100644 --- a/intranet/apps/polls/views.py +++ b/intranet/apps/polls/views.py @@ -4,8 +4,6 @@ from collections import OrderedDict import pyrankvote -from pyrankvote import Ballot - from django import http from django.contrib import messages from django.contrib.auth import get_user_model @@ -15,6 +13,7 @@ from django.db.models import Q from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone +from pyrankvote import Ballot from ...utils.date import get_senior_graduation_year from ...utils.html import safe_html diff --git a/intranet/apps/preferences/tests.py b/intranet/apps/preferences/tests.py index a3a8ac52b30..c895be7a198 100644 --- a/intranet/apps/preferences/tests.py +++ b/intranet/apps/preferences/tests.py @@ -5,6 +5,7 @@ from ...test.ion_test import IonTestCase from ...utils.date import get_senior_graduation_year from ..bus.models import Route + # from ..users.models import Email, Phone, Photo, User, UserProperties, Website from ..users.models import Email, Photo, User, UserProperties from .forms import EmailForm @@ -180,7 +181,7 @@ def test_privacy_options_view(self): options = get_privacy_options(user_student) PERMISSIONS_NAMES = { - prefix: [name[len(prefix) + 1:] for name in dir(UserProperties) if name.startswith(prefix + "_")] for prefix in ["self", "parent"] + prefix: [name[len(prefix) + 1 :] for name in dir(UserProperties) if name.startswith(prefix + "_")] for prefix in ["self", "parent"] } for permission_type in PERMISSIONS_NAMES.keys(): diff --git a/intranet/apps/preferences/views.py b/intranet/apps/preferences/views.py index 6740926eeee..6bcbf78337c 100644 --- a/intranet/apps/preferences/views.py +++ b/intranet/apps/preferences/views.py @@ -1,7 +1,6 @@ import logging from cacheops import invalidate_obj - from django.conf import settings from django.contrib import messages from django.contrib.auth import get_user_model diff --git a/intranet/apps/printing/views.py b/intranet/apps/printing/views.py index 8503115e38e..6e9e0562cf3 100644 --- a/intranet/apps/printing/views.py +++ b/intranet/apps/printing/views.py @@ -8,9 +8,6 @@ from typing import Dict, Optional import magic -from sentry_sdk import add_breadcrumb, capture_exception -from xhtml2pdf import pisa - from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required @@ -19,6 +16,8 @@ from django.template.loader import get_template from django.utils import timezone from django.utils.text import slugify +from sentry_sdk import add_breadcrumb, capture_exception +from xhtml2pdf import pisa from ..auth.decorators import deny_restricted from ..context_processors import _get_current_ip @@ -177,7 +176,7 @@ def get_numpages(tmpfile_name: str) -> int: for line in lines: if line.startswith(pages_prefix): try: - num_pages = int(line[len(pages_prefix):].strip()) + num_pages = int(line[len(pages_prefix) :].strip()) except ValueError: num_pages = -1 diff --git a/intranet/apps/schedule/api.py b/intranet/apps/schedule/api.py index 0bb6a640442..56c83906c3f 100644 --- a/intranet/apps/schedule/api.py +++ b/intranet/apps/schedule/api.py @@ -1,11 +1,10 @@ import logging +from django.core import exceptions from rest_framework import generics, serializers from rest_framework.pagination import PageNumberPagination from rest_framework.permissions import AllowAny -from django.core import exceptions - from .models import Day, DayType from .serializers import DaySerializer diff --git a/intranet/apps/schedule/tests.py b/intranet/apps/schedule/tests.py index 49497d49a98..c01bc6fb9a5 100644 --- a/intranet/apps/schedule/tests.py +++ b/intranet/apps/schedule/tests.py @@ -3,7 +3,6 @@ from unittest.mock import patch import pytz - from django.contrib.auth.models import AnonymousUser from django.test import RequestFactory from django.urls import reverse diff --git a/intranet/apps/schedule/views.py b/intranet/apps/schedule/views.py index 0704f1e3796..de97ef4ebe4 100644 --- a/intranet/apps/schedule/views.py +++ b/intranet/apps/schedule/views.py @@ -3,7 +3,6 @@ from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta - from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required, user_passes_test diff --git a/intranet/apps/signage/consumers.py b/intranet/apps/signage/consumers.py index 3598715d60b..f8b07a3df92 100644 --- a/intranet/apps/signage/consumers.py +++ b/intranet/apps/signage/consumers.py @@ -1,5 +1,4 @@ from channels.generic.websocket import JsonWebsocketConsumer - from django.conf import settings from django.utils import timezone diff --git a/intranet/apps/templatetags/form_field.py b/intranet/apps/templatetags/form_field.py index f9e464e81f8..de18037b9d5 100644 --- a/intranet/apps/templatetags/form_field.py +++ b/intranet/apps/templatetags/form_field.py @@ -16,5 +16,5 @@ def field_(self, name): try: field = self.fields[name] except KeyError as e: - raise KeyError("Key %r not found in '%s'" % (name, self.__class__.__name__)) from e + raise KeyError(f"Key {name!r} not found in '{self.__class__.__name__}'") from e return BoundField(self, field, name) diff --git a/intranet/apps/users/api.py b/intranet/apps/users/api.py index 0550a25ad14..aa26af6e502 100644 --- a/intranet/apps/users/api.py +++ b/intranet/apps/users/api.py @@ -1,12 +1,11 @@ import os +from django.conf import settings +from django.contrib.auth import get_user_model from rest_framework import generics from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from django.conf import settings -from django.contrib.auth import get_user_model - from intranet.apps.search.views import get_search_results from ..auth.rest_permissions import ApiAndOauthPermission, DenyRestrictedPermission diff --git a/intranet/apps/users/management/commands/lock.py b/intranet/apps/users/management/commands/lock.py index 1ab07f50816..b70c0a6bb05 100644 --- a/intranet/apps/users/management/commands/lock.py +++ b/intranet/apps/users/management/commands/lock.py @@ -8,7 +8,7 @@ class Command(BaseCommand): def handle(self, *args, **options): u = get_user_model().objects.get(username=args[0]) - self.stdout.write("%s - %s - %s" % (u, u.user_locked, str(u.last_login))) + self.stdout.write(f"{u} - {u.user_locked} - {u.last_login}") u.user_locked = True u.save() - self.stdout.write("%s - %s" % (u, u.user_locked)) + self.stdout.write(f"{u} - {u.user_locked}") diff --git a/intranet/apps/users/models.py b/intranet/apps/users/models.py index 807bf6cfa4e..d592100c261 100644 --- a/intranet/apps/users/models.py +++ b/intranet/apps/users/models.py @@ -5,7 +5,6 @@ from typing import Collection, Dict, Optional, Union from dateutil.relativedelta import relativedelta - from django.conf import settings from django.contrib.auth.models import AbstractBaseUser, AnonymousUser, PermissionsMixin from django.contrib.auth.models import UserManager as DjangoUserManager @@ -104,7 +103,7 @@ def get_teachers(self) -> Union[Collection["User"], QuerySet]: # pylint: disabl return users - def get_teachers_attendance_users(self) -> "QuerySet[User]": # noqa + def get_teachers_attendance_users(self) -> "QuerySet[User]": """Like ``get_teachers()``, but includes attendance-only users as well as teachers. @@ -130,7 +129,7 @@ def get_teachers_sorted(self) -> Union[Collection["User"], QuerySet]: # pylint: """ return self.get_teachers().order_by("last_name", "first_name") - def get_teachers_attendance_users_sorted(self) -> "QuerySet[User]": # noqa + def get_teachers_attendance_users_sorted(self) -> "QuerySet[User]": """Returns a ``QuerySet`` containing both teachers and attendance-only users sorted by last name, then first name. @@ -140,7 +139,7 @@ def get_teachers_attendance_users_sorted(self) -> "QuerySet[User]": # noqa """ return self.get_teachers_attendance_users().order_by("last_name", "first_name") - def get_approve_announcements_users(self) -> "QuerySet[User]": # noqa + def get_approve_announcements_users(self) -> "QuerySet[User]": """Returns a ``QuerySet`` containing all users except simple users, tjstar presenters, alumni, service users and students. @@ -156,7 +155,7 @@ def get_approve_announcements_users(self) -> "QuerySet[User]": # noqa return users - def get_approve_announcements_users_sorted(self) -> "QuerySet[User]": # noqa + def get_approve_announcements_users_sorted(self) -> "QuerySet[User]": """Returns a ``QuerySet`` containing all users except simple users, tjstar presenters, alumni, service users and students sorted by last name, then first name. @@ -321,7 +320,7 @@ def full_name(self) -> str: The user's full name (first + " " + last). """ - return "{} {}".format(self.first_name, self.last_name) + return f"{self.first_name} {self.last_name}" @property def full_name_nick(self) -> str: @@ -349,7 +348,7 @@ def last_first(self) -> str: """Return a name in the format of: Lastname, Firstname [(Nickname)] """ - return "{}, {}".format(self.last_name, self.first_name) + (" ({})".format(self.nickname) if self.nickname else "") + return f"{self.last_name}, {self.first_name}" + (f" ({self.nickname})" if self.nickname else "") @property def last_first_id(self) -> str: @@ -358,8 +357,8 @@ def last_first_id(self) -> str: """ return ( "{}{} ".format(self.last_name, ", " + self.first_name if self.first_name else "") - + ("({}) ".format(self.nickname) if self.nickname else "") - + ("({})".format(self.student_id if self.is_student and self.student_id else self.username)) + + (f"({self.nickname}) " if self.nickname else "") + + (f"({self.student_id if self.is_student and self.student_id else self.username})") ) @property @@ -368,7 +367,7 @@ def last_first_initial(self) -> str: Lastname, F [(Nickname)] """ return "{}{}".format(self.last_name, ", " + self.first_name[:1] + "." if self.first_name else "") + ( - " ({})".format(self.nickname) if self.nickname else "" + f" ({self.nickname})" if self.nickname else "" ) @property @@ -431,7 +430,7 @@ def tj_email(self) -> str: else: domain = "tjhsst.edu" - return "{}@{}".format(self.username, domain) + return f"{self.username}@{domain}" @property def non_tj_email(self) -> Optional[str]: @@ -929,7 +928,7 @@ def is_eighth_sponsor(self) -> bool: def frequent_signups(self): """Return a QuerySet of activity id's and counts for the activities that a given user has signed up for more than `settings.SIMILAR_THRESHOLD` times""" - key = "{}:frequent_signups".format(self.username) + key = f"{self.username}:frequent_signups" cached = cache.get(key) if cached: return cached @@ -948,7 +947,7 @@ def frequent_signups(self): @property def recommended_activities(self): - key = "{}:recommended_activities".format(self.username) + key = f"{self.username}:recommended_activities" cached = cache.get(key) if cached is not None: return cached @@ -972,7 +971,7 @@ def recommended_activities(self): def archive_admin_comments(self): current_year = timezone.localdate().year previous_year = current_year - 1 - self.admin_comments = "\n=== {}-{} comments ===\n{}".format(previous_year, current_year, self.admin_comments) + self.admin_comments = f"\n=== {previous_year}-{current_year} comments ===\n{self.admin_comments}" self.save(update_fields=["admin_comments"]) def get_eighth_sponsor(self): @@ -1079,7 +1078,7 @@ def __getattr__(self, name): return UserProperties.objects.get_or_create(user=self)[0] elif name == "dark_mode_properties": return UserDarkModeProperties.objects.get_or_create(user=self)[0] - raise AttributeError("{!r} object has no attribute {!r}".format(type(self).__name__, name)) + raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}") def __str__(self): return self.username or self.ion_username or str(self.id) @@ -1125,7 +1124,7 @@ def __getattr__(self, name): return self._address if self.attribute_is_visible("show_address") else None if name == "schedule": return self._schedule if self.attribute_is_visible("show_schedule") else None - raise AttributeError("{!r} object has no attribute {!r}".format(type(self).__name__, name)) + raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}") def __setattr__(self, name, value): if name == "address": @@ -1152,17 +1151,17 @@ def set_permission(self, permission: str, value: bool, parent: bool = False, adm """ try: - if not getattr(self, "parent_{}".format(permission)) and not parent and not admin: + if not getattr(self, f"parent_{permission}") and not parent and not admin: return False level = "parent" if parent else "self" - setattr(self, "{}_{}".format(level, permission), value) + setattr(self, f"{level}_{permission}", value) - update_fields = ["{}_{}".format(level, permission)] + update_fields = [f"{level}_{permission}"] # Set student permission to false if parent sets permission to false. if parent and not value: - setattr(self, "self_{}".format(permission), False) - update_fields.append("self_{}".format(permission)) + setattr(self, f"self_{permission}", False) + update_fields.append(f"self_{permission}") self.save(update_fields=update_fields) return True @@ -1229,8 +1228,8 @@ def attribute_is_visible(self, permission: str) -> bool: """ try: - parent = getattr(self, "parent_{}".format(permission)) - student = getattr(self, "self_{}".format(permission)) + parent = getattr(self, f"parent_{permission}") + student = getattr(self, f"self_{permission}") except Exception: logger.error("Could not retrieve permissions for %s", permission) @@ -1248,8 +1247,8 @@ def attribute_is_public(self, permission: str) -> bool: """ try: - parent = getattr(self, "parent_{}".format(permission)) - student = getattr(self, "self_{}".format(permission)) + parent = getattr(self, f"parent_{permission}") + student = getattr(self, f"self_{permission}") except Exception: logger.error("Could not retrieve permissions for %s", permission) @@ -1257,7 +1256,7 @@ def attribute_is_public(self, permission: str) -> bool: PERMISSIONS_NAMES = { - prefix: [name[len(prefix) + 1:] for name in dir(UserProperties) if name.startswith(prefix + "_")] for prefix in ["self", "parent"] # noqa: E501 + prefix: [name[len(prefix) + 1 :] for name in dir(UserProperties) if name.startswith(prefix + "_")] for prefix in ["self", "parent"] } @@ -1309,10 +1308,10 @@ def __setattr__(self, name, value): def __getattr__(self, name): if name == "number": return self._number if self.user.properties.attribute_is_visible("show_telephone") else None - raise AttributeError("{!r} object has no attribute {!r}".format(type(self).__name__, name)) + raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}") def __str__(self): - return "{}: {}".format(self.get_purpose_display(), self.number) + return f"{self.get_purpose_display()}: {self.number}" class Meta: unique_together = ("user", "_number") @@ -1357,7 +1356,7 @@ class Address(models.Model): def __str__(self): """Returns full address string.""" - return "{}\n{}, {} {}".format(self.street, self.city, self.state, self.postal_code) + return f"{self.street}\n{self.city}, {self.state} {self.postal_code}" class Photo(models.Model): @@ -1378,7 +1377,7 @@ def __setattr__(self, name, value): def __getattr__(self, name): if name == "binary": return self._binary if self.user.properties.attribute_is_visible("show_pictures") else None - raise AttributeError("{!r} object has no attribute {!r}".format(type(self).__name__, name)) + raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}") @cached_property def base64(self) -> Optional[bytes]: @@ -1436,13 +1435,13 @@ def name(self) -> str: @property def name_plural(self) -> str: """Return the grade's plural name (e.g. freshmen)""" - return "freshmen" if (self._number and self._number == 9) else "{}s".format(self._name) if self._name else "" + return "freshmen" if (self._number and self._number == 9) else f"{self._name}s" if self._name else "" @property def text(self) -> str: """Return the grade's number as a string (e.g. Grade 12, Graduate)""" if 9 <= self._number <= 12: - return "Grade {}".format(self._number) + return f"Grade {self._number}" else: return self._name @@ -1491,7 +1490,7 @@ class Course(models.Model): course_id = models.CharField(max_length=12, unique=True) def __str__(self): - return "{} ({})".format(self.name, self.course_id) + return f"{self.name} ({self.course_id})" class Meta: ordering = ("name", "course_id") @@ -1516,7 +1515,7 @@ def __str__(self): def __getattr__(self, name): if name == "students": return [s.user for s in self._students.all() if s.attribute_is_visible("show_schedule")] - raise AttributeError("{!r} object has no attribute {!r}".format(type(self).__name__, name)) + raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}") class Meta: ordering = ("section_id", "period") diff --git a/intranet/apps/users/serializers.py b/intranet/apps/users/serializers.py index 4929b3e90d6..2abe1f207f5 100644 --- a/intranet/apps/users/serializers.py +++ b/intranet/apps/users/serializers.py @@ -1,7 +1,6 @@ # pylint: disable=abstract-method -from rest_framework import serializers - from django.contrib.auth import get_user_model +from rest_framework import serializers from .models import Address diff --git a/intranet/apps/users/tests.py b/intranet/apps/users/tests.py index 8a0e4ddfc3d..10093859389 100644 --- a/intranet/apps/users/tests.py +++ b/intranet/apps/users/tests.py @@ -1,13 +1,12 @@ import datetime import os -from oauth2_provider.models import AccessToken, get_application_model -from oauth2_provider.settings import oauth2_settings - from django.conf import settings from django.contrib.auth import get_user_model from django.urls import reverse from django.utils import timezone +from oauth2_provider.models import AccessToken, get_application_model +from oauth2_provider.settings import oauth2_settings from ...test.ion_test import IonTestCase from ..eighth.models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup diff --git a/pyproject.toml b/pyproject.toml index 853b7e3700b..b71f675f3df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,3 +42,95 @@ exclude = ''' | stubs )/ ''' + +[tool.ruff] +extend-exclude = [ + "Ion.egg-info", + "build", + ".env", + "env", + "migrations", +] + +# show fixes made in stdout +# show-fixes = true + +line-length = 150 + +target-version = "py38" + +[tool.ruff.lint] +select = [ + # flake8-bugbear + "B", + # flake8-comprehensions + "C4", + # flake8-django + "DJ", + # pycodestyle + "E", + # Pyflakes + "F", + # isort + "I", + # flake8-no-pep420 + "INP", + # Pylint + "PL", + # ruff + "RUF", + # pyupgrade + "UP", +] +ignore = [ + # default arguments for timezone.now() + "B008", + # null=True on CharField/TextField + "DJ001", + # No __str__ method on Model + "DJ008", + # Django order of model methods + "DJ012", + # ambiguous variable name + "E741", + # as recommended by https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "E111", + "E114", + "E117", + "E501", + # branching + "PLR09", + # avoid magic numbers + "PLR2004", + # loop variables overwritten by assignment + "PLW2901", + # Use ternary operator (x if cond else y) + "RUF005", + # mutable class attrs annotated as typing.ClassVar + "RUF012", + # implicit Optional + "RUF013", +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = [ + "F401", + "F403", +] + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false +parametrize-names-type = "tuple" +parametrize-values-type = "tuple" + +[tool.ruff.format] +docstring-code-format = true +line-ending = "lf" + +[tool.codespell] +write-changes = true +skip = "*.pyi,*/vendor,*/migrations,*media" +ignore-words-list = "num,ans,intranet,ther,usera,userb" +# match: # codespell: ignore +ignore-regex=".+# *codespell: *ignore" diff --git a/requirements-dev.txt b/requirements-dev.txt index 96156df604a..4ff86f5f860 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,11 +1,4 @@ # Dependencies for development -autopep8==2.1.0 -black==24.3.0 -flake8==7.0.0 -isort==5.13.2 -pycodestyle==2.11.0 -pylint==3.1.0 -pylint-django==2.5.5 -pylint-plugin-utils==0.8.2 names==0.3.0 whitenoise==6.6.0 +pre-commit==3.5.0