[Vuejs]-How to save form data in flask with Vue.js

0👍

My example uses a database to store the entries.
To realize this, the Flask SQLAlchemy, Flask-Marshmallow, Marshmallow-SQLAlchemy and Webargs are used.
These are installed using this command.

pip install flask-sqlalchemy flask-marshmallow marshmallow-sqlalchemy webargs

Once all entries have been made and the Add button is pressed, they are sent to the server via AJAX in JSON format. Here all data is automatically deserialized, validated again and added to the database. The database entry is then converted back into JSON and sent back to the client, where it is added to the list of students.

If the page is reloaded, all entries are loaded from the database, serialized and sent to the client.

Flask (./app.py)
from flask import Flask, jsonify
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from webargs import fields, validate, ValidationError
from webargs.flaskparser import use_args

app = Flask(__name__)
# Database configuration
app.config.from_mapping(
    SQLALCHEMY_DATABASE_URI='sqlite:///demo.db'
)
db = SQLAlchemy(app)
ma = Marshmallow(app)

# Database model with all required columns
class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False, unique=True)
    partner = db.Column(db.String, nullable=False)
    can_swim = db.Column(db.Boolean, nullable=False, default=False)

# Validation of the uniqueness of the name.
def validate_uniqueness(val):
    # If a student with that name already exists, throw an error.
    if Student.query.filter_by(name=val).first():
        raise ValidationError('Name already exists.')

# Scheme for serialization and validation
class StudentSchema(ma.SQLAlchemyAutoSchema):
    name = ma.String(required=True, 
        validate=[
            validate.Length(min=2, max=8), 
            validate.Regexp(r'^[A-Za-z]+[0-9]*$'), 
            validate_uniqueness
        ]
    )
    partner = ma.String(required=True, 
        validate=[
            validate.Length(min=2, max=8), 
            validate.Regexp(r'^[A-Za-z]+[0-9]*$')
        ]
    )
    can_swim = ma.Boolean(required=True)

    class Meta:
        model = Student
        load_instance = True

# Creation of the database. 
# This can also be done via the flask shell.
with app.app_context():
    db.drop_all()
    db.create_all()

# Deliver the VueJS application as a static page.
@app.route('/')
def index():
    return app.send_static_file('index.html')

# Creation of a new database entry
@app.post('/students/new')
@use_args(StudentSchema(), location='json')
def students_create(student):
    # Save the data to the database.
    db.session.add(student)
    db.session.commit()
    # Convert the data to JSON.
    student_schema = StudentSchema()
    student_data = student_schema.dump(student)
    return jsonify(student_data)

# Query and delivery of all existing database entries.
@app.route('/students')
def students():
    # Query all students from the database.
    students = Student.query.all()
    # Convert the data to JSON.
    student_schema = StudentSchema(many=True)
    student_data = student_schema.dump(students)
    return jsonify(student_data)

# Error handler for failed validation.
@app.errorhandler(422)
@app.errorhandler(400)
def handle_error(err):
    headers = err.data.get("headers", None)
    messages = err.data.get("messages", ["Invalid request."])
    if headers:
        return jsonify({"errors": messages}), err.code, headers
    else:
        return jsonify({"errors": messages}), err.code

So that the syntax of VueJS does not collide with that of Jinja2, I deliver the application as a static page from the static folder, bypassing the template engine.

HTML (./static/index.html)
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Index</title>
</head>
<body>

    <div id="app">
        <form @submit.prevent="onSubmit">
            <div>
                <label for="name">Name</label>
                <input ref="name" v-model="student.name" id="name" autofocus />
                <span v-if="student.name && !isValidName">Name invalid</span>
            </div>
            <div>
                <label for="partner">Partner</label>
                <input ref="partner" v-model="student.partner" id="partner" />
                <span v-if="student.partner && !isValidPartner">Partner invalid</span>
            </div>
            <div>
                <input type="checkbox" v-model="student.can_swim" id="can_swim" />
                <label for="can_swim">Can Swim</label>
            </div>
            <button type="submit" :disabled="!isValid">Add</button>
        </form>

        <div>
            <ul>
                <li class="student" v-for="(student, index) in students" :key="index">
                    <div>
                        {{ student.name }} & 
                        {{ student.partner }}, 
                        {{ student.can_swim ? "Can Swim" : "Can't swim" }}
                    </div>
                </li>               
            </ul>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app', 
            data: {
                student: {
                    name: '', 
                    can_swim: false, 
                    partner: ''
                }, 
                students: [], 
            }, 
            methods: {
                onSubmit() {
                    if(!this.isValid) return;

                    const url = '/students/new';
                    fetch(url, {
                        method: 'POST', 
                        headers: {
                            'Content-Type': 'application/json'
                        }, 
                        body: JSON.stringify(this.student)
                    }).then(resp => resp.ok && resp.json())
                        .then(data => {
                            if (data) {
                                this.students.push(data);
                                this.student = {
                                    name: '', 
                                    can_swim: false, 
                                    partner: ''
                                };
                                this.$refs.name.focus();
                            } else {
                                this.$refs.name.select();
                            }
                        });
                }, 
                loadStudents() {
                    const url = '/students'; 
                    return fetch(url)
                        .then(resp => resp.ok && resp.json())
                        .then(data => { return data || []});
                }
            }, 
            computed: {
                isValidName() {
                    return this.student.name 
                            && 2 <= this.student.name.length
                            && this.student.name.length <= 8
                            && this.student.name.match(/^[A-Za-z]+[0-9]*$/);
                }, 
                isValidPartner() {
                    return this.student.partner 
                            && 2 <= this.student.partner.length
                            && this.student.partner.length <= 8
                            && this.student.partner.match(/^[A-Za-z]+[0-9]*$/);
                }, 
                isValid() {
                    return this.isValidName && this.isValidPartner;
                }
            }, 
            async created() {
                this.students = await this.loadStudents();
            }
        });
    </script>

</body>
</html>

I’ve tried to guide you through this with comments in the code, and I hope you get on with it.

Leave a comment