Django input checkbox example (ajax example)

jQuery + vanilla JavaScript

 1 $('#id_etat_demande').change(function () {
 2     let etat_demande= $(this).val()
 3     let id_demande_article = document.getElementById('id_demande_article').textContent;
 4     let widget = document.getElementById('id_send_email');
 5     let send_email = widget.checked;
 6     // alert("id_demande_article=" + id_demande_article +" etat_demande=" + etat_demande);
 7     let url = '{% url "articles:ajax_update_demande_article" %}';
 8     $.ajax(
 9             {
10                 url: url,
11                 data: {
12                     'id_demande_article': id_demande_article,
13                     'etat_demande': etat_demande,
14                     'send_email': send_email,
15                 },
16                 dataType: 'json',
17                 success: function (data) {
18                 }
19         }
20     );
21 });

urls.py

path(
    "ajax/demande_article_update",
    view_update_demande_article,
    name="ajax_update_demande_article",
),

articles_update.html

  1{% extends "base.html" %}
  2{% load static %}
  3{% load djmoney %}
  4{% load article %}
  5
  6{% block stylesheet %}
  7    <link rel="stylesheet" href="{% static 'css/couleurs_demande_article.css' %}">
  8
  9    <!-- CSS pour datetime picker -->
 10    <link rel="stylesheet" href="{% static 'js/jquery.datetimepicker.min.css' %}">
 11
 12    <!-- CSS pour autocomplete_light -->
 13    <link rel="stylesheet" href="{% static 'select2/css/select2.css' %}">
 14    <link rel="stylesheet" href="{% static 'autocomplete_light/select2.css' %}">
 15
 16    <!-- Bordure des tables -->
 17    <link rel="stylesheet" href="{% static 'css/table.css' %}">
 18{% endblock  %}
 19
 20
 21{% block javascript %}
 22
 23    {# a employer dans les templates fils->{{ block.super }} #}
 24    {{ block.super }}
 25    <script src="{% static 'js/jquery.datetimepicker.full.js' %}"></script>
 26
 27    <script src="{% static 'autocomplete_light/jquery.init.js' %}"></script>
 28    <script src="{% static 'autocomplete_light/autocomplete.init.js' %}"></script>
 29    <script src="{% static 'select2/js/select2.full.js' %}"></script>
 30    <script src="{% static 'autocomplete_light/forward.js' %}"></script>
 31    <script src="{% static 'autocomplete_light/select2.js' %}"></script>
 32
 33
 34{% endblock javascript %}
 35
 36
 37
 38{% block extra_js %}
 39{# Code composé de Javascript et de Django Template Language #}
 40{# ========================================================= #}
 41
 42<script type="text/javascript">
 43
 44{% comment %}
 45https://learn.jquery.com/using-jquery-core/document-ready/
 46
 47Experienced developers sometimes use the shorthand $() for $( document ).ready().
 48If you are writing code that people who aren't experienced with jQuery may see,
 49it's best to use the long form.
 50
 51{% endcomment %}
 52
 53$(document).ready(function() {
 54
 55    // langue française pour les datetime pickers
 56    $.datetimepicker.setLocale('fr');
 57    $('#id_description').change(function () {
 58        let description= $(this).val()
 59        let id_demande_article = document.getElementById('id_demande_article').textContent;
 60        let widget = document.getElementById('id_send_email');
 61        let send_email = widget.checked;
 62        // alert("id_demande_article=" + id_demande_article +" description=" + description);
 63        let url = '{% url "articles:ajax_update_demande_article" %}';
 64        $.ajax(
 65                {
 66                    url: url,
 67                    data: {
 68                        'id_demande_article': id_demande_article,
 69                        'description': description,
 70                        'send_email': send_email,
 71                    },
 72                    dataType: 'json',
 73                    success: function (data) {
 74                        {% for form_achat in formset_achats %}
 75                            {% if forloop.counter >= 2 %}
 76                                document.getElementById('libelle_description_{{ forloop.counter0 }}').textContent = data.libelle_description;
 77                            {% endif %}
 78                        {% endfor %}
 79                    }
 80            }
 81        );
 82    });
 83    $('#id_etat_demande').change(function () {
 84        let etat_demande= $(this).val()
 85        let id_demande_article = document.getElementById('id_demande_article').textContent;
 86        let widget = document.getElementById('id_send_email');
 87        let send_email = widget.checked;
 88        // alert("id_demande_article=" + id_demande_article +" etat_demande=" + etat_demande);
 89        let url = '{% url "articles:ajax_update_demande_article" %}';
 90        $.ajax(
 91                {
 92                    url: url,
 93                    data: {
 94                        'id_demande_article': id_demande_article,
 95                        'etat_demande': etat_demande,
 96                        'send_email': send_email,
 97                    },
 98                    dataType: 'json',
 99                    success: function (data) {
100                    }
101            }
102        );
103    });
104    {% for form_achat in formset_achats %}
105        $('#id_achatservice_related-{{ forloop.counter0 }}-etat_poste').change(function () {
106            let etat_poste = $(this).val()
107            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
108            let url = '{% url "articles:ajax_update_achat_service" %}';
109            $.ajax(
110                    {
111                        url: url,
112                        data: {
113                            'id': achatservice_id,
114                            'etat_poste': etat_poste,
115                        },
116                        dataType: 'json',
117                        success: function (data) {
118                            // alert("Retour:" + data.update);
119                        }
120                }
121            );
122        });
123        $('#id_achatservice_related-{{ forloop.counter0 }}-reponse').change(function () {
124            let reponse = $(this).val()
125            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
126            let url = '{% url "articles:ajax_update_achat_service" %}';
127            $.ajax(
128                    {
129                        url: url,
130                        data: {
131                            'id': achatservice_id,
132                            'reponse': reponse,
133                        },
134                        dataType: 'json',
135                        success: function (data) {
136                            // alert("Retour:" + data.update);
137                        }
138                }
139            );
140        });
141        $('#id_achatservice_related-{{ forloop.counter0 }}-article').change(function () {
142            let article_id = $(this).val()
143            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
144            let libelle_article = document.getElementById('libelle_article_{{ forloop.counter0 }}');
145
146            // alert("achatservice_id=" + achatservice_id + " article_id=" + article_id + + " libelle=" + libelle_article.textContent);
147            let url = '{% url "articles:ajax_update_achat_service" %}';
148
149            $.ajax(
150                    {
151                        url: url,
152                        data: {
153                            'id': achatservice_id,
154                            'article_id': article_id,
155                        },
156                        dataType: 'json',
157                        success: function (data) {
158                            // alert("Retour:" + data.update + " libelle article:" + data.libelle_article);
159                            libelle_article.textContent = data.libelle_article;
160                        }
161                }
162            );
163        });
164        $('#id_achatservice_related-{{ forloop.counter0 }}-projet').change(function () {
165            let projet_id = $(this).val()
166            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
167            let libelle_projet = document.getElementById('libelle_projet_{{ forloop.counter0 }}');
168
169            // alert("achatservice_id=" + achatservice_id + " projet_id=" + projet_id + " libelle=" + libelle_projet.textContent);
170            let url = '{% url "articles:ajax_update_achat_service" %}';
171            $.ajax(
172                    {
173                        url: url,
174                        data: {
175                            'id': achatservice_id,
176                            'projet_id': projet_id,
177                        },
178                        dataType: 'json',
179                        success: function (data) {
180                            // alert("Retour:" + data.update);
181                            libelle_projet.textContent = data.libelle_projet;
182                        }
183                }
184            );
185        });
186        $('#id_achatservice_related-{{ forloop.counter0 }}-prestataire').change(function () {
187            {# un prestataire est un tiers (classe Tiers et Prestataire proxy sur Tiers #}
188            let prestataire_id = $(this).val()
189            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
190            let libelle_prestataire = document.getElementById('libelle_prestataire_{{ forloop.counter0 }}');
191
192            // alert("achatservice_id=" + achatservice_id + " prestataire_id=" + prestataire_id + " libelle=" + libelle_prestataire.textContent);
193            let url = '{% url "articles:ajax_update_achat_service" %}';
194            $.ajax(
195                    {
196                        url: url,
197                        data: {
198                            'id': achatservice_id,
199                            'prestataire_id': prestataire_id,
200                        },
201                        dataType: 'json',
202                        success: function (data) {
203                            // alert("Retour:" + data.update);
204                            libelle_prestataire.textContent = data.libelle_prestataire;
205                        }
206                }
207            );
208        });
209        $('#id_achatservice_related-{{ forloop.counter0 }}-description').change(function () {
210            let description = $(this).val()
211            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
212            let url = '{% url "articles:ajax_update_achat_service" %}';
213            $.ajax(
214                    {
215                        url: url,
216                        data: {
217                            'id': achatservice_id,
218                            'description': description,
219                        },
220                        dataType: 'json',
221                        success: function (data) {
222                            // alert("Retour:" + data.update);
223                        }
224                }
225            );
226        });
227        $('#id_achatservice_related-{{ forloop.counter0 }}-quantite').change(function () {
228            let quantite = $(this).val()
229            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
230            let url = '{% url "articles:ajax_update_achat_service" %}';
231            // alert("achatservice_id=" + achatservice_id + " quantite=" + quantite);
232            $.ajax(
233                    {
234                        url: url,
235                        data: {
236                            'id': achatservice_id,
237                            'quantite': quantite,
238                        },
239                        dataType: 'json',
240                        success: function (data) {
241                            // alert("Retour:" + data.update);
242                        }
243                }
244            );
245        });
246        $('#id_achatservice_related-{{ forloop.counter0 }}-prix_unitaire_0').change(function () {
247            let prix_unitaire = $(this).val()
248            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
249            let url = '{% url "articles:ajax_update_achat_service" %}';
250            // alert("achatservice_id=" + achatservice_id + " prix unitaire=" + prix_unitaire);
251            $.ajax(
252                    {
253                        url: url,
254                        data: {
255                            'id': achatservice_id,
256                            'prix_unitaire': prix_unitaire,
257                        },
258                        dataType: 'json',
259                        success: function (data) {
260                            // alert("Retour:" + data.update);
261                        }
262                }
263            );
264        });
265
266        $('#id_achatservice_related-{{ forloop.counter0 }}-prix_unitaire_1').change(function () {
267            let currency = $(this).val()
268            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
269            let url = '{% url "articles:ajax_update_achat_service" %}';
270            // alert("achatservice_id=" + achatservice_id + " currency=" + currency);
271            $.ajax(
272                    {
273                        url: url,
274                        data: {
275                            'id': achatservice_id,
276                            'currency': currency,
277                        },
278                        dataType: 'json',
279                        success: function (data) {
280                            // alert("Retour:" + data.update);
281                        }
282                }
283            );
284
285        });
286        $('#id_achatservice_related-{{ forloop.counter0 }}-date_butoir').change(function () {
287            let date_butoir = $(this).val()
288            let achatservice_id = $('#id_achatservice_related-{{ forloop.counter0 }}-id').val();
289            let url = '{% url "articles:ajax_update_achat_service" %}';
290            $.ajax(
291                    {
292                        url: url,
293                        data: {
294                            'id': achatservice_id,
295                            'datetime_livraison': date_butoir,
296                        },
297                        dataType: 'json',
298                        success: function (data) {
299                        }
300                }
301            );
302        });
303        {# Choix de la livraison souhaitée #}
304        $('#id_achatservice_related-{{ forloop.counter0 }}-date_butoir').datetimepicker(
305            {
306                // http://marcocecchetti.it/Post/Articolo/608
307                weeks: true,
308                rtl: false,
309                // formatTime:'H:i',
310                // formatDate:'d/m/Y',
311                format: 'd/m/Y H:i',
312                // date
313                datepicker: true,
314                opened: false,
315                todayButton: true,     // Show button "Go To Today"
316                defaultSelect:  true,  // Highlight the current date even if the input is empty
317                dayOfWeekStart : 1, // Start week DatePicker. Default Sunday=0.
318                defaultDate: false,
319                // https://secure.php.net/manual/fr/function.date.php
320                minDate:0,    // today
321                weekends : [],
322                disabledDates : [],
323                // time
324                timepicker:true,
325                timepickerScrollbar:true,
326                step: 60,
327                defaultTime:'10:00',
328                allowTimes:[
329                  '8:00', '9:00', '10:00', '11:00', '12:00',
330                  '13:00', '14:00', '15:00', '16:00', '17:00',
331                  '18:00', '19:00'
332                ]
333            }
334        );
335
336    {% endfor %}
337
338});
339
340</script>
341{% endblock extra_js %}
342
343
344{% block content %}
345
346
347<a class="btn btn-success" href="{% url 'articles:demande_article_non_cloturee_list' %}">Liste des demandes non clôturées</a>
348<a class="btn btn-success" href="{% url 'articles:demande_article_list' %}">Liste de toutes les demandes</a>
349
350
351{% url 'articles:demande_achat_service_update' demande_article.pk  as url_demande_achat_service_update %}
352
353
354{# https://getbootstrap.com/components/#labels #}
355
356{% if demande_article.demande_non_transmise %}
357<h4 class="couleur_demande_non_transmise">
358{% elif demande_article.demande_transmise %}
359<h4 class="couleur_demande_transmise">
360{% elif demande_article.demande_en_cours %}
361<h4 class="couleur_demande_en_cours">
362{% elif demande_article.demande_traitee %}
363<h4 class="couleur_demande_traitee">
364{% elif demande_article.demande_cloturee %}
365<h4 class="couleur_demande_cloturee">
366{% endif %}
367    <a href="{{ url_demande_achat_service_update }}">
368        Demande N° <span id="id_demande_article"> {{ demande_article.id }}</span>
369    </a>
370    {{ demande_article.get_description_without_crlf}}
371</h4>
372
373
374{% if messages %}
375    <ul class="messages">
376        {% comment %} https://getbootstrap.com/components/#alerts {% endcomment %}
377        {% for message in messages %}
378        <!--li{% if message.tags %} class="{{ message.tags }}"{% endif %} -->
379            {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
380                <div class="alert alert-danger" role="alert">{{ message }}</div>
381            {% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}
382                <div class="alert alert-success" role="alert">{{ message }}</div>
383            {% else %}
384                <div class="alert alert-info" role="alert">{{ message }}</div>
385            {% endif %}
386        <!-- /li -->
387        {% endfor %}
388    </ul>
389{% endif %}
390
391{{ form.description }}
392{{ form.etat_demande }}
393<tr>
394    <th><label for="id_send_email">Envoi courriel :</label></th>
395    <td><input type="checkbox" name="send_email" id="id_send_email"></td>
396</tr>
397
398{% url 'tiers:create'  as url_tiers_create %}
399
400<form id="articles_achats" method="post" action=".">
401    {% csrf_token %}
402    <input type="hidden" name="demande_article" value="{{ demande_article }}">
403    <div class="table-responsive">
404        <table id="id_list_table" class="table table-condensed">
405            <thead>
406            <tbody>
407                {{ formset_achats.management_form }}
408                {# rajout des erreurs #}
409                {% if formset_achats.errors %}
410                    {% for error in formset_achats.non_field_errors %}
411                        <div class="alert alert-error">
412                            <strong>{{ error|escape }}</strong>
413                        </div>
414                    {% endfor %}
415                {% endif %}
416                {# Parcours des formulaires et des instances initiales associées #}
417                {% for form_achat, achat_service in formset_instances %}
418                    {{ form_achat.id }}
419                    <tr>
420                       <td>
421                           <table id="id_table_in_table" class="table table-hover table-condensed
422                                {% if forloop.counter|divisibleby:"2" %}
423                                   table-pair
424                                {% else %}
425                                    table-impair
426                                {% endif %}
427                               ">
428                               {# un element de formulaire => un tableau dans un tableau #}
429                                <tbody>
430                                    <tr>
431                                        {% url 'articles:achat_service_delete' form_achat.id.value  as url_achat_service_delete %}
432                                        {% url 'articles:achat_service_clone' form_achat.id.value  as url_achat_service_clone %}
433                                        {% if forloop.counter <= nb_articles %}
434                                            <td colspan=2 id={{ forloop.counter }}>
435                                               {% for num_article in list_articles %}
436                                                  {% if num_article != forloop.parentloop.counter  %}
437                                                      <a href="#{{ num_article }}">Achat {{ num_article }}</a>
438                                                  {% else %}
439                                                      Achat {{ forloop.parentloop.counter }}{{achat_service.id }}
440                                                  {% endif %}
441                                               {% endfor %}
442                                               {% if demande_article.demande_non_cloturee %}
443                                                   <a href="{{ url_achat_service_clone }}">| Ajouter un article</a>
444                                                   <a href="{{ url_achat_service_delete }}">| Supprimer un article</a
445                                                   {% if forloop.counter >= 2 %}
446                                                        <ul class=list-group>
447                                                          <li class="alert alert-success list-group-item">
448                                                            <a href="#id_demande_article">Demande article N°{{ demande_article.id }}</a>
449                                                               <span id="libelle_description_{{ forloop.counter0 }}"> {{ demande_article.get_description_without_crlf }}</span>
450                                                          </li>
451                                                       </ul>
452                                                   {% endif %}
453                                               {% endif %}
454                                            </td>
455                                        {% else %}
456                                            <td colspan=2>
457                                            </td>
458                                        {% endif %}
459                                    </tr>
460                                    {% if demande_article.reponse_affichable %}
461                                        <tr>
462                                            {% comment %} Affichage en vert quand l'état du poste est dans l'état "Traité" {% endcomment %}
463                                            <td {% if achat_service.is_poste_traite %} class="text-right poste_traite" {% else %} class="text-right" {% endif %}>
464                                                Etat du poste {{ form_achat.etat_poste }}
465                                            </td>
466                                            <td {% if achat_service.is_poste_traite %} {% else %} class="info" {% endif %}>
467                                                Réponse {{ form_achat.reponse }}
468                                            </td>
469                                        </tr>
470                                        {% if achat_service.demande_cloturee_and_no_responses %}
471                                        {% else %}
472                                            <tr>
473                                                {% url 'articles:achat_service_reponse_list'  achat_service.id  as url_list_reponses %}
474                                                <td class="text-right">
475                                                    {% if demande_article.demande_non_cloturee %}
476                                                        <a href="{{ url_list_reponses }}">Documents réponse</a>
477                                                    {% else %}
478                                                        Documents réponse
479                                                    {% endif %}
480                                                </td>
481                                                <td>
482                                                    {% if forloop.counter <= nb_articles %}
483                                                        {% for document in achat_service.responses.all %}
484                                                            {% with media=document.item %}
485                                                                {# https://getbootstrap.com/css/#tables-example #}
486                                                                <p>{{ media.render }} </p>
487                                                            {% endwith %}
488                                                        {% endfor %}
489                                                    {% endif %}
490                                                </td>
491                                            </tr>
492                                        {% endif %}
493                                    {% endif %}
494                                    <tr>
495                                        <td class="text-right">Code article</td>
496                                        <td class="danger">
497                                        {% if demande_article.demande_non_cloturee %}
498                                            {{ form_achat.article }}
499                                        {% endif %}
500                                        <span id="libelle_article_{{ forloop.counter0 }}"> {{ achat_service.get_libelle_article }}</span>
501                                        </td>
502                                    </tr>
503                                    <tr>
504                                        <td class="text-right">Projet</td>
505                                        <td>
506                                            {% if demande_article.demande_non_cloturee %}
507                                                {{ form_achat.projet }}
508                                            {% endif %}
509                                            <span id="libelle_projet_{{ forloop.counter0 }}" {{ achat_service.get_libelle_projet }} </span>
510                                        </td>
511                                    </tr>
512                                    <tr>
513                                        <td class="text-right">Description</td>
514                                        <td>{{ form_achat.description }}</td>
515                                    </tr>
516                                    <tr>
517                                        <td class="text-right">Prestataire</td>
518                                        <td>
519                                            {% if demande_article.demande_non_cloturee %}
520                                                {{ form_achat.prestataire }}
521                                                <a href="{{ url_tiers_create }}"> <img src="{% static "tiers/icon-addlink.svg" %}" alt="Ajouter"/> </a>
522                                            {% endif %}
523                                            <span id="libelle_prestataire_{{ forloop.counter0 }}" {{ achat_service.get_libelle_prestataire }} </span>
524                                        </td>
525                                    </tr>
526                                    <tr>
527                                        <td class="text-right">Quantite</td>
528                                        <td>{{ form_achat.quantite }} Prix {{ form_achat.prix_unitaire }} </td>
529                                    </tr>
530                                    <tr>
531                                        <td class="text-right">Livraison souhaitée</td>
532                                        <td class="danger">{{ form_achat.date_butoir }}</td>
533                                    </tr>
534                                    {% if achat_service.demande_cloturee_and_no_documents %}
535                                    {% else %}
536                                        <tr>
537                                            {% url 'articles:achat_service_doc_list'  achat_service.id  as url_list_documents %}
538                                            <td class="text-right">
539                                                {% if demande_article.demande_non_cloturee %}
540                                                    {% if forloop.counter <= nb_articles %}
541                                                        <a href="{{ url_list_documents }}">Documents associés</a>
542                                                    {% endif %}
543                                                {% else %}
544                                                    Documents associés
545                                                {% endif %}
546                                            </td>
547                                            <td>
548                                                {% if forloop.counter <= nb_articles %}
549                                                    {% for document in achat_service.contents.all %}
550                                                        {% with media=document.item %}
551                                                            <p>{{ media.render }} </p>
552                                                        {% endwith %}
553                                                    {% endfor %}
554                                                {% endif %}
555                                            </td>
556                                        </tr>
557                                    {% endif %}
558                                    <tr>
559                                        <td colspan=2>
560                                        </td>
561                                    </tr>
562                                </tbody>
563                            {# table imbriquee #}
564                            </table>
565                       </td>
566                    </tr>
567                {% endfor %}
568            </tbody>
569        </table>
570    </div>
571</form>
572
573
574
575{% endblock %}
576
577
578{% block footer %}
579{% endblock footer %}

views_demande_article.py

  1"""Vues concernant les articles et demandes d'articles.
  2
  3Techniquement on utilise les vues génériques
  4
  5- https://docs.djangoproject.com/en/dev/ref/class-based-views/generic-display/
  6
  7
  8"""
  9import logging
 10import time
 11from typing import List, Union
 12
 13import pendulum
 14
 15from django.http.request import HttpRequest
 16from django.http import HttpResponseRedirect, JsonResponse
 17
 18from django.contrib.auth.mixins import LoginRequiredMixin
 19from django.contrib.auth.decorators import login_required
 20
 21from django.core.paginator import EmptyPage, Page, PageNotAnInteger, Paginator
 22from django.http import HttpResponse, HttpResponseRedirect
 23from django.http.request import HttpRequest, QueryDict
 24from django.shortcuts import get_object_or_404, redirect, render
 25from django.urls import reverse, reverse_lazy
 26from django.views.generic.base import RedirectView
 27
 28# https://docs.djangoproject.com/en/dev/ref/class-based-views/generic-display/#detailview
 29from django.views.generic.detail import DetailView
 30
 31# https://docs.djangoproject.com/en/dev/ref/class-based-views/generic-editing/#updateview
 32from django.views.generic.edit import CreateView, UpdateView
 33
 34# https://docs.djangoproject.com/en/dev/ref/class-based-views/generic-display/#listview
 35# http://ccbv.co.uk/projects/Django/1.9/django.views.generic.list/ListView/
 36from django.views.generic.list import ListView
 37
 38from documents.views import parse_date
 39from employes.decorators import require_authenticated_permission
 40from employes.models import Employe, StatusEmploye
 41from projets.models import Projet
 42
 43from .forms_demande_article import (
 44    DemandeArticleCreateForm,
 45    DemandeArticleFormFiltre,
 46    DemandeArticleUpdateForm,
 47)
 48from .models_demande_article import (
 49    ETAT_DEMANDE_ARTICLE_CHOICES,
 50    DemandeArticle,
 51    ListdisplayDemandeArticle,
 52)
 53
 54from django.contrib import messages
 55
 56# Get an instance of a logger
 57logger = logging.getLogger(__name__)
 58
 59
 60__all__ = (
 61    "DemandeArticleNonClotureeListView",
 62    "DemandeArticleCreateView",
 63    "DemandeCodifArticleUpdateView",
 64    "DemandeCodifNomenclatureUpdateView",
 65    "DemandeAchatServiceUpdateView",
 66    "DemandeSortieArticleUpdateView",
 67    "view_update_demande_article",
 68)
 69
 70
 71class DemandeArticleRedirectView(RedirectView):
 72
 73    permanent = False
 74    query_string = True
 75    pattern_name = "articles:demande_achat_service_update"
 76
 77    def get_redirect_url(self, *args, **kwargs):
 78        demande_article = get_object_or_404(DemandeArticle, pk=kwargs["pk"])
 79        # logger.info(f"DemandeArticleRedirectView() demande_article={demande_article} permanent={DemandeArticleRedirectView.permanent}")
 80        if demande_article.demande_codification_par_article():
 81            DemandeArticleRedirectView.pattern_name = (
 82                "articles:demande_codif_article_update"
 83            )
 84        elif demande_article.demande_codification_par_nomenclature():
 85            DemandeArticleRedirectView.pattern_name = (
 86                "articles:demande_codif_nomenclature_update"
 87            )
 88        elif demande_article.demande_codification_achat_par_service():
 89            DemandeArticleRedirectView.pattern_name = (
 90                "articles:demande_achat_service_update"
 91            )
 92        elif demande_article.demande_codification_sortie_stock():
 93            DemandeArticleRedirectView.pattern_name = (
 94                "articles:demande_sortie_article_update"
 95            )
 96
 97        return super().get_redirect_url(*args, **kwargs)
 98
 99
100class DemandeArticleMixin:
101    @property
102    def success_msg(self):
103        return NotImplemented
104
105    def get_context_data(self, **kwargs):
106        """
107        - http://gravitymad.com/blog/2012/4/14/how-filter-django-forms-multiple-choice-or-foreign/
108        """
109        context = super().get_context_data(**kwargs)
110        return context
111
112    def form_valid(self, form):
113        messages.info(self.request, self.success_msg)
114        return super().form_valid(form)
115
116
117class DemandeArticleNonClotureeListView(LoginRequiredMixin, ListView):
118    """Affiche la liste des demandes d'articles non cloturées.
119
120    - on n'affiche pas les demandes cloturees
121    - affichage des dernières demandes (tri decroissant sur id)
122
123    """
124
125    queryset = (
126        DemandeArticle.objects.exclude(
127            etat_demande=ETAT_DEMANDE_ARTICLE_CHOICES.Cloturee
128        )
129        .order_by("-id")
130        .select_related("demandeur")
131        .prefetch_related(
132            "codifarticle_related",
133            "achatservice_related",
134            "sortiestock_related",
135            "codifnomenclature_related",
136        )
137    )
138
139    template_name = "articles/demande/list_non_cloturee.html"
140    context_object_name = "demandes_articles"
141
142
143@require_authenticated_permission("add_demandearticle")
144class DemandeArticleCreateView(DemandeArticleMixin, CreateView):
145    """Affiche la vue permettant la création d'une demande d'articles.
146
147    SeeAlso:
148
149    - http://ccbv.co.uk/projects/Django/1.9/django.views.generic.edit/CreateView/
150
151    """
152
153    model = DemandeArticle
154    form_class = DemandeArticleCreateForm
155    template_name = "articles/demande/create.html"
156    success_msg = "La demande d'article a bien été créé !"
157
158    def form_valid(self, form):
159        """
160        voir p.523 Mastering Django core
161        """
162        try:
163            # logger.info(f"User={self.request.user}")
164            employe = Employe.objects.get(user=self.request.user)
165            # Le demandeur est l'employé connecté
166            form.instance.demandeur = employe
167            return super().form_valid(form)
168        except Exception as error:
169            logger.error(f"Employe {self.request.user} not found:{error}")
170            messages.error(self.request, error)
171            return None
172
173    def get_success_url(self):
174        """Retourne l'URL à afficher après une création réussie d'une demande d'article.
175
176        Depuis le lundi 24 février 2020, on affiche directement le premier article
177        de la demande.
178
179        """
180        demande_article = self.object
181        # update_url = demande_article.get_update_url()
182        # on crée un premier enregistrement
183        try:
184            demande_article.create_first_article()
185        except Exception as error:
186            logger.error(f"Exception={error}")
187
188        update_url = demande_article.get_update_url_for_articles()
189        # logger.info(f"DemandeArticleCreateView.get_success_url() {demande_article=}\n{update_url=}")
190
191        return update_url
192
193
194@require_authenticated_permission("change_demandearticle")
195class DemandeAchatServiceUpdateView(UpdateView):
196    """Affiche la vue permettant de modifier une demande d'article par un service.
197
198    SeeAlso:
199
200    - http://ccbv.co.uk/projects/Django/1.9/django.views.generic.edit/UpdateView/
201
202    """
203
204    model = DemandeArticle
205    context_object_name = "demande_article"
206    form_class = DemandeArticleUpdateForm
207    template_name = "articles/demande/achat_service/update.html"
208    success_msg = "La demande d'article a bien été mise à jour !"
209
210    def get_success_url(self):
211        return reverse(
212            "articles:demande_achat_service_update", kwargs={"pk": self.object.pk}
213        )
214
215
216@require_authenticated_permission("change_demandearticle")
217class DemandeCodifArticleUpdateView(UpdateView):
218    """Affiche la vue permettant de modifier une demande de codification par article.
219
220    SeeAlso:
221
222    - http://ccbv.co.uk/projects/Django/1.9/django.views.generic.edit/UpdateView/
223
224    """
225
226    model = DemandeArticle
227    context_object_name = "demande_article"
228    form_class = DemandeArticleUpdateForm
229    template_name = "articles/demande/codif_article/update.html"
230    success_msg = "Demande article mis à jour !"
231
232    def get_success_url(self):
233        return reverse(
234            "articles:demande_codif_article_update", kwargs={"pk": self.object.pk}
235        )
236
237
238@require_authenticated_permission("change_demandearticle")
239class DemandeCodifNomenclatureUpdateView(UpdateView):
240    """Affiche la vue permettant de modifier une demande de codification nomenclature.
241
242    SeeAlso:
243
244    - http://ccbv.co.uk/projects/Django/1.9/django.views.generic.edit/UpdateView/
245
246    """
247
248    model = DemandeArticle
249    context_object_name = "demande_article"
250    form_class = DemandeArticleUpdateForm
251    template_name = "articles/demande/codif_nomenclature/update.html"
252    success_msg = "Demande article mis à jour !"
253
254    def get_success_url(self):
255        return reverse(
256            "articles:demande_codif_nomenclature_update", kwargs={"pk": self.object.pk}
257        )
258
259
260@require_authenticated_permission("change_demandearticle")
261class DemandeSortieArticleUpdateView(UpdateView):
262    """Affiche la vue permettant de modifier une demande de sortie d'article .
263
264    SeeAlso:
265
266    - http://ccbv.co.uk/projects/Django/1.9/django.views.generic.edit/UpdateView/
267
268    """
269
270    model = DemandeArticle
271    context_object_name = "demande_article"
272    form_class = DemandeArticleUpdateForm
273    template_name = "articles/demande/sortie_stock/update.html"
274    success_msg = "Demande article mis à jour !"
275
276    def get_success_url(self):
277        return reverse(
278            "articles:demande_sortie_article_update", kwargs={"pk": self.object.pk}
279        )
280
281
282class DemandeArticleListView(LoginRequiredMixin, ListView):
283    """Affiche la liste des demandes d'articles.
284
285    - on affiche toutes les demandes
286    - affichage des dernières demandes (tri decroissant sur id)
287
288    """
289
290    queryset = (
291        DemandeArticle.objects.exclude(
292            etat_demande=ETAT_DEMANDE_ARTICLE_CHOICES.Cloturee
293        )
294        .order_by("-id")
295        .select_related("demandeur")
296        .prefetch_related(
297            "codifarticle_related",
298            "achatservice_related",
299            "sortiestock_related",
300            "codifnomenclature_related",
301        )
302    )
303
304    template_name = "articles/demande/list.html"
305    context_object_name = "demandes_articles"
306
307
308def ReadGetVueDemandeArticleList(
309    request: HttpRequest,
310) -> (
311    pendulum.datetime,
312    pendulum.datetime,
313    int,
314    int,
315    Employe,
316    str,
317    int,
318    Projet,
319    DemandeArticleFormFiltre,
320):
321    # GET: c'est quand on pointe sur la page directement ou que l'on
322    # utilise paginator
323    # logger.info(f"Begin ReadGetVueDemandeArticleList() request.GET={request.GET}")
324    start_time = time.time()
325
326    # Est-ce qu'il y a un filtre sur la date from_date ?
327    from_date = parse_date(request.GET.get("from_date"))
328    if from_date is None:
329        # from_date = pendulum.now("Europe/Paris").add(months=-12)
330        from_date = pendulum.datetime(1990, 4, 1, tz="Europe/Paris")
331
332    # Est-ce qu'il y a un filtre sur la date to_date ?
333    to_date = parse_date(request.GET.get("to_date"))
334    if to_date is None:
335        # la date du jour
336        to_date = pendulum.now("Europe/Paris").add(hours=12)
337        # logger.info(f"to_date:{to_date}")
338
339    try:
340        type_demande = request.GET.get("type_demande")
341        if type_demande != "":
342            type_demande = int(type_demande)
343        else:
344            type_demande = None
345    except Exception as error:
346        type_demande = None
347
348    try:
349        etat_demande = request.GET.get("etat_demande")
350        if etat_demande != "":
351            etat_demande = int(etat_demande)
352        else:
353            etat_demande = None
354
355    except Exception as error:
356        etat_demande = None
357
358    try:
359        demandeur = request.GET.get("demandeur")
360        if demandeur != "":
361            id_demandeur = int(demandeur)
362            demandeur = Employe.objects.get(pk=id_demandeur)
363        else:
364            demandeur = None
365    except Exception as error:
366        demandeur = None
367
368    try:
369        description = request.GET.get("description")
370    except Exception as error:
371        description = None
372
373    try:
374        id_demande = request.GET.get("id_demande")
375        if id_demande == "":
376            id_demande = None
377    except Exception as error:
378        id_demande = None
379
380    try:
381        projet = request.GET.get("projet")
382        if projet != "":
383            id_projet = int(projet)
384            # on s'en sert pour récupérer le type de document
385            projet = Projet.objects.get(pk=id_projet)
386        else:
387            projet = None
388
389    except Exception as error:
390        projet = None
391
392    # Instanciation du formulaire de recherche.
393    # https://docs.djangoproject.com/en/dev/ref/forms/api/#dynamic-initial-values
394    formulaire_filtre_demandes_articles = DemandeArticleFormFiltre(
395        initial={
396            "from_date": from_date,
397            "to_date": to_date,
398            "description": description,
399            "filtre_description": False
400            if description is None or description == ""
401            else True,
402            "id_demande": id_demande if id_demande is not None else "",
403            "filtre_id_demande": False if id_demande is None else True,
404            "type_demande": type_demande if type_demande is not None else "",
405            "filtre_type_demande": False if type_demande is None else True,
406            "etat_demande": etat_demande if etat_demande is not None else "",
407            "filtre_etat_demande": False if etat_demande is None else True,
408            "demandeur": demandeur.id if demandeur is not None else "",
409            "filtre_demandeur": False if demandeur is None else True,
410            "projet": projet.id if projet is not None else "",
411            "filtre_projet": False if projet is None else True,
412        }
413    )
414
415    # logger.info(
416    #    f"ReadGetVueDemandeArticleList() formulaire_filtre_demandes_articles:{formulaire_filtre_demandes_articles}"
417    # )
418
419    end_time = time.time()
420    duree = end_time - start_time
421    logger.info(f"ReadGetVueDemandeArticleList() DUREE:<{duree}s>")
422
423    return (
424        from_date,
425        to_date,
426        type_demande,
427        etat_demande,
428        demandeur,
429        description,
430        id_demande,
431        projet,
432        formulaire_filtre_demandes_articles,
433    )
434
435
436def ReadPostVueDemandeArticleList(
437    request: HttpRequest,
438) -> (
439    pendulum.datetime,
440    pendulum.datetime,
441    int,
442    int,
443    Employe,
444    str,
445    int,
446    Projet,
447    DemandeArticleFormFiltre,
448):
449    # POST c'est quand l'utilisateur appuie sur le bouton 'Appliquer'
450    # A revoir car "Search forms that are idempotent should use the GET method"
451    # p. 142, chapitre 11 "Form fundamentals" Two scoops of Django
452    # logger.info("POST={}".format(request.POST))
453    type_demande = None
454    etat_demande = None
455    demandeur = None
456    description = ""
457    id_demande = None
458    projet = None
459    if "btn_form_filtre" in request.POST:
460        # Instanciation du formulaire
461        form_filtres_demandes_articles = DemandeArticleFormFiltre(request.POST)
462        if form_filtres_demandes_articles.is_valid():
463            # indispensable pour avoir le tableau 'cleaned_data'
464            # qui remet les dates anglaises en place:
465            # Exemple
466            date_naive = form_filtres_demandes_articles.cleaned_data["from_date"]
467            from_date = pendulum.datetime(
468                date_naive.year, date_naive.month, date_naive.day, tz=("Europe/Paris")
469            )
470            date_naive = form_filtres_demandes_articles.cleaned_data["to_date"]
471            to_date = pendulum.datetime(
472                date_naive.year, date_naive.month, date_naive.day, tz=("Europe/Paris")
473            )
474            filtre_type_demande = form_filtres_demandes_articles.cleaned_data[
475                "filtre_type_demande"
476            ]
477            if filtre_type_demande:
478                # recuperation de l'id de la table type_demande
479                type_demande = int(
480                    form_filtres_demandes_articles.cleaned_data["type_demande"]
481                )
482
483            filtre_etat_demande = form_filtres_demandes_articles.cleaned_data[
484                "filtre_etat_demande"
485            ]
486            if filtre_etat_demande:
487                # recuperation de l'id de la table etat_demande
488                etat_demande = int(
489                    form_filtres_demandes_articles.cleaned_data["etat_demande"]
490                )
491
492            filtre_demandeur = form_filtres_demandes_articles.cleaned_data[
493                "filtre_demandeur"
494            ]
495            if filtre_demandeur:
496                # recuperation de l'id de la table Demandeur
497                id_demandeur = int(
498                    form_filtres_demandes_articles.cleaned_data["demandeur"]
499                )
500                demandeur = Employe.objects.get(pk=id_demandeur)
501
502            filtre_description = form_filtres_demandes_articles.cleaned_data[
503                "filtre_description"
504            ]
505            if filtre_description:
506                # recuperation de la désignation
507                description = form_filtres_demandes_articles.cleaned_data["description"]
508
509            filtre_id_demande = form_filtres_demandes_articles.cleaned_data[
510                "filtre_id_demande"
511            ]
512            if filtre_id_demande:
513                id_demande = int(
514                    form_filtres_demandes_articles.cleaned_data["id_demande"]
515                )
516            else:
517                id_demande = None
518
519            filtre_projet = form_filtres_demandes_articles.cleaned_data["filtre_projet"]
520            if filtre_projet:
521                # recuperation de l'id de la table Projet
522                id_projet = int(form_filtres_demandes_articles.cleaned_data["projet"])
523                projet = Projet.objects.get(pk=id_projet)
524
525        else:
526            logger.info(f"Form is NOT valid {form_filtres_demandes_articles}")
527
528    return (
529        from_date,
530        to_date,
531        type_demande,
532        etat_demande,
533        demandeur,
534        description,
535        id_demande,
536        projet,
537        form_filtres_demandes_articles,
538    )
539
540
541def get_page_and_paginator(
542    request: HttpRequest,
543    liste_demandes_articles: List[DemandeArticle],
544    NB_ELEMS_IN_PAGE=50,
545) -> (Page, Paginator):
546
547    # mise à jour de l'objet paginator
548    # https://docs.djangoproject.com/en/dev/topics/pagination/
549    paginator = Paginator(liste_demandes_articles, NB_ELEMS_IN_PAGE, orphans=3)
550    # Quelle est la page à afficher
551    page = request.GET.get("page")
552    try:
553        current_page = paginator.page(page)
554    except PageNotAnInteger:
555        current_page = paginator.page(1)
556    except EmptyPage:
557        current_page = paginator.page(paginator.num_pages)
558
559    return current_page, paginator
560
561
562def get_liste_demandes_articles(
563    from_date,
564    to_date,
565    type_demande: int,
566    etat_demande: int,
567    demandeur: Employe,
568    description: str,
569    id_demande: int,
570    projet: Projet,
571) -> List[DemandeArticle]:
572    """Lecture des DemandeArticles en fonction des critères."""
573    # a partir du jour précédent la date de sélection
574    # logger.info("Begin get_liste_demandes_articles()")
575
576    """
577    logger.info(
578        f"\nBegin get_liste_demandes_articles()\n"
579        f"from_date:{from_date}\n"
580        f"to_date:{to_date}\n"
581        f"description:{description}\n"
582        f"id_demande:'{id_demande}'\n"
583        f"type_demande:{type_demande}\n"
584        f"etat_demande:{etat_demande}\n"
585        f"demandeur:{demandeur}\n"
586        f"projet:{projet}\n"
587    )
588    """
589
590    # cas 1: on connait l'identifiant de la demande
591    if id_demande is not None:
592        try:
593            liste_demandes_articles = DemandeArticle.objects.filter(id=id_demande)
594            return liste_demandes_articles
595        except Exception as error:
596            logger.error(f"Exception: {error}")
597
598    # start_time = time.time()
599    liste_demandes_articles = DemandeArticle.objects.filter(
600        created__range=(from_date, to_date)
601    ).select_related("demandeur")
602
603    # end_time = time.time(); duree = end_time - start_time
604    # logger.info(f"uDemandeArticle.objects.filter({from_date} {to_date}): DUREE:<{duree}s>")
605
606    # cas 2 : sélection sur un projet
607    if projet is not None:
608        try:
609            # logger.info(f"Projet:{projet}")
610            set_demandes = set()
611            # sélection des différents articles liés au projet
612            for article in projet.achatservice_related.all():
613                set_demandes.add(article.demande_article.id)
614
615            for article in projet.codifarticle_related.all():
616                set_demandes.add(article.demande_article.id)
617
618            for article in projet.codifnomenclature_related.all():
619                set_demandes.add(article.demande_article.id)
620
621            for article in projet.sortiestock_related.all():
622                set_demandes.add(article.demande_article.id)
623
624            # filtre sur les ids des demandes
625            # pour constitution d'un query set
626            liste_demandes_articles = liste_demandes_articles.filter(
627                pk__in=list(set_demandes)
628            )
629        except Exception as error:
630            logger.error(f"Exception: {error}")
631
632    # cas 3
633    # filtres normaux
634    try:
635        if type_demande is not None:
636            # selection d'une agence particulière
637            liste_demandes_articles = liste_demandes_articles.filter(
638                type_demande=type_demande
639            )
640        if etat_demande is not None:
641            liste_demandes_articles = liste_demandes_articles.filter(
642                etat_demande=etat_demande
643            )
644        if demandeur is not None:
645            liste_demandes_articles = liste_demandes_articles.filter(
646                demandeur=demandeur
647            )
648        if description is not None:
649            # recherche description = on recherche dans la description
650            liste_demandes_articles = liste_demandes_articles.filter(
651                description__icontains=description
652            )
653    except Exception as error:
654        logger.error(f"Exception: {error}")
655
656    return liste_demandes_articles
657
658
659@login_required
660def DemandeArticleList(
661    request: HttpRequest, template_name="articles/demande/list.html"
662) -> HttpResponse:
663    """Affichage des DemandeArticles (codes-chronos)."""
664    logger.info("Begin DemandeArticleList()")
665
666    user = request.user
667    form_filtres_demandes_articles = None
668    if request.method == "POST":
669        (
670            from_date,
671            to_date,
672            type_demande,
673            etat_demande,
674            demandeur,
675            description,
676            id_demande,
677            projet,
678            form_filtres_demandes_articles,
679        ) = ReadPostVueDemandeArticleList(request)
680    elif request.method == "GET":
681        (
682            from_date,
683            to_date,
684            type_demande,
685            etat_demande,
686            demandeur,
687            description,
688            id_demande,
689            projet,
690            form_filtres_demandes_articles,
691        ) = ReadGetVueDemandeArticleList(request)
692
693    # lecture de la liste des DemandeArticle
694    liste_demandes_articles = get_liste_demandes_articles(
695        from_date,
696        to_date,
697        type_demande,
698        etat_demande,
699        demandeur,
700        description,
701        id_demande,
702        projet,
703    )
704    # logger.info(f"liste_demandes_articles:{liste_demandes_articles}")
705
706    # mise à jour de l'objet paginator
707    current_page, paginator = get_page_and_paginator(request, liste_demandes_articles)
708    nb_demandes_articles = liste_demandes_articles.count()
709
710    # logger.info(f"paginator={paginator} num_pages:{paginator.num_pages}")
711    multiple_page = False
712    # if current_page.count() > 1:
713    #    multiple_page = True
714
715    # logger.info(f"nb_demandes_articles={nb_demandes_articles}")
716
717    return render(
718        request,
719        template_name,
720        context={
721            "form_filtres_demandes_articles": form_filtres_demandes_articles,
722            "user": user,
723            "nb_demandes_articles": nb_demandes_articles,
724            "demandes_articles": liste_demandes_articles,
725            "from_date": "0" if from_date is None else from_date,
726            "to_date": "0" if to_date is None else to_date,
727            "type_demande": type_demande if type_demande is not None else "",
728            "etat_demande": etat_demande if etat_demande is not None else "",
729            "demandeur": demandeur if demandeur is not None else "",
730            "description": description if description is not None else "",
731            "id_demande": id_demande if id_demande is not None else "",
732            "projet": projet if projet is not None else "",
733            "paginator": paginator,
734            "current_page": current_page,
735        },
736    )
737
738
739def get_change_for_etat_demande(
740    send_email: bool, current_demande: DemandeArticle, nouvel_etat_demande: int
741) -> Union[str, None]:
742    """S courriel à envoyer et etat_demande On indique le changement d'état dans le courriel envoyé
743
744    Si pas de courriel à envoyer message_change = None
745    """
746    if not send_email:
747        return None
748
749    if current_demande.etat_demande == nouvel_etat_demande:
750        return None
751
752    message_change = (
753        f"Changement par rapport à la dernière version:\n"
754        f"=============================================\n"
755        f"\n"
756    )
757    try:
758        message_change = message_change + (
759            f"Etat_demande:\n"
760            f"-------------\n"
761            f"{ETAT_DEMANDE_ARTICLE_CHOICES.for_value(current_demande.etat_demande).display} "
762            f"=> "
763            f"{ETAT_DEMANDE_ARTICLE_CHOICES.for_value(nouvel_etat_demande).display}"
764            f"\n"
765        )
766    except Exception as error:
767        logger.error(f"{error=}")
768
769    return message_change
770
771
772def get_change_for_description(
773    send_email: bool, current_demande: DemandeArticle, nouvelle_description: str
774) -> Union[str, None]:
775    """S courriel à envoyer et nouvelle_description != description courante
776    on indique le changement d'état.
777    desz
778
779    Si pas de courriel à envoyer message_change = None
780    """
781    if not send_email:
782        return None
783
784    if current_demande.description == nouvelle_description:
785        return None
786
787    if current_demande.description != nouvelle_description:
788        message_change = (
789            f"Changement par rapport à la dernière version:\n"
790            f"=============================================\n"
791            f"\n"
792        )
793        message_change = message_change + (
794            f"Description:\n"
795            f"------------\n"
796            f"\n"
797            f"{current_demande.description} "
798            f"\n=>\n"
799            f"{nouvelle_description}\n"
800            f"\n"
801        )
802
803    return message_change
804
805
806def view_update_demande_article(request: HttpRequest) -> JsonResponse:
807    """Mise à jour de la demande d'article par AJAX.
808    """
809    id_demande_article = int(request.GET.get("id_demande_article", None))
810    try:
811        demande_article = DemandeArticle.objects.get(pk=id_demande_article)
812    except Exception as error:
813        logger.error(f"{error=}")
814
815    description = request.GET.get("description", None)
816    etat_demande = request.GET.get("etat_demande", None)
817    send_email = request.GET.get("send_email", None)
818    if send_email is not None:
819        if send_email == "false":
820            send_email = False
821        elif send_email == "true":
822            send_email = True
823
824    else:
825        send_email = False
826    logger.info(f"{send_email=}")
827
828    data = {}
829    data["update"] = False
830    message_change = ""
831    logger.info(f"{demande_article=}")
832    try:
833        save = False
834        msg = ""
835        if description is not None:
836            description = description.strip()
837            logger.info(f"{description=}")
838            changement = get_change_for_description(
839                send_email, demande_article, description
840            )
841            if changement is not None:
842                message_change = message_change + "\n" + changement
843            demande_article.description = description
844            msg = f"new description:{description}"
845            save = True
846
847        if etat_demande is not None:
848            etat_demande = int(etat_demande)
849            logger.info(f"{etat_demande=}")
850            changement = get_change_for_etat_demande(
851                send_email, demande_article, etat_demande
852            )
853            if changement is not None:
854                message_change = message_change + "\n" + changement
855            demande_article.etat_demande = etat_demande
856            msg = f"new etat_demande:{etat_demande}"
857            save = True
858
859        if save:
860            demande_article.save(message_change=message_change, send_email=send_email)
861            data["update"] = msg
862            # retour data
863            if description is not None:
864                data["libelle_description"] = description
865
866    except Exception as error:
867        logger.error(f"view_update_demande_article {error=}")
868
869    return JsonResponse(data)