diff --git a/mia_core/converters.py b/mia_core/converters.py index 93b4e84..44cc952 100644 --- a/mia_core/converters.py +++ b/mia_core/converters.py @@ -25,11 +25,30 @@ class UserConverter: """The path converter for the user accounts.""" regex = ".*" - def to_python(self, value): + def to_python(self, value: str) -> User: + """Returns the user by her log in ID. + + Args: + value: The log in ID. + + Returns: + The user. + + Raises: + ValueError: When the value is invalid + """ try: return User.objects.get(login_id=value) except User.DoesNotExist: raise ValueError - def to_url(self, value): + def to_url(self, value: User) -> str: + """Returns the log in ID of a user. + + Args: + value: The user. + + Returns: + The log in ID. + """ return value.login_id diff --git a/mia_core/digest_auth.py b/mia_core/digest_auth.py index 628baf6..dd29c5a 100644 --- a/mia_core/digest_auth.py +++ b/mia_core/digest_auth.py @@ -22,6 +22,7 @@ application. import ipaddress import socket from functools import wraps +from typing import Optional from django.conf import settings from django.db.models import F @@ -35,23 +36,23 @@ from .models import User, Country class AccountBackend: """The account backend for the django-digest module.""" - def get_partial_digest(self, username): + def get_partial_digest(self, username: str) -> Optional[str]: """Returns the HTTP digest authentication password digest hash of a user. Args: - username (str): The log in user name. + username: The log in user name. Return: - str: The HTTP digest authentication password hash of - the user, or None if the user does not exist. + The HTTP digest authentication password hash of the user, or None + if the user does not exist. """ user = User.objects.filter(login_id=username).first() if user is None: return None return user.password - def get_user(self, username): + def get_user(self, username: str) -> Optional[User]: """Returns the user by her log in user name. Args: @@ -86,7 +87,7 @@ def login_required(function=None): return decorator -def _log_visit(request): +def _log_visit(request: HttpRequest) -> None: """Logs the visit information for the logged-in user. Args: @@ -106,21 +107,29 @@ def _log_visit(request): request.session["visit_logged"] = True -def _get_remote_ip(request): +def _get_remote_ip(request: HttpRequest) -> str: + """Returns the IP of the remote client. + + Args: + request: The request. + + Returns: + The IP of the remote client. + """ x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") if x_forwarded_for: return x_forwarded_for.split(",")[0] return request.META.get('REMOTE_ADDR') -def _get_host(ip): +def _get_host(ip: str) -> Optional[str]: """Look-up the host name by its IP. Args: - ip (str): The IP + ip: The IP Returns: - str: The host name, or None if the look-up fails. + The host name, or None if the look-up fails. """ try: return socket.gethostbyaddr(ip)[0] @@ -128,31 +137,30 @@ def _get_host(ip): return None -def _get_country(ip): +def _get_country(ip: str) -> Optional[Country]: """Look-up the country by its IP. Args: - ip (str): The IP + ip: The IP Returns: - Country: The country. + The country. """ code = _get_country_code(ip) try: return Country.objects.get(code=code) except Country.DoesNotExist: - pass - return None + return None -def _get_country_code(ip): +def _get_country_code(ip: str) -> Optional[str]: """Look-up the country code by its IP. Args: - ip (str): The IP + ip: The IP Returns: - str: The country code, or None if the look-up fails. + The country code, or None if the look-up fails. """ try: return geolite2.lookup(ip).country diff --git a/mia_core/forms.py b/mia_core/forms.py index 02d9d75..07eef11 100644 --- a/mia_core/forms.py +++ b/mia_core/forms.py @@ -75,7 +75,7 @@ class UserForm(forms.Form): if errors: raise forms.ValidationError(errors) - def _validate_login_id_unique(self): + def _validate_login_id_unique(self) -> None: """Validates whether the log in ID is unique. Raises: @@ -93,7 +93,7 @@ class UserForm(forms.Form): self.add_error("login_id", error) raise error - def _validate_password_new_required(self): + def _validate_password_new_required(self) -> None: """Validates whether the password is entered for newly-created users. Raises: @@ -108,7 +108,7 @@ class UserForm(forms.Form): self.add_error("password", error) raise error - def _validate_password_login_id_changed_required(self): + def _validate_password_login_id_changed_required(self) -> None: """Validates whether the password is entered for users whose login ID changed. @@ -129,7 +129,7 @@ class UserForm(forms.Form): self.add_error("password", error) raise error - def _validate_password2_required(self): + def _validate_password2_required(self) -> None: """Validates whether the second password is entered. Raises: @@ -145,7 +145,7 @@ class UserForm(forms.Form): self.add_error("password2", error) raise error - def _validate_passwords_equal(self): + def _validate_passwords_equal(self) -> None: """Validates whether the two passwords are equa. Raises: @@ -162,7 +162,7 @@ class UserForm(forms.Form): self.add_error("password2", error) raise error - def _validate_is_disabled_not_oneself(self): + def _validate_is_disabled_not_oneself(self) -> None: """Validates whether the user tries to disable herself Raises: diff --git a/mia_core/models.py b/mia_core/models.py index 875693b..1bf44a6 100644 --- a/mia_core/models.py +++ b/mia_core/models.py @@ -18,7 +18,6 @@ """The data models of the Mia core application. """ -import datetime import hashlib from dirtyfields import DirtyFieldsMixin @@ -57,11 +56,12 @@ class Country(DirtyFieldsMixin, models.Model): return self.code.__str__() + " " + self.name.__str__() @property - def name(self): + def name(self) -> str: + """The country name in the current language.""" return get_multi_lingual_attr(self, "name", "en") @name.setter - def name(self, value): + def name(self, value: str) -> None: set_multi_lingual_attr(self, "name", value) class Meta: @@ -101,11 +101,11 @@ class User(DirtyFieldsMixin, models.Model): USERNAME_FIELD = "login_id" @property - def is_anonymous(self): + def is_anonymous(self) -> bool: return False @property - def is_authenticated(self): + def is_authenticated(self) -> bool: return True def set_password(self): @@ -142,16 +142,16 @@ class User(DirtyFieldsMixin, models.Model): F"{login_id}:{settings.DIGEST_REALM}:{password}") @staticmethod - def md5(value): + def md5(value: str) -> str: m = hashlib.md5() m.update(value.encode("utf-8")) return m.hexdigest() - def is_in_use(self): + def is_in_use(self) -> bool: """Returns whether this user is in use. Returns: - bool: True if this user is in use, or False otherwise. + True if this user is in use, or False otherwise. """ for table in connection.introspection.table_names(): if self._is_in_use_with(F"SELECT * FROM {table}" @@ -163,14 +163,14 @@ class User(DirtyFieldsMixin, models.Model): return True return False - def _is_in_use_with(self, sql): + def _is_in_use_with(self, sql: str) -> bool: """Returns whether this user is in use with a specific SQL statement. Args: - sql (str): The SQL query statement + sql: The SQL query statement Returns: - bool: True if this user is in use, or False otherwise. + True if this user is in use, or False otherwise. """ with connection.cursor() as cursor: try: diff --git a/mia_core/period.py b/mia_core/period.py index 0aa8639..c080d70 100644 --- a/mia_core/period.py +++ b/mia_core/period.py @@ -20,6 +20,8 @@ """ import datetime import re +from datetime import date +from typing import Optional, List, Any, Union from django.core.serializers.json import DjangoJSONEncoder from django.template import defaultfilters @@ -33,72 +35,73 @@ class Period: """The template helper for the period chooser. Args: - spec (str): The current period specification - data_start (datetime.date): The available first day of the data. - data_end (datetime.date): The available last day of the data. + spec: The current period specification + data_start: The available first day of the data. + data_end: The available last day of the data. Raises: ValueError: When the period specification is invalid. """ - def __init__(self, spec=None, data_start=None, data_end=None): + def __init__(self, spec: str = None, data_start: datetime.date = None, + data_end: datetime.date = None): # Raises ValueError self._period = self.Parser(spec) self._data_start = data_start self._data_end = data_end @property - def spec(self): + def spec(self) -> str: """Returns the period specification. Returns: - str: The period specification. + The period specification. """ return self._period.spec @property - def start(self): + def start(self) -> datetime.date: """Returns the start day of the currently-specified period. Returns: - datetime.date: The start day of the currently-specified period. + The start day of the currently-specified period. """ return self._period.start @property - def end(self): + def end(self) -> datetime.date: """Returns the end day of the currently-specified period. Returns: - datetime.date: The end day of the currently-specified period. + The end day of the currently-specified period. """ return self._period.end @property - def description(self): + def description(self) -> str: """Returns the text description of the currently-specified period. Returns: - str: The text description of the currently-specified period + The text description of the currently-specified period """ return self._period.description @property - def prep_desc(self): + def prep_desc(self) -> str: """Returns the text description with preposition of the currently-specified period. Returns: - str: The text description with preposition of the - currently-specified period + The text description with preposition of the currently-specified + period. """ return self._period.prep_desc @staticmethod - def _get_last_month_start(): + def _get_last_month_start() -> datetime.date: """Returns the first day of the last month. Returns: - datetime.date: The first day of the last month. + The first day of the last month. """ today = timezone.localdate() month = today.month - 1 @@ -109,11 +112,11 @@ class Period: return datetime.date(year, month, 1) @staticmethod - def _get_next_month_start(): + def _get_next_month_start() -> datetime.date: """Returns the first day of the next month. Returns: - datetime.date: The first day of the next month. + The first day of the next month. """ today = timezone.localdate() month = today.month + 1 @@ -123,12 +126,12 @@ class Period: year = year + 1 return datetime.date(year, month, 1) - def this_month(self): + def this_month(self) -> Optional[str]: """Returns the specification of this month. Returns: - str|None: The specification of this month, or None if there is no - data in or before this month. + The specification of this month, or None if there is no data in or + before this month. """ if self._data_start is None: return None @@ -139,12 +142,12 @@ class Period: return None return dateformat.format(today, "Y-m") - def last_month(self): + def last_month(self) -> Optional[str]: """Returns the specification of last month. Returns: - str|None: The specification of last month, or None if there is no - data in or before last month. + The specification of last month, or None if there is no data in or + before last month. """ if self._data_start is None: return None @@ -155,25 +158,25 @@ class Period: return None return dateformat.format(last_month_start, "Y-m") - def since_last_month(self): + def since_last_month(self) -> Optional[str]: """Returns the specification since last month. Returns: - str|None: The specification since last month, or None if there is - no data in or before last month. + The specification since last month, or None if there is no data in + or before last month. """ last_month = self.last_month() if last_month is None: return None return last_month + "-" - def has_months_to_choose(self): + def has_months_to_choose(self) -> bool: """Returns whether there are months to choose besides this month and last month. Returns: - bool: True if there are months to choose besides this month and - last month, or False otherwise. + True if there are months to choose besides this month and last + month, or False otherwise. """ if self._data_start is None: return False @@ -183,14 +186,13 @@ class Period: return True return False - def chosen_month(self): + def chosen_month(self) -> Optional[str]: """Returns the specification of the chosen month, or None if the current period is not a month or is out of available data range. Returns: - str|None: The specification of the chosen month, or None if the - current period is not a month or is out of available data - range. + The specification of the chosen month, or None if the current + period is not a month or is out of available data range. """ if self._data_start is None: return None @@ -203,12 +205,12 @@ class Period: return None return self._period.spec - def this_year(self): + def this_year(self) -> Optional[str]: """Returns the specification of this year. Returns: - str|None: The specification of this year, or None if there is no - data in or before this year. + The specification of this year, or None if there is no data in or + before this year. """ if self._data_start is None: return None @@ -217,12 +219,12 @@ class Period: return None return str(this_year) - def last_year(self): + def last_year(self) -> Optional[str]: """Returns the specification of last year. Returns: - str|None: The specification of last year, or None if there is no - data in or before last year. + The specification of last year, or None if there is no data in or + before last year. """ if self._data_start is None: return None @@ -231,13 +233,13 @@ class Period: return None return str(last_year) - def has_years_to_choose(self): + def has_years_to_choose(self) -> bool: """Returns whether there are years to choose besides this year and last year. Returns: - bool: True if there are years to choose besides this year and - last year, or False otherwise. + True if there are years to choose besides this year and last year, + or False otherwise. """ if self._data_start is None: return False @@ -248,11 +250,12 @@ class Period: return True return False - def years_to_choose(self): + def years_to_choose(self) -> Optional[List[str]]: """Returns the years to choose besides this year and last year. Returns: - list[str]: The years to choose besides this year and last year. + The years to choose besides this year and last year, or None if + there is no data. """ if self._data_start is None: return None @@ -263,12 +266,12 @@ class Period: self._data_end.year, this_year, -1)] return after + before[::-1] - def today(self): + def today(self) -> Optional[None]: """Returns the specification of today. Returns: - (str): The specification of today, or None if there is no data - in or before today. + The specification of today, or None if there is no data in or + before today. """ if self._data_start is None: return None @@ -277,12 +280,12 @@ class Period: return None return dateformat.format(today, "Y-m-d") - def yesterday(self): + def yesterday(self) -> Optional[str]: """Returns the specification of yesterday. Returns: - (str): The specification of yesterday, or None if there is no data - in or before yesterday. + The specification of yesterday, or None if there is no data in or + before yesterday. """ if self._data_start is None: return None @@ -291,21 +294,21 @@ class Period: return None return dateformat.format(yesterday, "Y-m-d") - def chosen_day(self): + def chosen_day(self) -> str: """Returns the specification of the chosen day. Returns: - (str): The specification of the chosen day, or the start day - of the period if the current period is not a day. + The specification of the chosen day, or the start day of the period + if the current period is not a day. """ return dateformat.format(self._period.start, "Y-m-d") - def has_days_to_choose(self): + def has_days_to_choose(self) -> bool: """Returns whether there are more than one day to choose from. Returns: - bool: True if there are more than one day to choose from, - or False otherwise. + True if there are more than one day to choose from, or False + otherwise. """ if self._data_start is None: return False @@ -313,33 +316,35 @@ class Period: return False return True - def first_day(self): + def first_day(self) -> Optional[str]: """Returns the specification of the available first day. Returns: - str: The specification of the available first day. + The specification of the available first day, or None if there is + no data. """ if self._data_start is None: return None return dateformat.format(self._data_start, "Y-m-d") - def last_day(self): + def last_day(self) -> Optional[str]: """Returns the specification of the available last day. Returns: - str: The specification of the available last day. + The specification of the available last day, or None if there is no + data. """ if self._data_end is None: return None return dateformat.format(self._data_end, "Y-m-d") - def chosen_start(self): + def chosen_start(self) -> Optional[str]: """Returns the specification of of the first day of the specified period. Returns: - str: The specification of of the first day of the - specified period. + The specification of of the first day of the specified period, or + None if there is no data. """ if self._data_start is None: return None @@ -348,13 +353,13 @@ class Period: else self._data_start return dateformat.format(day, "Y-m-d") - def chosen_end(self): + def chosen_end(self) -> Optional[str]: """Returns the specification of of the last day of the specified period. Returns: - str: The specification of of the last day of the - specified period. + The specification of of the last day of the specified period, or + None if there is data. """ if self._data_end is None: return None @@ -363,12 +368,12 @@ class Period: else self._data_end return dateformat.format(day, "Y-m-d") - def period_before(self): + def period_before(self) -> Optional[str]: """Returns the specification of the period before the current period. Returns: - str|None: The specification of the period before the current - period, or None if there is no data before the current period. + The specification of the period before the current period, or None + if there is no data before the current period. """ if self._data_start is None: return None @@ -381,11 +386,12 @@ class Period: return dateformat.format(previous_day, "-Y-m") return dateformat.format(previous_day, "-Y-m-d") - def month_picker_params(self): + def month_picker_params(self) -> Optional[str]: """Returns the parameters for the month-picker, as a JSON text string. Returns: - str: The parameters for the month-picker, as a JSON text string. + The parameters for the month-picker, as a JSON text string, or None + if there is no data. """ if self._data_start is None: return None @@ -398,7 +404,7 @@ class Period: }) @staticmethod - def default_spec(): + def default_spec() -> str: """Returns the specification for the default period. Returns: @@ -422,9 +428,9 @@ class Period: description (str): The text description of the period. prep_desc (str): The text description with preposition. """ - VERY_START = datetime.date(1990, 1, 1) + VERY_START: datetime.date = datetime.date(1990, 1, 1) - def __init__(self, spec): + def __init__(self, spec: str): self.spec = None self.start = None self.end = None @@ -574,7 +580,7 @@ class Period: # Wrong period format raise ValueError - def _set_this_month(self): + def _set_this_month(self) -> None: """Sets the period to this month.""" today = timezone.localdate() self.spec = dateformat.format(today, "Y-m") @@ -583,14 +589,14 @@ class Period: self.description = gettext("This Month") @staticmethod - def _month_last_day(day): + def _month_last_day(day: datetime.date) -> datetime.date: """Calculates and returns the last day of a month. Args: - day (datetime.date): A day in the month. + day: A day in the month. Returns: - date: The last day in the month + The last day in the month """ next_month = day.month + 1 next_year = day.year @@ -601,15 +607,15 @@ class Period: next_year, next_month, 1) - datetime.timedelta(days=1) @staticmethod - def _month_text(year, month): + def _month_text(year: int, month: int) -> str: """Returns the text description of a month. Args: - year (int): The year. - month (int): The month. + year: The year. + month: The month. Returns: - str: The description of the month. + The description of the month. """ today = timezone.localdate() if year == today.year and month == today.month: @@ -625,14 +631,14 @@ class Period: return "%d/%d" % (year, month) @staticmethod - def _year_text(year): + def _year_text(year: int) -> str: """Returns the text description of a year. Args: - year (int): The year. + year: The year. Returns: - str: The description of the year. + The description of the year. """ this_year = timezone.localdate().year if year == this_year: @@ -642,14 +648,14 @@ class Period: return str(year) @staticmethod - def _date_text(day): + def _date_text(day: datetime.date) -> str: """Returns the text description of a day. Args: - day (datetime.date): The date. + day: The date. Returns: - str: The description of the day. + The description of the day. """ today = timezone.localdate() if day == today: diff --git a/mia_core/stored_post.py b/mia_core/stored_post.py index 5e34b16..20250bf 100644 --- a/mia_core/stored_post.py +++ b/mia_core/stored_post.py @@ -19,16 +19,18 @@ """ import random +from typing import Dict, Mapping, Any, Optional -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpRequest from django.shortcuts import redirect from .utils import UrlBuilder -STORAGE_KEY = "stored_post" +STORAGE_KEY: str = "stored_post" -def error_redirect(request, url, post): +def error_redirect(request: HttpRequest, url: str, + post: Dict[str, str]) -> HttpResponseRedirect: """Redirects to a specific URL on error, with the POST data ID appended as the query parameter "s". The POST data can be loaded with the get_previous_post() utility. @@ -45,7 +47,7 @@ def error_redirect(request, url, post): return redirect(str(UrlBuilder(url).query(s=post_id))) -def get_previous_post(request): +def get_previous_post(request: HttpRequest) -> Optional[Dict[str, str]]: """Retrieves the previously-stored POST data. Args: @@ -59,16 +61,16 @@ def get_previous_post(request): return _retrieve(request, request.GET["s"]) -def _store(request, post): +def _store(request: HttpRequest, post: Dict[str, str]) -> str: """Stores the POST data into the session, and returns the POST data ID that can be used to retrieve it later with _retrieve(). Args: - request (HttpRequest): The request. - post (dict): The POST data. + request: The request. + post: The POST data. Returns: - str: The POST data ID + The POST data ID """ if STORAGE_KEY not in request.session: request.session[STORAGE_KEY] = {} @@ -77,15 +79,15 @@ def _store(request, post): return post_id -def _retrieve(request, post_id): +def _retrieve(request: HttpRequest, post_id: str) -> Optional[Dict[str, str]]: """Retrieves the POST data from the storage. Args: - request (HttpRequest): The request. - post_id (str): The POST data ID. + request: The request. + post_id: The POST data ID. Returns: - dict: The POST data, or None if the corresponding data does not exist. + The POST data, or None if the corresponding data does not exist. """ if STORAGE_KEY not in request.session: return None @@ -94,7 +96,7 @@ def _retrieve(request, post_id): return request.session[STORAGE_KEY][post_id] -def _new_post_id(post_store): +def _new_post_id(post_store: Mapping[int, Any]) -> str: """Generates and returns a new POST ID that does not exist yet. Args: diff --git a/mia_core/templatetags/mia_core.py b/mia_core/templatetags/mia_core.py index 8273620..a77f50e 100644 --- a/mia_core/templatetags/mia_core.py +++ b/mia_core/templatetags/mia_core.py @@ -18,12 +18,14 @@ """The template tags and filters of the Mia core application. """ +import datetime from datetime import date +from typing import Any import titlecase from django import template from django.http import HttpRequest -from django.template import defaultfilters +from django.template import defaultfilters, RequestContext from django.urls import reverse from django.utils import timezone from django.utils.safestring import SafeString @@ -35,31 +37,31 @@ register = template.Library() @register.simple_tag(takes_context=True) -def setvar(context, key, value): +def setvar(context: RequestContext, key: str, value: Any) -> str: """Sets a variable in the template. Args: - context (Context): the context - key (str): The variable name - value (str): The variable value + context: the context + key: The variable name + value: The variable value Returns: - str: An empty string. + An empty string. """ context.dicts[0][key] = value return "" @register.simple_tag(takes_context=True) -def url_period(context, period_spec): +def url_period(context: RequestContext, period_spec: str) -> str: """Returns the current URL with a new period. Args: - context (RequestContext): The request context. - period_spec (str): The period specification. + context: The request context. + period_spec: The period specification. Returns: - str: The current URL with the new period. + The current URL with the new period. """ view_name = "%s:%s" % ( context.request.resolver_match.app_name, @@ -70,47 +72,47 @@ def url_period(context, period_spec): @register.simple_tag(takes_context=True) -def url_with_return(context, url): +def url_with_return(context: RequestContext, url: str) -> str: """Returns the URL with the current page added as the "r" query parameter, so that returning to this page is possible. Args: - context (RequestContext): The request context. - url (str): The URL. + context: The request context. + url: The URL. Returns: - str: The URL with the current page added as the "r" query parameter. + The URL with the current page added as the "r" query parameter. """ return str(UrlBuilder(url).query( r=str(UrlBuilder(context.request.get_full_path()).remove("s")))) @register.simple_tag(takes_context=True) -def url_keep_return(context, url): +def url_keep_return(context: RequestContext, url: str) -> str: """Returns the URL with the current "r" query parameter set, so that the next processor can still return to the same page. Args: - context (RequestContext): The request context. - url (str): The URL. + context: The request context. + url: The URL. Returns: - str: The URL with the current "r" query parameter set. + The URL with the current "r" query parameter set. """ return str(UrlBuilder(url).query(r=context.request.GET.get("r"))) @register.simple_tag(takes_context=True) -def add_css(context, url): +def add_css(context: RequestContext, url: str) -> str: """Adds a local CSS file. The file is added to the "css" template list variable. Args: - context (RequestContext): The request context. - url (str): The URL or path of the CSS file. + context: The request context. + url: The URL or path of the CSS file. Returns: - str: An empty string + An empty string """ if "css" not in context.dicts[0]: context.dicts[0]["css"] = [] @@ -119,16 +121,16 @@ def add_css(context, url): @register.simple_tag(takes_context=True) -def add_js(context, url): +def add_js(context: RequestContext, url: str) -> str: """Adds a local JavaScript file. The file is added to the "js" template list variable. Args: - context (RequestContext): The request context. - url (str): The URL or path of the JavaScript file. + context: The request context. + url: The URL or path of the JavaScript file. Returns: - str: An empty string + An empty string """ if "js" not in context.dicts[0]: context.dicts[0]["js"] = [] @@ -137,14 +139,14 @@ def add_js(context, url): @register.filter -def smart_date(value): +def smart_date(value: datetime.date) -> str: """Formats the date for human friendliness. Args: - value (datetime.date): The date. + value: The date. Returns: - str: The human-friendly format of the date. + The human-friendly format of the date. """ if value == date.today(): return gettext("Today") @@ -156,14 +158,14 @@ def smart_date(value): @register.filter -def smart_month(value): +def smart_month(value: datetime.date) -> str: """Formats the month for human friendliness. Args: - value (datetime.date): The month. + value: The month. Returns: - str: The human-friendly format of the month. + The human-friendly format of the month. """ today = timezone.localdate() if value.year == today.year and value.month == today.month: @@ -179,14 +181,14 @@ def smart_month(value): @register.filter -def title_case(value): +def title_case(value: str) -> str: """Formats the title in a proper American-English case. Args: - value (str): The title. + value: The title. Returns: - str: The title in a proper American-English case. + The title in a proper American-English case. """ value = str(value) if isinstance(value, SafeString): @@ -195,16 +197,15 @@ def title_case(value): @register.filter -def is_in_section(request, section_name): +def is_in_section(request: HttpRequest, section_name: str) -> bool: """Returns whether the request is currently in a section. Args: - request (HttpRequest): The request. - section_name (str): The view name of this section. + request: The request. + section_name: The view name of this section. Returns: - bool: True if the request is currently in this section, or False - otherwise + True if the request is currently in this section, or False otherwise. """ if request is None: return False diff --git a/mia_core/utils.py b/mia_core/utils.py index de12c21..bf908d7 100644 --- a/mia_core/utils.py +++ b/mia_core/utils.py @@ -20,20 +20,22 @@ """ import random import urllib.parse +from typing import Dict, List, Any, Type from django.conf import settings from django.db.models import Model, Q +from django.http import HttpRequest from django.utils.translation import pgettext, get_language -def new_pk(cls): +def new_pk(cls: Type[Model]) -> int: """Finds a random ID that does not conflict with the existing data records. Args: - cls (class): The Django model class. + cls: The Django model class. Returns: - int: The new random ID. + The new random ID. """ while True: pk = random.randint(100000000, 999999999) @@ -43,7 +45,7 @@ def new_pk(cls): return pk -def strip_post(post): +def strip_post(post: Dict[str, str]) -> None: """Strips the values of the POSTed data. Empty strings are removed. Args: @@ -59,7 +61,7 @@ class Language: """A language. Args: - language (str): The Django language code. + language: The Django language code. Attributes: id (str): The language ID @@ -67,7 +69,7 @@ class Language: locale (str); The locale name of this language. is_default (bool): Whether this is the default language. """ - def __init__(self, language): + def __init__(self, language: str): self.id = language self.db = "_" + language.lower().replace("-", "_") if language == "zh-hant": @@ -87,17 +89,18 @@ class Language: return Language(get_language()) -def get_multi_lingual_attr(model, name, default=None): +def get_multi_lingual_attr(model: Model, name: str, + default: str = None) -> str: """Returns a multi-lingual attribute of a data model. Args: - model (object): The data model. - name (str): The attribute name. - default (str): The default language. + model: The data model. + name: The attribute name. + default: The default language. Returns: - (any): The attribute in this language, or in the default - language if there is no content in the current language. + The attribute in this language, or in the default language if there is + no content in the current language. """ language = Language.current() title = getattr(model, name + language.db) @@ -110,27 +113,27 @@ def get_multi_lingual_attr(model, name, default=None): return getattr(model, name + Language.default().db) -def set_multi_lingual_attr(model, name, value): +def set_multi_lingual_attr(model: Model, name: str, value: str) -> None: """Sets a multi-lingual attribute of a data model. Args: - model (object): The data model. - name (str): The attribute name. - value (any): The new value + model: The data model. + name: The attribute name. + value: The new value """ language = Language.current() setattr(model, name + language.db, value) -def get_multi_lingual_search(attr, query): +def get_multi_lingual_search(attr: str, query: str) -> Q: """Returns the query condition on a multi-lingual attribute. Args: - attr (str): The base name of the multi-lingual attribute. - query (str): The query. + attr: The base name of the multi-lingual attribute. + query: The query. Returns: - Q: The query condition + The query condition """ language = Language.current() if language.is_default: @@ -147,10 +150,10 @@ class UrlBuilder: """The URL builder. Attributes: - base_path (str): the base path + path (str): the base path params (list[Param]): The query parameters """ - def __init__(self, start_url): + def __init__(self, start_url: str): """Constructs a new URL builder. Args: @@ -158,10 +161,10 @@ class UrlBuilder: """ pos = start_url.find("?") if pos == -1: - self.base_path = start_url + self.path = start_url self.params = [] return - self.base_path = start_url[:pos] + self.path = start_url[:pos] self.params = [] for piece in start_url[pos + 1:].split("&"): pos = piece.find("=") @@ -219,25 +222,25 @@ class UrlBuilder: Returns: UrlBuilder: A copy of this URL builder. """ - another = UrlBuilder(self.base_path) + another = UrlBuilder(self.path) another.params = [ self.Param(x.name, x.value) for x in self.params] return another - def __str__(self): + def __str__(self) -> str: if len(self.params) == 0: - return self.base_path - return self.base_path + "?" + "&".join([ + return self.path + return self.path + "?" + "&".join([ str(x) for x in self.params]) class Param: """A query parameter. Attributes: - name (str): The parameter name - value (str): The parameter value + name: The parameter name + value: The parameter value """ - def __init__(self, name, value): + def __init__(self, name: str, value: str): """Constructs a new query parameter Args: @@ -247,7 +250,7 @@ class UrlBuilder: self.name = name self.value = value - def __str__(self): + def __str__(self) -> str: """Returns the string representation of this query parameter. @@ -264,9 +267,9 @@ class Pagination: """The pagination. Args: - request (HttpRequest): The request. - items (list): All the items. - is_reversed (bool): Whether we should display the last page first. + request: The request. + items: All the items. + is_reversed: Whether we should display the last page first. Raises: PaginationException: With invalid pagination parameters @@ -282,7 +285,8 @@ class Pagination: """ DEFAULT_PAGE_SIZE = 10 - def __init__(self, request, items, is_reversed=False): + def __init__(self, request: HttpRequest, items: List[Any], + is_reversed: bool = False): self.current_url = UrlBuilder(request.get_full_path()) self.is_reversed = is_reversed self.page_size = self.DEFAULT_PAGE_SIZE @@ -334,7 +338,7 @@ class Pagination: """Returns the navigation links of the pagination bar. Returns: - list[Link]: The navigation links of the pagination bar. + List[Link]: The navigation links of the pagination bar. """ base_url = self.current_url.clone().remove("page").remove("s") links = [] @@ -443,14 +447,14 @@ class Pagination: """Returns the page size options. Returns: - list[PageSizeOption]: The page size options. + List[PageSizeOption]: The page size options. """ base_url = self.current_url.remove("page").remove("page-size") return [self.PageSizeOption(x, self._page_size_url(base_url, x)) for x in [10, 100, 200]] @staticmethod - def _page_size_url(base_url, size): + def _page_size_url(base_url: UrlBuilder, size: int) -> str: """Returns the URL for a new page size. Args: @@ -468,14 +472,14 @@ class Pagination: """A page size option. Args: - size (int): The page size. - url (str): The URL of this page size. + size: The page size. + url: The URL of this page size. Attributes: size (int): The page size. url (str): The URL for this page size. """ - def __init__(self, size, url): + def __init__(self, size: int, url: str): self.size = size self.url = url @@ -484,10 +488,10 @@ class PaginationException(Exception): """The exception thrown with invalid pagination parameters. Args: - url_builder (UrlBuilder): The canonical URL to redirect to. + url_builder: The canonical URL to redirect to. Attributes: url (str): The canonical URL to redirect to. """ - def __init__(self, url_builder): + def __init__(self, url_builder: UrlBuilder): self.url = str(url_builder) diff --git a/mia_core/views.py b/mia_core/views.py index 4301dc0..9027cf4 100644 --- a/mia_core/views.py +++ b/mia_core/views.py @@ -21,7 +21,8 @@ from django.contrib import messages from django.contrib.auth import logout as logout_user from django.contrib.messages.views import SuccessMessageMixin -from django.http import HttpResponse, JsonResponse +from django.http import HttpResponse, JsonResponse, HttpRequest, \ + HttpResponseRedirect from django.shortcuts import redirect, render from django.urls import reverse from django.utils.decorators import method_decorator @@ -47,14 +48,14 @@ class DeleteView(SuccessMessageMixin, CoreDeleteView): @require_POST -def logout(request): +def logout(request: HttpRequest) -> HttpResponseRedirect: """The view to log out a user. Args: - request (HttpRequest): The request. + request: The request. Returns: - HttpRedirectResponse: The redirect response. + The redirect response. """ logout_user(request) if "next" in request.POST: @@ -80,15 +81,15 @@ class UserView(DetailView): @require_GET @login_required -def user_form(request, user=None): +def user_form(request: HttpRequest, user: User = None) -> HttpResponse: """The view to edit an accounting transaction. Args: - request (HttpRequest): The request. - user (User): The account. + request: The request. + user: The account. Returns: - HttpResponse: The response. + The response. """ previous_post = stored_post.get_previous_post(request) if previous_post is not None: @@ -108,15 +109,16 @@ def user_form(request, user=None): }) -def user_store(request, user=None): +def user_store(request: HttpRequest, + user: User = None) -> HttpResponseRedirect: """The view to store a user. Args: - request (HttpRequest): The request. - user (Account): The user. + request: The request. + user: The user. Returns: - HttpResponseRedirect: The response. + The response. """ post = request.POST.dict() strip_post(post) @@ -148,15 +150,15 @@ def user_store(request, user=None): @require_POST @login_required -def user_delete(request, user): +def user_delete(request: HttpRequest, user: User) -> HttpResponseRedirect: """The view to delete an user. Args: - request (HttpRequest): The request. - user (User): The user. + request: The request. + user: The user. Returns: - HttpResponseRedirect: The response. + The response. """ message = None if user.pk == request.user.pk: @@ -177,14 +179,14 @@ def user_delete(request, user): @require_GET @login_required -def my_account_form(request): +def my_account_form(request: HttpRequest) -> HttpResponse: """The view to edit my account. Args: - request (HttpRequest): The request. + request: The request. Returns: - HttpResponse: The response. + The response. """ previous_post = stored_post.get_previous_post(request) if previous_post is not None: @@ -201,14 +203,14 @@ def my_account_form(request): }) -def my_account_store(request): +def my_account_store(request: HttpRequest) -> HttpResponseRedirect: """The view to store my account. Args: - request (HttpRequest): The request. + request: The request. Returns: - HttpResponseRedirect: The response. + The response. """ post = request.POST.dict() strip_post(post) @@ -232,15 +234,15 @@ def my_account_store(request): return redirect("mia_core:my-account") -def api_users_exists(request, login_id): +def api_users_exists(request: HttpRequest, login_id: str) -> JsonResponse: """The view to check whether a user with a log in ID exists. Args: - request (HttpRequest): The request. - login_id (str): The log in ID. + request: The request. + login_id: The log in ID. Returns: - JsonResponse: The response. + The response. """ try: User.objects.get(login_id=login_id)