chartManD commited on
Commit
270f8c2
·
1 Parent(s): fabdc43

Se crea plano para napping

Browse files
tecnicas/controllers/__init__.py CHANGED
@@ -47,6 +47,7 @@ from .views_controller.sessions_tester.tests_forms.test_rata_controller import T
47
  from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
48
  from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
49
  from .views_controller.sessions_tester.tests_forms.test_sort_controller import TestSortController
 
50
 
51
  from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
52
  from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
 
47
  from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
48
  from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
49
  from .views_controller.sessions_tester.tests_forms.test_sort_controller import TestSortController
50
+ from .views_controller.sessions_tester.tests_forms.test_napping_controller import TestNappingController
51
 
52
  from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
53
  from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py CHANGED
@@ -10,12 +10,13 @@ class InitSessionNappingController(InitSessionController):
10
  def __init__(self, sensorial_session, user_tester):
11
  super().__init__(sensorial_session, user_tester)
12
  self.current_direction = "tecnicas/forms_tester/init_test_napping.html"
13
- self.sort_direction = "cata_system:session_napping"
14
 
15
  def controllGet(self, request: HttpRequest):
16
  self.context = {
17
  "session": self.session,
18
  "type_technique": "napping",
 
19
  }
20
 
21
  if self.session.tecnica.repeticion == 1:
@@ -29,4 +30,45 @@ class InitSessionNappingController(InitSessionController):
29
  return render(request, self.current_direction, self.context)
30
 
31
  def isEndedSession(self):
32
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  def __init__(self, sensorial_session, user_tester):
11
  super().__init__(sensorial_session, user_tester)
12
  self.current_direction = "tecnicas/forms_tester/init_test_napping.html"
13
+ self.napping_direction = "cata_system:session_napping"
14
 
15
  def controllGet(self, request: HttpRequest):
16
  self.context = {
17
  "session": self.session,
18
  "type_technique": "napping",
19
+ "has_ended": self.isEndedSession()
20
  }
21
 
22
  if self.session.tecnica.repeticion == 1:
 
30
  return render(request, self.current_direction, self.context)
31
 
32
  def isEndedSession(self):
