Search code examples
htmldjangoajaxajax-updatedjango-3.0

Django update HTML table with AJAX


Ok, I tried to understand all the posts here, but I couldn't. Some are outdated, some are not similar to my problem.

Don't mind me for asking again.

I have a simple HTML table, with some data in it, and I just need to update the data, when it's changed, without refreshing it.

The object has 'is_locked' field, and what I need to do is, when an object is locked(is_locked=True), a small locker icon shows. I can do all of that but after a manual refresh, which is not good.

So when a user, locks an object, after a redirect(page refreshes), locker icon shows. But I need that in a real-time, so when the two users look at the same table, at the same time, after an object lock(from one user), that icon needs to appear on the other user's browser, without refreshing the page.

I just need that 'extra' content(locker icon) to show up in real-time, when the user locks an object.

Try to ignore the custom names(they're not in English)


My models.py :

from django.db import models

class OpportunityList(models.Model):
    name = models.CharField(max_length=30, blank=True)
    address = models.CharField(max_length=100, blank=True)
    age = models.IntegerField(blank=True, null=True)
    is_locked = models.BooleanField(blank=True, null=True)
    ...

views.py :

from django.http import JsonResponse
from django.shortcuts import render

from .models import OpportunityList
...
@login_required(login_url='registration/login')
def OptikaList(request):
    optika = OpportunityList.objects.all().filter(opp_type='optika')
    context = {
        'optika': optika
    }

    if request.is_ajax():
        data = {'rendered_table':render_to_string('opportunity/get_more_tables.html', context=context)}
        return JsonResponse(data)

    return render(request, 'opportunity/opp_optika.html', context)


def updateTable(request):
    qs = OpportunityList.objects.all().values()
    return JsonResponse({'users': list(qs)})

urls.py :

from django.urls import path
from .views import *

urlpatterns = [
    ...
    path('optika/', OptikaList, name='optika'),
    path('ajax/update/', update_table, name='update_table'),
    ...

opp_optika.html, where the table is, with ajax script.

{% extends 'base.html' %}
{% load static %}
{% block title %} Optika {% endblock %}
{% block content_row %}

    {% if messages %}
        {% for message in messages %}
            <div class=" container-fluid alert alert-success alert-dismissible" role="alert">
                <button type="button" class="close" data-dismiss="alert">&times;</button>
                <h4 class="alert-heading">Uspešno!</h4>
                <hr>
                <p class="mb-0">{{ message }}</p>
            </div>
        {% endfor %}
    {% endif %}

    <div class="card-body">
        <div class="table-responsive">
            <table class="table table-bordered table table-hover dt-responsive" id="dataTable">
                {% include 'opportunity/opp_breadcrumb.html' %}
                <h2 style="margin-left: 50%;">OPTIKA</h2>

                <thead>
                <tr>
                    <th>Adresa Korisnika</th>
                    <th>Područje</th>
                    <th>Korisnik</th>
                    <th>TIS</th>
                    <th>Napomena</th>
                    <th>Akcije</th>
                    <th style="width: 5px">STATUS</th>
                </tr>
                </thead>
                <tbody>
                {% for opp in optika %}
                    <tr>
                        <td>{{ opp.optika_korisnik_adr }}</td>
                        <td>{{ opp.oppc_place }}</td>
                        <td>{{ opp.optika_korisnik }}</td>
                        <td>{{ opp.optika_korisnik_tis_id }}</td>
                        <td>{% if opp.oppc_comment %}{{ opp.oppc_comment }}{% else %}Bez napomene{% endif %}</td>
                        <td>
                            <button type="button" class="btn btn-success btn-sm" data-toggle="modal"
                                    data-target="#detalji_modal{{ opp.pk }}">
                                Detalji
                            </button>
                        </td>
                        <td>
                            {% if opp.is_locked %}
                                <form action="{% url 'opportunity:delete_locked' opp.pk %}" method="POST"
                                      style="display: table-cell">
                                    {% csrf_token %}
                                    <button class="btn"><i style="width: 50px;color: red" class="fa fa-lock"
                                                           aria-hidden="true"></i></button>
                                </form>
                            {% endif %}
                        </td>
                    </tr>
                    <!------------------------------------- DETALJIMODAL ---------------------------------------------->
                    <div class="modal fade bd-example-modal-lg" tabindex="-1" role="dialog"
                         id="detalji_modal{{ opp.pk }}"
                         aria-labelledby="detalji_modalLabel">
                        <div class="modal-dialog modal-lg" role="document">
                            <div class="modal-content">
                                <div class="modal-header">
                                    <h5 class="modal-title" id="detalji_modalLabel">Detalji</h5>
                                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                        <span aria-hidden="true">&times;</span>
                                    </button>
                                </div>
                                <div class="modal-body" id="">
                                    <div class="container-fluid">
                                        <h5>{{ opp.optika_korisnik }}</h5>
                                        <p>{{ opp.optika_korisnik_adr.title }}</p>
                                        <p>Broj linije : {{ opp.optika_broj_linije }}</p>
                                        <p>Kontakt : {{ opp.optika_kontakt_tel1 }}</p>
                                        <hr>
                                        <p>Okvirni Datum : {{ opp.optika_instal_okvirni_datum }}</p>
                                        <p>TIS Posla : {{ opp.optika_posao_tis_id }}</p>
                                        <p>Zahtev Info : {{ opp.optika_zahev_uneo }}</p>
                                        <hr>
                                        <p>Datum Unosa : {{ opp.optika_zahtev_datum_unosa }}</p>
                                        <hr>
                                        <p>Napomena : <br> <br>
                                            {% if opp.oppc_comment %} {{ opp.oppc_comment }} {% else %} Nema
                                                napomene za korisnika {{ opp.optika_korisnik|title }}. {% endif %}
                                        </p>
                                    </div>
                                </div>
                                <div class="modal-footer">
                                    <p style="margin-right: auto">Zahtev uneo/la : {{ opp.optika_zahev_uneo }}</p>
                                    {% if not opp.is_locked %}
                                        <form action="{% url 'opportunity:poziv' opp.pk %}" method="POST">
                                            {% csrf_token %}
                                            <button type="submit" name="poziv" class="btn btn-outline-danger">
                                                Poziv
                                            </button>
                                        </form>
                                    {% endif %}
                                    {% if opp.is_locked %}
                                        <form action="{% url 'opportunity:delete_locked' opp.pk %}" method="POST"
                                              style="display: table-cell">
                                            {% csrf_token %}
                                            <button type="submit" class="btn btn-dark">
                                                <i class="fas fa-lock-open"></i>
                                                Otključaj
                                            </button>
                                        </form>
                                    {% endif %}
                                    <button type="button" class="btn btn-outline-primary" data-dismiss="modal">
                                        Zatvori
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!------------------------------------ DETALJI ENDMODAL ------------------------------------------->
                {% endfor %}
                </tbody>
            </table>
        </div>
        <script>
            $(document).ready(function () {
                setInterval(function () {
                    $.ajax({
                        url: '{% url 'opportunity:optika' %}',
                        type: "get",
                        cache: true,
                        dataType: 'html',
                        success: function (data) {
                            console.log("success");
                            $('#dataTable').html(data.rendered_table);
                        },
                        error: function (data) {
                            alert("Got an error dude " + data);
                        }
                    });
                }, 5000);
            });
        </script>
{% endblock content_row %}

get_more_tables.html :

<tr>
    <th>Adresa Korisnika</th>
    <th>Područje</th>
    <th>Korisnik</th>
    <th>TIS</th>
    <th>Napomena</th>
    <th>Akcije</th>
    <th style="width: 5px">STATUS</th>
</tr>
{% for opp in optika %}
    <tr>
        <td>{{ opp.optika_korisnik_adr }}</td>
        <td>{{ opp.oppc_place }}</td>
        <td>{{ opp.optika_korisnik }}</td>
        <td>{{ opp.optika_korisnik_tis_id }}</td>
        <td>{% if opp.oppc_comment %}{{ opp.oppc_comment }}{% else %}Bez napomene{% endif %}</td>
        <td>
            <button type="button" class="btn btn-success btn-sm" data-toggle="modal"
                    data-target="#detalji_modal{{ opp.pk }}">
                Detalji
            </button>
        </td>
        <td>
            {% if opp.is_locked %}
                <form action="{% url 'opportunity:delete_locked' opp.pk %}" method="POST"
                      style="display: table-cell">
                    {% csrf_token %}
                    <button class="btn"><i style="width: 50px;color: red" class="fa fa-lock"
                                           aria-hidden="true"></i></button>
                </form>
            {% endif %}
        </td>
    </tr>
    <!------------------------------------- DETALJIMODAL ---------------------------------------------->
    <div class="modal fade bd-example-modal-lg" tabindex="-1" role="dialog"
         id="detalji_modal{{ opp.pk }}"
         aria-labelledby="detalji_modalLabel">
        <div class="modal-dialog modal-lg" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="detalji_modalLabel">Detalji</h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body" id="">
                    <div class="container-fluid">
                        <h5>{{ opp.optika_korisnik }}</h5>
                        <p>{{ opp.optika_korisnik_adr.title }}</p>
                        <p>Broj linije : {{ opp.optika_broj_linije }}</p>
                        <p>Kontakt : {{ opp.optika_kontakt_tel1 }}</p>
                        <hr>
                        <p>Okvirni Datum : {{ opp.optika_instal_okvirni_datum }}</p>
                        <p>TIS Posla : {{ opp.optika_posao_tis_id }}</p>
                        <p>Zahtev Info : {{ opp.optika_zahev_uneo }}</p>
                        <hr>
                        <p>Datum Unosa : {{ opp.optika_zahtev_datum_unosa }}</p>
                        <hr>
                        <p>Napomena : <br> <br>
                            {% if opp.oppc_comment %} {{ opp.oppc_comment }} {% else %} Nema
                                napomene za korisnika {{ opp.optika_korisnik|title }}. {% endif %}
                        </p>
                    </div>
                </div>
                <div class="modal-footer">
                    <p style="margin-right: auto">Zahtev uneo/la : {{ opp.optika_zahev_uneo }}</p>
                    {% if not opp.is_locked %}
                        <form action="{% url 'opportunity:poziv' opp.pk %}" method="POST">
                            {% csrf_token %}
                            <button type="submit" name="poziv" class="btn btn-outline-danger">
                                Poziv
                            </button>
                        </form>
                    {% endif %}
                    {% if opp.is_locked %}
                        <form action="{% url 'opportunity:delete_locked' opp.pk %}" method="POST"
                              style="display: table-cell">
                            {% csrf_token %}
                            <button type="submit" class="btn btn-dark">
                                <i class="fas fa-lock-open"></i>
                                Otključaj
                            </button>
                        </form>
                    {% endif %}
                    <button type="button" class="btn btn-outline-primary" data-dismiss="modal">
                        Zatvori
                    </button>
                </div>
            </div>
        </div>
    </div>
    <!------------------------------------ DETALJI ENDMODAL ------------------------------------------->
{% endfor %}

Solution

  • You have got a number of issues in your code.

    What your current AJAX call does: It calls your displayUser view. This view renders the whole crud.html template (including the javascript!) and returns it. Your javascript puts this in the place you defined. So after the first AJAX call you've got some nested table structure with two javascript blocks, both running in parrallel.

    And then these two functions start calling the view again receiving fully rendered pages. So after some time you have got a muddled up web page with multiple scripts running in parrallel, putting fully rendered pages all over the place. This will surely eat up memory.

    What you could do is check whether your request is an ajax request and in that case render only the inner table and return this back to your javascript:

    def displayUser(request):
        users = CrudUser.objects.all()
        context = {
            'users': users
        }
    
        if request.is_ajax():
            data = {'rendered_table': render_to_string('table_contents.html', context=context)}
            return JsonResponse(data)
    
        return render(request, 'crud.html', context) 
    

    The sub template for the inner table:

    # table_contents.html
    <tr>
        <th>Name</th>
        <th>Address</th>
        <th>Age</th>
    </tr>
    {% for user in users %}
        <tr>
            <td>{{ user.name }}</td>
            <td>{{ user.address }}</td>
            <td>{{ user.age }}</td>
        </tr>
    {% endfor %}
    

    Now you can put that in your template in your javascript

    $('#_appendHere').html(data.rendered_table);
    

    One final note: In your ajax function I think it should be data_type and not dataType.