Norberto Montalvo García commited on
Commit
a42a476
·
unverified ·
2 Parent(s): b208347 df3eb83

Merge pull request #25 from CascoArcilla/HU9

Browse files
Files changed (28) hide show
  1. Dockerfile +35 -0
  2. docker-compose.yml +0 -0
  3. tecnicas/controllers/__init__.py +1 -0
  4. tecnicas/controllers/models_controller/calificacion_controller.py +42 -55
  5. tecnicas/controllers/models_controller/dato_controller.py +13 -14
  6. tecnicas/controllers/models_controller/particiapacion_controller.py +6 -9
  7. tecnicas/controllers/models_controller/sesion_controller.py +0 -8
  8. tecnicas/controllers/views_controller/api_rating_controller.py +6 -53
  9. tecnicas/controllers/views_controller/session_management/details_escala_controller.py +27 -12
  10. tecnicas/controllers/views_controller/session_management/monitor_controller.py +0 -4
  11. tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py +1 -1
  12. tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py +158 -0
  13. tecnicas/controllers/views_controller/sessions_tester/init_session_tester_controller.py +14 -8
  14. tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py +23 -23
  15. tecnicas/models/calificacion.py +4 -1
  16. tecnicas/models/dato.py +1 -1
  17. tecnicas/models/dato_valor.py +2 -2
  18. tecnicas/static/js/created-scale.js +2 -0
  19. tecnicas/templates/tecnicas/components/form-scale-continue.html +62 -0
  20. tecnicas/templates/tecnicas/components/form-scale-structure.html +52 -0
  21. tecnicas/templates/tecnicas/forms_tester/convencional.html +19 -128
  22. tecnicas/templates/tecnicas/forms_tester/init_session.html +1 -1
  23. tecnicas/templates/tecnicas/manage_sesions/detalles-sesion.html +12 -1
  24. tecnicas/utils/general_controllers.py +2 -2
  25. tecnicas/views/apis/rating_word.py +5 -4
  26. tecnicas/views/sessions_management/session_details.py +1 -1
  27. tecnicas/views/tester_forms/convencional_scales.py +15 -67
  28. tecnicas/views/tester_forms/init_tester_form.py +2 -5
Dockerfile ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ubuntu:24.04
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ libpq-dev \
8
+ python3 python3-dev python3-pip python3-venv \
9
+ gcc pkg-config \
10
+ default-libmysqlclient-dev \
11
+ nodejs npm \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ WORKDIR /cata_system
15
+
16
+ RUN npm install -g pnpm
17
+
18
+ COPY requirements.txt .
19
+
20
+ RUN python3 -m venv /opt/venv
21
+ ENV PATH="/opt/venv/bin:$PATH"
22
+
23
+ RUN pip install --upgrade pip && \
24
+ pip install wheel && \
25
+ pip install -r requirements.txt
26
+
27
+ COPY . .
28
+
29
+ RUN python3 manage.py tailwind install
30
+ RUN python3 manage.py tailwind build
31
+ RUN python3 manage.py collectstatic --noinput
32
+
33
+ EXPOSE 7860
34
+
35
+ CMD ["python3", "manage.py", "runserver", "0.0.0.0:7860"]
docker-compose.yml ADDED
File without changes
tecnicas/controllers/__init__.py CHANGED
@@ -26,6 +26,7 @@ from .views_controller.session_management.monitor_escalas_controller import Moni
26
 
27
  from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
28
  from .views_controller.sessions_tester.init_session_tester_controller import InitSessionTesterController
 
29
 
30
  from .views_controller.api_rating_controller import ApiRatingController
31
  from .views_controller.tester_list_controller import TesterListController
 
26
 
27
  from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
28
  from .views_controller.sessions_tester.init_session_tester_controller import InitSessionTesterController
29
+ from .views_controller.sessions_tester.convencional_scales_controller import ConvencionalScalesController
30
 
31
  from .views_controller.api_rating_controller import ApiRatingController
32
  from .views_controller.tester_list_controller import TesterListController
tecnicas/controllers/models_controller/calificacion_controller.py CHANGED
@@ -6,49 +6,22 @@ from tecnicas.utils import controller_error, getId
6
 
7
  class CalificacionController():
8
  def __init__(self, product: Producto | int, technique: Tecnica | int, tester: Catador | int):
 
 
 
9
  atributes = {
10
- "num_repeticion": 0,
11
- "id_tecnica_id": getId(technique),
12
  "id_producto_id": getId(product),
13
  "id_catador_id": getId(tester),
14
  }
15
 
16
- self.rating = Calificacion(**atributes)
17
-
18
- def validateRating(self):
19
- try:
20
- self.rating.clean()
21
- return self.rating
22
- except ValidationError as e:
23
- return controller_error("No es posible validar la calificación")
24
-
25
- def setRepetition(self, repetition: int = None) -> int | dict:
26
- try:
27
- if repetition is not None:
28
- self.rating.num_repeticion = repetition
29
- else:
30
- self.rating.num_repeticion = self.rating.id_tecnica.repeticion
31
-
32
- return self.rating.num_repeticion
33
- except ValidationError as e:
34
- return controller_error(e)
35
-
36
- def saveRating(self):
37
- try:
38
- self.rating.save()
39
- return self.rating
40
- except ValidationError as e:
41
- return controller_error(e)
42
 
43
  @staticmethod
44
  def getRatingsByTechnique(technique: Tecnica):
45
  repetition = technique.repeticion
46
-
47
- if not repetition:
48
- return controller_error("Sin datos calificados aún")
49
-
50
  ratings = list(Calificacion.objects.filter(id_tecnica=technique))
51
-
52
  return ratings
53
 
54
  @staticmethod
@@ -94,39 +67,53 @@ class CalificacionController():
94
  return ratings
95
 
96
  @staticmethod
97
- def checkProducsWithoutRating(
98
  positions: list[Posicion] = None,
99
- user_cata: str = None,
100
  repetition: int = None,
101
- technique: Tecnica = None,
102
- id_technique: int = None,
103
  num_words: int = None):
104
- check_products = [position.id_producto for position in positions]
 
 
 
 
105
 
106
  filters = {
107
- "user_tester": user_cata,
108
- "repetition": repetition
109
  }
110
 
111
- if technique is not None:
112
- filters["technique"] = technique
113
- elif id_technique is not None:
114
- filters["id_technique"] = id_technique
 
115
 
116
- ratings = CalificacionController.getRatings(**filters)
 
 
 
 
117
 
 
118
  if len(ratings) == 0:
119
- return positions
120
 
121
- ratings_dict = defaultdict(list)
 
122
 
123
- for rat in ratings:
124
- ratings_dict[rat.id_producto.id].append(rat)
125
 
126
- for index, product in enumerate(check_products):
127
- ratings_of_product = ratings_dict.get(product.id, [])
128
 
129
- if len(ratings_of_product) < num_words or len(ratings_of_product) == 0:
130
- return positions[index]
131
-
132
- return controller_error("Sin productos por calificar")
 
 
 
 
 
6
 
7
  class CalificacionController():
8
  def __init__(self, product: Producto | int, technique: Tecnica | int, tester: Catador | int):
9
+ technique = Tecnica.objects.only(
10
+ "repeticion").get(id=getId(technique))
11
+
12
  atributes = {
13
+ "num_repeticion": technique.repeticion,
14
+ "id_tecnica": technique,
15
  "id_producto_id": getId(product),
16
  "id_catador_id": getId(tester),
17
  }
18
 
