Von HTML Knopf in Python rein

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
mzh
User
Beiträge: 295
Registriert: Dienstag 3. März 2009, 15:27
Wohnort: ZH

Hallo Leute
In diesem Post geht es um folgendes: Auf einer HTML Seite ist ein Knopf, wenn dieser geklickt wird, so soll ein Pythonskript ausgeführt werden ('script.cgi'). Das Skript soll ein 'text' Feld auslesen-- um zu überprüfen ob das auch funktioniert wollte ich den Text der eingegeben wird in ein File schreiben, muss aber nicht sein.
Ein paar Dinge verstehe ich nicht ganz:
- Braucht man hier JavaScript?

Jetzt sieht es ungefähr so aus:
HTML:

Code: Alles auswählen

 <FORM onSubmit="return OnSubmitForm();"
       METHOD="POST"
       ENCTYPE="multipart/form-data"
       name="connectForm" 
 
       <input type="submit" value="CONNECT" name="connect"
              onClick="document.pressed=this.name"> </p>
       Text Input: <input type="text" name="textInput">
 </form>
Also, der Knopf ist in der Form und das Textfeld auch. Damit der Server das CGI Skript startet, hab ich hier ein JavaScript:

Code: Alles auswählen

function OnSubmitForm() {
    document.connectForm.action ="hello.cgi"
    return true;
}
Wenn ich das jetzt richtig verstehe (den Code hab ich von einem Kollegen bekommen der aber im Moment weg ist), dann läuft das so, dass der onSubmit-Variable (im form-tag) 'true' zugewiesen wird und gleichzeitig wird festgelegt, welches CGI-Skript ausgeführt werden soll (nämlich hello.cgi). Dh. der Browser bewirkt, dass hello.cgi ausgeführt wird.
Ich verstehe aber leider nicht, wie das der Teil im Input-tag bedeutet. V.a. krieg ich nicht rein, was 'onClick' zugewiesen wird, also 'document.pressed=this.name'.

Der letzte Teil wäre nun das CGI Skript.
Damit habe ich ja Zugriff auf die Elemente der Form.

Code: Alles auswählen

#!/usr/bin/python

import cgi
import cgitb
cgitb.enable()
form = cgi.FieldStorage()

print "Content-type: text/html\n\n"
print

print form['textInput'].value
Das sollte ja doch den Text ausgeben, der in das Textfeld geschrieben wurde.


