Preface:

I have read some foreign articles about introducing the separation of flask and Vue’s front and back ends, but I haven’t seen any that are easy to understand and have perfect code. Yesterday I saw a very new article, and the content is very good, so I want to translate it for everyone to study together.

I’ll start Developing a CRUD App with Flask and vue.js

Body:

What follows is a step-by-step demonstration of how to complete a basic CRUD application using Flask and Vue. We’ll start by building a framework, building a new Vue application using the Vue CLI, and then performing basic CRUD operations through the RESTful apis developed in Python and Flask.

The main libraries rely on include:

  • Vue v2.6.10
  • Vue CLI v3.7.0
  • The Node v12.1.0
  • NPM v6.9.0
  • Flask v1.0.2
  • Python v3.7.3

Objective of this article

By the end of the article, you will be able to know:

  1. What is a Flask
  2. What is Vue and how it interacts with other UI libraries or front-end frameworks (React and Angular)
  3. Build a Vue project using the Vue CLI
  4. Create and submit a Vue component in the browser
  5. Create a single page application from the Vue component
  6. Connect the Vue application to the Flask backend
  7. Develop RESTful apis through Flask
  8. Use Bootstrap to style Vue components
  9. Use the Vue Router to create routing and rendering components

What is a Flask?

Flask is a simple but powerful Python microWeb framework that is ideal for building RESTful apis. Like Sinatra(Ruby) and Express(nodes), it’s very small and flexible, so you can start with a small application and build more complex applications based on your needs.

If you are using Flask for the first time, check out the following two learning resources:

  1. Flaskr TDD
  2. Flask for Node developers

What is Vue?

VUE is an open source JavaScript framework for building user interfaces. It uses some of the best practices from React and Angular. That said, it’s more approachable than React and Angular, so beginners can get started and use Vue quickly. It is also powerful and provides all the functionality needed to create the latest front-end applications.

For more information about Vue and its various advantages and disadvantages with React and Angular, see the following article:

  1. VUE: Comparison with other frameworks
  2. React vs Angular vs vue.js: A complete comparison guide
  3. React vs Angular vs Vue: A 2017 comparison

If you’re using Vue for the first time, take some time to read the official Vue guide.

Flask installation

First create a new folder:

$ mkdir flask-vue-crud
$ cd flask-vue-crudCopy the code

Next, create a virtual environment for this directory, which may vary from development environment to development environment.

$python3.7 -m venv env $source env/bin/activateCopy the code

Install Flask and flask-CORS extensions.

(env)$PIP install Flask== flask-cors ==3.0.7Copy the code

Create a new server folder in the root directory and create an app.py file in the folder:

from flask import Flask, jsonify
from flask_cors import CORS


# configuration
DEBUG = True

# instantiate the app
app = Flask(__name__)
app.config.from_object(__name__)

# enable CORS
CORS(app, resources={r'/ *': {'origins': The '*'}})


# sanity check route
@app.route('/ping', methods=['GET'])
def ping_pong():
    return jsonify('pong! ')


if __name__ == '__main__':
    app.run()Copy the code

Why flask-CORS extensions? The purpose is to make cross-domain requests — for example, requests from different protocols, IP addresses, domain names or ports — and flask-Cors handles that for us.

Note that although the Settings above allow cross-domain requests (from any domain, protocol, or port) on all routes. But in a production environment, you should only allow cross-domain requests from the domain hosting the front-end application. For more information on this issue, see
Flask – CORS document.

Running the app.

(env)$ python server/app.pyCopy the code

Now can use the browser to http://localhost:5000/ping to test, you will see a json format

"pong!"Copy the code

Back in terminal, press Ctrl+C to shut down the server. Now we can turn our attention to the front end and start setting up Vue.

VUE set

We will use the powerful Vue CLI tool to generate a custom project template.

Install Vue CLI globally:

$NPM install -g@vue /[email protected]Copy the code

First time using NPM, check it out
About npmGuide.

After the installation is complete, initialize a Vue project named Client with the following command:

$ vue create clientCopy the code

Next, you need to answer some questions about the project. In this project, the specific choices are as follows:

Vue CLI v3.7.0? Please pick a preset: Manually select features ? Check the features neededforyour project: ◉ Babel Infection infection TypeScript infection Progressive Web App (PWA) supported ❯◉ Router infection Vuex infection infection of CSS pre-processors ◉ Linter/Formatter infection Unit Testing infection of E2E Testing? Please pick a preset: Manually select features ? Check the features neededfor your project: Babel, Router, Linter
? Use history mode for router? Yes
? Pick a linter / formatter config: Airbnb
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? (y/N) NoCopy the code

After the project is created, a client folder is added to the project root directory. There is a lot of content in there, but we only need to deal with the contents in the ‘SRC’ folder and the index.html file in the ‘public’ folder.

The index.html file is the starting point for Vue applications.

The file structure in the SRC folder is as follows:

├── all exercises, exercises, exercises, exercises, exercises, exercises, exercises, exercises, exercises About the vue └ ─ ─ Home. VueCopy the code

A:

  • Main.js app entry point, which loads and initializes the Vue with the root component.
  • The app.vue root component, which is the starting point to start rendering all the other components.
  • ‘Components’ stores UI components
  • Router.js defines urls and maps them to corresponding components
  • ‘Views’ stores the UI components bound to the router
  • ‘Asset’ stores static resources such as images and fonts

Open/client/SRC/components/HelloWorld. Vue file. This is a single-file component that consists of three parts:

  1. Template: For the component-specific HTML part
  2. Script: The place where component logic is implemented through JavaScript
  3. Style: Used for CSS styles

Now to run the development server:

$ cd client
$ npm run serveCopy the code

To simplify the project, remove the views folder and add a file named ping.vue to the Client/ SRC /Components folder.

<template>
  <div>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  name: 'Ping'.data() {
    return {
      msg: 'Hello! '}; }}; </script>Copy the code

Then update Client/ SRC /router.js to map “/ping” to the ping component as follows:

import Vue from 'vue';
import Router from 'vue-router';
import Ping from './components/Ping.vue';

Vue.use(Router);

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/ping',
      name: 'Ping',
      component: Ping,
    }
  ],
});Copy the code

Finally, delete the template navigation bar in Client/ SRC/app. vue and it will look like this:

<template>
  <div id="app">
    <router-view/>
  </div>
</template>Copy the code

Now, you can see through the browser to http://localhost:8080/ping hello! .

To connect the client-side Vue application to the back-end Flask application, we can use the AXIOS library to send Ajax requests.

First install the corresponding library:

$NPM install [email protected] --saveCopy the code

Update the script part of the component in ping.vue as follows:

<script>
import axios from 'axios';

export default {
  name: 'Ping'.data() {
    return {
      msg: ' '}; }, methods: {getMessage() {
      const path = 'http://localhost:5000/ping'; axios.get(path) .then((res) => { this.msg = res.data; }) .catch((error) => { // eslint-disable-next-line console.error(error); }); }},created() { this.getMessage(); }}; </script>Copy the code

Start the Flask application in a new terminal window. You can see the http://localhost:8080/ping page is no longer a hello! But the pong! . In fact, when the response is returned from the back end, we set the MSG above to the value of data from the server response object.

Install the Bootstrap

Next, let’s add Bootstrap, a popular CSS framework, to our application so we can quickly add some styles.

Installation:

$NPM install [email protected] --saveCopy the code

Ignore warnings from jquery and popper.js. Do not add them to the project. More on that later.

Import the styles from Bootstrap into Client/ SRC /main.js:

import 'bootstrap/dist/css/bootstrap.css';
import Vue from 'vue';
import App from './App.vue';
import router from './router';

Vue.config.productionTip = false;

new Vue({
  router,
  render: h => h(App),
}).$mount('#app');Copy the code

Update the style section in Client/ SRC/app.vue:

<style>
#app {
  margin-top: 60px
}
</style>Copy the code

Make sure Bootstrap connects properly by using Button and Container in the ping component:

<template>
  <div class="container">
    <button type="button" class="btn btn-primary">{{ msg }}</button>
  </div>
</template>Copy the code

Run server:

$ npm run serveCopy the code

You can see:


Next, create a new books. vue file and add a Books component to it:

<template>
  <div class="container">
    <p>books</p>
  </div>
</template>Copy the code

Update the routing file router.js:

import Vue from 'vue';
import Router from 'vue-router';
import Books from './components/Books.vue';
import Ping from './components/Ping.vue';

Vue.use(Router);

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'Books',
      component: Books,
    },
    {
      path: '/ping',
      name: 'Ping',
      component: Ping,
    },
  ],
});Copy the code

Testing: http://localhost:8080

Finally, let’s add the bootstrap-Styled form to the Books component:

<template>
  <div class="container">
    <div class="row">
      <div class="col-sm-10">
        <h1>Books</h1>
        <hr><br><br>
        <button type="button" class="btn btn-success btn-sm">Add Book</button>
        <br><br>
        <table class="table table-hover">
          <thead>
            <tr>
              <th scope="col">Title</th>
              <th scope="col">Author</th>
              <th scope="col">Read? </th> <th></th> </tr> </thead> <tbody> <tr> <td>foo</td> <td>bar</td> <td>foobar</td> <td> <div class="btn-group" role="group">
                  <button type="button" class="btn btn-warning btn-sm">Update</button>
                  <button type="button" class="btn btn-danger btn-sm">Delete</button>
                </div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>Copy the code

You should see by now:



Now we can start building the functionality of the CRUD application.

What are we going to build?

Our goal was to design a back-end RESTful API for Books, implemented by Python and Flask. The API itself should follow RESTful design principles and use basic HTTP functions: GET, POST, PUT, and DELETE.

We will also use Vue to build a complete front-end application based on the back-end API:



This tutorial only discusses the road to happiness construction; dealing with mistakes is a separate exercise. Try adding appropriate error handling on both the front end and back end on your own based on your understanding.

GET the routing

The server side

Add a list of books to server/app.py:

BOOKS = [
    {
        'title': 'On the Road'.'author': 'Jack Kerouac'.'read': True
    },
    {
        'title': 'Harry Potter and the Philosopher\'s Stone', 'author':'J. K. Rowling', 'read': False }, { 'title':'Green Eggs and Ham', 'author':'Dr. Seuss', 'read': True } ]Copy the code

Add a route handler:

@app.route('/books', methods=['GET'])
def all_books():
    return jsonify({
        'status': 'success'.'books': BOOKS
    })Copy the code

Run the flask app, and test the routing HTTP: / / http://localhost:5000/books.

Want to take on more challenges? You can write an automated test for this program. To view
hereThere is more information about resources for testing Flask applications.

The client

Update books component:

<template>
  <div class="container">
    <div class="row">
      <div class="col-sm-10">
        <h1>Books</h1>
        <hr><br><br>
        <button type="button" class="btn btn-success btn-sm">Add Book</button>
        <br><br>
        <table class="table table-hover">
          <thead>
            <tr>
              <th scope="col">Title</th>
              <th scope="col">Author</th>
              <th scope="col">Read? </th> <th></th> </tr> </thead> <tbody> <tr v-for="(book, index) in books" :key="index">
              <td>{{ book.title }}</td>
              <td>{{ book.author }}</td>
              <td>
                <span v-if="book.read">Yes</span>
                <span v-else>No</span>
              </td>
              <td>
                <div class="btn-group" role="group">
                  <button type="button" class="btn btn-warning btn-sm">Update</button>
                  <button type="button" class="btn btn-danger btn-sm">Delete</button>
                </div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      books: [],
    };
  },
  methods: {
    getBooks() {
      const path = 'http://localhost:5000/books'; axios.get(path) .then((res) => { this.books = res.data.books; }) .catch((error) => { // eslint-disable-next-line console.error(error); }); }},created() { this.getBooks(); }}; </script>Copy the code

After the component is initialized, the getBooks() method is called through the lifecycle hook created, which gets the book from the back-end endpoint we just set up.

To view
The lifecycle hook for the instanceLearn more about the component lifecycle and available methods.

In the template, we iterate through the list of books with the V-for instruction, creating a new table row in each iteration. The index value is used as key. Finally, v-if is used to render “yes” or “no” to indicate whether the user has read the book



Bootstrap Vue

In the next section, we will use a pattern to add a new book. We’ll add a Bootstrap Vue library for this, which provides a set of Vue components using bootstrapping HTML and CSS styles.

Why use the Bootstrap Vue library? The Bootstrap
modalThe component uses
jQueryTherefore, you should avoid using Bootstrap with Vue in the same project, because Vue uses
Virtual DOMTo update the DOM. In other words, if you use jQuery to manipulate the DOM, Vue will not know about those operations. If you must use jQuery, at least don’t use Vue and jQuery on the same DOM element.

Installation:

$NPM install [email protected] --saveCopy the code

Enable the Bootstrap Vue library in Client/ SRC /main.js:

import 'bootstrap/dist/css/bootstrap.css';
import BootstrapVue from 'bootstrap-vue';
import Vue from 'vue';
import App from './App.vue';
import router from './router';

Vue.use(BootstrapVue);

Vue.config.productionTip = false;

new Vue({
  router,
  render: h => h(App),
}).$mount('#app');Copy the code

POST the routing

The server side

Update the current route handler to support handling POST requests, adding new books:

from flask import Flask, jsonify, request

@app.route('/books', methods=['GET'.'POST'])
def all_books():
    response_object = {'status': 'success'}
    if request.method == 'POST':
        post_data = request.get_json()
        BOOKS.append({
            'title': post_data.get('title'),
            'author': post_data.get('author'),
            'read': post_data.get('read')
        })
        response_object['message'] = 'Book added! '
    else:
        response_object['books'] = BOOKS
    return jsonify(response_object)Copy the code

When Flask server is running, you can test the functionality of POST routing in a new terminal TAB:

$ curl -X POST http://localhost:5000/books -d \ ‘{“title”: “1Q84”, “author”: “Haruki Murakami”, “read”: “true”}’ \ -H ‘Content-Type: application/json’

You can see:

{ “message”: “Book added!” , “status”: “success” }

You can also visit http://localhost:5000/books endpoint see if in the response successfully added a new book.

What if the title already exists? Or what if a title has more than one author? You can test your understanding by trying to solve these problems yourself. And what about invalid data bodies, such as in the absence of title, author, or read?

The client

Let’s now add POST mode on the client side to add new Books to the Books component, starting with HTML:

<b-modal ref="addBookModal"
         id="book-modal"
         title="Add a new book"
         hide-footer>
  <b-form @submit="onSubmit" @reset="onReset" class="w-100">
  <b-form-group id="form-title-group"
                label="Title:"
                label-for="form-title-input">
      <b-form-input id="form-title-input"
                    type="text"
                    v-model="addBookForm.title"
                    required
                    placeholder="Enter title">
      </b-form-input>
    </b-form-group>
    <b-form-group id="form-author-group"
                  label="Author:"
                  label-for="form-author-input">
        <b-form-input id="form-author-input"
                      type="text"
                      v-model="addBookForm.author"
                      required
                      placeholder="Enter author">
        </b-form-input>
      </b-form-group>
    <b-form-group id="form-read-group">
      <b-form-checkbox-group v-model="addBookForm.read" id="form-checks">
        <b-form-checkbox value="true">Read? </b-form-checkbox> </b-form-checkbox-group> </b-form-group> <b-buttontype="submit" variant="primary">Submit</b-button>
    <b-button type="reset" variant="danger">Reset</b-button>
  </b-form>
</b-modal>Copy the code

Add it before the last closing dev tag. You can look at the code. V-model is an instruction that binds input values to corresponding states. You’ll see that soon enough.

hide-footerWhat’s the use? To find out, check out the Bootstrap Vue documentation
The document.

Update script section:

<script>
import axios from 'axios';

export default {
  data() {
    return {
      books: [],
      addBookForm: {
        title: ' ',
        author: ' '.read: [].}}; }, methods: {getBooks() {
      const path = 'http://localhost:5000/books';
      axios.get(path)
        .then((res) => {
          this.books = res.data.books;
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
        });
    },
    addBook(payload) {
      const path = 'http://localhost:5000/books';
      axios.post(path, payload)
        .then(() => {
          this.getBooks();
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.log(error);
          this.getBooks();
        });
    },
    initForm() {
      this.addBookForm.title = ' ';
      this.addBookForm.author = ' ';
      this.addBookForm.read = [];
    },
    onSubmit(evt) {
      evt.preventDefault();
      this.$refs.addBookModal.hide();
      let read = false;
      if (this.addBookForm.read[0]) read = true;
      const payload = {
        title: this.addBookForm.title,
        author: this.addBookForm.author,
        read, // property shorthand
      };
      this.addBook(payload);
      this.initForm();
    },
    onReset(evt) {
      evt.preventDefault();
      this.$refs.addBookModal.hide(); this.initForm(); }},created() { this.getBooks(); }}; </script>Copy the code

What does this code do?

  1. AddBookForm is v-modal bound to form input. When one is updated, the other is also updated, which is called bidirectional binding. Take a moment to think, do you think this will make state management easier or harder? How do React and Angular handle this? In my opinion, bidirectional binding (and variability) makes Vue easier to understand than Reaction, but less scalable in the long run.
  2. OnSubmit is triggered when the user successfully submits the form. On submission, we block normal browser behavior (evt.preitdefault ()), close the modal component ($rens. Addbookmodal.hid () in this case), trigger the addBook method, and clear the form (initForm()).
  3. AddBook sends a POST request to/Books to add a new book.
  4. Review the rest of the changes yourself and refer to the Vue documentation if necessary.

Can you spot potential errors on the client or server? Handle these yourself to improve the user experience.

Finally, update the “AddBook” button in the template to show Modal when the button is clicked:

<button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>Copy the code

The complete component code should now look like this:

<template>
  <div class="container">
    <div class="row">
      <div class="col-sm-10">
        <h1>Books</h1>
        <hr><br><br>
        <button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>
        <br><br>
        <table class="table table-hover">
          <thead>
            <tr>
              <th scope="col">Title</th>
              <th scope="col">Author</th>
              <th scope="col">Read? </th> <th></th> </tr> </thead> <tbody> <tr v-for="(book, index) in books" :key="index">
              <td>{{ book.title }}</td>
              <td>{{ book.author }}</td>
              <td>
                <span v-if="book.read">Yes</span>
                <span v-else>No</span>
              </td>
              <td>
                <div class="btn-group" role="group">
                  <button type="button" class="btn btn-warning btn-sm">Update</button>
                  <button type="button" class="btn btn-danger btn-sm">Delete</button>
                </div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
    <b-modal ref="addBookModal"
            id="book-modal"
            title="Add a new book"
            hide-footer>
      <b-form @submit="onSubmit" @reset="onReset" class="w-100">
      <b-form-group id="form-title-group"
                    label="Title:"
                    label-for="form-title-input">
          <b-form-input id="form-title-input"
                        type="text"
                        v-model="addBookForm.title"
                        required
                        placeholder="Enter title">
          </b-form-input>
        </b-form-group>
        <b-form-group id="form-author-group"
                      label="Author:"
                      label-for="form-author-input">
            <b-form-input id="form-author-input"
                          type="text"
                          v-model="addBookForm.author"
                          required
                          placeholder="Enter author">
            </b-form-input>
          </b-form-group>
        <b-form-group id="form-read-group">
          <b-form-checkbox-group v-model="addBookForm.read" id="form-checks">
            <b-form-checkbox value="true">Read? </b-form-checkbox> </b-form-checkbox-group> </b-form-group> <b-button-group> <b-buttontype="submit" variant="primary">Submit</b-button>
          <b-button type="reset" variant="danger">Reset</b-button>
        </b-button-group>
      </b-form>
    </b-modal>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      books: [],
      addBookForm: {
        title: ' ',
        author: ' '.read: [].}}; }, methods: {getBooks() {
      const path = 'http://localhost:5000/books';
      axios.get(path)
        .then((res) => {
          this.books = res.data.books;
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
        });
    },
    addBook(payload) {
      const path = 'http://localhost:5000/books';
      axios.post(path, payload)
        .then(() => {
          this.getBooks();
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.log(error);
          this.getBooks();
        });
    },
    initForm() {
      this.addBookForm.title = ' ';
      this.addBookForm.author = ' ';
      this.addBookForm.read = [];
    },
    onSubmit(evt) {
      evt.preventDefault();
      this.$refs.addBookModal.hide();
      let read = false;
      if (this.addBookForm.read[0]) read = true;
      const payload = {
        title: this.addBookForm.title,
        author: this.addBookForm.author,
        read, // property shorthand
      };
      this.addBook(payload);
      this.initForm();
    },
    onReset(evt) {
      evt.preventDefault();
      this.$refs.addBookModal.hide(); this.initForm(); }},created() { this.getBooks(); }}; </script>Copy the code

Give it a try! Try adding a book:


Alert component

Next, let’s add an Alert component so that when a new book is added, an Alert message is displayed to the user. We’ll create a separate new component for this, because you might use this functionality in many components.

Add a new file named alert. vue to “Client/ SRC /Components” :

<template> <p>It works! </p> </template>Copy the code

Then, import it into the Script section of the Books component and register the component:

<script>
import axios from 'axios';
import Alert from './Alert.vue'; .export default {
  data() {
    return {
      books: [],
      addBookForm: {
        title: ' ',
        author: ' '.read: [].}}; }, components: { alert: Alert, }, ... }; </script>Copy the code

Now we can reference the new component in the Template section:

<template>
  <b-container>
    <b-row>
      <b-col col sm="10">
        <h1>Books</h1>
        <hr><br><br>
        <alert></alert>
        <button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button>

        ...

      </b-col>
    </b-row>
  </b-container>
</template> Copy the code

Refresh the browser. You can see it now:

For more information about using a component in other components, see the official VUE documentation
Composing with ComponentsPart.

Next, let’s add the B-alert component to the template:

<template>
  <div>
    <b-alert variant="success" show>{{ message }}</b-alert>
    <br>
  </div>
</template>

<script>
export default {
  props: ['message']}; </script>Copy the code

Notice the props option in the script section. We can pass a message from the parent component (Books) as follows:

<alert message="hi"></alert>Copy the code

Try the effect:

To view
docsTo get more information about PROPS

To make it dynamic for passing custom messages, you can use binding expressions in books.vue:

<alert :message="message"></alert>Copy the code

Add the message message to the data option in books.vue:

data() {
  return {
    books: [],
    addBookForm: {
      title: ' ',
      author: ' '.read: [],
    },
    message: ' '}; },Copy the code

Then, in addBook, update the message:

addBook(payload) {
  const path = 'http://localhost:5000/books';
  axios.post(path, payload)
    .then(() => {
      this.getBooks();
      this.message = 'Book added! ';
    })
    .catch((error) => {
      // eslint-disable-next-line
      console.log(error);
      this.getBooks();
    });
},Copy the code

Finally, add a V-if, so the message will only be prompted if showMessage is true:

<alert :message=message v-if="showMessage"></alert>Copy the code

Add showMessage to data:

data() {
  return {
    books: [],
    addBookForm: {
      title: ' ',
      author: ' '.read: [],
    },
    message: ' ',
    showMessage: false}; },Copy the code

Update addBook again and set showMessage to true:

addBook(payload) {
  const path = 'http://localhost:5000/books';
  axios.post(path, payload)
    .then(() => {
      this.getBooks();
      this.message = 'Book added! ';
      this.showMessage = true;
    })
    .catch((error) => {
      // eslint-disable-next-line
      console.log(error);
      this.getBooks();
    });
},Copy the code

Try it again!

The challenge:

  1. Think about when showMessage should be set to false and update your code.
  2. Try using the Alert component to prompt an error.
  3. Refactor alert to be turned off.


PUT the routing

The server side

For updates, we need to use unique identifiers because we can’t expect all titles to be unique. We can use UUID in the Python standard library.

Update books in server/app.py:

import uuid

BOOKS = [
    {
        'id': uuid.uuid4().hex,
        'title': 'On the Road'.'author': 'Jack Kerouac'.'read': True
    },
    {
        'id': uuid.uuid4().hex,
        'title': 'Harry Potter and the Philosopher\'s Stone', 'author':'J. K. Rowling', 'read': False }, { 'id': uuid.uuid4().hex, 'title':'Green Eggs and Ham', 'author':'Dr. Seuss', 'read': True } ]Copy the code

When adding a new book, refactor All_Books to add a unique ID:

@app.route('/books', methods=['GET'.'POST'])
def all_books():
    response_object = {'status': 'success'}
    if request.method == 'POST':
        post_data = request.get_json()
        BOOKS.append({
            'id': uuid.uuid4().hex,
            'title': post_data.get('title'),
            'author': post_data.get('author'),
            'read': post_data.get('read')
        })
        response_object['message'] = 'Book added! '
    else:
        response_object['books'] = BOOKS
    return jsonify(response_object)Copy the code

Add a new route handler:

@app.route('/books/<book_id>', methods=['PUT'])
def single_book(book_id):
    response_object = {'status': 'success'}
    if request.method == 'PUT':
        post_data = request.get_json()
        remove_book(book_id)
        BOOKS.append({
            'id': uuid.uuid4().hex,
            'title': post_data.get('title'),
            'author': post_data.get('author'),
            'read': post_data.get('read')
        })
        response_object['message'] = 'Book updated! '
    return jsonify(response_object)Copy the code

Add helper methods:

def remove_book(book_id):
    for book in BOOKS:
        if book['id'] == book_id:
            BOOKS.remove(book)
            return True
    return FalseCopy the code

Take a moment to think about how you would handle a situation where the ID doesn’t exist. What if the data body is incorrect? Also, try refactoring the for loop in the helper method to make it more Pythonic.

The client

Steps:

  1. Add modes and forms
  2. Update button
  3. Connecting Ajax requests
  4. The user
  5. Cancel button

(1) Add modes and forms

First, add a new modal to the template, following the first modal:

<b-modal ref="editBookModal"
         id="book-update-modal"
         title="Update"
         hide-footer>
  <b-form @submit="onSubmitUpdate" @reset="onResetUpdate" class="w-100">
  <b-form-group id="form-title-edit-group"
                label="Title:"
                label-for="form-title-edit-input">
      <b-form-input id="form-title-edit-input"
                    type="text"
                    v-model="editForm.title"
                    required
                    placeholder="Enter title">
      </b-form-input>
    </b-form-group>
    <b-form-group id="form-author-edit-group"
                  label="Author:"
                  label-for="form-author-edit-input">
        <b-form-input id="form-author-edit-input"
                      type="text"
                      v-model="editForm.author"
                      required
                      placeholder="Enter author">
        </b-form-input>
      </b-form-group>
    <b-form-group id="form-read-edit-group">
      <b-form-checkbox-group v-model="editForm.read" id="form-checks">
        <b-form-checkbox value="true">Read? </b-form-checkbox> </b-form-checkbox-group> </b-form-group> <b-button-group> <b-buttontype="submit" variant="primary">Update</b-button>
      <b-button type="reset" variant="danger">Cancel</b-button>
    </b-button-group>
  </b-form>
</b-modal> Copy the code

Add form state to data in script section:

editForm: {
  id: ' ',
  title: ' ',
  author: ' '.read: []},Copy the code

Challenge: Try using the same Modal for POST and PUT requests instead of using the new modal.

(2) “Update” button

Update the “Update” button in the update table:

<button
        type="button"
        class="btn btn-warning btn-sm"
        v-b-modal.book-update-modal
        @click="editBook(book)">
    Update
</button>Copy the code

Add a new method to update the editForm:

editBook(book) {
  this.editForm = book;
},Copy the code

Then, add a method to handle form submission:

onSubmitUpdate(evt) {
  evt.preventDefault();
  this.$refs.editBookModal.hide();
  let read = false;
  if (this.editForm.read[0]) read = true;
  const payload = {
    title: this.editForm.title,
    author: this.editForm.author,
    read}; this.updateBook(payload, this.editForm.id); },Copy the code

(3) Connect Ajax requests

updateBook(payload, bookID) {
  const path = `http://localhost:5000/books/${bookID}`;
  axios.put(path, payload)
    .then(() => {
      this.getBooks();
    })
    .catch((error) => {
      // eslint-disable-next-line
      console.error(error);
      this.getBooks();
    });
},Copy the code

(4) User prompts

Update updateBook:

updateBook(payload, bookID) {
  const path = `http://localhost:5000/books/${bookID}`;
  axios.put(path, payload)
    .then(() => {
      this.getBooks();
      this.message = 'Book updated! ';
      this.showMessage = true;
    })
    .catch((error) => {
      // eslint-disable-next-line
      console.error(error);
      this.getBooks();
    });
},Copy the code

(5) “Cancel” button

Add method:

onResetUpdate(evt) {
  evt.preventDefault();
  this.$refs.editBookModal.hide();
  this.initForm();
  this.getBooks(); // why?
},Copy the code

Update the initForm:

initForm() {
  this.addBookForm.title = ' ';
  this.addBookForm.author = ' ';
  this.addBookForm.read = [];
  this.editForm.id = ' ';
  this.editForm.title = ' ';
  this.editForm.author = ' ';
  this.editForm.read = [];
},Copy the code

Be sure to review the code before proceeding. When you’re done, test the application. Make sure you see Modal when you click the button and fill the input field with the correct value.



Delete the routing

The server side

Update the routing handler:

@app.route('/books/<book_id>', methods=['PUT'.'DELETE'])
def single_book(book_id):
    response_object = {'status': 'success'}
    if request.method == 'PUT':
        post_data = request.get_json()
        remove_book(book_id)
        BOOKS.append({
            'id': uuid.uuid4().hex,
            'title': post_data.get('title'),
            'author': post_data.get('author'),
            'read': post_data.get('read')
        })
        response_object['message'] = 'Book updated! '
    if request.method == 'DELETE':
        remove_book(book_id)
        response_object['message'] = 'Book removed! '
    return jsonify(response_object)Copy the code

The client

Update the delete button as follows:

<button
        type="button"
        class="btn btn-danger btn-sm"
        @click="onDeleteBook(book)">
    Delete
</button>Copy the code

Add delete button:

removeBook(bookID) {
  const path = `http://localhost:5000/books/${bookID}`;
  axios.delete(path)
    .then(() => {
      this.getBooks();
      this.message = 'Book removed! ';
      this.showMessage = true;
    })
    .catch((error) => {
      // eslint-disable-next-line
      console.error(error);
      this.getBooks();
    });
},
onDeleteBook(book) {
  this.removeBook(book.id);
},Copy the code

Now, when the user clicks the Delete button, the onDeleteBook method is triggered, followed by the removeBook method. This method sends the DELETE request to the back end. When the response returns, display a prompt message and run getBooks.

The challenge:

  1. Instead of clicking the button and deleting it, add a confirmation prompt.
  2. Displays a message when there are no Books in Books such as “No Books! Please add one more “.


conclusion

This article covers the basics of setting up CRUD applications using Vue and Flask.

Check your learning, review from this article, and complete each of the challenges.

For more information, check out the source code, which is flask-vue-crud.

Thank you for reading.