From 08c56188ffb3ee14818e54cfa943ee2705e06f08 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Sat, 26 Dec 2020 20:50:20 +0100
Subject: [PATCH] Add docstrings for data check system

---
 aleksis/core/data_checks.py | 118 +++++++++++++++++++++++++++++++++++-
 1 file changed, 117 insertions(+), 1 deletion(-)

diff --git a/aleksis/core/data_checks.py b/aleksis/core/data_checks.py
index bf823ccc9..9f2a0ac07 100644
--- a/aleksis/core/data_checks.py
+++ b/aleksis/core/data_checks.py
@@ -11,6 +11,31 @@ from .util.core_helpers import celery_optional, get_site_preferences
 
 
 class SolveOption:
+    """Define a solve option for one or more data checks.
+
+    Solve options are used in order to give the data admin typical
+    solutions to a data issue detected by a data check.
+
+    Example definition
+
+    .. code-block:: python
+
+        from aleksis.core.data_checks import SolveOption
+        from django.utils.translation import gettext as _
+
+        class DeleteSolveOption(SolveOption):
+            name = "delete" # has to be unqiue
+            verbose_name = _("Delete") # should make use of i18n
+
+            @classmethod
+            def solve(cls, check_result: "DataCheckResult"):
+                check_result.related_object.delete()
+                check_result.delete()
+
+    After the solve option has been successfully executed,
+    the corresponding data check result has to be deleted.
+    """
+
     name: str = "default"
     verbose_name: str = ""
 
@@ -25,11 +50,87 @@ class IgnoreSolveOption(SolveOption):
 
     @classmethod
     def solve(cls, check_result: "DataCheckResult"):
+        """Mark the object as solved without doing anything more."""
         check_result.solved = True
         check_result.save()
 
 
 class DataCheck:
+    """Define a data check.
+
+    Data checks should be used to search objects of
+    any type which are broken or need some extra action.
+
+    Defining data checks
+    --------------------
+    Data checks are defined by inheriting from the class DataCheck
+    and registering the inherited class in the data check registry.
+
+    Example:
+
+    .. code-block:: python
+
+        from aleksis.core.data_checks import DataCheck, DATA_CHECK_REGISTRY
+        from django.utils.translation import gettext as _
+
+        @DATA_CHECK_REGISTRY.register
+        class ExampleDataCheck(DataCheck):
+            name = "example" # has to be unique
+            verbose_name = _("Ensure that there are no examples.")
+            problem_name = _("There is an example.") # should both make use of i18n
+
+            solve_options = {
+                IgnoreSolveOption.name: IgnoreSolveOption
+            }
+
+            @classmethod
+            def check_data(cls):
+                from example_app.models import ExampleModel
+
+                wrong_examples = ExampleModel.objects.filter(wrong_value=True)
+
+                for example in wrong_examples:
+                    cls.register_result(example)
+
+    Solve options are used in order to give the data admin typical solutions to this specific issue.
+    They are defined by inheriting from SolveOption.
+    More information about defining solve options can be find there.
+
+    The dictionary ``solve_options`` should include at least the IgnoreSolveOption,
+    but preferably also own solve options. The keys in this dictionary
+    have to be ``<YourOption>SolveOption.name``
+    and the values must be the corresponding solve option classes.
+
+    The class method ``check_data`` does the actual work. In this method
+    your code should find all objects with issues and should register
+    them in the result database using the class method ``register_result``.
+
+    Data checks have to be registered in the central registry.
+    This can be done by decorating the class with
+    ``@DATA_CHECK_REGISTRY.register`` or adding it later
+    by ``DATA_CHECK_REGISTRY.register(<YourCheck>DataCheck)``.
+
+    Executing data checks
+    ---------------------
+    The data checks can be executed by using the
+    celery task named ``aleksis.core.data_checks.check_data``.
+    We recommend to create a periodic task in the backend
+    which executes ``check_data`` on a regular base (e. g. every day).
+
+    .. warning::
+        To use the option described above, you must have setup celery properly.
+
+    Notifications about results
+    ---------------------------
+    The data check tasks can notify persons via email
+    if there are new data issues. You can set these persons
+    by adding them to the preference
+    ``Email recipients for data checks problem emails`` in the site configuration.
+
+    To enable this feature, you also have to activate
+    the preference ``Send emails if data checks detect problems``.
+    """  # noqa: D412
+
     name: str = ""
     verbose_name: str = ""
     problem_name: str = ""
@@ -38,15 +139,26 @@ class DataCheck:
 
     @classmethod
     def check_data(cls):
+        """Find all objects with data issues and register them."""
         pass
 
     @classmethod
-    def solve(cls, check_result: "DataCheckResult", solve_option: str = "default"):
+    def solve(cls, check_result: "DataCheckResult", solve_option: str):
+        """Execute a solve option for an object detected by this check.
+
+        :param check_result: The result item from database
+        :param solve_option: The name of the solve option that should be executed
+        """
         with reversion.create_revision():
             cls.solve_options[solve_option].solve(check_result)
 
     @classmethod
     def register_result(cls, instance) -> "DataCheckResult":
+        """Register an object with data issues in the result database.
+
+        :param instance: The affected object
+        :return: The database entry
+        """
         from aleksis.core.models import DataCheckResult
 
         ct = ContentType.objects.get_for_model(instance)
@@ -57,12 +169,15 @@ class DataCheck:
 
 
 class DataCheckRegistry:
+    """Create central registry for all data checks in AlekSIS."""
+
     def __init__(self):
         self.data_checks = []
         self.data_checks_by_name = {}
         self.data_checks_choices = []
 
     def register(self, check: DataCheck):
+        """Add a new data check to the registry."""
         self.data_checks.append(check)
         self.data_checks_by_name[check.name] = check
         self.data_checks_choices.append((check.name, check.verbose_name))
@@ -74,6 +189,7 @@ DATA_CHECK_REGISTRY = DataCheckRegistry()
 
 @celery_optional
 def check_data():
+    """Execute all registered data checks and send email if activated."""
     for check in DATA_CHECK_REGISTRY.data_checks:
         logging.info(f"Run check: {check.verbose_name}")
         check.check_data()
-- 
GitLab