Vielen Dank, wenn jemand etwas Licht ins Dunkel bringen könnte.
[url=http://www.proandkon.com]proandkon.com[/url]
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Muss es wirklich Python-Code sein, der sozusagen aus einer HTML/JavaScript-Umgebung aufgerufen wird? Der übliche Weg ist eigentlich, dass man mittels Web-Framework eine App baut, dort die "Ereignisse" (Seite anfordern mittels GET, Daten senden mittels POST) abfängt und entsprechenden HTML-Code zurückgibt. Als Empfehlung seien wie immer Bottle oder Flask genannt. Infos zu beiden findest du über eine Suchmaschine.
mzh
User
Beiträge: 295
Registriert: Dienstag 3. März 2009, 15:27
Wohnort: ZH

Die Idee hinter dem Ganzen ist, dass ich jetzt etwas mit Python geschrieben habe, das über das Internet ausführbar sein soll, deshalb wäre ich froh, wenn das so möglich wäre.
[url=http://www.proandkon.com]proandkon.com[/url]
BlackJack

@mzh: Einfach nur um Form-Daten an ein CGI-Skript zu schicken benötigt man kein JavaScript.

Dem `onsubmit`-Attribut wird nicht ``true`` zugewiesen, sondern das Stückchen JavaScript-Code als Zeichenkette. Der wird ausgeführt, wenn die Formulardaten abgeschickt werden sollen, beziehungsweise kurz vorher. Der Code sorgt dann dafür, dass das Attribut `action` von dem Form auf "hello.cgi" gesetzt wird. Warum die Funktion ``true`` zurück gibt und warum für den Aufruf ein ``return`` mit in dem Code bei `onsubmit` steht, ist mir nicht klar. Ist IMHO beides überflüssig.

Der `onclick`-Code hat auch nichts mit dem eigentlichen Problem zu tun -- ist also hier auch überflüssig.

Wo liegt denn jetzt das Problem? Hier müssen ja viele Komponenten zusammen spielen. Das HTML, der Browser (JavaScript), der Server. Wenn es nicht das tut was es soll, musst Du erst einmal heraus finden an welcher Stelle dieser Kette es hängt. Werden die Form-Daten abgeschickt? Ist der Server so konfiguriert, dass er das CGI-Skript ausführt? Kommen die Daten dort an?

Ein allgemeiner Text über CGI, wie die entsprechenden Abschnitte bei SelfHTML wären vielleicht an dieser Stelle auch eine nützliche Lektüre.

Und natürlich wie immer die Frage bei CGI -- muss es denn wirklich CGI sein? Wie schon gesagt wurde Bottle und Flask existieren.
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

mzh hat geschrieben: Jetzt sieht es ungefähr so aus:
HTML:

Code: Alles auswählen

 <FORM onSubmit="return OnSubmitForm();"
       METHOD="POST"
       ENCTYPE="multipart/form-data"
       name="connectForm" 
 
       <input type="submit" value="CONNECT" name="connect"
              onClick="document.pressed=this.name"> </p>
       Text Input: <input type="text" name="textInput">
 </form>
Das "ungefähr" stimmt mich etwas stutzig. Vielleicht ist das Original ja in Ordnung, aber das da oben ist ein ziemlich kaputtes HTML-Fragment.

Wenn JavaScript nicht für andere Dinge hier zwangsweise erforderlich ist, dann würde ich völlig darauf verzichten.
mzh
User
Beiträge: 295
Registriert: Dienstag 3. März 2009, 15:27
Wohnort: ZH

@ me: gemeint ist, die HTML Tags html, head, title, body wurden weggelassen, sorry kann man ja nicht wissen.

@ BlackJack: Danke für die Hinweise. Die JavaScript Funktion würdest du also eher so schreiben:

Code: Alles auswählen

function OnSubmitForm() {
    document.connectForm.action ="hello.cgi"
}
und das Form-Tag: <FORM onSubmit="OnSubmitForm();" METHOD="POST" ENCTYPE="...">, also ohne 'return'.

Ok, also dem 'action'-Attribut der Form wird das 'hello.cgi' Skript zugewiesen. Wenn ich das richtig verstehe, dann erweitert das JavaScript also die Attributliste der Form? Im Form-Tag steht ja kein 'action'-Attribut.

Das soll keine grosse Sache werden, im Prinzip ist es ziemlich simpel, der kompliziertere Teil wird auf der Serverseite erledigt. Alles was möglich sein soll ist das der Benutzer ein oder zwei Parameter mitgeben kann und dann soll das laufen.
[url=http://www.proandkon.com]proandkon.com[/url]
BlackJack

@mzh: Auch mit den genannten Tags ist das noch kaputt. Das Start-Tag vom ``form`` wird zum Beispiel nicht geschlossen, was dazu führt dass gar keine Schaltfläche zum Abschicken der Daten angezeigt wird.

Ich würde die überflüssigen ``return``\s weglassen, ja. Aber vielleicht auch gleich das überflüssige JavaScript ganz. Wo liegt der nutzen das `action`-Attribut per JavaScript zu setzen, wenn man es auch gleich ins HTML schreiben kann!?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mzh hat geschrieben: Ok, also dem 'action'-Attribut der Form wird das 'hello.cgi' Skript zugewiesen. Wenn ich das richtig verstehe, dann erweitert das JavaScript also die Attributliste der Form? Im Form-Tag steht ja kein 'action'-Attribut.
Dann schreib da doch mal eins rein!!! :mrgreen:
mzh hat geschrieben: Das soll keine grosse Sache werden, im Prinzip ist es ziemlich simpel, der kompliziertere Teil wird auf der Serverseite erledigt. Alles was möglich sein soll ist das der Benutzer ein oder zwei Parameter mitgeben kann und dann soll das laufen.
Wie bereits empfohlen nutze dafür dennoch ein Framework! Django ist (fast) immer die richtige Wahl; willst Du nur dieses bisschen machen und Dich nie wieder mit Webprogrammierung befassen, dann würde ich Dir flask empfehlen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mzh
User
Beiträge: 295
Registriert: Dienstag 3. März 2009, 15:27
Wohnort: ZH

Da ging eine Klammer beim Kopieren verloren.
Gut, ich versuchs ohne JavaScript.
Aber wie merkt dann der Server, das der Submit-Button geklickt wird?
[url=http://www.proandkon.com]proandkon.com[/url]
BlackJack

@mzh: Der Server bemerkt das gar nicht -- der Browser tut das. Und der ruft dann die unter `action` hinterlegte URL auf und schickt dort den Form-Inhalt hin. Lies doch mal SelfHTML oder irgend eine andere Quelle zum Thema CGI.
mzh
User
Beiträge: 295
Registriert: Dienstag 3. März 2009, 15:27
Wohnort: ZH

Also, ich hab jetzt ein Minimalbeispiel am laufen.
Hier der HTML Teil:

Code: Alles auswählen

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/htm
l4/loose.dtd"-->
<html>
<head> 
<title>Title: HELLO</title> 
<link rel="stylesheet" href="../a_presentation/baker.css" type="text/css">
</head>

<body>
<h2>Body: HELLO-- a_content/hello.html</h2>

<FORM METHOD="POST" name="helloForm" action="./hello.cgi">
    <p>Text Input: <input type="text" name="nameInput"></p>
    <p>Submit: <input type="submit" value="Submit"></p>
</form>

</body>
</html>
Das CGI-Skript, mit dem ich dann den Form-Werte auslesen, in Parameter speichern und an die weiteren Pythonskripte senden will, sieht so aus (und befindet sich im selben Verzeichnis):

Code: Alles auswählen

#!/usr/bin/python
print "Content-type: text/html\n\n"

import cgi
import cgitb
#cgi.test() 
cgitb.enable()
page = '<html>\
        <head>\
        <title>\
        Title: hello.cgi\
        </title>\
        </head>\
        <body>\
        Body: hello.cgi\
        </body>\
        </html>' 
print page
Ich konnte auch den Wert, den ich ins Textfeld eingegeben habe, anschliessend über die Form auslesen und im CGI Skript wieder (mit String-Formatting) wiedergeben.

Das Ganze scheint jetzt etwas robuster geworden zu sein.
Eine Frage: woran kann es liegen, dass ich im CGI-Skript bspw. ohne weiters urllib importieren kann, allerdings ist es nicht möglich ein selbstgeschriebenes Modul zu importieren. Wenn ich allerdings den Pythoninterpreter starte, dann lässt sich besagtes Modul ohne weiters (von jedem Verzeichnis aus) importieren.
Was kann da die Ursache für dieses Verhalten sein?
[url=http://www.proandkon.com]proandkon.com[/url]
BlackJack

@mzh: Wie versuchst Du denn das Modul zu importieren? Und wo liegt das? Hat der Benutzer unter dem der Webserver läuft entsprechende Zugriffsrechte?
mzh
User
Beiträge: 295
Registriert: Dienstag 3. März 2009, 15:27
Wohnort: ZH

Ich verstehe nicht ganz wie du 'wie' meinst?
Im CGI-Skript steht 'import connect' (das Modul das ich importieren will heisst 'connect').
Im Apache2 error.log file steht dann: ImportError: No module named connect.

Wenn ich den Pythoninterpreter aus genau dem Verzeichnis starte, in dem das CGI-Skript liegt, so kann ich ohne Probleme 'import connect' ausführen.

Wäre echt froh, wenn ich das irgendwie hinkriege. Ansonsten, was gäbe es dann sonst noch, das 'connect' Modul auszuführen? 'connect' ist jetzt ein Python-File, sonst müsste ich ja daraus auch ein CGI-File machen. Wäre das eine Möglichkeit?
[url=http://www.proandkon.com]proandkon.com[/url]
deets

Nein. Das hat damit nichts zu tun. Fuer dein Problem gibt es eine sehr grosse Menge an Loesungen. Das Grundproblem besteht darin, dass du dein Modul "connect" im Interpreter deshalb findest, weil die Liste der Pfade, die Python absucht, automatisch den aktuellen Pfad beinhaltet.

Versuch mal zB in das draueberliegende Verzeichnis zu wechseln, und dann "Python pfad/mein.cgi" aufzurufen. Dann ist essig mit connect importieren.

Um das Problem zu loesen gibt es eine ganze Reihe von Moeglichkeiten. Da du ja beharrlich alle Hinweise darauf, dass ganze mit einem Webframework zu machen ignorierst, hier mal die einfachste fuer deinen Fall: vor dem import musst du sys.path so abaendern, dass der import klappt.

Also so etwa:

Code: Alles auswählen

import sys
import os
sys.path.append(os.dirname(__file__)) # den Pfad zum aktuellen Skript einhaengen
import connect # sollte jetzt gehen.
Falls das doch Probleme macht, weil aus irgendwelchen Gruenden __file__ nicht gesetzt ist, kannst du natuerlich auch den Pfad zum beinhaltenden Verzeichnis hart kodieren.
mzh
User
Beiträge: 295
Registriert: Dienstag 3. März 2009, 15:27
Wohnort: ZH

@deets:
es ist nicht so, dass ich hier grundsätzlich mich gegen ein Framework sträuben will. Es ist nur so, dass ich einfach eine irgendwie laufende Version am laufen haben will, damit ich sehen kann ob das Program in der Art und Weise wie ich es mir vorstelle auch irgendwie funktioniert. Ich hab mir das Flask-Framework angeschaut, ich hab aber im Moment schlicht keine Zeit mich damit auseinanderzusetzen..
Ich hab deinen Code versucht (in Python3 und Python2.4), leider funktioniert es nicht. Bist du sicher dass os.dirname stimmt?

Code: Alles auswählen

mzh $ python2.4 
Python 2.4.3 (#2, Jan 21 2010, 19:56:43) 
[GCC 4.0.3 (Ubuntu 4.0.3-1ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.dirname
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'module' object has no attribute 'dirname'
>>> 
Sowie

Code: Alles auswählen

mzh $ python
Python 3.1.1 (r311:74480, Nov 16 2010, 14:33:59) 
[GCC 4.0.3 (Ubuntu 4.0.3-1ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.dirname
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'dirname'
>>> 
Zuletzt geändert von mzh am Donnerstag 28. April 2011, 08:33, insgesamt 1-mal geändert.
[url=http://www.proandkon.com]proandkon.com[/url]
BlackJack

@mzh: Falls mit "das" `os.dirname()` gemeint sein sollte: Das heisst eigentlich `os.path.dirname()`.
mzh
User
Beiträge: 295
Registriert: Dienstag 3. März 2009, 15:27
Wohnort: ZH

@BlackJack, cool.
@deets: ach so, in der interaktiven Session ist das __file__ Attribute nicht definiert. Klar, dass meine Beispiele nicht laufen.
Ich habs jetzt so hingekriegt:

Code: Alles auswählen

import os.path
print(os.path.dirname(os.path.abspath(__file__)))
Ich möchte meine Ursprüngliche Frage nochmals anders stellen:
Ganz konkret geht es darum, dass folgendes Skript über einen Knopf auf einer Webseite gestartet werden kann. Das Skript ruft ein paar Programme auf, die auf dem Server installiert sind und schreibt ein paar Files, mehr nicht.
Alles was ich möchte ist jetzt, dass der Besucher der Website dieses Skript starten kann.

Code: Alles auswählen

#!/usr/local/bin/python3

"""Description of the 'connect' module.

The 'connect' module prepares the BioFET-SIM calculation.
It carries out the following steps:
    - Download of the PDB file
    - Fixing the PDB file with PDB2PQR
    - Realigning the protein structure with VMD
    - From the realignment, the dimensions are obtained
    - Calculation of overall charge with PROPKA 3.
""" 

# NOTE:
# - Calls all functions in order to obtain the
#   charge, charge-carrier number and dimensions
#   of the protein.

# Default modules
import sys
import os
import time
from os.path    import splitext
from subprocess import call 

# Custom modules
import propka 
import getCharge 
import getDimension

# Builtin modules 
#import realign 
#import getPointCharges
#import getCharge 
from signature import signature
from tests     import hasExt
from parse     import parse

# ************************************************************
# ************************************************************
# SETUP
def setup(pdbfile): 
    """Setting up the run.

    The function handles the submitted argument, ie. it checks
    if there is an extension or not. Incase it is missing, it is
    appended, incase it is present, the extension remains.

    setup(<PDB_ID[.pdb]>) --> target

    The return value is an uppercase string including the '.pdb'
    extension.
    """
    signature('SETUP')

    # Handle the case with/without extension in the argument
    if hasExt(pdbfile):
        target = pdbfile.upper() 
    else:
        target = pdbfile.upper() + '.pdb' 
    return target 

def getPDB(target):
    """Obtain the PDB file.
    The 'target' argument is the correct file name, including
    the '.pdb' extension, eg. '1AVD.pdb'.

    The file is written to disc after download."""

    print("Provided PDB file: " + target) 
    print("Requesting PDB file: " + target)
    #call(['wget', 'http://www.pdb.org/pdb/files/'\
    #        + target])
    os.system("wget http://www.pdb.org/pdb/files/"\
            + target)
# ------------------------------------------------------------



# ************************************************************
# ************************************************************
# PDB2PQR Part 
def callPDB2PQR(target): 
    signature('PDB2PQR')
    call(['pdb2pqr.py', '-v', '--ff=CHARMM', target,\
            splitext(target)[0] + '-out.pqr'])
    #os.system('pdb2pqr.py -v --ff=CHARMM ' + target\
    #        + ' ' + splitext(target)[0] + '-out.pqr')
    print('PDB2PQR done.')
    print() 
# ------------------------------------------------------------



# ************************************************************
# ************************************************************
# VMD Part
def callVMD(target):
    signature('VMD') 
    #call(['align.py', target])
    os.system('realign.py ' + target[0:4] + '-out.pqr')
    print('VMD done.')
    print() 
# ------------------------------------------------------------ 



# ************************************************************
# ************************************************************
# PROPKA Part
def callPropka3(target):
    signature('PROPKA') 
    #call(['cp', splitext(target)[0] + '-out.pqr',\
    #            splitext(target)[0] + '-out.pdb'])
    #call(['propka.py', splitext(target)[0] + '-out.pdb'])
    #os.system('cp ' + splitext(target)[0] + '-out.pqr'\
    #        ' ' + splitext(target)[0] + '-out.pdb')
    if os.system('propka.py ' + splitext(target)[0] +\
            '-out-new.pdb') == 0:
        print('Propka 3 done.') 
    print() 
# ------------------------------------------------------------



# ************************************************************
# ************************************************************
# BOX Part
def callBox(target):
    signature('BOX')
    dim = getDimension.BoxPDB(target)
    print(dim.boxIt())
# ------------------------------------------------------------



# ************************************************************
# ************************************************************
# COLLECT Part 
def collectData(pH, target):
    signature("COLLECTING DATA") 
    charge = getCharge.getCharge(pH, target)
    print('Charge at pH {0}: {1}'.format(pH, charge[1])) 
    print()
# ------------------------------------------------------------ 



# ************************************************************
# ************************************************************
# Entry point of Python.  
try:
    # Get the argument
    pdbfile = sys.argv[1] 
    target = setup(pdbfile)

    # Download
    getPDB(target) 
    # OUT: 'PDB.pdb'

    # Correction
    # IN:  'PDB.pdb'
    callPDB2PQR(target) 
    # OUT: 'PDB-out.pqr'

    # Realignment
    # IN:  'PDB-out.pqr'
    callVMD(target)
    # OUT: 'PDB-out-new.pdb'

    # Charging
    # IN:  'PDB-out-new.pdb'
    callPropka3(target) 
    # OUT: 'PDB-out-new.pka'


    if 1:
        # pH=7
        parse(target)
        collectData(7, target)
    else:
        for i in range(1,15):
            collectData(i, target)
    print(callBox(target))



except IndexError:
    if len(sys.argv) == 1:
        print("No argument provided.")
        print("$ connect.py <PDB>") 

# End of the program.
# ************************************************************
# ************************************************************ 

Aus irgendeinem Grund wird der wget Befehl ausgeschrieben.. das sieht im Code normal aus.
Wie kann ich das machen? Wenn ein Framework (Flask) die beste Lösung ist, dann probiere ich es aus. Ich habe einfach etwas die Befürchtung, dass es dann nur noch etwas komplizierter wird.. deshalb habe ich bis anhin versucht es zu vermeiden. Ich bin mir auch bewusst, dass das Skript so wahrscheinlich alle Programmierpraktiken ausdolcht, Hinweise in dieser Richtung nehme ich gerne entgegen, allerdings haben diese für mich im Moment weniger Priorität.
Danke für weiteren Input.
[url=http://www.proandkon.com]proandkon.com[/url]
Antworten