From 977fde44174366baaa61363cbbefbc383f81f363 Mon Sep 17 00:00:00 2001 From: "Yury V. Zaytsev" Date: Wed, 1 Nov 2023 15:32:54 +0100 Subject: [PATCH] refactor(models): switch enums to django built-in type --- config/settings.py | 1 + logbook/context_processors.py | 15 ++++++ .../management/commands/import_flightlog.py | 8 +-- logbook/models.py | 50 ++++++++----------- logbook/statistics/currency.py | 4 +- logbook/templates/logbook/logentry_list.html | 8 +-- logbook/views/aircraft.py | 2 - logbook/views/dashboard.py | 24 ++++----- logbook/views/entries.py | 8 +-- logbook/views/experience.py | 26 +++++----- 10 files changed, 73 insertions(+), 73 deletions(-) create mode 100644 logbook/context_processors.py diff --git a/config/settings.py b/config/settings.py index a0065b2..4c4859e 100644 --- a/config/settings.py +++ b/config/settings.py @@ -85,6 +85,7 @@ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", + "logbook.context_processors.enums", ], }, }, diff --git a/logbook/context_processors.py b/logbook/context_processors.py new file mode 100644 index 0000000..1ca1d5d --- /dev/null +++ b/logbook/context_processors.py @@ -0,0 +1,15 @@ +from .models import AircraftType, FunctionType, LaunchType, SpeedUnit +from .statistics.currency import CurrencyStatus + + +def enums(_): + return { + enum.__name__: enum + for enum in [ + AircraftType, + FunctionType, + LaunchType, + SpeedUnit, + CurrencyStatus, + ] + } diff --git a/logbook/management/commands/import_flightlog.py b/logbook/management/commands/import_flightlog.py index 5478d93..d53ccf8 100644 --- a/logbook/management/commands/import_flightlog.py +++ b/logbook/management/commands/import_flightlog.py @@ -84,11 +84,11 @@ def handle(self, *args, **options): launch_type = ( { - "Self": models.LaunchType.SELF.name, - "Tow": models.LaunchType.TOW.name, - "Winch": models.LaunchType.WINCH.name, + "Self": models.LaunchType.SELF, + "Tow": models.LaunchType.TOW, + "Winch": models.LaunchType.WINCH, }[remarks] - if aircraft.type == models.AircraftType.GLD.name + if aircraft.type == models.AircraftType.GLD else "" ) diff --git a/logbook/models.py b/logbook/models.py index 3bde963..ed6f176 100644 --- a/logbook/models.py +++ b/logbook/models.py @@ -1,5 +1,4 @@ from datetime import UTC, datetime -from enum import Enum, StrEnum from typing import Optional from django.core.exceptions import ValidationError @@ -10,32 +9,27 @@ from .statistics.currency import NinetyDaysCurrency, get_ninety_days_currency -class NameStrEnum(StrEnum): - def __str__(self) -> str: - return self.name +class AircraftType(models.TextChoices): + GLD = "GLD", "Glider" + TMG = "TMG", "Touring Motor Glider" + SEP = "SEP", "Single Engine Piston" -class AircraftType(Enum): - GLD = "Glider" - TMG = "Touring Motor Glider" - SEP = "Single Engine Piston" +class FunctionType(models.TextChoices): + PIC = "PIC", "Pilot-in-Command" + DUAL = "DUAL", "Dual instruction time" -class FunctionType(Enum): - PIC = "Pilot-in-Command" - DUAL = "Dual instruction time" +class LaunchType(models.TextChoices): + SELF = "SELF", "Self-launch" + WINCH = "WINCH", "Winch launch" + TOW = "TOW", "Aerotow" -class LaunchType(Enum): - SELF = "Self-launch" - WINCH = "Winch launch" - TOW = "Aerotow" - - -class SpeedUnit(NameStrEnum): - KMH = "km/h" - KT = "kt" - MPH = "mph" +class SpeedUnit(models.TextChoices): + KMH = "KMH", "km/h" + KT = "KT", "kt" + MPH = "MPH", "mph" class Aerodrome(models.Model): @@ -56,7 +50,7 @@ def __str__(self): class Aircraft(models.Model): - type = models.CharField(max_length=3, choices=[(at.name, at.value) for at in AircraftType]) + type = models.CharField(max_length=3, choices=AircraftType.choices) maker = models.CharField(max_length=64) model = models.CharField(max_length=64) @@ -67,7 +61,7 @@ class Aircraft(models.Model): currency_required = models.BooleanField(default=False) - speed_unit = models.CharField(max_length=3, choices=[(su.name, su.value) for su in SpeedUnit]) + speed_unit = models.CharField(max_length=3, choices=SpeedUnit.choices) v_r = models.PositiveSmallIntegerField(verbose_name="Vr", help_text="Rotation speed", blank=True, null=True) v_y = models.PositiveSmallIntegerField( @@ -118,12 +112,12 @@ class LogEntry(models.Model): landings = models.PositiveSmallIntegerField(default=1) - time_function = models.CharField(max_length=5, choices=[(ft.name, ft.value) for ft in FunctionType]) + time_function = models.CharField(max_length=5, choices=FunctionType.choices) pilot = models.ForeignKey(Pilot, on_delete=models.PROTECT, related_name="pilot_set") copilot = models.ForeignKey(Pilot, on_delete=models.PROTECT, related_name="copilot_set", blank=True, null=True) - launch_type = models.CharField(max_length=5, blank=True, choices=[(lt.name, lt.value) for lt in LaunchType]) + launch_type = models.CharField(max_length=5, blank=True, choices=LaunchType.choices) remarks = models.CharField(max_length=255, blank=True) @@ -138,8 +132,8 @@ class Meta: CheckConstraint(check=~Q(copilot=F("pilot")), name="copilot_not_pilot"), CheckConstraint( check=( - Q(time_function=FunctionType.PIC.name) # PIC time may be XC or not XC - | ~Q(time_function=FunctionType.PIC.name) & Q(cross_country=False) # non-PIC time must be non-XC + Q(time_function=FunctionType.PIC) # PIC time may be XC or not XC + | ~Q(time_function=FunctionType.PIC) & Q(cross_country=False) # non-PIC time must be non-XC ), name="no_pic_no_xc", ), @@ -167,7 +161,7 @@ def clean(self): # Check constraints can't reference other tables; it's possible via UDFs, but not universally supported by RDBs if ( self.aircraft_id is not None # checks if foreign key is set to avoid `RelatedObjectDoesNotExist` exception! - and self.aircraft.type == AircraftType.GLD.name + and self.aircraft.type == AircraftType.GLD and not self.launch_type ): raise ValidationError("Launch type is required for gliders!") diff --git a/logbook/statistics/currency.py b/logbook/statistics/currency.py index 2861e61..caaea3b 100644 --- a/logbook/statistics/currency.py +++ b/logbook/statistics/currency.py @@ -1,15 +1,15 @@ from dataclasses import dataclass from datetime import UTC, date, datetime, timedelta -from enum import StrEnum from typing import TYPE_CHECKING +from django.db import models from django.db.models import OuterRef, QuerySet, Subquery, Sum, Value if TYPE_CHECKING: from ..models import LogEntry -class CurrencyStatus(StrEnum): +class CurrencyStatus(models.TextChoices): CURRENT = "🟢" EXPIRING = "🟡" NOT_CURRENT = "🔴" diff --git a/logbook/templates/logbook/logentry_list.html b/logbook/templates/logbook/logentry_list.html index 2becc1e..b3e2f8a 100644 --- a/logbook/templates/logbook/logentry_list.html +++ b/logbook/templates/logbook/logentry_list.html @@ -16,12 +16,12 @@