19
+ (self.rating, created) = Calificacion.objects.get_or_create(**atributes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  @staticmethod
22
  def getRatingsByTechnique(technique: Tecnica):
23
  repetition = technique.repeticion
 
 
 
 
24
  ratings = list(Calificacion.objects.filter(id_tecnica=technique))
 
25
  return ratings
26
 
27
  @staticmethod
 
67
  return ratings
68
 
69
  @staticmethod
70
+ def checkPositionWithoutRating(
71
  positions: list[Posicion] = None,
72
+ user_cata: Catador | str = None,
73
  repetition: int = None,
74
+ technique: Tecnica | int = None,
 
75
  num_words: int = None):
76
+ end_rating = False
77
+
78
+ # Obtener lista con codigos de productos
79
+ check_products = [
80
+ position.id_producto.codigoProducto for position in positions]
81
 
82
  filters = {
83
+ "num_repeticion": repetition,
84
+ "id_tecnica": getId(technique)
85
  }
86
 
87
+ if isinstance(user_cata, Catador):
88
+ filters["id_catador"] = user_cata
89
+ else:
90
+ filters["id_catador"] = Catador.objects.get(
91
+ user__username=user_cata)
92
 
93
+ ratings = list(Calificacion.objects.filter(**filters).select_related(
94
+ "id_producto",
95
+ "id_tecnica",
96
+ "id_catador",
97
+ ))
98
 
99
+ # Si no hay calificaciones regresar las posiciones
100
  if len(ratings) == 0:
101
+ return (positions, end_rating)
102
 
103
+ rating_products = [
104
+ rating.id_producto.codigoProducto for rating in ratings]
105
 
106
+ for index, rating in enumerate(ratings):
107
+ data_rating = rating.dato_calificacion.all()
108
 
109
+ if len(data_rating) < num_words or len(data_rating) == 0:
110
+ return (positions[index], end_rating)
111
 
112
+ next_product = list(set(check_products) - set(rating_products))
113
+ if len(next_product) != 0:
114
+ next_position = [
115
+ position for position in positions if position.id_producto.codigoProducto == next_product[0]][0]
116
+ return (next_position, end_rating)
117
+ else:
118
+ end_rating = True
119
+ return (positions[-1], end_rating)
tecnicas/controllers/models_controller/dato_controller.py CHANGED
@@ -12,7 +12,11 @@ class DatoController():
12
  }
13
 
14
  self.data = Dato(**atributes)
15
- self.value_data = ValorDecimal(valor=value_rating)
 
 
 
 
16
 
17
  def setRating(self, new_rating: Calificacion):
18
  try:
@@ -35,19 +39,14 @@ class DatoController():
35
  except ValidationError as e:
36
  return controller_error(e.message)
37
 
38
- def setInstanceValue(self):
39
- technique = self.data.id_calificacion.id_tecnica
40
-
41
- if technique.tipo_tecnica == "cata":
42
- self.value_data = ValorBooleano(
43
- id_dato=self.data,
44
- valor=self.value_data.valor
45
- )
46
  else:
47
- self.value_data = ValorDecimal(
48
- id_dato=self.data,
49
- valor=self.value_data.valor
50
- )
51
 
52
  return self.value_data
53
 
@@ -88,7 +87,7 @@ class DatoController():
88
  repeticion=F("id_dato__id_calificacion__num_repeticion"),
89
  producto_code=F(
90
  "id_dato__id_calificacion__id_producto__codigoProducto"),
91
- usuarioCatador=F(
92
  "id_dato__id_calificacion__id_catador__user__username"),
93
  dato_valor=F("valor")
94
  )
 
12
  }
13
 
14
  self.data = Dato(**atributes)
15
+
16
+ if isinstance(value_rating, bool):
17
+ self.value_data = ValorBooleano(valor=value_rating)
18
+ else:
19
+ self.value_data = ValorDecimal(valor=value_rating)
20
 
21
  def setRating(self, new_rating: Calificacion):
22
  try:
 
39
  except ValidationError as e:
40
  return controller_error(e.message)
41
 
42
+ def setValue(self, new_value=None):
43
+ if new_value:
44
+ if isinstance(new_value, bool):
45
+ self.value_data = ValorBooleano(valor=new_value)
46
+ else:
47
+ self.value_data = ValorDecimal(valor=new_value)
 
 
48
  else:
49
+ self.value_data.id_dato = self.data
 
 
 
50
 
51
  return self.value_data
52
 
 
87
  repeticion=F("id_dato__id_calificacion__num_repeticion"),
88
  producto_code=F(
89
  "id_dato__id_calificacion__id_producto__codigoProducto"),
90
+ usuario_catador=F(
91
  "id_dato__id_calificacion__id_catador__user__username"),
92
  dato_valor=F("valor")
93
  )
tecnicas/controllers/models_controller/particiapacion_controller.py CHANGED
@@ -16,15 +16,12 @@ class ParticipacionController():
16
  return controller_error("No se ha encontrado la participación")
17
 
18
  @staticmethod
19
- def finishSession(id_participation: int):
20
- try:
21
- participation = Participacion.objects.get(id=id_participation)
22
- participation.finalizado = True
23
- participation.activo = False
24
- participation.save()
25
- return participation
26
- except Participacion.DoesNotExist:
27
- return controller_error("No se ha encontrado la participación")
28
 
29
  @staticmethod
30
  def outSession(tester: Catador, session: SesionSensorial):
 
16
  return controller_error("No se ha encontrado la participación")
17
 
18
  @staticmethod
19
+ def finishSession(participation: Participacion):
20
+ participation.refresh_from_db()
21
+ participation.finalizado = True
22
+ participation.activo = False
23
+ participation.save()
24
+ return participation
 
 
 
25
 
26
  @staticmethod
27
  def outSession(tester: Catador, session: SesionSensorial):
tecnicas/controllers/models_controller/sesion_controller.py CHANGED
@@ -131,14 +131,6 @@ class SesionController():
131
  else:
132
  use_session = session
133
 
134
- (is_update_participations,
135
- message) = ParticipacionController.outAllInSession(use_session)
136
-
137
- if not is_update_participations:
138
- return controller_error(message)
139
-
140
  use_session.activo = False
141
-
142
  use_session.save()
143
-
144
  return session
 
131
  else:
132
  use_session = session
133
 
 
 
 
 
 
 
134
  use_session.activo = False
 
135
  use_session.save()
 
136
  return session
tecnicas/controllers/views_controller/api_rating_controller.py CHANGED
@@ -7,65 +7,18 @@ class ApiRatingController():
7
  self.rating_controller = rating_controller
8
  self.data_controller = data_controller
9
 
10
- def setRating(self) -> int | dict:
11
- repetition = self.rating_controller.setRepetition()
12
- if isinstance(repetition, dict):
13
- return controller_error(repetition["error"])
14
- return repetition
15
 
16
- def saveRating(self):
17
- rating = self.rating_controller.saveRating()
18
- if isinstance(rating, dict):
19
- return controller_error(rating["error"])
20
- return rating
21
-
22
- def setRatingInData(self):
23
- update_rating = self.data_controller.setRating(
24
- self.rating_controller.rating)
25
- if isinstance(update_rating, dict):
26
- return controller_error(update_rating["error"])
27
- return update_rating
28
-
29
- def saveData(self):
30
  data = self.data_controller.saveData()
31
- if isinstance(data, dict):
32
- return controller_error(data["error"])
33
- return data
34
-
35
- def setValueRating(self):
36
- self.data_controller.setInstanceValue()
37
-
38
- def saveValue(self):
39
- value = self.data_controller.saveValue()
40
- if isinstance(value, dict):
41
- return controller_error(value["error"])
42
-
43
- def logicView(self) -> dict:
44
- validate = self.rating_controller.validateRating()
45
- if isinstance(validate, dict):
46
- return controller_error(validate["error"])
47
-
48
- reptition = self.setRating()
49
- if isinstance(reptition, dict):
50
- return controller_error(reptition["error"])
51
-
52
- rating = self.saveRating()
53
- if isinstance(rating, dict):
54
- return controller_error(rating["error"])
55
-
56
- rating_data = self.setRatingInData()
57
- if isinstance(rating_data, dict):
58
- return controller_error(rating_data["error"])
59
-
60
- data = self.saveData()
61
  if isinstance(data, dict):
62
  return controller_error(data["error"])
63
 
64
- value = self.setValueRating()
65
- if isinstance(value, dict):
66
- return controller_error(value["error"])
67
 
68
- value_save = self.saveValue()
69
  if isinstance(value_save, dict):
70
  return controller_error(value_save["error"])
71
 
 
7
  self.rating_controller = rating_controller
8
  self.data_controller = data_controller
9
 
10
+ def controllPostScales(self) -> dict:
11
+ self.data_controller.setRating(
12
+ new_rating=self.rating_controller.rating
13
+ )
 
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  data = self.data_controller.saveData()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  if isinstance(data, dict):
17
  return controller_error(data["error"])
18
 
19
+ self.data_controller.setValue()
 
 
20
 
21
+ value_save = self.data_controller.saveValue()
22
  if isinstance(value_save, dict):
23
  return controller_error(value_save["error"])
24
 
tecnicas/controllers/views_controller/session_management/details_escala_controller.py CHANGED
@@ -14,8 +14,8 @@ Encabezados de como deben de aparecer los datos juntos
14
  from django.http import HttpRequest
15
  from django.shortcuts import render, redirect
16
  from django.urls import reverse
