From 4420bf0c089da08220a8d77c07425a4c7eedb2a2 Mon Sep 17 00:00:00 2001 From: Arthur Deierlein Date: Fri, 26 Apr 2024 15:31:35 +0200 Subject: [PATCH] chore(views): added type annotations --- timed/employment/views.py | 25 ++++++++++++++----------- timed/projects/views.py | 28 ++++++++++++++++------------ timed/reports/views.py | 28 ++++++++++++++++++++++++---- timed/subscription/views.py | 13 ++++++++++--- timed/tracking/views.py | 26 ++++++++++++-------------- 5 files changed, 76 insertions(+), 44 deletions(-) diff --git a/timed/employment/views.py b/timed/employment/views.py index 9548aae6..c6b6ba48 100644 --- a/timed/employment/views.py +++ b/timed/employment/views.py @@ -1,6 +1,9 @@ """Viewsets for the employment app.""" +from __future__ import annotations + import datetime +from typing import TYPE_CHECKING from django.contrib.auth import get_user_model from django.db.models import CharField, DateField, IntegerField, Q, Value @@ -28,6 +31,9 @@ from timed.projects.models import CustomerAssignee, Task from timed.tracking.models import Absence, Report +if TYPE_CHECKING: + from django.db.models import QuerySet + class UserViewSet(ModelViewSet): """Expose user actions. @@ -57,7 +63,7 @@ class UserViewSet(ModelViewSet): "last_name", ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.User]: user = self.request.user queryset = get_user_model().objects.prefetch_related( "employments", "supervisees", "supervisors" @@ -114,7 +120,7 @@ def transfer(self, _request, pk=None): # noqa: ARG002 It will skip any credits if a credit already exists on the first of the new year. """ - user = self.get_object() + user: models.User = self.get_object() year = datetime.date.today().year start_year = datetime.date(year, 1, 1) @@ -325,7 +331,7 @@ class EmploymentViewSet(ModelViewSet): ), ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Employment]: """Get queryset of employments. Following rules apply: @@ -350,7 +356,7 @@ class LocationViewSet(ReadOnlyModelViewSet): serializer_class = serializers.LocationSerializer ordering = ("name",) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Location]: """Don't show locations to customers.""" user = self.request.user @@ -368,13 +374,10 @@ class PublicHolidayViewSet(ReadOnlyModelViewSet): filterset_class = filters.PublicHolidayFilterSet ordering = ("date",) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.PublicHoliday]: """Prefetch the related data. Don't show public holidays to customers. - - :return: The public holidays - :rtype: QuerySet """ user = self.request.user @@ -393,7 +396,7 @@ class AbsenceTypeViewSet(ReadOnlyModelViewSet): filterset_class = filters.AbsenceTypeFilterSet ordering = ("name",) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.AbsenceType]: """Don't show absence types to customers.""" user = self.request.user @@ -418,7 +421,7 @@ class AbsenceCreditViewSet(ModelViewSet): ), ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.AbsenceCredit]: """Get queryset of absence credits. Following rules apply: @@ -450,7 +453,7 @@ class OvertimeCreditViewSet(ModelViewSet): ), ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.OvertimeCredit]: """Get queryset of overtime credits. Following rules apply: diff --git a/timed/projects/views.py b/timed/projects/views.py index 99c4b950..ef2f7129 100644 --- a/timed/projects/views.py +++ b/timed/projects/views.py @@ -1,5 +1,9 @@ """Viewsets for the projects app.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + from django.db.models import Q from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet @@ -15,6 +19,9 @@ ) from timed.projects import filters, models, serializers +if TYPE_CHECKING: + from django.db.models import QuerySet + class CustomerViewSet(ReadOnlyModelViewSet): """Customer view set.""" @@ -23,13 +30,10 @@ class CustomerViewSet(ReadOnlyModelViewSet): filterset_class = filters.CustomerFilterSet ordering = ("name",) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Customer]: """Prefetch related data. If an employee is external, get only assigned customers. - - :return: The customers - :rtype: QuerySet """ user = self.request.user queryset = models.Customer.objects.prefetch_related("projects") @@ -63,7 +67,7 @@ class BillingTypeViewSet(ReadOnlyModelViewSet): ), ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.BillingType] | None: """Get billing types depending on the user's role. Internal employees should see all billing types. @@ -108,7 +112,7 @@ class CostCenterViewSet(ReadOnlyModelViewSet): ), ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.CostCenter]: return models.CostCenter.objects.all() @@ -136,7 +140,7 @@ class ProjectViewSet(ModelViewSet): ), ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Project]: """Get only assigned projects, if an employee is external.""" user = self.request.user queryset = ( @@ -180,7 +184,7 @@ class TaskViewSet(ModelViewSet): ), ) - def filter_queryset(self, queryset): + def filter_queryset(self, queryset: QuerySet[models.Task]) -> QuerySet[models.Task]: """Specific filter queryset options.""" # my most frequent filter uses LIMIT so default ordering # needs to be disabled to avoid exception @@ -190,7 +194,7 @@ def filter_queryset(self, queryset): return super().filter_queryset(queryset) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Task]: """Get only assigned tasks, if an employee is external.""" user = self.request.user queryset = super().get_queryset().select_related("project", "cost_center") @@ -218,7 +222,7 @@ class TaskAsssigneeViewSet(ReadOnlyModelViewSet): serializer_class = serializers.TaskAssigneeSerializer filterset_class = filters.TaskAssigneeFilterSet - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.TaskAssignee]: """Don't show task assignees to customers.""" user = self.request.user @@ -234,7 +238,7 @@ class ProjectAsssigneeViewSet(ReadOnlyModelViewSet): serializer_class = serializers.ProjectAssigneeSerializer filterset_class = filters.ProjectAssigneeFilterSet - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.ProjectAssignee]: """Don't show project assignees to customers.""" user = self.request.user @@ -250,7 +254,7 @@ class CustomerAsssigneeViewSet(ReadOnlyModelViewSet): serializer_class = serializers.CustomerAssigneeSerializer filterset_class = filters.CustomerAssigneeFilterSet - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.CustomerAssignee]: """Don't show customer assignees to customers.""" user = self.request.user diff --git a/timed/reports/views.py b/timed/reports/views.py index 48dd76b1..ef6b50a4 100644 --- a/timed/reports/views.py +++ b/timed/reports/views.py @@ -1,7 +1,10 @@ +from __future__ import annotations + import re from collections import defaultdict from datetime import date from io import BytesIO +from typing import TYPE_CHECKING from zipfile import ZipFile from django.conf import settings @@ -23,6 +26,13 @@ from . import filters +if TYPE_CHECKING: + from typing import Iterable + + from ezodf.document import FlatXMLDocument, PackagedDocument + + from timed.employment.models import User + class YearStatisticViewSet(AggregateQuerysetMixin, ReadOnlyModelViewSet): """Year statistics calculates total reported time per year.""" @@ -273,7 +283,7 @@ def _parse_query_params(self, queryset, request): form.is_valid() return form.cleaned_data - def _clean_filename(self, name): + def _clean_filename(self, name: str) -> str: """Clean name so it can be used in file paths. To accomplish this it will remove all special chars and @@ -282,7 +292,9 @@ def _clean_filename(self, name): escaped = re.sub(r"[^\w\s-]", "", name) return re.sub(r"\s+", "_", escaped) - def _generate_workreport_name(self, from_date, today, project): + def _generate_workreport_name( + self, from_date: date, today: date, project: Project + ) -> str: """Generate workreport name. Name is in format: YYMM-YYYYMMDD-$Customer-$Project.ods @@ -296,7 +308,15 @@ def _generate_workreport_name(self, from_date, today, project): self._clean_filename(project.name), ) - def _create_workreport(self, from_date, to_date, today, project, reports, user): # noqa: PLR0913 + def _create_workreport( # noqa: PLR0913 + self, + from_date: date, + to_date: date, + today: date, + project: Project, + reports: Iterable[Report], + user: User, + ) -> tuple[str, PackagedDocument | FlatXMLDocument]: """Create ods workreport. :rtype: tuple @@ -312,7 +332,7 @@ def _create_workreport(self, from_date, to_date, today, project, reports, user): ) tmpl = settings.WORK_REPORT_PATH - doc = opendoc(tmpl) + doc: PackagedDocument | FlatXMLDocument = opendoc(tmpl) table = doc.sheets[0] tasks = defaultdict(int) date_style = table["C5"].style_name diff --git a/timed/subscription/views.py b/timed/subscription/views.py index 1cb08ec4..771e9064 100644 --- a/timed/subscription/views.py +++ b/timed/subscription/views.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + from django.db.models import Q from rest_framework import decorators, exceptions, response, status, viewsets from rest_framework_json_api.serializers import ValidationError @@ -16,6 +20,9 @@ from . import filters, models, serializers +if TYPE_CHECKING: + from django.db.models import QuerySet + class SubscriptionProjectViewSet(viewsets.ReadOnlyModelViewSet): """Subscription specific project view. @@ -31,7 +38,7 @@ class SubscriptionProjectViewSet(viewsets.ReadOnlyModelViewSet): "id", ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[Project]: user = self.request.user queryset = Project.objects.filter(archived=False, customer_visible=True) current_employment = user.get_active_employment() @@ -53,7 +60,7 @@ class PackageViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.PackageSerializer filterset_class = filters.PackageFilter - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Package]: return models.Package.objects.select_related("billing_type") @@ -111,7 +118,7 @@ def confirm(self, request, pk=None): # noqa: ARG002 return response.Response(status=status.HTTP_204_NO_CONTENT) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Order]: return models.Order.objects.select_related("project") def destroy(self, _request, pk=None): # noqa: ARG002 diff --git a/timed/tracking/views.py b/timed/tracking/views.py index 366c0ea7..687734aa 100644 --- a/timed/tracking/views.py +++ b/timed/tracking/views.py @@ -1,6 +1,9 @@ """Viewsets for the tracking app.""" +from __future__ import annotations + from datetime import date +from typing import TYPE_CHECKING import django_excel from django.conf import settings @@ -34,6 +37,9 @@ from . import tasks +if TYPE_CHECKING: + from django.db.models import QuerySet + class ActivityViewSet(ModelViewSet): """Activity view set.""" @@ -50,12 +56,8 @@ class ActivityViewSet(ModelViewSet): ), ) - def get_queryset(self): - """Filter the queryset by the user of the request. - - :return: The filtered activities - :rtype: QuerySet - """ + def get_queryset(self) -> QuerySet[models.Activity]: + """Filter the queryset by the user of the request.""" return models.Activity.objects.select_related( "task", "user", "task__project", "task__project__customer" ).filter(user=self.request.user) @@ -77,12 +79,8 @@ class AttendanceViewSet(ModelViewSet): ), ) - def get_queryset(self): - """Filter the queryset by the user of the request. - - :return: The filtered attendances - :rtype: QuerySet - """ + def get_queryset(self) -> QuerySet[models.Attendance]: + """Filter the queryset by the user of the request.""" return models.Attendance.objects.select_related("user").filter( user=self.request.user ) @@ -128,7 +126,7 @@ class ReportViewSet(ModelViewSet): "rejected", ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Report]: """Get filtered reports for external employees.""" user = self.request.user queryset = super().get_queryset() @@ -375,7 +373,7 @@ class AbsenceViewSet(ModelViewSet): ), ) - def get_queryset(self): + def get_queryset(self) -> QuerySet[models.Absence]: """Get absences only for internal employees. User should be able to create an absence on a public holiday if the