Skip to content

Sending a profile picture from react frontend to flask-restful backend and storing

I want to store profile pictures on the file system (images/{username}_pfp.{extension} and store its location in the database as a string. My frontend react code is

const Signup = () => {
    const [state, setState] = useState({
        email: "",
        password: "",
        confirmPassword: "",
        username: "",
        profile_picture: "",
    });

    const navigate = useNavigate();

    const onSubmit = (e) => {
        e.preventDefault();
        console.log(state.email, state.password, state.confirmPassword, state.username);

        if (state.password === state.confirmPassword) {

            getData('http://localhost:5000/users')
            .then(data => {
                console.log(data);
                let userExists = false;
                for (let i = 0; i < data.length; i++) {
                    if (data[i].email === state.email) {
                        userExists = true;
                    }
                    if (data[i].username === state.username) {
                        userExists = true;
                    }
                }
                if (userExists) {
                    alert("Email or Username already exists");
                } else {
                    const data = new FormData();
                    for(var x = 0; x<state.profile_picture.length; x++) {
                        data.append('file', state.profile_picture[x])
                    }
                    postData('http://localhost:5000/users', {
                        email: state.email,
                        password: state.password,
                        name: state.username,
                        profile_picture: data
                    })
                    .then(data => {
                        console.log(data);
                        alert("User created successfully");
                        navigate('/');
                    })
                    .catch(err => {
                        console.log(err);
                        alert("Error creating user");
                    });
                }
            })
            .catch(err => {
                console.log(err);
                alert("Error creating user");
            });

        };
    };
    return (
        <>
        <Header />
    <div className="container">
    <Form>
    //creating other values
    <Form.Group className="mb-3" controlId="formFile">
        <Form.Label>Upload Profile Picture (image format must be png, jpg, or jpeg).</Form.Label>
        <Form.Control type="file" onChange={e => setState(prevState => { return {...prevState, profile_picture: e.target.value}})}/>
    </Form.Group>

    <Button variant="primary" type="submit" onClick={onSubmit}>
        Submit
    </Button>
    </Form>
</div>
</>
    );
};

and my flask backend code is

class UserListResource(Resource):
    def get(self):
        users = User.query.all()
        return users_schema.dump(users)

    def post(self):
        received_file = request.json['profile_picture']
        filename = request.json['name'] + '_pfp' + received_file.filename.split('.')[1]
        filename = secure_filename(filename)
        filename = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        received_file.save(filename)
        new_user = User(
            email=request.json['email'],
            password=request.json['password'],
            name=request.json['name'],
            profile_picture=filename
        )
        db.session.add(new_user)
        db.session.commit()
        return user_schema.dump(new_user)


api.add_resource(UserListResource, '/users')

I have gotten the bits of code relevant to this from multiple sources (Flask – Get the name of an uploaded file minus the file extension, https://flask.palletsprojects.com/en/2.1.x/patterns/fileuploads/). When the image is sent to the backend, it gives an AttributeError: dict object has no attribute filename in the backend terminal. How can I get this to work? Have I missed something?

Answer

So. I tried some more stuff and I thought that I would reply to this to document it for others. My frontend code ended up with me sending two separate post requests, one to post the json data, and the other for the image.

By the way, frontend code uses react-bootstrap, but the principle is the same. Frontend code:

const Signup = () => {
    const [state, setState] = useState({
        email: "",
        password: "",
        confirmPassword: "",
        username: "",
    });
    const [profile_picture, setProfile_picture] = useState({});

    const navigate = useNavigate();

    const uploadedImage = (e) => {
        console.log(e.target.files[0]);
        const formData = new FormData();
        formData.append('profile_picture', e.target.files[0]);
        console.log(formData);
        setProfile_picture(formData);
    };

    const onSubmit = (e) => {
        e.preventDefault();
        console.log(state.email, state.password, state.confirmPassword, state.username);

        if (state.password === state.confirmPassword) {

            getData('http://localhost:5000/users')
            .then(data => {
                console.log(data);
                let userExists = false;
                for (let i = 0; i < data.length; i++) {
                    if (data[i].email === state.email) {
                        userExists = true;
                    }
                    if (data[i].username === state.username) {
                        userExists = true;
                    }
                }
                if (userExists) {
                    alert("Email or Username already exists");
                } else {
                    postData('http://localhost:5000/users', {
                        email: state.email,
                        password: state.password,
                        name: state.username,
                    }) 
                    .then(data => {
                        console.log(data);
                        alert("User created successfully");
                        fetch('http://localhost:5000/users/' + data.name, {
                            method: 'POST',
                            mode: 'cors',
                            cache: 'no-cache',
                            cors: 'cors',
                            redirect: 'follow',
                            referrerPolicy: 'no-referrer',
                            body: profile_picture,
                            })
                            .then(response => {
                                console.log(response);
                            })

                        navigate('/');
                    })
                    .catch(err => {
                        console.log(err);
                        alert("Error creating user in post thing");
                    });
                }
            })
            .catch(err => {
                console.log(err);
                alert("Error creating user");
            });

        };
    };
    return (
        <>
        <Header />
    <div className="container">
    <Form>
    <Form.Group className="mb-3" controlId="formBasicEmail">
        <Form.Label>Email address</Form.Label>
        <Form.Control type="email" placeholder="Enter email" value={state.email} onChange={e => setState(prevState => { return {...prevState, email: e.target.value}})}/>
        <Form.Text className="text-muted">
        We'll never share your email with anyone else.
        </Form.Text>
    </Form.Group>

// making more input fields

    <Form.Group className="mb-3" controlId="formFile">
        <Form.Label>Upload Profile Picture (image format must be png, jpg, or jpeg).</Form.Label>
        <Form.Control type="file" onChange={e => uploadedImage(e)}/>
    </Form.Group>

    <Button variant="primary" type="submit" onClick={onSubmit}>
        Submit
    </Button>
    </Form>
</div>
</>
    );
};

And the backend code for just the image is

    def post(self, user_name):
        current_user = User.query.get_or_404(user_name)
        received_file = request.files['profile_picture']
        filename = current_user.name + '_pfp' + '.' + received_file.filename.split('.')[1]
        filename = secure_filename(filename)
        filename = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        img_file = Image.open(received_file.stream)
        img_file.save(filename)
        current_user.profile_picture = filename
        db.session.commit()
        return user_schema.dump(current_user)

The first post request just defines the profile_picture field as an empty string. Of course, in the user delete function add some lines to delete the profile picture file:

    def delete(self, user_name):
        user = User.query.get_or_404(user_name)
        pfp_fname = user.profile_picture
        os.remove(pfp_fname)
        db.session.delete(user)
        db.session.commit()
        return '', 204

Hope this helps, feel free to comment if you need clarification :). Also, postData and getData functions in comment because they are just fetch requests.