Способ реализации CRUD с помощью Ajax и Json

Способ реализации CRUD с помощью Ajax и Json

Nuances of programming

Перевод статьи Vitor Freitas: How to Implement CRUD Using Ajax and Json

(Рисунок: https://www.pexels.com/photo/macbook-laptop-smartphone-apple-7358/)

Руководство

Весьма распространенным вариантом использования Ajax является асинхронное манипулирование моделями Django. При этом Ajax может быть использован для редактирования строк в таблице или создания нового экземпляра модели без перехода между двумя вариантами веб-сайта. Однако, с другой стороны, это несет в себе ряд трудностей, таких как сохранение состояний объектов.

Если вы пока еще не слышали ничего о термине CRUD, начнем с того, что поясним, как расшифровывается эта аббревиатура: Create Read Update Delete (Вставка, Выборка, Обновление или Удаление данных).

То есть в термине CRUD, по-сути, перечислены основные операции, которые мы выполняем над объектами приложения. По большей части администрирование Django – это как раз и есть этот самый CRUD.

Данное руководство рассчитано на версии Python 2.7 и 3.5, использует Django 1.8, 1.9 или 1.10.

Содержание

  • Базовая конфигурация
  • Рабочий пример
  • Представление книг
  • Создание книги
  • Редактирование книги
  • Удаление книги
  • Выводы

Базовая конфигурация

В этом руководстве мы будем использовать jQuery для реализации запросов Ajax. Однако вы можете воспользоваться и любым другим JavaScript-фреймворком (или реализовать его с использованием голово JavaScript). При этом общие идеи и концепции останутся неизменными.

Возьмите копию jQuery, либо загрузите ее, либо воспользуйтесь любым удобным вам средством из многих вариантов сетей передачи данных.

jquery.com/download/

Обычно я предпочитаю использовать локальную копию, потому что иногда мне приходится работать в автономном режиме. Поместите jQuery в нижнюю часть базового шаблона:

base.html

{% load static %}<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bookstore - Simple is Better Than Complex</title>
    <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    {% include 'includes/header.html' %}
    <div class="container">
      {% block content %}
      {% endblock %}
    </div>
    <script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>  <!-- JQUERY HERE -->
    <script src="{% static 'js/bootstrap.min.js' %}"></script>
    {% block javascript %}
    {% endblock %}
  </body>
</html>

Я также буду использовать Bootstrap. Это не обязателено, но он обеспечивает хороший базовый CSS, а также некоторые полезные компоненты HTML, такие как таблицы форм и стилей.

Рабочий пример

Я буду работать над приложением под названием книги. Для операций CRUD рассмотрим следующую модель:

models.py

class Book(models.Model):
    HARDCOVER = 1
    PAPERBACK = 2
    EBOOK = 3
    BOOK_TYPES = (
        (HARDCOVER, 'Hardcover'),
        (PAPERBACK, 'Paperback'),
        (EBOOK, 'E-book'),
    )
    title = models.CharField(max_length=50)
    publication_date = models.DateField(null=True)
    author = models.CharField(max_length=30, blank=True)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    pages = models.IntegerField(blank=True, null=True)
    book_type = models.PositiveSmallIntegerField(choices=BOOK_TYPES)

Представление книг

Давайте начнем с перечисления всех объектов приложения "книги".

Нам нужен маршрут в urlconf:

urls.py:

from django.conf.urls import url, include
from mysite.books import views
 
urlpatterns = [
    url(r'^books/$', views.book_list, name='book_list'),
]

Простой вид списковой структуры приложения:

views.py

from django.shortcuts import render
from .models import Book
 
def book_list(request):
    books = Book.objects.all()
    return render(request, 'books/book_list.html', {'books': books})

book_list.html

{% extends 'base.html' %}
 
{% block content %}
  <h1 class="page-header">Books</h1>
  <table class="table" id="book-table">
    <thead>
      <tr>
        <th>#</th>
        <th>Title</th>
        <th>Author</th>
        <th>Type</th>
        <th>Publication date</th>
        <th>Pages</th>
        <th>Price</th>
      </tr>
    </thead>
    <tbody>
      {% for book in book_list %}
        <tr>
          <td>{{ book.id }}</td>
          <td>{{ book.title }}</td>
          <td>{{ book.author }}</td>
          <td>{{ book.get_book_type_display }}</td>
          <td>{{ book.publication_date }}</td>
          <td>{{ book.pages }}</td>
          <td>{{ book.price }}</td>
        </tr>
      {% empty %}
        <tr>
          <td colspan="7" class="text-center bg-warning">No book</td>
        </tr>
      {% endfor %}
    </tbody>
  </table>
{% endblock %}

Пока ничего особенного. Наш шаблон должен выглядеть следующим образом:

Создание книги

Во-первых, давайте создадим форму модели. Пусть Django выполнит свою работу.

forms.py

from django import forms
from .models import Book
 
class BookForm(forms.ModelForm):
    class Meta:
        model = Book
        fields = ('title', 'publication_date', 'author', 'price', 'pages', 'book_type', )

Теперь нам нужно подготовить шаблон для обработки операции создания моделей.

Стратегия, которую я хотел бы использовать заключается в том, чтобы использовать общую модель bootstrap и использовать ее для всех операций.

book_list.html

{% extends 'base.html' %}
 
{% block content %}
  <h1 class="page-header">Books</h1>
 
  <!-- BUTTON TO TRIGGER THE ACTION -->
  <p>
    <button type="button" class="btn btn-primary js-create-book">
      <span class="glyphicon glyphicon-plus"></span>
      New book
    </button>
  </p>
 
  <table class="table" id="book-table">
    <!-- TABLE CONTENT SUPPRESSED FOR BREVITY'S SAKE -->
  </table>
 
  <!-- THE MODAL WE WILL BE USING -->
  <div class="modal fade" id="modal-book">
    <div class="modal-dialog">
      <div class="modal-content">
      </div>
    </div>
{% endblock %}

Обратите внимание, что я уже добавил кнопку, которая будет использоваться для начала процесса создания. Я добавил класс js-create-book, чтобы подключает событие нажатия на клавишу. Обычно я добавляю класс, начинающийся с js- для всех элементов, которые взаимодействуют с кодом JavaScript. Так будет легче отладить код. Это не обязательное общепринятое правило, а просто принятое нами соглашение, помогающее создавать более качественный код.

Добавим новый маршрут:

urls.py:

from django.conf.urls import url, include
from mysite.books import views
 
urlpatterns = [
    url(r'^books/$', views.book_list, name='book_list'),
    url(r'^books/create/$', views.book_create, name='book_create'),
]

Давайте реализуем представление book_create:

views.py

from django.http import JsonResponse
from django.template.loader import render_to_string
from .forms import BookForm
 
def book_create(request):
    form = BookForm()
    context = {'form': form}
    html_form = render_to_string('books/includes/partial_book_create.html',
        context,
        request=request,
    )
    return JsonResponse({'html_form': html_form})

Обратите внимание, что мы не создаем шаблон, а возвращаем ответ JSON.

Теперь мы создаем частичный шаблон для отображения формы:

partial_book_create.html

{% load widget_tweaks %}
 
<form method="post">
  {% csrf_token %}
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
    <h4 class="modal-title">Create a new book</h4>
  </div>
  <div class="modal-body">
    {% for field in form %}
      <div class="form-group{% if field.errors %} has-error{% endif %}">
        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
        {% render_field field class="form-control" %}
        {% for error in field.errors %}
          <p class="help-block">{{ error }}</p>
        {% endfor %}
      </div>
    {% endfor %}
  </div>
  <div class="modal-footer">
    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
    <button type="submit" class="btn btn-primary">Create book</button>
  </div>
</form>

Я использую библиотеку django-widget-tweaks для правильного отображения полей формы с использованием загрузочного класса. Вы можете узнать об этом подробнее из опубликованной в прошлом году статьи: Пакет недели: виджеты Django Widget Tweaks.

Теперь давайте "склеем" все вместе, для этого воспользуемся JavaScript.

Создайте внешний файл JavaScript. Я создал свой файл и расположил его по следующему пути: mysite/books/static/books/js/books.js

books.js

$(function () {
 
  $(".js-create-book").click(function () {
    $.ajax({
      url: '/books/create/',
      type: 'get',
      dataType: 'json',
      beforeSend: function () {
        $("#modal-book").modal("show");
      },
      success: function (data) {
        $("#modal-book .modal-content").html(data.html_form);
      }
    });
  });
 
});

Не забудьте подключить этот JavaScript-файл в шаблон book_list.html:

book_list.html

{% extends 'base.html' %}
 
{% load static %}
 
{% block javascript %}
  <script src="{% static 'books/js/books.js' %}"></script>
{% endblock %}
 
{% block content %}
  <!-- BLOCK CONTENT SUPPRESSED FOR BREVITY'S SAKE -->
{% endblock %}

Давайте подробно рассмотрим фрагмент кода на JavaScript:

Это ярлык jQuery, чтобы попросить браузер подождать, пока весь HTML не отобразится перед выполнением кода:

$(function () {
  ...
});

Здесь мы подключаемся к элементу по событию click с помощью класса js-create-book, который является нашей кнопкой «Добавить книгу».

$(".js-create-book").click(function () {
  ...
});

Когда пользователь нажимает кнопку js-create-book, будет выполнена эта анонимная функция с вызовом $.ajax:

$.ajax({
  url: '/books/create/',
  type: 'get',
  dataType: 'json',
  beforeSend: function () {
    $("#modal-book").modal("show");
  },
  success: function (data) {
    $("#modal-book .modal-content").html(data.html_form);
  }
});

Теперь это ajax-запрос, говорящий браузеру:

Эй, ресурс, который я хочу, находится по этому пути:

url: '/books/create/',

Убедитесь, что вы запрашиваете мои данные с помощью метода HTTP GET:

type: 'get',

Кстати, я хочу получить данные в JSON формате:

dataType: 'json',

Но прямо перед тем, как вы свяжетесь с сервером, выполните этот код:

beforeSend: function () {
  $("#modal-book").modal("show");
},

(Здесь перед запуском Ajax-запроса откроется Bootstrap-модель).

И сразу после получения данных (в переменной data) выполните этот код:

success: function (data) {
  $("#modal-book .modal-content").html(data.html_form);
}

(Это визуализирует частичную форму, определенную в шаблоне partial_book_create.html.)

Давайте посмотрим, что у нас уже имеется:

Затем, когда пользователь нажимает кнопку:

Здорово. Форма книги создается асинхронно. Но на данный момент сделано пока еще не так много. Хорошая новость заключается в том, что структура готова и теперь "поиграемся" с данными.

Давайте реализуем обработку отправки формы.

Сначала улучшим функцию просмотра book_create:

views.py

def book_create(request):
    data = dict()
 
    if request.method == 'POST':
        form = BookForm(request.POST)
        if form.is_valid():
            form.save()
            data['form_is_valid'] = True
        else:
            data['form_is_valid'] = False
    else:
        form = BookForm()
 
    context = {'form': form}
    data['html_form'] = render_to_string('books/includes/partial_book_create.html',
        context,
        request=request
    )
    return JsonResponse(data)

partial_book_create.html

{% load widget_tweaks %}
 
<form method="post" action="{% url 'book_create' %}" class="js-book-create-form">
  <!-- FORM CONTENT SUPPRESSED FOR BREVITY'S SAKE -->
</form>

Я добавил атрибут action, чтобы сообщить браузеру, куда он должен для нас отправить представление и класс js-book-create-form, чтобы использовать их на стороне JavaScript, подключая событие отправки формы.

books.js

  $("#modal-book").on("submit", ".js-book-create-form", function () {
    ...
  });

То, как мы слушаем событие отправки, немного отличается от того, что мы реализовали раньше. Это потому, что элемент с классом .js-book-create-form не существовал при начальной загрузке шаблона страницы book_list.html. Таким образом, мы не можем зарегистрировать обработчик события для не существующего элемента.

Работа заключается в регистрации обработчика события элемента, который всегда будет существовать в контексте страницы. #modal-book – ближайший элемент. Это немного сложнее того, что происходит, но короче говоря, HTML-события распространяются на родительские элементы до тех пор, пока они не дойдут до конца документа.

Подключение к элементу body будет иметь тот же эффект, но это будет немного хуже, потому что ему придется пройти через несколько элементов HTML пока он не достигнет его. Поэтому всегда выбирайте ближайший элемент.

Теперь действительная функция:

books.js

  $("#modal-book").on("submit", ".js-book-create-form", function () {
    var form = $(this);
    $.ajax({
      url: form.attr("action"),
      data: form.serialize(),
      type: form.attr("method"),
      dataType: 'json',
      success: function (data) {
        if (data.form_is_valid) {
          alert("Book created!");  // <-- This is just a placeholder for now for testing
        }
        else {
          $("#modal-book .modal-content").html(data.html_form);
        }
      }
    });
    return false;
  });

Очень важная деталь: в конце функции мы возвращаем false. Это происходит потому, что мы фиксируем событие отправки формы, чтобы браузер не отправлял полный HTTP POST на сервер, мы отменяем поведение по умолчанию, возвращающая false в функции.

Итак, что мы здесь делаем:

var form = $(this);

В этом контексте  this относится к элементу с классом  .js-book-create-form. Это элемент, который вызвал событие отправки. Поэтому, когда мы выбираем  $(this), мы выбираем фактическую форму.

url: form.attr("action"),

Теперь я использую атрибуты формы для создания Ajax-запроса. Действие здесь относится к атрибуту action в форме, который переводится в /books/create/.

data: form.serialize(),

Как следует из названия, мы сериализуем все данные из формы и отправляем их на сервер.

Прежде чем двигаться дальше, давайте посмотрим, что у нас есть.

Пользователь заполняет данные:

Пользователь нажимает кнопку Создать книгу:

Данные недействительны. Никакого обновления нет. Только незначительная часть изменилась при валидации. Так что же произошло:

  1. Форма была отправлена через Ajax.
  2. Функция представления обработала форму.
  3. Данные формы недействительны.
  4. Функция представления отобразила недопустимое значение для данных data['html_form'], используя render_to_string.
  5. Запрос Ajax вернулся к функции JavaScript.
  6. Выполнен обратный успешный вызов Ajax, заменив содержимое формы на новые data['html_form'].

Обратите внимание, что обратный успешный вызов Ajax:

$.ajax({
  // ...
  success: function (data) {
    // ...
  }
});

Относится к статусу HTTP-запроса, который не имеет ничего общего с состоянием вашей формы или с тем, была ли форма успешно обработана или нет. Это означает только то, что HTTP-запрос возвратил статус 200.

Исправьте значение даты публикации и снова отправьте форму:

Готово, уведомляющие сообщение сообщает нам, что форма была успешно обработана и, будем надеятся, она была создана в базе данных.

success: function (data) {
  if (data.form_is_valid) {
    alert("Book created!");  // <-- This line was executed! Means success
  }
  else {
    $("#modal-book .modal-content").html(data.html_form);
  }
}

Это не на 100% именно то, что мы хотим, но мы приближаемся к цели. Давайте обновим страницу и посмотрим, отображается ли новая книга в таблице:

Отлично. Мы движемся в верном направлении.

Что мы хотим сделать сейчас: после успешной обработки формы мы хотим закрыть bootstrap-форму и обновить таблицу с помощью вновь созданной книги. Для этого случая мы выделим тело таблицы для внешнего частичного шаблона, а затем вернем новое тело таблицы в обратном успешном вызове Ajax.

Посмотрите на это:

book_list.html

<table class="table" id="book-table">
  <thead>
    <tr>
      <th>#</th>
      <th>Title</th>
      <th>Author</th>
      <th>Type</th>
      <th>Publication date</th>
      <th>Pages</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
    {% include 'books/includes/partial_book_list.html' %}
  </tbody>
</table>

partial_book_list.html

{% for book in books %}
  <tr>
    <td>{{ book.id }}</td>
    <td>{{ book.title }}</td>
    <td>{{ book.author }}</td>
    <td>{{ book.get_book_type_display }}</td>
    <td>{{ book.publication_date }}</td>
    <td>{{ book.pages }}</td>
    <td>{{ book.price }}</td>
  </tr>
{% empty %}
  <tr>
    <td colspan="7" class="text-center bg-warning">No book</td>
  </tr>
{% endfor %}

Теперь мы можем уже самостоятельно повторно воспользоваться фрагментом partial_book_list.html.

Следующий шаг: функция просмотра book_create.

views.py

def book_create(request):
    data = dict()
 
    if request.method == 'POST':
        form = BookForm(request.POST)
        if form.is_valid():
            form.save()
            data['form_is_valid'] = True
            books = Book.objects.all()
            data['html_book_list'] = render_to_string('books/includes/partial_book_list.html', {
                'books': books
            })
        else:
            data['form_is_valid'] = False
    else:
        form = BookForm()
 
    context = {'form': form}
    data['html_form'] = render_to_string('books/includes/partial_book_create.html',
        context,
        request=request
    )
    return JsonResponse(data)

Обработчик на стороне JavaScript:

books.js

$("#modal-book").on("submit", ".js-book-create-form", function () {
    var form = $(this);
    $.ajax({
      url: form.attr("action"),
      data: form.serialize(),
      type: form.attr("method"),
      dataType: 'json',
      success: function (data) {
        if (data.form_is_valid) {
          $("#book-table tbody").html(data.html_book_list);  // <-- Replace the table body
          $("#modal-book").modal("hide");  // <-- Close the modal
        }
        else {
          $("#modal-book .modal-content").html(data.html_form);
        }
      }
    });
    return false;
  });

Чудесно. Это работает!

Редактирование книги

Как и следовало ожидать, это будет очень похоже на то, что мы сделали в разделе «Создать книгу». Нам нужно будет передать идентификатор книги, которую мы хотим изменить. Все остальное должно остаться без изменений. Мы будем повторно использовать несколько частей кода.

urls.py:

from django.conf.urls import url, include
from mysite.books import views
 
urlpatterns = [
    url(r'^books/$', views.book_list, name='book_list'),
    url(r'^books/create/$', views.book_create, name='book_create'),
    url(r'^books/(?P<pk>\d+)/update/$', views.book_update, name='book_update'),
]

Теперь мы выполним реорганизацию кода book_create для повторного использования в book_update:

views.py
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse
from django.template.loader import render_to_string
 
from .models import Book
from .forms import BookForm
 
 
def save_book_form(request, form, template_name):
    data = dict()
    if request.method == 'POST':
        if form.is_valid():
            form.save()
            data['form_is_valid'] = True
            books = Book.objects.all()
            data['html_book_list'] = render_to_string('books/includes/partial_book_list.html', {
                'books': books
            })
        else:
            data['form_is_valid'] = False
    context = {'form': form}
    data['html_form'] = render_to_string(template_name, context, request=request)
    return JsonResponse(data)
 
 
def book_create(request):
    if request.method == 'POST':
        form = BookForm(request.POST)
    else:
        form = BookForm()
    return save_book_form(request, form, 'books/includes/partial_book_create.html')
 
 
def book_update(request, pk):
    book = get_object_or_404(Book, pk=pk)
    if request.method == 'POST':
        form = BookForm(request.POST, instance=book)
    else:
        form = BookForm(instance=book)
    return save_book_form(request, form, 'books/includes/partial_book_update.html')

В основном, функции book_create и book_update отвечают за получение запроса, подготовку экземпляра формы и передачу его в save_book_form наряду с именем шаблона, который будет использоваться в процессе визуализации.

Следующий шаг - создать шаблон partial_book_update.html. Подобно тому, что мы сделали с функциями представления, мы также реорганизуем partial_book_create.html для повторного использования.

partial_book_form.html

{% load widget_tweaks %}
 
{% for field in form %}
  <div class="form-group{% if field.errors %} has-error{% endif %}">
    <label for="{{ field.id_for_label }}">{{ field.label }}</label>
    {% render_field field class="form-control" %}
    {% for error in field.errors %}
      <p class="help-block">{{ error }}</p>
    {% endfor %}
  </div>
{% endfor %}

partial_book_create.html

<form method="post" action="{% url 'book_create' %}" class="js-book-create-form">
  {% csrf_token %}
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
    <h4 class="modal-title">Create a new book</h4>
  </div>
  <div class="modal-body">
    {% include 'books/includes/partial_book_form.html' %}
  </div>
  <div class="modal-footer">
    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
    <button type="submit" class="btn btn-primary">Create book</button>
  </div>
</form>

partial_book_update.html

<form method="post" action="{% url 'book_update' form.instance.pk %}" class="js-book-update-form">
  {% csrf_token %}
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
    <h4 class="modal-title">Update book</h4>
  </div>
  <div class="modal-body">
    {% include 'books/includes/partial_book_form.html' %}
  </div>
  <div class="modal-footer">
    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
    <button type="submit" class="btn btn-primary">Update book</button>
  </div>
</form>

Этого будет достаточно. Теперь мы должны добавить кнопку редактирования, чтобы вызвать действие.

partial_book_list.html

{% for book in books %}
  <tr>
    <td>{{ book.id }}</td>
    <td>{{ book.title }}</td>
    <td>{{ book.author }}</td>
    <td>{{ book.get_book_type_display }}</td>
    <td>{{ book.publication_date }}</td>
    <td>{{ book.pages }}</td>
    <td>{{ book.price }}</td>
    <td>
      <button type="button"
              class="btn btn-warning btn-sm js-update-book"
              data-url="{% url 'book_update' book.id %}">
        <span class="glyphicon glyphicon-pencil"></span> Edit
      </button>
    </td>
  </tr>
{% empty %}
  <tr>
    <td colspan="8" class="text-center bg-warning">No book</td>
  </tr>
{% endfor %}

Класс js-update-book будет использоваться для редактирования. Теперь обратите внимание, что я добавил дополнительный атрибут HTML с именем data-url. Это URL-адрес, который будет использоваться для динамического повторного поиска Аjax.

Отредактируйте кнопку js-create-book, чтобы использовать data-url, таким образом мы можем извлечьзаданный прямо в программном коде URL-адрес из запроса Ajax.

book_list.html

{% extends 'base.html' %}
 
{% block content %}
  <h1 class="page-header">Books</h1>
 
  <p>
    <button type="button"
            class="btn btn-primary js-create-book"
            data-url="{% url 'book_create' %}">
      <span class="glyphicon glyphicon-plus"></span>
      New book
    </button>
  </p>
 
  <!-- REST OF THE PAGE... -->
 
{% endblock %}

books.js

$(".js-create-book").click(function () {
  var btn = $(this);  // <-- HERE
  $.ajax({
    url: btn.attr("data-url"),  // <-- AND HERE
    type: 'get',
    dataType: 'json',
    beforeSend: function () {
      $("#modal-book").modal("show");
    },
    success: function (data) {
      $("#modal-book .modal-content").html(data.html_form);
    }
  });
});

Следующий шаг – создание функции редактирования. Дело в том, что они почти такие же, как и ранее разработанные функции создания книги. Итак, мы хотим извлечь анонимные функции которые используем и повторно использовать их в кнопках и формах редактирования:

books.js

$(function () {
 
  /* Functions */
 
  var loadForm = function () {
    var btn = $(this);
    $.ajax({
      url: btn.attr("data-url"),
      type: 'get',
      dataType: 'json',
      beforeSend: function () {
        $("#modal-book").modal("show");
      },
      success: function (data) {
        $("#modal-book .modal-content").html(data.html_form);
      }
    });
  };
 
  var saveForm = function () {
    var form = $(this);
    $.ajax({
      url: form.attr("action"),
      data: form.serialize(),
      type: form.attr("method"),
      dataType: 'json',
      success: function (data) {
        if (data.form_is_valid) {
          $("#book-table tbody").html(data.html_book_list);
          $("#modal-book").modal("hide");
        }
        else {
          $("#modal-book .modal-content").html(data.html_form);
        }
      }
    });
    return false;
  };
 
 
  /* Binding */
 
  // Create book
  $(".js-create-book").click(loadForm);
  $("#modal-book").on("submit", ".js-book-create-form", saveForm);
 
  // Update book
  $("#book-table").on("click", ".js-update-book", loadForm);
  $("#modal-book").on("submit", ".js-book-update-form", saveForm);
 
});

Давайте посмотрим, что у нас теперь уже имеется.

Пользователь нажимает кнопку редактирования.

Изменяет некоторые данные, например название книги, и нажмите кнопку Обновить книгу:

Круто! Теперь осталось разобраться и удалением и мы закончили.

Удаление книги

urls.py:

from django.conf.urls import url, include
from mysite.books import views
 
urlpatterns = [
    url(r'^books/$', views.book_list, name='book_list'),
    url(r'^books/create/$', views.book_create, name='book_create'),
    url(r'^books/(?P<pk>\d+)/update/$', views.book_update, name='book_update'),
    url(r'^books/(?P<pk>\d+)/delete/$', views.book_delete, name='book_delete'),
]

views.py

def book_delete(request, pk):
    book = get_object_or_404(Book, pk=pk)
    data = dict()
    if request.method == 'POST':
        book.delete()
        data['form_is_valid'] = True  # This is just to play along with the existing code
        books = Book.objects.all()
        data['html_book_list'] = render_to_string('books/includes/partial_book_list.html', {
            'books': books
        })
    else:
        context = {'book': book}
        data['html_form'] = render_to_string('books/includes/partial_book_delete.html',
            context,
            request=request,
        )
    return JsonResponse(data)

partial_book_delete.html

<form method="post" action="{% url 'book_delete' book.id %}" class="js-book-delete-form">
  {% csrf_token %}
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
    <h4 class="modal-title">Confirm book deletion</h4>
  </div>
  <div class="modal-body">
    <p class="lead">Are you sure you want to delete the book <strong>{{ book.title }}</strong>?</p>
  </div>
  <div class="modal-footer">
    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
    <button type="submit" class="btn btn-danger">Delete book</button>
  </div>
</form>

partial_book_list.html

{% for book in books %}
  <tr>
    <td>{{ book.id }}</td>
    <td>{{ book.title }}</td>
    <td>{{ book.author }}</td>
    <td>{{ book.get_book_type_display }}</td>
    <td>{{ book.publication_date }}</td>
    <td>{{ book.pages }}</td>
    <td>{{ book.price }}</td>
    <td>
      <button type="button"
              class="btn btn-warning btn-sm js-update-book"
              data-url="{% url 'book_update' book.id %}">
        <span class="glyphicon glyphicon-pencil"></span> Edit
      </button>
      <button type="button"
              class="btn btn-danger btn-sm js-delete-book"
              data-url="{% url 'book_delete' book.id %}">
        <span class="glyphicon glyphicon-trash"></span> Delete
      </button>
    </td>
  </tr>
{% empty %}
  <tr>
    <td colspan="8" class="text-center bg-warning">No book</td>
  </tr>
{% endfor %}

books.js

// Delete book
$("#book-table").on("click", ".js-delete-book", loadForm);
$("#modal-book").on("submit", ".js-book-delete-form", saveForm);

И результат будет следующий:

Пользователь нажимает кнопку «Удалить»:

Пользователь подтвердит удаление и таблица обновляется в фоновом режиме:

Выводы

В этом примере я решил использовать представления основанные на функциях, потому что их легче читать и использовать.

Я попытался представить как можно больше деталей и обсудить некоторые решения по разработке кода. Если вам что-то не понятно или вы хотите предложить некоторые улучшения, не стесняйтесь оставлять комментарии! Я хотел бы услышать ваши мысли.

Код доступен на GitHub: github.com/sibtc/simple-ajax-crud

Report Page