6
from django.db import models
import os
from django.db.models.signals import post_save
import sys

class Form(models.Model):
    site = models.CharField(max_length=50)
    num = models.CharField(max_length=10)
    octet = models.CharField(max_length=30)

    def __unicode__(self):
        return self.site
        return self.num
        return self.octet

def create_conf(sender, **kwargs):
    os.system("/usr/local/build " + self.site + ' ' + self.num + ' ' + self.octet)

post_save.connect(create_conf, sender=Form)

Trying to get my django web app to execute python command line application with arguments. Not sure if this is the best way to go around it? If its not any advice would be great. Trying to take input from user via web form and use it as the arguments to execute cmd application.

Help would be fantastic

Thanks William

LinuxBill
  • 415
  • 1
  • 8
  • 19
  • 3
    I'd put a something like Celery (http://www.celeryproject.org/) so that the commands all get queued and run in order. It will ensure your server doesn't get crushed by load from running that command and it running for a long time. – stormlifter Sep 13 '13 at 16:35
  • Cheers. Will have a look at that. Its not a hard build, it takes like 2 secs. And won't be very regular. In its current form it, doesn't seem to be working. How can I enable logging to see what its failing with. My self.num will those be passing through the user variables correctly? Thanks – LinuxBill Sep 15 '13 at 13:55
  • 3
    I'm no security expert but it seems to me that this is a risky thing to do. – pydanny Sep 16 '13 at 07:39
  • @pydanny so do I. I would prefer to see some check about the rights of the user who triggers this `create_conf` –  Sep 16 '13 at 07:46
  • I can see why your concerned with this, makes sense. Implementing some kind of security check makes sense. But I would like to get the main functionality of this working before I start adding anything else to it. – LinuxBill Sep 16 '13 at 08:05
  • try subprocess.call() – oleg.foreigner Sep 17 '13 at 10:20
  • 2
    @pydanny Good to see you on SO. Thanks for your very clear didactic contributions to the django community. I wish I could upvote your profile. – Private Sep 21 '13 at 07:42

3 Answers3

4

This seems to be a good way if you want to execute the command at each save() of your objects.

However, you should sanitize your inputs: if users set some special characters (,, ; or & for example) it could break your command and it could be risky for your system (for example a && rm -rf /* in octet could be fun here :p). You should look at this answer which uses Popen to secure parameters:

from subprocess import Popen  

def create_conf(sender, instance, **kwargs):
     p1 = Popen(["/usr/local/build", instance.site, instance.num, instance.octet])

I also fix the function declaration to get the instance, according to the documentation.

Another thing: your __unicode__ function is incorrect, you can't return 3 values like this.

class Form(models.Model):
    site = models.CharField(max_length=50)
    num = models.CharField(max_length=10)
    octet = models.CharField(max_length=30)

    def __unicode__(self):
        return "%s %s %s" % (self.site, self.num, self.octet)
Community
  • 1
  • 1
Maxime Lorant
  • 34,607
  • 19
  • 87
  • 97
  • def create_conf(sender, instance, **kwargs): p1 = Popen(["/usr/local/buildswitchconfig", instance.site, instance.num, instance.octet], shell=True) – LinuxBill Sep 16 '13 at 09:24
  • This is now telling me that there is no OS or directory. I can assume that its to do with paths and env's. I have tried adding the full python path before the build command. But the same. Added shell=true. Still fails. – LinuxBill Sep 16 '13 at 09:25
  • It is probably your permissions. Make sure you are outputting to/from a place you can write to. Use os.path.join(settings.MEDIA_ROOT, ...) to save it to a place within your app you have write permissions to. Similarly, if the user is acting on a file they uploaded, make sure you saved it first. – Chrismit Sep 17 '13 at 16:31
3

Why this code doesn't work is explained in another answer, I'm just answering his questions for the next time something like this happens.

Would like advice on how to debug why this is not working.

def create_conf(sender, **kwargs):
    import pdb
    pdb.set_trace()
    os.system("/usr/local/build " + self.site + ' ' + self.num + ' ' + self.octet)

This will give you the built-in Python debugger, in the console runserver is running. With it, you can go trough your code step by step, and execute regular Python commands (like print).

Read up on the commands for inside the debugger here Some IDEs come with Python debugging support, and present it with a nice GUI.

When using it a lot: use the oneliner:

import pdb; pdb.set_trace;  # yes, Python supports semicolons 
                            # (although its not recommended)

And to make sure that the strings are being passed through from django web page to arguments on cmd application.

You can either:

cast them to strings to make sure

os.system("/usr/local/build " + str(self.site) + ' ' + str(self.num) + ' ' + str(self.octet))

or use the string formatter (which is better than using the + sign and makes sure input gets cast to strings)

os.system("usr/local/build {0} {1} {2}".format(self.site, self.num, self.octet))

Read up on the string .format method here

Maikel Wever
  • 141
  • 3
2
  1. Would like advice on how to debug why this is not working.

You can use the python / django logging mechanism and throw logging information wherever needed. In short:

import logging
logger = logging.getLogger(__name__) # Get an instance of a logger
my_custom_command = "/usr/local/build " + self.site + ' ' + self.num + ' ' + self.octet
logger.warning(my_custom_command)

prints a warning message to console. If you use the django debug toolbar (highly recommended!), you can easily log using the .debug-level (not .warning) and see the results on the web page.

You can include those logging messeges on every intended step of your work flow and check whether everything is as it should be.

I'm doing something like this (compiling c-code which is prvided by the user) using the following code snipplet:

    try:
        cfile = os.path.join(MEDIA_ROOT, 'temp/src/cfile.c')
        ofile = os.path.join(MEDIA_ROOT, 'temp/src/cfile.o')
        efile = os.path.join(MEDIA_ROOT, 'temp/src/cfile.exe')
        command_string = 'gcc -O0 -g3 -Wall -c -fmessage-length=0 ' + cfile + ' -o ' + ofile
        compile_result = subprocess.check_output(command_string,stderr=subprocess.STDOUT,shell=True)
        if compile_result != "": #Compile error
            self.ausgabe = "Compile:\n"
            self.ausgabe += str(compile_result)
    except:
        self.ausgabe = u"Compilierfehler:\n"
        self.ausgabe += str(compile_result)

(please ignore the German words, they are not important for understanding the logic)

One additional thing which might be important is to use correct encoding for all strings.

OBu
  • 4,977
  • 3
  • 29
  • 45