Datei Upload (Flask, Python & Datenbank)

Django, Flask, Bottle, WSGI, CGI…
Antworten
Bindl
User
Beiträge: 70
Registriert: Donnerstag 27. Oktober 2016, 11:48

Hi zusammen,
ich möchte einen Dateiupload durchführen und stehe etwas auf dem Schlauch.

Hier mein Formular:
[codebox=html5 file=Unbenannt.html]<form method="post" enctype="multipart/form-data">
<label>Wählen Sie eine kompatible Datei aus<br>(*.doc,*.docx,*.xls,*.xlsx,*.ppt,*.pptx,*.pdf,*.css,*.html)
<input name="datei" type="file" size="50" accept="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/pdf,text/css,text/html">
</label>
<button>Uploaden</button>
</form>[/code]
Nur die angegebenen Datei sollen hochgeladen werden dürfen.

Hier die Datenbanktabelle:
file_name varchar(100)
file_author varchar(45)
file_type varchar(45)
file_upload datetime
data_size int(8)
file_file LONGBLOB

Hier der dazugehörige Python Code:

Code: Alles auswählen

@app.route('/upload', methods=['POST','GET'])
def upload():
    if request.method == 'POST':
        f = request.files['datei']
        c = g.con.cursor()
        c.execute("INSERT INTO file (file_file, file_name, file_author, file_type, file_upload, data_size) VALUES (%s, %s, %s, %s, %s, %s)",
                  (f, ))
        g.con.commit()
        c.close()
        return 'Datei erfolgreich hochgeladen'
f = request.files['datei'] -> das ist die hochgeladene Datei
session['username'] -> das ist der Autor
cur_time() -> Uploadzeitpunkt (Habe dazu eine eigene Funktion)

Nur wie komme ich nun an
- den Dateinamen (file_name)
- den Dateityp (file_type) z.B. pdf
- die Dateigröße (data_size)
???

Danke schonmal für die Hilfe im voraus
Bindl
User
Beiträge: 70
Registriert: Donnerstag 27. Oktober 2016, 11:48

Beim Formular fehlt natürlich noch
action="{{ url_for('upload') }}"

und auch
return render_template("upload.html")
bei dem python code
Bindl
User
Beiträge: 70
Registriert: Donnerstag 27. Oktober 2016, 11:48

Hi,
ist der Dateiname (file_name) über

Code: Alles auswählen

f.filename
zu bekommen?
Ich glaube ja, denn wenn ich

Code: Alles auswählen

return f.filename
mache bekomme ich diesen ausgegeben.
Bindl
User
Beiträge: 70
Registriert: Donnerstag 27. Oktober 2016, 11:48

Hi,
zur Dateigröße.

Kann ich das folgendermaßen machen?

Code: Alles auswählen

mess = request.files['datei'].read()
size = len(mess)
Leider kommt hierbei folgender Fehlertext:

'int' object is not callable

Wenn ich auch dem int einen string mache, gibt er mir die Dateigröße in Bytes wieder. Also auch das wäre gelöst
BlackJack

@Bindl: Statt zu glauben und zu raten kann man das auch einfach in der API-Dokumentation nachlesen. Da erfährst Du dann das `f` ein `FileStorage`-Objekt ist, und welche Attribute und Methoden das hat.

Wenn man im Inhaltsverzeichnis der Flask-Dokumentation nach „upload“ sucht, findet man im Kapitel „Patterns“ einen Abschnitt über das Hochladen von Dateien, der auch lesenswert ist. Unter anderem auch mit dem Hinweis niemals Benutzereingaben zu trauen — Du solltest also auch auf dem Server prüfen ob die Datei erlaubt ist oder nicht.

Wenn die Tabelle schon den Namen `file` hat, dann muss man das übrigens nicht als Präfix noch mal vor jeden Spaltennamen stellen. Denn der voll Qualifizierte Name `file.file_file` ist fast schon komisch. Auch `file.file` klingt noch eigenartig, denn das ist ja gar keine Datei, sondern der Datei*inhalt*. `file.content` ist da passender. Und warum ist bei `data_size` kein `file_`-Präfix? `file.size` würde da aber auch genügen. Bei `file_upload` sieht man dem Namen nicht an das da ein Datum hintersteckt. `uploaded_on` würde das besser ausdrücken.

Warum nennst Du denn Dateiinhalt `mess`? Ist der tatsächlich so fürchterlich unordentlich? ;-)
Bindl
User
Beiträge: 70
Registriert: Donnerstag 27. Oktober 2016, 11:48

Hi,
also die Datenbankbezeichnungen kommen nicht von mir.

mess sollte für messen stehen :)

Ich habe nun einiges durchgelesen, nur werde ich dadurch nicht so richtig schlau.

Das ist es was ich nun habe.
Kann jmd meinen jetzigen Python Code durchgehen und diesen möglicherweise verbessern?
Die Datenbank hat sich natürlich nicht geändert, als auch der HTML Part nicht.

Code: Alles auswählen

