Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: Subprocess Continuous Output To HTML View

I need a HTML webpage in my Django app to load and show the continous output of a script in a scrollable box. Is this possible?

I'm presently using a subprocess to run a Python script, but the HTML page won't load until after the script has finished (which can take about 5 minutes). I want the users to see something is happening, rather than just a spinning circle.

What I have already also unloads the full output of the script with "\n" in the text; I'd like it to output each new line instead, if possible.

My code is as follows:

Views.py:

def projectprogress(request):
    GenerateProjectConfig(request)
    home = os.getcwd()
    project_id = request.session['projectname']
    staging_folder = home + "/staging/" + project_id + "/"
    output = ""
    os.chdir(staging_folder)
    script = home + '/webscripts/terraformdeploy.py'
    try:
        output = subprocess.check_output(['python', script], shell=True)
    except subprocess.CalledProcessError:
        exit_code, error_msg = output.returncode, output.output
    os.chdir(home)
    return render(request, 'projectprogress.html', locals())

projectprogress.html:

<style>
  div.ex1 {
  background-color: black;
  width: 900px;
  height: 500px;
  overflow: scroll;
  margin: 50px;
}
</style>

<body style="background-color: #565c60; font-family: Georgia, 'Times New Roman', Times, serif; color: white; margin:0"></body>
    <div class="ex1">
        {% if output %}<h3>{{ output }}</h3>{% endif %}
        {% if exit_code %}<h3> The command returned an error: {{ error_msg }}</h3>{% endif %}
    </div>
    <div class="container">
        <a class="button button--wide button--white" href="home.html" title="Home" style="color: white; margin: 60px;">
            <span class="button__inner">
          Home
        </span>
        </a>
    </div>
</body>
</html>
like image 801
RobTheRobot16 Avatar asked Jul 22 '20 13:07

RobTheRobot16


1 Answers

You could simplify your task using StreamingHttpResponse and Popen:

def test_iterator():
    from subprocess import Popen, PIPE, CalledProcessError

    with Popen(['ping', 'localhost'], stdout=PIPE, bufsize=1, universal_newlines=True) as p:
        for line in p.stdout:
            yield(line + '<br>') # process line here

    if p.returncode != 0:
        raise CalledProcessError(p.returncode, p.args)

def busy_view(request):
    from django.http import StreamingHttpResponse
    return StreamingHttpResponse(test_iterator())

StreamingHttpResponse expects an iterator as its parameter. An iterator function is one which has a yield expression (or a generator expression), and its return value is a generator object (an iterator).

In this example, I simply echo the ping command to prove it works.

Substitute ['ping', 'localhost'] by a list (it has to be a list if you pass parameters to the command - in this case, localhost). Your original ['python', script] should work.

If you want to know more about generators, I would recommend Trey Hunner's talk, and also strongly that you read chapter 14 of Fluent Python book. Both are amazing sources.

Disclaimer:

Performance considerations

Django is designed for short-lived requests. Streaming responses will tie a worker process for the entire duration of the response. This may result in poor performance.

Generally speaking, you should perform expensive tasks outside of the request-response cycle, rather than resorting to a streamed response.

like image 156
Niloct Avatar answered Sep 29 '22 12:09

Niloct