0

I am making a homework system on my site which should include the fact that the teacher will be able to create a task with an attachment, but I don't know how to break up the file that the teacher sends to the file name and file data in the table in the mysql database. I am programming it in vanilla flask, could someone please advise me how to do it, I would be grateful. So far I have this code which reports this error:

AttributeError AttributeError: 'str' object has no attribute 'filename'

Sample code where I create a homework with an attachment, but it is not working:

@app.route("/create_homework", methods=["POST", "GET"])
@isLoggedIn
def create_homework():
    if session["group_id"] != Group.REDITEL and session["group_id"] != Group.UCITEL:
        abort(401, description="Nemáš dostatečné oprávnění")
    
    form = HomeworkForm()
    if request.method == "POST":
        connection = mysql.connection
        cursor = connection.cursor(prepared=True)

        hmTitle = request.form.get("hmTitle")
        description = request.form.get("description")
        date_of_completion = request.form.get("date_of_completion")
        classname = session["class"]
        school = session["school"]

        if session["class"] == None:
            return "Učitel není přiřazen k žádné třídě"

        if(request.method == "POST"):
            usersName  = loadUserNameFromClass(classname, school)
            for userName in usersName:
                if form.validate_on_submit():
                    attachment_data = form.attachment.data
                    attachment_name = secure_filename(attachment_data.filename)

                    file_stream = Binary(attachment_data.read(1024))
                    attachment_data.save(file_stream)

                    cursor.execute("INSERT INTO homeworks (id, userName, homework_title, homework_description, school, class, date_of_completion, status, attachment_name, attachment_data) VALUES (0, %s, %s, %s, %s, %s, %s, 0, %s, %s);", (userName, hmTitle, description, school,classname, date_of_completion, attachment_name, file_stream, ))
                clearForm(form)
                connection.commit()
                return redirect(url_for("homeworks"))
            else:
                return redirect(url_for("homeworks"))
            cursor.close()
            connection.close()
            
    return render_template("profile/homework/create_homework.html", form=form)

Sample code where I select the attachment so that the user can download it:

@app.route("/download_file/<int:id>")
@isLoggedIn
def download_file(id):
    connection = mysql.connection
    cursor = connection.cursor(prepared=True)

    file_name = get_file_name(cursor, id)
    file_data = get_file_data(cursor, id)

    return send_file(BytesIO(file_data), attachment_filename=file_name, as_attachment=True)

def get_file_name(cursor, id):
    cursor.execute("SELECT attachment_name FROM homeworks WHERE id=%s;", (id, ))
    output = cursor.fetchone()

    return output[0]

def get_file_data(cursor, id):
    cursor.execute("SELECT attachment_data FROM homeworks WHERE id=%s;", (id, ))
    output = cursor.fetchone()

    return output[0]

HomeworkForm code:

class HomeworkForm(FlaskForm):
    hmTitle = TextField('Titulek', [validators.DataRequired(), validators.Length(min=1, max=200)])
    description = TextAreaField("Popis", [validators.DataRequired()])
    attachment = FileField('Příloha', validators=[FileRequired(), FileAllowed(['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar'], 'Povolené typy souborů: pdf, doc, docx, xls, xlsx, ppt, pptx, zip, rar')], id='attachment')   
    date_of_completion = DateTimeLocalField('Datum odevzdání', format='%m/%d/%y', validators=[validators.DataRequired()])
    submit = SubmitField("Vytvořit")

HTML code:

<form method="POST" action="/create_homework" class="create-homework" enctype="multipart/form-data">
        {{ form.csrf_token }}

        <strong>{{ form.hmTitle.label(class="react-to-dark") }} </strong>
        {{ form.hmTitle(id="hmTitle", class="react-to-dark") }}

        <br>
        <strong>{{ form.description.label(class="react-to-dark") }} </strong>
        {{ form.description(id="description", class="react-to-dark") }}
        <br>
        <strong>{{ form.attachment.label(class="react-to-dark") }} </strong>
        {{ form.attachment(id="attachment", class="react-to-dark") }}
        <br>
        <strong>{{ form.date_of_completion.label(class="react-to-dark") }} </strong>
        {{ form.date_of_completion(id="date_of_completion", class="react-to-dark") }}

        <br>
        {{ form.submit(id="submit", class="react-to-dark") }}

        <!--<input type="file" name="attachment">-->
        <br>
        {%if success == False%}
            <p style="color: red;">Zpráva nebyla zaslána</p>
        {%endif%}
        <hr>
        <button class="main-btn" href="/homeworks" id="return-back">Vrátit zpátky</button>
    </form>
