1

I have a Django project with an Analytic model. This model is a list of analytics. It has a ForeignKeyField and a ManyToMany 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: https://simpleisbetterthancomplex.com/tutorial/2016/11/15/how-to-implement-a-crud-using-ajax-and-json.html, I accomplished all of these objectives in a regular Bootstrap HTML table (i.e. not in a DataTable).

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.

It turns out, regular Django does not do a good job of serializing ManyToMany fields, so I opted to use DRF to serialize my Analytic 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.

Question: 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?

views.py:

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)

urls.py:

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<pk>\d+)/update/$', views.analytic_update, name='analytic_update'),
    url(r'^dataanalytics/analytics/(?P<pk>\d+)/delete/$', views.analytic_delete, name='analytic_delete'),

analytic_list.html:

{% block content %}
<!-- BUTTON TO TRIGGER THE ACTION -->
  <p>
  <button type="button"
          class="btn btn-primary js-create-analytic"
          data-url="{% url 'analytic_create' %}">
    <span class="fa fa-plus"></span>
    New analytic
  </button>
</p>

  <table class="table table-hover table-sm display responsive" width="100%" cellspacing="0" id="analytic-table">
    <thead>
      <tr>
        <th class="all align-top">#</th>
        <th class="all align-top">Name</th>
        <th class="all align-top">Description</th>
        <th class="all align-top">Category</th>
        <th class="all align-top">Type</th>
        <th class="all align-top">Format</th>
        <th class="all align-top">Data Source(s)</th>
        <th class="all align-top"></th>
        <th class="none">Created By</th>
        <th class="none">Created Date</th>
        <th class="none">Modified By</th>
        <th class="none">Modified Date</th>
      </tr>
    </thead>
    <!-- <tbody>
      {% include 'analytics/includes/partial_analytic_list.html' %}
    </tbody> -->
  </table>

<!-- THE MODAL WE WILL BE USING -->
  <div class="modal fade" id="modal-analytic">
  <div class="modal-dialog">
    <div class="modal-content">
    </div>
  </div>
{% endblock %}

partial_analytic_list.html:

{% for analytic in analytics %}
  <tr>
    <td>{{ analytic.id }}</td>
    <td>{{ analytic.analytic }}</td>
    <td>{{ analytic.analyticDescription }}</td>
    <td>{{ analytic.analyticCategory }}</td>
    <td>{{ analytic.analyticType }}</td>
    <td>{{ analytic.analyticFormat }}</td>
    <td>
      {% for data_source in analytic.dataSources.all %}
        {{ data_source }}
      {% endfor %}
    </td>
    <td>
      <button type="button"
              class="btn btn-warning btn-sm js-update-analytic"
              data-url="{% url 'analytic_update' analytic.id %}">
        <span class="fa fa-pencil-alt"></span>
      </button>
      <button type="button"
              class="btn btn-danger btn-sm js-delete-analytic"
              data-url="{% url 'analytic_delete' analytic.id %}">
        <span class="fa fa-trash-alt"></span>
      </button>
    </td>
    <td>{{ analytic.createdBy }}</td>
    <td>{{ analytic.createdDateTime }}</td>
    <td>{{ analytic.modifiedBy }}</td>
    <td>{{ analytic.modifiedDateTime }}</td>
  </tr>
{% empty %}
  <tr>
    <td colspan="7" class="text-center bg-warning">No analytic</td>
  </tr>
{% endfor %}

analytics.js:

$(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(
    {
    });

});

2 Answers 2

1

I'm going to assume you're talking about JQuery Datatables. You're SO close, just missing a few bits! You need to destroy and reinitialize the table, no need to use .draw(). Do as shown here:

Ajax:

  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) {
          $("#modal-analytic").modal("hide"); //hide it first if you want
          $("#analytic-table").DataTable().destroy(); //this will flush DT's cache
          $("#analytic-table tbody").html(data.html_analytic_list); // replace the html
          $("#analytic-table").DataTable(); // re-initialize the DataTable
        }
        else {
          $("#modal-analytic .modal-content").html(data.html_form);
        }
      }
    });
    return false;
  };

Nice work and good luck! Late answer, but maybe it will help somebody.

Sign up to request clarification or add additional context in comments.

Comments

0

It looks like your table updates are using this:

$("#analytic-table tbody").html(data.html_analytic_list);

You will need to you Datatables APIs to update the Datatables data. Since you are directly updating the HTML Datatables is not aware of the updates. One option is to use something like rows().invalidate() to have Datatables update its data cache after you update with HTML methods.

However a better option is to use rows.add() for multiple rows or row.add() for a single row.

Since it looks like you have tr elements in your data then you can use something like this for one row or multiple rows, respectively:

table.row.add($(data.html_analytic_list)).get(1)).draw();
table.rows.add($(data.html_analytic_list))).draw();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.