Home > database >  Page with a dynamic number of forms, each form for each table row
Page with a dynamic number of forms, each form for each table row

Time:12-24

The company has workers performing various activities during a day. Each activity has start_time and finish_time. It is usual that workers forget to beep the end of activity (finish_time) and that's because there is a stored procedure read_unended time_from time_to which reads records between time_from and time_to which has not finish_time (is NULL). For example

id  name    day         start_time              finish_time place   activity
38  Thomas  2021-12-03  2021-12-03 08:51:38.000 NULL    p1  a1
28  Charles 2021-12-02  2021-12-02 12:29:03.000 NULL    p2  a2
49  John    2021-12-06  2021-12-06 11:59:48.000 NULL    p3  a3
68  Jessie  2021-12-08  2021-12-08 10:55:12.000 NULL    p4  a4
82  Susanne 2021-12-10  2021-12-10 12:38:03.000 NULL    p5  a5

There is a form in (forms.py)

class FromToForm(Form):
    start_date = DateField(widget=AdminDateWidget())
    start_time = TimeField(widget=AdminTimeWidget())
    end_date = DateField(widget=AdminDateWidget())
    end_time = TimeField(widget=AdminTimeWidget())

There is a view in (views.py) which displays such a table.

def ending(req):
    from_to_form = FromToForm()
    result = []
    context = {
                'form': from_to_form,
                'result': result
              }
    if req.method == "POST":
        from_to_form = FromToForm(req.POST)
        if from_to_form.is_valid():
            start = datetime.combine(from_to_form.cleaned_data['start_date'], from_to_form.cleaned_data['start_time']).isoformat()
            end = datetime.combine(from_to_form.cleaned_data['end_date'], from_to_form.cleaned_data['end_time']).isoformat()
            with connections["mssql_database"].cursor() as cursor:
                cursor.execute("EXEC read_unended @dt_od='%s', @dt_do='%s'" % (start, end))
                result = cursor.fetchall()
            context['result'] = result
            return render(req, 'ending.html', context)
        else:
            return render(req, 'ending.html', context)
    else:
        return render(req, 'ending.html', context)

and an associated template in templates.py.

<form action='.' method='POST'>{% csrf_token %}
                {{ form.media }}
                {{ form.as_p }}
                <input type='submit' value='Read unended'  />
        </form>
{% if result %}
        <table >
                <thead>
                        <tr>
                                <th>id</th>
                                <th>name</th>
                                <th>day</th>
                                <th>start_time</th>
                                <th>finish_time</th>
                                <th>place</th>
                                <th>activity</th>
                        </tr>
                </thead>
                <tbody>
                   {%for i in result %}
                     <tr>
                           <td>{{i.0}}</td>
                           <td>{{i.1}}</td>
                           <td>{{i.2}}</td>
                           <td>{{i.3}}</td>
                           <td>CELL TO INSERT END TIME*</td>
                           <td>{{i.5}}</td>
                           <td>{{i.6}}</td>
                           <td>BUTTON TO FINISH THIS ACTIVITY**<td/>
                     </tr>
                   {% endfor %}
                </tbody>
        </table>
        {% else %}
           Every activity is ended
        {% endif %}

** and * is not implemented yet. I'd like to implement the following functionality. In every row of the dynamically generated table, there should be a button ** to finish this activity (this row) with the time * the user of the application inserts. In that moment the page should refresh and that row should not be displayed anymore, because this activity has already finish_time assigned. How can I implement such a view and template? Do I need to add a dynamically generated fields to the existing form? What do you suggest?

CodePudding user response:

You have to create a function to post your data and another to get the table body

template.html

     <table >
    <thead>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>day</th>
            <th>start_time</th>
            <th>finish_time</th>
            <th>place</th>
            <th>activity</th>
        </tr>
    </thead>
    <tbody id="tbodyId">
        {% for i in result %}
        <tr>
            <td id="tdId_{{ i.0 }}0">{{i.0}}</td>
            <td id="tdId_{{ i.0 }}1">{{i.1}}</td>
            <td id="tdId_{{ i.0 }}2">{{i.2}}</td>
            <td id="tdId_{{ i.0 }}3">{{i.3}}</td>
            <td><input type="date" id="tdId_{{i.0}}4"/></td>
            <td id="tdId_{{ i.0 }}5">{{i.5}}</td>
            <td id="tdId_{{ i.0 }}6">{{i.6}}</td>
            <td><button value="{{i.0}}" type="button" onclick="setEndActivity(this.value)">End</button><td/>
        </tr>
        {% endfor %}
    </tbody>
