How to run Python, Perl, Ruby, Bash – any script with Django Views

Read Time: 8 mins

Have you ever wondered how we can run a ready-made script with Django ? In this article, I will explain how to run / execute any script( Python , Perl , Ruby , Bash  ..etc) from Django views. I hope you have installed and configured Django on your box. If not, read my article on “How to Install and configure Django to render HTML Template in 3 steps”.

In general, we are going to setup Django in a way that, it run our desired script in the backend when we make a HTTP request and it returns a HTTP response with status of the execution.
 

Introduction:

Django is a high-level Python Web framework which follows model-view-template  (MVT) architectural pattern. The framework is developed by web programmers Adrian Holovaty and Simon Willison and It was named after guitarist Django Reinhardt. You can develop powerful User Interface with robust design with Django. Django is based on high-level programming language Python which encourage a lot of Python developers to explore in Web development. It is a free and open-source Web framework and it is ridiculously fast, Reassuringly secure and exceedingly scalable. Django has largely adopted by Developers and DevOps to built various internal complex automation tools.

Explore the official documentation from here >>

 

Some stuffs from my personal experience. I have used Django in a lot of situations at my workplace. It was a special experience that I integrated Django  with ReactJS  and built beautiful and powerful user interface. Once you get used to Django, you will never let go off it. Think of running a script with one click on your browser rather than spending time to open a terminal and run it.

Step1: Let’s write a Sample Bash Script for Django:

You can write a script using any languages such as Python, Perl, Bash, Ruby ..etc. The only factor to consider is, the code should give proper exit codes. It should give exit code zero and non-zero in the case of success and failure respectively And output and error should printed stdout and stderr respectively.

 

I hope you have Django project created. Let’s create directory scripts  where settings.py  of your Django project located. Here go through the commands. JFYI,  my Django project name is easyaslinux 

[email protected]:~/easyaslinux$ cd easyaslinux/
[email protected]:~/easyaslinux/easyaslinux$ ls
__init__.py  __init__.pyc  settings.py  settings.pyc  templates  urls.py  urls.pyc  wsgi.py  wsgi.pyc
[email protected]:~/easyaslinux/easyaslinux$ mkdir scripts
[email protected]:~/easyaslinux/easyaslinux$ ls
__init__.py  __init__.pyc  scripts  settings.py  settings.pyc  templates  urls.py  urls.pyc  wsgi.py  wsgi.pyc

 

I am writing a bash script for the demonstration purpose. You need to create a file called file_manipulater.sh  under scripts  directory with content given below.

#!/bin/bash

if [ $1 = "create" ]; then
        mkdir ~/test
	if [ $? -eq 0 ]; then
    	echo "-Successfully  created directory"
    	exit 0

	else
    	>&2 echo "Error: Failed creating directory"
    	exit 1
	fi

elif [ $1 = "delete" ]
then
   	rm -rf ~/test
        if [ $? -eq 0 ]; then
        echo "-Successfully deleted directory"
        exit 0

        else
        >&2 echo "Error: Failed deleting directory"
        exit 1
        fi

else
  echo "Invalid argument"
  exit 1

fi

 

As you can see file_manipulater.sh  takes only two arguments create  and delete . Let’s see what it does. bash file_manipulater.sh create   creates a directory test  in the home directory of the user.  You can see that the script uses shell command mkdir  to create the directory. bash file_manipulater.sh delete  delete the directory test  in the home directory of the user. And we are using shell command rm -rf  to delete the directory. The script exits with an error “Invalid argument” if the argument is neither create  nor delete .

You might have noticed, the script exit with 0, if the operation is succeed and exit with, if the operation is failed. We need this behaviour in order to discover the execution status of the script from Django View. Let’s setup our Django to run this script.

Step2: Write our View function to execute the script in Django:

We have the script ready to be executed by Django. To do that, let’s write a python function in views.py  file. The purpose of functions in views.py  file is to receive HTTP request, do some processing and give HTTP response back. You can learn more about view functions from official documentation here >>

Let’s start:

If you don’t have views.py  already created, create it where you just created scripts  directory. And your views.py  content should look like this.

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
import json,requests
from subprocess import Popen, PIPE, STDOUT



def create_directory():

        command = ["bash","easyaslinux/scripts/file_manipulater.sh","create" ]
        try:
                process = Popen(command, stdout=PIPE, stderr=STDOUT)
                output = process.stdout.read()
                exitstatus = process.poll()
                if (exitstatus==0):
                        return {"status": "Success", "output":str(output)}
                else:
                        return {"status": "Failed", "output":str(output)}
        except Exception as e:
                return {"status": "failed", "output":str(e)}


