diff --git a/aleksis/core/assets/components/CeleryProgress.vue b/aleksis/core/assets/components/CeleryProgress.vue
new file mode 100644
index 0000000000000000000000000000000000000000..48c35370e5efb6ca6213bbcea6d8445eac01a850
--- /dev/null
+++ b/aleksis/core/assets/components/CeleryProgress.vue
@@ -0,0 +1,127 @@
+<template>
+  <v-row>
+    <v-col sm="0" md="1" lg="2" xl="3"/>
+    <v-col sm="12" md="10" lg="8" xl="6">
+      <v-card :loading="$apollo.loading">
+        <v-card-title v-if="progress">
+          {{ progress.meta.title }}
+        </v-card-title>
+        <v-card-text v-if="progress">
+          <v-progress-linear
+              :value="progress.progress.percent"
+              buffer-value="0"
+              color="primary"
+              class="mb-2"
+              stream
+          />
+          <div class="text-center mb-4">
+            {{ progress.meta.progressTitle }}
+          </div>
+          <v-alert
+              v-if="data"
+              v-for="(message, idx) in progress.messages"
+              dense
+              text
+              :type="STYLE_CLASSES[message.level]"
+              transition="slide-x-transition"
+              :key="idx"
+          >
+            {{ message.message }}
+          </v-alert>
+          <v-alert
+              v-if="progress.state === 'ERROR'"
+              dense
+              text
+              type="error"
+              transition="slide-x-transition"
+          >
+            {{ progress.meta.errorMessage }}
+          </v-alert>
+          <v-alert
+              v-if="progress.state === 'SUCCESS'"
+              dense
+              text
+              type="success"
+              transition="slide-x-transition"
+          >
+            {{ progress.meta.successMessage }}
+          </v-alert>
+        </v-card-text>
+        <v-card-actions
+            v-if="progress && (progress.state === 'ERROR' || progress.state === 'SUCCESS')">
+          <v-btn :href="progress.meta.backUrl" text color="secondary">
+            <v-icon left>mdi-arrow-left</v-icon>
+            Go back
+          </v-btn>
+          <v-spacer/>
+          <v-btn v-if="progress.meta.additionalButton"
+                 :href="progress.meta.additionalButton.url" text color="primary">
+            <v-icon v-if="progress.meta.additionalButton.icon" left>
+              {{ progress.meta.additionalButton.icon }}
+            </v-icon>
+            {{ progress.meta.additionalButton.title }}
+          </v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-col>
+    <v-col sm="0" md="1" lg="2" xl="3"/>
+  </v-row>
+</template>
+
+<script>
+const STYLE_CLASSES = {
+  10: 'info',
+  20: 'info',
+  25: 'success',
+  30: 'warning',
+  40: 'error',
+};
+
+const ICONS = {
+  10: 'mdi-information',
+  20: 'mdi-information',
+  25: 'mdi-check-circle',
+  30: 'mdi-alert-outline',
+  40: 'mdi-alert-octagon-outline',
+};
+export default {
+  name: 'CeleryProgress',
+  apollo: {
+    celeryProgressByTaskId: {
+      query: require('./celeryProgress.graphql'),
+      variables() {
+        return {
+          taskId: this.$route.params.taskId,
+        };
+      },
+      pollInterval: 1000,
+    }
+  },
+  data() {
+    return {
+      STYLE_CLASSES,
+      ICONS,
+    };
+  },
+  computed: {
+    progress() {
+      return this.celeryProgressByTaskId;
+    },
+    state() {
+      return this.progress ? this.progress.state : null;
+    },
+  },
+  watch: {
+    state(newState, oldState)
+    {
+      if (newState === 'SUCCESS' || newState === 'ERROR') {
+        this.$apollo.queries.celeryProgressByTaskId.stopPolling();
+      }
+      if (newState === 'SUCCESS') {
+        window.location.replace(this.progress.meta.redirectOnSuccessUrl);
+        // FIXME this.$router.push(this.progress.meta.redirectOnSuccessUrl);
+      }
+    }
+  }
+}
+</script>
diff --git a/aleksis/core/assets/components/celeryProgress.graphql b/aleksis/core/assets/components/celeryProgress.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..49b03ef82bd13e72248d72a98abf90fe6e7c8f1f
--- /dev/null
+++ b/aleksis/core/assets/components/celeryProgress.graphql
@@ -0,0 +1,29 @@
+query($taskId: String!){
+    celeryProgressByTaskId(taskId:$taskId) {
+        state
+        success
+        progress {
+            current
+            total
+            percent
+        }
+        complete
+        messages {
+            level
+            message
+        }
+        meta {
+            title
+            progressTitle
+            errorMessage
+            successMessage
+            redirectOnSuccessUrl
+            backUrl
+            additionalButton {
+                title
+                icon
+                url
+            }
+        }
+    }
+}
diff --git a/aleksis/core/assets/index.js b/aleksis/core/assets/index.js
index 94f4131baf7181ea2a299c518f5fe942b850926d..9d2837ee974ce2b0d2becdf3cdfeffc05448f969 100644
--- a/aleksis/core/assets/index.js
+++ b/aleksis/core/assets/index.js
@@ -2,3 +2,6 @@ import '@mdi/font/css/materialdesignicons.css'
 
 import "./util"
 import "./app"
+import CeleryProgress from "./components/CeleryProgress.vue";
+
+window.router.addRoute({ path: "/celery_progress/:taskId", component: CeleryProgress, props: true });
diff --git a/aleksis/core/migrations/0042_task_assignment_meta.py b/aleksis/core/migrations/0042_task_assignment_meta.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe70ebd2b6783e9f92a28239164144a01d1ccb3b
--- /dev/null
+++ b/aleksis/core/migrations/0042_task_assignment_meta.py
@@ -0,0 +1,62 @@
+# Generated by Django 3.2.15 on 2022-10-03 18:38
+
+from django.db import migrations, models
+import django.utils.timezone
+import oauth2_provider.generators
+import oauth2_provider.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0041_update_gender_choices'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='additional_button_icon',
+            field=models.CharField(blank=True, max_length=255, verbose_name='Additional button icon'),
+        ),
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='additional_button_title',
+            field=models.CharField(blank=True, max_length=255, verbose_name='Additional button title'),
+        ),
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='additional_button_url',
+            field=models.URLField(blank=True, verbose_name='Additional button URL'),
+        ),
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='back_url',
+            field=models.URLField(blank=True, verbose_name='Back URL'),
+        ),
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='error_message',
+            field=models.TextField(blank=True, verbose_name='Error message'),
+        ),
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='success_message',
+            field=models.TextField(blank=True, verbose_name='Success message'),
+        ),
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='progress_title',
+            field=models.CharField(blank=True, max_length=255, verbose_name='Progress title'),
+        ),
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='redirect_on_success_url',
+            field=models.URLField(blank=True, verbose_name='Redirect on success URL'),
+        ),
+        migrations.AddField(
+            model_name='taskuserassignment',
+            name='title',
+            field=models.CharField(default='Data are processed', max_length=255, verbose_name='Title'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 2f603ae0d2e2813b50f2024056cb460d71b4e484..3462c40349f7d2130b8d59f812e174944b50b1e8 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -31,6 +31,7 @@ import jsonstore
 from cachalot.api import cachalot_disabled
 from cache_memoize import cache_memoize
 from celery.result import AsyncResult
+from celery_progress.backend import Progress
 from ckeditor.fields import RichTextField
 from django_celery_results.models import TaskResult
 from django_cte import CTEQuerySet, With
@@ -1257,6 +1258,20 @@ class TaskUserAssignment(ExtensibleModel):
         get_user_model(), on_delete=models.CASCADE, verbose_name=_("Task user")
     )
 
+    title = models.CharField(max_length=255, verbose_name=_("Title"))
+    back_url = models.URLField(verbose_name=_("Back URL"), blank=True)
+    progress_title = models.CharField(max_length=255, verbose_name=_("Progress title"), blank=True)
+    error_message = models.TextField(verbose_name=_("Error message"), blank=True)
+    success_message = models.TextField(verbose_name=_("Success message"), blank=True)
+    redirect_on_success_url = models.URLField(verbose_name=_("Redirect on success URL"), blank=True)
+    additional_button_title = models.CharField(
+        max_length=255, verbose_name=_("Additional button title"), blank=True
+    )
+    additional_button_url = models.URLField(verbose_name=_("Additional button URL"), blank=True)
+    additional_button_icon = models.CharField(
+        max_length=255, verbose_name=_("Additional button icon"), blank=True
+    )
+
     @classmethod
     def create_for_task_id(cls, task_id: str, user: "User") -> "TaskUserAssignment":
         # Use get_or_create to ensure the TaskResult exists
@@ -1265,6 +1280,11 @@ class TaskUserAssignment(ExtensibleModel):
             result, __ = TaskResult.objects.get_or_create(task_id=task_id)
         return cls.objects.create(task_result=result, user=user)
 
+    def get_progress(self) -> dict[str, any]:
+        """Get progress information for this task."""
+        progress = Progress(AsyncResult(self.task_result.task_id))
+        return progress.get_info()
+
     class Meta:
         verbose_name = _("Task user assignment")
         verbose_name_plural = _("Task user assignments")
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index e12744ff6df91c72f8ec541b8f0c0672d2ddf273..c5f2bc4d498064d687392b7e63057de50b5c1136 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -11,6 +11,7 @@ from .util.predicates import (
     is_current_person,
     is_group_owner,
     is_notification_recipient,
+    is_own_celery_task,
     is_site_preference_set,
 )
 
@@ -374,3 +375,6 @@ rules.add_perm("core.edit_ical_rule", edit_ical_predicate)
 
 delete_ical_predicate = edit_ical_predicate
 rules.add_perm("core.delete_ical_rule", delete_ical_predicate)
+
+view_progress_predicate = has_person & is_own_celery_task
+rules.add_perm("core.view_progress_rule", view_progress_predicate)
diff --git a/aleksis/core/schema.py b/aleksis/core/schema.py
index cb6325c0640557314ae299a1d81719d910468391..5fed3cfdb08e902f38e806361c3203ec73434cf2 100644
--- a/aleksis/core/schema.py
+++ b/aleksis/core/schema.py
@@ -7,7 +7,7 @@ from graphene_django import DjangoObjectType
 from graphene_django.forms.mutation import DjangoModelFormMutation
 
 from .forms import PersonForm
-from .models import Group, Notification, Person
+from .models import Group, Notification, Person, TaskUserAssignment
 from .util.core_helpers import get_app_module, get_app_packages, has_person
 from .util.frontend_helpers import get_language_cookie
 
@@ -55,6 +55,68 @@ class SystemPropertiesType(graphene.ObjectType):
         ]
 
 
+class CeleryProgressMessage(ObjectType):
+    message = graphene.String(required=True)
+    level = graphene.Int(required=True)
+
+    def resolve_message(root, info, **kwargs):
+        return root[1]
+
+    def resolve_level(root, info, **kwargs):
+        return root[0]
+
+
+class CeleryProgressAdditionalButtonType(ObjectType):
+    title = graphene.String(required=True)
+    url = graphene.String(required=True)
+    icon = graphene.String()
+
+
+class CeleryProgressMetaType(DjangoObjectType):
+    additional_button = graphene.Field(CeleryProgressAdditionalButtonType, required=False)
+
+    class Meta:
+        model = TaskUserAssignment
+        fields = (
+            "title",
+            "back_url",
+            "progress_title",
+            "error_message",
+            "success_message",
+            "redirect_on_success_url",
+            "additional_button",
+        )
+
+    def resolve_additional_button(root, info, **kwargs):
+        if not root.additional_button_title or not root.additional_button_url:
+            return None
+        return {
+            "title": root.additional_button_title,
+            "url": root.additional_button_url,
+            "icon": root.additional_button_icon,
+        }
+
+
+class CeleryProgressProgressType(ObjectType):
+    current = graphene.Int()
+    total = graphene.Int()
+    percent = graphene.Float()
+
+
+class CeleryProgressType(graphene.ObjectType):
+    state = graphene.String()
+    complete = graphene.Boolean()
+    success = graphene.Boolean()
+    progress = graphene.Field(CeleryProgressProgressType)
+    messages = graphene.List(CeleryProgressMessage)
+    meta = graphene.Field(CeleryProgressMetaType)
+
+    def resolve_messages(root, info, **kwargs):  # noqa
+        if root["complete"] and isinstance(root["result"], list):
+            return root["result"]
+        return root["progress"].get("messages", [])
+
+
 class PersonMutation(DjangoModelFormMutation):
     person = graphene.Field(PersonType)
 
@@ -89,6 +151,8 @@ class Query(graphene.ObjectType):
 
     system_properties = graphene.Field(SystemPropertiesType)
 
+    celery_progress_by_task_id = graphene.Field(CeleryProgressType, task_id=graphene.String())
+
     def resolve_notifications(root, info, **kwargs):
         # FIXME do permission stuff
         return Notification.objects.all()
@@ -109,6 +173,18 @@ class Query(graphene.ObjectType):
     def resolve_system_properties(root, info, **kwargs):
         return True
 
+    def resolve_celery_progress_by_task_id(root, info, task_id, **kwargs):
+        print(TaskUserAssignment.objects.filter(task_result__task_id=task_id), "HDS")
+        print("DOES DO ")
+        print("RUN")
+        task = TaskUserAssignment.objects.get(task_result__task_id=task_id)
+
+        if not info.context.user.has_perm("core.view_progress_rule", task):
+            return None
+        progress = task.get_progress()
+        progress["meta"] = task
+        return progress
+
 
 class Mutation(graphene.ObjectType):
     update_person = PersonMutation.Field()
diff --git a/aleksis/core/static/js/progress.js b/aleksis/core/static/js/progress.js
deleted file mode 100644
index 8a97577e93d276cd62c69ead4c65b448a7631d49..0000000000000000000000000000000000000000
--- a/aleksis/core/static/js/progress.js
+++ /dev/null
@@ -1,84 +0,0 @@
-const OPTIONS = getJSONScript("progress_options");
-
-const STYLE_CLASSES = {
-    10: 'info',
-    20: 'info',
-    25: 'success',
-    30: 'warning',
-    40: 'error',
-};
-
-const ICONS = {
-    10: 'mdi:information',
-    20: 'mdi:information',
-    25: 'mdi:check-circle',
-    30: 'mdi:alert-outline',
-    40: 'mdi:alert-octagon-outline',
-};
-
-function setProgress(progress) {
-    $("#progress-bar").css("width", progress + "%");
-}
-
-function renderMessageBox(level, text) {
-    return '<div class="alert ' + STYLE_CLASSES[level] + '"><p><i class="material-icons iconify left" data-icon="' + ICONS[level] + '"></i>' + text + '</p></div>';
-}
-
-function updateMessages(messages) {
-    const messagesBox = $("#messages");
-
-    // Clear container
-    messagesBox.html("");
-
-    // Render message boxes
-    $.each(messages, function (i, message) {
-        messagesBox.append(renderMessageBox(message[0], message[1]));
-    });
-}
-
-function customProgress(progressBarElement, progressBarMessageElement, progress) {
-    setProgress(progress.percent);
-
-    if (progress.hasOwnProperty("messages")) {
-        updateMessages(progress.messages);
-    }
-}
-
-
-function customSuccess(progressBarElement, progressBarMessageElement, result) {
-    setProgress(100);
-    if (result) {
-        updateMessages(result);
-    }
-    $("#result-alert").addClass("success");
-    $("#result-icon").attr("data-icon", "mdi:check-circle-outline");
-    $("#result-text").text(OPTIONS.success);
-    $("#result-box").show();
-    $("#result-button").show();
-    const redirect = "redirect_on_success" in OPTIONS && OPTIONS.redirect_on_success;
-    if (redirect) {
-        window.location.replace(OPTIONS.redirect_on_success);
-    }
-}
-
-function customError(progressBarElement, progressBarMessageElement, excMessage) {
-    setProgress(100);
-    if (excMessage) {
-        updateMessages([40, excMessage]);
-    }
-    $("#result-alert").addClass("error");
-    $("#result-icon").attr("data-icon", "mdi:alert-octagon-outline");
-    $("#result-text").text(OPTIONS.error);
-    $("#result-box").show();
-}
-
-$(document).ready(function () {
-    $("#progress-bar").removeClass("indeterminate").addClass("determinate");
-
-    var progressUrl = Urls["taskStatus"](OPTIONS.task_id);
-    CeleryProgressBar.initProgressBar(progressUrl, {
-        onProgress: customProgress,
-        onSuccess: customSuccess,
-        onError: customError,
-    });
-});
diff --git a/aleksis/core/templates/core/pages/progress.html b/aleksis/core/templates/core/pages/progress.html
index dc12869c8130a26bf2d5d96f1d30e8a83444bd61..82dc6506058e0b0e7da785a0161c1f45585634b7 100644
--- a/aleksis/core/templates/core/pages/progress.html
+++ b/aleksis/core/templates/core/pages/progress.html
@@ -1,63 +1,10 @@
-{% extends "core/base.html" %}
+{% extends "core/vue_base.html" %}
 {% load i18n static %}
 
 {% block browser_title %}
-  {{ title }}
-{% endblock %}
-{% block page_title %}
-  {{ title }}
+  {% trans "Progress" %}
 {% endblock %}
 
 {% block content %}
-
-  <div class="container">
-    <div class="row">
-      <div class="progress center">
-        <div class="indeterminate" style="width: 0;" id="progress-bar"></div>
-      </div>
-      <h6 class="center">
-        {{ progress.title }}
-      </h6>
-    </div>
-    <div class="row">
-      <noscript>
-        <div class="alert warning">
-          <p>
-            <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>
-            {% blocktrans %}
-              Without activated JavaScript the progress status can't be updated.
-            {% endblocktrans %}
-          </p>
-        </div>
-      </noscript>
-
-      <div id="messages"></div>
-
-      <div id="result-box" style="display: none;">
-        <div class="alert" id="result-alert">
-          <div>
-            <i class="material-icons iconify left" id="result-icon" data-icon="mdi:check-circle-outline"></i>
-            <p id="result-text"></p>
-          </div>
-        </div>
-
-        {% url "index" as index_url %}
-        <a class="btn waves-effect waves-light" href="{{ back_url|default:index_url }}">
-          <i class="material-icons iconify left" data-icon="mdi:arrow-left"></i>
-          {% trans "Go back" %}
-        </a>
-        {% if additional_button %}
-          <a class="btn waves-effect waves-light" href="{{ additional_button.href }}" id="result-button" style="display: none;">
-            <i class="material-icons iconify left" data-icon="{{ additional_button.icon|default:"" }}"></i>
-            {{ additional_button.caption }}
-          </a>
-        {% endif %}
-      </div>
-    </div>
-  </div>
-
-  {{ progress|json_script:"progress_options" }}
-  <script src="{% static "js/helper.js" %}"></script>
-  <script src="{% static "celery_progress/celery_progress.js" %}"></script>
-  <script src="{% static "js/progress.js" %}"></script>
+  <router-view></router-view>
 {% endblock %}
diff --git a/aleksis/core/util/celery_progress.py b/aleksis/core/util/celery_progress.py
index 91b5b7168f215da509c71806f137483a422872d3..c0d271d1d2896d639052e0f54d922918d14b457f 100644
--- a/aleksis/core/util/celery_progress.py
+++ b/aleksis/core/util/celery_progress.py
@@ -5,7 +5,7 @@ from typing import Callable, Generator, Iterable, Optional, Sequence, Union
 from django.apps import apps
 from django.contrib import messages
 from django.http import HttpRequest
-from django.shortcuts import render
+from django.shortcuts import redirect
 
 from celery.result import AsyncResult
 from celery_progress.backend import PROGRESS_STATE, AbstractProgressRecorder
@@ -203,22 +203,15 @@ def render_progress_page(
     TaskUserAssignment = apps.get_model("core", "TaskUserAssignment")
     assignment = TaskUserAssignment.create_for_task_id(task_result.task_id, request.user)
 
-    # Prepare context for progress page
-    context["title"] = title
-    context["back_url"] = back_url
-    context["progress"] = {
-        "task_id": task_result.task_id,
-        "title": progress_title,
-        "success": success_message,
-        "error": error_message,
-        "redirect_on_success": redirect_on_success_url,
-    }
-
-    if button_url and button_title:
-        context["additional_button"] = {
-            "href": button_url,
-            "caption": button_title,
-            "icon": button_icon,
-        }
-
-    return render(request, "core/pages/progress.html", context)
+    assignment.title = title
+    assignment.back_url = back_url or ""
+    assignment.progress_title = progress_title or ""
+    assignment.error_message = error_message or ""
+    assignment.success_message = success_message or ""
+    assignment.redirect_on_success_url = redirect_on_success_url or ""
+    assignment.additional_button_title = button_title or ""
+    assignment.additional_button_url = button_url or ""
+    assignment.additional_button_icon = button_icon or ""
+    assignment.save()
+
+    return redirect("task_status", task_id=task_result.task_id)
diff --git a/aleksis/core/util/pdf.py b/aleksis/core/util/pdf.py
index 8721d8c3167ba8a01fd250a0967f56af495bdf78..24d360394b03298c2eb88c244f13e079220a33c0 100644
--- a/aleksis/core/util/pdf.py
+++ b/aleksis/core/util/pdf.py
@@ -147,7 +147,7 @@ def render_pdf(
         back_url=context.get("back_url", reverse("index")),
         button_title=_("Download PDF"),
         button_url=redirect_url,
-        button_icon="picture_as_pdf",
+        button_icon="mdi-file-pdf-box",
     )
 
 
diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py
index 5ba4271c08a4244ba5f359e9c46e01eff6c6d112..9996fa4655c75d525d7d6a305d3f1fc839323889 100644
--- a/aleksis/core/util/predicates.py
+++ b/aleksis/core/util/predicates.py
@@ -160,3 +160,9 @@ def has_activated_2fa(user: User) -> bool:
 def is_assigned_to_current_person(user: User, obj: Model) -> bool:
     """Check if the object is assigned to the current person."""
     return getattr(obj, "person", None) == user.person
+
+
+@predicate
+def is_own_celery_task(user: User, obj: Model) -> bool:
+    """Check if the celery task is owned by the current user."""
+    return obj.user == user
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index 6512782b6e970bde7a236bd480359883e27cdb7e..e55a58b9c88e62ba3c9716300e8416e86e6d6eec 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -40,7 +40,6 @@ from allauth.account.utils import has_verified_email, send_email_confirmation
 from allauth.account.views import PasswordChangeView, PasswordResetView, SignupView
 from allauth.socialaccount.adapter import get_adapter
 from allauth.socialaccount.models import SocialAccount
-from celery_progress.views import get_progress
 from django_celery_results.models import TaskResult
 from django_filters.views import FilterView
 from django_tables2 import RequestConfig, SingleTableMixin, SingleTableView
@@ -1338,17 +1337,15 @@ class RedirectToPDFFile(SingleObjectMixin, View):
         return redirect(file_object.file.url)
 
 
-class CeleryProgressView(View):
+class CeleryProgressView(PermissionRequiredMixin, DetailView):
     """Wrap celery-progress view to check permissions before."""
 
-    def get(self, request: HttpRequest, task_id: str, *args, **kwargs) -> HttpResponse:
-        if request.user.is_anonymous:
-            raise Http404(_("The requested task does not exist or is not accessible"))
-        if not TaskUserAssignment.objects.filter(
-            task_result__task_id=task_id, user=request.user
-        ).exists():
-            raise Http404(_("The requested task does not exist or is not accessible"))
-        return get_progress(request, task_id, *args, **kwargs)
+    template_name = "core/pages/progress.html"
+    permission_required = "core.view_progress_rule"
+
+    def get_object(self, queryset=None):
+        task_id = self.kwargs.get("task_id")
+        return TaskUserAssignment.objects.get(task_result__task_id=task_id)
 
 
 class CustomPasswordChangeView(LoginRequiredMixin, PermissionRequiredMixin, PasswordChangeView):