Django: Как загрузить файл, сгенерированный скриптом python, запущенным в задаче celery, после ее завершения
Я создаю веб-приложение Django, и в настоящее время у меня есть возможность загрузить набор данных (файл .csv). При нажатии кнопки 'upload' запускается задача celery, которая манипулирует набором данных.
Я хочу отобразить страницу с кнопкой "результат загрузки", когда задача celery будет выполнена. В настоящее время существует прогресс-бар, который отображает прогресс выполнения скрипта с помощью инструментария celery-progress (это занимает довольно много времени).
У меня уже есть встроенные пользователи, если это поможет.
Вопрос 1: Где я должен хранить выходной файл, сгенерированный скриптом python, выполняемым в задаче celery, чтобы пользователь мог его скачать?
Вопрос 2: Как отобразить страницу с кнопкой "загрузить результат", когда задача celery будет выполнена?
Вопрос 3: Как мне заставить эту кнопку загрузить файл, относящийся к файлу пользователя?
Вот мой код на данный момент:
views.py
from django.conf import settings
from .models import Document
from .forms import DocumentForm
from django.views import View
from .tasks import RunBlackLight
import os
from django.http import Http404, HttpResponse
class RunView(View):
form_class = DocumentForm
initial = {'key': 'value'}
template_name = 'runblacklight/progress.html'
error_template_name = 'runblacklight/error.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
if request.method == 'POST':
# Get the uploaded dataset from the html page.
form = self.form_class(request.POST, request.FILES)
if form.is_valid():
# Get the information out of the HTML form.
num_gens = form.cleaned_data.get('number_of_generations')
num_pop = form.cleaned_data.get('number_in_initial_populations')
# Save the form, creating an object of the document model located in models.py
obj = form.save()
obj.user = request.user
# Append /code to the file path, as the location of all of our media files is in
# ./code in our DOCKER container.
file_path = "/code" + obj.document.url
print(os.path.abspath(file_path))
# Error Handling, right now it only throws an error if .csv is not in the filename.
if ".csv" not in file_path:
return render(request, self.error_template_name, {'form': form})
# Save the instance of blacklight into the database.
obj.save()
# Call our celery task which will run the Blacklight instance
run_blacklight_task = RunBlackLight.delay(num_pop, 2, num_gens, file_path)
# Get the task id so that we can display the progress bar.
task_id = run_blacklight_task.task_id
return render(request, self.template_name, {'task_id': task_id})
else:
form = self.form_class()
return render(request, self.template_name, {'form': form})
runblacklight/models.py
from django.conf import settings
User = settings.AUTH_USER_MODEL
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'documents/user_{0}/{1}'.format(instance.user.id, filename)
class Document(models.Model):
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
description = models.CharField(max_length=255, blank=True)
document = models.FileField(upload_to=user_directory_path)
number_of_generations = models.IntegerField()
number_in_initial_populations = models.IntegerField()
uploaded_at = models.DateTimeField(auto_now_add=True)
runblacklight/forms.py
from django import forms
from .models import Document
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
fields = ('description', 'document', 'number_of_generations', 'number_in_initial_populations')
runblacklight/templates/runblacklight/run.html
{% extends "home/base.html" %}
{% load static %}
{% block title %} Run Page {% endblock title%}
<!-- Extends the base template, defined in home/templates/home/base.html. This fills the content block on the website. -->
{% block content %}
<div class="jumbotron" style="background-color: #000000; border-radius: 0 !important">
<!-- The block below is where the form to upload datasets lives. It is defined in .runblacklight.html-->
{% block demo %}
{% endblock %}
<!-- JQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<!-- Bootstrap JS -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script>
<!-- Celery Progress -->
<script src="{% static 'celery_progress/celery_progress.js' %}"></script>
<!-- The block below is where the js code for the progress bar lives. It is defined in .progress.html-->
{% block progress_bar_js %}
{% endblock progress_bar_js %}
</div>
{% endblock content %}
runblacklight/templates/runblacklight/runblacklight.html
{% extends "runblacklight/run.html" %}
{% load static %}
{% block demo %}
<!-- Reset Form -->
<a href="{% url 'run' %}" role="button" class="btn btn-primary btn-lg btn-block" style="border-radius: 0px; background-color:#000000; border-color:#000000"><b>RESET SIMULATION</b></a>
<!-- Download Form -->
<div class="container text-center" style="padding-top: 20px; background-color:#000000">
<form id='fileuploadform' method="post" enctype="multipart/form-data" style="color:white">
{% csrf_token %}
{{ form.as_p }}
<button id="fileuploadbutton" type="submit" style="background-color: #ffffff; border-color: #ffffff; color: #000000">Run</button>
</form>
</div>
<!-- Download Status -->
<div class="container" style="padding-top: 20px; background-color:#000000">
<div class="card" style="height: 120px; background-color:#000000">
{% block progress %}
{% endblock progress %}
</div>
</div>
{% endblock demo %}
runblacklight/templates/runblacklight/progress.html
{% extends "runblacklight/runblacklight.html" %}
{% load static %}
{% block progress %}
<div class="text-center" style="font-size: 14px; background-color: #000000">
<div id="progress-bar-message" style="color: white">
Click the "Run" button
</div>
</div>
<div class='progress-wrapper' style="padding-top: 10px;">
<div
id='progress-bar' class='progress-bar progress-bar-striped' role='progressbar'
style="height:30px; width: 0; border-radius: 5px; color: white">
</div>
</div>
<div id="celery-result"></div>
{% endblock progress %}
{% block progress_bar_js %}
{% if task_id %}
<script type="text/javascript">
// Progress Bar (JQuery)
$(function () {
var progressUrl = "{% url 'celery_progress:task_status' task_id %}";
CeleryProgressBar.initProgressBar(progressUrl, {})
});
</script>
{% endif %}
{% endblock progress_bar_js %}
runblacklight/tasks.py
# Celery
from celery import shared_task
# Celery-progress
from celery_progress.backend import ProgressRecorder
# Task imports
from locationofpythoncript.src.pythonscript import * #Population class
import time
# Celery Task
@shared_task(bind=True)
def RunBlackLight(self,
individuals_per_population,
number_of_parents_mating,
number_of_generations,
environment_goal_location):
print('Task started')
# Create the progress recorder instance
# which we'll use to update the web page
progress_recorder = ProgressRecorder(self)
print('Start')
population = Population(individuals_per_population, number_of_parents_mating, environment_goal_location)
for generation in range(number_of_generations):
print("Generation : ", generation)
print("Number of individuals : ", len(population.individuallist))
population.findparents()
population.reproduce()
progress_recorder.set_progress(generation + 1, number_of_generations, description="Downloading")
best = max([population.individuallist[i].fitness for i in range(len(population.individuallist))])
best.model.save()
return 'Task Complete'
Большое спасибо за любую помощь, и, пожалуйста, дайте мне знать, если вам понадобится дополнительная информация. <3