def delete_directory():

        command = ["bash","easyaslinux/scripts/file_manipulater.sh","delete" ]
        try:
                process = Popen(command, stdout=PIPE, stderr=STDOUT)
                output = process.stdout.read()
                exitstatus = process.poll()
                if (exitstatus==0):
                        return {"status": "Success", "output":str(output)}
                else:
                        return {"status": "Failed", "output":str(output)}
        except Exception as e:
                return {"status": "failed", "output":str(e)}



@csrf_exempt
def file_maniputer(request):

        if request.method == 'POST':
                request_data=json.loads(request.body)

                if request_data["action"] == "create":
                        data = create_directory()
                elif request_data["action"] == "delete":
                        data  =delete_directory()
                else:
                        data = {"status": "not defined", "output":"not defined"}

                response = HttpResponse(json.dumps(data) , content_type='application/json', status=200)
                return response

 

Looks beautiful, right? 🙂 Let’s break down our views.py  and explain each function.

main function – file_maniputer():

@csrf_exempt
def file_maniputer(request):

        if request.method == 'POST':
                request_data=json.loads(request.body)

                if request_data["action"] == "create":
                        data = create_directory()
                elif request_data["action"] == "delete":
                        data  =delete_directory()
                else:
                        data = {"status": "not defined", "output":"not defined"}

                response = HttpResponse(json.dumps(data) , content_type='application/json', status=200)
                return response

The main function file_manipulater  processes requests for us. We have restricted the function to process only POST requests with IF  condition request.method == ‘POST’ . The POST data in HTTP request is saved as a dictionary in variable request_data . Next comes our core IF  condition, which check value for key action  in request_data  dictionary. If the value for action  key is create , then it run function create_directory()   and if it delete , then it run function delete_directory() . Returned data from these functions are saved in data  variable. data  variable is send back as HTTP response with help of Django’s HttpResponse  method. In the 14th line of above code snippet, we are returning a 200OK  HTTP response with content in JSON format. The content is converted to JSON format from dictionary data  using method json.dumps() .

Executer functions –  create_directory() and delete_directory():

Here create_directory()  and delete_directory()  are our executer functions which means they do the real work behind the screens!  Let’s see what we have in function create_directory() .

 

def create_directory():

        command = ["bash","easyaslinux/scripts/file_manipulater.sh","create" ]
        try:
                process = Popen(command, stdout=PIPE, stderr=STDOUT)
                output = process.stdout.read()
                exitstatus = process.poll()
                if (exitstatus==0):
                        return {"status": "Success", "output":str(output)}
                else:
                        return {"status": "Failed", "output":str(output)}
        except Exception as e:
                return {"status": "failed", "output":str(e)}

In general, this function run our bash script and save it’s exit code and output(both stdout and stderr) to variables exitstatus  and output  respectively. And a dictionary contains status message and output is returned according to the exitstatus  variable. I believe you are familiar with subprocess  module of Python, using which you can execute any system command from your Python code. Here we are using Popen  method of the same module to execute our Bash script as a system command. For this, you just need to specify each word in the command as a element in a list. For example  command bash easyaslinux/scripts/file_manipulater.sh create  is converted as [“bash”,”easyaslinux/scripts/file_manipulater.sh”,”create” ] . I am not using this article to deeply explain about Subprocess module, so please explore Subprocess module from here >>

I would say never forget to put proper try-except in your function to catch all exceptions. In case of an exception, just save it in a variable and send back as HTTP response just like I did here. You might have noticed, I am returning a dictionary with keys status  and output  according to the exitstatus  variable. This is helpful for the client who made HTTP request to understand whether the script failed or not.

 

I am not going to explain function delete_directory()  here because everything in it, is same as create_directory()  except it run command bash easyaslinux/scripts/file_manipulater.sh delete .

 

If you need to run scripts written in other languages, just assign command variable as mentioned below.

Python -  ["python","easyaslinux/scripts/your_script_name.py","argument_here" ]

Perl -  ["perl","easyaslinux/scripts/your_script_name.pl","argument_here" ]

Ruby -  ["ruby","easyaslinux/scripts/your_script_name.rb","argument_here" ]

Like this you can execute any script as a system command.

Step3: Let’s connect our View function to an URL:

We have our View function ready. Now let’s wire up the function with an URL. Just open urls.py  file which is located in the same directory of views.py . Content of  urls.py  should look like as shown below.

from django.conf.urls import url
from django.contrib import admin
from django.views import generic
from easyaslinux.views import file_maniputer  #added

urlpatterns = [
    url(r'^admin/', admin.site.urls),
   url(r'^$',generic.TemplateView.as_view(template_name='sample_app.html')),   #this is added for a previous article. Ignore this line on this article
 url(r'^file_maniputer_api$', file_maniputer)        #added
]

 

