我正在构建一个基于Django (4.1.1)和Vue的画廊网络应用程序。我也想上传和显示视频(不仅仅是图片)。为了支持不适用于视频html标记的格式,我将通过pyffmpeg将这些格式转换为mp4。
为此,我为基于FileField的模型创建了一个自定义字段。在它的save方法中,我获取文件内容,转换它并保存结果。这是由序列化程序通过相应的ViewSet调用的。这是可行的,但是视频转换的时间太长了,所以我的Vue应用程序(用axios执行)的way请求正在进入超时状态。
显然,我需要以某种方式卸载转换,直接返回相应的响应,并在转换完成后立即将数据保存到数据库中。
这有可能吗?或者,除了ViewSet之外,我是否需要编写一个自定义视图来进行计算?你能给我一个关于如何卸下计算的提示吗?我对像asyncio这样的东西只有基本的知识。
TL;DR:如何在使用FileField将文件数据保存到模型并在计算结束前返回响应之前,对文件数据进行大量的计算?
如果有必要,我可以提供我的当前代码。
发布于 2022-11-09 17:15:19
我现在已经解决了我的问题,尽管我仍然对其他/更好的解决方案感兴趣。我的解决方案是有效的,但可能不是最好的,我觉得在某些地方有点麻烦。
TL;DR:将django-q安装为具有redis数据库后端的任务队列管理器,并将其连接到django,然后调用从我的视图中转换视频文件的函数。
taskid = async_task("apps.myapp.services.transcode_video", data)这应该是一个健壮的系统,可以并行和不阻塞请求地处理这些代码转换任务。
我找到了本教程介绍Django-Q。Django-Q管理并执行django的任务。它与Django并行运行,并通过它的代理(在本例中是一个redis数据库)连接到它。
首先,我通过pip安装了django-q和redis客户端模块。
pip install django-q redis然后,我建立了一个Redis数据库(这里运行在我的机器上的一个带有官方redis图像的码头容器中)。如何做到这一点在很大程度上取决于您的平台。
然后通过将配置添加到settings.py中来配置Django-Q (注意,我禁用了超时,因为代码转换任务可能会花费相当长的时间)。可在今后改变这一情况):
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:
INSTALLED_APPS = [
...
'django_q',
]然后通过以下方式迁移Django Q的模型定义:
python manage.py migrate然后启动Django Q via ( Redis数据库此时应该运行):
python manage.py qcluster这在一个独立的终端中运行,而不是典型的终端。
python manage.py runserver注:当然,这两者只是为了发展。我目前还不知道如何在生产中部署Django Q。
现在我们需要一个函数文件。在教程中,我将文件services.py添加到我的应用程序中。在这里,我简单地定义了要运行的函数:
def transcode_video(data):
# Doing my transcoding stuff here
return {'entryid': entry.id, 'filename': target_name}然后,可以通过以下方法在视图代码中调用此函数:
taskid = async_task("apps.myapp.services.transcode_video", data)因此,我可以向函数提供数据,并获得一个任务ID作为返回值。执行的函数的返回值将出现在创建的任务的result字段中,这样甚至可以从那里返回数据。
在那个阶段我遇到了一个问题:data包含一个TemporaryUploadedFile对象,这导致了一个泡菜错误。在将数据传递给Django Q之前,数据似乎会被腌制,Django Q对该对象不起作用。虽然我已经需要文件系统上的文件来调用pyffmeg,但在视图中,我只需将数据写入一个文件(块,以避免将整个文件同时加载到内存中)。
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.uploadedfile,AlbumEntry是我自己的模型,我想为它创建一个实例)
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()的返回值(以前没有返回任何内容),对其进行了轻微更改,以返回特定的响应:
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获取任务:
@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。当我的前端看到这个时,它将加载对象内容。
https://stackoverflow.com/questions/74353823
复制相似问题