From c6eea47dd3a7a50ebcfdb20af5c432bc1ffef31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ma=C4=87kowski?= Date: Sun, 26 Nov 2023 16:44:19 +0000 Subject: [PATCH] format: reformat codebase, fix ruff lint errors --- admin/admin.py | 21 ++-- admin/apps.py | 2 +- badges/admin.py | 64 ++++++----- badges/apps.py | 2 +- badges/local_badge_client.py | 16 ++- badges/models.py | 50 ++++---- badges/serializers.py | 2 +- badges/tests.py | 2 - badges/urls.py | 14 +-- badges/validators.py | 40 +++---- badges/views.py | 19 ++- badgeupdater/client/auth.py | 16 +-- badgeupdater/client/badge_client.py | 9 +- badgeupdater/models.py | 3 +- badgeupdater/server/auth.py | 2 +- badgeupdater/server/badge_updater.py | 18 +-- badgeupdater/server/errors.py | 4 +- chombos/admin.py | 20 ++-- chombos/apps.py | 2 +- chombos/badgeclients.py | 18 +-- chombos/models.py | 2 +- chombos/serializers.py | 2 +- chombos/tests.py | 2 - chombos/urls.py | 4 +- chombos/views.py | 2 - kcc3/__init__.py | 2 +- kcc3/celery.py | 8 +- kcc3/hosts.py | 8 +- kcc3/settings/base.py | 107 +++++++++-------- kcc3/settings/settings.py | 40 +++---- kcc3/urls.py | 36 +++--- kcc3/wsgi.py | 2 +- manage.py | 4 +- players/admin.py | 17 ++- players/apps.py | 2 +- players/models.py | 22 ++-- players/serializers.py | 9 +- players/tests.py | 2 - players/urls.py | 2 +- pyproject.toml | 3 + src/kcc3/__init__.py | 2 - static/images/logo.svg | 2 +- yakumans/admin.py | 52 +++++---- yakumans/apps.py | 2 +- yakumans/forms.py | 15 +-- yakumans/metadata_stripper.py | 4 +- yakumans/models.py | 40 +++---- yakumans/tests.py | 2 - yakumans/urls.py | 7 +- yakumans/views.py | 4 +- yakumans/yakumans.py | 166 +++++++++++++-------------- 51 files changed, 424 insertions(+), 472 deletions(-) delete mode 100644 src/kcc3/__init__.py diff --git a/admin/admin.py b/admin/admin.py index 755800e..f42630e 100644 --- a/admin/admin.py +++ b/admin/admin.py @@ -4,24 +4,27 @@ class Kcc3AdminSite(admin.AdminSite): - index_title = 'Home' + index_title = "Home" def get_app_list(self, request): apps = super().get_app_list(request) - grouped_apps = ('badges', 'chombos', 'players', 'yakumans') - group_filter = lambda x: x['app_label'] in grouped_apps + grouped_apps = ("badges", "chombos", "players", "yakumans") + + def group_filter(x): + return x["app_label"] in grouped_apps + first_party = filter(group_filter, apps) third_party = itertools.filterfalse(group_filter, apps) - models = list(itertools.chain(*map(lambda x: x['models'], first_party))) + models = list(itertools.chain(*(x["models"] for x in first_party))) kcc3_app = { - 'name': 'Mahjong', - 'app_label': 'kcc3', - 'app_url': '', - 'has_module_perms': True, - 'models': models, + "name": "Mahjong", + "app_label": "kcc3", + "app_url": "", + "has_module_perms": True, + "models": models, } return itertools.chain([kcc3_app], third_party) diff --git a/admin/apps.py b/admin/apps.py index 38882b6..bd6a95f 100644 --- a/admin/apps.py +++ b/admin/apps.py @@ -2,4 +2,4 @@ class Kcc3AdminConfig(AdminConfig): - default_site = 'admin.admin.Kcc3AdminSite' + default_site = "admin.admin.Kcc3AdminSite" diff --git a/badges/admin.py b/badges/admin.py index fc957a7..ff11b42 100644 --- a/badges/admin.py +++ b/badges/admin.py @@ -1,3 +1,6 @@ +from collections.abc import Iterable +from typing import ClassVar + from django.contrib import admin from django.contrib.admin import SimpleListFilter from django.utils.html import format_html @@ -6,43 +9,41 @@ class IsAutomaticFilter(SimpleListFilter): - title = 'automatic' - parameter_name = 'automatic' + title = "automatic" + parameter_name = "automatic" def lookups(self, request, model_admin): return ( - ('1', 'Yes'), - ('0', 'No'), + ("1", "Yes"), + ("0", "No"), ) def queryset(self, request, queryset): - if self.value() == '1': + if self.value() == "1": return queryset.automatic() - elif self.value() == '0': + elif self.value() == "0": return queryset.not_automatic() return queryset class BadgeAdmin(admin.ModelAdmin): - prepopulated_fields = {'id': ('title',)} - autocomplete_fields = ('owners', 'players') - readonly_fields = ('token',) + prepopulated_fields: ClassVar[dict[str, Iterable[str]]] = {"id": ("title",)} + autocomplete_fields = ("owners", "players") + readonly_fields = ("token",) - list_display = ('image_tag', 'id', 'title', 'automatic', 'description') - list_display_links = ('image_tag', 'id', 'title') - list_filter = ('owners', IsAutomaticFilter) - search_fields = ('id', 'title', 'description') - ordering = ('id',) + list_display = ("image_tag", "id", "title", "automatic", "description") + list_display_links = ("image_tag", "id", "title") + list_filter = ("owners", IsAutomaticFilter) + search_fields = ("id", "title", "description") + ordering = ("id",) view_on_site = True def image_tag(self, obj): - return format_html( - f'') + return format_html(f'') - image_tag.short_description = 'Image' + image_tag.short_description = "Image" def automatic(self, obj): return obj.is_automatic @@ -57,33 +58,34 @@ def get_queryset(self, request): return qs.filter(owners=request.user) def get_fieldsets(self, request, obj=None): - general_fields = ['title', 'id', 'description', 'image'] + general_fields = ["title", "id", "description", "image"] if request.user.is_superuser or obj is not None: - general_fields.append('owners') - general_fields += ['players'] + general_fields.append("owners") + general_fields += ["players"] return ( - (None, { - 'fields': general_fields - }), - ('Endpoint', { - 'fields': ('endpoint_url', 'refresh_interval', 'token'), - }), + (None, {"fields": general_fields}), + ( + "Endpoint", + { + "fields": ("endpoint_url", "refresh_interval", "token"), + }, + ), ) def get_changeform_initial_data(self, request): - return {'owners': (request.user,)} + return {"owners": (request.user,)} def get_readonly_fields(self, request, obj=None): - fields = super(BadgeAdmin, self).get_readonly_fields(request, obj) + fields = super().get_readonly_fields(request, obj) if not request.user.is_superuser: - fields = fields + ('owners',) + fields = (*fields, "owners") return fields def save_related(self, request, form, formsets, change): - super(BadgeAdmin, self).save_related(request, form, formsets, change) + super().save_related(request, form, formsets, change) if not form.instance.owners.exists(): form.instance.owners.add(request.user) diff --git a/badges/apps.py b/badges/apps.py index 5995093..6f6db7c 100644 --- a/badges/apps.py +++ b/badges/apps.py @@ -2,4 +2,4 @@ class ApiConfig(AppConfig): - name = 'badges' + name = "badges" diff --git a/badges/local_badge_client.py b/badges/local_badge_client.py index 97dc34b..80b1fe6 100644 --- a/badges/local_badge_client.py +++ b/badges/local_badge_client.py @@ -1,5 +1,5 @@ from abc import ABC -from typing import Union, List, Iterable +from collections.abc import Iterable from rest_framework.exceptions import APIException from rest_framework.status import HTTP_400_BAD_REQUEST @@ -9,25 +9,23 @@ from badgeupdater.models import BadgeUpdateRequest from players.models import Player -PlayerIterable = Iterable[Union[str, Player]] +PlayerIterable = Iterable[str | Player] class BadgeDoesNotExist(APIException): status_code = HTTP_400_BAD_REQUEST - default_detail = 'The Badge with given ID does not exist' + default_detail = "The Badge with given ID does not exist" class LocalBadgeClient(BadgeClient, ABC): def get_token(self, badge_id: str) -> str: try: return Badge.objects.get(id=badge_id).token - except Badge.DoesNotExist: - raise BadgeDoesNotExist + except Badge.DoesNotExist as e: + raise BadgeDoesNotExist from e - def get_badge_player_ids(self, request: BadgeUpdateRequest) -> List[str]: - return list(map( - lambda x: x.id if isinstance(x, Player) else x, - self.get_badge_players(request))) + def get_badge_player_ids(self, request: BadgeUpdateRequest) -> list[str]: + return [x.id if isinstance(x, Player) else x for x in self.get_badge_players(request)] def get_badge_players(self, request: BadgeUpdateRequest) -> PlayerIterable: raise NotImplementedError diff --git a/badges/models.py b/badges/models.py index 8e868d5..6f0e247 100644 --- a/badges/models.py +++ b/badges/models.py @@ -6,13 +6,16 @@ from django.db.models import Q from django.db.models.signals import post_delete from django.dispatch import receiver -from django_celery_beat.models import PeriodicTask, IntervalSchedule +from django_celery_beat.models import IntervalSchedule, PeriodicTask from django_hosts import reverse -from badges.token_generator import generate_token, MAX_TOKEN_LENGTH +from badges.token_generator import MAX_TOKEN_LENGTH, generate_token from badges.validators import ( - ImageMinResolutionValidator, ImageMaxResolutionValidator, - MaxFileSizeValidator, image_square_validator) + ImageMaxResolutionValidator, + ImageMinResolutionValidator, + MaxFileSizeValidator, + image_square_validator, +) from players.models import Player MIN_RES = settings.BADGE_IMAGE_MIN_RES @@ -21,7 +24,7 @@ class BadgeQuerySet(models.QuerySet): - not_automatic_q = Q(endpoint_url__isnull=True) | Q(endpoint_url__exact='') + not_automatic_q = Q(endpoint_url__isnull=True) | Q(endpoint_url__exact="") def automatic(self): return self.exclude(self.not_automatic_q) @@ -31,30 +34,27 @@ def not_automatic(self): class Badge(models.Model): - id = models.SlugField(primary_key=True, verbose_name='ID') + id = models.SlugField(primary_key=True, verbose_name="ID") title = models.CharField(max_length=256) description = models.TextField(blank=True) image = models.ImageField( - help_text=f'The image must be square between {MIN_RES}x{MIN_RES} and ' - f'{MAX_RES}x{MAX_RES} and must not exceed ' - f'{MaxFileSizeValidator.human_readable_size(MAX_SIZE)}.', + help_text=f"The image must be square between {MIN_RES}x{MIN_RES} and " + f"{MAX_RES}x{MAX_RES} and must not exceed " + f"{MaxFileSizeValidator.human_readable_size(MAX_SIZE)}.", validators=[ ImageMinResolutionValidator(MIN_RES), ImageMaxResolutionValidator(MAX_RES), image_square_validator, MaxFileSizeValidator(MAX_SIZE), - ] + ], ) owners = models.ManyToManyField(to=User, blank=True) - endpoint_url = models.URLField(blank=True, verbose_name='Endpoint URL') + endpoint_url = models.URLField(blank=True, verbose_name="Endpoint URL") refresh_interval = models.DurationField(null=True, blank=True) - token = models.CharField( - blank=True, max_length=MAX_TOKEN_LENGTH, default=generate_token, - editable=False) - periodic_task = models.OneToOneField( - to=PeriodicTask, on_delete=models.CASCADE, null=True, editable=False) + token = models.CharField(blank=True, max_length=MAX_TOKEN_LENGTH, default=generate_token, editable=False) + periodic_task = models.OneToOneField(to=PeriodicTask, on_delete=models.CASCADE, null=True, editable=False) players = models.ManyToManyField(to=Player, blank=True) @@ -65,7 +65,7 @@ def __init__(self, *args, **kwargs): self.__original_refresh_interval = self.refresh_interval def get_absolute_url(self): - return reverse('badge-detail', kwargs={'slug': self.id}, host='root') + return reverse("badge-detail", kwargs={"slug": self.id}, host="root") @property def is_automatic(self): @@ -75,7 +75,7 @@ def save(self, *args, **kwargs): if self.refresh_interval != self.__original_refresh_interval: self.__update_task() - super(Badge, self).save(*args, **kwargs) + super().save(*args, **kwargs) def __update_task(self): if self.refresh_interval is None and self.periodic_task is not None: @@ -85,20 +85,16 @@ def __update_task(self): def __create_or_update_task_object(self): if self.periodic_task is None: - kwargs = { - 'badge_id': self.id - } - task = PeriodicTask( - task='badgeupdater.server.tasks.update_badge_task', - kwargs=json.dumps(kwargs)) + kwargs = {"badge_id": self.id} + task = PeriodicTask(task="badgeupdater.server.tasks.update_badge_task", kwargs=json.dumps(kwargs)) else: task = self.periodic_task schedule, _ = IntervalSchedule.objects.get_or_create( - every=round(self.refresh_interval.total_seconds()), - period=IntervalSchedule.SECONDS) + every=round(self.refresh_interval.total_seconds()), period=IntervalSchedule.SECONDS + ) task.interval = schedule - task.name = f'Update {self.id} badge' + task.name = f"Update {self.id} badge" task.save() self.periodic_task = task diff --git a/badges/serializers.py b/badges/serializers.py index c06671e..9376608 100644 --- a/badges/serializers.py +++ b/badges/serializers.py @@ -6,4 +6,4 @@ class BadgeSerializer(serializers.ModelSerializer): class Meta: model = Badge - fields = ['id', 'title', 'description', 'image', 'players'] + fields = ("id", "title", "description", "image", "players") diff --git a/badges/tests.py b/badges/tests.py index 7ce503c..a39b155 100644 --- a/badges/tests.py +++ b/badges/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/badges/urls.py b/badges/urls.py index 598be22..ec815ba 100644 --- a/badges/urls.py +++ b/badges/urls.py @@ -1,23 +1,21 @@ from django.urls import path from badges.badgeclients import TestBadgeClient -from .views import BadgeDetailView, UpdateBadgeBearersView, BadgeRankingView + +from .views import BadgeDetailView, BadgeRankingView, UpdateBadgeBearersView global_urlpatterns = [ - path('', BadgeRankingView.as_view(), name='badge-ranking'), + path("", BadgeRankingView.as_view(), name="badge-ranking"), ] urlpatterns = [ - path('/', BadgeDetailView.as_view(), name='badge-detail'), + path("/", BadgeDetailView.as_view(), name="badge-detail"), ] admin_urlpatters = [ - path( - 'badge//update_bearers/', - UpdateBadgeBearersView.as_view(), - name='update-badge-bearers'), + path("badge//update_bearers/", UpdateBadgeBearersView.as_view(), name="update-badge-bearers"), ] badgeclients_urlpatterns = [ - path('test/', TestBadgeClient.as_view()), + path("test/", TestBadgeClient.as_view()), ] diff --git a/badges/validators.py b/badges/validators.py index 6930e84..4434efb 100644 --- a/badges/validators.py +++ b/badges/validators.py @@ -4,47 +4,35 @@ @deconstructible class ImageMinResolutionValidator: - def __init__(self, width: int, height: int = None): + def __init__(self, width: int, height: int | None = None): self.width = width self.height = width if height is None else height def __call__(self, value): if value.width < self.height or value.height < self.height: - raise ValidationError( - f'The image resolution must be at least ' - f'{self.width}x{self.height}.') + raise ValidationError(f"The image resolution must be at least " f"{self.width}x{self.height}.") def __eq__(self, o: object) -> bool: - return ( - isinstance(o, ImageMinResolutionValidator) and - self.width == o.width and - self.height == o.height - ) + return isinstance(o, ImageMinResolutionValidator) and self.width == o.width and self.height == o.height @deconstructible class ImageMaxResolutionValidator: - def __init__(self, width: int, height: int = None): + def __init__(self, width: int, height: int | None = None): self.width = width self.height = width if height is None else height def __call__(self, value): if value.width > self.height or value.height > self.height: - raise ValidationError( - f'The image resolution must be at most ' - f'{self.width}x{self.height}.') + raise ValidationError(f"The image resolution must be at most " f"{self.width}x{self.height}.") def __eq__(self, o: object) -> bool: - return ( - isinstance(o, ImageMaxResolutionValidator) and - self.width == o.width and - self.height == o.height - ) + return isinstance(o, ImageMaxResolutionValidator) and self.width == o.width and self.height == o.height def image_square_validator(value): if value.width != value.height: - raise ValidationError('The image dimensions must be a square.') + raise ValidationError("The image dimensions must be a square.") @deconstructible @@ -54,22 +42,20 @@ def __init__(self, size): @staticmethod def human_readable_size(size): - for unit in ['B', 'kB', 'MB', 'GB', 'TB']: - if size < 1000.: + # ruff: noqa: B007 + for unit in ["B", "kB", "MB", "GB", "TB"]: + if size < 1000.0: break - size /= 1000. + size /= 1000.0 return f"{size:.1f}{unit}" def __call__(self, value): if value.size > self.size: size_str = self.human_readable_size(self.size) - raise ValidationError(f'The file size cannot exceed {size_str}.') + raise ValidationError(f"The file size cannot exceed {size_str}.") return value def __eq__(self, o: object) -> bool: - return ( - isinstance(o, MaxFileSizeValidator) and - self.size == o.size - ) + return isinstance(o, MaxFileSizeValidator) and self.size == o.size diff --git a/badges/views.py b/badges/views.py index 95752b7..775eacc 100644 --- a/badges/views.py +++ b/badges/views.py @@ -10,16 +10,16 @@ from badgeupdater.server.badge_updater import BadgeUpdater from badgeupdater.server.errors import BadgeUpdateError from players.models import Player + from .models import Badge class BadgeRankingView(ListView): model = Player - template_name = 'badges/ranking.html' + template_name = "badges/ranking.html" def get_queryset(self): - players = Player.objects.all().annotate( - num_badges=Count('badge')).order_by('-num_badges') + players = Player.objects.all().annotate(num_badges=Count("badge")).order_by("-num_badges") for i in range(len(players)): players[i].rank = i + 1 @@ -32,21 +32,20 @@ def get_queryset(self): class BadgeDetailView(DetailView): model = Badge - slug_field = 'id' + slug_field = "id" class UpdateBadgeBearersView(SingleObjectMixin, View): model = Badge - slug_field = 'id' + slug_field = "id" def get(self, request, *args, **kwargs): badge = self.get_object() try: BadgeUpdater(badge).update_badge() - messages.info(request, 'Successfully updated the badge bearers!') - except (IOError, BadgeUpdateError, IntegrityError) as e: - messages.error(request, f'{e.__class__.__name__}: {e}') + messages.info(request, "Successfully updated the badge bearers!") + except (OSError, BadgeUpdateError, IntegrityError) as e: + messages.error(request, f"{e.__class__.__name__}: {e}") - return HttpResponseRedirect( - reverse('admin:badges_badge_change', args=[badge.pk])) + return HttpResponseRedirect(reverse("admin:badges_badge_change", args=[badge.pk])) diff --git a/badgeupdater/client/auth.py b/badgeupdater/client/auth.py index 6411583..32fe4fb 100644 --- a/badgeupdater/client/auth.py +++ b/badgeupdater/client/auth.py @@ -6,21 +6,21 @@ class HMACAuth(BaseAuthentication): def __init__(self, badge_client): - super(HMACAuth, self).__init__() + super().__init__() self.badge_client = badge_client def authenticate(self, request): - badge_id = request.data.get('id') - authorization = request.META.get('HTTP_AUTHORIZATION') + badge_id = request.data.get("id") + authorization = request.META.get("HTTP_AUTHORIZATION") if badge_id is None: - raise NotAuthenticated('Badge ID was not sent') + raise NotAuthenticated("Badge ID was not sent") if authorization is None: - raise NotAuthenticated('No Authorization header') - if not authorization.startswith(f'Token '): - raise NotAuthenticated('Invalid Authorization method') + raise NotAuthenticated("No Authorization header") + if not authorization.startswith("Token "): + raise NotAuthenticated("Invalid Authorization method") client_token = self.badge_client.get_token(badge_id) request_token = authorization.split()[1].strip() if not hmac.compare_digest(client_token, request_token): - raise NotAuthenticated('Invalid token') + raise NotAuthenticated("Invalid token") diff --git a/badgeupdater/client/badge_client.py b/badgeupdater/client/badge_client.py index 6fc59f8..14971e6 100644 --- a/badgeupdater/client/badge_client.py +++ b/badgeupdater/client/badge_client.py @@ -1,5 +1,3 @@ -from typing import List - from rest_framework.decorators import permission_classes from rest_framework.permissions import AllowAny from rest_framework.request import Request @@ -8,8 +6,7 @@ from badgeupdater.client.auth import HMACAuth from badgeupdater.models import BadgeUpdateRequest, BadgeUpdateResponse -from badgeupdater.serializers import ( - BadgeUpdateRequestSerializer, BadgeUpdateResponseSerializer) +from badgeupdater.serializers import BadgeUpdateRequestSerializer, BadgeUpdateResponseSerializer @permission_classes((AllowAny,)) @@ -34,9 +31,9 @@ def __create_response(self, badge_update_request: BadgeUpdateRequest): return BadgeUpdateResponse(players) def get_authenticators(self): - return HMACAuth(self), + return (HMACAuth(self),) - def get_badge_player_ids(self, request: BadgeUpdateRequest) -> List[str]: + def get_badge_player_ids(self, request: BadgeUpdateRequest) -> list[str]: raise NotImplementedError def get_token(self, badge_id: str) -> str: diff --git a/badgeupdater/models.py b/badgeupdater/models.py index e7e57a9..273c9eb 100644 --- a/badgeupdater/models.py +++ b/badgeupdater/models.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import List @dataclass @@ -9,4 +8,4 @@ class BadgeUpdateRequest: @dataclass class BadgeUpdateResponse: - players: List[str] + players: list[str] diff --git a/badgeupdater/server/auth.py b/badgeupdater/server/auth.py index a7b0760..a54a6e9 100644 --- a/badgeupdater/server/auth.py +++ b/badgeupdater/server/auth.py @@ -7,6 +7,6 @@ def __init__(self, token: str): self.token = token def __call__(self, r: PreparedRequest): - r.headers['Authorization'] = f'Token {self.token}' + r.headers["Authorization"] = f"Token {self.token}" return r diff --git a/badgeupdater/server/badge_updater.py b/badgeupdater/server/badge_updater.py index 0299010..dd48a00 100644 --- a/badgeupdater/server/badge_updater.py +++ b/badgeupdater/server/badge_updater.py @@ -3,25 +3,21 @@ from badges.models import Badge from badgeupdater.models import BadgeUpdateRequest, BadgeUpdateResponse -from badgeupdater.serializers import ( - BadgeUpdateRequestSerializer, BadgeUpdateResponseSerializer) +from badgeupdater.serializers import BadgeUpdateRequestSerializer, BadgeUpdateResponseSerializer from badgeupdater.server.auth import HmacAuth from badgeupdater.server.errors import BadgeUpdateError class BadgeUpdater: def __init__(self, badge: Badge): - assert badge.endpoint_url, 'Endpoint URL was not provided' - assert badge.token, 'Authorization token was not generated' + assert badge.endpoint_url, "Endpoint URL was not provided" + assert badge.token, "Authorization token was not generated" self.badge = badge def update_badge(self): body = self.__create_message_body() - response = requests.post( - self.badge.endpoint_url, - json=body, - auth=HmacAuth(self.badge.token)) + response = requests.post(self.badge.endpoint_url, json=body, auth=HmacAuth(self.badge.token)) if not response.ok: raise BadgeUpdateError(response.content, response.status_code) @@ -36,14 +32,12 @@ def __get_update_response(response) -> BadgeUpdateResponse: try: serializer.is_valid(raise_exception=True) except ValidationError as e: - raise BadgeUpdateError( - f'remote server sent an invalid response: {e}') + raise BadgeUpdateError(f"remote server sent an invalid response: {e}") from e return serializer.save() def __create_message_body(self): - request = BadgeUpdateRequest( - id=self.badge.id) + request = BadgeUpdateRequest(id=self.badge.id) serializer = BadgeUpdateRequestSerializer(instance=request) return serializer.data diff --git a/badgeupdater/server/errors.py b/badgeupdater/server/errors.py index cb6d6fb..e6f2873 100644 --- a/badgeupdater/server/errors.py +++ b/badgeupdater/server/errors.py @@ -6,6 +6,6 @@ def __init__(self, message, status_code=None): def __str__(self): message = self.message if self.status_code is not None: - message = f'HTTP {self.status_code}, body: {message}' + message = f"HTTP {self.status_code}, body: {message}" - return f'Could not update badge: {message}' + return f"Could not update badge: {message}" diff --git a/chombos/admin.py b/chombos/admin.py index a7db094..f792e68 100644 --- a/chombos/admin.py +++ b/chombos/admin.py @@ -5,17 +5,21 @@ class ChomboAdmin(admin.ModelAdmin): search_fields = ( - 'player__id', 'player__first_name', 'player__last_name', - 'player__nickname', 'timestamp', 'comment' + "player__id", + "player__first_name", + "player__last_name", + "player__nickname", + "timestamp", + "comment", ) - list_display = ('player', 'timestamp', 'comment') - list_display_links = ('player', 'timestamp') + list_display = ("player", "timestamp", "comment") + list_display_links = ("player", "timestamp") list_filter = ( - 'timestamp', - ('player', admin.RelatedOnlyFieldListFilter), + "timestamp", + ("player", admin.RelatedOnlyFieldListFilter), ) - ordering = ('-timestamp',) - date_hierarchy = 'timestamp' + ordering = ("-timestamp",) + date_hierarchy = "timestamp" admin.site.register(Chombo, ChomboAdmin) diff --git a/chombos/apps.py b/chombos/apps.py index 444f344..aa5ca8f 100644 --- a/chombos/apps.py +++ b/chombos/apps.py @@ -2,4 +2,4 @@ class ChombosConfig(AppConfig): - name = 'chombos' + name = "chombos" diff --git a/chombos/badgeclients.py b/chombos/badgeclients.py index 38e2103..823f6e5 100644 --- a/chombos/badgeclients.py +++ b/chombos/badgeclients.py @@ -6,17 +6,21 @@ class ChombosBadgeClient(LocalBadgeClient): def get_badge_players(self, request: BadgeUpdateRequest) -> PlayerIterable: - return Chombo.objects.values_list('player') + return Chombo.objects.values_list("player") class ChombosInRangeClient(LocalBadgeClient): def get_badge_players(self, request: BadgeUpdateRequest) -> PlayerIterable: - a = int(self.request.query_params.get('a')) - b = self.request.query_params.get('b') + a = int(self.request.query_params.get("a")) + b = self.request.query_params.get("b") if b is None: - pred = lambda c: a <= c + + def pred(c): + return a <= c else: b = int(b) - pred = lambda c: a <= c <= b - return filter(lambda p: pred(p.chombo_set.count()), - Player.objects.all()) + + def pred(c): + return a <= c <= b + + return filter(lambda p: pred(p.chombo_set.count()), Player.objects.all()) diff --git a/chombos/models.py b/chombos/models.py index 382ef89..fd0d217 100644 --- a/chombos/models.py +++ b/chombos/models.py @@ -9,4 +9,4 @@ class Chombo(models.Model): comment = models.TextField(blank=True) def __str__(self): - return f'{self.player} at {self.timestamp}' + return f"{self.player} at {self.timestamp}" diff --git a/chombos/serializers.py b/chombos/serializers.py index 0c9a2c8..937fd79 100644 --- a/chombos/serializers.py +++ b/chombos/serializers.py @@ -6,4 +6,4 @@ class ChomboSerializer(serializers.ModelSerializer): class Meta: model = Chombo - fields = '__all__' + fields = "__all__" diff --git a/chombos/tests.py b/chombos/tests.py index 7ce503c..a39b155 100644 --- a/chombos/tests.py +++ b/chombos/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/chombos/urls.py b/chombos/urls.py index 1f7f71d..b40a3b9 100644 --- a/chombos/urls.py +++ b/chombos/urls.py @@ -3,6 +3,6 @@ from chombos.badgeclients import ChombosBadgeClient, ChombosInRangeClient badgeclients_urlpatterns = [ - path('chombos/', ChombosBadgeClient.as_view()), - path('in-range/', ChombosInRangeClient.as_view()), + path("chombos/", ChombosBadgeClient.as_view()), + path("in-range/", ChombosInRangeClient.as_view()), ] diff --git a/chombos/views.py b/chombos/views.py index 91ea44a..60f00ef 100644 --- a/chombos/views.py +++ b/chombos/views.py @@ -1,3 +1 @@ -from django.shortcuts import render - # Create your views here. diff --git a/kcc3/__init__.py b/kcc3/__init__.py index 15d7c50..5568b6d 100644 --- a/kcc3/__init__.py +++ b/kcc3/__init__.py @@ -2,4 +2,4 @@ # Django starts so that shared_task will use this app. from .celery import app as celery_app -__all__ = ('celery_app',) +__all__ = ("celery_app",) diff --git a/kcc3/celery.py b/kcc3/celery.py index 21150a3..4035594 100644 --- a/kcc3/celery.py +++ b/kcc3/celery.py @@ -3,16 +3,16 @@ from celery import Celery # set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kcc3.settings.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "kcc3.settings.settings") -app = Celery('kcc3') +app = Celery("kcc3") # Using a string here means the worker doesn't have to serialize # the configuration object to child processes. # - namespace='CELERY' means all celery-related configuration keys # should have a `CELERY_` prefix. -app.config_from_object('django.conf:settings', namespace='CELERY') +app.config_from_object("django.conf:settings", namespace="CELERY") # Load task modules from all registered Django app configs. app.autodiscover_tasks() -app.autodiscover_tasks(('badgeupdater.server.tasks',)) +app.autodiscover_tasks(("badgeupdater.server.tasks",)) diff --git a/kcc3/hosts.py b/kcc3/hosts.py index f76bd7a..1734043 100644 --- a/kcc3/hosts.py +++ b/kcc3/hosts.py @@ -1,7 +1,7 @@ -from django_hosts import patterns, host +from django_hosts import host, patterns host_patterns = patterns( - '', - host(r'fanpai', 'kcc3.urls', name='root'), - host(r'yakuman', 'yakumans.urls', name='yakumans'), + "", + host(r"fanpai", "kcc3.urls", name="root"), + host(r"yakuman", "yakumans.urls", name="yakumans"), ) diff --git a/kcc3/settings/base.py b/kcc3/settings/base.py index a11560f..7a06834 100644 --- a/kcc3/settings/base.py +++ b/kcc3/settings/base.py @@ -13,93 +13,92 @@ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__)))) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Application definition DJANGO_APPS = [ - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", ] THIRD_PARTY_APPS = [ - 'django_celery_beat', - 'django_hosts', - 'rest_framework', - 'rest_framework.authtoken', + "django_celery_beat", + "django_hosts", + "rest_framework", + "rest_framework.authtoken", ] PROJECT_APPS = [ - 'admin.apps.Kcc3AdminConfig', - 'badges', - 'players', - 'chombos', - 'yakumans', + "admin.apps.Kcc3AdminConfig", + "badges", + "players", + "chombos", + "yakumans", ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS MIDDLEWARE = [ - 'django_hosts.middleware.HostsRequestMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django_hosts.middleware.HostsResponseMiddleware', + "django_hosts.middleware.HostsRequestMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django_hosts.middleware.HostsResponseMiddleware", ] -ROOT_URLCONF = 'kcc3.urls' -ROOT_HOSTCONF = 'kcc3.hosts' -DEFAULT_HOST = 'root' +ROOT_URLCONF = "kcc3.urls" +ROOT_HOSTCONF = "kcc3.hosts" +DEFAULT_HOST = "root" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'builtins': [ - 'django_hosts.templatetags.hosts_override', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "builtins": [ + "django_hosts.templatetags.hosts_override", ], - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'kcc3.wsgi.application' +WSGI_APPLICATION = "kcc3.wsgi.application" # Database -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # Password validation # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -107,9 +106,9 @@ # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -122,20 +121,20 @@ # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'static'), + os.path.join(BASE_DIR, "static"), ] -STATIC_URL = '/static/' +STATIC_URL = "/static/" # REST Framework REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.SessionAuthentication', - 'rest_framework.authentication.TokenAuthentication', + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.SessionAuthentication", + "rest_framework.authentication.TokenAuthentication", ], - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticatedOrReadOnly', + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticatedOrReadOnly", ], } diff --git a/kcc3/settings/settings.py b/kcc3/settings/settings.py index 36f9e98..9cb13e0 100644 --- a/kcc3/settings/settings.py +++ b/kcc3/settings/settings.py @@ -1,40 +1,40 @@ -from .base import * +# ruff: noqa: F403 +# ruff: noqa: F405 +from .base import * # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'CHANGE_ME') +SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "CHANGE_ME") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = bool(os.environ.get('DEBUG', default=1)) +DEBUG = bool(os.environ.get("DEBUG", default=1)) -ALLOWED_HOSTS = os.environ.get( - 'DJANGO_ALLOWED_HOSTS', - 'fanpai.localhost,yakuman.localhost,localhost,127.0.0.1' -).split(',') -PARENT_HOST = os.environ.get('DJANGO_PARENT_HOST', 'localhost:8000') +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "fanpai.localhost,yakuman.localhost,localhost,127.0.0.1").split( + "," +) +PARENT_HOST = os.environ.get("DJANGO_PARENT_HOST", "localhost:8000") # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'), - 'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')), - 'USER': os.environ.get('SQL_USER', None), - 'PASSWORD': os.environ.get('SQL_PASSWORD', None), - 'HOST': os.environ.get('SQL_HOST', None), - 'PORT': os.environ.get('SQL_PORT', None), + "default": { + "ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"), + "NAME": os.environ.get("SQL_DATABASE", os.path.join(BASE_DIR, "db.sqlite3")), + "USER": os.environ.get("SQL_USER", None), + "PASSWORD": os.environ.get("SQL_PASSWORD", None), + "HOST": os.environ.get("SQL_HOST", None), + "PORT": os.environ.get("SQL_PORT", None), } } -MEDIA_ROOT = os.environ.get('MEDIA_ROOT', os.path.join(BASE_DIR, 'media')) -MEDIA_URL = '/media/' -STATIC_ROOT = os.environ.get('STATIC_ROOT', os.path.join(BASE_DIR, 'staticfiles')) +MEDIA_ROOT = os.environ.get("MEDIA_ROOT", os.path.join(BASE_DIR, "media")) +MEDIA_URL = "/media/" +STATIC_ROOT = os.environ.get("STATIC_ROOT", os.path.join(BASE_DIR, "staticfiles")) -CELERY_BROKER_URL = os.environ.get( - 'CELERY_BROKER', 'amqp://guest:guest@localhost:5672//') +CELERY_BROKER_URL = os.environ.get("CELERY_BROKER", "amqp://guest:guest@localhost:5672//") diff --git a/kcc3/urls.py b/kcc3/urls.py index 48f15ff..6d543ab 100644 --- a/kcc3/urls.py +++ b/kcc3/urls.py @@ -16,7 +16,7 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin -from django.urls import path, include +from django.urls import include, path from rest_framework import routers import badges.urls @@ -27,29 +27,27 @@ from players.viewsets import PlayerViewSet router = routers.DefaultRouter() -router.register('chombos', ChomboViewSet) -router.register('badges', BadgeViewSet) -router.register('players', PlayerViewSet) +router.register("chombos", ChomboViewSet) +router.register("badges", BadgeViewSet) +router.register("players", PlayerViewSet) admin_urlpatterns = [ - path('badges/', include(badges.urls.admin_urlpatters)), - path('', admin.site.urls), + path("badges/", include(badges.urls.admin_urlpatters)), + path("", admin.site.urls), ] badgeclients_urlpatterns = [ - path('badges/', include(badges.urls.badgeclients_urlpatterns)), - path('chombos/', include(chombos.urls.badgeclients_urlpatterns)), + path("badges/", include(badges.urls.badgeclients_urlpatterns)), + path("chombos/", include(chombos.urls.badgeclients_urlpatterns)), ] urlpatterns = [ - path('api/', include(router.urls)), - path('api-auth/', - include('rest_framework.urls', namespace='rest_framework')), - - path('admin/', include(admin_urlpatterns)), - path('badge-clients/', include(badgeclients_urlpatterns)), - - path('', include(badges.urls.global_urlpatterns)), - path('badges/', include(badges.urls.urlpatterns)), - path('players/', include(players.urls.urlpatterns)), -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + path("api/", include(router.urls)), + path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), + path("admin/", include(admin_urlpatterns)), + path("badge-clients/", include(badgeclients_urlpatterns)), + path("", include(badges.urls.global_urlpatterns)), + path("badges/", include(badges.urls.urlpatterns)), + path("players/", include(players.urls.urlpatterns)), + *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), +] diff --git a/kcc3/wsgi.py b/kcc3/wsgi.py index 780ea0d..140c9a4 100644 --- a/kcc3/wsgi.py +++ b/kcc3/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kcc3.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "kcc3.settings") application = get_wsgi_application() diff --git a/manage.py b/manage.py index 9ff183a..e2e8569 100755 --- a/manage.py +++ b/manage.py @@ -5,7 +5,7 @@ def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kcc3.settings.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "kcc3.settings.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -17,5 +17,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/players/admin.py b/players/admin.py index 42cc8ba..b4c9f15 100644 --- a/players/admin.py +++ b/players/admin.py @@ -1,19 +1,18 @@ +from collections.abc import Iterable +from typing import ClassVar + from django.contrib import admin from players.models import Player class PlayerAdmin(admin.ModelAdmin): - prepopulated_fields = {'id': ('nickname',)} + prepopulated_fields: ClassVar[dict[str, Iterable[str]]] = {"id": ("nickname",)} - search_fields = ( - 'id', 'first_name', 'last_name', 'nickname', 'usma_id', - 'discord_id') - list_display = ( - 'id', 'user', 'first_name', 'last_name', 'nickname', 'usma_id', - 'discord_id') - list_display_links = ('id', 'first_name', 'last_name', 'nickname') - ordering = ('id',) + search_fields = ("id", "first_name", "last_name", "nickname", "usma_id", "discord_id") + list_display = ("id", "user", "first_name", "last_name", "nickname", "usma_id", "discord_id") + list_display_links = ("id", "first_name", "last_name", "nickname") + ordering = ("id",) admin.site.register(Player, PlayerAdmin) diff --git a/players/apps.py b/players/apps.py index fb25831..40072b6 100644 --- a/players/apps.py +++ b/players/apps.py @@ -2,4 +2,4 @@ class CommonConfig(AppConfig): - name = 'players' + name = "players" diff --git a/players/models.py b/players/models.py index c458d21..13533dd 100644 --- a/players/models.py +++ b/players/models.py @@ -5,40 +5,36 @@ class Player(models.Model): - id = models.SlugField(primary_key=True, verbose_name='ID') + id = models.SlugField(primary_key=True, verbose_name="ID") - user = models.OneToOneField( - User, on_delete=models.SET_NULL, null=True, blank=True) + user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True, blank=True) first_name = models.CharField(max_length=30, blank=True) last_name = models.CharField(max_length=150, blank=True) nickname = models.CharField(max_length=50, blank=True) - usma_id = models.CharField( - max_length=30, blank=True, verbose_name='USMA ID') - discord_id = models.CharField( - max_length=30, blank=True, verbose_name='Discord ID') + usma_id = models.CharField(max_length=30, blank=True, verbose_name="USMA ID") + discord_id = models.CharField(max_length=30, blank=True, verbose_name="Discord ID") def clean(self): has_name = self.first_name and self.last_name has_nickname = self.nickname if not (has_name or has_nickname): - raise ValidationError( - {'first_name': 'Either full name, or nickname is required'}) + raise ValidationError({"first_name": "Either full name, or nickname is required"}) def get_absolute_url(self): - return reverse('player-detail', kwargs={'pk': self.pk}, host='root') + return reverse("player-detail", kwargs={"pk": self.pk}, host="root") def __str__(self): - s = '' + s = "" if self.first_name and self.last_name: - s = f'{self.first_name} {self.last_name}' + s = f"{self.first_name} {self.last_name}" if self.nickname: if s: - s += f' ({self.nickname})' + s += f" ({self.nickname})" else: s = self.nickname diff --git a/players/serializers.py b/players/serializers.py index dc76a73..b2bd779 100644 --- a/players/serializers.py +++ b/players/serializers.py @@ -6,10 +6,5 @@ class PlayerSerializer(serializers.ModelSerializer): class Meta: model = Player - fields = [ - 'id', 'first_name', 'last_name', 'nickname', 'badge_set', - 'usma_id', 'discord_id' - ] - read_only_fields = [ - 'badge_set' - ] + fields = ("id", "first_name", "last_name", "nickname", "badge_set", "usma_id", "discord_id") + read_only_fields = ("badge_set",) diff --git a/players/tests.py b/players/tests.py index 7ce503c..a39b155 100644 --- a/players/tests.py +++ b/players/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/players/urls.py b/players/urls.py index 94b176c..2b25e39 100644 --- a/players/urls.py +++ b/players/urls.py @@ -3,5 +3,5 @@ from .views import PlayerDetailView urlpatterns = [ - path('/', PlayerDetailView.as_view(), name='player-detail'), + path("/", PlayerDetailView.as_view(), name="player-detail"), ] diff --git a/pyproject.toml b/pyproject.toml index c90d6f7..8a062e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,9 @@ sqlparse = "0.4.4" [tool.ruff] line-length = 120 target-version = "py311" +exclude = [ + "*/migrations" +] [tool.ruff.lint] select = [ diff --git a/src/kcc3/__init__.py b/src/kcc3/__init__.py deleted file mode 100644 index 4753037..0000000 --- a/src/kcc3/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -def hello(): - return "Hello from kcc3!" diff --git a/static/images/logo.svg b/static/images/logo.svg index 4900327..cc2d0bb 100644 --- a/static/images/logo.svg +++ b/static/images/logo.svg @@ -47,4 +47,4 @@ id="path30" /> \ No newline at end of file + id="path32" /> diff --git a/yakumans/admin.py b/yakumans/admin.py index 6b5fafd..7496b6a 100644 --- a/yakumans/admin.py +++ b/yakumans/admin.py @@ -9,7 +9,7 @@ class YakumanFilter(FieldListFilter): - title = 'yaku' + title = "yaku" def __init__(self, field, request, params, model, model_admin, field_path): self.lookup_kwarg = field_path @@ -21,7 +21,7 @@ def lookups(self, request, model_admin): def queryset(self, request, queryset): if self.lookup_val: - return queryset.filter(yaku__contains=f'{self.lookup_val};') + return queryset.filter(yaku__contains=f"{self.lookup_val};") return queryset @@ -30,23 +30,21 @@ def expected_parameters(self): def choices(self, changelist): yield { - 'selected': self.lookup_val is None, - 'query_string': changelist.get_query_string( - remove=[self.lookup_kwarg]), - 'display': 'All', + "selected": self.lookup_val is None, + "query_string": changelist.get_query_string(remove=[self.lookup_kwarg]), + "display": "All", } for yaku in yakumans.YAKUMANS_BY_NAME: yield { - 'selected': self.lookup_val == yaku.id, - 'query_string': changelist.get_query_string( - {self.lookup_kwarg: yaku.id}), - 'display': yaku.name, + "selected": self.lookup_val == yaku.id, + "query_string": changelist.get_query_string({self.lookup_kwarg: yaku.id}), + "display": yaku.name, } class YakumanAdminForm(forms.ModelForm): def clean_picture(self): - orig_file = self.cleaned_data['picture'] + orig_file = self.cleaned_data["picture"] if orig_file is None: return orig_file @@ -62,22 +60,28 @@ def clean_picture(self): class YakumanAdmin(admin.ModelAdmin): form = YakumanAdminForm search_fields = ( - 'winner__id', 'winner__first_name', 'winner__last_name', - 'winner__nickname', - 'loser__id', 'loser__first_name', 'loser__last_name', - 'loser__nickname', - 'timestamp', 'yaku', 'comment' + "winner__id", + "winner__first_name", + "winner__last_name", + "winner__nickname", + "loser__id", + "loser__first_name", + "loser__last_name", + "loser__nickname", + "timestamp", + "yaku", + "comment", ) - list_display = ('timestamp', 'yaku', 'winner', 'loser') - list_display_links = ('timestamp', 'winner') + list_display = ("timestamp", "yaku", "winner", "loser") + list_display_links = ("timestamp", "winner") list_filter = ( - 'timestamp', - ('yaku', YakumanFilter), - ('winner', admin.RelatedOnlyFieldListFilter), - ('loser', admin.RelatedOnlyFieldListFilter), + "timestamp", + ("yaku", YakumanFilter), + ("winner", admin.RelatedOnlyFieldListFilter), + ("loser", admin.RelatedOnlyFieldListFilter), ) - ordering = ('-timestamp',) - date_hierarchy = 'timestamp' + ordering = ("-timestamp",) + date_hierarchy = "timestamp" admin.site.register(Yakuman, YakumanAdmin) diff --git a/yakumans/apps.py b/yakumans/apps.py index af3ec5c..080a743 100644 --- a/yakumans/apps.py +++ b/yakumans/apps.py @@ -2,4 +2,4 @@ class YakumansConfig(AppConfig): - name = 'yakumans' + name = "yakumans" diff --git a/yakumans/forms.py b/yakumans/forms.py index da6447c..6b662d9 100644 --- a/yakumans/forms.py +++ b/yakumans/forms.py @@ -1,4 +1,4 @@ -from django.forms import TypedMultipleChoiceField, SelectMultiple +from django.forms import SelectMultiple, TypedMultipleChoiceField from yakumans import yakumans from yakumans.yakumans import yakuman_by_name @@ -12,8 +12,7 @@ def format_value(self, value): return [x.name for x in value] def value_from_datadict(self, data, files, name): - value = super(YakumansSelectMultiple, self).value_from_datadict( - data, files, name) + value = super().value_from_datadict(data, files, name) print(value) return [yakumans.yakuman_by_name(yaku_id) for yaku_id in value] @@ -23,10 +22,8 @@ class YakumansChoiceField(TypedMultipleChoiceField): widget = YakumansSelectMultiple def __init__(self, **kwargs): - kwargs['empty_value'] = [] - kwargs['choices'] = ( - (yaku, yaku.verbose_name) for yaku in yakumans.YAKUMANS_BY_NAME - ) - kwargs['coerce'] = yakuman_by_name + kwargs["empty_value"] = [] + kwargs["choices"] = ((yaku, yaku.verbose_name) for yaku in yakumans.YAKUMANS_BY_NAME) + kwargs["coerce"] = yakuman_by_name - super(YakumansChoiceField, self).__init__(**kwargs) + super().__init__(**kwargs) diff --git a/yakumans/metadata_stripper.py b/yakumans/metadata_stripper.py index ef04d6a..6ca340d 100644 --- a/yakumans/metadata_stripper.py +++ b/yakumans/metadata_stripper.py @@ -1,9 +1,9 @@ -from io import BytesIO import subprocess +from io import BytesIO def strip_exif(fp): - args = ('exiftool', '-all=', '-') + args = ("exiftool", "-all=", "-") proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) out, err = proc.communicate(input=fp.read()) diff --git a/yakumans/models.py b/yakumans/models.py index 59a0cb4..84b6e1b 100644 --- a/yakumans/models.py +++ b/yakumans/models.py @@ -1,5 +1,3 @@ -from typing import List - from django.db import models from django_hosts import reverse @@ -11,7 +9,7 @@ class YakumansField(models.Field): def __init__(self, *args, **kwargs): - kwargs['max_length'] = 200 + kwargs["max_length"] = 200 super().__init__(*args, **kwargs) def deconstruct(self): @@ -20,11 +18,11 @@ def deconstruct(self): return name, path, args, kwargs def get_internal_type(self): - return 'CharField' + return "CharField" @staticmethod - def __parse_yakuman_list(s: str) -> List[yakumans.Yakuman]: - return [yakuman_by_id(yaku_id) for yaku_id in s.split(';') if yaku_id] + def __parse_yakuman_list(s: str) -> list[yakumans.Yakuman]: + return [yakuman_by_id(yaku_id) for yaku_id in s.split(";") if yaku_id] def from_db_value(self, value, expression, connection): if value is None: @@ -32,7 +30,7 @@ def from_db_value(self, value, expression, connection): return self.__parse_yakuman_list(value) - def to_python(self, value) -> List[yakumans.Yakuman]: + def to_python(self, value) -> list[yakumans.Yakuman]: if isinstance(value, list): return value @@ -42,10 +40,7 @@ def to_python(self, value) -> List[yakumans.Yakuman]: return self.__parse_yakuman_list(value) def get_prep_value(self, value): - return ''.join( - f'{yaku.id};' if isinstance(yaku, yakumans.Yakuman) else f'{yaku};' - for yaku in value - ) + return "".join(f"{yaku.id};" if isinstance(yaku, yakumans.Yakuman) else f"{yaku};" for yaku in value) def formfield(self, **kwargs): return YakumansChoiceField(**kwargs) @@ -56,25 +51,24 @@ class Yakuman(models.Model): yaku = YakumansField() winner = models.ForeignKey( - to=Player, on_delete=models.CASCADE, - related_name='yakumans_won', - help_text='The player who has got the yakuman' + to=Player, on_delete=models.CASCADE, related_name="yakumans_won", help_text="The player who has got the yakuman" ) loser = models.ForeignKey( - to=Player, on_delete=models.CASCADE, null=True, blank=True, - related_name='yakumans_lost', - help_text='The player that has dealt in, if any' + to=Player, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="yakumans_lost", + help_text="The player that has dealt in, if any", ) is_tsumo = models.BooleanField() - picture = models.ImageField(upload_to='yakumans/', blank=True) + picture = models.ImageField(upload_to="yakumans/", blank=True) comment = models.TextField(blank=True) def get_absolute_url(self): - return reverse( - 'yakuman-detail', host='yakumans', kwargs={'pk': self.pk} - ) + return reverse("yakuman-detail", host="yakumans", kwargs={"pk": self.pk}) def __str__(self): - yaku_str = ', '.join(x.name for x in self.yaku) - return f'{yaku_str} by {self.winner} at {self.timestamp}' + yaku_str = ", ".join(x.name for x in self.yaku) + return f"{yaku_str} by {self.winner} at {self.timestamp}" diff --git a/yakumans/tests.py b/yakumans/tests.py index 7ce503c..a39b155 100644 --- a/yakumans/tests.py +++ b/yakumans/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/yakumans/urls.py b/yakumans/urls.py index 5d37dfa..58f1750 100644 --- a/yakumans/urls.py +++ b/yakumans/urls.py @@ -5,6 +5,7 @@ from yakumans.views import YakumanDetailView, YakumanListView urlpatterns = [ - path('', YakumanListView.as_view(), name='yakuman-list'), - path('/', YakumanDetailView.as_view(), name='yakuman-detail'), -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + path("", YakumanListView.as_view(), name="yakuman-list"), + path("/", YakumanDetailView.as_view(), name="yakuman-detail"), + *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), +] diff --git a/yakumans/views.py b/yakumans/views.py index 9a7f225..d51ba3b 100644 --- a/yakumans/views.py +++ b/yakumans/views.py @@ -5,9 +5,9 @@ class YakumanListView(ListView): model = Yakuman - ordering = ('-timestamp',) + ordering = ("-timestamp",) class YakumanDetailView(DetailView): model = Yakuman - slug_field = 'id' + slug_field = "id" diff --git a/yakumans/yakumans.py b/yakumans/yakumans.py index b22cd82..0b71baf 100644 --- a/yakumans/yakumans.py +++ b/yakumans/yakumans.py @@ -18,200 +18,200 @@ class Yakuman: @property def verbose_name(self): - return f'{self.name} ({self.japanese_name})' + return f"{self.name} ({self.japanese_name})" def __str__(self): return self.name KAZOE_YAKUMAN = Yakuman( - id='KAZOE_YAKUMAN', - name='Kazoe Yakuman', - japanese_name='数え役満', + id="KAZOE_YAKUMAN", + name="Kazoe Yakuman", + japanese_name="数え役満", type=YakumanType.REGULAR, double=False, ) KOKUSHI_MUSOU = Yakuman( - id='KOKUSHI_MUSOU', - name='Kokushi musou', - japanese_name='国士無双', + id="KOKUSHI_MUSOU", + name="Kokushi musou", + japanese_name="国士無双", type=YakumanType.REGULAR, double=False, ) KOKUSHI_MUSOU_JUUSAN_MENMACHI = Yakuman( - id='KOKUSHI_MUSOU_JUUSAN_MENMACHI', - name='Kokushi musou juusan menmachi', - japanese_name='国士無双13面待ち', + id="KOKUSHI_MUSOU_JUUSAN_MENMACHI", + name="Kokushi musou juusan menmachi", + japanese_name="国士無双13面待ち", type=YakumanType.REGULAR, double=True, ) SUUANKOU = Yakuman( - id='SUUANKOU', - name='Suuankou', - japanese_name='四暗刻', + id="SUUANKOU", + name="Suuankou", + japanese_name="四暗刻", type=YakumanType.REGULAR, double=False, ) SUUANKOU_TANKI = Yakuman( - id='SUUANKOU_TANKI', - name='Suuankou tanki', - japanese_name='四暗刻単騎', + id="SUUANKOU_TANKI", + name="Suuankou tanki", + japanese_name="四暗刻単騎", type=YakumanType.REGULAR, double=True, ) DAISANGEN = Yakuman( - id='DAISANGEN', - name='Daisangen', - japanese_name='大三元', + id="DAISANGEN", + name="Daisangen", + japanese_name="大三元", type=YakumanType.REGULAR, double=False, ) SHOUSUUSHII = Yakuman( - id='SHOUSUUSHII', - name='Shousuushii', - japanese_name='小四喜', + id="SHOUSUUSHII", + name="Shousuushii", + japanese_name="小四喜", type=YakumanType.REGULAR, double=False, ) DAISUUSHII = Yakuman( - id='DAISUUSHII', - name='Daisuushii', - japanese_name='大四喜', + id="DAISUUSHII", + name="Daisuushii", + japanese_name="大四喜", type=YakumanType.REGULAR, double=True, ) TSUUIISOU = Yakuman( - id='TSUUIISOU', - name='Tsuuiisou', - japanese_name='字一色', + id="TSUUIISOU", + name="Tsuuiisou", + japanese_name="字一色", type=YakumanType.REGULAR, double=False, ) CHINROUTOU = Yakuman( - id='CHINROUTOU', - name='Chinroutou', - japanese_name='清老頭', + id="CHINROUTOU", + name="Chinroutou", + japanese_name="清老頭", type=YakumanType.REGULAR, double=False, ) RYUUIISOU = Yakuman( - id='RYUUIISOU', - name='Ryuuiisou', - japanese_name='緑一色', + id="RYUUIISOU", + name="Ryuuiisou", + japanese_name="緑一色", type=YakumanType.REGULAR, double=False, ) CHUUREN_POUTOU = Yakuman( - id='CHUUREN_POUTOU', - name='Chuuren poutou', - japanese_name='九連宝燈', + id="CHUUREN_POUTOU", + name="Chuuren poutou", + japanese_name="九連宝燈", type=YakumanType.REGULAR, double=False, ) JUNSEI_CHUUREN_POUTOU = Yakuman( - id='JUNSEI_CHUUREN_POUTOU', - name='Junsei chuuren poutou', - japanese_name='純正九蓮宝燈', + id="JUNSEI_CHUUREN_POUTOU", + name="Junsei chuuren poutou", + japanese_name="純正九蓮宝燈", type=YakumanType.REGULAR, double=True, ) SUUKANTSU = Yakuman( - id='SUUKANTSU', - name='Suukantsu', - japanese_name='四槓子', + id="SUUKANTSU", + name="Suukantsu", + japanese_name="四槓子", type=YakumanType.REGULAR, double=False, ) TENHOU = Yakuman( - id='TENHOU', - name='Tenhou', - japanese_name='天和', + id="TENHOU", + name="Tenhou", + japanese_name="天和", type=YakumanType.INITIAL, double=False, ) CHIIHOU = Yakuman( - id='CHIIHOU', - name='Chiihou', - japanese_name='地和', + id="CHIIHOU", + name="Chiihou", + japanese_name="地和", type=YakumanType.INITIAL, double=False, ) DAICHISEI = Yakuman( - id='DAICHISEI', - name='Daichisei', - japanese_name='大七星', + id="DAICHISEI", + name="Daichisei", + japanese_name="大七星", type=YakumanType.OPTIONAL, double=True, ) DAISHARIN = Yakuman( - id='DAISHARIN', - name='Daisharin', - japanese_name='大車輪', + id="DAISHARIN", + name="Daisharin", + japanese_name="大車輪", type=YakumanType.OPTIONAL, double=False, ) DAICHIKURIN = Yakuman( - id='DAICHIKURIN', - name='Daichikurin', - japanese_name='大竹林', + id="DAICHIKURIN", + name="Daichikurin", + japanese_name="大竹林", type=YakumanType.OPTIONAL, double=False, ) DAISUURIN = Yakuman( - id='DAISUURIN', - name='Daisuurin', - japanese_name='大数林', + id="DAISUURIN", + name="Daisuurin", + japanese_name="大数林", type=YakumanType.OPTIONAL, double=False, ) IISHOKU_YONJUN = Yakuman( - id='IISHOKU_YONJUN', - name='Iishoku yonjun', - japanese_name='一色四順', + id="IISHOKU_YONJUN", + name="Iishoku yonjun", + japanese_name="一色四順", type=YakumanType.OPTIONAL, double=False, ) OPEN_RIICHI = Yakuman( - id='OPEN_RIICHI', - name='Open riichi', - japanese_name='オープン立直', + id="OPEN_RIICHI", + name="Open riichi", + japanese_name="オープン立直", type=YakumanType.OPTIONAL, double=False, ) PAARENCHAN = Yakuman( - id='PAARENCHAN', - name='Paarenchan', - japanese_name='八連荘', + id="PAARENCHAN", + name="Paarenchan", + japanese_name="八連荘", type=YakumanType.OPTIONAL, double=False, ) RENHOU = Yakuman( - id='RENHOU', - name='Renhou', - japanese_name='人和', + id="RENHOU", + name="Renhou", + japanese_name="人和", type=YakumanType.OPTIONAL, double=False, ) SUURENKOU = Yakuman( - id='SUURENKOU', - name='Suurenkou', - japanese_name='四連刻', + id="SUURENKOU", + name="Suurenkou", + japanese_name="四連刻", type=YakumanType.OPTIONAL, double=False, ) SHIISANPUUTAA = Yakuman( - id='SHIISANPUUTAA', - name='Shiisanpuutaa', - japanese_name='十三不塔', + id="SHIISANPUUTAA", + name="Shiisanpuutaa", + japanese_name="十三不塔", type=YakumanType.OPTIONAL, double=False, ) SHIISUUPUUTAA = Yakuman( - id='SHIISUUPUUTAA', - name='Shiisuupuutaa', - japanese_name='十四不塔', + id="SHIISUUPUUTAA", + name="Shiisuupuutaa", + japanese_name="十四不塔", type=YakumanType.OPTIONAL, double=False, ) @@ -231,10 +231,8 @@ def __str__(self): CHUUREN_POUTOU, JUNSEI_CHUUREN_POUTOU, SUUKANTSU, - TENHOU, CHIIHOU, - DAICHISEI, DAISHARIN, DAICHIKURIN,