You can see that I have added two lines in urls.py  file. In line 4, we import our main function file_maniputer  from views.py . And in line 9, we are telling Django to always load and execute function file_maniputer  whenever HTTP request for url /file_maniputer_api  comes.

 

8th line does not have any significance in this article. So you can ignore it in this article. It tells Django to render a template sample_app.html  when request for the main url comes. Complete explanation is given here in this article “How to Install and configure Django to render HTML Template in 3 steps”.

 

Let’s save the file and start our Django server by running following command.

[email protected]:~/easyaslinux$ ./manage.py runserver 0.0.0.0:8000
Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

November 13, 2018 - 07:16:23
Django version 1.9.3, using settings 'easyaslinux.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

 

Step4: It is time to make some HTTP POST calls to Django:

Use Curl command to make HTTP request:

We have everything setup and running. Now we make a HTTP POST to verify the working. I am using my favourite Curl  command.

curl -i -X POST localhost:8000/file_maniputer_api -d '{"action":"create"}'

 

This HTTP request posts a key/value {“action”:”create”}  and our Django should create a directory called test  once it receives this request. Let’s see.

[email protected]:~$ curl -i -X POST localhost:8000/file_maniputer_api -d '{"action":"create"}'
HTTP/1.0 200 OK
Date: Tue, 13 Nov 2018 07:28:22 GMT
Server: WSGIServer/0.1 Python/2.7.12
X-Frame-Options: SAMEORIGIN
Content-Type: application/json

{"status": "Success", "output": "-Successfully  created directory\n"}

 

You have received a 200OK  response with content {“status”: “Success”, “output”: “-Successfully created directory\n”} . The HTTP response says the directory is created. Let’s confirm it.

[email protected]:~$ cd ~
[email protected]:~$ ls
easyaslinux  test

confirmed, the directory has been created 🙂 .

 

Similarly, You can delete the directory by making below HTTP request.

[email protected]:~$ curl -i -X POST localhost:8000/file_maniputer_api -d '{"action":"delete"}'
HTTP/1.0 200 OK
Date: Tue, 13 Nov 2018 07:35:07 GMT
Server: WSGIServer/0.1 Python/2.7.12
X-Frame-Options: SAMEORIGIN
Content-Type: application/json

{"status": "Success", "output": "-Successfully deleted directory\n"}

 

The HTTP response says the directory is deleted. Let’s confirm that too.

[email protected]:~$ cd ~
[email protected]:~$ ls
easyaslinux

Let’s see what happens when the script fails:

We need to see how our Django server respond when the backend bash script fails. How can we make it fail? Let’s try to create the directory test  two times, So the second times, the script will fail as the directory already exists.

Let’s create for the first time.

[email protected]:~$ curl -i -X POST localhost:8000/file_maniputer_api -d '{"action":"create"}'
HTTP/1.0 200 OK
Date: Wed, 14 Nov 2018 14:27:26 GMT
Server: WSGIServer/0.1 Python/2.7.12
X-Frame-Options: SAMEORIGIN
Content-Type: application/json

{"status": "Success", "output": "-Successfully  created directory\n"}

 

Now the directory test  is created. Let’s try to create again by making the same HTTP POST call.

[email protected]:~$ curl -i -X POST localhost:8000/file_maniputer_api -d '{"action":"create"}'
HTTP/1.0 200 OK
Date: Wed, 14 Nov 2018 14:28:31 GMT
Server: WSGIServer/0.1 Python/2.7.12
X-Frame-Options: SAMEORIGIN
Content-Type: application/json

{"status": "Failed", "output": "mkdir: cannot create directory /home/user/test: File exists\nError: Failed creating directory\n"}

See! the bash script is failed as the directory already exist. The script exited with a non-zero status code, then our view function captured it and processed a HTTP response as we taught Django to do.

Hurray! You have completed the course 😀 . This is just a basic setup that you can develop into a powerful server.  You can run code/script of any language as a system command like this. At where I work, we have a developed a plug-in-play system in Django where you can easily plug-in your backend script and it run on the this same setup.

 

Entire code is available at my Gitlab repo :  https://gitlab.com/nijilraj/how-to-run-any-script-python-perl-ruby-bash-from-django-views.git

 

You can write a frontend like ReactJS, NodeJS..etc on top of our Django setup and make these HTTP POST calls from the frontend itself. In my next article, I will explain “how to setup ReactJS on top of this Django server”.

 

Please Subscribe  to this blog so that you don’t miss out anything useful (Checkout Right Sidebar for the Subscription Form) . Also put your thought on this article as a comment .

A Site Reliability Engineer and an automation enthusiast.
Posts created 37

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top
x