Array aus Form

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
x-herbert
User
Beiträge: 59
Registriert: Mittwoch 27. November 2002, 20:52

Moin,

ich starte gerade mit der Python-CGI programmierung und habe schon die ein oder andere 500er Seite gesehen... ;-)

Bisher habe ich die Websachen in PHP gestrickt, was hier aber aus verschiedenen Gründen nicht geht.

Ich probiere mich gerade an einen File-Upload-Script für mehrere Dateien.

Für PHP kann man Daten als Array übergeben, wenn name="meinname[]" ist . Dafür braucht man dem Wert name nicht unterschiedliche Werte zuweisen (fix oder dynamisch).

Frage:
kann ich auf die einzelnen file-inputs "eleganter" zugreifen als ich das probiere...

Kann mir jemand eine gute Doku zu Python-CGI geben? Was im "18.2 cgi -- Common Gateway Interface support." steht ist recht dürftig finde ich.

Das Form:

Code: Alles auswählen

<form id="uploadForm" name="uploadForm" method="post" action="/cgitest.py" enctype="multipart/form-data">
  <p class="Stil1">Upload-Test für Files</p>
  <p class="Stil1">=>Python </p>
  <p>
    <input id="file" class="fileupload" size="30" type="file" name="file" />
  </p>
  <p>
    <input id="file" class="fileupload" size="30" type="file" name="file" />
  </p>
  <p>
    <input id="file" class="fileupload" size="30" type="file" name="file" />
  </p>
  <p>
    <input type="submit" name="Submit" value="Senden" />
  </p>
