Added type hints to the Mia core application.

This commit is contained in:
依瑪貓 2020-08-13 08:05:35 +08:00
parent c070b11ea2
commit 1be05c2252
9 changed files with 292 additions and 250 deletions

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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)