17
- from tecnicas.models import SesionSensorial, Presentador, Tecnica
18
- from tecnicas.controllers import DatoController, CalificacionController, PalabrasController
19
  from .details_controller import DetallesController
20
  from tecnicas.utils import defaultdict_to_dict, controller_error
21
  from collections import defaultdict
@@ -34,7 +34,7 @@ class DetallesEscalasController(DetallesController):
34
  context["error"] = error
35
  if message != "" or message:
36
  context["message"] = message
37
-
38
  return render(
39
  request, self.url_template, context)
40
 
@@ -43,16 +43,19 @@ class DetallesEscalasController(DetallesController):
43
  "use_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
44
  }
45
  self.context["sesion"] = self.session
 
 
46
  self.words = PalabrasController.getWordsInTechnique(
47
  self.session.tecnica)
48
  self.context["palabras"] = [word.nombre_palabra for word in self.words]
49
 
 
50
  ratings_for_repetition = []
51
 
52
- ratings = CalificacionController.getRatingsByTechnique(
53
- technique=self.session.tecnica)
54
 
55
- if isinstance(ratings, dict) or not ratings:
56
  self.context["calificaciones"] = ratings_for_repetition
57
  self.context["existen_calificaciones"] = False
58
  return self.context
@@ -64,7 +67,7 @@ class DetallesEscalasController(DetallesController):
64
  lambda: defaultdict(lambda: defaultdict(list)))
65
 
66
  for item in data:
67
- user = item["usuarioCatador"]
68
  rep = item["repeticion"]
69
  prod = item["producto_code"]
70
 
@@ -77,18 +80,30 @@ class DetallesEscalasController(DetallesController):
77
  ratings_for_repetition)
78
  self.context["existen_calificaciones"] = True
79
 
 
 
 
80
  return self.context
81
 
82
- def startRepetition(self, presenter: Presentador):
83
  creator = presenter
84
  technique = self.session.tecnica
85
 
86
  if creator.user.username != self.session.creadoPor.user.username:
87
- return self.getResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición")
88
  elif self.session.activo:
89
- return self.getResponse(error="La sesión ya está activada")
90
- elif technique.repeticion == technique.repeticiones_max:
91
- return self.getResponse(error="Se ha alcanzado el número de repeticiones máxima")
 
 
 
 
 
 
 
 
 
92
 
93
  self.session.activo = True
94
  technique.repeticion = technique.repeticion + 1
 
14
  from django.http import HttpRequest
15
  from django.shortcuts import render, redirect
16
  from django.urls import reverse
17
+ from tecnicas.models import SesionSensorial, Presentador, Participacion, Calificacion
18
+ from tecnicas.controllers import DatoController, PalabrasController, ParticipacionController
19
  from .details_controller import DetallesController
20
  from tecnicas.utils import defaultdict_to_dict, controller_error
21
  from collections import defaultdict
 
34
  context["error"] = error
35
  if message != "" or message:
36
  context["message"] = message
37
+
38
  return render(
39
  request, self.url_template, context)
40
 
 
43
  "use_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
44
  }
45
  self.context["sesion"] = self.session
46
+
47
+ # Recuperar la palabras de la tecnica
48
  self.words = PalabrasController.getWordsInTechnique(
49
  self.session.tecnica)
50
  self.context["palabras"] = [word.nombre_palabra for word in self.words]
51
 
52
+ # Se recuperan las calificaciones
53
  ratings_for_repetition = []
54
 
55
+ ratings = list(Calificacion.objects.filter(
56
+ id_tecnica=self.session.tecnica))
57
 
58
+ if not ratings:
59
  self.context["calificaciones"] = ratings_for_repetition
60
  self.context["existen_calificaciones"] = False
61
  return self.context
 
67
  lambda: defaultdict(lambda: defaultdict(list)))
68
 
69
  for item in data:
70
+ user = item["usuario_catador"]
71
  rep = item["repeticion"]
72
  prod = item["producto_code"]
73
 
 
80
  ratings_for_repetition)
81
  self.context["existen_calificaciones"] = True
82
 
83
+ # Se comprueba que ya no se pueda iniciar la repeticion
84
+ self.context["fin_repeticiones"] = self.session.tecnica.repeticion >= self.session.tecnica.repeticiones_max
85
+
86
  return self.context
87
 
88
+ def startRepetition(self, presenter: Presentador, request: HttpRequest):
89
  creator = presenter
90
  technique = self.session.tecnica
91
 
92
  if creator.user.username != self.session.creadoPor.user.username:
93
+ return self.getResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición", request=request)
94
  elif self.session.activo:
95
+ return self.getResponse(error="La sesión ya está activada", request=request)
96
+ elif technique.repeticion >= technique.repeticiones_max:
97
+ return self.getResponse(error="Se ha alcanzado el número de repeticiones máxima", request=request)
98
+
99
+ there_participacions = Participacion.objects.filter(
100
+ tecnica=technique).exists()
101
+
102
+ if there_participacions:
103
+ (is_update_participations,
104
+ message) = ParticipacionController.outAllInSession(self.session)
105
+ if not is_update_participations:
106
+ return self.getResponse(error=message, request=request)
107
 
108
  self.session.activo = True
109
  technique.repeticion = technique.repeticion + 1
tecnicas/controllers/views_controller/session_management/monitor_controller.py CHANGED
@@ -4,7 +4,3 @@ from tecnicas.models import SesionSensorial
4
  class MonitorController():
5
  def __init__(self, session: SesionSensorial):
6
  self.sensorial_session = session
7
-
8
- def updataSession(self):
9
- self.sensorial_session = SesionSensorial.objects.get(
10
- codigo_sesion=self.sensorial_session.codigo_sesion)
 
4
  class MonitorController():
5
  def __init__(self, session: SesionSensorial):
6
  self.sensorial_session = session
 
 
 
 
tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py CHANGED
@@ -89,5 +89,5 @@ class MonitorEscalasController(MonitorController):
89
  response = SesionController.finishRepetion(self.sensorial_session)
90
  if isinstance(response, dict):
91
  return controller_error(response["error"])
92
- self.updataSession()
93
  return self.sensorial_session
 
89
  response = SesionController.finishRepetion(self.sensorial_session)
90
  if isinstance(response, dict):
91
  return controller_error(response["error"])
92
+ self.sensorial_session.refresh_from_db()
93
  return self.sensorial_session
tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import HttpRequest
2
+ from django.shortcuts import redirect, render
3
+ from django.urls import reverse
4
+ from tecnicas.models import SesionSensorial, Catador, Participacion, Producto, Calificacion, Palabra
5
+ from tecnicas.controllers import PosicionController, CalificacionController, ParticipacionController, PalabrasController, EscalaController, DatoController
6
+
7
+
8
+ class ConvencionalScalesController:
9
+ context = {}
10
+ current_directory = "tecnicas/forms_tester/convencional.html"
11
+ previus_directory = "cata_system:catador_init_session"
12
+
13
+ def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
14
+ self.tester = user_tester
15
+ self.session = sensorial_session
16
+
17
+ def controllGetEscalas(self, request: HttpRequest):
18
+ technique = self.session.tecnica
19
+ self.participation = Participacion.objects.get(
20
+ tecnica=technique, catador=request.user.user_catador)
21
+
22
+ ctx = self.context
23
+ ctx["session"] = self.session
24
+
25
+ # Obtener posiciones y palabras de la técnica
26
+ positions_in_order = PosicionController.getPostionsInOrder(
27
+ id_order=request.session["id_order"])
28
+ aligned_positions_in_order = sorted(
29
+ positions_in_order, key=lambda p: p.posicion)
30
+ words = PalabrasController.getWordsInTechnique(technique=technique)
31
+
32
+ # Comprobar siguiente posición sin calificar
33
+ (next_position, end_products) = CalificacionController.checkPositionWithoutRating(
34
+ positions=aligned_positions_in_order,
35
+ user_cata=request.user.user_catador,
36
+ repetition=technique.repeticion,
37
+ technique=technique,
38
+ num_words=len(words)
39
+ )
40
+
41
+ # Si no hay productos se finaliza la sesion
42
+ if end_products:
43
+ ParticipacionController.finishSession(self.participation)
44
+ params = {"code_sesion": self.session.codigo_sesion}
45
+ return redirect(reverse('cata_system:catador_init_session', kwargs=params))
46
+
47
+ # Si devuelve una lista, tomar el primer elemento
48
+ if isinstance(next_position, list):
49
+ next_position = next_position[0]
50
+
51
+ # Producto a calificar ahora
52
+ product = next_position.id_producto
53
+ ctx["product"] = product
54
+
55
+ # Revisar las palabras para calificar
56
+ try:
57
+ rating = Calificacion.objects.get(
58
+ num_repeticion=technique.repeticion,
59
+ id_producto=product,
60
+ id_tecnica=technique,
61
+ id_catador=self.tester
62
+ )
63
+ there_rating = True
64
+ except Calificacion.DoesNotExist:
65
+ there_rating = False
66
+
67
+ # Si no hay calificaciones previas, usar todas las palabras
68
+ if not there_rating:
69
+ ctx["words"] = words
70
+ else:
71
+ ratings_product = rating.dato_calificacion.all()
72
+ # Filtrar palabras que faltan
73
+ words_to_use = PalabrasController.getWordsWithoutData(
74
+ recoreded_data=ratings_product,
75
+ words=words
76
+ )
77
+ ctx["words"] = words_to_use
78
+
79
+ # Escala y etiquetas relacionadas
80
+ scale = EscalaController.getScaleByTechnique(technique=technique)
81
+ ctx["scale"] = scale
82
+ ctx["type_scale"] = scale.id_tipo_escala.nombre_escala
83
+ ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale)
84
+
85
+ return render(request, self.current_directory, ctx)
86
+
87
+ def controllGetRATA(self, request: HttpRequest):
88
+ technique = self.session.tecnica
89
+ self.participation = Participacion.objects.get(
90
+ tecnica=technique, catador=request.user.user_catador)
91
+
92
+ self.context["session"] = self.session
93
+
94
+ products_in_technique = Producto.objects.filter(id_tecnica=technique)
95
+
96
+ words = PalabrasController.getWordsInTechnique(technique=technique)
97
+
98
+ use_product: Producto = None
99
+ use_words: list[Palabra] = None
100
+
101
+ # Revisamos el producto que le falten calificaciones
102
+ for current_product in products_in_technique:
103
+ try:
104
+ rating = Calificacion.objects.get(
105
+ num_repeticion=technique.repeticion,
106
+ id_producto=current_product,
107
+ id_tecnica=technique,
108
+ id_catador=self.tester
109
+ )
110
+ there_rating = True
111
+ except Calificacion.DoesNotExist:
112
+ there_rating = False
113
+
114
+ # Si no hay calificacion mandamos el producto actual y todas la palabras
115
+ if not there_rating:
116
+ use_product = current_product
117
+ use_words = words
118
+ break
119
+
120
+ # Obtener los datos asociados para la calificacion para ver que palabras quedan por calificar
121
+ recoreded_data = rating.dato_calificacion.all()
122
+
123
+ if not recoreded_data:
124
+ # Si no hay datos entonces devolver el producto con todas las palabras
125
+ use_product = current_product
126
+ use_words = words
127
+ break
128
+ else:
129
+ words_to_use = PalabrasController.getWordsWithoutData(
130
+ recoreded_data=recoreded_data, words=words)
131
+
132
+ # Si quedan palabras por calificar mandar las palabras con el producto
133
+ if not isinstance(words_to_use, dict) and words_to_use:
134
+ use_product = current_product
135
+ use_words = words_to_use
136
+ break
137
+
138
+ # Si no hay producto que falta por calificar finalizar sesion para el Catador
139
+ if not use_product:
140
+ updated_participation = ParticipacionController.finishSession(
141
+ self.participation)
142
+ params = {
143
+ "code_sesion": self.session.codigo_sesion
144
+ }
145
+ return redirect(reverse(self.previus_directory, kwargs=params))
146
+
147
+ self.context["product"] = use_product
148
+ self.context["words"] = use_words
149
+
150
+ # Agregar informacion de la escala
151
+ scale = EscalaController.getScaleByTechnique(technique=technique)
152
+ self.context["scale"] = scale
153
+ self.context["type_scale"] = scale.id_tipo_escala.nombre_escala
154
+
155
+ use_tags = EscalaController.getRelatedTagsInScale(scale=scale)
156
+ self.context["tags"] = use_tags
157
+
158
+ return render(request, self.current_directory, self.context)
tecnicas/controllers/views_controller/sessions_tester/init_session_tester_controller.py CHANGED
@@ -2,7 +2,7 @@ from django.db import transaction
2
  from django.http import HttpRequest
3
  from django.shortcuts import render, redirect
4
  from django.urls import reverse
5
- from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, Calificacion, EsVocabulario
6
  from tecnicas.controllers import ParticipacionController
7
  from tecnicas.utils import controller_error, shuffleArray
8
 
@@ -37,6 +37,9 @@ class InitSessionTesterController():
37
  if is_end:
38
  context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
39
 
 
 
 
40
  return render(request, self.current_direction, context)
41
 
42
  def controllPostEscalas(self, request: HttpRequest):
@@ -54,7 +57,7 @@ class InitSessionTesterController():
54
  if is_end:
55
  context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
56
  return render(request, self.current_direction, context)
57
-
58
  update_participation = ParticipacionController.enterSession(
59
  tester=request.user.user_catador, session=self.session)
60
  if isinstance(update_participation, dict):
@@ -122,8 +125,8 @@ class InitSessionTesterController():
122
 
123
  # ////////////////////////////////////////////////////////////// #
124
  #
125
- # numero_calificaciones_esperadas = num_productos * num_palabras
126
- # Si numero_calificaciones_esperadas ss igual a numero_calificaciones_actuales en la repetcion R
127
  # Ha terminado la repeticion
128
  #
129
  # ////////////////////////////////////////////////////////////// #
@@ -137,17 +140,20 @@ class InitSessionTesterController():
137
 
138
  num_words: int
139
 
140
- if style_words.nombre_estilo == "atributos":
141
  num_words = EsAtributo.objects.get(
142
  id_tecnica=self.session.tecnica).palabras.count()
143
- elif style_words.nombre_estilo == "vocabulario":
144
  num_words = EsVocabulario.objects.get(
145
  id_tecnica=self.session.tecnica).id_vocabulario.palabras.count()
146
 
147
  expected_ratings_repetition = num_products * num_words
148
 
149
- num_ratings_now = Calificacion.objects.filter(
150
- id_tecnica=technique, id_catador=self.tester, num_repeticion=technique.repeticion).count()
 
 
 
151
 
152
  is_end = num_ratings_now >= expected_ratings_repetition
153
 
 
2
  from django.http import HttpRequest
3
  from django.shortcuts import render, redirect
4
  from django.urls import reverse
5
+ from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, Calificacion, EsVocabulario, Dato
6
  from tecnicas.controllers import ParticipacionController
7
  from tecnicas.utils import controller_error, shuffleArray
8
 
 
37
  if is_end:
38
  context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
39
 
40
+ if "error" in request.GET:
41
+ context["error"] = request.GET["error"]
42
+
43
  return render(request, self.current_direction, context)
44
 
45
  def controllPostEscalas(self, request: HttpRequest):
 
57
  if is_end:
58
  context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
59
  return render(request, self.current_direction, context)
60
+
61
  update_participation = ParticipacionController.enterSession(
62
  tester=request.user.user_catador, session=self.session)
63
  if isinstance(update_participation, dict):
 
125
 
126
  # ////////////////////////////////////////////////////////////// #
127
  #
128
+ # numero_datos_esperadas = num_productos * num_palabras
129
+ # Si numero_datos_esperadas es igual a numero_datos_actuales en la repetcion R
130
  # Ha terminado la repeticion
131
  #
132
  # ////////////////////////////////////////////////////////////// #
 
140
 
141
  num_words: int
142
 
143
+ if style_words == "atributos":
144
  num_words = EsAtributo.objects.get(
145
  id_tecnica=self.session.tecnica).palabras.count()
146
+ elif style_words == "vocabulario":
147
  num_words = EsVocabulario.objects.get(
148
  id_tecnica=self.session.tecnica).id_vocabulario.palabras.count()
149
 
150
  expected_ratings_repetition = num_products * num_words
151
 
152
+ num_ratings_now = Dato.objects.filter(
153
+ id_calificacion__id_catador=self.tester,
154
+ id_calificacion__id_tecnica=technique,
155
+ id_calificacion__num_repeticion=technique.repeticion
156
+ ).count()
157
 
158
  is_end = num_ratings_now >= expected_ratings_repetition
159
 
tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py CHANGED
@@ -33,38 +33,38 @@ class LoginSessionTesterController():
33
  context["error"] = "La sesión no está activa actualmente"
34
  return render(request, self.current_direcction, context)
35
 