</form>
Für die Auswertung folgender Test (siehe #16 fieldStorage[ key ]):

Code: Alles auswählen

#!/usr/bin/python

import os, sys
import cgi
import cgitb

cgitb.enable()

def cgiFieldStorageToDict( fieldStorage ):
		"""Get a plain dictionary, rather than the '.value' system used by the cgi module."""
		params = {}
		for key in fieldStorage.keys():
				if isinstance(fieldStorage[ key ], list):
						for i in range (len(fieldStorage[ key ])):
								params[ i ] = fieldStorage[ key ][i]
								# typ, name, wert...
				else:
					params[ key ] = fieldStorage[ key ].value
		return params

if __name__ == "__main__":
		dict = cgiFieldStorageToDict( cgi.FieldStorage() )
		print "Content-Type: text/plain"
		print
		print dict
Gruss x-herbert
gruss x-herbert
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Da du ja schon Erfahrung mit der Web Programmierung hast, würde ich dir raten, nimm ein Framework, siehe: [wiki]Web-Frameworks[/wiki]

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Ich verstehe die Funktion nicht so ganz. Wenn es mehr als einen Wert in `FieldStorage` gibt der eine Liste ist, dann überschreiben die sich gegenseitig die "Zahl-Schlüssel" im Ergebnisdictionary.

In der Doku gibt's etwas zum Thema Einzelwerte vs. Listen, und das man entweder `getfirst()` nehmen soll um einen Einzelwert abzufragen, oder `getlist()` wenn man eine Liste möchte. Beide Methoden funktionieren in jedem Fall korrekt. Das heisst, `getfirst()` liefert bei Einzelwerten den Einzelwert und bei Listen das erste Element. Und `getlist()` liefert bei Listen die Liste und bei Einzelwerten den Einzelwert in einer Liste.

Die elegante Methode wäre also ``field_storage.getlist('file')``.

Namenskonvention bei Funktionsnamen und Attributen ist in Python übrigens kleine_buchstaben_und_unterstriche. GROSSBUCHSTABEN für Konstanten und MixedCase für Klassen.
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Ich habe letze Woche auch ein PythonCGI Upload Script geschreiben, mich wundert das du allen <input type=file> den gleichen Name gegeben hast.
Ich habe sie nummeriert von 1 bis 5 und greife dann auf sie zu, wenn du das machst und BlackJacks beschreibene Methode dann sollte es funktionieren.

In deiner Funktion 'cgiField....' sollte:

Code: Alles auswählen

params[i] = ...
wohl eher:

Code: Alles auswählen

params[key] = ...
heißen.

Ich bezweifel jedoch das `fieldStorage[ key ]` funktioniert, ich kann mich schwach erinnern das ich das ganz anders gemacht habe.
Ich behalten den Thread mal im Auge und poste ggf. meinen Lösungsweg, wenn ich um 16Uhr zuhause bin.

PS: Im wxPython Forum habe ich das gestern auch versprochen, habe es gestern aber nicht mehr an den Rechner geschafft, heute wird das was, bin mir ganz sicher :-D
x-herbert
User
Beiträge: 59
Registriert: Mittwoch 27. November 2002, 20:52

Thema Namenskonvention: Grundgerüst habe ich von http://aspn.activestate.com/ASPN/Cookbo ... cipe/81547

Den gleichen Namen kann man anscheinen vergeben - für PHP muss man an den Namen eine Klammer anhängen also z.B. name="filename[]" und schon hat man die Sachen in PHP als Array (= Liste) - braucht man z.B. auch, um mehrere Werte von Checkoxen einzusammeln.

... ich dachte bisher die eckige Klammer wäre "POST-Standard" für Array isses aber anscheinend nicht und nur eine Hilfe für PHP ein Array/Liste zu erkennen.

Ihr könnt ja mal cgi.FieldStorage() ausgeben lassen - da sieht man die Ineinanderschachtelung der Werte.

@BlackJack: field_storage.getlist('file') gibt nur die Werte zurück - ich kann nicht mehr Prüfen, ob der Typ = File ist

@Sr4l: so habe ich es z.z. - man muss natürlich sicherstellen, dass nix doppelt ist... bei mir landen die Dateien in einem "Korb" aus dem Sie vor dem Upload auch gelöscht werden können - die Namen sind name="file_irdnedwaseindeutiges" und suche anschließend nach "file":

Code: Alles auswählen

		for key in form.keys():
				if string.find(key,"file") >= 0:				
						fi = form[key]
						fn = fi.filename
						if fn:
								writeFileData(upload_path, fi)
Mit meinem Script vom ersten Eintrag kommt beim Print:

Code: Alles auswählen

{0: FieldStorage('file', '', ''), 1: FieldStorage('file', '', ''), 2: FieldStorage('file', '', ''), 'Submit': 'Senden'}
(hier ohne Dateiangabe daher "Name" und "Wert" leer)
gruss x-herbert
BlackJack

Wenn man alle Datei-Upload-Felder im Form 'file' nennt, kann man das auch so machen:

Code: Alles auswählen

def main():
    form = cgi.FieldStorage()
    
    print 'Content-Type: text/plain'
    print
    for upload in form['file']:
        if upload.file and upload.filename:
            print 'Do something with file (name=%r)' % upload.filename
            # 
            # `upload.file` auslesen und irgendwo speichern.
            #
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Ich habe folgendes geschrieben:

Code: Alles auswählen

    def ParseInput(self):
        """
        For simple use: input string pack into a handy dict.
        """
        get = {}
        fs = cgi.FieldStorage()
        for x in range(len(fs)):
            value = []
            # 
            if fs.list[x].value.isdigit():
                value.append(int(fs.list[x].value))
            else:
                value.append(fs.list[x].value)
            #
            value.append(fs.list[x].filename)
            get[fs.list[x].name] = value
        return get
BlackJack

Also für Zeile 7 sollte man Dich teeren und federn und aus der Stadt jagen. :twisted: Und warum ist das keine Funktion? `self` wird nirgends verwendet. Dann ist `FieldStorage.list` nicht in der Doku. Ist also vielleicht nicht garantiert, dass es dieses Attribut immer gibt. Die Funktion scheint auch nicht damit klar zu kommen, wenn man verschiedenen Feldern den gleichen Namen gibt!?
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Bitte heute nicht noch mehr bestrafen :-)
BlackJack hat geschrieben:Und warum ist das keine Funktion? `self` wird nirgends verwendet.
Ich habe das einfach aus einer Klasse rauskopiert.
BlackJack hat geschrieben:Dann ist `FieldStorage.list` nicht in der Doku. Ist also vielleicht nicht garantiert, dass es dieses Attribut immer gibt.
(Sehr schwache Antwort:) auf meinem Server gibt es sie (noch) :-)
BlackJack hat geschrieben:Die Funktion scheint auch nicht damit klar zu kommen, wenn man verschiedenen Feldern den gleichen Namen gibt!?
Stimmt. Deshalb war ich so überrascht von deinem Beispiel, wo das möglich ist.

Es liegen zwar 14Minuten zwischen unseren Beiträgen aber ich habe dein's nicht gelesen bevor ich meins abgeschickt habe, das liegt daran das ich immer zichtausend Tabs auf mache die ich dann abarbeite ;-)
BlackJack

Sr4l hat geschrieben:
BlackJack hat geschrieben:Und warum ist das keine Funktion? `self` wird nirgends verwendet.
Ich habe das einfach aus einer Klasse rauskopiert.
Das habe ich mir schon gedacht. Die Frage ist, warum eine Funktion als Methode in einer Klasse steckt.
x-herbert
User
Beiträge: 59
Registriert: Mittwoch 27. November 2002, 20:52

@BlackJack

... werde ich mal testen - wird warscheinlich erst was am WE...

Danke schonmal - Feedback kommt noch!
gruss x-herbert
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Da ich mir gerade ein FileUpload Script gebastelt habe kann ich es wohl auch posten, ich vermute mal es hilft dir:

Code: Alles auswählen

#!/usr/bin/python
from __future__ import with_statement
"""A simple cgi file upload script"""

UPLOAD_DIR = "/home/veers/shared/incoming/webupload"
FORM_FIELD_PREFIX = "file"

import cgi
import cgitb; cgitb.enable()
import os
import sys

form = cgi.FieldStorage()
uploaded = False
for field in form:
    if field.startswith(FORM_FIELD_PREFIX):
        fileitem = form[field]
        if not fileitem.filename or os.sep in fileitem.filename:
            continue
        upload_path = os.path.join(UPLOAD_DIR, fileitem.filename)
        if os.path.exists(upload_path):
            n = 0
            while os.path.exists("%s.%i" % (upload_path, n)):
                n += 1
            upload_path = "%s.%i" % (upload_path, n)
        with open(upload_path, "wb") as f:
            chunk = fileitem.file.read(65535)
            while chunk:
                f.write(chunk)
                chunk = fileitem.file.read(65535)
        uploaded = True
# ...
x-herbert
User
Beiträge: 59
Registriert: Mittwoch 27. November 2002, 20:52

Hallo nochmal ... habs natürlich doch noch getestet...

Schlüsselzeile war

Code: Alles auswählen

for upload in form['file[]']:
- mit Klammer, damit PHP-Kompatibilität bestehen bleibt...

Danke nochmal - auch an veers => sieht gut aus...

Ich greife jedoch vorher den sys.stdin ab und mache ein Polling für Ajax

gruss x-herbert
gruss x-herbert
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

x-herbert hat geschrieben:Ich greife jedoch vorher den sys.stdin ab und mache ein Polling für Ajax
Ist das wirklich nötig? Ich habe es gerade getestet und es scheint als würde der stdin erst beim auslesen des Files mit read() gelesen werden. :wink:

edit:
Naja nicht ganz es wird eine temporäre Datei angelegt :/
x-herbert
User
Beiträge: 59
Registriert: Mittwoch 27. November 2002, 20:52

@VEERS

o.k. ich bin mir nicht sicher wie es bei Python läuft - bei PHP funktioniert i.e. die Sache so: PHP nimmt Datenstrom entgegen und schreibt eine temporäte Datei in (je nach php.ini) /temp mit (eigenem) temporären Dateinamen. Wenn Datei vollständig, kann - solange PHP-Script läuft - die Datei nach Eigenschaften abgefragt werden und unter realem Dateinamen an beliebigen Ordner im eigenen Webspace verschoben werden. Ist Script abgelaufen wird temp. Datei automatisch gelöscht.

ergo => ich komme nur sehr schlecht an die temporäre Datei um zwischendurch zu gucken, wieviel den schon da ist...

Lösung => StdIn abfangen und selber bestimmen wohin mit temporärer Datei und abfragen, wieviel (kB, %, Zeit) von CONTENT_LENGHT angekommen ist...

Wenns für Python "elegantere" Lösungen gibt - her damit :-)

gruss x-herbert
gruss x-herbert
x-herbert
User
Beiträge: 59
Registriert: Mittwoch 27. November 2002, 20:52

@VEERS => Nachtrag

was bezweckst Du mit

Code: Alles auswählen

... os.sep in fileitem.filename
??
gruss x-herbert
BlackJack

Vielleicht solltest Du mal einen Blick in das `cgi`-Modul werfen und dort insbesondere in die `FieldStorage`-Klasse. Die `make_file()`-Methode könnte interessant sein. Die ist im Docstring als "überschreibbar" beschrieben, wenn man selber festlegen möchte wo die temporären Dateien landen.

Auf jeden Fall kannst Du in dem Modul genau sehen was Python bei CGI macht und wie es gemacht wird.

Zum `os.sep`: Ich denke veers möchte verhindern, dass Dateinamen mit Pfadtrennern gespeichert werden, weil die entweder in einem Unterverzeichnis landen würden, was es nicht gibt, oder aber ein böser Mensch mit so etwas wie '../../index.html' versucht irgendwelche Dateien der Website zu überschreiben.
x-herbert
User
Beiträge: 59
Registriert: Mittwoch 27. November 2002, 20:52

@BlackJack

ich würde ja mit dem os.sep nicht so rigoros umgehen und gleich ein continue einbauen - kann ja auch mal am Browser liegen... vielleicht ist das rausfischen des eigentlichen Dateinames besser (os.path.basename)...

das cgi-modul werde ich mir mal ansehen - ich hatte bisher ein perl-Script in Python nachgebastelt da Perl (warum auch immer) nach ca 16MB Upload ausstieg - Python schaffte über 65MB...

Für das Polling gibt es natürlich verschiedene Möglichkeiten wie z.B. während des Einlesens des StdIn eine Statusdatei schreiben mit kB, Zeit, % usw. oder die Sachen "von außen" über ein anderes Script anhand der Temp-Datei "beobachten" - hat beides sein "für-und-wider".
gruss x-herbert
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

BlackJack hat geschrieben:Zum `os.sep`: Ich denke veers möchte verhindern, dass Dateinamen mit Pfadtrennern gespeichert werden, weil die entweder in einem Unterverzeichnis landen würden, was es nicht gibt, oder aber ein böser Mensch mit so etwas wie '../../index.html' versucht irgendwelche Dateien der Website zu überschreiben.
Ja, hatte ich damit vor. Ist aber anscheinend nicht nötig. Ich versuche noch herauszufinden wo das gemacht wird.

@x-herbert,
Ich verwende nun basename das ist wohl schöner. Sowie eine Ausnahme für "bestimmte" Browser welche meinen in filename den ganzen Pfad stecken zu müssen.

Zu deinem 65 mb, ich konnte Files bis zu 2gb Problemlos hochladen ;)
BlackJack

Ich weiss es nicht genau, aber ich vermute mal das ist auch keine Beschränkung von Perl sondern irgendwo eine Einstellung, vielleicht sogar beim Webserver, um zu verhindern das jemand von aussen mit überdimensionalen Uploads die Platte zumüllen kann.
Antworten