diff --git a/aleksis/core/util/celery_progress.py b/aleksis/core/util/celery_progress.py
index ac48c7c56001a51696c8099950ed799c6a2a9cea..63428460be071e03a7bd5fe6fa64f706f3670cfa 100644
--- a/aleksis/core/util/celery_progress.py
+++ b/aleksis/core/util/celery_progress.py
@@ -1,6 +1,8 @@
-from decimal import Decimal
 from functools import wraps
-from typing import Callable, Union
+from numbers import Number
+from typing import Callable, Optional
+
+from django.contrib import messages
 
 from celery_progress.backend import PROGRESS_STATE, AbstractProgressRecorder
 
@@ -26,11 +28,11 @@ class ProgressRecorder(AbstractProgressRecorder):
         @recorded_task
         def do_something(foo, bar, recorder, baz=None):
             # ...
-            recorder.total = len(list_with_data)
+            recorder.set_progress(total=len(list_with_data))
 
             for i, item in enumerate(list_with_data):
                 # ...
-                recorder.set_progress(i + 1)
+                recorder.set_progress(i+1)
                 # ...
 
             recorder.add_message(messages.SUCCESS, "All data were imported successfully.")
@@ -61,42 +63,57 @@ class ProgressRecorder(AbstractProgressRecorder):
 
     def __init__(self, task):
         self.task = task
-        self.messages = []
-        self.total = 100
-        self.current = 0
-
-    def set_progress(self, current: Union[int, float], **kwargs):
+        self._messages = []
+        self._current = 0
+        self._total = 100
+
+    def set_progress(
+        self,
+        current: Optional[Number] = None,
+        total: Optional[Number] = None,
+        description: Optional[str] = None,
+        level: int = messages.INFO,
+    ):
         """Set the current progress in the frontend.
 
         The progress percentage is automatically calculated in relation to self.total.
 
-        :param current: The absolute number of processed items
+        :param current: The number of processed items; relative to total, default unchanged
+        :param total: The total number of items (or 100 if using a percentage), default unchanged
+        :param description: A textual description, routed to the frontend as an INFO message
         """
-        self.current = current
+        if current is not None:
+            self._current = current
+        if total is not None:
+            self._total = total
 
         percent = 0
-        if self.total > 0:
-            percent = (Decimal(current) / Decimal(self.total)) * Decimal(100)
-            percent = float(round(percent, 2))
+        if self._total > 0:
+            percent = self._current / self._total
+
+        if description is not None:
+            self._messages.append((level, description))
 
         self.task.update_state(
             state=PROGRESS_STATE,
             meta={
-                "current": current,
-                "total": self.total,
+                "current": self._current,
+                "total": self._total,
                 "percent": percent,
-                "messages": self.messages,
+                "messages": self._messages,
             },
         )
 
-    def add_message(self, level: int, message: str):
+    def add_message(self, level: int, message: str) -> None:
         """Show a message in the progress frontend.
 
+        This method is a shortcut for set_progress with no new progress arguments,
+        passing only the message and level as description.
+
         :param level: The message level (default levels from django.contrib.messages)
         :param message: The actual message (should be translated)
         """
-        self.messages.append((level, message))
-        self.set_progress(self.current)
+        self.set_progress(description=message, level=level)
 
 
 def recorded_task(orig: Callable, *args, **kwargs) -> app.Task: