0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>В<a>предыдущей части статьи</a>мы настроили Flask, Vue и Bootstrap, а также приступили к созданию базового CRUD-приложения. Пришло время закончить начатое.</p>
1
<p>В<a>предыдущей части статьи</a>мы настроили Flask, Vue и Bootstrap, а также приступили к созданию базового CRUD-приложения. Пришло время закончить начатое.</p>
2
<h2>POST-маршрут</h2>
2
<h2>POST-маршрут</h2>
3
<h3>Сервер</h3>
3
<h3>Сервер</h3>
4
<p>Теперь давайте выполним обновление существующего обработчика маршрута для обработки POST-запросов и добавления новых книг:</p>
4
<p>Теперь давайте выполним обновление существующего обработчика маршрута для обработки POST-запросов и добавления новых книг:</p>
5
@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)<p>Также нужно обновить импорты:</p>
5
@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)<p>Также нужно обновить импорты:</p>
6
from flask import Flask, jsonify, request<p>После запуска сервера Flask, можно выполнить проверку POST-маршрута на новой вкладке веб-браузера:</p>
6
from flask import Flask, jsonify, request<p>После запуска сервера Flask, можно выполнить проверку POST-маршрута на новой вкладке веб-браузера:</p>
7
$ curl -X POST http://localhost:5000/books -d \ '{"title": "1Q84", "author": "Haruki Murakami", "read": "true"}' \ -H 'Content-Type: application/json'<p>Должный увидеть следующее:</p>
7
$ curl -X POST http://localhost:5000/books -d \ '{"title": "1Q84", "author": "Haruki Murakami", "read": "true"}' \ -H 'Content-Type: application/json'<p>Должный увидеть следующее:</p>
8
{ "message": "Book added!", "status": "success" }<p>Кроме того, по адресу http://localhost:5000/books должна появиться добавленная книга.</p>
8
{ "message": "Book added!", "status": "success" }<p>Кроме того, по адресу http://localhost:5000/books должна появиться добавленная книга.</p>
9
<h3>Клиент</h3>
9
<h3>Клиент</h3>
10
<p>Далее, для добавления новой книги надо внести следующий modal. Давайте начнём с HTML:</p>
10
<p>Далее, для добавления новой книги надо внести следующий modal. Давайте начнём с HTML:</p>
11
<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 type="submit" variant="primary">Submit</b-button> <b-button type="reset" variant="danger">Reset</b-button> </b-form> </b-modal><p>Эту часть кода следует поместить перед закрывающим тегом . Обратите внимание, что v-model является директивой, используемой для привязки обратно к стейту входных значений. Если интересует Hide-Footer, вы можете посмотреть, что он делает, в<a>официальной документации</a>по Bootstrap Vue.</p>
11
<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 type="submit" variant="primary">Submit</b-button> <b-button type="reset" variant="danger">Reset</b-button> </b-form> </b-modal><p>Эту часть кода следует поместить перед закрывающим тегом . Обратите внимание, что v-model является директивой, используемой для привязки обратно к стейту входных значений. Если интересует Hide-Footer, вы можете посмотреть, что он делает, в<a>официальной документации</a>по Bootstrap Vue.</p>
12
<p>Теперь следует выполнить обновление раздела script:</p>
12
<p>Теперь следует выполнить обновление раздела script:</p>
13
<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-отключение следующей строки console.error(error); }); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() => { this.getBooks(); }) .catch((error) => { // eslint-отключение следующей строки 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, // сокращённое свойство }; this.addBook(payload); this.initForm(); }, onReset(evt) { evt.preventDefault(); this.$refs.addBookModal.hide(); this.initForm(); }, }, created() { this.getBooks(); }, }; </script><p>Посмотрим, что выполняется в вышеописанном коде: 1) addBookForm() привязывается ко входным данным формы посредством v-model - это называют 2-сторонней привязкой; 2) onSubmit() запускается, если пользователь успешно отправляет форму. При этом при отправке предотвращается обычное поведение веб-браузера (evt.preventDefault()), плюс закрывается modal (this.$Refs.addBookModal.hide()), осуществляется запуск метода addBook() и очищается форма (initForm()); 3) addBook() отправляет POST-запрос в /books, что необходимо для добавления новой книги; 4) последующие изменения можно посмотреть самостоятельно в<a>Vue-документации</a>.</p>
13
<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-отключение следующей строки console.error(error); }); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() => { this.getBooks(); }) .catch((error) => { // eslint-отключение следующей строки 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, // сокращённое свойство }; this.addBook(payload); this.initForm(); }, onReset(evt) { evt.preventDefault(); this.$refs.addBookModal.hide(); this.initForm(); }, }, created() { this.getBooks(); }, }; </script><p>Посмотрим, что выполняется в вышеописанном коде: 1) addBookForm() привязывается ко входным данным формы посредством v-model - это называют 2-сторонней привязкой; 2) onSubmit() запускается, если пользователь успешно отправляет форму. При этом при отправке предотвращается обычное поведение веб-браузера (evt.preventDefault()), плюс закрывается modal (this.$Refs.addBookModal.hide()), осуществляется запуск метода addBook() и очищается форма (initForm()); 3) addBook() отправляет POST-запрос в /books, что необходимо для добавления новой книги; 4) последующие изменения можно посмотреть самостоятельно в<a>Vue-документации</a>.</p>
14
<p>Далее обновляем кнопку "Add Book" в темплейте, дабы при нажатии кнопки отображался modal:</p>
14
<p>Далее обновляем кнопку "Add Book" в темплейте, дабы при нажатии кнопки отображался modal:</p>
15
<button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button><p>После выполнения всех вышеописанных действий компонент должен выглядеть так:</p>
15
<button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal>Add Book</button><p>После выполнения всех вышеописанных действий компонент должен выглядеть так:</p>
16
<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> <button type="button" class="btn btn-warning btn-sm">Update</button> <button type="button" class="btn btn-danger btn-sm">Delete</button> </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 type="submit" variant="primary">Submit</b-button> <b-button type="reset" variant="danger">Reset</b-button> </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-отключение следующей строки console.error(error); }); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() => { this.getBooks(); }) .catch((error) => { // eslint-отключение следующей строки 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, // сокращённое свойство }; this.addBook(payload); this.initForm(); }, onReset(evt) { evt.preventDefault(); this.$refs.addBookModal.hide(); this.initForm(); }, }, created() { this.getBooks(); }, }; </script><p>Давайте сделаем проверку и попробуем добавить книгу:</p>
16
<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> <button type="button" class="btn btn-warning btn-sm">Update</button> <button type="button" class="btn btn-danger btn-sm">Delete</button> </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 type="submit" variant="primary">Submit</b-button> <b-button type="reset" variant="danger">Reset</b-button> </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-отключение следующей строки console.error(error); }); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() => { this.getBooks(); }) .catch((error) => { // eslint-отключение следующей строки 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, // сокращённое свойство }; this.addBook(payload); this.initForm(); }, onReset(evt) { evt.preventDefault(); this.$refs.addBookModal.hide(); this.initForm(); }, }, created() { this.getBooks(); }, }; </script><p>Давайте сделаем проверку и попробуем добавить книгу:</p>
17
<h2>Компонент Alert</h2>
17
<h2>Компонент Alert</h2>
18
<p>Теперь пришло время добавить компонент Alert. Он нужен, чтобы пользователь получал сообщения о добавлении новой книги. Итак, создадим новый компонент, добавив новый файл Alert.vue в каталог client/src/components:</p>
18
<p>Теперь пришло время добавить компонент Alert. Он нужен, чтобы пользователь получал сообщения о добавлении новой книги. Итак, создадим новый компонент, добавив новый файл Alert.vue в каталог client/src/components:</p>
19
<template> <p>It works!</p> </template><p>Потом следует импортировать его в разделе script компонента Books и зарегистрировать:</p>
19
<template> <p>It works!</p> </template><p>Потом следует импортировать его в разделе script компонента Books и зарегистрировать:</p>
20
<script> import axios from 'axios'; import Alert from './Alert'; ... export default { data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, }; }, components: { alert: Alert, }, ... }; </script><p>И вот сейчас в разделе template мы можем ссылаться на новый компонент:</p>
20
<script> import axios from 'axios'; import Alert from './Alert'; ... export default { data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, }; }, components: { alert: Alert, }, ... }; </script><p>И вот сейчас в разделе template мы можем ссылаться на новый компонент:</p>
21
<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><p>После обновления браузера увидим:</p>
21
<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><p>После обновления браузера увидим:</p>
22
<p>Далее нужно добавить в шаблон фактический компонент b-alert:</p>
22
<p>Далее нужно добавить в шаблон фактический компонент b-alert:</p>
23
<template> <div> <b-alert variant="success" show>{{ message }}</b-alert> <br> </div> </template> <script> export default { props: ['message'], }; </script><p>Тут нужно обратить особое внимание на параметр props (находится в разделе script). Мы можем передавать сообщение из родительского компонента (Books) так:</p>
23
<template> <div> <b-alert variant="success" show>{{ message }}</b-alert> <br> </div> </template> <script> export default { props: ['message'], }; </script><p>Тут нужно обратить особое внимание на параметр props (находится в разделе script). Мы можем передавать сообщение из родительского компонента (Books) так:</p>
24
<alert message="hi"></alert><p>Если желаете сделать Alert динамическим и передать пользовательское сообщение, используйте в Books.vue выражение привязки (binding expression):</p>
24
<alert message="hi"></alert><p>Если желаете сделать Alert динамическим и передать пользовательское сообщение, используйте в Books.vue выражение привязки (binding expression):</p>
25
<alert :message="message"></alert><p>Теперь добавим message в параметр data в Books.vue:</p>
25
<alert :message="message"></alert><p>Теперь добавим message в параметр data в Books.vue:</p>
26
data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, message: '', }; },<p>Далее следует обновить сообщение в addBook:</p>
26
data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, message: '', }; },<p>Далее следует обновить сообщение в addBook:</p>
27
addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() => { this.getBooks(); this.message = 'Book added!'; }) .catch((error) => { // eslint-отключение следующей строки console.log(error); this.getBooks(); }); },<p>Чтобы alert отображался, только когда showMessage имеет значение true, добавим v-if.</p>
27
addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() => { this.getBooks(); this.message = 'Book added!'; }) .catch((error) => { // eslint-отключение следующей строки console.log(error); this.getBooks(); }); },<p>Чтобы alert отображался, только когда showMessage имеет значение true, добавим v-if.</p>
28
<alert :message=message v-if="showMessage"></alert><p>Также надо добавить showMessage в data:</p>
28
<alert :message=message v-if="showMessage"></alert><p>Также надо добавить showMessage в data:</p>
29
data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, message: '', showMessage: false, }; },<p>Выполним очередное обновление addBook(), установив значение true в showMessage :</p>
29
data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, message: '', showMessage: false, }; },<p>Выполним очередное обновление addBook(), установив значение true в showMessage :</p>
30
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-отключение следующей строки console.log(error); this.getBooks(); }); },<p>Проверяем, как всё теперь работает:</p>
30
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-отключение следующей строки console.log(error); this.getBooks(); }); },<p>Проверяем, как всё теперь работает:</p>
31
<h2>PUT-маршрут</h2>
31
<h2>PUT-маршрут</h2>
32
<h3>Сервер</h3>
32
<h3>Сервер</h3>
33
<p>Для обновления следует использовать уникальный идентификатор. Подойдёт uuid из стандартной Python-библиотеки.</p>
33
<p>Для обновления следует использовать уникальный идентификатор. Подойдёт uuid из стандартной Python-библиотеки.</p>
34
<p>Выполняем обновление BOOKS в server/app.py:</p>
34
<p>Выполняем обновление BOOKS в server/app.py:</p>
35
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 } ]<p>Импортируем:</p>
35
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 } ]<p>Импортируем:</p>
36
<p>Выполняем рефакторинг all_books, что необходимо для учёта уникального идентификатора во время добавления новой книги:</p>
36
<p>Выполняем рефакторинг all_books, что необходимо для учёта уникального идентификатора во время добавления новой книги:</p>
37
@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)<p>Теперь добавляем новый обработчик маршрута:</p>
37
@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)<p>Теперь добавляем новый обработчик маршрута:</p>
38
@app.route('/books/', 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)<p>И вспомогательную функцию:</p>
38
@app.route('/books/', 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)<p>И вспомогательную функцию:</p>
39
def remove_book(book_id): for book in BOOKS: if book['id'] == book_id: BOOKS.remove(book) return True return False<h3>Клиент</h3>
39
def remove_book(book_id): for book in BOOKS: if book['id'] == book_id: BOOKS.remove(book) return True return False<h3>Клиент</h3>
40
<p>Действуем поэтапно: 1. Добавляем modal и формы. 2. Обрабатываем нажатия кнопки Update. 3. Подключаем AJAX-запрос. 4. Оповещаем пользователя (Alert). 5. Обрабатываем нажатия кнопки Cancel.</p>
40
<p>Действуем поэтапно: 1. Добавляем modal и формы. 2. Обрабатываем нажатия кнопки Update. 3. Подключаем AJAX-запрос. 4. Оповещаем пользователя (Alert). 5. Обрабатываем нажатия кнопки Cancel.</p>
41
<p>Приступим.</p>
41
<p>Приступим.</p>
42
<h4>1. Добавляем modal и формы</h4>
42
<h4>1. Добавляем modal и формы</h4>
43
<p>В первую очередь, следует добавить новый modal к темплейту, выполнив это сразу после первого modal:</p>
43
<p>В первую очередь, следует добавить новый modal к темплейту, выполнив это сразу после первого modal:</p>
44
<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 type="submit" variant="primary">Update</b-button> <b-button type="reset" variant="danger">Cancel</b-button> </b-form> </b-modal><p>Теперь надо добавить стейт формы в часть data в разделе script:</p>
44
<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 type="submit" variant="primary">Update</b-button> <b-button type="reset" variant="danger">Cancel</b-button> </b-form> </b-modal><p>Теперь надо добавить стейт формы в часть data в разделе script:</p>
45
editForm: { id: '', title: '', author: '', read: [], },<h4>2. Обрабатываем нажатия кнопки Update</h4>
45
editForm: { id: '', title: '', author: '', read: [], },<h4>2. Обрабатываем нажатия кнопки Update</h4>
46
<p>Выполняем обновление кнопки "Update" в таблице:</p>
46
<p>Выполняем обновление кнопки "Update" в таблице:</p>
47
<button type="button" class="btn btn-warning btn-sm" v-b-modal.book-update-modal @click="editBook(book)"> Update </button><p>Чтобы обновить значения в editForm, добавляем новый метод:</p>
47
<button type="button" class="btn btn-warning btn-sm" v-b-modal.book-update-modal @click="editBook(book)"> Update </button><p>Чтобы обновить значения в editForm, добавляем новый метод:</p>
48
editBook(book) { this.editForm = book; },<p>Чтобы обрабатывать отправку формы, тоже нужен метод:</p>
48
editBook(book) { this.editForm = book; },<p>Чтобы обрабатывать отправку формы, тоже нужен метод:</p>
49
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); },<h4>3. Подключаем AJAX-запрос</h4>
49
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); },<h4>3. Подключаем AJAX-запрос</h4>
50
updateBook(payload, bookID) { const path = `http://localhost:5000/books/${bookID}`; axios.put(path, payload) .then(() => { this.getBooks(); }) .catch((error) => { // eslint-отключение следующей строки console.error(error); this.getBooks(); }); },<h4>4. Оповещаем пользователя (Alert)</h4>
50
updateBook(payload, bookID) { const path = `http://localhost:5000/books/${bookID}`; axios.put(path, payload) .then(() => { this.getBooks(); }) .catch((error) => { // eslint-отключение следующей строки console.error(error); this.getBooks(); }); },<h4>4. Оповещаем пользователя (Alert)</h4>
51
<p>Обновляем updateBook:</p>
51
<p>Обновляем updateBook:</p>
52
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-отключение следующей строки console.error(error); this.getBooks(); }); },<h4>5. Обрабатываем нажатия кнопки Cancel</h4>
52
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-отключение следующей строки console.error(error); this.getBooks(); }); },<h4>5. Обрабатываем нажатия кнопки Cancel</h4>
53
<p>Добавляем метод:</p>
53
<p>Добавляем метод:</p>
54
onResetUpdate(evt) { evt.preventDefault(); this.$refs.editBookModal.hide(); this.initForm(); this.getBooks(); },<p>Обновляем initForm():</p>
54
onResetUpdate(evt) { evt.preventDefault(); this.$refs.editBookModal.hide(); this.initForm(); this.getBooks(); },<p>Обновляем initForm():</p>
55
initForm() { this.addBookForm.title = ''; this.addBookForm.author = ''; this.addBookForm.read = []; this.editForm.id = ''; this.editForm.title = ''; this.editForm.author = ''; this.editForm.read = []; },<p>Приложение следует в обязательном порядке протестировать и убедиться в том, что modal отображается при нажатии кнопки, как и в том, что введённые значения заполнены верно.</p>
55
initForm() { this.addBookForm.title = ''; this.addBookForm.author = ''; this.addBookForm.read = []; this.editForm.id = ''; this.editForm.title = ''; this.editForm.author = ''; this.editForm.read = []; },<p>Приложение следует в обязательном порядке протестировать и убедиться в том, что modal отображается при нажатии кнопки, как и в том, что введённые значения заполнены верно.</p>
56
<h2>DELETE-маршрут</h2>
56
<h2>DELETE-маршрут</h2>
57
<h3>Сервер</h3>
57
<h3>Сервер</h3>
58
<p>Выполним обновление обработчика маршрута:</p>
58
<p>Выполним обновление обработчика маршрута:</p>
59
@app.route('/books/', 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)<h3>Клиент</h3>
59
@app.route('/books/', 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)<h3>Клиент</h3>
60
<p>Выполним обновление кнопки "Delete":</p>
60
<p>Выполним обновление кнопки "Delete":</p>
61
<button type="button" class="btn btn-danger btn-sm" @click="onDeleteBook(book)"> Delete </button><p>Выполним добавление методов для обработки нажатия кнопки, а потом удалим книгу:</p>
61
<button type="button" class="btn btn-danger btn-sm" @click="onDeleteBook(book)"> Delete </button><p>Выполним добавление методов для обработки нажатия кнопки, а потом удалим книгу:</p>
62
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-отключение следующей строки console.error(error); this.getBooks(); }); }, onDeleteBook(book) { this.removeBook(book.id); },<p>Когда юзер нажимает кнопку удаления, происходит вызов метода onDeleteBook(), запускающего другой метод под названием removeBook(). Данный метод отправляет DELETE-запрос на сервер. Когда ответ приходит, происходит отображение Alert и запуск getBooks().</p>
62
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-отключение следующей строки console.error(error); this.getBooks(); }); }, onDeleteBook(book) { this.removeBook(book.id); },<p>Когда юзер нажимает кнопку удаления, происходит вызов метода onDeleteBook(), запускающего другой метод под названием removeBook(). Данный метод отправляет DELETE-запрос на сервер. Когда ответ приходит, происходит отображение Alert и запуск getBooks().</p>
63
<h2>Вот и всё!</h2>
63
<h2>Вот и всё!</h2>
64
<p>Итак, в нашей статье мы рассмотрели основные настройки при написании CRUD-приложения посредством Vue и Flask. Исходный код смотрите<a>здесь</a>.</p>
64
<p>Итак, в нашей статье мы рассмотрели основные настройки при написании CRUD-приложения посредством Vue и Flask. Исходный код смотрите<a>здесь</a>.</p>
65
<p><em>Источник - "<a>Developing a Single Page App with Flask and Vue.js</a>".</em></p>
65
<p><em>Источник - "<a>Developing a Single Page App with Flask and Vue.js</a>".</em></p>
66
66