diff --git a/aleksis/core/checks.py b/aleksis/core/checks.py index 9518b5e26bfe2f8d2cc8eff7494eeb6f4495035d..db3199b6081d77e9bd8e2c559db07f7924484599 100644 --- a/aleksis/core/checks.py +++ b/aleksis/core/checks.py @@ -11,7 +11,7 @@ from .util.apps import AppConfig def check_app_configs_base_class( app_configs: Optional[django.apps.registry.Apps] = None, **kwargs ) -> list: - """Checks whether all apps derive from AlekSIS's base app config.""" + """Check whether all apps derive from AlekSIS's base app config.""" results = [] if app_configs is None: @@ -39,7 +39,7 @@ def check_app_configs_base_class( def check_app_models_base_class( app_configs: Optional[django.apps.registry.Apps] = None, **kwargs ) -> list: - """Checks whether all app models derive from AlekSIS's base ExtensibleModel.""" + """Check whether all app models derive from AlekSIS's base ExtensibleModel.""" results = [] if app_configs is None: diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py index 94283722c9d58168c245668c1a2a77d9e79b697d..d0b44263b1ccdcb73fcba55ee7e7aff36d44d5bd 100644 --- a/aleksis/core/mixins.py +++ b/aleksis/core/mixins.py @@ -142,22 +142,22 @@ class ExtensibleModel(models.Model): @classmethod def property(cls, func: Callable[[], Any], name: Optional[str] = None) -> None: - """Adds the passed callable as a property.""" + """Add the passed callable as a property.""" cls._safe_add(property(func), func.__name__) @classmethod def method(cls, func: Callable[[], Any], name: Optional[str] = None) -> None: - """Adds the passed callable as a method.""" + """Add the passed callable as a method.""" cls._safe_add(func, func.__name__) @classmethod def class_method(cls, func: Callable[[], Any], name: Optional[str] = None) -> None: - """Adds the passed callable as a classmethod.""" + """Add the passed callable as a classmethod.""" cls._safe_add(classmethod(func), func.__name__) @classmethod def field(cls, **kwargs) -> None: - """Adds the passed jsonstore field. Must be one of the fields in django-jsonstore. + """Add the passed jsonstore field. Must be one of the fields in django-jsonstore. Accepts exactly one keyword argument, with the name being the desired model field name and the value the field instance. diff --git a/aleksis/core/models.py b/aleksis/core/models.py index d924a7fdaabdcbb9e4c2aa728a390ce0e71cc54d..b22c4e729235a961902a0a95890b9e92f6ac35e6 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -38,11 +38,13 @@ FIELD_CHOICES = ( ("TextField", _("Text (multi-line)")), ("TimeField", _("Time")), ("URLField", _("URL / Link")), + ) class Person(ExtensibleModel): - """ + """Person model. + A model describing any person related to a school, including, but not limited to, students, teachers and guardians (parents). """ @@ -80,7 +82,7 @@ class Person(ExtensibleModel): ) short_name = models.CharField( - verbose_name=_("Short name"), max_length=255, blank=True, null=True, unique=True + verbose_name=_("Short name"), max_length=255, blank=True, unique=True ) street = models.CharField(verbose_name=_("Street"), max_length=255, blank=True) @@ -111,21 +113,21 @@ class Person(ExtensibleModel): "Group", models.SET_NULL, null=True, blank=True, verbose_name=_("Primary group") ) - description = models.TextField(verbose_name=_("Description"), blank=True, null=True) + description = models.TextField(verbose_name=_("Description"), blank=True) def get_absolute_url(self) -> str: return reverse("person_by_id", args=[self.id]) @property def primary_group_short_name(self) -> Optional[str]: - """Returns the short_name field of the primary group related object.""" + """Return the short_name field of the primary group related object.""" if self.primary_group: return self.primary_group.short_name @primary_group_short_name.setter def primary_group_short_name(self, value: str) -> None: """ - Sets the primary group related object by a short name. + Set the primary group related object by a short name. It uses the first existing group with this short name it can find, creating one @@ -237,7 +239,8 @@ class AdditionalField(ExtensibleModel): class Group(ExtensibleModel): - """ + """Group model. + Any kind of group of persons in a school, including, but not limited classes, clubs, and the like. """ @@ -252,7 +255,7 @@ class Group(ExtensibleModel): name = models.CharField(verbose_name=_("Long name"), max_length=255, unique=True) short_name = models.CharField( - verbose_name=_("Short name"), max_length=255, unique=True, blank=True, null=True + verbose_name=_("Short name"), max_length=255, unique=True, blank=True ) members = models.ManyToManyField( @@ -387,7 +390,8 @@ class AnnouncementQuerySet(models.QuerySet): """Queryset for announcements providing time-based utility functions.""" def relevant_for(self, obj: Union[models.Model, models.QuerySet]) -> models.QuerySet: - """ + """Get all relevant announcements. + Get a QuerySet with all announcements relevant for a certain Model (e.g. a Group) or a set of models in a QuerySet. """ @@ -437,7 +441,8 @@ class AnnouncementQuerySet(models.QuerySet): class Announcement(ExtensibleModel): - """ + """Announcement model. + Persistent announcement to display to groups or persons in various places during a specific time range. """ @@ -464,7 +469,7 @@ class Announcement(ExtensibleModel): return persons def get_recipients_for_model(self, obj: Union[models.Model]) -> Sequence[models.Model]: - """ + """Get all recipients. Get all recipients for this announcement with a special content type (provided through model) """ @@ -480,7 +485,8 @@ class Announcement(ExtensibleModel): class AnnouncementRecipient(ExtensibleModel): - """ + """Announcement recipient model. + Generalisation of a recipient for an announcement, used to wrap arbitrary objects that can receive announcements. @@ -564,14 +570,15 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel): media = Media() title = models.CharField(max_length=150, verbose_name=_("Widget Title")) - active = models.BooleanField(blank=True, verbose_name=_("Activate Widget")) + active = models.BooleanField(verbose_name=_("Activate Widget")) def get_context(self): """Get the context dictionary to pass to the widget template.""" raise NotImplementedError("A widget subclass needs to implement the get_context method.") def get_template(self): - """ + """Get template. + Get the template to render the widget with. Defaults to the template attribute, but can be overridden to allow more complex template generation scenarios. """ @@ -613,7 +620,7 @@ class CustomMenuItem(ExtensibleModel): name = models.CharField(max_length=150, verbose_name=_("Name")) url = models.URLField(verbose_name=_("Link")) icon = models.CharField( - max_length=50, blank=True, null=True, choices=ICONS, verbose_name=_("Icon") + max_length=50, blank=True, choices=ICONS, verbose_name=_("Icon") ) def __str__(self): @@ -625,7 +632,8 @@ class CustomMenuItem(ExtensibleModel): class GroupType(ExtensibleModel): - """ + """Group type model. + Descriptive type of a group; used to tag groups and for apps to distinguish how to display or handle a certain group. """ diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py index 0f101a11d19a6097fd7c04dc54de266cf340f79f..11bc70d1bbffe2b986c3543f708aa8b1f4dba10b 100644 --- a/aleksis/core/util/apps.py +++ b/aleksis/core/util/apps.py @@ -2,6 +2,7 @@ from importlib import import_module from typing import Any, List, Optional, Sequence, Tuple import django.apps +from django.contrib.auth import get_user_model from django.contrib.auth.signals import user_logged_in, user_logged_out from django.db.models.signals import post_migrate, pre_migrate from django.http import HttpRequest @@ -13,6 +14,9 @@ from spdx_license_list import LICENSES from .core_helpers import copyright_years +User = get_user_model() + + class AppConfig(django.apps.AppConfig): """An extended version of DJango's AppConfig container.""" @@ -149,7 +153,7 @@ class AppConfig(django.apps.AppConfig): new_value: Optional[Any] = None, **kwargs, ) -> None: - """Called on every app instance if a dynamic preference changes, and once on startup. + """Call on every app instance if a dynamic preference changes, and once on startup. By default, it does nothing. """ @@ -165,7 +169,7 @@ class AppConfig(django.apps.AppConfig): apps: django.apps.registry.Apps, **kwargs, ) -> None: - """Called on every app instance before its models are migrated. + """Call on every app instance before its models are migrated. By default, it does nothing. """ @@ -181,7 +185,7 @@ class AppConfig(django.apps.AppConfig): apps: django.apps.registry.Apps, **kwargs, ) -> None: - """Called on every app instance after its models have been migrated. + """Call on every app instance after its models have been migrated. By default, asks all models to do maintenance on their default data. """ @@ -190,7 +194,7 @@ class AppConfig(django.apps.AppConfig): def user_logged_in( self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs ) -> None: - """Called after a user logged in. + """Call after a user logged in. By default, it does nothing. """ @@ -199,7 +203,7 @@ class AppConfig(django.apps.AppConfig): def user_logged_out( self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs ) -> None: - """Called after a user logged out. + """Call after a user logged out. By default, it does nothing. """ diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py index 79bf5059443aa64aa8c45deb007c6b5619e30285..67c9daae584fbdbe84294b8439d7cfb20481aa01 100644 --- a/aleksis/core/util/core_helpers.py +++ b/aleksis/core/util/core_helpers.py @@ -16,7 +16,7 @@ from django.utils.functional import lazy def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = "–") -> str: - """Takes a sequence of integegers and produces a string with ranges. + """Take a sequence of integegers and produces a string with ranges. >>> copyright_years([1999, 2000, 2001, 2005, 2007, 2008, 2009]) '1999–2001, 2005, 2007–2009' @@ -34,7 +34,7 @@ def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = " def dt_show_toolbar(request: HttpRequest) -> bool: - """Helper to determin if Django debug toolbar should be displayed. + """Add a helper to determin if Django debug toolbar should be displayed. Extends the default behaviour by enabling DJDT for superusers independent of source IP. @@ -66,7 +66,8 @@ def get_app_packages() -> Sequence[str]: def merge_app_settings( setting: str, original: Union[dict, list], deduplicate: bool = False ) -> Union[dict, list]: - """ + """Merge app settings. + Get a named settings constant from all apps and merge it into the original. To use this, add a settings.py file to the app, in the same format as Django's main settings.py. @@ -170,7 +171,7 @@ def has_person(obj: Union[HttpRequest, Model]) -> bool: def celery_optional(orig: Callable) -> Callable: - """Decorator that makes Celery optional for a function. + """Add a decorator that makes Celery optional for a function. If Celery is configured and available, it wraps the function in a Task and calls its delay method when invoked; if not, it leaves it untouched @@ -191,7 +192,7 @@ def celery_optional(orig: Callable) -> Callable: def path_and_rename(instance, filename: str, upload_to: str = "files") -> str: - """Updates path of an uploaded file and renames it to a random UUID in Django FileField.""" + """Update path of an uploaded file and renames it to a random UUID in Django FileField.""" _, ext = os.path.splitext(filename) # set filename as random string @@ -205,7 +206,7 @@ def path_and_rename(instance, filename: str, upload_to: str = "files") -> str: def custom_information_processor(request: HttpRequest) -> dict: - """Provides custom information in all templates.""" + """Provide custom information in all templates.""" from ..models import CustomMenu return { diff --git a/aleksis/core/util/messages.py b/aleksis/core/util/messages.py index bd125d1cb46b54d890dd80850627d06406f153f2..5efc04c12befb65825164f8f954eedbd4dc4500c 100644 --- a/aleksis/core/util/messages.py +++ b/aleksis/core/util/messages.py @@ -8,7 +8,9 @@ from django.http import HttpRequest def add_message( request: Optional[HttpRequest], level: int, message: str, **kwargs ) -> Optional[Any]: - """ Add a message to either Django's message framework, if called from a web request, + """Add a message. + + Add a message to either Django's message framework, if called from a web request, or to the default logger. Default to DEBUG level. @@ -20,7 +22,9 @@ def add_message( def debug(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]: - """ Add a message to either Django's message framework, if called from a web request, + """Add a debug message. + + Add a message to either Django's message framework, if called from a web request, or to the default logger. Default to DEBUG level. @@ -29,7 +33,9 @@ def debug(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[An def info(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]: - """ Add a message to either Django's message framework, if called from a web request, + """Add a info message. + + Add a message to either Django's message framework, if called from a web request, or to the default logger. Default to INFO level. @@ -38,7 +44,9 @@ def info(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any def success(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]: - """ Add a message to either Django's message framework, if called from a web request, + """Add a success message. + + Add a message to either Django's message framework, if called from a web request, or to the default logger. Default to SUCCESS level. @@ -47,7 +55,9 @@ def success(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[ def warning(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]: - """ Add a message to either Django's message framework, if called from a web request, + """Add a warning message. + + Add a message to either Django's message framework, if called from a web request, or to the default logger. Default to WARNING level. @@ -56,7 +66,9 @@ def warning(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[ def error(request: Optional[HttpRequest], message: str, **kwargs) -> Optional[Any]: - """ Add a message to either Django's message framework, if called from a web request, + """Add an error message. + + Add a message to either Django's message framework, if called from a web request, or to the default logger. Default to ERROR level. diff --git a/aleksis/core/util/notifications.py b/aleksis/core/util/notifications.py index 2c7172a1d28a0c69c705fd9390e090ffdac61e3c..1a770a7818e88212494ab9442421a546bede93bf 100644 --- a/aleksis/core/util/notifications.py +++ b/aleksis/core/util/notifications.py @@ -11,6 +11,7 @@ from django.utils.translation import gettext_lazy as _ from templated_email import send_templated_mail from .core_helpers import lazy_preference +from ..models import Notification try: from twilio.rest import Client as TwilioClient diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py index da099c3a948d5ab05c36803922b61bd8b6a743d5..0e452ddb0cb560ba4edbe174c560ade56c6261ed 100644 --- a/aleksis/core/util/predicates.py +++ b/aleksis/core/util/predicates.py @@ -13,24 +13,24 @@ from .core_helpers import has_person as has_person_helper def permission_validator(request: HttpRequest, perm: str) -> bool: - """Checks whether the request user has a permission.""" + """Check whether the request user has a permission.""" if request.user: return request.user.has_perm(perm) return False def check_global_permission(user: User, perm: str) -> bool: - """Checks whether a user has a global permission.""" + """Check whether a user has a global permission.""" return ModelBackend().has_perm(user, perm) def check_object_permission(user: User, perm: str, obj: Model) -> bool: - """Checks whether a user has a permission on a object.""" + """Check whether a user has a permission on a object.""" return ObjectPermissionBackend().has_perm(user, perm, obj) def has_global_perm(perm: str): - """Builds predicate which checks whether a user has a global permission.""" + """Build predicate which checks whether a user has a global permission.""" name = f"has_global_perm:{perm}" @predicate(name) @@ -41,7 +41,7 @@ def has_global_perm(perm: str): def has_object_perm(perm: str): - """Builds predicate which checks whether a user has a permission on a object.""" + """Build predicate which checks whether a user has a permission on a object.""" name = f"has_global_perm:{perm}" @predicate(name) @@ -54,8 +54,10 @@ def has_object_perm(perm: str): def has_any_object(perm: str, klass): - """ - Build predicate which checks whether a user has access to objects with the provided permission. + """Check if has any object. + + Build predicate which checks whether a user has access + to objects with the provided permission. """ name = f"has_any_object:{perm}" @@ -98,7 +100,8 @@ def is_group_owner(user: User, group: Group) -> bool: @predicate def is_notification_recipient(user: User, obj: Model) -> bool: - """ + """Check if is a notification recipient. + Predicate which checks whether the recipient of the notification a user wants to mark read is this user. """ diff --git a/aleksis/core/util/search.py b/aleksis/core/util/search.py index 437887ae6412c0e3f8b493908346be8315b69481..22abef17c59d667f0954eb22c15dbab71b64aae2 100644 --- a/aleksis/core/util/search.py +++ b/aleksis/core/util/search.py @@ -12,7 +12,7 @@ else: class SearchIndex(BaseSearchIndex): - """ Base class for search indexes on AlekSIS models. + """Base class for search indexes on AlekSIS models. It provides a default document field caleld text and exects the related model in the model attribute.