chartManD commited on
Commit
16f4cf8
·
1 Parent(s): efd99b7

Presentacion de sesion con Sort para el catador

Browse files
tecnicas/controllers/__init__.py CHANGED
@@ -43,6 +43,7 @@ from .views_controller.sessions_tester.tests_forms.test_scales_controller import
43
  from .views_controller.sessions_tester.tests_forms.test_rata_controller import TestRataController
44
  from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
45
  from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
 
46
 
47
  from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
48
  from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
 
43
  from .views_controller.sessions_tester.tests_forms.test_rata_controller import TestRataController
44
  from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
45
  from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
46
+ from .views_controller.sessions_tester.tests_forms.test_sort_controller import TestSortController
47
 
48
  from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
49
  from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_sort_controller.py CHANGED
@@ -1,6 +1,8 @@
1
  from django.http import HttpRequest
2
- from django.shortcuts import render
 
3
  from tecnicas.models import Participacion
 
4
  from .init_session_controller import InitSessionController
5
 
6
 
@@ -8,9 +10,9 @@ class InitSessionSortController(InitSessionController):
8
  def __init__(self, sensorial_session, user_tester):
9
  super().__init__(sensorial_session, user_tester)
10
  self.current_direction = "tecnicas/forms_tester/init_test_sort.html"
11
- self.pf_direction = "cata_system:session_pf"
12
 
13
- def controllGet(self, request: HttpRequest):
14
  context = {
15
  "session": self.session,
16
  "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
@@ -23,6 +25,9 @@ class InitSessionSortController(InitSessionController):
23
  if is_end:
24
  context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
25
 
 
 
 
26
  return render(request, self.current_direction, context)
27
 
28
  def isEndedSession(self):
@@ -30,3 +35,43 @@ class InitSessionSortController(InitSessionController):
30
  catador=self.tester, tecnica=self.session.tecnica)
31
 
32
  return participation.finalizado
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from django.http import HttpRequest
2
+ from django.shortcuts import render, redirect
3
+ from django.urls import reverse
4
  from tecnicas.models import Participacion
5
+ from tecnicas.controllers import ParticipacionController
6
  from .init_session_controller import InitSessionController
7
 
8
 
 
10
  def __init__(self, sensorial_session, user_tester):
11
  super().__init__(sensorial_session, user_tester)
12
  self.current_direction = "tecnicas/forms_tester/init_test_sort.html"
13
+ self.sort_direction = "cata_system:session_sort"
14
 
15
+ def controllGet(self, request: HttpRequest, error=""):
16
  context = {
17
  "session": self.session,
18
  "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
 
25
  if is_end:
26
  context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
27
 
28
+ if "error" in request.GET:
29
+ context["error"] = request.GET["error"]
30
+
31
  return render(request, self.current_direction, context)
32
 
33
  def isEndedSession(self):
 
35
  catador=self.tester, tecnica=self.session.tecnica)
36
 
37
  return participation.finalizado