Log entries

Type
Registration From
To Departure
Arrival - {% for aircraft_type in aircraft_types reversed %} + {% for aircraft_type in AircraftType reversed %} {{ aircraft_type.name }} {% endfor %} Landings Name - {% for function_type in function_types %} + {% for function_type in FunctionType %} {{ function_type.name }} {% endfor %} Remarks @@ -34,7 +34,7 @@

Log entries

{{ obj.aircraft.icao_designator }}
{{ obj.aircraft.registration }} {{ obj.from_aerodrome.icao_code }}
{{ obj.to_aerodrome.icao_code }} {{ obj.departure_time | time:"H:i" }}
{{ obj.arrival_time | time:"H:i" }} - {% for aircraft_type in aircraft_types reversed %} + {% for aircraft_type in AircraftType reversed %} {% if obj.aircraft.type == aircraft_type.name %} {{ obj.arrival_time | subtract:obj.departure_time | duration:"%h:%M" }} @@ -45,7 +45,7 @@

Log entries

{% endfor %} {{ obj.landings }} {% if not obj.pilot.self %}{{ obj.pilot.first_name.0 }}. {{ obj.pilot.last_name }}{% else %}Self{% endif %} - {% for function_type in function_types %} + {% for function_type in FunctionType %} {% if obj.time_function == function_type.name %} {{ obj.arrival_time | subtract:obj.departure_time | duration:"%h:%M" }} diff --git a/logbook/views/aircraft.py b/logbook/views/aircraft.py index 4ee0c2b..2b01bd5 100644 --- a/logbook/views/aircraft.py +++ b/logbook/views/aircraft.py @@ -1,5 +1,4 @@ from ..models import Aircraft -from ..statistics.currency import CurrencyStatus from .utils import AuthenticatedListView @@ -8,6 +7,5 @@ class AircraftIndexView(AuthenticatedListView): def get_context_data(self, *, object_list=None, **kwargs): return super().get_context_data(**kwargs) | { - "CurrencyStatus": CurrencyStatus.__members__, "aircraft_fields": {field.name: field for field in Aircraft._meta.get_fields()}, } diff --git a/logbook/views/dashboard.py b/logbook/views/dashboard.py index 9c5fc14..6fe5a8e 100644 --- a/logbook/views/dashboard.py +++ b/logbook/views/dashboard.py @@ -1,7 +1,7 @@ from django.db.models import QuerySet from ..models import Aircraft, AircraftType, FunctionType, LogEntry -from ..statistics.currency import CURRENCY_REQUIRED_LANDINGS_NIGHT, CurrencyStatus, get_ninety_days_currency +from ..statistics.currency import CURRENCY_REQUIRED_LANDINGS_NIGHT, get_ninety_days_currency from ..statistics.experience import compute_totals from .utils import ( AuthenticatedListView, @@ -14,25 +14,21 @@ class DashboardView(AuthenticatedListView): def get_context_data(self, *args, **kwargs): def totals_per_function(log_entries: QuerySet[LogEntry]): - return { - function.name: compute_totals(log_entries.filter(time_function=function.name)) - for function in FunctionType - } + return {function: compute_totals(log_entries.filter(time_function=function)) for function in FunctionType} return super().get_context_data(*args, **kwargs) | { - "CurrencyStatus": CurrencyStatus.__members__, "passenger_currency": { "sep": { "day": get_ninety_days_currency( LogEntry.objects.filter( - aircraft__type=AircraftType.SEP.name, - time_function=FunctionType.PIC.name, + aircraft__type=AircraftType.SEP, + time_function=FunctionType.PIC, ), ), "night": get_ninety_days_currency( LogEntry.objects.filter( - aircraft__type=AircraftType.SEP.name, - time_function=FunctionType.PIC.name, + aircraft__type=AircraftType.SEP, + time_function=FunctionType.PIC, night=True, ), required_landings=CURRENCY_REQUIRED_LANDINGS_NIGHT, @@ -41,14 +37,14 @@ def totals_per_function(log_entries: QuerySet[LogEntry]): "tmg": { "day": get_ninety_days_currency( LogEntry.objects.filter( - aircraft__type=AircraftType.TMG.name, - time_function=FunctionType.PIC.name, + aircraft__type=AircraftType.TMG, + time_function=FunctionType.PIC, ), ), "night": get_ninety_days_currency( LogEntry.objects.filter( - aircraft__type=AircraftType.TMG.name, - time_function=FunctionType.PIC.name, + aircraft__type=AircraftType.TMG, + time_function=FunctionType.PIC, night=True, ), required_landings=CURRENCY_REQUIRED_LANDINGS_NIGHT, diff --git a/logbook/views/entries.py b/logbook/views/entries.py index 15f63a6..a7b754d 100644 --- a/logbook/views/entries.py +++ b/logbook/views/entries.py @@ -10,7 +10,7 @@ from vereinsflieger.vereinsflieger import VereinsfliegerSession -from ..models import Aerodrome, Aircraft, AircraftType, FunctionType, LogEntry, Pilot +from ..models import Aerodrome, Aircraft, FunctionType, LogEntry, Pilot from .utils import AuthenticatedListView @@ -86,11 +86,7 @@ class EntryIndexView(AuthenticatedListView, FormView): success_url = reverse_lazy("logbook:entries") def get_context_data(self, *args, **kwargs): - return super().get_context_data(*args, **kwargs) | { - "aircraft_types": list(AircraftType), - "function_types": list(FunctionType), - "form": self.get_form(), - } + return super().get_context_data(*args, **kwargs) | {"form": self.get_form()} def paginate_queryset(self, queryset, page_size): entries = tuple(chain.from_iterable(([entry] + [None] * (entry.slots - 1)) for entry in queryset)) diff --git a/logbook/views/experience.py b/logbook/views/experience.py index 2ed8fdb..9bbc82c 100644 --- a/logbook/views/experience.py +++ b/logbook/views/experience.py @@ -35,7 +35,7 @@ def get_context_data(self, **kwargs): def get_sep_revalidation_experience(log_entries: QuerySet[LogEntry]) -> ExperienceRequirements: eligible_entries = log_entries.filter( - aircraft__type__in={AircraftType.SEP.name, AircraftType.TMG.name}, + aircraft__type__in={AircraftType.SEP, AircraftType.TMG}, departure_time__gte=make_aware( datetime.combine( Certificate.objects.get(name__contains="SEP").valid_until - relativedelta(months=12), @@ -52,7 +52,7 @@ def get_sep_revalidation_experience(log_entries: QuerySet[LogEntry]) -> Experien ), "6 hours as PIC": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=6), landings=0), - accrued=compute_totals(eligible_entries.filter(time_function=FunctionType.PIC.name)), + accrued=compute_totals(eligible_entries.filter(time_function=FunctionType.PIC)), ), "12 take-offs and 12 landings": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=0), landings=12), @@ -60,7 +60,7 @@ def get_sep_revalidation_experience(log_entries: QuerySet[LogEntry]) -> Experien ), "Refresher training with FI or CRI": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=1), landings=0), - accrued=compute_totals(eligible_entries.filter(time_function=FunctionType.DUAL.name)), + accrued=compute_totals(eligible_entries.filter(time_function=FunctionType.DUAL)), ), }, details=""" @@ -75,15 +75,15 @@ def get_ppl_experience(log_entries: QuerySet[LogEntry]) -> ExperienceRequirement experience={ "Dual instruction": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=25), landings=0), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.DUAL.name)), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.DUAL)), ), "Supervised solo": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=10), landings=0), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC.name)), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC)), ), "Cross-country solo": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=5), landings=0), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC.name, cross_country=True)), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC, cross_country=True)), ), "Total hours": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=45), landings=0), @@ -102,15 +102,15 @@ def get_night_experience(log_entries: QuerySet[LogEntry]) -> ExperienceRequireme experience={ "Solo full-stop landings": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=0), landings=5), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC.name), full_stop=True), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC), full_stop=True), ), "Dual instruction": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=3), landings=0), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.DUAL.name)), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.DUAL)), ), "Dual cross-country (>27 NM)": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=1), landings=0), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.DUAL.name, cross_country=True)), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.DUAL, cross_country=True)), ), "Total hours": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=5), landings=0), @@ -125,7 +125,7 @@ def get_ir_experience(log_entries: QuerySet[LogEntry]) -> ExperienceRequirements experience={ "Cross-country PIC": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=50), landings=0), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC.name, cross_country=True)), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC, cross_country=True)), ), }, ) @@ -136,16 +136,16 @@ def get_cpl_experience(log_entries: QuerySet[LogEntry]) -> ExperienceRequirement experience={ # TODO: add dual "PIC": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=100), landings=0), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC.name)), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC)), ), "Cross-country PIC": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=20), landings=0), - accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC.name, cross_country=True)), + accrued=compute_totals(log_entries.filter(time_function=FunctionType.PIC, cross_country=True)), ), "Visual dual instruction": ExperienceRecord( required=TotalsRecord(time=timedelta(hours=15), landings=0), accrued=compute_totals( - log_entries.filter(time_function=FunctionType.DUAL.name, departure_time__gte=CPL_START_DATE), + log_entries.filter(time_function=FunctionType.DUAL, departure_time__gte=CPL_START_DATE), ), ), "Total hours": ExperienceRecord(