Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to render multiple files uploaded using a single form field via MultipleFileField. #123

Closed
puludisumpia opened this issue Dec 5, 2021 · 3 comments
Assignees
Labels
enhancement New feature or request

Comments

@puludisumpia
Copy link

Limitations

I have a question: I want to build an application with Flask and I use flask-file-upload to save the files in the database but I am limited by the possibilities of flask-file-upload. So here is the model:

@file_upload.Model
class Personne(db.Model):
    __tablename__ = "personnes"
    id = db.Column(db.Integer(), primary_key=True)
    last_name = db.Column(db.String(100), nullable=False)
    first_name = db.Column(db.String(100), nullable=False)
    sexe = db.Column(db.String(20), nullable=False)
    age = db.Column(db.Integer(), nullable=False)
    fonction = db.Column(db.String(50), nullable=False)
    file = file_upload.Column()
    date = db.Column(db.DateTime(), default=datetime.utcnow())

    def __str__(self):
        return f"{self.last_name} {self.first_name}"
SEXE = ["Femme", "Homme"]

class PersonneForm(FlaskForm):
    last_name = StringField(label="Votre nom", validators=[DataRequired()])
    first_name = StringField(label="Votre prénom", validators=[DataRequired()])
    sexe = SelectField(label="Sexe", choices=SEXE)
    age = IntegerField(label="Age", validators=[DataRequired()])
    fonction = StringField(label="Fonction", validators=[DataRequired()])
    file = MultipleFileField(label="Fichiers", validators=[DataRequired()])
    submit = SubmitField(label="Créer")

Here is the code that allows me to create the objects in the database and it works because I get to download the files and save them to the database.

@app.route("/create/", methods=("GET", "POST"))
def create_personne():
    form = PersonneForm()
    if request.method == "POST":
        last_name = form.last_name.data
        first_name = form.first_name.data
        sexe = form.sexe.data
        age = form.age.data
        fonction = form.fonction.data
        file = form.file.data

        new_personne = Personne(
            last_name=last_name,
            first_name=first_name,
            sexe=sexe,
            age = form.age.data,
            fonction=fonction,
        )
        
        file_upload.save_files(new_personne, files={"file": file})

        flash(f"{last_name} {first_name} ajouté avec succès", "success")

        db.session.add(new_personne)
        db.session.commit()

        return redirect(url_for("create_personne"))
    else:
        form = PersonneForm()
    return render_template('create_personne.html', form=form)

So my question is this:
how to render these files in the templates?
Because the explanations provided, it is necessary to use:

{{ file_upload.get_file_url(personne, filename='file') }}

This only works for one file. Me, I uploaded several files using the MultipleFileField of the Flask-WTF. Here is my the template of my form.

{% extends "base.html" %}

{% from 'bootstrap/form.html' import render_form %}
{% from 'bootstrap/utils.html' import render_messages %}

{% block main %}
    <div class="container">
        {{ render_messages() }}
        <form method="post" enctype="multipart/form-data">
            {{ render_form(form) }}
        </form>
    </div>
{% endblock %}

By reading the explanations, one of the solutions would be to create a field for each file to upload and then to render it one by one in the templates.

In the view:

file_upload.save_files(new_personne, files={"file1": file1, "file2": file2})

In the template:

<img src="{{ file_upload.get_file_url(personne, filename='file1') }}" class="img-fluid rounded-circle" alt="photo">

<img src="{{ file_upload.get_file_url(personne, filename='file2') }}" class="img-fluid rounded-circle" alt="photo">

Is this the only solution? If not, can you tell me how to render them with uploaded files using a single MultipleFileField.

Thanks for your help.

@joegasewicz joegasewicz self-assigned this Dec 6, 2021
@joegasewicz joegasewicz added the enhancement New feature or request label Dec 6, 2021
@joegasewicz joegasewicz removed their assignment Dec 6, 2021
@joegasewicz
Copy link
Owner

joegasewicz commented Dec 6, 2021

Hi @puludisumpia,

Thanks for opening this issue & you're right that your example above is the current solution. I think the feature should be able to just return a list of file names stored via the MultipleFileField.

Linked to Multiple file uploads #82

@joegasewicz joegasewicz self-assigned this Dec 6, 2021
@joegasewicz
Copy link
Owner

joegasewicz commented Dec 7, 2021

I have looked into accommodating multiple files & because this library is reliant on statically declared model attributes it would seem an anti pattern that would go against this aspect of the library design.

A solution is then to create a model with a maximum amount of images as individual attributes:

@file_upload.Model
class UserModel(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)

    image_one = file_upload.Column()
    image_two = file_upload.Column()
    image_three = file_upload.Column()
    image_four = file_upload.Column()

Then in your view handler use Flask's request.files.getlist():

user_model = UserModel(name="Joe")
images = request.files.getlist("images")
files_dict = {
            "image_one": images[0],
            "image_two": images[1],
            "image_three": images[2],
            "images_four": images[3],
        }
 file_upload.add_files(user_model, files=images_dict)

If the file list size is dynamic then get the size of the list and use the python range function to loop over your model's file attributes:

user_model = UserModel(name="Joe")
images_dict = {}
image_name_list = ("image_one", "image_two", "image_three", "image_four")
images = request.files.getlist("images")
for image_item, index in zip(image_name_list, range(len(images))):
         if index == 0 or index:
                images_dict[image_item] = images[index]

file_upload.add_files(user_model, files=images_dict)

I'm going to close this issue as we have a work around & i have explained why we can't accommodate this feature, thanks.

@puludisumpia
Copy link
Author

Thanks, maybe in the future you could allow to manage the uploaded files with flask-file-upload with Flask-Admin as well. Thank you for your reply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants