Skip to content
Snippets Groups Projects
Verified Commit 08c56188 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Add docstrings for data check system

parent 9c91d140
No related branches found
No related tags found
1 merge request!389Add data check system
Pipeline #5013 passed
......@@ -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()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment