From 266e62cf5c5f7e252831a5370cfc7145b10d0bc1 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 21 Oct 2024 16:36:00 +0800 Subject: [PATCH 01/13] Add terminate selected tasks in action --- django_celery_results/admin.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/django_celery_results/admin.py b/django_celery_results/admin.py index de5172af..72e5e886 100644 --- a/django_celery_results/admin.py +++ b/django_celery_results/admin.py @@ -1,7 +1,8 @@ """Result Task Admin interface.""" +from celery import current_app as celery_app from django.conf import settings -from django.contrib import admin +from django.contrib import admin, messages from django.utils.translation import gettext_lazy as _ try: @@ -58,6 +59,7 @@ class TaskResultAdmin(admin.ModelAdmin): 'classes': ('extrapretty', 'wide') }), ) + actions = ['terminate_task'] def get_readonly_fields(self, request, obj=None): if ALLOW_EDITS: @@ -67,6 +69,25 @@ def get_readonly_fields(self, request, obj=None): field.name for field in self.opts.local_fields }) + def terminate_task(self, request, queryset): + """Terminate selected tasks.""" + task_ids = list(queryset.values_list('task_id', flat=True)) + try: + celery_app.control.terminate(task_ids) + self.message_user( + request, + f"{len(task_ids)} Task was terminated successfully.", + messages.SUCCESS, + ) + except Exception as e: + self.message_user( + request, + f"Error while terminating tasks: {e}", + messages.ERROR, + ) + + terminate_task.short_description = "Terminate selected tasks" + admin.site.register(TaskResult, TaskResultAdmin) From 0a183d4e0529634981d7e1c0af240dc2d9752696 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Sat, 12 Apr 2025 22:32:13 +0800 Subject: [PATCH 02/13] Add test for terminate selected tasks --- t/unit/test_admin.py | 84 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 t/unit/test_admin.py diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py new file mode 100644 index 00000000..8eac466c --- /dev/null +++ b/t/unit/test_admin.py @@ -0,0 +1,84 @@ +from unittest.mock import patch, MagicMock +import pytest +from celery import uuid +from django.test import TestCase +from django.test import RequestFactory +from django.contrib.messages import get_messages, constants +from django.contrib.messages.middleware import MessageMiddleware +from django.contrib.sessions.middleware import SessionMiddleware +from django_celery_results.admin import TaskResultAdmin +from django_celery_results.models import TaskResult + +@pytest.mark.usefixtures('depends_on_current_app') +class test_Admin(TestCase): + + def setUp(self): + self.task_admin = TaskResultAdmin(model=TaskResult, admin_site=None) + self.factory = RequestFactory() + + def _apply_middleware(self, request): + SessionMiddleware(lambda req: None).process_request(request) + MessageMiddleware(lambda req: None).process_request(request) + request.session.save() + + def create_task_result(self): + id = uuid() + taskmeta, created = TaskResult.objects.get_or_create(task_id=id) + return taskmeta + + @patch('celery.current_app.control.terminate') + def test_terminate_task_success(self, mock_terminate): + # Create mock request + request = self.factory.post('/') + request.user = MagicMock() + self._apply_middleware(request) + + # Create mock queryset + tr1 = self.create_task_result() + tr2 = self.create_task_result() + task_id_list = [tr1.task_id, tr2.task_id] + + mock_queryset = MagicMock() + mock_queryset.values_list.return_value = task_id_list + + # Call the terminate_task method + self.task_admin.terminate_task(request, mock_queryset) + + # Verify terminate was called with the correct task IDs + mock_terminate.assert_called_once_with(task_id_list) + + # Verify message_user was called with the success message + messages = list(get_messages(request)) + self.assertEqual(len(messages), 1) + self.assertEqual(str(messages[0]), "2 Task was terminated successfully.") + self.assertEqual(messages[0].level, constants.SUCCESS) + + @patch('celery.current_app.control.terminate') + def test_terminate_task_failure(self, mock_terminate): + # Create mock request + request = self.factory.post('/') + request.user = MagicMock() + self._apply_middleware(request) + + # Create mock queryset + tr1 = self.create_task_result() + tr2 = self.create_task_result() + task_id_list = [tr1.task_id, tr2.task_id] + + mock_queryset = MagicMock() + mock_queryset.values_list.return_value = task_id_list + + # Simulate an exception in terminate + mock_terminate.side_effect = Exception("Termination failed") + + # Call the terminate_task method + self.task_admin.terminate_task(request, mock_queryset) + + # Verify terminate was called with the correct task IDs + mock_terminate.assert_called_once_with(task_id_list) + + # Verify message_user was called with the error message + messages = list(get_messages(request)) + self.assertEqual(len(messages), 1) + self.assertIn("Error while terminating tasks: Termination failed", str(messages[0])) + self.assertEqual(messages[0].level, constants.ERROR) From 7a19716f7627cb55ec3acd9f8ece1910013234ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 14:32:26 +0000 Subject: [PATCH 03/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- t/unit/test_admin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index 8eac466c..96bb9b90 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -1,14 +1,16 @@ -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch + import pytest from celery import uuid -from django.test import TestCase -from django.test import RequestFactory -from django.contrib.messages import get_messages, constants +from django.contrib.messages import constants, get_messages from django.contrib.messages.middleware import MessageMiddleware from django.contrib.sessions.middleware import SessionMiddleware +from django.test import RequestFactory, TestCase + from django_celery_results.admin import TaskResultAdmin from django_celery_results.models import TaskResult + @pytest.mark.usefixtures('depends_on_current_app') class test_Admin(TestCase): From 8555e3b267e0df4dbf14249e08321364ac55e82f Mon Sep 17 00:00:00 2001 From: caoqianming Date: Sat, 12 Apr 2025 22:48:59 +0800 Subject: [PATCH 04/13] fix flake8 check --- t/unit/test_admin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index 96bb9b90..037ff331 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -33,7 +33,7 @@ def test_terminate_task_success(self, mock_terminate): # Create mock request request = self.factory.post('/') request.user = MagicMock() - self._apply_middleware(request) + self._apply_middleware(request) # Create mock queryset tr1 = self.create_task_result() @@ -52,7 +52,8 @@ def test_terminate_task_success(self, mock_terminate): # Verify message_user was called with the success message messages = list(get_messages(request)) self.assertEqual(len(messages), 1) - self.assertEqual(str(messages[0]), "2 Task was terminated successfully.") + self.assertEqual(str(messages[0]), + "2 Task was terminated successfully.") self.assertEqual(messages[0].level, constants.SUCCESS) @patch('celery.current_app.control.terminate') @@ -60,7 +61,7 @@ def test_terminate_task_failure(self, mock_terminate): # Create mock request request = self.factory.post('/') request.user = MagicMock() - self._apply_middleware(request) + self._apply_middleware(request) # Create mock queryset tr1 = self.create_task_result() @@ -82,5 +83,6 @@ def test_terminate_task_failure(self, mock_terminate): # Verify message_user was called with the error message messages = list(get_messages(request)) self.assertEqual(len(messages), 1) - self.assertIn("Error while terminating tasks: Termination failed", str(messages[0])) + self.assertIn(str(messages[0]), + "Error while terminating tasks: Termination failed") self.assertEqual(messages[0].level, constants.ERROR) From f645f4c7190e9ce7465713df8f1bddef4f223a61 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Sat, 12 Apr 2025 23:01:44 +0800 Subject: [PATCH 05/13] fix flake8 check2 --- t/unit/test_admin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index 037ff331..f55160b4 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -52,7 +52,8 @@ def test_terminate_task_success(self, mock_terminate): # Verify message_user was called with the success message messages = list(get_messages(request)) self.assertEqual(len(messages), 1) - self.assertEqual(str(messages[0]), + self.assertEqual( + str(messages[0]), "2 Task was terminated successfully.") self.assertEqual(messages[0].level, constants.SUCCESS) @@ -83,6 +84,7 @@ def test_terminate_task_failure(self, mock_terminate): # Verify message_user was called with the error message messages = list(get_messages(request)) self.assertEqual(len(messages), 1) - self.assertIn(str(messages[0]), + self.assertIn( + str(messages[0]), "Error while terminating tasks: Termination failed") self.assertEqual(messages[0].level, constants.ERROR) From c595d0c714f4b29da2b81d3a17095a494e86d863 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:24:34 +0000 Subject: [PATCH 06/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- t/unit/test_admin.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index 9b206eda..a891238a 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -2,15 +2,13 @@ import pytest from celery import uuid +from django.apps import apps +from django.contrib import admin +from django.contrib.auth import get_user_model from django.contrib.messages import constants, get_messages from django.contrib.messages.middleware import MessageMiddleware from django.contrib.sessions.middleware import SessionMiddleware from django.test import RequestFactory, TestCase - -from django.apps import apps -from django.contrib import admin -from django.contrib.auth import get_user_model -from django.test import TestCase from django.urls import ( clear_url_caches, get_resolver, @@ -18,7 +16,6 @@ reverse, ) - from django_celery_results.admin import TaskResultAdmin from django_celery_results.models import TaskResult From b2c6b754d790803f72ed336e1e3c75d22f63930e Mon Sep 17 00:00:00 2001 From: caoqianming Date: Sat, 7 Mar 2026 20:39:50 +0800 Subject: [PATCH 07/13] Fix flake8 errors in test_admin.py: add missing blank line after function definition and remove trailing blank line at end of file --- t/unit/test_admin.py | 334 +++++++++++++++++++++---------------------- 1 file changed, 167 insertions(+), 167 deletions(-) diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index a891238a..e05a530d 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -1,167 +1,167 @@ -from unittest.mock import MagicMock, patch - -import pytest -from celery import uuid -from django.apps import apps -from django.contrib import admin -from django.contrib.auth import get_user_model -from django.contrib.messages import constants, get_messages -from django.contrib.messages.middleware import MessageMiddleware -from django.contrib.sessions.middleware import SessionMiddleware -from django.test import RequestFactory, TestCase -from django.urls import ( - clear_url_caches, - get_resolver, - path, - reverse, -) - -from django_celery_results.admin import TaskResultAdmin -from django_celery_results.models import TaskResult - - -@pytest.mark.usefixtures('depends_on_current_app') -class test_Admin(TestCase): - - def setUp(self): - self.task_admin = TaskResultAdmin(model=TaskResult, admin_site=None) - self.factory = RequestFactory() - - def _apply_middleware(self, request): - SessionMiddleware(lambda req: None).process_request(request) - MessageMiddleware(lambda req: None).process_request(request) - request.session.save() - - def create_task_result(self): - id = uuid() - taskmeta, created = TaskResult.objects.get_or_create(task_id=id) - return taskmeta - - @patch('celery.current_app.control.terminate') - def test_terminate_task_success(self, mock_terminate): - # Create mock request - request = self.factory.post('/') - request.user = MagicMock() - self._apply_middleware(request) - - # Create mock queryset - tr1 = self.create_task_result() - tr2 = self.create_task_result() - task_id_list = [tr1.task_id, tr2.task_id] - - mock_queryset = MagicMock() - mock_queryset.values_list.return_value = task_id_list - - # Call the terminate_task method - self.task_admin.terminate_task(request, mock_queryset) - - # Verify terminate was called with the correct task IDs - mock_terminate.assert_called_once_with(task_id_list) - - # Verify message_user was called with the success message - messages = list(get_messages(request)) - self.assertEqual(len(messages), 1) - self.assertEqual( - str(messages[0]), - "2 Task was terminated successfully.") - self.assertEqual(messages[0].level, constants.SUCCESS) - - @patch('celery.current_app.control.terminate') - def test_terminate_task_failure(self, mock_terminate): - # Create mock request - request = self.factory.post('/') - request.user = MagicMock() - self._apply_middleware(request) - - # Create mock queryset - tr1 = self.create_task_result() - tr2 = self.create_task_result() - task_id_list = [tr1.task_id, tr2.task_id] - - mock_queryset = MagicMock() - mock_queryset.values_list.return_value = task_id_list - - # Simulate an exception in terminate - mock_terminate.side_effect = Exception("Termination failed") - - # Call the terminate_task method - self.task_admin.terminate_task(request, mock_queryset) - - # Verify terminate was called with the correct task IDs - mock_terminate.assert_called_once_with(task_id_list) - - # Verify message_user was called with the error message - messages = list(get_messages(request)) - self.assertEqual(len(messages), 1) - self.assertIn( - str(messages[0]), - "Error while terminating tasks: Termination failed") - self.assertEqual(messages[0].level, constants.ERROR) - -User = get_user_model() - - -class TaskResultAdminTests(TestCase): - app_name = "django_celery_results" - model = TaskResult - - def setUp(self): - self.admin_user = User.objects.create_superuser( - username="admin", email="admin@test.com", password="password" - ) - self.client.login(username="admin", password="password") - self.task_result = TaskResult.objects.create( - task_id=uuid(), task_name="test_task" - ) - - def test_add_view(self): - url = reverse( - f"admin:{self.app_name}_{self.model._meta.model_name}_add" - ) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_change_view(self): - url = reverse( - f"admin:{self.app_name}_{self.model._meta.model_name}_change", - args=[self.task_result.id], - ) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - -class TaskResultProxyAdminTests(TaskResultAdminTests): - @classmethod - def setUpClass(cls): - super().setUpClass() - - class TaskResultProxy(TaskResult): - class Meta: - proxy = True - app_label = "django_celery_results" - - cls.model = TaskResultProxy - admin.site.register(TaskResultProxy, TaskResultAdmin) - - # The temporary registration of admin requires refreshing the URL cache - # Otherwise, it cannot be resolved - default_resolver = get_resolver() - cls.ori_url_patterns_0 = default_resolver.url_patterns[0] - get_resolver().url_patterns[0] = path("admin/", admin.site.urls) - clear_url_caches() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - # Unregister the proxy model - admin.site.unregister(cls.model) - app_config = apps.get_app_config(cls.app_name) - model_name = cls.model._meta.model_name - if model_name in app_config.models: - del app_config.models[model_name] - - # Restore the original URL patterns - get_resolver().url_patterns[0] = cls.ori_url_patterns_0 - clear_url_caches() - +from unittest.mock import MagicMock, patch + +import pytest +from celery import uuid +from django.apps import apps +from django.contrib import admin +from django.contrib.auth import get_user_model +from django.contrib.messages import constants, get_messages +from django.contrib.messages.middleware import MessageMiddleware +from django.contrib.sessions.middleware import SessionMiddleware +from django.test import RequestFactory, TestCase +from django.urls import ( + clear_url_caches, + get_resolver, + path, + reverse, +) + +from django_celery_results.admin import TaskResultAdmin +from django_celery_results.models import TaskResult + + +@pytest.mark.usefixtures('depends_on_current_app') +class test_Admin(TestCase): + + def setUp(self): + self.task_admin = TaskResultAdmin(model=TaskResult, admin_site=None) + self.factory = RequestFactory() + + def _apply_middleware(self, request): + SessionMiddleware(lambda req: None).process_request(request) + MessageMiddleware(lambda req: None).process_request(request) + request.session.save() + + def create_task_result(self): + id = uuid() + taskmeta, created = TaskResult.objects.get_or_create(task_id=id) + return taskmeta + + @patch('celery.current_app.control.terminate') + def test_terminate_task_success(self, mock_terminate): + # Create mock request + request = self.factory.post('/') + request.user = MagicMock() + self._apply_middleware(request) + + # Create mock queryset + tr1 = self.create_task_result() + tr2 = self.create_task_result() + task_id_list = [tr1.task_id, tr2.task_id] + + mock_queryset = MagicMock() + mock_queryset.values_list.return_value = task_id_list + + # Call the terminate_task method + self.task_admin.terminate_task(request, mock_queryset) + + # Verify terminate was called with the correct task IDs + mock_terminate.assert_called_once_with(task_id_list) + + # Verify message_user was called with the success message + messages = list(get_messages(request)) + self.assertEqual(len(messages), 1) + self.assertEqual( + str(messages[0]), + "2 Task was terminated successfully.") + self.assertEqual(messages[0].level, constants.SUCCESS) + + @patch('celery.current_app.control.terminate') + def test_terminate_task_failure(self, mock_terminate): + # Create mock request + request = self.factory.post('/') + request.user = MagicMock() + self._apply_middleware(request) + + # Create mock queryset + tr1 = self.create_task_result() + tr2 = self.create_task_result() + task_id_list = [tr1.task_id, tr2.task_id] + + mock_queryset = MagicMock() + mock_queryset.values_list.return_value = task_id_list + + # Simulate an exception in terminate + mock_terminate.side_effect = Exception("Termination failed") + + # Call the terminate_task method + self.task_admin.terminate_task(request, mock_queryset) + + # Verify terminate was called with the correct task IDs + mock_terminate.assert_called_once_with(task_id_list) + + # Verify message_user was called with the error message + messages = list(get_messages(request)) + self.assertEqual(len(messages), 1) + self.assertIn( + str(messages[0]), + "Error while terminating tasks: Termination failed") + self.assertEqual(messages[0].level, constants.ERROR) + + +User = get_user_model() + + +class TaskResultAdminTests(TestCase): + app_name = "django_celery_results" + model = TaskResult + + def setUp(self): + self.admin_user = User.objects.create_superuser( + username="admin", email="admin@test.com", password="password" + ) + self.client.login(username="admin", password="password") + self.task_result = TaskResult.objects.create( + task_id=uuid(), task_name="test_task" + ) + + def test_add_view(self): + url = reverse( + f"admin:{self.app_name}_{self.model._meta.model_name}_add" + ) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_change_view(self): + url = reverse( + f"admin:{self.app_name}_{self.model._meta.model_name}_change", + args=[self.task_result.id], + ) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + +class TaskResultProxyAdminTests(TaskResultAdminTests): + @classmethod + def setUpClass(cls): + super().setUpClass() + + class TaskResultProxy(TaskResult): + class Meta: + proxy = True + app_label = "django_celery_results" + + cls.model = TaskResultProxy + admin.site.register(TaskResultProxy, TaskResultAdmin) + + # The temporary registration of admin requires refreshing the URL cache + # Otherwise, it cannot be resolved + default_resolver = get_resolver() + cls.ori_url_patterns_0 = default_resolver.url_patterns[0] + get_resolver().url_patterns[0] = path("admin/", admin.site.urls) + clear_url_caches() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + + # Unregister the proxy model + admin.site.unregister(cls.model) + app_config = apps.get_app_config(cls.app_name) + model_name = cls.model._meta.model_name + if model_name in app_config.models: + del app_config.models[model_name] + + # Restore the original URL patterns + get_resolver().url_patterns[0] = cls.ori_url_patterns_0 + clear_url_caches() From 271ff60fa3ea14e7f6cc304fe14ebc2d87eb8e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asif=20Saif=20Uddin=20=7B=22Auvi=22=3A=22=E0=A6=85?= =?UTF-8?q?=E0=A6=AD=E0=A6=BF=22=7D?= Date: Sat, 7 Mar 2026 19:15:05 +0600 Subject: [PATCH 08/13] Update django_celery_results/admin.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- django_celery_results/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django_celery_results/admin.py b/django_celery_results/admin.py index 267ed8ff..4b543388 100644 --- a/django_celery_results/admin.py +++ b/django_celery_results/admin.py @@ -73,7 +73,8 @@ def terminate_task(self, request, queryset): """Terminate selected tasks.""" task_ids = list(queryset.values_list('task_id', flat=True)) try: - celery_app.control.terminate(task_ids) + for task_id in task_ids: + celery_app.control.terminate(task_id) self.message_user( request, f"{len(task_ids)} Task was terminated successfully.", From 0c6f04dc280b73251c875850239d5c6054cb59e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asif=20Saif=20Uddin=20=7B=22Auvi=22=3A=22=E0=A6=85?= =?UTF-8?q?=E0=A6=AD=E0=A6=BF=22=7D?= Date: Sat, 7 Mar 2026 19:15:51 +0600 Subject: [PATCH 09/13] Update t/unit/test_admin.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- t/unit/test_admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index e05a530d..23cc9d99 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -94,8 +94,8 @@ def test_terminate_task_failure(self, mock_terminate): messages = list(get_messages(request)) self.assertEqual(len(messages), 1) self.assertIn( - str(messages[0]), - "Error while terminating tasks: Termination failed") + "Error while terminating tasks: Termination failed", + str(messages[0])) self.assertEqual(messages[0].level, constants.ERROR) From b157f7c526ffa4605011bd29a55e50bfae4f6f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asif=20Saif=20Uddin=20=7B=22Auvi=22=3A=22=E0=A6=85?= =?UTF-8?q?=E0=A6=AD=E0=A6=BF=22=7D?= Date: Sat, 7 Mar 2026 19:16:14 +0600 Subject: [PATCH 10/13] Update t/unit/test_admin.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- t/unit/test_admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index 23cc9d99..07848341 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -33,8 +33,8 @@ def _apply_middleware(self, request): request.session.save() def create_task_result(self): - id = uuid() - taskmeta, created = TaskResult.objects.get_or_create(task_id=id) + task_id = uuid() + taskmeta, _ = TaskResult.objects.get_or_create(task_id=task_id) return taskmeta @patch('celery.current_app.control.terminate') From fe24849c50da72d9dfa1df7f642e1d4e1f85f516 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Sat, 7 Mar 2026 22:19:02 +0800 Subject: [PATCH 11/13] Fix test failures in test_admin.py and adjust admin message --- django_celery_results/admin.py | 222 +++++++++++++++++---------------- t/unit/test_admin.py | 22 ++-- 2 files changed, 129 insertions(+), 115 deletions(-) diff --git a/django_celery_results/admin.py b/django_celery_results/admin.py index 267ed8ff..b5231e82 100644 --- a/django_celery_results/admin.py +++ b/django_celery_results/admin.py @@ -1,106 +1,116 @@ -"""Result Task Admin interface.""" - -from celery import current_app as celery_app -from django.conf import settings -from django.contrib import admin, messages -from django.utils.translation import gettext_lazy as _ - -try: - ALLOW_EDITS = settings.DJANGO_CELERY_RESULTS['ALLOW_EDITS'] -except (AttributeError, KeyError): - ALLOW_EDITS = False - pass - -from .models import GroupResult, TaskResult - - -class TaskResultAdmin(admin.ModelAdmin): - """Admin-interface for results of tasks.""" - - model = TaskResult - date_hierarchy = 'date_done' - list_display = ('task_id', 'periodic_task_name', 'task_name', 'date_done', - 'status', 'worker') - list_filter = ('status', 'date_done', 'periodic_task_name', 'task_name', - 'worker') - readonly_fields = ('date_created', 'date_started', 'date_done', - 'result', 'meta') - search_fields = ('task_name', 'task_id', 'status', 'task_args', - 'task_kwargs') - fieldsets = ( - (None, { - 'fields': ( - 'task_id', - 'task_name', - 'periodic_task_name', - 'status', - 'worker', - 'content_type', - 'content_encoding', - ), - 'classes': ('extrapretty', 'wide') - }), - (_('Parameters'), { - 'fields': ( - 'task_args', - 'task_kwargs', - ), - 'classes': ('extrapretty', 'wide') - }), - (_('Result'), { - 'fields': ( - 'result', - 'date_created', - 'date_started', - 'date_done', - 'traceback', - 'meta', - ), - 'classes': ('extrapretty', 'wide') - }), - ) - actions = ['terminate_task'] - - def get_readonly_fields(self, request, obj=None): - if ALLOW_EDITS: - return self.readonly_fields - else: - return list({ - field.name for field in self.model._meta.fields - }) - - def terminate_task(self, request, queryset): - """Terminate selected tasks.""" - task_ids = list(queryset.values_list('task_id', flat=True)) - try: - celery_app.control.terminate(task_ids) - self.message_user( - request, - f"{len(task_ids)} Task was terminated successfully.", - messages.SUCCESS, - ) - except Exception as e: - self.message_user( - request, - f"Error while terminating tasks: {e}", - messages.ERROR, - ) - - terminate_task.short_description = "Terminate selected tasks" - - -admin.site.register(TaskResult, TaskResultAdmin) - - -class GroupResultAdmin(admin.ModelAdmin): - """Admin-interface for results of grouped tasks.""" - - model = GroupResult - date_hierarchy = 'date_done' - list_display = ('group_id', 'date_done') - list_filter = ('date_done',) - readonly_fields = ('date_created', 'date_done', 'result') - search_fields = ('group_id',) - - -admin.site.register(GroupResult, GroupResultAdmin) +"""Result Task Admin interface.""" + +import logging + +from celery import current_app as celery_app +from django.conf import settings +from django.contrib import admin, messages +from django.utils.translation import gettext_lazy as _ + +logger = logging.getLogger(__name__) + +try: + ALLOW_EDITS = settings.DJANGO_CELERY_RESULTS['ALLOW_EDITS'] +except (AttributeError, KeyError): + ALLOW_EDITS = False + pass + +from .models import GroupResult, TaskResult + + +class TaskResultAdmin(admin.ModelAdmin): + """Admin-interface for results of tasks.""" + + model = TaskResult + date_hierarchy = 'date_done' + list_display = ('task_id', 'periodic_task_name', 'task_name', 'date_done', + 'status', 'worker') + list_filter = ('status', 'date_done', 'periodic_task_name', 'task_name', + 'worker') + readonly_fields = ('date_created', 'date_started', 'date_done', + 'result', 'meta') + search_fields = ('task_name', 'task_id', 'status', 'task_args', + 'task_kwargs') + fieldsets = ( + (None, { + 'fields': ( + 'task_id', + 'task_name', + 'periodic_task_name', + 'status', + 'worker', + 'content_type', + 'content_encoding', + ), + 'classes': ('extrapretty', 'wide') + }), + (_('Parameters'), { + 'fields': ( + 'task_args', + 'task_kwargs', + ), + 'classes': ('extrapretty', 'wide') + }), + (_('Result'), { + 'fields': ( + 'result', + 'date_created', + 'date_started', + 'date_done', + 'traceback', + 'meta', + ), + 'classes': ('extrapretty', 'wide') + }), + ) + actions = ['terminate_task'] + + def get_readonly_fields(self, request, obj=None): + if ALLOW_EDITS: + return self.readonly_fields + else: + return list({ + field.name for field in self.model._meta.fields + }) + + def terminate_task(self, request, queryset): + """Terminate selected tasks.""" + task_ids = list(queryset.values_list('task_id', flat=True)) + try: + celery_app.control.terminate(task_ids) + self.message_user( + request, + f"{len(task_ids)} task(s) was terminated successfully.", + messages.SUCCESS, + ) + except Exception as e: + logger.error( + "Error while terminating tasks: %s", + e, + exc_info=True, + extra={'task_ids': task_ids} + ) + self.message_user( + request, + f"Error while terminating tasks: {e}", + messages.ERROR, + ) + + terminate_task.short_description = _("Terminate selected tasks") + + +admin.site.register(TaskResult, TaskResultAdmin) + + +class GroupResultAdmin(admin.ModelAdmin): + """Admin-interface for results of grouped tasks.""" + + model = GroupResult + date_hierarchy = 'date_done' + list_display = ('group_id', 'date_done') + list_filter = ('date_done',) + readonly_fields = ('date_created', 'date_done', 'result') + search_fields = ('group_id',) + + +admin.site.register(GroupResult, GroupResultAdmin) diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index e05a530d..7061c95d 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -49,21 +49,23 @@ def test_terminate_task_success(self, mock_terminate): tr2 = self.create_task_result() task_id_list = [tr1.task_id, tr2.task_id] - mock_queryset = MagicMock() - mock_queryset.values_list.return_value = task_id_list + # Use queryset + queryset = TaskResult.objects.filter(task_id__in=task_id_list) # Call the terminate_task method - self.task_admin.terminate_task(request, mock_queryset) + self.task_admin.terminate_task(request, queryset) # Verify terminate was called with the correct task IDs - mock_terminate.assert_called_once_with(task_id_list) + mock_terminate.assert_called_once() + called_args = mock_terminate.call_args[0][0] + self.assertEqual(sorted(called_args), sorted(task_id_list)) # Verify message_user was called with the success message messages = list(get_messages(request)) self.assertEqual(len(messages), 1) self.assertEqual( str(messages[0]), - "2 Task was terminated successfully.") + "2 task(s) was terminated successfully.") self.assertEqual(messages[0].level, constants.SUCCESS) @patch('celery.current_app.control.terminate') @@ -78,17 +80,19 @@ def test_terminate_task_failure(self, mock_terminate): tr2 = self.create_task_result() task_id_list = [tr1.task_id, tr2.task_id] - mock_queryset = MagicMock() - mock_queryset.values_list.return_value = task_id_list + # Use queryset + queryset = TaskResult.objects.filter(task_id__in=task_id_list) # Simulate an exception in terminate mock_terminate.side_effect = Exception("Termination failed") # Call the terminate_task method - self.task_admin.terminate_task(request, mock_queryset) + self.task_admin.terminate_task(request, queryset) # Verify terminate was called with the correct task IDs - mock_terminate.assert_called_once_with(task_id_list) + mock_terminate.assert_called_once() + called_args = mock_terminate.call_args[0][0] + self.assertEqual(sorted(called_args), sorted(task_id_list)) # Verify message_user was called with the error message messages = list(get_messages(request)) From 47f869822ddd20bf1cd3c884361c2605c1f5e66e Mon Sep 17 00:00:00 2001 From: caoqianming Date: Sat, 7 Mar 2026 22:29:07 +0800 Subject: [PATCH 12/13] Fix E402 import order and update patch target in tests --- django_celery_results/admin.py | 3 +-- t/unit/test_admin.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/django_celery_results/admin.py b/django_celery_results/admin.py index b5231e82..39311c2c 100644 --- a/django_celery_results/admin.py +++ b/django_celery_results/admin.py @@ -6,6 +6,7 @@ from django.conf import settings from django.contrib import admin, messages from django.utils.translation import gettext_lazy as _ +from .models import GroupResult, TaskResult logger = logging.getLogger(__name__) @@ -15,8 +16,6 @@ ALLOW_EDITS = False pass -from .models import GroupResult, TaskResult - class TaskResultAdmin(admin.ModelAdmin): """Admin-interface for results of tasks.""" diff --git a/t/unit/test_admin.py b/t/unit/test_admin.py index 002093cc..2d4dc956 100644 --- a/t/unit/test_admin.py +++ b/t/unit/test_admin.py @@ -37,7 +37,7 @@ def create_task_result(self): taskmeta, _ = TaskResult.objects.get_or_create(task_id=task_id) return taskmeta - @patch('celery.current_app.control.terminate') + @patch('django_celery_results.admin.celery_app.control.terminate') def test_terminate_task_success(self, mock_terminate): # Create mock request request = self.factory.post('/') @@ -68,7 +68,7 @@ def test_terminate_task_success(self, mock_terminate): "2 task(s) was terminated successfully.") self.assertEqual(messages[0].level, constants.SUCCESS) - @patch('celery.current_app.control.terminate') + @patch('django_celery_results.admin.celery_app.control.terminate') def test_terminate_task_failure(self, mock_terminate): # Create mock request request = self.factory.post('/') From eb949bc6f258eeef2aa932090a4df54afb22f0e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 14:32:20 +0000 Subject: [PATCH 13/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- django_celery_results/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django_celery_results/admin.py b/django_celery_results/admin.py index 39311c2c..d1da2727 100644 --- a/django_celery_results/admin.py +++ b/django_celery_results/admin.py @@ -6,6 +6,7 @@ from django.conf import settings from django.contrib import admin, messages from django.utils.translation import gettext_lazy as _ + from .models import GroupResult, TaskResult logger = logging.getLogger(__name__)