Django Admin调用Playwright时出现SynchronousOnlyOperation错误
解决Django Admin自定义操作中Playwright触发的SynchronousOnlyOperation错误
问题场景
在Django Admin中定义了自定义操作,意图通过Playwright执行自动化任务,但触发了SynchronousOnlyOperation错误。
错误信息
SynchronousOnlyOperation at /admin/app/modelA/
You cannot call this from an async context - use a thread or sync_to_async.
现有代码实现
Admin操作代码(admin.py)
@admin.action(description="Do task") def run_task(modeladmin, request, queryset): for obj in queryset: task = Task(obj) task.run()
Task类代码
class Task: def __init__(self, task: Task) -> None: self.task = task def run(self): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.go_to("www.google.com") # 执行其他操作 browser.close()
无效的尝试
- 在admin.py中调用时用
sync_to_async,但仅包装函数未执行:
@admin.action(description="Do task") def run_test(modeladmin, request, queryset): for obj in queryset: task = Task(obj) sync_to_async(task.run, thread_sensitive=True) # 未实际调用包装后的函数
- 给
run方法添加@sync_to_async装饰器,但未处理内部ORM操作:
... @sync_to_async def run(self, ...): ... ...
完整报错回溯
Environment: Request Method: POST Request URL: http://0.0.0.0:8000/admin/app/task/ Django Version: 4.1.3 Python Version: 3.10.5 Installed Applications: ['django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app.apps.AppConfig', 'nested_admin'] Installed Middleware: ['django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware'] Traceback (most recent call last): File ".../venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) File ".../venv/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File ".../venv/lib/python3.10/site-packages/django/contrib/admin/options.py", line 686, in wrapper return self.admin_site.admin_view(view)(*args, **kwargs) File ".../venv/lib/python3.10/site-packages/django/utils/decorators.py", line 133, in _wrapped_view response = view_func(request, *args, **kwargs) File ".../venv/lib/python3.10/site-packages/django/views/decorators/cache.py", line 62, in _wrapped_view_func response = view_func(request, *args, **kwargs) File ".../venv/lib/python3.10/site-packages/django/contrib/admin/sites.py", line 242, in inner return view(request, *args, **kwargs) File ".../venv/lib/python3.10/site-packages/django/utils/decorators.py", line 46, in _wrapper return bound_method(*args, **kwargs) File ".../venv/lib/python3.10/site-packages/django/utils/decorators.py", line 133, in _wrapped_view response = view_func(request, *args, **kwargs) File ".../venv/lib/python3.10/site-packages/django/contrib/admin/options.py", line 1964, in changelist_view response = self.response_action( File ".../venv/lib/python3.10/site-packages/django/contrib/admin/options.py", line 1593, in response_action response = func(self, request, queryset) File ".../app/admin.py", line 13, in run_test task.run() File ".../app/tasks.py", line 14, in run for step in self.steps: File ".../venv/lib/python3.10/site-packages/django/db/models/query.py", line 394, in __iter__ self._fetch_all() File ".../venv/lib/python3.10/site-packages/django/db/models/query.py", line 1866, in _fetch_all self._result_cache = list(self._iterable_class(self)) File ".../venv/lib/python3.10/site-packages/django/db/models/query.py", line 87, in __iter__ results = compiler.execute_sql( File ".../venv/lib/python3.10/site-packages/django/db/models/sql/compiler.py", line 1396, in execute_sql cursor = self.connection.cursor() File ".../venv/lib/python3.10/site-packages/django/utils/asyncio.py", line 24, in inner raise SynchronousOnlyOperation(message) Exception Type: SynchronousOnlyOperation at /admin/app/task/ Exception Value: You cannot call this from an async context - use a thread or sync_to_async.
解决方案
从报错回溯可知,触发错误的核心是run方法中遍历self.steps(Django QuerySet)时,该操作处于异步上下文,违反了Django ORM不能在异步环境直接执行的规则,而非Playwright启动本身。以下是两种有效解决方式:
方法一:用线程池执行整个任务
Django Admin操作是同步视图,将task.run()放到单独线程执行,既避免阻塞请求,又避开异步上下文问题:
import threading from django.contrib import admin @admin.action(description="Do task") def run_task(modeladmin, request, queryset): for obj in queryset: task = Task(obj) # 启动独立线程执行任务 threading.Thread(target=task.run).start() # 立即返回响应,无需等待任务完成 modeladmin.message_user(request, "任务已启动")
方法二:正确结合异步Playwright与ORM处理
若需用异步方式,需将ORM操作提前转为同步上下文执行,同时使用Playwright异步版本:
# Task类修改为异步版本 from playwright.async_api import async_playwright class Task: def __init__(self, task: Task) -> None: self.task = task # 提前在同步上下文将QuerySet转为列表,避免异步中访问ORM self.steps = list(self.task.steps.all()) async def run(self): async with async_playwright() as p: browser = await p.chromium.launch(headless=True) page = await browser.new_page() await page.goto("https://www.google.com") # 示例:获取页面标题 title = await page.title() print(f"页面标题:{title}") await browser.close()
# Admin操作中调用异步方法 from asgiref.sync import async_to_sync from django.contrib import admin @admin.action(description="Do task") def run_task(modeladmin, request, queryset): for obj in queryset: task = Task(obj) # 用async_to_sync将异步方法转为同步调用 async_to_sync(task.run)() modeladmin.message_user(request, "任务已完成")
关键原因总结
- 之前尝试无效的原因:第一种仅包装函数未实际调用;第二种将整个
run转为异步,但未处理内部ORM查询,导致依然触发错误。 - 核心问题:Django ORM默认禁止在异步上下文直接执行,需提前在同步环境处理QuerySet,或通过线程/同步包装器隔离异步操作。
内容的提问来源于stack exchange,提问作者Rexhep Krasniqi
相关产品推荐
相关产品推荐