36
- if self.session.tecnica.repeticion > 1:
37
  try:
38
  self.taster_participation = Participacion.objects.get(
39
  tecnica=self.session.tecnica, catador=self.tester)
40
  context["error"] = "Usted ya esta dentro de la sesión"
41
  return render(request, self.current_direcction, context)
42
  except Participacion.DoesNotExist:
43
- context["error"] = "No tienes permitido entrar a esta sesión"
44
- return render(request, self.current_direcction, context)
45
- else:
46
- with transaction.atomic():
47
- code_session = self.session.codigo_sesion
48
- self.session = SesionSensorial.objects.select_for_update().get(
49
- codigo_sesion=code_session)
50
 
51
- max_testers = self.session.tecnica.limite_catadores
52
- current_num_testers = Participacion.objects.filter(
53
- tecnica=self.session.tecnica).count()
54
 
55
- if current_num_testers >= max_testers:
56
- context["error"] = "La sesión ha alcanzado el número máximo de catadores"
57
- return render(request, self.current_direcction, context)
58
 
59
- self.taster_participation = Participacion.objects.create(
60
- tecnica=self.session.tecnica,
61
- catador=self.tester,
62
- finalizado=False
63
- )
64
- params = {
65
- "code_sesion": self.session.codigo_sesion
66
- }
67
- return redirect(reverse(self.destinity_direcction, kwargs=params))
 
 
 
68
 
69
  def validateEntryRATA(self, request: HttpRequest):
70
  context = {}
 
33
  context["error"] = "La sesión no está activa actualmente"
34
  return render(request, self.current_direcction, context)
35
 
36
+ if self.session.tecnica.repeticion == 1:
37
  try:
38
  self.taster_participation = Participacion.objects.get(
39
  tecnica=self.session.tecnica, catador=self.tester)
40
  context["error"] = "Usted ya esta dentro de la sesión"
41
  return render(request, self.current_direcction, context)
42
  except Participacion.DoesNotExist:
43
+ with transaction.atomic():
44
+ code_session = self.session.codigo_sesion
45
+ self.session = SesionSensorial.objects.select_for_update().get(
46
+ codigo_sesion=code_session)
 
 
 
47
 
48
+ max_testers = self.session.tecnica.limite_catadores
49
+ current_num_testers = Participacion.objects.filter(
50
+ tecnica=self.session.tecnica).count()
51
 
52
+ if current_num_testers >= max_testers:
53
+ context["error"] = "La sesión ha alcanzado el número máximo de catadores"
54
+ return render(request, self.current_direcction, context)
55
 
56
+ self.taster_participation = Participacion.objects.create(
57
+ tecnica=self.session.tecnica,
58
+ catador=self.tester,
59
+ finalizado=False
60
+ )
61
+ params = {
62
+ "code_sesion": self.session.codigo_sesion
63
+ }
64
+ return redirect(reverse(self.destinity_direcction, kwargs=params))
65
+ else:
66
+ context["error"] = "Ya no es posible ingresar a la sesión"
67
+ return render(request, self.current_direcction, context)
68
 
69
  def validateEntryRATA(self, request: HttpRequest):
70
  context = {}
tecnicas/models/calificacion.py CHANGED
@@ -8,4 +8,7 @@ class Calificacion(models.Model):
8
  num_repeticion = models.IntegerField()
9
  id_producto = models.ForeignKey(Producto, on_delete=models.CASCADE, related_name="calificacion_producto")
10
  id_tecnica = models.ForeignKey(Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica")
11
- id_catador = models.ForeignKey(Catador, on_delete=models.CASCADE, related_name="calificacion_catador")
 
 
 
 
8
  num_repeticion = models.IntegerField()
9
  id_producto = models.ForeignKey(Producto, on_delete=models.CASCADE, related_name="calificacion_producto")
10
  id_tecnica = models.ForeignKey(Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica")
11
+ id_catador = models.ForeignKey(Catador, on_delete=models.CASCADE, related_name="calificacion_catador")
12
+
13
+ def __str__(self):
14
+ return f"{self.id} - {self.id_tecnica.sesion_tecnica} - {self.num_repeticion} - {self.id_catador.user.username}"
tecnicas/models/dato.py CHANGED
@@ -11,4 +11,4 @@ class Dato(models.Model):
11
  Calificacion, on_delete=models.CASCADE, related_name="dato_calificacion")
12
 
13
  def __str__(self):
14
- return f"{self.id_palabra.nombre_palabra} - {self.id_calificacion.id_producto.codigoProducto} - {self.id_calificacion.id_catador.usuarioCatador}"
 
11
  Calificacion, on_delete=models.CASCADE, related_name="dato_calificacion")
12
 
13
  def __str__(self):
14
+ return f"{self.id_calificacion.id_tecnica.sesion_tecnica}- {self.id_calificacion.num_repeticion} - {self.id_palabra.nombre_palabra} - {self.id_calificacion.id_producto.codigoProducto} - {self.id_calificacion.id_catador.user.username}"
tecnicas/models/dato_valor.py CHANGED
@@ -9,7 +9,7 @@ class ValorDecimal(models.Model):
9
  valor = models.FloatField()
10
 
11
  def __str__(self):
12
- return f"{self.id} - {self.id_dato.id_palabra}: {self.valor} - {self.id_dato.id_calificacion.id_catador.usuarioCatador}"
13
 
14
 
15
  class ValorBooleano(models.Model):
@@ -18,4 +18,4 @@ class ValorBooleano(models.Model):
18
  valor = models.BooleanField()
19
 
20
  def __str__(self):
21
- return f"{self.id} - {self.id_dato.id_palabra}: {self.valor} - {self.id_dato.id_calificacion.id_catador.usuarioCatador}"
 
9
  valor = models.FloatField()
10
 
11
  def __str__(self):
12
+ return f"{self.id} - {self.id_dato.id_palabra}: {self.valor} - {self.id_dato.id_calificacion.id_catador.user.username}"
13
 
14
 
15
  class ValorBooleano(models.Model):
 
18
  valor = models.BooleanField()
19
 
20
  def __str__(self):
