首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Django自定义FileField保存方法中的卸载扩展计算

Django自定义FileField保存方法中的卸载扩展计算
EN

Stack Overflow用户
提问于 2022-11-07 22:48:29
回答 1查看 25关注 0票数 1

我正在构建一个基于Django (4.1.1)和Vue的画廊网络应用程序。我也想上传和显示视频(不仅仅是图片)。为了支持不适用于视频html标记的格式,我将通过pyffmpeg将这些格式转换为mp4

为此,我为基于FileField的模型创建了一个自定义字段。在它的save方法中,我获取文件内容,转换它并保存结果。这是由序列化程序通过相应的ViewSet调用的。这是可行的,但是视频转换的时间太长了,所以我的Vue应用程序(用axios执行)的way请求正在进入超时状态。

显然,我需要以某种方式卸载转换,直接返回相应的响应,并在转换完成后立即将数据保存到数据库中。

这有可能吗?或者,除了ViewSet之外,我是否需要编写一个自定义视图来进行计算?你能给我一个关于如何卸下计算的提示吗?我对像asyncio这样的东西只有基本的知识。

TL;DR:如何在使用FileField将文件数据保存到模型并在计算结束前返回响应之前,对文件数据进行大量的计算?

如果有必要,我可以提供我的当前代码。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-11-09 17:15:19

我现在已经解决了我的问题,尽管我仍然对其他/更好的解决方案感兴趣。我的解决方案是有效的,但可能不是最好的,我觉得在某些地方有点麻烦。

TL;DR:将django-q安装为具有redis数据库后端的任务队列管理器,并将其连接到django,然后调用从我的视图中转换视频文件的函数。

代码语言:javascript
复制
taskid = async_task("apps.myapp.services.transcode_video", data)

这应该是一个健壮的系统,可以并行和不阻塞请求地处理这些代码转换任务。

我找到了本教程介绍Django-Q。Django-Q管理并执行django的任务。它与Django并行运行,并通过它的代理(在本例中是一个redis数据库)连接到它。

首先,我通过pip安装了django-q和redis客户端模块。

代码语言:javascript
复制
pip install django-q redis

然后,我建立了一个Redis数据库(这里运行在我的机器上的一个带有官方redis图像的码头容器中)。如何做到这一点在很大程度上取决于您的平台。

然后通过将配置添加到settings.py中来配置Django-Q (注意,我禁用了超时,因为代码转换任务可能会花费相当长的时间)。可在今后改变这一情况):

代码语言:javascript
复制
Q_CLUSTER = {
    'name': 'django_q_django',
    'workers': 8,
    'recycle': 500,
    'timeout': None,
    'compress': True,
    'save_limit': 250,
    'queue_limit': 500,
    'cpu_affinity': 1,
    'label': 'Django Q',
    'redis': {
        'host': 'redishostname',
        'port': 6379,
        'password': 'mysecureredisdbpassword',
        'db': 0, }
}

然后通过将其添加到settings.py中安装的应用程序中激活Django-Q:

代码语言:javascript
复制
INSTALLED_APPS = [
    ...
    'django_q',
]

然后通过以下方式迁移Django Q的模型定义:

代码语言:javascript
复制
python manage.py migrate

然后启动Django Q via ( Redis数据库此时应该运行):

代码语言:javascript
复制
python manage.py qcluster

这在一个独立的终端中运行,而不是典型的终端。

代码语言:javascript
复制
python manage.py runserver

注:当然,这两者只是为了发展。我目前还不知道如何在生产中部署Django Q。

现在我们需要一个函数文件。在教程中,我将文件services.py添加到我的应用程序中。在这里,我简单地定义了要运行的函数:

代码语言:javascript
复制
def transcode_video(data):
    # Doing my transcoding stuff here
    return {'entryid': entry.id, 'filename': target_name}

然后,可以通过以下方法在视图代码中调用此函数:

代码语言:javascript
复制
taskid = async_task("apps.myapp.services.transcode_video", data)

因此,我可以向函数提供数据,并获得一个任务ID作为返回值。执行的函数的返回值将出现在创建的任务的result字段中,这样甚至可以从那里返回数据。

在那个阶段我遇到了一个问题:data包含一个TemporaryUploadedFile对象,这导致了一个泡菜错误。在将数据传递给Django Q之前,数据似乎会被腌制,Django Q对该对象不起作用。虽然我已经需要文件系统上的文件来调用pyffmeg,但在视图中,我只需将数据写入一个文件(块,以避免将整个文件同时加载到内存中)。

代码语言:javascript
复制
with open(filepath, 'wb') as f:
    for chunk in self.request.data['file'].chunks():
        f.write(chunk)

通常,在ViewSet中,我会在最后调用serializer.save(),但是对于转码,我不会这样做,因为新对象在事务后保存在Django Q函数中。在这里,我像这样创建它:(UploadedFile来自dango.core.files.uploadedfileAlbumEntry是我自己的模型,我想为它创建一个实例)

代码语言:javascript
复制
with open(target_path, 'rb') as f:
    file = UploadedFile(
        file=f,
        name=target_name,
        content_type=data['file_type']+"/"+data['target_ext'],
    )
    entry = AlbumEntry(
        file=file,
        ... other Model fields here)
    entry.save()

要从视图集中返回一个定义的响应,即使对象尚未创建,除了create()方法之外,我还必须覆盖该方法(在该方法中,我完成了所有处理)。为此,我从父类中复制了代码,并根据perform_create()的返回值(以前没有返回任何内容),对其进行了轻微更改,以返回特定的响应:

代码语言:javascript
复制
def create(self, request, *args, **kwargs):
    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    taskid = self.perform_create(serializer)
    if taskid:
        return HttpResponse(json.dumps({'taskid': taskid, 'status': 'transcoding'}), status=status.HTTP_201_CREATED)
    headers = self.get_success_headers(serializer.data)
    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

因此,perform_create()将在代码转换作业上返回任务ID,否则将返回None。相应的回复在这里发送。

最后,但并非最不重要的是,有一个问题,前端不知道什么时候进行转码。因此,我构建了一个简单的视图,通过ID获取任务:

代码语言:javascript
复制
@api_view(['GET'])
@authentication_classes([authentication.SessionAuthentication])
@permission_classes([permissions.IsAuthenticated])
def get_task(request, task_id):
    task = Task.get_task(task_id)
    if not task:
        return HttpResponse(json.dumps({
            'success': False
        }))
    return HttpResponse(json.dumps({
        'id': task.id,
        'result': task.result,
        ...some more data to return}))

您可以看到,当找不到任务时,我返回一个固定的响应。这是我的解决办法,因为默认情况下,Task对象只有在任务完成时才会被创建。就我的目的而言,只要假设它还在运行,就可以了。Django Q的github问题中的一条评论建议,要获得最新的Task对象,您需要编写自己的任务模型并以某种方式实现它,它定期检查Django q的任务状态。我不想这么做。

我还将结果放入响应中,这样我的前端就可以定期轮询任务(通过其任务ID),并且当转换代码完成时,它将在数据库中包含创建的Model对象的ID。当我的前端看到这个时,它将加载对象内容。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74353823

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档