title: DataTables Refresh Django Ajax Data on CRUD Operations
body: <p>I have a Django project with an <code>Analytic</code> model. This model is a list of analytics. It has a <code>ForeignKeyField</code> and a <code>ManyToMany</code> Field. The end goal is to have the user go to a URL where they can view a list of Analytics in a DataTable, create a new analytic, edit analytics, and delete analytics. Using this tutorial: <a href="https://simpleisbetterthancomplex.com/tutorial/2016/11/15/how-to-implement-a-crud-using-ajax-and-json.html" rel="nofollow noreferrer">https://simpleisbetterthancomplex.com/tutorial/2016/11/15/how-to-implement-a-crud-using-ajax-and-json.html</a>, I accomplished all of these objectives in a regular Bootstrap HTML table (i.e. not in a DataTable).</p>
  
  <p>When I attempted to introduce a DataTable to the mix, I discovered that my DataTable was pulling from the HTML/DOM source, so it was not updating unless the page was refreshed. So I then realized that I need to either configure the DataTable to initially pull from HTML/DOM and then pull from AJAX, or I need to initially use Ajax as the source. </p>
  
  <p>It turns out, regular Django does not do a good job of serializing <code>ManyToMany</code> fields, so I opted to use DRF to serialize my <code>Analytic</code> model. This works to a degree: the JSON output looks decent, and the results show up in my DataTable. However, the data is still not updating when an Ajax call is made. In addition, DataTables does not really allow inline buttons for editing/deleting, which is why it was necessary to manually write those buttons into the HTML in the first place.</p>
  
  <p><strong>Question:</strong> How do I force a DataTable that is sourcing from HTML/DOM to update its data without refreshing the page when an Ajax CRUD operation is performed?</p>
  
  <p>views.py:</p>
  
  <pre><code>def analytic_list(request):
      analytics = Analytic.objects.all().select_related('analyticCategory').prefetch_related('dataSources')
      return render(request, 'analytics/analytic_list.html', {'analytics':analytics})
  
  def save_analytic_form(request, form, template_name):
      data = dict()
      if request.method == 'POST':
          if form.is_valid():
              form.save()
              data['form_is_valid'] = True
              analytics = Analytic.objects.all()
              data['html_analytic_list'] = render_to_string('analytics/includes/partial_analytic_list.html', {
                  'analytics': analytics
              })
          else:
              data['form_is_valid'] = False
      context = {'form': form}
      data['html_form'] = render_to_string(template_name, context, request=request)
      return JsonResponse(data)
  
  
  def analytic_create(request):
      if request.method == 'POST':
          form = AnalyticForm(request.POST)
      else:
          form = AnalyticForm()
      return save_analytic_form(request, form, 'analytics/includes/partial_analytic_create.html')
  
  
  def analytic_update(request, pk):
      analytic = get_object_or_404(Analytic, pk=pk)
      if request.method == 'POST':
          form = AnalyticForm(request.POST, instance=analytic)
      else:
          form = AnalyticForm(instance=analytic)
      return save_analytic_form(request, form, 'analytics/includes/partial_analytic_update.html')
  
  def analytic_delete(request, pk):
      analytic = get_object_or_404(Analytic, pk=pk)
      data = dict()
      if request.method == 'POST':
          analytic.delete()
          data['form_is_valid'] = True  # This is just to play along with the existing code
          analytics = Analytic.objects.all()
          data['html_analytic_list'] = render_to_string('analytics/includes/partial_analytic_list.html', {
              'analytics': analytics
          })
      else:
          context = {'analytic': analytic}
          data['html_form'] = render_to_string('analytics/includes/partial_analytic_delete.html',
              context,
              request=request,
          )
      return JsonResponse(data)
  </code></pre>
  
  <p>urls.py:</p>
  
  <pre><code>url(r'^dataanalytics/analytics/$', views.analytic_list, name='analytic_list'),
      url(r'^dataanalytics/analytics/create/$', views.analytic_create, name='analytic_create'),
      url(r'^dataanalytics/analytics/(?P&lt;pk&gt;\d+)/update/$', views.analytic_update, name='analytic_update'),
      url(r'^dataanalytics/analytics/(?P&lt;pk&gt;\d+)/delete/$', views.analytic_delete, name='analytic_delete'),
  </code></pre>
  
  <p>analytic_list.html:</p>
  
  <pre><code>{% block content %}
  &lt;!-- BUTTON TO TRIGGER THE ACTION --&gt;
    &lt;p&gt;
    &lt;button type="button"
            class="btn btn-primary js-create-analytic"
            data-url="{% url 'analytic_create' %}"&gt;
      &lt;span class="fa fa-plus"&gt;&lt;/span&gt;
      New analytic
    &lt;/button&gt;
  &lt;/p&gt;
  
    &lt;table class="table table-hover table-sm display responsive" width="100%" cellspacing="0" id="analytic-table"&gt;
      &lt;thead&gt;
        &lt;tr&gt;
          &lt;th class="all align-top"&gt;#&lt;/th&gt;
          &lt;th class="all align-top"&gt;Name&lt;/th&gt;
          &lt;th class="all align-top"&gt;Description&lt;/th&gt;
          &lt;th class="all align-top"&gt;Category&lt;/th&gt;
          &lt;th class="all align-top"&gt;Type&lt;/th&gt;
          &lt;th class="all align-top"&gt;Format&lt;/th&gt;
          &lt;th class="all align-top"&gt;Data Source(s)&lt;/th&gt;
          &lt;th class="all align-top"&gt;&lt;/th&gt;
          &lt;th class="none"&gt;Created By&lt;/th&gt;
          &lt;th class="none"&gt;Created Date&lt;/th&gt;
          &lt;th class="none"&gt;Modified By&lt;/th&gt;
          &lt;th class="none"&gt;Modified Date&lt;/th&gt;
        &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;!-- &lt;tbody&gt;
        {% include 'analytics/includes/partial_analytic_list.html' %}
      &lt;/tbody&gt; --&gt;
    &lt;/table&gt;
  
  &lt;!-- THE MODAL WE WILL BE USING --&gt;
    &lt;div class="modal fade" id="modal-analytic"&gt;
    &lt;div class="modal-dialog"&gt;
      &lt;div class="modal-content"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  {% endblock %}
  </code></pre>
  
  <p>partial_analytic_list.html:</p>
  
  <pre><code>{% for analytic in analytics %}
    &lt;tr&gt;
      &lt;td&gt;{{ analytic.id }}&lt;/td&gt;
      &lt;td&gt;{{ analytic.analytic }}&lt;/td&gt;
      &lt;td&gt;{{ analytic.analyticDescription }}&lt;/td&gt;
      &lt;td&gt;{{ analytic.analyticCategory }}&lt;/td&gt;
      &lt;td&gt;{{ analytic.analyticType }}&lt;/td&gt;
      &lt;td&gt;{{ analytic.analyticFormat }}&lt;/td&gt;
      &lt;td&gt;
        {% for data_source in analytic.dataSources.all %}
          {{ data_source }}
        {% endfor %}
      &lt;/td&gt;
      &lt;td&gt;
        &lt;button type="button"
                class="btn btn-warning btn-sm js-update-analytic"
                data-url="{% url 'analytic_update' analytic.id %}"&gt;
          &lt;span class="fa fa-pencil-alt"&gt;&lt;/span&gt;
        &lt;/button&gt;
        &lt;button type="button"
                class="btn btn-danger btn-sm js-delete-analytic"
                data-url="{% url 'analytic_delete' analytic.id %}"&gt;
          &lt;span class="fa fa-trash-alt"&gt;&lt;/span&gt;
        &lt;/button&gt;
      &lt;/td&gt;
      &lt;td&gt;{{ analytic.createdBy }}&lt;/td&gt;
      &lt;td&gt;{{ analytic.createdDateTime }}&lt;/td&gt;
      &lt;td&gt;{{ analytic.modifiedBy }}&lt;/td&gt;
      &lt;td&gt;{{ analytic.modifiedDateTime }}&lt;/td&gt;
    &lt;/tr&gt;
  {% empty %}
    &lt;tr&gt;
      &lt;td colspan="7" class="text-center bg-warning"&gt;No analytic&lt;/td&gt;
    &lt;/tr&gt;
  {% endfor %}
  </code></pre>
  
  <p>analytics.js:</p>
  
  <pre><code>$(function () {
  
    /* Functions */
  
    var loadForm = function () {
      var btn = $(this);
      $.ajax({
        url: btn.attr("data-url"),
        type: 'get',
        dataType: 'json',
        beforeSend: function () {
          $("#modal-analytic").modal("show");
        },
        success: function (data) {
          $("#modal-analytic .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) {
            $("#analytic-table tbody").html(data.html_analytic_list);
            $("#modal-analytic").modal("hide");
          }
          else {
            $("#modal-analytic .modal-content").html(data.html_form);
          }
        }
      });
      return false;
    };
  
  
    /* Binding */
  
    // Create analytic
    $(".js-create-analytic").click(loadForm);
    $("#modal-analytic").on("submit", ".js-analytic-create-form", saveForm);
  
    // Update analytic
    $("#analytic-table").on("click", ".js-update-analytic", loadForm);
    $("#modal-analytic").on("submit", ".js-analytic-update-form", saveForm);
  
    // Delete analytic
    $("#analytic-table").on("click", ".js-delete-analytic", loadForm);
    $("#modal-analytic").on("submit", ".js-analytic-delete-form", saveForm);
  
  
    var table = $('#analytic-table').DataTable(
      {
      });
  
  });
  </code></pre>