@app.route('/upload', methods=['POST','GET'])
def upload():
    if request.method == 'POST':
        file = request.files['datei']
        datname = str(file.filename)
        size = str(len(request.files['datei'].read()))
        #size = str(len(mess))
        type01 = datname
        type01 = type01.split(".")
        type02 = str(type01[-1])
        if datname != "":
            c = g.con.cursor()
            c.execute("INSERT INTO file (file_file, file_name, file_author, file_upload, file_type, data_szie) VALUES (%s, %s, %s, %s, %s, %s)",
                    (file.read(), datname, session['username'], cur_time(), type02, size))
            g.con.commit()
            c.close()
            flash('Die Datei '+datname+' wurde erfolgreich hochgeladen')
            return render_template("upload.html")
        else:
            flash('Sie haben keine Datei ausgewählt')
            return render_template("upload.html")
    return render_template("upload.html")
Ich bitte zu bedenken das ich in Sachen Flask, Python und auch SQL wahrlich kein Experte bin wie ihr es seit.
Vielen Dank für die Hilfe im voraus
BlackJack

@Bindl: Keiner der `str()`-Aufrufe ist sinnvoll, weil das entweder schon Zeichenketten sind, oder der Wert im Falle von `size` gar keine Zeichenkette sein sollte. Dass das beim einfügen in die Datenbank als Zeichenkette funktioniert, würde ich als glücklichen Zufall ansehen. Das Datenbankmodul hätte bei einer als INT deklarierten Spalte bei einer Zeichenkette als Wert auch meckern können.

Ebenfalls wundern würde mich wenn Du tatsächlich zweimal `read()` auf dem `FileStorage`-Objekt aufrufen kannst. Da dürften kein Dateiinhalt in der Datenbank landen.

`datname` ist kein guter Name. Warum bleibst Du dafür nicht bei `filename`? Keine Abkürzung und der übliche Name für allgemeine Dateinamen.

`type01` und `type02` sind ebenfalls keine guten Namen. Wenn man die Dateinamenserweiterung haben möchte, nimmt man `os.path.splitext()`. Was zum Beispiel auch mit Dateinamen ohne Endungen korrekt umgehen kann.

Falls hier der Einwand kommen sollte, dass das doch durch das HTML verhindert wird: Könnte sein, muss aber nicht, und vor allem garantiert Dir keiner das der Benutzer nicht was ganz anderes als einen Browser verwendet oder Dir/Deiner Anwendung freundlich gesonnen ist.

Der `file_type` ist als VARCHAR(45) deklariert — sicher das dort einfach die Dateiendung rein soll? Das wäre ja auch redundant, denn die ist im Feld `file_name` ja auch schon enthalten.

`con` und `c` sollten besser `connection` und `cursor` heissen.

Alle drei möglichen Pfade durch die Funktion enden mit einem ``return render_template("upload.html")`` → das sollte deshalb nur einmal am Ende der Funktion stehen.

Ich bin ja bekanntlich ein Fan von SQLAlchemy. Da könnte man den Models dann auch bessere und weniger redundante Namen für die Attribute geben, wenn vom Datenbankentwurf so schröckliche kommen. :-D
Bindl
User
Beiträge: 70
Registriert: Donnerstag 27. Oktober 2016, 11:48

Hi,

ohne jetzt frech wirken und zu wollen, und ich habe wirklich unmengen an Webseiten und Erklrungen durchgelesen, könnten Sie mir bei meinem Beispiel kurz zeigen wie ich das zu schreiben habe.
Ich komme beim besten Will nicht darauf wie ich die Beispiele auf meinen Fall umsetzen soll.
Ich wäre wirklich heilfroh wenn das möglich wäre.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Bindl: gute Feldnamen für Datenbanken sind noch wichtiger als bei Variablen, weil man das später nur schwer ändern kann. Je nachdem was man mit den Daten später machen will, ist es wichtig, zu testen, ob das, was Du da bekommst auch wirklich dem entspricht, was Du erwartest. Der Dateiname sollte von bösen Zeichen gesäubert werden, der Dateityp tatsächlich am Inhalt ermittelt werden. data_size ist unnötig, da die Länge der Daten jederzeit mit length(file_file) ermittelt werden kann.

Code: Alles auswählen

@app.route('/upload', methods=['POST','GET'])
def upload():
    if request.method == 'POST':
        file = request.files['datei']
        data = file.read()
        filename = os.path.basename(file.filename)
        if filename != "":
            cursor = g.connection.cursor()
            cursor.execute("INSERT INTO file (file_file, file_name, file_author, file_upload, file_type, data_size) VALUES (%s, %s, %s, NOW(), %s, %s)",
                    (data, filename, session['username'], os.path.splitext(filename)[1], len(data)))
            g.connection.commit()
            cursor.close()
            flash('Die Datei {} wurde erfolgreich hochgeladen'.format(file.filename))
        else:
            flash('Sie haben keine Datei ausgewählt')
    return render_template("upload.html")
Bindl
User
Beiträge: 70
Registriert: Donnerstag 27. Oktober 2016, 11:48

Hi,
vielen vielen Dank.
Den Dateityp habe ich folgendermaßen bekommen

Code: Alles auswählen

pfad, typ = os.path.splitext(file.filename)
typ ist dann der Dateytyp als String
Antworten