medmejd123
  • 13
  • 3

1 Answers1

0

You haven't provided the definition of your HomeworkForm() so it's hard to provide an answer but I can tell you why you're seeing that exception.

On the line if attachment(attachment.filename): the attachment variable is a string and doesn't have the property 'filename' on it.

You need to change the line attachment = request.form.get("attachment"). If you are using WTForms FileField, this returns the path to a file, which is why you're getting a string here and seeing that exception.

Here's a tutorial that goes into detail on how to use flask and WTForms to upload a file.

Edit: Looking at the documentation for Flask-WTForms's FileField

To get the data of the file:

if form.validate_on_submit():
    homework_data = form.attachment.data
    homework_filename = secure_filename(homework_data.filename)

The docs also mention making sure you set the encoding type for the form in your html:

<form method="POST" enctype="multipart/form-data">
        
     
barryodev
  • 509
  • 3
  • 11
  • I edited the post and added the homework form code. I don't want what you are referring to, I need to insert the file into the database, not into the directory in the project. @barryodev – medmejd123 Jul 12 '22 at 14:37
  • @medmejd123 updated my answer to reflect what the flask-wtforms docs suggest. – barryodev Jul 12 '22 at 15:03
  • When I tried what you wrote here, it no longer gives me an error, but it gets to the redirect but nothing is saved in the database, do you know what it could be? @barryodev – medmejd123 Jul 12 '22 at 17:18
  • I would put a break point in the line homework_data = form.attachment.data and see if you can access the bytes data of the file there, going by flask-wtf docs it's a [FileStorage object](https://tedboy.github.io/flask/generated/generated/werkzeug.FileStorage.html). Here's a [good answer on the topic.](https://stackoverflow.com/questions/20015550/read-file-data-without-saving-it-in-flask) read down through the answers as it's an old one from 2013 at the top. Once you have the bytes stream, sending it to your database is another question. – barryodev Jul 12 '22 at 18:45
  • I tried something but I don't know if I used it correctly, see post update. Btw it doesn't throw me any error when I have it there. @barryodev – medmejd123 Jul 12 '22 at 19:09
  • You're issue now is that you're still passing the Flask FileStorage variable into your SQL insert command rather than the byte stream from the file object. When you call `attachment_data.save(Binary(attachment_data.read(1024)))` you're not changing anything on the attachment_data variable here, just saying save data to this temporary binary stream `Binary(attachment_data.read(1024))` that you're not keeping a reference too. I would try something like `file_stream = Binary(attachment_data.read(1024))` and `attachment_data.save(file_stream)` and pass file_stream to your SQL. – barryodev Jul 13 '22 at 09:44
  • I updated the code in the post again, but when I have the file_stream there and save it in it, nothing is still sent to the database @barryodev – medmejd123 Jul 13 '22 at 09:53
  • Have you looked at file_stream in the debugger to verify that it contains binary data? At this point I would do two things so you can isolate where the problem is occurring. First write a script that's only opens a binary stream from text file saves it to the database and then selects that binary data from the table and saves it to another file. Do the same for file upload. Write a minimal flask app that has a form with a single FileField and save that field to a directory on your local machine. Get both those working and you'll know what you need to do to get your homework app working. – barryodev Jul 13 '22 at 09:58
  • And can't there be an error somewhere on the side of the mysql database, i.e. the insert table, I have these two values ​​as attachment_name and attachment_data in the table when I have a varchar(64) cell set for the value name and a blob for data? @barryodev – medmejd123 Jul 13 '22 at 10:40
  • attachment_data is a variable that's a wrapper to the binary data for the file, not the binary data itself. I would write a "helloworld" version of saving a file to the database, and separately a flask app that accepts a file upload and saves it to disk. That way you isolate where your code is failing. – barryodev Jul 13 '22 at 10:55