38
+
39
+ def controllPost(self, request: HttpRequest):
40
+ context = {
41
+ "session": self.session,
42
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
43
+ }
44
+
45
+ use_action = request.POST["action"]
46
+
47
+ if use_action == "start_posting":
48
+ parameters = {
49
+ "code_sesion": self.session.codigo_sesion
50
+ }
51
+
52
+ is_end = self.isEndedSession()
53
+ if is_end:
54
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
55
+ return render(request, self.current_direction, context)
56
+
57
+ update_participation = ParticipacionController.enterSession(
58
+ tester=request.user.user_catador, session=self.session)
59
+
60
+ if isinstance(update_participation, dict):
61
+ context["error"] = update_participation["error"]
62
+ return render(request, self.current_direction, context)
63
+
64
+ request.session["id_participation"] = update_participation.id
65
+
66
+ return redirect(reverse(self.sort_direction, kwargs=parameters))
67
+
68
+ elif use_action == "exit_session":
69
+ response = ParticipacionController.outSession(
70
+ tester=request.user.user_catador, session=self.session)
71
+ if isinstance(response, dict):
72
+ context["error"] = response["error"]
73
+ return render(request, self.current_direction, context)
74
+
75
+ else:
76
+ context["error"] = "Acción sin especificar"
77
+ return render(request, self.current_direction, context)
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_sort_controller.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import redirect, render
3
+ from django.urls import reverse
4
+ from tecnicas.models import Participacion, Producto, Calificacion, Palabra, GrupoProducto
5
+ from tecnicas.controllers import ParticipacionController, PalabrasController, EscalaController
6
+ from tecnicas.forms import ListWordsForm
7
+ from .general_test_controller import GenetalTestController
8
+
9
+
10
+ class TestSortController(GenetalTestController):
11
+ def __init__(self, sensorial_session, user_tester):
12
+ super().__init__(sensorial_session, user_tester)
13
+ self.current_directory = "tecnicas/forms_tester/test_sort.html"
14
+
15
+ def controllGet(self, request: HttpRequest):
16
+ '''
17
+ Objetivo: Entregar al cliente los grupos de productos guardados hechos por el catador en una lista, de lo contrario solo mandar una lista vacia
18
+ - Comprobar que el Catador aun no finalice su la sesion
19
+ - Obtener todos los productos en la tecnica
20
+ - Obtener todos los grupos creadas por el usuario
21
+ - Si hay grupos, cada item de la lista a mandar debe incluir los productos asociados al grupo como las palabras que describen al grupo
22
+ - Si no hay grupos, solo mandar una lista vacia
23
+ - Mandar la lista de grupos como la lista de productos
24
+ - Agregar el formulario para describir los grupo
25
+ '''
26
+ self.context["session"] = self.session
27
+ technique = self.session.tecnica
28
+
29
+ self.participation = Participacion.objects.get(
30
+ tecnica=technique, catador=request.user.user_catador)
31
+
32
+ # Comprobar que el Catador no haya finalizado
33
+ if self.participation.finalizado:
34
+ params = {
35
+ "code_sesion": self.session.codigo_sesion
36
+ }
37
+ return redirect(reverse(self.previus_directory, kwargs=params))
38
+
39
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
40
+ self.context["products"] = products_in_technique
41
+
42
+ grups_products = GrupoProducto.objects.filter(
43
+ tecnica=technique, catador=request.user.user_catador)
44
+
45
+ self.context["grups_products"] = grups_products if grups_products else []
46
+
47
+ self.context["form_word"] = ListWordsForm()
48
+
49
+ return render(request, self.current_directory, self.context)
tecnicas/migrations/0024_alter_sesionsensorial_codigo_sesion_grupoproducto.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.1 on 2025-11-25 01:18
2
+
3
+ import django.db.models.deletion
4
+ import shortuuid.main
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('tecnicas', '0023_alter_sesionsensorial_codigo_sesion_listapalabras'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AlterField(
16
+ model_name='sesionsensorial',
17
+ name='codigo_sesion',
18
+ field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
19
+ ),
20
+ migrations.CreateModel(
21
+ name='GrupoProducto',
22
+ fields=[
23
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24
+ ('catador', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='catador_grupo_producto', to='tecnicas.catador')),
25
+ ('palabras', models.ManyToManyField(related_name='grupo_producto', to='tecnicas.palabra')),
26
+ ('productos', models.ManyToManyField(related_name='grupo_producto', to='tecnicas.producto')),
27
+ ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_grupo_producto', to='tecnicas.tecnica')),
28
+ ],
29
+ ),
30
+ ]
tecnicas/models/__init__.py CHANGED
@@ -30,3 +30,4 @@ from .orden import Posicion
30
  from .participacion import Participacion
31
 
32
  from .lista_palabras import ListaPalabras
 
 
30
  from .participacion import Participacion
31
 
32
  from .lista_palabras import ListaPalabras