</table>
<script>
function loadResults(activityId) {
    let xhttpRequest = new XMLHttpRequest()
    const start_date = document.getElementById(`tdId_${activityId}2`).value
    const start_time = document.getElementById(`tdId_${activityId}3`).value
    const end_date = document.getElementById(`tdId_${activityId}2`).value
    const end_time = document.getElementById(`tdId_${activityId}4`).value
    xhttpRequest.onreadystatechange = function (data) {
        if (this.readyState === 4 && this.status === 200) {
            document.getElementById("tbodyId").innerHTML = this.response
        }
    }
    xhttpRequest.open("GET", `./ajax/load-results?start_date=${start_date}&end_date=${end_date}&start_time=${start_time}&end_time=${end_time}`, true)
    xhttpRequest.send()
}

function setEndActivity(activityId) {
    const dataToBackEnd = new FormData()
    dataToBackEnd.append("activity", activityId)
    dataToBackEnd.append("start_date", document.getElementById(`tdId_${activityId}2`).value)
    dataToBackEnd.append("start_time", document.getElementById(`tdId_${activityId}3`).value)
    dataToBackEnd.append("end_date", document.getElementById(`tdId_${activityId}2`).value)
    dataToBackEnd.append("end_time", document.getElementById(`tdId_${activityId}4`).value)
    const request = new Request('./ajax/end-activity/',
    {
        method: 'POST',
        headers: { 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value },
        body: dataToBackEnd
    })
    fetch(request, {
        method: 'POST',
        mode: 'same-origin'
    }).then(
        function(response) {
            if (response.status === 200) {
                loadResults(activityId)
            } else {
                alert("Error")
            }
        }
    )
}
</script>

In your views you will add 2 views, or just 1 if you modify the "ending" view to receive the post and return a http response

views.py

def ajax_ending(req):
    if req.method == "POST":
        from_to_form = FromToForm(req.POST)
        if from_to_form.is_valid():
            # save in database
            return HttpResponse(status=200)
        else:
            return HttpResponse(status=400)
    else:
        return HttpResponse(status=405)


def ajax_load_results(request):
if request.method == "GET":
    if request.user:
        # get the activities
        start_date = request.GET['start_date']
        start_time = request.GET['start_time']
        start_date_time = f'{start_date} {start_time}'
        end_date = request.GET['end_date']
        end_time = request.GET['end_time']
        end_date_time = f'{end_date} {end_time}'
        # this datetime.strptime depends of how you write your date and time
        start = datetime.strptime(start_date_time, "%d-%m-%Y %H:%M:%S").isoformat()
        end = datetime.strptime(end_date_time, "%d-%m-%Y %H:%M:%S").isoformat()
        # i don't understand why you are executing queries like this, so i assume they work
        with connections["mssql_database"].cursor() as cursor:
            cursor.execute("EXEC read_unended @dt_od='%s', @dt_do='%s'" % (start, end))
            result = cursor.fetchall()
        context['result'] = result
        return render(request, 'ajax-ending.html', {"context": context})
    else:
        return HttpResponse(status=403)
else:
    return HttpResponse(status=405)

And you will have another template.html just for tbody of table

ajax-ending.html

{% for i in result %}
    <tr>
        <td id="tdId_{{ i.0 }}0">{{i.0}}</td>
        <td id="tdId_{{ i.0 }}1">{{i.1}}</td>
        <td id="tdId_{{ i.0 }}2">{{i.2}}</td>
        <td id="tdId_{{ i.0 }}3">{{i.3}}</td>
        <td id="tdId_{{ i.0 }}4">CELL TO INSERT END TIME*</td>
        <td id="tdId_{{ i.0 }}5">{{i.5}}</td>
        <td id="tdId_{{ i.0 }}6">{{i.6}}</td>
        <td><button value="{{i.0}}" type="button" onclick="setEndActivity(this.value)">End</button><td/>
    </tr>
{% endfor %}

ulrs.py

from django.urls import path
from . import views


app_name = 'your_app_name'

urlpatterns = [
        'ending/',
        views.ending, 
        name='ending'),
    path(
        'ending/ajax/end-activity/',
        views.ajax_ending, 
        name='ajax_ending'),
    path(
        'ending/ajax/load-results/',
        views.ajax_load_results, 
        name='ajax_load_results'),
]

(Not tested, if throws a error let me know)

  • Related