HTML Diff
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 &lt;b-modal ref="addBookModal" id="book-modal" title="Add a new book" hide-footer&gt; &lt;b-form @submit="onSubmit" @reset="onReset" class="w-100"&gt; &lt;b-form-group id="form-title-group" label="Title:" label-for="form-title-input"&gt; &lt;b-form-input id="form-title-input" type="text" v-model="addBookForm.title" required placeholder="Enter title"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-author-group" label="Author:" label-for="form-author-input"&gt; &lt;b-form-input id="form-author-input" type="text" v-model="addBookForm.author" required placeholder="Enter author"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-read-group"&gt; &lt;b-form-checkbox-group v-model="addBookForm.read" id="form-checks"&gt; &lt;b-form-checkbox value="true"&gt;Read?&lt;/b-form-checkbox&gt; &lt;/b-form-checkbox-group&gt; &lt;/b-form-group&gt; &lt;b-button type="submit" variant="primary"&gt;Submit&lt;/b-button&gt; &lt;b-button type="reset" variant="danger"&gt;Reset&lt;/b-button&gt; &lt;/b-form&gt; &lt;/b-modal&gt;<p>Эту часть кода следует поместить перед закрывающим тегом . Обратите внимание, что v-model является директивой, используемой для привязки обратно к стейту входных значений. Если интересует Hide-Footer, вы можете посмотреть, что он делает, в<a>официальной документации</a>по Bootstrap Vue.</p>
11 &lt;b-modal ref="addBookModal" id="book-modal" title="Add a new book" hide-footer&gt; &lt;b-form @submit="onSubmit" @reset="onReset" class="w-100"&gt; &lt;b-form-group id="form-title-group" label="Title:" label-for="form-title-input"&gt; &lt;b-form-input id="form-title-input" type="text" v-model="addBookForm.title" required placeholder="Enter title"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-author-group" label="Author:" label-for="form-author-input"&gt; &lt;b-form-input id="form-author-input" type="text" v-model="addBookForm.author" required placeholder="Enter author"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-read-group"&gt; &lt;b-form-checkbox-group v-model="addBookForm.read" id="form-checks"&gt; &lt;b-form-checkbox value="true"&gt;Read?&lt;/b-form-checkbox&gt; &lt;/b-form-checkbox-group&gt; &lt;/b-form-group&gt; &lt;b-button type="submit" variant="primary"&gt;Submit&lt;/b-button&gt; &lt;b-button type="reset" variant="danger"&gt;Reset&lt;/b-button&gt; &lt;/b-form&gt; &lt;/b-modal&gt;<p>Эту часть кода следует поместить перед закрывающим тегом . Обратите внимание, что v-model является директивой, используемой для привязки обратно к стейту входных значений. Если интересует Hide-Footer, вы можете посмотреть, что он делает, в<a>официальной документации</a>по Bootstrap Vue.</p>
12 <p>Теперь следует выполнить обновление раздела script:</p>
12 <p>Теперь следует выполнить обновление раздела script:</p>
13 &lt;script&gt; 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) =&gt; { this.books = res.data.books; }) .catch((error) =&gt; { // eslint-отключение следующей строки console.error(error); }); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() =&gt; { this.getBooks(); }) .catch((error) =&gt; { // 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(); }, }; &lt;/script&gt;<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 &lt;script&gt; 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) =&gt; { this.books = res.data.books; }) .catch((error) =&gt; { // eslint-отключение следующей строки console.error(error); }); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() =&gt; { this.getBooks(); }) .catch((error) =&gt; { // 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(); }, }; &lt;/script&gt;<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 &lt;button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal&gt;Add Book&lt;/button&gt;<p>После выполнения всех вышеописанных действий компонент должен выглядеть так:</p>
15 &lt;button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal&gt;Add Book&lt;/button&gt;<p>После выполнения всех вышеописанных действий компонент должен выглядеть так:</p>
16 &lt;template&gt; &lt;div class="container"&gt; &lt;div class="row"&gt; &lt;div class="col-sm-10"&gt; &lt;h1&gt;Books&lt;/h1&gt; &lt;hr&gt;&lt;br&gt;&lt;br&gt; &lt;button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal&gt;Add Book&lt;/button&gt; &lt;br&gt;&lt;br&gt; &lt;table class="table table-hover"&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope="col"&gt;Title&lt;/th&gt; &lt;th scope="col"&gt;Author&lt;/th&gt; &lt;th scope="col"&gt;Read?&lt;/th&gt; &lt;th&gt;&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr v-for="(book, index) in books" :key="index"&gt; &lt;td&gt;{{ book.title }}&lt;/td&gt; &lt;td&gt;{{ book.author }}&lt;/td&gt; &lt;td&gt; &lt;span v-if="book.read"&gt;Yes&lt;/span&gt; &lt;span v-else&gt;No&lt;/span&gt; &lt;/td&gt; &lt;td&gt; &lt;button type="button" class="btn btn-warning btn-sm"&gt;Update&lt;/button&gt; &lt;button type="button" class="btn btn-danger btn-sm"&gt;Delete&lt;/button&gt; &lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;/div&gt; &lt;/div&gt; &lt;b-modal ref="addBookModal" id="book-modal" title="Add a new book" hide-footer&gt; &lt;b-form @submit="onSubmit" @reset="onReset" class="w-100"&gt; &lt;b-form-group id="form-title-group" label="Title:" label-for="form-title-input"&gt; &lt;b-form-input id="form-title-input" type="text" v-model="addBookForm.title" required placeholder="Enter title"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-author-group" label="Author:" label-for="form-author-input"&gt; &lt;b-form-input id="form-author-input" type="text" v-model="addBookForm.author" required placeholder="Enter author"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-read-group"&gt; &lt;b-form-checkbox-group v-model="addBookForm.read" id="form-checks"&gt; &lt;b-form-checkbox value="true"&gt;Read?&lt;/b-form-checkbox&gt; &lt;/b-form-checkbox-group&gt; &lt;/b-form-group&gt; &lt;b-button type="submit" variant="primary"&gt;Submit&lt;/b-button&gt; &lt;b-button type="reset" variant="danger"&gt;Reset&lt;/b-button&gt; &lt;/b-form&gt; &lt;/b-modal&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; 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) =&gt; { this.books = res.data.books; }) .catch((error) =&gt; { // eslint-отключение следующей строки console.error(error); }); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() =&gt; { this.getBooks(); }) .catch((error) =&gt; { // 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(); }, }; &lt;/script&gt;<p>Давайте сделаем проверку и попробуем добавить книгу:</p>
16 &lt;template&gt; &lt;div class="container"&gt; &lt;div class="row"&gt; &lt;div class="col-sm-10"&gt; &lt;h1&gt;Books&lt;/h1&gt; &lt;hr&gt;&lt;br&gt;&lt;br&gt; &lt;button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal&gt;Add Book&lt;/button&gt; &lt;br&gt;&lt;br&gt; &lt;table class="table table-hover"&gt; &lt;thead&gt; &lt;tr&gt; &lt;th scope="col"&gt;Title&lt;/th&gt; &lt;th scope="col"&gt;Author&lt;/th&gt; &lt;th scope="col"&gt;Read?&lt;/th&gt; &lt;th&gt;&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr v-for="(book, index) in books" :key="index"&gt; &lt;td&gt;{{ book.title }}&lt;/td&gt; &lt;td&gt;{{ book.author }}&lt;/td&gt; &lt;td&gt; &lt;span v-if="book.read"&gt;Yes&lt;/span&gt; &lt;span v-else&gt;No&lt;/span&gt; &lt;/td&gt; &lt;td&gt; &lt;button type="button" class="btn btn-warning btn-sm"&gt;Update&lt;/button&gt; &lt;button type="button" class="btn btn-danger btn-sm"&gt;Delete&lt;/button&gt; &lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;/div&gt; &lt;/div&gt; &lt;b-modal ref="addBookModal" id="book-modal" title="Add a new book" hide-footer&gt; &lt;b-form @submit="onSubmit" @reset="onReset" class="w-100"&gt; &lt;b-form-group id="form-title-group" label="Title:" label-for="form-title-input"&gt; &lt;b-form-input id="form-title-input" type="text" v-model="addBookForm.title" required placeholder="Enter title"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-author-group" label="Author:" label-for="form-author-input"&gt; &lt;b-form-input id="form-author-input" type="text" v-model="addBookForm.author" required placeholder="Enter author"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-read-group"&gt; &lt;b-form-checkbox-group v-model="addBookForm.read" id="form-checks"&gt; &lt;b-form-checkbox value="true"&gt;Read?&lt;/b-form-checkbox&gt; &lt;/b-form-checkbox-group&gt; &lt;/b-form-group&gt; &lt;b-button type="submit" variant="primary"&gt;Submit&lt;/b-button&gt; &lt;b-button type="reset" variant="danger"&gt;Reset&lt;/b-button&gt; &lt;/b-form&gt; &lt;/b-modal&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; 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) =&gt; { this.books = res.data.books; }) .catch((error) =&gt; { // eslint-отключение следующей строки console.error(error); }); }, addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() =&gt; { this.getBooks(); }) .catch((error) =&gt; { // 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(); }, }; &lt;/script&gt;<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 &lt;template&gt; &lt;p&gt;It works!&lt;/p&gt; &lt;/template&gt;<p>Потом следует импортировать его в разделе script компонента Books и зарегистрировать:</p>
19 &lt;template&gt; &lt;p&gt;It works!&lt;/p&gt; &lt;/template&gt;<p>Потом следует импортировать его в разделе script компонента Books и зарегистрировать:</p>
20 &lt;script&gt; import axios from 'axios'; import Alert from './Alert'; ... export default { data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, }; }, components: { alert: Alert, }, ... }; &lt;/script&gt;<p>И вот сейчас в разделе template мы можем ссылаться на новый компонент:</p>
20 &lt;script&gt; import axios from 'axios'; import Alert from './Alert'; ... export default { data() { return { books: [], addBookForm: { title: '', author: '', read: [], }, }; }, components: { alert: Alert, }, ... }; &lt;/script&gt;<p>И вот сейчас в разделе template мы можем ссылаться на новый компонент:</p>
21 &lt;template&gt; &lt;b-container&gt; &lt;b-row&gt; &lt;b-col col sm="10"&gt; &lt;h1&gt;Books&lt;/h1&gt; &lt;hr&gt;&lt;br&gt;&lt;br&gt; &lt;alert&gt;&lt;/alert&gt; &lt;button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal&gt;Add Book&lt;/button&gt; ... &lt;/b-col&gt; &lt;/b-row&gt; &lt;/b-container&gt; &lt;/template&gt;<p>После обновления браузера увидим:</p>
21 &lt;template&gt; &lt;b-container&gt; &lt;b-row&gt; &lt;b-col col sm="10"&gt; &lt;h1&gt;Books&lt;/h1&gt; &lt;hr&gt;&lt;br&gt;&lt;br&gt; &lt;alert&gt;&lt;/alert&gt; &lt;button type="button" class="btn btn-success btn-sm" v-b-modal.book-modal&gt;Add Book&lt;/button&gt; ... &lt;/b-col&gt; &lt;/b-row&gt; &lt;/b-container&gt; &lt;/template&gt;<p>После обновления браузера увидим:</p>
22 <p>Далее нужно добавить в шаблон фактический компонент b-alert:</p>
22 <p>Далее нужно добавить в шаблон фактический компонент b-alert:</p>
23 &lt;template&gt; &lt;div&gt; &lt;b-alert variant="success" show&gt;{{ message }}&lt;/b-alert&gt; &lt;br&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; export default { props: ['message'], }; &lt;/script&gt;<p>Тут нужно обратить особое внимание на параметр props (находится в разделе script). Мы можем передавать сообщение из родительского компонента (Books) так:</p>
23 &lt;template&gt; &lt;div&gt; &lt;b-alert variant="success" show&gt;{{ message }}&lt;/b-alert&gt; &lt;br&gt; &lt;/div&gt; &lt;/template&gt; &lt;script&gt; export default { props: ['message'], }; &lt;/script&gt;<p>Тут нужно обратить особое внимание на параметр props (находится в разделе script). Мы можем передавать сообщение из родительского компонента (Books) так:</p>
24 &lt;alert message="hi"&gt;&lt;/alert&gt;<p>Если желаете сделать Alert динамическим и передать пользовательское сообщение, используйте в Books.vue выражение привязки (binding expression):</p>
24 &lt;alert message="hi"&gt;&lt;/alert&gt;<p>Если желаете сделать Alert динамическим и передать пользовательское сообщение, используйте в Books.vue выражение привязки (binding expression):</p>
25 &lt;alert :message="message"&gt;&lt;/alert&gt;<p>Теперь добавим message в параметр data в Books.vue:</p>
25 &lt;alert :message="message"&gt;&lt;/alert&gt;<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(() =&gt; { this.getBooks(); this.message = 'Book added!'; }) .catch((error) =&gt; { // 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(() =&gt; { this.getBooks(); this.message = 'Book added!'; }) .catch((error) =&gt; { // eslint-отключение следующей строки console.log(error); this.getBooks(); }); },<p>Чтобы alert отображался, только когда showMessage имеет значение true, добавим v-if.</p>
28 &lt;alert :message=message v-if="showMessage"&gt;&lt;/alert&gt;<p>Также надо добавить showMessage в data:</p>
28 &lt;alert :message=message v-if="showMessage"&gt;&lt;/alert&gt;<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(() =&gt; { this.getBooks(); this.message = 'Book added!'; this.showMessage = true; }) .catch((error) =&gt; { // eslint-отключение следующей строки console.log(error); this.getBooks(); }); },<p>Проверяем, как всё теперь работает:</p>
30 addBook(payload) { const path = 'http://localhost:5000/books'; axios.post(path, payload) .then(() =&gt; { this.getBooks(); this.message = 'Book added!'; this.showMessage = true; }) .catch((error) =&gt; { // 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 &lt;b-modal ref="editBookModal" id="book-update-modal" title="Update" hide-footer&gt; &lt;b-form @submit="onSubmitUpdate" @reset="onResetUpdate" class="w-100"&gt; &lt;b-form-group id="form-title-edit-group" label="Title:" label-for="form-title-edit-input"&gt; &lt;b-form-input id="form-title-edit-input" type="text" v-model="editForm.title" required placeholder="Enter title"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-author-edit-group" label="Author:" label-for="form-author-edit-input"&gt; &lt;b-form-input id="form-author-edit-input" type="text" v-model="editForm.author" required placeholder="Enter author"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-read-edit-group"&gt; &lt;b-form-checkbox-group v-model="editForm.read" id="form-checks"&gt; &lt;b-form-checkbox value="true"&gt;Read?&lt;/b-form-checkbox&gt; &lt;/b-form-checkbox-group&gt; &lt;/b-form-group&gt; &lt;b-button type="submit" variant="primary"&gt;Update&lt;/b-button&gt; &lt;b-button type="reset" variant="danger"&gt;Cancel&lt;/b-button&gt; &lt;/b-form&gt; &lt;/b-modal&gt;<p>Теперь надо добавить стейт формы в часть data в разделе script:</p>
44 &lt;b-modal ref="editBookModal" id="book-update-modal" title="Update" hide-footer&gt; &lt;b-form @submit="onSubmitUpdate" @reset="onResetUpdate" class="w-100"&gt; &lt;b-form-group id="form-title-edit-group" label="Title:" label-for="form-title-edit-input"&gt; &lt;b-form-input id="form-title-edit-input" type="text" v-model="editForm.title" required placeholder="Enter title"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-author-edit-group" label="Author:" label-for="form-author-edit-input"&gt; &lt;b-form-input id="form-author-edit-input" type="text" v-model="editForm.author" required placeholder="Enter author"&gt; &lt;/b-form-input&gt; &lt;/b-form-group&gt; &lt;b-form-group id="form-read-edit-group"&gt; &lt;b-form-checkbox-group v-model="editForm.read" id="form-checks"&gt; &lt;b-form-checkbox value="true"&gt;Read?&lt;/b-form-checkbox&gt; &lt;/b-form-checkbox-group&gt; &lt;/b-form-group&gt; &lt;b-button type="submit" variant="primary"&gt;Update&lt;/b-button&gt; &lt;b-button type="reset" variant="danger"&gt;Cancel&lt;/b-button&gt; &lt;/b-form&gt; &lt;/b-modal&gt;<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 &lt;button type="button" class="btn btn-warning btn-sm" v-b-modal.book-update-modal @click="editBook(book)"&gt; Update &lt;/button&gt;<p>Чтобы обновить значения в editForm, добавляем новый метод:</p>
47 &lt;button type="button" class="btn btn-warning btn-sm" v-b-modal.book-update-modal @click="editBook(book)"&gt; Update &lt;/button&gt;<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(() =&gt; { this.getBooks(); }) .catch((error) =&gt; { // 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(() =&gt; { this.getBooks(); }) .catch((error) =&gt; { // 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(() =&gt; { this.getBooks(); this.message = 'Book updated!'; this.showMessage = true; }) .catch((error) =&gt; { // 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(() =&gt; { this.getBooks(); this.message = 'Book updated!'; this.showMessage = true; }) .catch((error) =&gt; { // 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 &lt;button type="button" class="btn btn-danger btn-sm" @click="onDeleteBook(book)"&gt; Delete &lt;/button&gt;<p>Выполним добавление методов для обработки нажатия кнопки, а потом удалим книгу:</p>
61 &lt;button type="button" class="btn btn-danger btn-sm" @click="onDeleteBook(book)"&gt; Delete &lt;/button&gt;<p>Выполним добавление методов для обработки нажатия кнопки, а потом удалим книгу:</p>
62 removeBook(bookID) { const path = `http://localhost:5000/books/${bookID}`; axios.delete(path) .then(() =&gt; { this.getBooks(); this.message = 'Book removed!'; this.showMessage = true; }) .catch((error) =&gt; { // 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(() =&gt; { this.getBooks(); this.message = 'Book removed!'; this.showMessage = true; }) .catch((error) =&gt; { // 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