33
+ from .grupo_producto import GrupoProducto
tecnicas/models/grupo_producto.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.db import models
2
+ from tecnicas.models import Tecnica, Catador, Palabra, Producto
3
+
4
+
5
+ class GrupoProducto(models.Model):
6
+ tecnica = models.ForeignKey(
7
+ Tecnica, on_delete=models.CASCADE, related_name="tecnica_grupo_producto")
8
+
9
+ catador = models.ForeignKey(
10
+ Catador, on_delete=models.CASCADE, related_name="catador_grupo_producto")
11
+
12
+ productos = models.ManyToManyField(
13
+ Producto, related_name="grupo_producto")
14
+
15
+ palabras = models.ManyToManyField(
16
+ Palabra, related_name="grupo_producto")
17
+
18
+ def __str__(self):
19
+ return f"{self.tecnica.sesion_tecnica.codigo_sesion} - {self.catador.user.username}"
tecnicas/static/js/test-sort.js ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let dragged = null;
2
+
3
+ const productsPlaceHolder = `
4
+ <p class="text-products text-center text-lg font-medium">
5
+ Agrupación de productos
6
+ </p>`;
7
+
8
+ const wordsPlaceHolder = `
9
+ <p class="text-words text-center text-lg font-medium">
10
+ Lista de atributos
11
+ </p>`;
12
+
13
+ const products = document.querySelectorAll(".draggable");
14
+ const zonesDrop = document.querySelectorAll(".dropzone");
15
+
16
+ const DATA_GRUPS = {};
17
+
18
+ products.forEach(manageDragables);
19
+ zonesDrop.forEach(manageDropZone);
20
+
21
+ document
22
+ .getElementById("question-save")
23
+ .addEventListener("click", showOptionsSave);
24
+
25
+ document
26
+ .getElementById("cancel-save")
27
+ .addEventListener("click", showQuestionSave);
28
+
29
+
30
+ /*
31
+ ////
32
+ //////
33
+ //////// Add new grups
34
+ //////
35
+ ////
36
+ */
37
+
38
+ function addNewGrup() {
39
+ const container = document.getElementById("containers");
40
+ const newGrup = document.createElement("section");
41
+ const styles = "w-fit space-y-4 border rounded p-2 bg-surface-sweet";
42
+ newGrup.classList.add(...styles.split(" "));
43
+
44
+ const formWord = document.getElementById("form-word");
45
+ const formWordClone = formWord.cloneNode(true);
46
+ formWordClone.classList.remove("hidden");
47
+
48
+ newGrup.innerHTML = `
49
+ <div class="dropzone w-64 min-h-16 bg-surface-alter-card rounded p-2 flex items-center justify-center flex-wrap gap-2">
50
+ ${productsPlaceHolder}
51
+ </div>
52
+ ${formWordClone.outerHTML}
53
+ <div
54
+ class="words-container w-64 min-h-16 bg-surface-alter rounded p-2 flex items-center justify-center flex-wrap gap-2">
55
+ ${wordsPlaceHolder}
56
+ </div>
57
+ <div class="cts-container-question flex items-center justify-around gap-2">
58
+ <button class="cts-question cts-btn-general-compress flex-1 cts-btn-secondary btn-push">
59
+ ¿Remover este grupo?
60
+ </button>
61
+ <button class="cts-remove cts-btn-general-compress flex-1 cts-btn-primary btn-push hidden">
62
+ Remover
63
+ </button>
64
+ <button class="cts-no-remove cts-btn-general-compress flex-1 cts-btn-error btn-push hidden">
65
+ Conservar
66
+ </button>
67
+ </div>`;
68
+
69
+ manageDropZone(newGrup.querySelector(".dropzone"));
70
+ container.appendChild(newGrup);
71
+ }
72
+
73
+
74
+ /*
75
+ ////
76
+ //////
77
+ //////// Manage products' drags and remove drags
78
+ //////
79
+ ////
80
+ */
81
+
82
+ /**
83
+ *
84
+ * @param {HTMLElement} zone
85
+ */
86
+ function manageDropZone(zone) {
87
+ /*
88
+ ////
89
+ ////// Trash Zone product
90
+ ////
91
+ */
92
+ if (zone.classList.contains("trash-products")) {
93
+ zone.addEventListener("dragover", (e) => e.preventDefault());
94
+
95
+ zone.addEventListener("drop", (event) => {
96
+ const codeDrag = dragged.dataset.code
97
+ const oldParent = dragged.parentNode;
98
+ const oldCodeZone = oldParent.getAttribute("id")
99
+
100
+ if (oldParent.classList.contains("original-products")) {
101
+ spanNotifaction("No puedes borrar este producto");
102
+ return
103
+ }
104
+
105
+ dragged.remove()
106
+ removeProduct(codeDrag, oldCodeZone)
107
+
108
+ spanNotifaction("Producto removido del grupo", false);
109
+ dragged = null
110
+
111
+ if (oldParent.classList.contains("dropzone") && oldParent.children.length === 0) {
112
+ oldParent.innerHTML = productsPlaceHolder;
113
+ }
114
+ })
115
+ return
116
+ }
117
+
118
+ /*
119
+ ////
120
+ ////// Identificate grups and creata dinamic data
121
+ ////
122
+ */
123
+
124
+ // Create id for grup
125
+ const idZone = generateSimpleID()
126
+
127
+ // Add products if there en the page
128
+ const products = []
129
+ const productsElements = zone.querySelectorAll(".draggable") || None;
130
+
131
+ if (productsElements) {
132
+ productsElements.forEach((proEle) => {
133
+ products.push({
134
+ id: proEle.dataset.idProduct,
135
+ code: proEle.dataset.code
136
+ })
137
+ })
138
+ }
139
+
140
+ const parentZone = zone.parentNode;
141
+ const wordsInGrup = parentZone.querySelectorAll(".item-word") || None
142
+
143
+ // Add words if there en the page
144
+ const words = []
145
+ if (wordsInGrup) {
146
+ wordsInGrup.forEach((wordElement) => {
147
+ words.push(wordElement.textContent)
148
+ })
149
+ }
150
+
151
+ DATA_GRUPS[idZone] = {
152
+ products: products,
153
+ words: words
154
+ }
155
+
156
+ /*
157
+ ////
158
+ ////// Drop Zone product
159
+ ////
160
+ */
161
+
162
+ zone.setAttribute("id", idZone)
163
+ zone.addEventListener("dragover", (e) => e.preventDefault());
164
+
165
+ zone.addEventListener("drop", () => {
166
+ const codeDrag = dragged.dataset.code
167
+ const zoneCode = zone.getAttribute("id")
168
+
169
+ // Check that new produck is not in currents products of the group
170
+ const obj = DATA_GRUPS[zoneCode].products.find((product) => product.code == codeDrag)
171
+ if (obj) {
172
+ spanNotifaction("Ese producto ya está en el grupo");
173
+ return
174
+ }
175
+
176
+ DATA_GRUPS[zoneCode].products.push({
177
+ id: dragged.dataset.idProduct,
178
+ code: dragged.dataset.code
179
+ })
180
+
181
+ const oldParent = dragged.parentNode;
182
+
183
+ const p = zone.querySelector("p");
184
+ if (p) {
185
+ p.remove();
186
+ }
187
+
188
+ // Move original or clone product in zone
189
+ if (oldParent.classList.contains("dropzone")) {
190
+ zone.appendChild(dragged);
191
+ } else {
192
+ const clone = dragged.cloneNode(true);
193
+ clone.classList.remove("opacity-50");
194
+ manageDragables(clone);
195
+ zone.appendChild(clone);
196
+ }
197
+
198
+ if (oldParent.classList.contains("dropzone")) {
199
+ if (oldParent.children.length === 0) oldParent.innerHTML = productsPlaceHolder;
200
+ const zoneCodeOld = oldParent.getAttribute("id")
201
+ removeProduct(codeDrag, zoneCodeOld)
202
+ }
203
+ });
204
+
205
+ // Add listeners to elements options and WordForm for the list words
206
+ addListenersButtonQuestionGrup(zone.parentNode)
207
+ manageFormWord(parentZone.querySelector("form"), parentZone.querySelector(".words-container"), idZone)
208
+ }
209
+
210
+ /*
211
+ ////
212
+ //////
213
+ //////// Management Drags and FormWord
214
+ //////
215
+ ////
216
+ */
217
+
218
+ /**
219
+ *
220
+ * @param {HTMLElement} el
221
+ */
222
+ function manageDragables(el) {
223
+ el.addEventListener("dragstart", () => {
224
+ dragged = el;
225
+ setTimeout(() => el.classList.add("opacity-50"), 0);
226
+ });
227
+
228
+ el.addEventListener("dragend", () => {
229
+ dragged = null;
230
+ el.classList.remove("opacity-50");
231
+ });
232
+ }
233
+
234
+ /**
235
+ *
236
+ * @param {HTMLFormElementt} form
237
+ * @param {HTMLElement} containerWords
238
+ */
239
+ function manageFormWord(form, containerWords, codeGrup) {
240
+ form.addEventListener("submit", (e) => {
241
+ e.preventDefault();
242
+
243
+ if (!form.reportValidity()) {
244
+ return;
245
+ }
246
+
247
+ const input = form.querySelector('input[type="text"]');
248
+
249
+ if (!input) return;
250
+
251
+ const name = input.value.trim();
252
+ if (!name) return;
253
+
254
+ if (DATA_GRUPS[codeGrup].words.includes(name)) {
255
+ input.value = "";
256
+ input.focus();
257
+ spanNotifaction("Esa palabra ya está en la lista");
258
+ return;
259
+ }
260
+
261
+ DATA_GRUPS[codeGrup].words.push(name)
262
+ const wordItem = getItemWord(name, codeGrup)
263
+
264
+ const p = containerWords.querySelector("p");
265
+ if (p) {
266
+ p.remove();
267
+ }
268
+
269
+ containerWords.appendChild(wordItem)
270
+
271
+ input.value = "";
272
+ input.focus();
273
+ });
274
+ }
275
+
276
+ /*
277
+ ////
278
+ //////
279
+ //////// Management Drags and FormWord
280
+ //////
281
+ ////
282
+ */
283
+
284
+ /**
285
+ *
286
+ * @param {HTMLElement} grup
287
+ */
288
+ function removeGrup(grup) {
289
+ delete DATA_GRUPS[grup.querySelector(".dropzone").getAttribute("id")]
290
+ grup.remove()
291
+ }
292
+
293
+ function removeWord(code, wordName) {
294
+ const newWords = DATA_GRUPS[code].words.filter(word => word != wordName)
295
+ DATA_GRUPS[code].words = newWords
296
+ }
297
+
298
+ function removeProduct(codeProduct, codeZone) {
299
+ const newProducts = DATA_GRUPS[codeZone].products.filter((product) => product.code != codeProduct)
300
+ DATA_GRUPS[codeZone].products = newProducts
301
+ }
302
+
303
+ /**
304
+ *
305
+ * @param {HTMLElement} grupContainer
306
+ */
307
+ function addListenersButtonQuestionGrup(grupContainer) {
308
+ // Function hidden question
309
+ grupContainer.querySelector(".cts-question").addEventListener("click", (event) => {
310
+ hiddenQuestionRemove(grupContainer)
311
+ })
312
+
313
+ // Function cancel remove
314
+ grupContainer.querySelector(".cts-no-remove").addEventListener("click", (event) => {
315
+ hiddenQuestionRemove(grupContainer, false)
316
+ })
317
+
318
+ // Function remove grup
319
+ grupContainer.querySelector(".cts-remove").addEventListener("click", (event) => {
320
+ removeGrup(grupContainer)
321
+ })
322
+ }
323
+
324
+ /**
325
+ *
326
+ * @param {HTMLDivElement} container
327
+ * @param {boolean} hiddenQuestion
328
+ */
329
+ function hiddenQuestionRemove(container, hiddenQuestion = true) {
330
+ const question = container.querySelector(".cts-question")
331
+ const remove = container.querySelector(".cts-remove")
332
+ const noRemove = container.querySelector(".cts-no-remove")
333
+
334
+
335
+ if (hiddenQuestion) {
336
+ question.classList.add("hidden")
337
+ remove.classList.remove("hidden")
338
+ noRemove.classList.remove("hidden")
339
+ } else {
340
+ question.classList.remove("hidden")
341
+ remove.classList.add("hidden")
342
+ noRemove.classList.add("hidden")
343
+ }
344
+ }
345
+
346
+ function generateSimpleID() {
347
+ const first = Date.now().toString(35);
348
+ const second = Math.random().toString(36).slice(2);
349
+ return first + second;
350
+ }
351
+
352
+ function showOptionsSave() {
353
+ document.getElementById("question-save").classList.add("hidden");
354
+ document.getElementById("save-data").classList.remove("hidden");
355
+ document.getElementById("cancel-save").classList.remove("hidden");
356
+ }
357
+
358
+ function showQuestionSave() {
359
+ document.getElementById("question-save").classList.remove("hidden");
360
+ document.getElementById("save-data").classList.add("hidden");
361
+ document.getElementById("cancel-save").classList.add("hidden");
362
+ }
363
+
364
+ function getItemWord(wordName, code) {
365
+ const STYLES_DIV = "cts-item-words bg-surface-ligt text-black rounded font-bold text-lg p-1 flex flex-wrap flex-row flex-1 min-w-fit justify-center items-center gap-3";
366
+ const STYLES_BTN = "cts-remove-word cts-btn-general-compress px-2 cts-btn-error"
367
+
368
+ const btn = document.createElement("button");
369
+ btn.setAttribute("data-code", code);
370
+ btn.setAttribute("data-word", wordName);
371
+ btn.classList.add(...STYLES_BTN.split(" "));
372
+ btn.textContent = "➖";
373
+
374
+ const span = document.createElement("span");
375
+ span.classList.add("item-word");
376
+ span.textContent = wordName;
377
+
378
+ const div = document.createElement("div");
379
+ div.setAttribute("id", `word-${code}-${wordName}`);
380
+ div.classList.add(...STYLES_DIV.split(" "));
381
+
382
+ div.appendChild(span);
383
+ div.appendChild(btn);
384
+
385
+ btn.addEventListener("click", (e) => {
386
+ removeWord(code, wordName)
387
+ div.remove()
388
+ })
389
+
390
+ return div;
391
+ };
392
+
393
+
394
+ /*
395
+ ////
396
+ //////
397
+ //////// Save data
398
+ //////
399
+ ////
400
+ */
tecnicas/templates/tecnicas/forms_tester/test_sort.html ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'tecnicas/layouts/base.html' %}
2
+
3
+ {% load static %}
4
+
5
+ {% block title %}Convencional{% endblock %}
6
+
7
+ {% block content %}
8
+ <article class="cts-container-main">
9
+ <article class="cts-wrap-content text-black max-w-4xl">
10
+ <header class="text-center flex-row w-full items-stretch flex justify-around flex-wrap gap-2">
11
+ <h1 class="rounded font-bold text-2xl bg-surface-ligt p-4 flex-1">
12
+ Sesión usando <br>técnica
13
+ <span class="uppercase">{{ session.tecnica.tipo_tecnica }}</span>
14
+ </h1>
15
+ <button class="cts-btn-general cts-btn-error btn-push" onclick="exit_sesion('form-actions')">
16
+ Salir de la sesión
17
+ </button>
18
+ </header>
19
+
20
+ <article class="hidden">
21
+ <form action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post"
22
+ class="form-actions">
23
+ {% csrf_token %}
24
+ <input type="hidden" name="action" class="action-input">
25
+ </form>
26
+ </article>
27
+
28
+ <section class="hidden">
29
+ <input type="hidden" value="{{ session.tecnica.id }}" name="id-tecnica" class="ct-input-id-tech">
30
+ </section>
31
+
32
+ {% if error %}
33
+ {% include "../components/error-message.html" with message=error %}
34
+ {% endif %}
35
+
36
+ <article class="rounded flex flex-col gap-4">
37
+ <section class="flex items-center justify-center bg-surface-ligt p-2 rounded-lg">
38
+ <p class="text-lg font-medium text-center">
39
+ {{ session.tecnica.instrucciones }}
40
+ </p>
41
+ </section>
42
+
43
+ <section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
44
+ <p class="text-xl font-bold text-center">
45
+ Agrupación de productos
46
+ </p>
47
+ </section>
48
+
49
+ <section class="flex items-center justify-center flex-wrap gap-4">
50
+ <div class="bg-surface-ligt p-2 rounded-lg flex-1">
51
+ <p class="text-lg font-bold text-center">
52
+ Repetición única
53
+ </p>
54
+ </div>
55
+ </section>
56
+ </article>
57
+
58
+ <form action="" id="form-word" method="post" class="flex items-center gap-2 hidden">
59
+ <label for="{{ form_word.nombre_palabra.id_for_label }}">
60
+ {{ form_word.nombre_palabra }}
61
+ </label>
62
+ <button type="submit" class="cts-btn-general-compress cts-btn-primary btn-push px-2">➕</button>
63
+ </form>
64
+
65
+ <article class="container-rating-word p-2 py-6 space-y-6 bg-surface-ligt rounded">
66
+ <!-- Definir toddos los productos -->
67
+ <h2 class="text-xl font-bold">Productos en la sesión</h2>
68
+ <div id="items" class="original-products flex gap-4 flex-wrap justify-center"
69
+ data-original-products="original">
70
+ {% for product in products %}
71
+ <div class="draggable bg-btn-secondary text-black font-bold px-4 py-2 rounded cursor-grab"
72
+ draggable="true" data-id-product="{{ product.id }}" data-code="{{ product.codigoProducto }}">
73
+ {{ product.codigoProducto }}
74
+ </div>
75
+ {% endfor %}
76
+ </div>
77
+
78
+ <section class="space-y-4">
79
+ <!-- Espacio para crear grupos -->
80
+ <h2 class="text-xl font-bold">Puedes crear los grupos aquí</h2>
81
+
82
+ <section class="flex sm:flex-row flex-col items-stretch w-full gap-4">
83
+ <article
84
+ class="dropzone trash-products flex-1 min-h-16 bg-ct-error rounded py-4 px-2 flex flex-col items-center justify-center flex-wrap gap-2">
85
+ <p class="text-lg text-center font-bold text-white">
86
+ Arrastra aquí para borrar
87
+ </p>
88
+ <figure class="w-10">
89
+ <img src="{% static 'img/basura.svg' %}" alt="bote de basura" class="invert">
90
+ </figure>
91
+ </article>
92
+
93
+ <article
94
+ class="flex flex-1 items-center justify-center bg-surface-alter p-4 rounded border-2 border-black">
95
+ <button class="cts-btn-general cts-btn-primary btn-push" type="button" onclick="addNewGrup()">
96
+ ➕ Grupo
97
+ </button>
98
+ </article>
99
+ </section>
100
+
101
+ <div id="containers" class="flex gap-4 overflow-x-auto py-4 px-2">
102
+
103
+ <section class="w-fit space-y-4 border rounded p-2 bg-surface-sweet">
104
+ <article
105
+ class="dropzone w-64 min-h-16 bg-surface-alter-card rounded p-2 flex items-center justify-center flex-wrap gap-2">
106
+ <p class="text-products text-center text-lg font-medium">
107
+ Agrupación de productos
108
+ </p>
109
+ </article>
110
+
111
+ <form action="" id="form-word" method="post" class="flex items-center gap-2">
112
+ <label for="{{ form_word.nombre_palabra.id_for_label }}">
113
+ {{ form_word.nombre_palabra }}
114
+ </label>
115
+ <button type="submit"
116
+ class="cts-btn-general-compress cts-btn-primary btn-push px-2">➕</button>
117
+ </form>
118
+
119
+ <article
120
+ class="words-container w-64 min-h-16 bg-surface-alter rounded p-2 flex items-center justify-center flex-wrap gap-2">
121
+ <p class="text-words text-center text-lg font-medium">
122
+ Lista de atributos
123
+ </p>
124
+ </article>
125
+
126
+ <article class="cts-container-question flex items-center justify-around gap-2">
127
+ <button class="cts-question cts-btn-general-compress flex-1 cts-btn-secondary btn-push">
128
+ ¿Remover este grupo?
129
+ </button>
130
+ <button class="cts-remove cts-btn-general-compress flex-1 cts-btn-primary btn-push hidden">
131
+ Remover
132
+ </button>
133
+ <button class="cts-no-remove cts-btn-general-compress flex-1 cts-btn-error btn-push hidden">
134
+ Conservar
135
+ </button>
136
+ </article>
137
+ </section>
138
+
139
+ </div>
140
+
141
+ </section>
142
+
143
+ <section class="flex justify-end gap-4">
144
+ <button id="save-progress" class="cts-btn-general cts-btn-primary btn-push">
145
+ Guardar datos actuales
146
+ </button>
147
+ <button id="question-save" class="cts-btn-general cts-btn-secondary btn-push">
148
+ ¿Finalizar sesión?
149
+ </button>
150
+ <button id="save-data" class="cts-btn-general cts-btn-primary btn-push hidden">
151
+ Finalizar
152
+ </button>
153
+ <button id="cancel-save" class="cts-btn-general cts-btn-error btn-push hidden">
154
+ Cancelar
155
+ </button>
156
+ <span id="loading-data-save" class="loading loading-spinner loading-xl text-accent hidden"></span>
157
+ </section>
158
+ </article>
159
+
160
+ {% include "../components/toast-container.html" %}
161
+ </article>
162
+ </article>
163
+ {% endblock %}
164
+
165
+ {% block extra_js %}
166
+ <script src="{% static 'js/actions-form.js' %}"></script>
167
+ <script src="{% static 'js/test-sort.js' %}"></script>
168
+ {% endblock %}
tecnicas/urls.py CHANGED
@@ -118,6 +118,10 @@ urlpatterns = [
118
  views.pfTest,
119
  name="session_pf"),
120
 
 
 
 
 
121
 
122
  # APIs
123
  path("presenter/api/nueva-etiqueta",
 
118
  views.pfTest,
119
  name="session_pf"),
120
 
121
+ path("testers/init-session/<str:code_sesion>/sort",
122
+ views.sortTest,
123
+ name="session_sort"),
124
+
125
 
126
  # APIs
127
  path("presenter/api/nueva-etiqueta",
tecnicas/views/__init__.py CHANGED
@@ -36,3 +36,4 @@ from .tester_forms.sessions_list_tester import sessionsListTester
36
  from .tester_forms.convencional_scales import convencionalScales
37
  from .tester_forms.cata_test import cataTest
38
  from .tester_forms.pf_test import pfTest
 
 
36
  from .tester_forms.convencional_scales import convencionalScales
37
  from .tester_forms.cata_test import cataTest
38
  from .tester_forms.pf_test import pfTest
39
+ from .tester_forms.sort_test import sortTest
tecnicas/views/tester_forms/init_tester_form.py CHANGED
@@ -50,6 +50,12 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
50
  view_controller = InitSessionPFController(
51
  sensorial_session=session, user_tester=req.user.user_catador)
52
  response = view_controller.controllPost(request=req)
 
 
 
 
 
 
53
  else:
54
  context = {
55
  "session": session,
 
50
  view_controller = InitSessionPFController(
51
  sensorial_session=session, user_tester=req.user.user_catador)
52
  response = view_controller.controllPost(request=req)
53
+
54
+ elif type_technique == "sort":
55
+ view_controller = InitSessionSortController(
56
+ sensorial_session=session, user_tester=req.user.user_catador)
57
+ response = view_controller.controllPost(request=req)
58
+
59
  else:
60
  context = {
61
  "session": session,
tecnicas/views/tester_forms/sort_test.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from tecnicas.models import SesionSensorial
3
+ from tecnicas.controllers import TestSortController
4
+ from tecnicas.utils import noValidTechnique
5
+
6
+
7
+ def sortTest(req: HttpRequest, code_sesion: str):
8
+ if req.method == "GET":
9
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
10
+ controll_view = TestSortController(
11
+ sensorial_session=session, user_tester=req.user.user_catador)
12
+ return controll_view.controllGet(request=req)
13
+
14
+ elif req.method == "POST":
15
+ return noValidTechnique(
16
+ name_view="cata_system:catador_init_session",
17
+ query_params={
18
+ "error": "Método no valido"
19
+ }
20
+ )