33
+ return Participacion.objects.get(
34
+ tecnica=self.session.tecnica, catador=self.tester).finalizado
35
+
36
+ def controllPost(self, request: HttpRequest):
37
+ context = {
38
+ "session": self.session,
39
+ "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
40
+ }
41
+
42
+ use_action = request.POST["action"]
43
+
44
+ if use_action == "start_posting":
45
+ parameters = {
46
+ "code_sesion": self.session.codigo_sesion
47
+ }
48
+
49
+ is_end = self.isEndedSession()
50
+ if is_end:
51
+ context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
52
+ return render(request, self.current_direction, context)
53
+
54
+ update_participation = ParticipacionController.enterSession(
55
+ tester=request.user.user_catador, session=self.session)
56
+
57
+ if isinstance(update_participation, dict):
58
+ context["error"] = update_participation["error"]
59
+ return render(request, self.current_direction, context)
60
+
61
+ request.session["id_participation"] = update_participation.id
62
+
63
+ return redirect(reverse(self.napping_direction, kwargs=parameters))
64
+
65
+ elif use_action == "exit_session":
66
+ response = ParticipacionController.outSession(
67
+ tester=request.user.user_catador, session=self.session)
68
+ if isinstance(response, dict):
69
+ context["error"] = response["error"]
70
+ return render(request, self.current_direction, context)
71
+
72
+ else:
73
+ context["error"] = "Acción sin especificar"
74
+ return render(request, self.current_direction, context)
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
5
+ from .general_test_controller import GenetalTestController
6
+
7
+
8
+ class TestNappingController(GenetalTestController):
9
+ def __init__(self, sensorial_session, user_tester):
10
+ super().__init__(sensorial_session, user_tester)
11
+ self.napping_test = "tecnicas/forms_tester/test_napping.html"
12
+
13
+ def controllGet(self, request: HttpRequest):
14
+ technique = self.session.tecnica
15
+
16
+ self.participation = Participacion.objects.get(
17
+ tecnica=technique, catador=request.user.user_catador)
18
+
19
+ # Comprobar que el Catador no haya finalizado
20
+ if self.participation.finalizado:
21
+ params = {
22
+ "code_sesion": self.session.codigo_sesion
23
+ }
24
+ return redirect(reverse(self.previus_directory, kwargs=params))
25
+
26
+ if technique.repeticion == 1:
27
+ return self.nappingTest(request)
28
+ else:
29
+ params = {
30
+ "code_sesion": self.session.codigo_sesion
31
+ }
32
+ return redirect(reverse(self.previus_directory, kwargs=params))
33
+
34
+ def nappingTest(self, request: HttpRequest):
35
+ self.context["session"] = self.session
36
+ technique = self.session.tecnica
37
+
38
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
39
+ self.context["products"] = products_in_technique
40
+
41
+ return render(request, self.napping_test, self.context)
tecnicas/static/js/test-napping.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ const planeContainer = document.getElementById('napping-plane');
3
+ const productsContainer = document.getElementById('items');
4
+ const products = document.querySelectorAll('.item-product');
5
+
6
+ // Configuration for the physical dimensions of the tablecloth (in cm)
7
+ const PHYSICAL_WIDTH = 60;
8
+ const PHYSICAL_HEIGHT = 40;
9
+
10
+ let selectedProductCode = null;
11
+ let selectedProductId = null;
12
+
13
+ // Object to store coordinates: { "CODE": { x: 10.5, y: 20.1, id: 123 } }
14
+ const placedPoints = {};
15
+
16
+ // 1. Handle Product Selection
17
+ products.forEach(product => {
18
+ product.addEventListener('click', () => {
19
+ // Remove selection from others
20
+ products.forEach(p => p.classList.remove('ring-4', 'ring-primary'));
21
+
22
+ // Select current
23
+ product.classList.add('ring-4', 'ring-primary');
24
+ selectedProductCode = product.dataset.code;
25
+ selectedProductId = product.dataset.idProduct;
26
+ });
27
+ });
28
+
29
+ // 2. Handle Plane Click (Placing Points)
30
+ planeContainer.addEventListener('click', (e) => {
31
+ if (!selectedProductCode) {
32
+ spanNotifaction("Por favor, selecciona un producto primero")
33
+ return;
34
+ }
35
+
36
+ const rect = planeContainer.getBoundingClientRect();
37
+
38
+ // Calculate click position relative to the container (in pixels)
39
+ const xPixel = e.clientX - rect.left;
40
+ const yPixel = e.clientY - rect.top;
41
+
42
+ // Calculate scaled coordinates (0 to PHYSICAL_DIMENSIONS)
43
+ // X axis: 0 on left, 60 on right
44
+ const xCoord = (xPixel / rect.width) * PHYSICAL_WIDTH;
45
+
46
+ // Y axis: 0 on bottom, 40 on top (Invert Y because DOM Y is 0 at top)
47
+ const yCoord = PHYSICAL_HEIGHT - ((yPixel / rect.height) * PHYSICAL_HEIGHT);
48
+
49
+ // Update Data Object
50
+ placedPoints[selectedProductCode] = {
51
+ x: parseFloat(xCoord.toFixed(2)),
52
+ y: parseFloat(yCoord.toFixed(2)),
53
+ id: selectedProductId
54
+ };
55
+
56
+ // Render Point
57
+ renderPoint(selectedProductCode, xPixel, yPixel, xCoord, yCoord);
58
+ });
59
+
60
+ function renderPoint(code, xPx, yPx, xVal, yVal) {
61
+ // Remove existing point for this product if it exists
62
+ const existingPoint = document.getElementById(`point-${code}`);
63
+ if (existingPoint) {
64
+ existingPoint.remove();
65
+ }
66
+
67
+ const point = document.createElement('div');
68
+ point.id = `point-${code}`;
69
+ point.className = 'absolute w-4 h-4 bg-red-600 rounded-full transform -translate-x-1/2 -translate-y-1/2 cursor-pointer border-2 border-white shadow-md group';
70
+ point.style.left = `${xPx}px`;
71
+ point.style.top = `${yPx}px`;
72
+
73
+ // Tooltip/Label
74
+ const label = document.createElement('div');
75
+ label.className = 'absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded whitespace-nowrap z-10 hidden group-hover:block';
76
+ label.innerHTML = `
77
+ <strong>${code}</strong><br>
78
+ X: ${xVal.toFixed(1)}<br>
79
+ Y: ${yVal.toFixed(1)}
80
+ `;
81
+
82
+ point.appendChild(label);
83
+ planeContainer.appendChild(point);
84
+
85
+ // Also show a permanent label next to the point if desired,
86
+ // or just rely on the hover. For now, let's add a small text label below it.
87
+ const textLabel = document.createElement('span');
88
+ textLabel.className = 'absolute top-4 left-1/2 transform -translate-x-1/2 text-xs font-bold text-gray-700 pointer-events-none';
89
+ textLabel.innerText = code;
90
+ point.appendChild(textLabel);
91
+ }
92
+
tecnicas/templates/tecnicas/forms_tester/test_napping.html ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'tecnicas/layouts/base.html' %}
2
+
3
+ {% load static %}
4
+
5
+ {% block title %}Napping{% 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
+ Modalidad: Napping
46
+ </p>
47
+ </section>
48
+ </article>
49
+
50
+ <article class="container-rating-word p-2 py-6 space-y-6 bg-surface-ligt rounded min-w-3xl">
51
+ <h2 class="text-xl font-bold">Productos en la sesión</h2>
52
+ <div id="items" class="original-products flex gap-4 flex-wrap justify-center"
53
+ data-original-products="original">
54
+ {% for product in products %}
55
+ <div class="item-product bg-btn-secondary text-black font-bold px-4 py-2 rounded cursor-grab transition-all"
56
+ data-id-product="{{ product.id }}" data-code="{{ product.codigoProducto }}">
57
+ {{ product.codigoProducto }}
58
+ </div>
59
+ {% endfor %}
60
+ </div>
61
+
62
+ <!-- Cartesian Plane Container -->
63
+ <div class="flex justify-center w-full">
64
+ <div class="relative w-full max-w-[800px] aspect-[3/2] bg-white border-2 border-gray-400 shadow-inner cursor-crosshair"
65
+ id="napping-plane"
66
+ style="background-image: linear-gradient(#e5e7eb 1px, transparent 1px), linear-gradient(90deg, #e5e7eb 1px, transparent 1px); background-size: 20px 20px;">
67
+ <!-- Axis Labels -->
68
+ <span class="absolute bottom-1 right-2 text-xs text-gray-500 font-bold">60cm (X)</span>
69
+ <span class="absolute top-2 left-1 text-xs text-gray-500 font-bold">40cm (Y)</span>
70
+ <span class="absolute bottom-1 left-1 text-xs text-gray-500 font-bold">0</span>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- <section role="alert" class="alert alert-info">
75
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
76
+ class="h-6 w-6 shrink-0 stroke-current">
77
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
78
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 0 0118 0z"></path>
79
+ </svg>
80
+ <span class="text-lg">
81
+ Para guardar los datos sin finalizar su sesión use el botón
82
+ <b>"Guardar progreso"</b>
83
+ </span>
84
+ </section>
85
+
86
+ <section role="alert" class="alert alert-warning">
87
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
88
+ viewBox="0 0 24 24">
89
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
90
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
91
+ </svg>
92
+ <div class="flex-col">
93
+ <span class="text-lg block">
94
+ Si usas el botón <b>“Salir de la sesión”</b> asegúrese de guardar el progreso de lo contrario
95
+ perderás todo lo que no hayas guardado antes
96
+ </span>
97
+ <span class="text-lg block">
98
+ Con el botón <b>��Finalizar la sesión”</b>, se guardan las palabras, sales de la sesión y no
99
+ podrás ingresar otra vez
100
+ </span>
101
+ </div>
102
+ </section> -->
103
+
104
+ <!-- <section class="flex justify-end gap-4 max-sm:flex-col">
105
+ <button id="save-progress" class="cts-btn-general cts-btn-primary btn-push">
106
+ Guardar progreso
107
+ </button>
108
+ <div class="flex gap-2">
109
+ <button id="question-save" class="cts-btn-general cts-btn-secondary btn-push flex-1">
110
+ ¿Finalizar sesión?
111
+ </button>
112
+ <button id="finish-session" class="cts-btn-general cts-btn-tertiary btn-push hidden flex-1">
113
+ Finalizar
114
+ </button>
115
+ <button id="cancel-save" class="cts-btn-general cts-btn-error btn-push hidden flex-1">
116
+ Cancelar
117
+ </button>
118
+ </div>
119
+ <span id="loading-data-save" class="loading loading-spinner loading-xl text-accent hidden"></span>
120
+ </section> -->
121
+ </article>
122
+
123
+ {% include "../components/toast-container.html" %}
124
+ </article>
125
+ </article>
126
+ {% endblock %}
127
+
128
+ {% block extra_js %}
129
+ <script src="{% static 'js/actions-form.js' %}"></script>
130
+ <script src="{% static 'js/test-napping.js' %}"></script>
131
+ {% endblock %}
tecnicas/urls.py CHANGED
@@ -122,6 +122,10 @@ urlpatterns = [
122
  views.sortTest,
123
  name="session_sort"),
124
 
 
 
 
 
125
 
126
  # APIs
127
  path("presenter/api/nueva-etiqueta",
 
122
  views.sortTest,
123
  name="session_sort"),
124
 
125
+ path("testers/init-session/<str:code_sesion>/nappping",
126
+ views.nappingTest,
127
+ name="session_napping"),
128
+
129
 
130
  # APIs
131
  path("presenter/api/nueva-etiqueta",
tecnicas/views/__init__.py CHANGED
@@ -38,3 +38,4 @@ from .tester_forms.convencional_scales import convencionalScales
38
  from .tester_forms.cata_test import cataTest
39
  from .tester_forms.pf_test import pfTest
40
  from .tester_forms.sort_test import sortTest
 
 
38
  from .tester_forms.cata_test import cataTest
39
  from .tester_forms.pf_test import pfTest
40
  from .tester_forms.sort_test import sortTest
41
+ from .tester_forms.napping_test import nappingTest
tecnicas/views/tester_forms/init_tester_form.py CHANGED
@@ -46,7 +46,7 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
46
  return response
47
 
48
  elif req.method == "POST":
49
- if type_technique == "escalas" or type_technique == "rata" or type_technique == "cata":
50
  view_controller = InitSessionEscalasController(
51
  sensorial_session=session, user_tester=req.user.user_catador)
52
  response = view_controller.controllPost(request=req)
@@ -60,7 +60,12 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
60
  view_controller = InitSessionSortController(
61
  sensorial_session=session, user_tester=req.user.user_catador)
62
  response = view_controller.controllPost(request=req)
63
-
 
 
 
 
 
64
  else:
65
  context = {
66
  "session": session,
 
46
  return response
47
 
48
  elif req.method == "POST":
49
+ if type_technique in ["escalas", "rata", "cata"]:
50
  view_controller = InitSessionEscalasController(
51
  sensorial_session=session, user_tester=req.user.user_catador)
52
  response = view_controller.controllPost(request=req)
 
60
  view_controller = InitSessionSortController(
61
  sensorial_session=session, user_tester=req.user.user_catador)
62
  response = view_controller.controllPost(request=req)
63
+
64
+ elif type_technique == "napping":
65
+ view_controller = InitSessionNappingController(
66
+ sensorial_session=session, user_tester=req.user.user_catador)
67
+ response = view_controller.controllPost(request=req)
68
+
69
  else:
70
  context = {
71
  "session": session,
tecnicas/views/tester_forms/napping_test.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from tecnicas.models import SesionSensorial
3
+ from tecnicas.controllers import TestNappingController
4
+ from tecnicas.utils import noValidTechnique
5
+
6
+
7
+ def nappingTest(req: HttpRequest, code_sesion: str):
8
+ if req.method == "GET":
9
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
10
+ controll_view = TestNappingController(
11
+ sensorial_session=session, user_tester=req.user.user_catador)
12
+ return controll_view.controllGet(request=req)
13
+
14
+ elif req.method == "POST":
15
+ # Se usa para finalizar la sesión
16
+ session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
17
+ controll_view = TestNappingController(
18
+ sensorial_session=session, user_tester=req.user.user_catador)
19
+ return controll_view.controllPost(request=req)
20
+
21
+ else:
22
+ return noValidTechnique(
23
+ name_view="cata_system:catador_init_session",
24
+ query_params={
25
+ "error": "Método no valido"
26
+ }
27
+ )