21
+ return f"{self.id} - {self.id_dato.id_palabra}: {self.valor} - {self.id_dato.id_calificacion.id_catador.user.username}"
tecnicas/static/js/created-scale.js CHANGED
@@ -54,11 +54,13 @@ async function sendRating(word) {
54
  .querySelector(".id-product").textContent;
55
 
56
  const idWord = formRatingWord.querySelector(".id-word").textContent;
 
57
 
58
  dataForm.set("code-product", codeProduct);
59
  dataForm.set("id-product", idProduct);
60
  dataForm.set("name-word", word);
61
  dataForm.set("id-word", idWord);
 
62
 
63
  try {
64
  const respone = await fetch(url, {
 
54
  .querySelector(".id-product").textContent;
55
 
56
  const idWord = formRatingWord.querySelector(".id-word").textContent;
57
+ const idTechnique = document.querySelector(".ct-input-id-tech").value
58
 
59
  dataForm.set("code-product", codeProduct);
60
  dataForm.set("id-product", idProduct);
61
  dataForm.set("name-word", word);
62
  dataForm.set("id-word", idWord);
63
+ dataForm.set("id-technique", idTechnique);
64
 
65
  try {
66
  const respone = await fetch(url, {
tecnicas/templates/tecnicas/components/form-scale-continue.html ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <form action="" method="post" class="form-rating-{{word}} w-full">
2
+ {% csrf_token %}
3
+ <article class="overflow-x-scroll w-full">
4
+ <article class="bg-surface-card p-6 rounded-lg mb-3 w-fit">
5
+ <label for="id-range-word-{{word}}"
6
+ class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
7
+
8
+ <span class="hidden id-word">{{ word.id }}</span>
9
+
10
+ <section class="block">
11
+ <div class="relative mx-6">
12
+ <input type="range" type="range" min="0" max="1000" value="500" name="rating-word"
13
+ class="range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0]"
14
+ style="width: {{scale.longitud}}cm;">
15
+
16
+ <div class="absolute top-0 left-0 w-0.5 transform -translate-x-1/2 h-full bg-red-500 z-10">
17
+ </div>
18
+
19
+ <div class="absolute top-1/5 left-1/2 w-0.5 transform -translate-x-1/2 h-4/5 bg-red-500 z-10">
20
+ </div>
21
+
22
+ <div class="absolute top-0 right-0 w-0.5 transform -translate-x-1/2 h-full bg-red-500 z-10">
23
+ </div>
24
+ </div>
25
+
26
+ <div class="flex justify-between mt-4">
27
+ <div class="flex flex-col items-start text-left w-[100px]">
28
+ <span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
29
+ {{ tags.0.id_etiqueta }}
30
+ </span>
31
+ </div>
32
+ <div class="flex flex-col items-start text-center w-[100px]">
33
+ <span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
34
+ {{ tags.1.id_etiqueta }}
35
+ </span>
36
+ </div>
37
+ <div class="flex flex-col items-start text-right w-[100px]">
38
+ <span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
39
+ {{ tags.2.id_etiqueta }}
40
+ </span>
41
+ </div>
42
+ </div>
43
+ </section>
44
+ </article>
45
+ </article>
46
+ <article class="flex flex-col justify-center gap-2 items-end actions-{{word}}">
47
+ <section class="flex justify-end items-center gap-2 btns-container">
48
+ <button type="button" onclick="checkSendRating('{{word}}')"
49
+ class="ct-btn-check-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-secondary btn-push">
50
+ ¿Guardar calificación?
51
+ </button>
52
+ <button type="button" onclick="sendRating('{{word}}')"
53
+ class="ct-btn-submit-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-primary btn-push hidden">
54
+ Estoy seguro
55
+ </button>
56
+ <button type="button" onclick="cancelSendRating('{{word}}')"
57
+ class="ct-btn-cancel-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-error btn-push hidden">
58
+ Cancelar
59
+ </button>
60
+ </section>
61
+ </article>
62
+ </form>
tecnicas/templates/tecnicas/components/form-scale-structure.html ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <form action="" method="post" class="form-rating-{{ word }} w-full">
2
+ {% csrf_token %}
3
+ <article class="overflow-x-scroll w-full">
4
+ <article class="bg-surface-card p-6 rounded-lg mb-3 w-fit">
5
+ <label for="id-scale-word-{{ word }}"
6
+ class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
7
+ <span class="hidden id-word">{{ word.id }}</span>
8
+
9
+ <section>
10
+ <div class="w-full min-w-xs">
11
+ <div class="relative mx-6">
12
+ <input type="range" min="1" max="{{ scale.longitud }}" step="1" name="rating-word"
13
+ class="w-full range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0] z-5">
14
+ <div class="absolute top-0 left-0 w-full h-full z-10 flex justify-between pointer-events-none">
15
+ {% for tag in tags %}
16
+ <div class="w-0.5 transform -translate-x-1/2 h-full bg-red-500">
17
+ </div>
18
+ {% endfor %}
19
+ </div>
20
+ </div>
21
+
22
+ <div class="flex justify-between mt-4 text-xs gap-1">
23
+ {% for tag in tags %}
24
+ <div class="flex items-center justify-center text-center w-[70px]">
25
+ <span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
26
+ {{ tag.id_etiqueta }}
27
+ </span>
28
+ </div>
29
+ {% endfor %}
30
+ </div>
31
+ </div>
32
+ </section>
33
+ </article>
34
+ </article>
35
+
36
+ <article class="flex flex-col justify-center gap-2 items-end actions-{{ word }}">
37
+ <section class="flex justify-end items-center gap-2 btns-container">
38
+ <button type="button" onclick="checkSendRating('{{ word }}')"
39
+ class="ct-btn-check-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-secondary btn-push">
40
+ ¿Guardar calificación?
41
+ </button>
42
+ <button type="button" onclick="sendRating('{{ word }}')"
43
+ class="ct-btn-submit-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-primary btn-push hidden">
44
+ Estoy seguro
45
+ </button>
46
+ <button type="button" onclick="cancelSendRating('{{ word }}')"
47
+ class="ct-btn-cancel-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-error btn-push hidden">
48
+ Cancelar
49
+ </button>
50
+ </section>
51
+ </article>
52
+ </form>
tecnicas/templates/tecnicas/forms_tester/convencional.html CHANGED
@@ -37,7 +37,8 @@
37
  <article class="cts-wrap-content text-black">
38
  <header class="text-center flex-row w-full items-stretch flex justify-around flex-wrap gap-2">
39
  <h1 class="rounded font-bold text-2xl bg-surface-ligt p-4 flex-1">
40
- Sesión usando <br>técnica Convencional
 
41
  </h1>
42
  <button class="cts-btn-general cts-btn-error btn-push" onclick="exit_sesion('form-actions')">
43
  Salir de la sesión
@@ -45,12 +46,17 @@
45
  </header>
46
 
47
  <article class="hidden">
48
- <form action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post" class="form-actions">
 
49
  {% csrf_token %}
50
  <input type="hidden" name="action" class="action-input">
51
  </form>
52
  </article>
53
 
 
 
 
 
54
  {% if error %}
55
  <hr>
56
  <article class="bg-red-600 p-4 text-white rounded-xl ct-notification-error">
@@ -95,132 +101,17 @@
95
 
96
  <article
97
  class="scales-container [&>*:not(:last-child)]:mb-5 min-lg:grid min-lg:items-start grid-cols-2 gap-3 justify-center items-center">
98
- {% if type_scale == "continua" %}
99
- {% for word in words %}
100
- <form action="" method="post" class="form-rating-{{word}} w-full">
101
- {% csrf_token %}
102
- <article class="overflow-x-scroll w-full">
103
- <article class="bg-surface-card p-6 rounded-lg mb-3 w-fit">
104
- <label for="id-range-word-{{word}}"
105
- class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
106
-
107
- <span class="hidden id-word">{{ word.id }}</span>
108
-
109
- <section class="block">
110
- <div class="relative mx-6">
111
- <input type="range" type="range" min="0" max="1000" value="500" name="rating-word"
112
- class="range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0]"
113
- style="width: {{scale.longitud}}cm;">
114
-
115
- <div
116
- class="absolute top-0 left-0 w-0.5 transform -translate-x-1/2 h-full bg-red-500 z-10">
117
- </div>
118
-
119
- <div
120
- class="absolute top-1/5 left-1/2 w-0.5 transform -translate-x-1/2 h-4/5 bg-red-500 z-10">
121
- </div>
122
-
123
- <div
124
- class="absolute top-0 right-0 w-0.5 transform -translate-x-1/2 h-full bg-red-500 z-10">
125
- </div>
126
- </div>
127
-
128
- <div class="flex justify-between mt-4">
129
- <div class="flex flex-col items-start text-left w-[100px]">
130
- <span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
131
- {{ tags.0.id_etiqueta }}
132
- </span>
133
- </div>
134
- <div class="flex flex-col items-start text-center w-[100px]">
135
- <span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
136
- {{ tags.1.id_etiqueta }}
137
- </span>
138
- </div>
139
- <div class="flex flex-col items-start text-right w-[100px]">
140
- <span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
141
- {{ tags.2.id_etiqueta }}
142
- </span>
143
- </div>
144
- </div>
145
- </section>
146
- </article>
147
- </article>
148
- <article class="flex flex-col justify-center gap-2 items-end actions-{{word}}">
149
- <section class="flex justify-end items-center gap-2 btns-container">
150
- <button type="button" onclick="checkSendRating('{{word}}')"
151
- class="ct-btn-check-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-secondary btn-push">
152
- ¿Guardar calificación?
153
- </button>
154
- <button type="button" onclick="sendRating('{{word}}')"
155
- class="ct-btn-submit-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-primary btn-push hidden">
156
- Estoy seguro
157
- </button>
158
- <button type="button" onclick="cancelSendRating('{{word}}')"
159
- class="ct-btn-cancel-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-error btn-push hidden">
160
- Cancelar
161
- </button>
162
- </section>
163
- </article>
164
- </form>
165
- {% endfor %}
166
- {% elif type_scale == "estructurada" %}
167
- {% for word in words %}
168
- <form action="" method="post" class="form-rating-{{ word }} w-full">
169
- {% csrf_token %}
170
- <article class="overflow-x-scroll w-full">
171
- <article class="bg-surface-card p-6 rounded-lg mb-3 w-fit">
172
- <label for="id-scale-word-{{ word }}"
173
- class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
174
- <span class="hidden id-word">{{ word.id }}</span>
175
-
176
- <section>
177
- <div class="w-full min-w-xs">
178
- <div class="relative mx-6">
179
- <input type="range" min="1" max="{{scale.longitud}}" step="1" name="rating-word"
180
- class="w-full range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0] z-5">
181
- <div
182
- class="absolute top-0 left-0 w-full h-full z-10 flex justify-between pointer-events-none">
183
- {% for tag in tags %}
184
- <div class="w-0.5 transform -translate-x-1/2 h-full bg-red-500">
185
- </div>
186
- {% endfor %}
187
- </div>
188
- </div>
189
-
190
- <div class="flex justify-between mt-4 text-xs gap-1">
191
- {% for tag in tags %}
192
- <div class="flex items-center justify-center text-center w-[70px]">
193
- <span
194
- class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
195
- {{ tag.id_etiqueta }}
196
- </span>
197
- </div>
198
- {% endfor %}
199
- </div>
200
- </div>
201
- </section>
202
- </article>
203
- </article>
204
-
205
- <article class="flex flex-col justify-center gap-2 items-end actions-{{ word }}">
206
- <section class="flex justify-end items-center gap-2 btns-container">
207
- <button type="button" onclick="checkSendRating('{{ word }}')"
208
- class="ct-btn-check-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-secondary btn-push">
209
- ¿Guardar calificación?
210
- </button>
211
- <button type="button" onclick="sendRating('{{ word }}')"
212
- class="ct-btn-submit-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-primary btn-push hidden">
213
- Estoy seguro
214
- </button>
215
- <button type="button" onclick="cancelSendRating('{{ word }}')"
216
- class="ct-btn-cancel-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-error btn-push hidden">
217
- Cancelar
218
- </button>
219
- </section>
220
- </article>
221
- </form>
222
- {% endfor %}
223
- {% endif %}
224
  </article>
225
  </article>
226
  </article>
 
37
  <article class="cts-wrap-content text-black">
38
  <header class="text-center flex-row w-full items-stretch flex justify-around flex-wrap gap-2">
39
  <h1 class="rounded font-bold text-2xl bg-surface-ligt p-4 flex-1">
40
+ Sesión usando <br>técnica
41
+ <span class="uppercase">{{ session.tecnica.tipo_tecnica }}</span>
42
  </h1>
43
  <button class="cts-btn-general cts-btn-error btn-push" onclick="exit_sesion('form-actions')">
44
  Salir de la sesión
 
46
  </header>
47
 
48
  <article class="hidden">
49
+ <form action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post"
50
+ class="form-actions">
51
  {% csrf_token %}
52
  <input type="hidden" name="action" class="action-input">
53
  </form>
54
  </article>
55
 
56
+ <section class="hidden">
57
+ <input type="hidden" value="{{ session.tecnica.id }}" name="id-tecnica" class="ct-input-id-tech">
58
+ </section>
59
+
60
  {% if error %}
61
  <hr>
62
  <article class="bg-red-600 p-4 text-white rounded-xl ct-notification-error">
 
101
 
102
  <article
103
  class="scales-container [&>*:not(:last-child)]:mb-5 min-lg:grid min-lg:items-start grid-cols-2 gap-3 justify-center items-center">
104
+ {% with path_con="../components/form-scale-continue.html" path_str="../components/form-scale-structure.html" %}
105
+ {% if type_scale == "continua" %}
106
+ {% for word in words %}
107
+ {% include path_con with word=word tags=tags scale=scale id_tecnica=session.tecnica.id %}
108
+ {% endfor %}
109
+ {% elif type_scale == "estructurada" %}
110
+ {% for word in words %}
111
+ {% include path_str with word=word tags=tags scale=scale %}
112
+ {% endfor %}
113
+ {% endif %}
114
+ {% endwith %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  </article>
116
  </article>
117
  </article>
tecnicas/templates/tecnicas/forms_tester/init_session.html CHANGED
@@ -5,7 +5,7 @@
5
 
6
  {% block content %}
7
  <article class="cts-container-main">
8
- <article class="cts-wrap-content text-black">
9
  <header class="text-center flex-row w-full flex justify-around items-center flex-wrap gap-10">
10
  <h1 class="rounded-xl font-bold text-2xl bg-surface-ligt p-4 flex-1">
11
  Panel principal de Catadores
 
5
 
6
  {% block content %}
7
  <article class="cts-container-main">
8
+ <article class="cts-wrap-content text-black max-w-4xl">
9
  <header class="text-center flex-row w-full flex justify-around items-center flex-wrap gap-10">
10
  <h1 class="rounded-xl font-bold text-2xl bg-surface-ligt p-4 flex-1">
11
  Panel principal de Catadores
tecnicas/templates/tecnicas/manage_sesions/detalles-sesion.html CHANGED
@@ -181,6 +181,14 @@
181
  </p>
182
  <article class="flex flex-wrap gap-10 max-sm:gap-2">
183
  {% if not sesion.activo %}
 
 
 
 
 
 
 
 
184
  <button
185
  class="ct-btn-start-repition flex-1 uppercase text-lg max-sm:text-base tracking-wider p-4 border-b-2 active:border-b-0 active:border-t-2 active:border-green-500 border-green-800 transition-all rounded-xl bg-green-600 text-white font-bold disabled:bg-amber-600 flex flex-col justify-center items-center gap-2"
186
  onclick="startRepetition()">
@@ -189,6 +197,7 @@
189
  <img src="{% static 'img/giro.svg' %}" alt="flechas girando" class="invert">
190
  </figure>
191
  </button>
 
192
  {% else %}
193
  <a href="{% url 'cata_system:monitor_sesion' session_code=sesion.codigo_sesion %}" class="flex-1 w-fit">
194
  <button
@@ -243,9 +252,11 @@
243
 
244
  {% if existen_calificaciones %}
245
  <article class="bg-surface-card p-4 max-sm:px-2 text-black rounded">
 
246
  {% for repeticion, catadores in calificaciones.items %}
247
- {% include "../components/table-convencional.html" with repeticion=repeticion catadores=catadores palabras=palabras %}
248
  {% endfor %}
 
249
  </article>
250
  {% else %}
251
  {% include "../components/error-message.html" with message='Sin calificaciones que mostrar aún' %}
 
181
  </p>
182
  <article class="flex flex-wrap gap-10 max-sm:gap-2">
183
  {% if not sesion.activo %}
184
+ {% if fin_repeticiones %}
185
+ <div
186
+ class="text-2xl font-semibold flex-1 cts-btn-secondary p-4 flex justify-center items-center rounded-lg select-none text-center">
187
+ <p class=" text-black">
188
+ Máximo número de repeticiones alcanzado
189
+ </p>
190
+ </div>
191
+ {% else %}
192
  <button
193
  class="ct-btn-start-repition flex-1 uppercase text-lg max-sm:text-base tracking-wider p-4 border-b-2 active:border-b-0 active:border-t-2 active:border-green-500 border-green-800 transition-all rounded-xl bg-green-600 text-white font-bold disabled:bg-amber-600 flex flex-col justify-center items-center gap-2"
194
  onclick="startRepetition()">
 
197
  <img src="{% static 'img/giro.svg' %}" alt="flechas girando" class="invert">
198
  </figure>
199
  </button>
200
+ {% endif %}
201
  {% else %}
202
  <a href="{% url 'cata_system:monitor_sesion' session_code=sesion.codigo_sesion %}" class="flex-1 w-fit">
203
  <button
 
252
 
253
  {% if existen_calificaciones %}
254
  <article class="bg-surface-card p-4 max-sm:px-2 text-black rounded">
255
+ {% with url_component="../components/table-convencional.html" %}
256
  {% for repeticion, catadores in calificaciones.items %}
257
+ {% include url_component with repeticion=repeticion catadores=catadores palabras=palabras %}
258
  {% endfor %}
259
+ {% endwith %}
260
  </article>
261
  {% else %}
262
  {% include "../components/error-message.html" with message='Sin calificaciones que mostrar aún' %}
tecnicas/utils/general_controllers.py CHANGED
@@ -17,7 +17,7 @@ def getId(value: object | int) -> int | None:
17
  def noValidTechnique(params: dict, query_params: dict, name_view: str):
18
  if query_params:
19
  query_string = urlencode(query_params)
20
- url_redireccion = f"{reverse({name_view}, kwargs=params)}?{query_string}"
21
  else:
22
- url_redireccion = f"{reverse({name_view}, kwargs=params)}"
23
  return redirect(url_redireccion)
 
17
  def noValidTechnique(params: dict, query_params: dict, name_view: str):
18
  if query_params:
19
  query_string = urlencode(query_params)
20
+ url_redireccion = f"{reverse(name_view, kwargs=params)}?{query_string}"
21
  else:
22
+ url_redireccion = f"{reverse(name_view, kwargs=params)}"
23
  return redirect(url_redireccion)
tecnicas/views/apis/rating_word.py CHANGED
@@ -28,8 +28,8 @@
28
  * Calquier otro metodo que se maneje mandar un error
29
  '''
30
  from django.http import HttpRequest, JsonResponse
31
- from ...controllers import ApiRatingController, CalificacionController, DatoController
32
- from ...utils import general_error
33
  import json
34
 
35
 
@@ -41,10 +41,11 @@ def reatingWord(req: HttpRequest):
41
  received_rating = json.loads(req.POST.get("rating-word"))
42
  received_id_word = json.loads(req.POST.get("id-word"))
43
  received_id_product = json.loads(req.POST.get("id-product"))
 
44
 
45
  view_controller = ApiRatingController(
46
  rating_controller=CalificacionController(
47
- technique=req.session["id_technique"],
48
  product=received_id_product,
49
  tester=req.user.user_catador
50
  ),
@@ -55,7 +56,7 @@ def reatingWord(req: HttpRequest):
55
  )
56
  )
57
 
58
- response_data = view_controller.logicView()
59
 
60
  return JsonResponse(response_data)
61
  else:
 
28
  * Calquier otro metodo que se maneje mandar un error
29
  '''
30
  from django.http import HttpRequest, JsonResponse
31
+ from tecnicas.controllers import ApiRatingController, CalificacionController, DatoController
32
+ from tecnicas.utils import general_error
33
  import json
34
 
35
 
 
41
  received_rating = json.loads(req.POST.get("rating-word"))
42
  received_id_word = json.loads(req.POST.get("id-word"))
43
  received_id_product = json.loads(req.POST.get("id-product"))
44
+ id_technique = json.loads(req.POST.get("id-technique"))
45
 
46
  view_controller = ApiRatingController(
47
  rating_controller=CalificacionController(
48
+ technique=id_technique,
49
  product=received_id_product,
50
  tester=req.user.user_catador
51
  ),
 
56
  )
57
  )
58
 
59
+ response_data = view_controller.controllPostScales()
60
 
61
  return JsonResponse(response_data)
62
  else:
tecnicas/views/sessions_management/session_details.py CHANGED
@@ -42,7 +42,7 @@ def sessionDetails(req: HttpRequest, session_code: str):
42
 
43
  if req.POST["action"] == "start_session":
44
  response = controller_view.startRepetition(
45
- presenter=req.user.user_presentador)
46
  elif req.POST.get("action") == "delete_session":
47
  controller_view.deleteSesorialSession()
48
  response = redirect(
 
42
 
43
  if req.POST["action"] == "start_session":
44
  response = controller_view.startRepetition(
45
+ presenter=req.user.user_presentador, request=req)
46
  elif req.POST.get("action") == "delete_session":
47
  controller_view.deleteSesorialSession()
48
  response = redirect(
tecnicas/views/tester_forms/convencional_scales.py CHANGED
@@ -55,78 +55,26 @@
55
  - Cata segmento en el que se divide debe tener la etiqueda correspondiente por debajo
56
  '''
57
  from django.http import HttpRequest
58
- from django.shortcuts import redirect, render
59
- from django.urls import reverse
60
- from ...controllers import SesionController, PosicionController, CalificacionController, ParticipacionController, PalabrasController, EscalaController, DatoController
61
 
62
 
63
  def convencionalScales(req: HttpRequest, code_sesion: str):
64
- if not "id_order" in req.session:
65
- return redirect(reverse("cata_system:catador_main"))
66
-
67
  session = SesionController.getSessionByCode(code_sesion)
68
- technique = session.tecnica
69
-
70
- context = {
71
- "session": session
72
- }
73
-
74
- req.session["id_technique"] = session.tecnica.id
75
 
76
  if req.method == "GET":
77
- positions = PosicionController.getPostionsInOrder(
78
- id_order=req.session["id_order"])
79
-
80
- sorted_positions = sorted(positions, key=lambda posi: posi.posicion)
81
-
82
- words = PalabrasController.getWordsInTechnique(technique=technique)
83
-
84
- next_position = CalificacionController.checkProducsWithoutRating(
85
- positions=sorted_positions,
86
- user_cata=req.user.username,
87
- id_technique=session.tecnica.id,
88
- repetition=session.tecnica.repeticion,
89
- technique=technique,
90
- num_words=len(words)
91
- )
92
-
93
- if isinstance(next_position, dict):
94
- updated_participation = ParticipacionController.finishSession(
95
- req.session["id_participation"])
96
- return redirect(reverse("cata_system:catador_main"))
97
-
98
- if isinstance(next_position, list):
99
- next_position = next_position[0]
100
-
101
- context["product"] = next_position.id_producto
102
-
103
- ratings_product = CalificacionController.getRatings(
104
- technique=technique,
105
- product=next_position.id_producto,
106
- repetition=technique.repeticion,
107
- user_tester=req.user.username
108
- )
109
-
110
- if isinstance(ratings_product, dict):
111
- context["error"] = ratings_product["error"]
112
- return render(req, "tecnicas/forms_tester/convencional.html", context)
113
- elif not ratings_product:
114
- context["words"] = words
115
  else:
116
- recoreded_data = DatoController.getRerecordedData(
117
- ratings=ratings_product)
118
- if not recoreded_data:
119
- context["words"] = words
120
- else:
121
- words_to_use = PalabrasController.getWordsWithoutData(
122
- recoreded_data=recoreded_data, words=words)
123
- context["words"] = words_to_use
124
-
125
- scale = EscalaController.getScaleByTechnique(technique=technique)
126
- context["scale"] = scale
127
- context["type_scale"] = scale.id_tipo_escala.nombre_escala
128
-
129
- use_tags = EscalaController.getRelatedTagsInScale(scale=scale)
130
- context["tags"] = use_tags
131
 
132
- return render(req, "tecnicas/forms_tester/convencional.html", context)
 
55
  - Cata segmento en el que se divide debe tener la etiqueda correspondiente por debajo
56
  '''
57
  from django.http import HttpRequest
58
+ from tecnicas.controllers import SesionController, ConvencionalScalesController
59
+ from tecnicas.utils import noValidTechnique
 
60
 
61
 
62
  def convencionalScales(req: HttpRequest, code_sesion: str):
 
 
 
63
  session = SesionController.getSessionByCode(code_sesion)
64
+ type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
 
 
 
 
 
 
65
 
66
  if req.method == "GET":
67
+ view_controller = ConvencionalScalesController(
68
+ sensorial_session=session, user_tester=req.user.user_catador)
69
+ if type_technique == "escalas":
70
+ respose = view_controller.controllGetEscalas(request=req)
71
+ elif type_technique == "rata":
72
+ respose = view_controller.controllGetRATA(request=req)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  else:
74
+ respose = noValidTechnique(
75
+ name_view='cata_system:catador_init_session',
76
+ params={"code_sesion": session.codigo_sesion},
77
+ query_params={"error": "No es posible poder usar esta técnica"}
78
+ )
 
 
 
 
 
 
 
 
 
 
79
 
80
+ return respose
tecnicas/views/tester_forms/init_tester_form.py CHANGED
@@ -7,11 +7,6 @@ from tecnicas.models import SesionSensorial
7
 
8
  def initTesterForm(req: HttpRequest, code_sesion: str):
9
  session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
10
-
11
- context = {
12
- "session": session
13
- }
14
-
15
  type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
16
  template_url = "tecnicas/forms_tester/init_session.html"
17
 
@@ -25,6 +20,7 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
25
  response = view_controller.controllGetRATA(request=req)
26
  else:
27
  context = {
 
28
  "error": "La técnica usada en esta sesión o ha sido implementada para ingresar a ella"
29
  }
30
  response = render(
@@ -36,6 +32,7 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
36
  response = view_controller.controllPostEscalas(request=req)
37
  else:
38
  context = {
 
39
  "error": "Esta opción aun no esta disponible para la técnica usada por la sesión"
40
  }
41
  response = render(
 
7
 
8
  def initTesterForm(req: HttpRequest, code_sesion: str):
9
  session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
 
 
 
 
 
10
  type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
11
  template_url = "tecnicas/forms_tester/init_session.html"
12
 
 
20
  response = view_controller.controllGetRATA(request=req)
21
  else:
22
  context = {
23
+ "session": session,
24
  "error": "La técnica usada en esta sesión o ha sido implementada para ingresar a ella"
25
  }
26
  response = render(
 
32
  response = view_controller.controllPostEscalas(request=req)
33
  else:
34
  context = {
35
+ "session": session,
36
  "error": "Esta opción aun no esta disponible para la técnica usada por la sesión"
37
  }
38
  response = render(