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 .
user@ubuntu:~/easyaslinux$ cd easyaslinux/ user@ubuntu:~/easyaslinux/easyaslinux$ ls __init__.py __init__.pyc settings.py settings.pyc templates urls.py urls.pyc wsgi.py wsgi.pyc user@ubuntu:~/easyaslinux/easyaslinux$ mkdir scripts user@ubuntu:~/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 1 , 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.
user@ubuntu:~/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.
user@ubuntu:~$ 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.
user@ubuntu:~$ cd ~ user@ubuntu:~$ ls easyaslinux test
confirmed, the directory has been created 🙂 .
Similarly, You can delete the directory by making below HTTP request.
user@ubuntu:~$ 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.
user@ubuntu:~$ cd ~ user@ubuntu:~$ 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.
user@ubuntu:~$ 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.
user@ubuntu:~$ 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 .