You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()

无效的尝试

  1. 在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)  # 未实际调用包装后的函数
  1. 给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 05:25:44