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 }} N°{{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)