Spaces:
Sleeping
Sleeping
Merge pull request #36 from CascoArcilla/HU10
Browse files- tecnicas/admin.py +2 -1
- tecnicas/controllers/__init__.py +12 -4
- tecnicas/controllers/api_controller/rating_pf_list_controller.py +143 -0
- tecnicas/controllers/models_controller/dato_controller.py +40 -14
- tecnicas/controllers/views_controller/session_management/details_pf_controller.py +155 -2
- tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py +0 -3
- tecnicas/controllers/views_controller/session_management/monitor_pf_controller.py +75 -0
- tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py +0 -3
- tecnicas/controllers/views_controller/sessions_tester/{init_session_tester_controller.py → init_session/init_session_controller.py} +5 -85
- tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py +82 -0
- tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py +144 -0
- tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py +24 -0
- tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py +4 -1
- tecnicas/controllers/views_controller/sessions_tester/{general_test_controller.py → tests_forms/general_test_controller.py} +0 -0
- tecnicas/controllers/views_controller/sessions_tester/{test_cata_controller.py → tests_forms/test_cata_controller.py} +0 -0
- tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py +188 -0
- tecnicas/controllers/views_controller/sessions_tester/{test_rata_controller.py → tests_forms/test_rata_controller.py} +0 -0
- tecnicas/controllers/views_controller/sessions_tester/{test_scales_controller.py → tests_forms/test_scales_controller.py} +6 -1
- tecnicas/forms/__init__.py +2 -0
- tecnicas/forms/list_words_form.py +49 -0
- tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py +19 -0
- tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py +30 -0
- tecnicas/models/__init__.py +3 -1
- tecnicas/models/lista_palabras.py +15 -0
- tecnicas/static/js/download-table-zip.js +74 -0
- tecnicas/static/js/lists-words-testers.js +103 -0
- tecnicas/static/js/pf-make-list.js +227 -0
- tecnicas/static/js/test-cata.js +0 -2
- tecnicas/templates/tecnicas/components/form-scale-continue.html +3 -2
- tecnicas/templates/tecnicas/components/item-list-words.html +8 -0
- tecnicas/templates/tecnicas/components/table_pf.html +39 -0
- tecnicas/templates/tecnicas/forms_tester/convencional.html +1 -1
- tecnicas/templates/tecnicas/forms_tester/init_session_pf.html +123 -0
- tecnicas/templates/tecnicas/forms_tester/test_pf_list_words.html +184 -0
- tecnicas/templates/tecnicas/forms_tester/test_pf_rating_list.html +129 -0
- tecnicas/templates/tecnicas/manage_sesions/details-session-pf.html +68 -14
- tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html +126 -0
- tecnicas/urls.py +8 -0
- tecnicas/views/__init__.py +2 -0
- tecnicas/views/apis/api_list_words_pf.py +23 -0
- tecnicas/views/apis/rating_word_cata.py +0 -2
- tecnicas/views/sessions_management/session_details.py +7 -2
- tecnicas/views/sessions_management/session_monitor.py +19 -2
- tecnicas/views/tester_forms/cata_test.py +0 -1
- tecnicas/views/tester_forms/init_tester_form.py +23 -9
- tecnicas/views/tester_forms/login_session.py +4 -0
- tecnicas/views/tester_forms/pf_test.py +19 -0
tecnicas/admin.py
CHANGED
|
@@ -14,7 +14,7 @@ from .models import Producto, Participacion
|
|
| 14 |
|
| 15 |
from .models import Orden, Posicion
|
| 16 |
|
| 17 |
-
from .models import Dato, ValorDecimal, ValorBooleano, Calificacion
|
| 18 |
|
| 19 |
# Register your models here.
|
| 20 |
admin.site.register(CategoriaTecnica)
|
|
@@ -46,3 +46,4 @@ admin.site.register(Dato)
|
|
| 46 |
admin.site.register(ValorDecimal)
|
| 47 |
admin.site.register(ValorBooleano)
|
| 48 |
admin.site.register(Calificacion)
|
|
|
|
|
|
| 14 |
|
| 15 |
from .models import Orden, Posicion
|
| 16 |
|
| 17 |
+
from .models import Dato, ValorDecimal, ValorBooleano, Calificacion, ListaPalabras
|
| 18 |
|
| 19 |
# Register your models here.
|
| 20 |
admin.site.register(CategoriaTecnica)
|
|
|
|
| 46 |
admin.site.register(ValorDecimal)
|
| 47 |
admin.site.register(ValorBooleano)
|
| 48 |
admin.site.register(Calificacion)
|
| 49 |
+
admin.site.register(ListaPalabras)
|
tecnicas/controllers/__init__.py
CHANGED
|
@@ -23,20 +23,28 @@ from .views_controller.session_management.details_escala_controller import Detal
|
|
| 23 |
from .views_controller.session_management.details_rata_controller import DetallesRATAController
|
| 24 |
from .views_controller.session_management.details_cata_controller import DetallesCATAController
|
| 25 |
from .views_controller.session_management.details_pf_controller import DetallesPFController
|
|
|
|
| 26 |
from .views_controller.session_management.monitor_controller import MonitorController
|
| 27 |
from .views_controller.session_management.monitor_escalas_controller import MonitorEscalasController
|
| 28 |
from .views_controller.session_management.monitor_rata_controller import MonitorRATAController
|
|
|
|
| 29 |
|
| 30 |
from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
|
| 31 |
-
from .views_controller.sessions_tester.init_session_tester_controller import InitSessionTesterController
|
| 32 |
from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController
|
| 33 |
-
|
| 34 |
-
from .views_controller.sessions_tester.
|
| 35 |
-
from .views_controller.sessions_tester.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController
|
| 38 |
from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController
|
| 39 |
|
| 40 |
from .api_controller.rating_sacales_controller import RatingScalesController
|
| 41 |
from .api_controller.rating_cata_controller import RatingCataController
|
|
|
|
| 42 |
from .views_controller.tester_list_controller import TesterListController
|
|
|
|
| 23 |
from .views_controller.session_management.details_rata_controller import DetallesRATAController
|
| 24 |
from .views_controller.session_management.details_cata_controller import DetallesCATAController
|
| 25 |
from .views_controller.session_management.details_pf_controller import DetallesPFController
|
| 26 |
+
|
| 27 |
from .views_controller.session_management.monitor_controller import MonitorController
|
| 28 |
from .views_controller.session_management.monitor_escalas_controller import MonitorEscalasController
|
| 29 |
from .views_controller.session_management.monitor_rata_controller import MonitorRATAController
|
| 30 |
+
from .views_controller.session_management.monitor_pf_controller import MonitorPFController
|
| 31 |
|
| 32 |
from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
|
|
|
|
| 33 |
from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController
|
| 34 |
+
|
| 35 |
+
from .views_controller.sessions_tester.tests_forms.test_scales_controller import TestScalesController
|
| 36 |
+
from .views_controller.sessions_tester.tests_forms.test_rata_controller import TestRataController
|
| 37 |
+
from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController
|
| 38 |
+
from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController
|
| 39 |
+
|
| 40 |
+
from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController
|
| 41 |
+
from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController
|
| 42 |
+
from .views_controller.sessions_tester.init_session.init_session_pf_controller import InitSessionPFController
|
| 43 |
|
| 44 |
from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController
|
| 45 |
from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController
|
| 46 |
|
| 47 |
from .api_controller.rating_sacales_controller import RatingScalesController
|
| 48 |
from .api_controller.rating_cata_controller import RatingCataController
|
| 49 |
+
from .api_controller.rating_pf_list_controller import RatingPFListController
|
| 50 |
from .views_controller.tester_list_controller import TesterListController
|
tecnicas/controllers/api_controller/rating_pf_list_controller.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import JsonResponse, HttpRequest
|
| 2 |
+
from django.db import transaction, IntegrityError
|
| 3 |
+
from tecnicas.models import Palabra, ListaPalabras, Participacion
|
| 4 |
+
from tecnicas.forms import ListWordsForm
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RatingPFListController():
|
| 8 |
+
def __init__(self):
|
| 9 |
+
pass
|
| 10 |
+
|
| 11 |
+
@staticmethod
|
| 12 |
+
def getListWords(request: HttpRequest):
|
| 13 |
+
participation_current_tester = Participacion.objects.get(
|
| 14 |
+
id=request.session["id_participation"])
|
| 15 |
+
|
| 16 |
+
technique = participation_current_tester.tecnica
|
| 17 |
+
|
| 18 |
+
participations_testers = Participacion.objects.exclude(
|
| 19 |
+
catador=participation_current_tester.catador).filter(tecnica=technique)
|
| 20 |
+
|
| 21 |
+
all_testers = [
|
| 22 |
+
participation.catador for participation in participations_testers]
|
| 23 |
+
|
| 24 |
+
list_words_testers = list(ListaPalabras.objects.filter(
|
| 25 |
+
tecnica=technique, catador__in=all_testers, es_final=True))
|
| 26 |
+
|
| 27 |
+
if not list_words_testers:
|
| 28 |
+
return JsonResponse({"error": "Aun no hay listas finales de Catadores"})
|
| 29 |
+
|
| 30 |
+
result = []
|
| 31 |
+
for list_tester in list_words_testers:
|
| 32 |
+
try:
|
| 33 |
+
username = list_tester.catador.user.username
|
| 34 |
+
except Exception:
|
| 35 |
+
username = None
|
| 36 |
+
|
| 37 |
+
finish = [
|
| 38 |
+
participation.finalizado for participation in participations_testers if participation.catador.user.username == username][0]
|
| 39 |
+
|
| 40 |
+
status = "Lista terminada" if finish else "Lista en proceso"
|
| 41 |
+
|
| 42 |
+
words_qs = list_tester.palabras.all()
|
| 43 |
+
words = []
|
| 44 |
+
for p in words_qs:
|
| 45 |
+
nombre = getattr(p, 'nombre_palabra', None)
|
| 46 |
+
words.append({
|
| 47 |
+
'id': getattr(p, 'id', None),
|
| 48 |
+
'nombre_palabra': nombre
|
| 49 |
+
})
|
| 50 |
+
|
| 51 |
+
result.append({
|
| 52 |
+
'username': username,
|
| 53 |
+
'words': words,
|
| 54 |
+
'status': status
|
| 55 |
+
})
|
| 56 |
+
|
| 57 |
+
return JsonResponse({
|
| 58 |
+
"message": "Listas encontradas",
|
| 59 |
+
"lists_words": result
|
| 60 |
+
})
|
| 61 |
+
|
| 62 |
+
@staticmethod
|
| 63 |
+
def saveList(request: HttpRequest, words: list, current_phase: int):
|
| 64 |
+
dic_words = {}
|
| 65 |
+
for index, word in enumerate(words, start=1):
|
| 66 |
+
dic_words[f"palabra_{index}"] = word
|
| 67 |
+
|
| 68 |
+
form = ListWordsForm(dic_words, new_words=words)
|
| 69 |
+
|
| 70 |
+
if form.is_valid():
|
| 71 |
+
participation = Participacion.objects.get(
|
| 72 |
+
id=request.session["id_participation"])
|
| 73 |
+
|
| 74 |
+
if not participation:
|
| 75 |
+
return JsonResponse({"error": "No está autorizado en la sesión"})
|
| 76 |
+
|
| 77 |
+
technique = participation.tecnica
|
| 78 |
+
|
| 79 |
+
list_words_tester: ListaPalabras
|
| 80 |
+
if current_phase == 1:
|
| 81 |
+
(list_words_tester, created) = ListaPalabras.objects.get_or_create(
|
| 82 |
+
tecnica=technique,
|
| 83 |
+
catador=request.user.user_catador,
|
| 84 |
+
es_final=False,
|
| 85 |
+
)
|
| 86 |
+
elif current_phase == 2:
|
| 87 |
+
(list_words_tester, created) = ListaPalabras.objects.get_or_create(
|
| 88 |
+
tecnica=technique,
|
| 89 |
+
catador=request.user.user_catador,
|
| 90 |
+
es_final=True,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
added_words = RatingPFListController.addWordsToListWordsTester(
|
| 94 |
+
list_words=words, list_tester=list_words_tester)
|
| 95 |
+
|
| 96 |
+
response = JsonResponse({
|
| 97 |
+
"message": "Palabras guardadas con exito",
|
| 98 |
+
"words": [word.nombre_palabra for word in added_words]
|
| 99 |
+
})
|
| 100 |
+
else:
|
| 101 |
+
response = JsonResponse({"error": "Palabras invalidas"})
|
| 102 |
+
|
| 103 |
+
return response
|
| 104 |
+
|
| 105 |
+
@staticmethod
|
| 106 |
+
def addWordsToListWordsTester(list_words: list[str], list_tester: ListaPalabras):
|
| 107 |
+
# Normalizar
|
| 108 |
+
clean_words = [s.strip() for s in list_words if s.strip()]
|
| 109 |
+
|
| 110 |
+
# Obtener existentes
|
| 111 |
+
all_words = Palabra.objects.filter(nombre_palabra__in=clean_words)
|
| 112 |
+
|
| 113 |
+
names_words_exist = set(
|
| 114 |
+
all_words.values_list('nombre_palabra', flat=True))
|
| 115 |
+
|
| 116 |
+
# Ejecutar el query para no sumar palabras repetidas
|
| 117 |
+
all_words = list(all_words)
|
| 118 |
+
|
| 119 |
+
# Determinar faltantes
|
| 120 |
+
missing_words = [
|
| 121 |
+
nombre for nombre in clean_words if nombre not in names_words_exist]
|
| 122 |
+
print("No save words", missing_words)
|
| 123 |
+
|
| 124 |
+
created_words = []
|
| 125 |
+
|
| 126 |
+
# Intentar crear missing_words
|
| 127 |
+
for nombre in missing_words:
|
| 128 |
+
try:
|
| 129 |
+
with transaction.atomic():
|
| 130 |
+
palabra, created = Palabra.objects.get_or_create(
|
| 131 |
+
nombre_palabra=nombre)
|
| 132 |
+
if created:
|
| 133 |
+
created_words.append(palabra)
|
| 134 |
+
except IntegrityError:
|
| 135 |
+
palabra = Palabra.objects.get(nombre_palabra=nombre)
|
| 136 |
+
created_words.append(palabra)
|
| 137 |
+
|
| 138 |
+
# Combinar todas (all_words + created_words)
|
| 139 |
+
all_new_words = all_words + created_words
|
| 140 |
+
|
| 141 |
+
list_tester.palabras.set(all_new_words)
|
| 142 |
+
|
| 143 |
+
return all_new_words
|
tecnicas/controllers/models_controller/dato_controller.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
from
|
| 2 |
-
from
|
| 3 |
from django.core.exceptions import ValidationError
|
| 4 |
from django.db.models import F
|
| 5 |
|
|
@@ -12,11 +12,7 @@ class DatoController():
|
|
| 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,15 +35,27 @@ class DatoController():
|
|
| 39 |
except ValidationError as e:
|
| 40 |
return controller_error(e.message)
|
| 41 |
|
| 42 |
-
def setValue(self
|
| 43 |
-
if
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
else:
|
| 47 |
-
self.value_data = ValorDecimal(valor=new_value)
|
| 48 |
else:
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
|
|
|
| 51 |
return self.value_data
|
| 52 |
|
| 53 |
def saveValue(self):
|
|
@@ -94,3 +102,21 @@ class DatoController():
|
|
| 94 |
)
|
| 95 |
|
| 96 |
return list(result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from tecnicas.models import Calificacion, Dato, Palabra, ValorDecimal, ValorBooleano, Tecnica, Catador
|
| 2 |
+
from tecnicas.utils import controller_error, getId
|
| 3 |
from django.core.exceptions import ValidationError
|
| 4 |
from django.db.models import F
|
| 5 |
|
|
|
|
| 12 |
}
|
| 13 |
|
| 14 |
self.data = Dato(**atributes)
|
| 15 |
+
self.value_rating = value_rating
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def setRating(self, new_rating: Calificacion):
|
| 18 |
try:
|
|
|
|
| 35 |
except ValidationError as e:
|
| 36 |
return controller_error(e.message)
|
| 37 |
|
| 38 |
+
def setValue(self):
|
| 39 |
+
if isinstance(self.value_rating, bool):
|
| 40 |
+
self.value_data = ValorBooleano(valor=self.value_rating)
|
| 41 |
+
|
|
|
|
|
|
|
| 42 |
else:
|
| 43 |
+
type_scale = self.data.id_calificacion.id_tecnica.escala_tecnica.id_tipo_escala.nombre_escala
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
if type_scale == "continua":
|
| 47 |
+
decimal_value = self.value_rating/100
|
| 48 |
+
value_rounded = round(decimal_value)
|
| 49 |
+
self.value_data = ValorDecimal(valor=value_rounded)
|
| 50 |
+
|
| 51 |
+
print(self.value_rating)
|
| 52 |
+
print(decimal_value)
|
| 53 |
+
print(value_rounded)
|
| 54 |
+
|
| 55 |
+
else:
|
| 56 |
+
self.value_data = ValorDecimal(valor=self.value_rating)
|
| 57 |
|
| 58 |
+
self.value_data.id_dato = self.data
|
| 59 |
return self.value_data
|
| 60 |
|
| 61 |
def saveValue(self):
|
|
|
|
| 102 |
)
|
| 103 |
|
| 104 |
return list(result)
|
| 105 |
+
|
| 106 |
+
@staticmethod
|
| 107 |
+
def getWordValuesPF(technique: Tecnica, ratings: list[Calificacion], tester: Catador):
|
| 108 |
+
ids_ratings = [rat.id for rat in ratings]
|
| 109 |
+
|
| 110 |
+
result = (
|
| 111 |
+
ValorDecimal.objects
|
| 112 |
+
.filter(id_dato__id_calificacion_id__in=ids_ratings, id_dato__id_calificacion__id_catador=tester)
|
| 113 |
+
.values(
|
| 114 |
+
nombre_palabra=F("id_dato__id_palabra__nombre_palabra"),
|
| 115 |
+
repeticion=F("id_dato__id_calificacion__num_repeticion"),
|
| 116 |
+
producto_code=F(
|
| 117 |
+
"id_dato__id_calificacion__id_producto__codigoProducto"),
|
| 118 |
+
dato_valor=F("valor")
|
| 119 |
+
)
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
return list(result)
|
tecnicas/controllers/views_controller/session_management/details_pf_controller.py
CHANGED
|
@@ -1,5 +1,11 @@
|
|
| 1 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from .details_controller import DetallesController
|
|
|
|
| 3 |
|
| 4 |
|
| 5 |
class DetallesPFController(DetallesController):
|
|
@@ -23,11 +29,158 @@ class DetallesPFController(DetallesController):
|
|
| 23 |
# Definir el estado de la sesion
|
| 24 |
rep = technique.repeticion
|
| 25 |
activate = self.session.activo
|
| 26 |
-
|
| 27 |
self.context["estado"] = self.getStatus(rep, activate)
|
| 28 |
|
|
|
|
|
|
|
| 29 |
return self.context
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
def getStatus(self, rep: int, activate: bool):
|
| 32 |
status = ""
|
| 33 |
|
|
|
|
| 1 |
+
from django.http import HttpRequest
|
| 2 |
+
from django.shortcuts import redirect
|
| 3 |
+
from django.urls import reverse
|
| 4 |
+
from tecnicas.models import SesionSensorial, Presentador, Participacion, ListaPalabras, Calificacion, Catador
|
| 5 |
+
from tecnicas.controllers import ParticipacionController, DatoController
|
| 6 |
+
from tecnicas.utils import defaultdict_to_dict
|
| 7 |
from .details_controller import DetallesController
|
| 8 |
+
from collections import defaultdict
|
| 9 |
|
| 10 |
|
| 11 |
class DetallesPFController(DetallesController):
|
|
|
|
| 29 |
# Definir el estado de la sesion
|
| 30 |
rep = technique.repeticion
|
| 31 |
activate = self.session.activo
|
|
|
|
| 32 |
self.context["estado"] = self.getStatus(rep, activate)
|
| 33 |
|
| 34 |
+
self.getDataPhases()
|
| 35 |
+
|
| 36 |
return self.context
|
| 37 |
|
| 38 |
+
def startRepetition(self, presenter: Presentador, request: HttpRequest):
|
| 39 |
+
creator = presenter
|
| 40 |
+
technique = self.session.tecnica
|
| 41 |
+
|
| 42 |
+
if creator.user.username != self.session.creadoPor.user.username:
|
| 43 |
+
return self.getResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición o fase", request=request)
|
| 44 |
+
elif self.session.activo:
|
| 45 |
+
return self.getResponse(error="La sesión ya está activada", request=request)
|
| 46 |
+
|
| 47 |
+
there_participacions = Participacion.objects.filter(
|
| 48 |
+
tecnica=technique).exists()
|
| 49 |
+
|
| 50 |
+
if there_participacions:
|
| 51 |
+
(is_update_participations,
|
| 52 |
+
message) = ParticipacionController.outAllInSession(self.session)
|
| 53 |
+
if not is_update_participations:
|
| 54 |
+
return self.getResponse(error=message, request=request)
|
| 55 |
+
|
| 56 |
+
self.session.activo = True
|
| 57 |
+
technique.repeticion = technique.repeticion + 1
|
| 58 |
+
|
| 59 |
+
technique.save()
|
| 60 |
+
self.session.save()
|
| 61 |
+
|
| 62 |
+
parameters = {
|
| 63 |
+
"session_code": self.session.codigo_sesion
|
| 64 |
+
}
|
| 65 |
+
return redirect(
|
| 66 |
+
reverse(self.url_next, kwargs=parameters))
|
| 67 |
+
|
| 68 |
+
def getDataPhases(self):
|
| 69 |
+
curren_repetition = self.session.tecnica.repeticion
|
| 70 |
+
|
| 71 |
+
if curren_repetition == 1:
|
| 72 |
+
self.context["fisrt_phase"] = self.getDataFirstPhase()
|
| 73 |
+
elif curren_repetition == 2:
|
| 74 |
+
self.context["fisrt_phase"] = self.getDataFirstPhase()
|
| 75 |
+
self.context["second_phase"] = self.getDataSecondPhase()
|
| 76 |
+
elif curren_repetition >= 3:
|
| 77 |
+
self.context["fisrt_phase"] = self.getDataFirstPhase()
|
| 78 |
+
self.context["second_phase"] = self.getDataSecondPhase()
|
| 79 |
+
self.context["data_ratings"] = self.getDataRatings()
|
| 80 |
+
|
| 81 |
+
return self.context
|
| 82 |
+
|
| 83 |
+
def getDataFirstPhase(self):
|
| 84 |
+
lists_testers = ListaPalabras.objects.filter(
|
| 85 |
+
tecnica=self.session.tecnica,
|
| 86 |
+
es_final=False
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
result = []
|
| 90 |
+
for list in lists_testers:
|
| 91 |
+
try:
|
| 92 |
+
username = list.catador.user.username
|
| 93 |
+
except Exception:
|
| 94 |
+
username = None
|
| 95 |
+
|
| 96 |
+
words_qs = list.palabras.all()
|
| 97 |
+
words = []
|
| 98 |
+
for p in words_qs:
|
| 99 |
+
nombre = getattr(p, 'nombre_palabra', None)
|
| 100 |
+
words.append({
|
| 101 |
+
'id': getattr(p, 'id', None),
|
| 102 |
+
'nombre_palabra': nombre
|
| 103 |
+
})
|
| 104 |
+
|
| 105 |
+
result.append({
|
| 106 |
+
'username': username,
|
| 107 |
+
'words': words
|
| 108 |
+
})
|
| 109 |
+
|
| 110 |
+
return result
|
| 111 |
+
|
| 112 |
+
def getDataSecondPhase(self):
|
| 113 |
+
lists_testers = ListaPalabras.objects.filter(
|
| 114 |
+
tecnica=self.session.tecnica,
|
| 115 |
+
es_final=True
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
result = []
|
| 119 |
+
for list in lists_testers:
|
| 120 |
+
try:
|
| 121 |
+
username = list.catador.user.username
|
| 122 |
+
except Exception:
|
| 123 |
+
username = None
|
| 124 |
+
|
| 125 |
+
words_qs = list.palabras.all()
|
| 126 |
+
words = []
|
| 127 |
+
for p in words_qs:
|
| 128 |
+
nombre = getattr(p, 'nombre_palabra', None)
|
| 129 |
+
words.append({
|
| 130 |
+
'id': getattr(p, 'id', None),
|
| 131 |
+
'nombre_palabra': nombre
|
| 132 |
+
})
|
| 133 |
+
|
| 134 |
+
result.append({
|
| 135 |
+
'username': username,
|
| 136 |
+
'words': words
|
| 137 |
+
})
|
| 138 |
+
|
| 139 |
+
return result
|
| 140 |
+
|
| 141 |
+
def getDataRatings(self):
|
| 142 |
+
lists_words_testers = self.context["second_phase"]
|
| 143 |
+
technique = self.session.tecnica
|
| 144 |
+
|
| 145 |
+
ratings_for_tester = []
|
| 146 |
+
|
| 147 |
+
for list_tester in lists_words_testers:
|
| 148 |
+
tester_username = list_tester["username"]
|
| 149 |
+
# Se recuperan las calificaciones
|
| 150 |
+
ratings_for_repetition = []
|
| 151 |
+
|
| 152 |
+
ratings = list(Calificacion.objects.filter(
|
| 153 |
+
id_tecnica=technique, id_catador__user__username=tester_username))
|
| 154 |
+
|
| 155 |
+
if not ratings:
|
| 156 |
+
continue
|
| 157 |
+
|
| 158 |
+
data = DatoController.getWordValuesPF(
|
| 159 |
+
ratings=ratings, technique=technique, tester=Catador.objects.get(user__username=tester_username))
|
| 160 |
+
|
| 161 |
+
ratings_for_repetition = defaultdict(lambda: defaultdict(list))
|
| 162 |
+
|
| 163 |
+
for item in data:
|
| 164 |
+
rep = item["repeticion"]
|
| 165 |
+
prod = item["producto_code"]
|
| 166 |
+
|
| 167 |
+
ratings_for_repetition[rep-2][prod].append({
|
| 168 |
+
"nombre_palabra": item["nombre_palabra"],
|
| 169 |
+
"dato_valor": item["dato_valor"]
|
| 170 |
+
})
|
| 171 |
+
|
| 172 |
+
ratings_for_tester.append(
|
| 173 |
+
{
|
| 174 |
+
"tester": tester_username,
|
| 175 |
+
"ratings": defaultdict_to_dict(
|
| 176 |
+
ratings_for_repetition),
|
| 177 |
+
"words": list_tester["words"]
|
| 178 |
+
}
|
| 179 |
+
)
|
| 180 |
+
self.context["existen_calificaciones"] = True
|
| 181 |
+
|
| 182 |
+
return ratings_for_tester
|
| 183 |
+
|
| 184 |
def getStatus(self, rep: int, activate: bool):
|
| 185 |
status = ""
|
| 186 |
|
tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
from django.http import HttpRequest
|
| 2 |
-
from django.shortcuts import render, redirect
|
| 3 |
-
from django.urls import reverse
|
| 4 |
from tecnicas.models import Dato, Participacion
|
| 5 |
from tecnicas.controllers import SesionController
|
| 6 |
from .monitor_controller import MonitorController
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from tecnicas.models import Dato, Participacion
|
| 2 |
from tecnicas.controllers import SesionController
|
| 3 |
from .monitor_controller import MonitorController
|
tecnicas/controllers/views_controller/session_management/monitor_pf_controller.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest
|
| 2 |
+
from django.shortcuts import render, redirect
|
| 3 |
+
from django.urls import reverse
|
| 4 |
+
from tecnicas.models import Dato, Participacion, Catador, ListaPalabras, Producto
|
| 5 |
+
from tecnicas.controllers import SesionController
|
| 6 |
+
from .monitor_controller import MonitorController
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class MonitorPFController(MonitorController):
|
| 10 |
+
def __init__(self, session: SesionController):
|
| 11 |
+
super().__init__(session)
|
| 12 |
+
self.url_view = "tecnicas/manage_sesions/monitor-session-pf.html"
|
| 13 |
+
self.previus_view = "cata_system:detalles_sesion"
|
| 14 |
+
|
| 15 |
+
def checkAllFinish(self):
|
| 16 |
+
rep = self.sensorial_session.tecnica.repeticion
|
| 17 |
+
|
| 18 |
+
finish_data = ()
|
| 19 |
+
|
| 20 |
+
if rep == 1 or rep == 2:
|
| 21 |
+
finish_data = self.checkFinishFirstPhase()
|
| 22 |
+
elif rep >= 3:
|
| 23 |
+
finish_data = self.checkFinishRepetition()
|
| 24 |
+
|
| 25 |
+
return finish_data
|
| 26 |
+
|
| 27 |
+
def checkFinishFirstPhase(self):
|
| 28 |
+
num_paricipations = Participacion.objects.filter(
|
| 29 |
+
tecnica=self.sensorial_session.tecnica).count()
|
| 30 |
+
if num_paricipations < self.sensorial_session.tecnica.limite_catadores:
|
| 31 |
+
return (False, "No se ha alcanzado el número máximo de catadores")
|
| 32 |
+
|
| 33 |
+
unfinished_participations = Participacion.objects.filter(
|
| 34 |
+
tecnica=self.sensorial_session.tecnica, finalizado=False).count()
|
| 35 |
+
if unfinished_participations:
|
| 36 |
+
return (False, "No todos los catadores han finalizado su evaluación")
|
| 37 |
+
|
| 38 |
+
return (True, "Puedes finalizar la sesión")
|
| 39 |
+
|
| 40 |
+
def checkFinishRepetition(self) -> tuple[bool, str]:
|
| 41 |
+
technique = self.sensorial_session.tecnica
|
| 42 |
+
|
| 43 |
+
# Revisar numero de catadores sea alcanzado
|
| 44 |
+
all_participations = list(
|
| 45 |
+
Participacion.objects.filter(tecnica=technique))
|
| 46 |
+
if len(all_participations) < technique.limite_catadores:
|
| 47 |
+
return (False, "No se ha alcanzado el número máximo de Catadores")
|
| 48 |
+
|
| 49 |
+
# Revisar que cada catador haya terminado de calificar sus palabras
|
| 50 |
+
for particiapation in all_participations:
|
| 51 |
+
expected_ratings_repetition = self.getExpectedRatings(
|
| 52 |
+
tester=particiapation.catador)
|
| 53 |
+
|
| 54 |
+
num_ratings_now = Dato.objects.filter(
|
| 55 |
+
id_calificacion__num_repeticion=technique.repeticion,
|
| 56 |
+
id_calificacion__id_catador=particiapation.catador,
|
| 57 |
+
id_calificacion__id_tecnica=technique
|
| 58 |
+
).count()
|
| 59 |
+
|
| 60 |
+
if num_ratings_now < expected_ratings_repetition:
|
| 61 |
+
return (False, "No todos los catadores han finalizado su evaluación")
|
| 62 |
+
|
| 63 |
+
return (True, "Puedes finalizar la sesión")
|
| 64 |
+
|
| 65 |
+
def getExpectedRatings(self, tester: Catador):
|
| 66 |
+
num_words = ListaPalabras.objects.get(
|
| 67 |
+
tecnica=self.sensorial_session.tecnica,
|
| 68 |
+
catador=tester,
|
| 69 |
+
es_final=True
|
| 70 |
+
).palabras.all().count()
|
| 71 |
+
|
| 72 |
+
num_products = Producto.objects.filter(
|
| 73 |
+
id_tecnica=self.sensorial_session.tecnica).count()
|
| 74 |
+
|
| 75 |
+
return num_products * num_words
|
tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
from django.http import HttpRequest
|
| 2 |
-
from django.shortcuts import render, redirect
|
| 3 |
-
from django.urls import reverse
|
| 4 |
from tecnicas.models import Dato, Participacion
|
| 5 |
from tecnicas.controllers import SesionController
|
| 6 |
from .monitor_controller import MonitorController
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from tecnicas.models import Dato, Participacion
|
| 2 |
from tecnicas.controllers import SesionController
|
| 3 |
from .monitor_controller import MonitorController
|
tecnicas/controllers/views_controller/sessions_tester/{init_session_tester_controller.py → init_session/init_session_controller.py}
RENAMED
|
@@ -1,103 +1,23 @@
|
|
| 1 |
-
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
|
| 6 |
-
from tecnicas.
|
| 7 |
from tecnicas.utils import controller_error, shuffleArray
|
| 8 |
|
| 9 |
|
| 10 |
-
class
|
| 11 |
tester: Catador
|
| 12 |
session: SesionSensorial
|
| 13 |
order: Orden | dict
|
|
|
|
| 14 |
current_direction = "tecnicas/forms_tester/init_session.html"
|
| 15 |
escalas_direction = "cata_system:session_convencional"
|
| 16 |
-
cata_cirection = "cata_system:session_cata"
|
| 17 |
|
| 18 |
def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
|
| 19 |
self.tester = user_tester
|
| 20 |
self.session = sensorial_session
|
| 21 |
|
| 22 |
-
def controllGetEscalas(self, request: HttpRequest):
|
| 23 |
-
context = {
|
| 24 |
-
"session": self.session,
|
| 25 |
-
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
order = self.checkAndAssignOrder()
|
| 29 |
-
if isinstance(order, dict):
|
| 30 |
-
context["error"] = order["error"]
|
| 31 |
-
return render(request, self.current_direction, context)
|
| 32 |
-
|
| 33 |
-
is_end = self.isEndedSessionEscalas()
|
| 34 |
-
|
| 35 |
-
request.session["id_order"] = order.id
|
| 36 |
-
context["has_ended"] = is_end
|
| 37 |
-
|
| 38 |
-
if is_end:
|
| 39 |
-
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 40 |
-
|
| 41 |
-
if "error" in request.GET:
|
| 42 |
-
context["error"] = request.GET["error"]
|
| 43 |
-
|
| 44 |
-
return render(request, self.current_direction, context)
|
| 45 |
-
|
| 46 |
-
def controllPostEscalas(self, request: HttpRequest):
|
| 47 |
-
context = {
|
| 48 |
-
"session": self.session,
|
| 49 |
-
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
if request.POST["action"] == "start_posting":
|
| 53 |
-
parameters = {
|
| 54 |
-
"code_sesion": self.session.codigo_sesion
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
is_end = self.isEndedSessionEscalas()
|
| 58 |
-
if is_end:
|
| 59 |
-
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 60 |
-
return render(request, self.current_direction, context)
|
| 61 |
-
|
| 62 |
-
update_participation = ParticipacionController.enterSession(
|
| 63 |
-
tester=request.user.user_catador, session=self.session)
|
| 64 |
-
if isinstance(update_participation, dict):
|
| 65 |
-
context["error"] = update_participation["error"]
|
| 66 |
-
return render(request, self.current_direction, context)
|
| 67 |
-
|
| 68 |
-
request.session["id_participation"] = update_participation.id
|
| 69 |
-
|
| 70 |
-
if self.session.tecnica.tipo_tecnica.nombre_tecnica == "cata":
|
| 71 |
-
return redirect(reverse(self.cata_cirection, kwargs=parameters))
|
| 72 |
-
|
| 73 |
-
return redirect(reverse(self.escalas_direction, kwargs=parameters))
|
| 74 |
-
|
| 75 |
-
elif request.POST["action"] == "exit_session":
|
| 76 |
-
response = ParticipacionController.outSession(
|
| 77 |
-
tester=request.user.user_catador, session=self.session)
|
| 78 |
-
if isinstance(response, dict):
|
| 79 |
-
context["error"] = response["error"]
|
| 80 |
-
return render(request, self.current_direction, context)
|
| 81 |
-
|
| 82 |
-
else:
|
| 83 |
-
context["error"] = "Acción sin especificar"
|
| 84 |
-
return render(request, self.current_direction, context)
|
| 85 |
-
|
| 86 |
-
def controllGetRATA(self, request: HttpRequest):
|
| 87 |
-
context = {
|
| 88 |
-
"session": self.session,
|
| 89 |
-
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
is_end = self.isEndedSessionEscalas()
|
| 93 |
-
|
| 94 |
-
context["has_ended"] = is_end
|
| 95 |
-
|
| 96 |
-
if is_end:
|
| 97 |
-
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 98 |
-
|
| 99 |
-
return render(request, self.current_direction, context)
|
| 100 |
-
|
| 101 |
def assignOrder(self):
|
| 102 |
with transaction.atomic():
|
| 103 |
orders_without_tester = list(Orden.objects.select_for_update().filter(
|
|
@@ -124,7 +44,7 @@ class InitSessionTesterController():
|
|
| 124 |
return create
|
| 125 |
return self.order_to_assign
|
| 126 |
|
| 127 |
-
def
|
| 128 |
try:
|
| 129 |
participation = Participacion.objects.get(
|
| 130 |
catador=self.tester, tecnica=self.session.tecnica)
|
|
|
|
|
|
|
| 1 |
from django.http import HttpRequest
|
| 2 |
from django.shortcuts import render, redirect
|
| 3 |
from django.urls import reverse
|
| 4 |
+
from django.db import transaction
|
| 5 |
+
from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, EsVocabulario, Dato, ListaPalabras
|
| 6 |
from tecnicas.utils import controller_error, shuffleArray
|
| 7 |
|
| 8 |
|
| 9 |
+
class InitSessionController():
|
| 10 |
tester: Catador
|
| 11 |
session: SesionSensorial
|
| 12 |
order: Orden | dict
|
| 13 |
+
current_direction: str
|
| 14 |
current_direction = "tecnicas/forms_tester/init_session.html"
|
| 15 |
escalas_direction = "cata_system:session_convencional"
|
|
|
|
| 16 |
|
| 17 |
def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
|
| 18 |
self.tester = user_tester
|
| 19 |
self.session = sensorial_session
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
def assignOrder(self):
|
| 22 |
with transaction.atomic():
|
| 23 |
orders_without_tester = list(Orden.objects.select_for_update().filter(
|
|
|
|
| 44 |
return create
|
| 45 |
return self.order_to_assign
|
| 46 |
|
| 47 |
+
def isEndedSession(self):
|
| 48 |
try:
|
| 49 |
participation = Participacion.objects.get(
|
| 50 |
catador=self.tester, tecnica=self.session.tecnica)
|
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest
|
| 2 |
+
from django.shortcuts import render, redirect
|
| 3 |
+
from django.urls import reverse
|
| 4 |
+
from tecnicas.models import Catador, SesionSensorial, Orden
|
| 5 |
+
from tecnicas.controllers import ParticipacionController
|
| 6 |
+
from .init_session_controller import InitSessionController
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class InitSessionEscalasController(InitSessionController):
|
| 10 |
+
tester: Catador
|
| 11 |
+
session: SesionSensorial
|
| 12 |
+
order: Orden | dict
|
| 13 |
+
|
| 14 |
+
def __init__(self, sensorial_session, user_tester):
|
| 15 |
+
super().__init__(sensorial_session, user_tester)
|
| 16 |
+
self.current_direction = "tecnicas/forms_tester/init_session.html"
|
| 17 |
+
self.escalas_direction = "cata_system:session_convencional"
|
| 18 |
+
self.cata_direction = "cata_system:session_cata"
|
| 19 |
+
|
| 20 |
+
def controllGet(self, request: HttpRequest):
|
| 21 |
+
context = {
|
| 22 |
+
"session": self.session,
|
| 23 |
+
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
order = self.checkAndAssignOrder()
|
| 27 |
+
if isinstance(order, dict):
|
| 28 |
+
context["error"] = order["error"]
|
| 29 |
+
return render(request, self.current_direction, context)
|
| 30 |
+
|
| 31 |
+
is_end = self.isEndedSession()
|
| 32 |
+
|
| 33 |
+
request.session["id_order"] = order.id
|
| 34 |
+
context["has_ended"] = is_end
|
| 35 |
+
|
| 36 |
+
if is_end:
|
| 37 |
+
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 38 |
+
|
| 39 |
+
if "error" in request.GET:
|
| 40 |
+
context["error"] = request.GET["error"]
|
| 41 |
+
|
| 42 |
+
return render(request, self.current_direction, context)
|
| 43 |
+
|
| 44 |
+
def controllPost(self, request: HttpRequest):
|
| 45 |
+
context = {
|
| 46 |
+
"session": self.session,
|
| 47 |
+
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
if request.POST["action"] == "start_posting":
|
| 51 |
+
parameters = {
|
| 52 |
+
"code_sesion": self.session.codigo_sesion
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
is_end = self.isEndedSession()
|
| 56 |
+
if is_end:
|
| 57 |
+
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 58 |
+
return render(request, self.current_direction, context)
|
| 59 |
+
|
| 60 |
+
update_participation = ParticipacionController.enterSession(
|
| 61 |
+
tester=request.user.user_catador, session=self.session)
|
| 62 |
+
if isinstance(update_participation, dict):
|
| 63 |
+
context["error"] = update_participation["error"]
|
| 64 |
+
return render(request, self.current_direction, context)
|
| 65 |
+
|
| 66 |
+
request.session["id_participation"] = update_participation.id
|
| 67 |
+
|
| 68 |
+
if self.session.tecnica.tipo_tecnica.nombre_tecnica == "cata":
|
| 69 |
+
return redirect(reverse(self.cata_direction, kwargs=parameters))
|
| 70 |
+
|
| 71 |
+
return redirect(reverse(self.escalas_direction, kwargs=parameters))
|
| 72 |
+
|
| 73 |
+
elif request.POST["action"] == "exit_session":
|
| 74 |
+
response = ParticipacionController.outSession(
|
| 75 |
+
tester=request.user.user_catador, session=self.session)
|
| 76 |
+
if isinstance(response, dict):
|
| 77 |
+
context["error"] = response["error"]
|
| 78 |
+
return render(request, self.current_direction, context)
|
| 79 |
+
|
| 80 |
+
else:
|
| 81 |
+
context["error"] = "Acción sin especificar"
|
| 82 |
+
return render(request, self.current_direction, context)
|
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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, Producto, Dato, ListaPalabras
|
| 5 |
+
from tecnicas.controllers import ParticipacionController
|
| 6 |
+
from .init_session_controller import InitSessionController
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class InitSessionPFController(InitSessionController):
|
| 10 |
+
def __init__(self, sensorial_session, user_tester):
|
| 11 |
+
super().__init__(sensorial_session, user_tester)
|
| 12 |
+
self.current_direction = "tecnicas/forms_tester/init_session_pf.html"
|
| 13 |
+
self.pf_direction = "cata_system:session_pf"
|
| 14 |
+
|
| 15 |
+
def controllGet(self, request: HttpRequest):
|
| 16 |
+
context = {
|
| 17 |
+
"session": self.session,
|
| 18 |
+
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
(is_end, message, rep_show) = self.isEndedSession()
|
| 22 |
+
|
| 23 |
+
context["has_ended"] = is_end
|
| 24 |
+
context["activity"] = message
|
| 25 |
+
context["repetition"] = rep_show
|
| 26 |
+
|
| 27 |
+
if "error" in request.GET:
|
| 28 |
+
context["error"] = request.GET["error"]
|
| 29 |
+
|
| 30 |
+
return render(request, self.current_direction, context)
|
| 31 |
+
|
| 32 |
+
def controllPost(self, request: HttpRequest):
|
| 33 |
+
context = {
|
| 34 |
+
"session": self.session,
|
| 35 |
+
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
action = request.POST["action"]
|
| 39 |
+
|
| 40 |
+
if action == "start_posting":
|
| 41 |
+
parameters = {
|
| 42 |
+
"code_sesion": self.session.codigo_sesion
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
(is_end, message, rep_show) = self.isEndedSession()
|
| 46 |
+
if is_end:
|
| 47 |
+
return self.controllGet(request)
|
| 48 |
+
|
| 49 |
+
update_participation = ParticipacionController.enterSession(
|
| 50 |
+
tester=request.user.user_catador, session=self.session)
|
| 51 |
+
if isinstance(update_participation, dict):
|
| 52 |
+
context["error"] = update_participation["error"]
|
| 53 |
+
return render(request, self.current_direction, context)
|
| 54 |
+
|
| 55 |
+
request.session["id_participation"] = update_participation.id
|
| 56 |
+
|
| 57 |
+
return redirect(reverse(self.pf_direction, kwargs=parameters))
|
| 58 |
+
|
| 59 |
+
elif action == "exit_session":
|
| 60 |
+
response = ParticipacionController.outSession(
|
| 61 |
+
tester=request.user.user_catador, session=self.session)
|
| 62 |
+
if isinstance(response, dict):
|
| 63 |
+
context["error"] = response["error"]
|
| 64 |
+
return self.controllGet(request)
|
| 65 |
+
|
| 66 |
+
else:
|
| 67 |
+
context["error"] = "Acción sin especificar"
|
| 68 |
+
return render(request, self.current_direction, context)
|
| 69 |
+
|
| 70 |
+
def isEndedSession(self) -> tuple[bool, str, int]:
|
| 71 |
+
rep = self.session.tecnica.repeticion
|
| 72 |
+
|
| 73 |
+
is_end = False
|
| 74 |
+
message = ""
|
| 75 |
+
repetitiom_show = 0
|
| 76 |
+
|
| 77 |
+
if rep == 1:
|
| 78 |
+
is_end = self.endedSessionMakeList()
|
| 79 |
+
message = "Ya has creado la Lista de palabras inicial" if is_end else "Debes crear tu lista de palabras inicial"
|
| 80 |
+
repetitiom_show = 0
|
| 81 |
+
elif rep == 2:
|
| 82 |
+
is_end = self.endedSessionMakeList()
|
| 83 |
+
message = "Ya has creado la Lista de palabras final" if is_end else "Debes crear tu lista de palabras final"
|
| 84 |
+
repetitiom_show = 0
|
| 85 |
+
elif rep >= 3:
|
| 86 |
+
is_end = self.endedSessionRepetition()
|
| 87 |
+
message = "Has finalizado con el proceso de calificación" if is_end else "Debe hacer tu proceso de calificación"
|
| 88 |
+
repetitiom_show = rep - 2
|
| 89 |
+
else:
|
| 90 |
+
message = "Parece que la repetición es cero, no es posible hacer algo ahora mismo"
|
| 91 |
+
|
| 92 |
+
return (is_end, message, repetitiom_show)
|
| 93 |
+
|
| 94 |
+
def endedSessionMakeList(self):
|
| 95 |
+
try:
|
| 96 |
+
return Participacion.objects.get(
|
| 97 |
+
catador=self.tester, tecnica=self.session.tecnica).finalizado
|
| 98 |
+
except Participacion.DoesNotExist:
|
| 99 |
+
print("No se ha encontrado la participación")
|
| 100 |
+
return False
|
| 101 |
+
|
| 102 |
+
def endedSessionRepetition(self):
|
| 103 |
+
try:
|
| 104 |
+
participation = Participacion.objects.get(
|
| 105 |
+
catador=self.tester, tecnica=self.session.tecnica)
|
| 106 |
+
self.session.refresh_from_db()
|
| 107 |
+
|
| 108 |
+
# ////////////////////////////////////////////////////////////// #
|
| 109 |
+
#
|
| 110 |
+
# numero_datos_esperadas = num_productos * num_palabras
|
| 111 |
+
# Si numero_datos_esperadas es igual a numero_datos_actuales en la repetcion R
|
| 112 |
+
# Ha terminado la repeticion
|
| 113 |
+
#
|
| 114 |
+
# ////////////////////////////////////////////////////////////// #
|
| 115 |
+
|
| 116 |
+
if participation.finalizado:
|
| 117 |
+
num_products = Producto.objects.filter(
|
| 118 |
+
id_tecnica=self.session.tecnica).count()
|
| 119 |
+
|
| 120 |
+
num_words = ListaPalabras.objects.get(
|
| 121 |
+
tecnica=self.session.tecnica,
|
| 122 |
+
catador=self.tester,
|
| 123 |
+
es_final=True
|
| 124 |
+
).palabras.all().count()
|
| 125 |
+
|
| 126 |
+
expected_ratings_repetition = num_products * num_words
|
| 127 |
+
|
| 128 |
+
technique = self.session.tecnica
|
| 129 |
+
num_ratings_now = Dato.objects.filter(
|
| 130 |
+
id_calificacion__id_catador=self.tester,
|
| 131 |
+
id_calificacion__id_tecnica=technique,
|
| 132 |
+
id_calificacion__num_repeticion=technique.repeticion
|
| 133 |
+
).count()
|
| 134 |
+
|
| 135 |
+
is_end = num_ratings_now >= expected_ratings_repetition
|
| 136 |
+
|
| 137 |
+
return is_end
|
| 138 |
+
|
| 139 |
+
else:
|
| 140 |
+
return participation.finalizado
|
| 141 |
+
|
| 142 |
+
except Participacion.DoesNotExist:
|
| 143 |
+
print("No se ha encontrado la participación")
|
| 144 |
+
return False
|
tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest
|
| 2 |
+
from django.shortcuts import render
|
| 3 |
+
from .init_session_controller import InitSessionController
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class InitSessionRATAController(InitSessionController):
|
| 7 |
+
def __init__(self, sensorial_session, user_tester):
|
| 8 |
+
super().__init__(sensorial_session, user_tester)
|
| 9 |
+
self.current_direction = "tecnicas/forms_tester/init_session.html"
|
| 10 |
+
|
| 11 |
+
def controllGet(self, request: HttpRequest):
|
| 12 |
+
context = {
|
| 13 |
+
"session": self.session,
|
| 14 |
+
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
is_end = self.isEndedSession()
|
| 18 |
+
|
| 19 |
+
context["has_ended"] = is_end
|
| 20 |
+
|
| 21 |
+
if is_end:
|
| 22 |
+
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 23 |
+
|
| 24 |
+
return render(request, self.current_direction, context)
|
tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from django.http import HttpRequest
|
| 2 |
from django.shortcuts import render, redirect
|
| 3 |
from django.urls import reverse
|
| 4 |
from django.db import transaction
|
|
@@ -97,3 +97,6 @@ class LoginSessionTesterController():
|
|
| 97 |
else:
|
| 98 |
context["error"] = "Imposible acceder a esta sesión"
|
| 99 |
return render(request, self.current_direcction, context)
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest
|
| 2 |
from django.shortcuts import render, redirect
|
| 3 |
from django.urls import reverse
|
| 4 |
from django.db import transaction
|
|
|
|
| 97 |
else:
|
| 98 |
context["error"] = "Imposible acceder a esta sesión"
|
| 99 |
return render(request, self.current_direcction, context)
|
| 100 |
+
|
| 101 |
+
def validateEntryPF(self, request=HttpRequest):
|
| 102 |
+
return self.validateEntryEscalas(request=request)
|
tecnicas/controllers/views_controller/sessions_tester/{general_test_controller.py → tests_forms/general_test_controller.py}
RENAMED
|
File without changes
|
tecnicas/controllers/views_controller/sessions_tester/{test_cata_controller.py → tests_forms/test_cata_controller.py}
RENAMED
|
File without changes
|
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest
|
| 2 |
+
from django.shortcuts import redirect, render
|
| 3 |
+
from django.urls import reverse
|
| 4 |
+
from tecnicas.models import Producto, Participacion, Palabra, Calificacion, ListaPalabras
|
| 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 TestPFController(GenetalTestController):
|
| 11 |
+
def __init__(self, sensorial_session, user_tester):
|
| 12 |
+
super().__init__(sensorial_session, user_tester)
|
| 13 |
+
|
| 14 |
+
def controllGet(self, request: HttpRequest, error=""):
|
| 15 |
+
self.participation = Participacion.objects.get(
|
| 16 |
+
tecnica=self.session.tecnica, catador=request.user.user_catador)
|
| 17 |
+
self.context["session"] = self.session
|
| 18 |
+
|
| 19 |
+
if error:
|
| 20 |
+
self.context["error"] = error
|
| 21 |
+
|
| 22 |
+
rep = self.session.tecnica.repeticion
|
| 23 |
+
|
| 24 |
+
if rep == 1:
|
| 25 |
+
self.current_directory = "tecnicas/forms_tester/test_pf_list_words.html"
|
| 26 |
+
response = self.getFirstPhase(request)
|
| 27 |
+
elif rep == 2:
|
| 28 |
+
self.current_directory = "tecnicas/forms_tester/test_pf_list_words.html"
|
| 29 |
+
response = self.getSecondPhase(request)
|
| 30 |
+
elif rep >= 3:
|
| 31 |
+
self.current_directory = "tecnicas/forms_tester/test_pf_rating_list.html"
|
| 32 |
+
response = self.getRepetitionPhase(request)
|
| 33 |
+
else:
|
| 34 |
+
response = self.getErrorRepetition(request)
|
| 35 |
+
|
| 36 |
+
return response
|
| 37 |
+
|
| 38 |
+
def controllPost(self, request: HttpRequest):
|
| 39 |
+
action = request.POST["action"]
|
| 40 |
+
|
| 41 |
+
if action == "finish_session":
|
| 42 |
+
self.participation = Participacion.objects.get(
|
| 43 |
+
tecnica=self.session.tecnica, catador=request.user.user_catador)
|
| 44 |
+
ParticipacionController.finishSession(self.participation)
|
| 45 |
+
params = {"code_sesion": self.session.codigo_sesion}
|
| 46 |
+
return redirect(reverse(self.previus_directory, kwargs=params))
|
| 47 |
+
|
| 48 |
+
else:
|
| 49 |
+
return self.controllGet(request, error="Acción no permitida")
|
| 50 |
+
|
| 51 |
+
def getFirstPhase(self, request: HttpRequest):
|
| 52 |
+
self.participation.refresh_from_db()
|
| 53 |
+
|
| 54 |
+
if self.participation.finalizado:
|
| 55 |
+
params = {
|
| 56 |
+
"code_sesion": self.session.codigo_sesion
|
| 57 |
+
}
|
| 58 |
+
return redirect(reverse(self.previus_directory, kwargs=params))
|
| 59 |
+
|
| 60 |
+
self.context["form"] = ListWordsForm()
|
| 61 |
+
self.context["initial_phase"] = True
|
| 62 |
+
|
| 63 |
+
try:
|
| 64 |
+
tester_list = ListaPalabras.objects.get(
|
| 65 |
+
tecnica=self.session.tecnica,
|
| 66 |
+
catador=request.user.user_catador,
|
| 67 |
+
es_final=False
|
| 68 |
+
)
|
| 69 |
+
list_words = list(tester_list.palabras.all())
|
| 70 |
+
self.context["words"] = list_words
|
| 71 |
+
except ListaPalabras.DoesNotExist:
|
| 72 |
+
self.context["words"] = []
|
| 73 |
+
|
| 74 |
+
return render(request, self.current_directory, self.context)
|
| 75 |
+
|
| 76 |
+
def getSecondPhase(self, request: HttpRequest):
|
| 77 |
+
self.participation.refresh_from_db()
|
| 78 |
+
|
| 79 |
+
if self.participation.finalizado:
|
| 80 |
+
params = {
|
| 81 |
+
"code_sesion": self.session.codigo_sesion
|
| 82 |
+
}
|
| 83 |
+
return redirect(reverse(self.previus_directory, kwargs=params))
|
| 84 |
+
|
| 85 |
+
try:
|
| 86 |
+
tester_list = ListaPalabras.objects.get(
|
| 87 |
+
tecnica=self.session.tecnica,
|
| 88 |
+
catador=request.user.user_catador,
|
| 89 |
+
es_final=True
|
| 90 |
+
)
|
| 91 |
+
except ListaPalabras.DoesNotExist:
|
| 92 |
+
tester_list = ListaPalabras.objects.get(
|
| 93 |
+
tecnica=self.session.tecnica,
|
| 94 |
+
catador=request.user.user_catador,
|
| 95 |
+
es_final=False
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
list_words = list(tester_list.palabras.all())
|
| 99 |
+
|
| 100 |
+
self.context["form"] = ListWordsForm()
|
| 101 |
+
self.context["initial_phase"] = False
|
| 102 |
+
self.context["words"] = list_words
|
| 103 |
+
|
| 104 |
+
return render(request, self.current_directory, self.context)
|
| 105 |
+
|
| 106 |
+
def getRepetitionPhase(self, request: HttpRequest):
|
| 107 |
+
technique = self.session.tecnica
|
| 108 |
+
|
| 109 |
+
products_in_technique = Producto.objects.filter(id_tecnica=technique)
|
| 110 |
+
|
| 111 |
+
words = list(
|
| 112 |
+
ListaPalabras.objects.get(
|
| 113 |
+
tecnica=self.session.tecnica,
|
| 114 |
+
catador=request.user.user_catador,
|
| 115 |
+
es_final=True
|
| 116 |
+
).palabras.all()
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
use_product: Producto = None
|
| 120 |
+
use_words: list[Palabra] = None
|
| 121 |
+
|
| 122 |
+
# Revisamos el producto que le falten calificaciones
|
| 123 |
+
for current_product in products_in_technique:
|
| 124 |
+
try:
|
| 125 |
+
rating = Calificacion.objects.get(
|
| 126 |
+
num_repeticion=technique.repeticion,
|
| 127 |
+
id_producto=current_product,
|
| 128 |
+
id_tecnica=technique,
|
| 129 |
+
id_catador=self.tester
|
| 130 |
+
)
|
| 131 |
+
except Calificacion.DoesNotExist:
|
| 132 |
+
# Si no hay calificacion mandamos el producto actual y todas la palabras
|
| 133 |
+
use_product = current_product
|
| 134 |
+
use_words = words
|
| 135 |
+
break
|
| 136 |
+
|
| 137 |
+
# Obtener los datos asociados para la calificacion para ver que palabras quedan por calificar
|
| 138 |
+
recoreded_data = rating.dato_calificacion.all()
|
| 139 |
+
|
| 140 |
+
if not recoreded_data:
|
| 141 |
+
# Si no hay datos entonces devolver el producto con todas las palabras
|
| 142 |
+
use_product = current_product
|
| 143 |
+
use_words = words
|
| 144 |
+
break
|
| 145 |
+
else:
|
| 146 |
+
words_to_use = PalabrasController.getWordsWithoutData(
|
| 147 |
+
recoreded_data=recoreded_data, words=words)
|
| 148 |
+
|
| 149 |
+
# Si quedan palabras por calificar mandar las palabras con el producto
|
| 150 |
+
if not isinstance(words_to_use, dict) and words_to_use:
|
| 151 |
+
use_product = current_product
|
| 152 |
+
use_words = words_to_use
|
| 153 |
+
break
|
| 154 |
+
|
| 155 |
+
# Si no hay producto que falta por calificar finalizar sesion para el Catador
|
| 156 |
+
if not use_product:
|
| 157 |
+
updated_participation = ParticipacionController.finishSession(
|
| 158 |
+
self.participation)
|
| 159 |
+
params = {
|
| 160 |
+
"code_sesion": self.session.codigo_sesion
|
| 161 |
+
}
|
| 162 |
+
return redirect(reverse(self.previus_directory, kwargs=params))
|
| 163 |
+
|
| 164 |
+
scale = EscalaController.getScaleByTechnique(technique=technique)
|
| 165 |
+
use_tags = EscalaController.getRelatedTagsInScale(scale=scale)
|
| 166 |
+
|
| 167 |
+
self.context["product"] = use_product
|
| 168 |
+
self.context["words"] = use_words
|
| 169 |
+
|
| 170 |
+
self.context["scale"] = scale
|
| 171 |
+
self.context["type_scale"] = scale.id_tipo_escala.nombre_escala
|
| 172 |
+
self.context["tags"] = use_tags
|
| 173 |
+
|
| 174 |
+
self.context["repetition"] = technique.repeticion - 2
|
| 175 |
+
|
| 176 |
+
if self.context["type_scale"] == "continua":
|
| 177 |
+
self.context["size_scale"] = {
|
| 178 |
+
"max_size": scale.longitud * 100,
|
| 179 |
+
"middle_size": (scale.longitud * 100)/2
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
return render(request, self.current_directory, self.context)
|
| 183 |
+
|
| 184 |
+
def getErrorRepetition(self, request: HttpRequest):
|
| 185 |
+
params = {
|
| 186 |
+
"code_sesion": self.session.codigo_sesion
|
| 187 |
+
}
|
| 188 |
+
return redirect(reverse(self.previus_directory, kwargs=params))
|
tecnicas/controllers/views_controller/sessions_tester/{test_rata_controller.py → tests_forms/test_rata_controller.py}
RENAMED
|
File without changes
|
tecnicas/controllers/views_controller/sessions_tester/{test_scales_controller.py → tests_forms/test_scales_controller.py}
RENAMED
|
@@ -78,5 +78,10 @@ class TestScalesController(GenetalTestController):
|
|
| 78 |
ctx["scale"] = scale
|
| 79 |
ctx["type_scale"] = scale.id_tipo_escala.nombre_escala
|
| 80 |
ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
return render(request, self.current_directory, ctx)
|
|
|
|
| 78 |
ctx["scale"] = scale
|
| 79 |
ctx["type_scale"] = scale.id_tipo_escala.nombre_escala
|
| 80 |
ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale)
|
| 81 |
+
if ctx["type_scale"] == "continua":
|
| 82 |
+
ctx["size_scale"] = {
|
| 83 |
+
"max_size": scale.longitud * 100,
|
| 84 |
+
"middle_size": (scale.longitud * 100)/2
|
| 85 |
+
}
|
| 86 |
|
| 87 |
+
return render(request, self.current_directory, ctx)
|
tecnicas/forms/__init__.py
CHANGED
|
@@ -8,3 +8,5 @@ from .codes_form import CodesForm
|
|
| 8 |
from .catador_form import CatadorForm
|
| 9 |
from .word_form import WordForm
|
| 10 |
from .vocabulary_select import VocabularioSelectForm
|
|
|
|
|
|
|
|
|
| 8 |
from .catador_form import CatadorForm
|
| 9 |
from .word_form import WordForm
|
| 10 |
from .vocabulary_select import VocabularioSelectForm
|
| 11 |
+
|
| 12 |
+
from .list_words_form import ListWordsForm
|
tecnicas/forms/list_words_form.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django import forms
|
| 2 |
+
import re
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class ListWordsForm(forms.Form):
|
| 6 |
+
regex = re.compile(r'^[a-zñ]{3,}$')
|
| 7 |
+
|
| 8 |
+
def __init__(self, *args, new_words=None, **kwargs):
|
| 9 |
+
super().__init__(*args, **kwargs)
|
| 10 |
+
|
| 11 |
+
if not new_words:
|
| 12 |
+
self.fields['nombre_palabra'] = forms.CharField(
|
| 13 |
+
label='Nombre de la palabra',
|
| 14 |
+
max_length=255,
|
| 15 |
+
widget=forms.TextInput(attrs={
|
| 16 |
+
'placeholder': 'Escribe una palabra',
|
| 17 |
+
'class': 'cts-input-list-word bg-surface-ligt p-1 border-b-1 text-center w-full',
|
| 18 |
+
'pattern': self.regex.pattern,
|
| 19 |
+
'title': 'Solo letras minúsculas y la letra ñ, no poner acentos. No se permiten mayúsculas ni caracteres especiales. Mínimo 3 letras',
|
| 20 |
+
})
|
| 21 |
+
)
|
| 22 |
+
else:
|
| 23 |
+
for index, name in enumerate(new_words, start=1):
|
| 24 |
+
self.fields[f'palabra_{index}'] = forms.CharField(
|
| 25 |
+
label=f'Palabra {index}',
|
| 26 |
+
max_length=255,
|
| 27 |
+
initial=name,
|
| 28 |
+
widget=forms.TextInput(attrs={
|
| 29 |
+
'placeholder': 'Escribe una palabra',
|
| 30 |
+
'class': 'cts-input-list-word bg-surface-ligt p-1 border-b-1 text-center w-full',
|
| 31 |
+
'pattern': self.regex.pattern,
|
| 32 |
+
'title': 'Solo letras minúsculas y la letra ñ. No se permiten mayúsculas ni caracteres especiales.'
|
| 33 |
+
})
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
def clean(self):
|
| 37 |
+
cleaned_data = super().clean()
|
| 38 |
+
errores = []
|
| 39 |
+
|
| 40 |
+
for field_name, value in cleaned_data.items():
|
| 41 |
+
if not self.regex.match(value):
|
| 42 |
+
errores.append(f"'{value}' contiene caracteres no permitidos.")
|
| 43 |
+
elif len(value) > 255:
|
| 44 |
+
errores.append(f"'{value}' excede los 255 caracteres.")
|
| 45 |
+
|
| 46 |
+
if errores:
|
| 47 |
+
raise forms.ValidationError(errores)
|
| 48 |
+
|
| 49 |
+
return cleaned_data
|
tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.1 on 2025-11-12 20:16
|
| 2 |
+
|
| 3 |
+
import shortuuid.main
|
| 4 |
+
from django.db import migrations, models
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class Migration(migrations.Migration):
|
| 8 |
+
|
| 9 |
+
dependencies = [
|
| 10 |
+
('tecnicas', '0021_rename_nomre_vocabulario_vocabulario_nombre_vocabulario_and_more'),
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
operations = [
|
| 14 |
+
migrations.AlterField(
|
| 15 |
+
model_name='sesionsensorial',
|
| 16 |
+
name='codigo_sesion',
|
| 17 |
+
field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
|
| 18 |
+
),
|
| 19 |
+
]
|
tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.1 on 2025-11-12 20:24
|
| 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', '0022_alter_sesionsensorial_codigo_sesion'),
|
| 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='ListaPalabras',
|
| 22 |
+
fields=[
|
| 23 |
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
| 24 |
+
('es_final', models.BooleanField(default=False)),
|
| 25 |
+
('catador', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='catador_lista', to='tecnicas.catador')),
|
| 26 |
+
('palabras', models.ManyToManyField(related_name='lista_palabras', to='tecnicas.palabra')),
|
| 27 |
+
('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_lista', to='tecnicas.tecnica')),
|
| 28 |
+
],
|
| 29 |
+
),
|
| 30 |
+
]
|
tecnicas/models/__init__.py
CHANGED
|
@@ -27,4 +27,6 @@ from .dato_valor import ValorBooleano
|
|
| 27 |
from .orden import Orden
|
| 28 |
from .orden import Posicion
|
| 29 |
|
| 30 |
-
from .participacion import Participacion
|
|
|
|
|
|
|
|
|
| 27 |
from .orden import Orden
|
| 28 |
from .orden import Posicion
|
| 29 |
|
| 30 |
+
from .participacion import Participacion
|
| 31 |
+
|
| 32 |
+
from .lista_palabras import ListaPalabras
|
tecnicas/models/lista_palabras.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
from tecnicas.models import Tecnica, Catador, Palabra
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class ListaPalabras(models.Model):
|
| 6 |
+
tecnica = models.ForeignKey(
|
| 7 |
+
Tecnica, on_delete=models.CASCADE, related_name="tecnica_lista")
|
| 8 |
+
catador = models.ForeignKey(
|
| 9 |
+
Catador, on_delete=models.CASCADE, related_name="catador_lista")
|
| 10 |
+
es_final = models.BooleanField(default=False)
|
| 11 |
+
palabras = models.ManyToManyField(
|
| 12 |
+
Palabra, related_name="lista_palabras")
|
| 13 |
+
|
| 14 |
+
def __str__(self):
|
| 15 |
+
return f"{self.tecnica.sesion_tecnica.codigo_sesion} - {self.catador.user.username}"
|
tecnicas/static/js/download-table-zip.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener("DOMContentLoaded", function () {
|
| 2 |
+
const btn = document.getElementById("download-csv-btn");
|
| 3 |
+
if (!btn) return;
|
| 4 |
+
|
| 5 |
+
btn.addEventListener("click", async function () {
|
| 6 |
+
if (typeof JSZip === "undefined" || typeof saveAs === "undefined") {
|
| 7 |
+
alert(
|
| 8 |
+
"Librerías JSZip o FileSaver no están cargadas. Asegúrate de incluir los CDN de JSZip y FileSaver."
|
| 9 |
+
);
|
| 10 |
+
return;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
const sections = document.querySelectorAll("section[data-tester]");
|
| 14 |
+
if (!sections || sections.length === 0) {
|
| 15 |
+
alert("No hay tablas para descargar.");
|
| 16 |
+
return;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
const zip = new JSZip();
|
| 20 |
+
|
| 21 |
+
let zipName = "";
|
| 22 |
+
|
| 23 |
+
sections.forEach((sec) => {
|
| 24 |
+
const tester = sec.getAttribute("data-tester") || "tester";
|
| 25 |
+
const sessionName = sec.getAttribute("data-session-name") || "";
|
| 26 |
+
const sessionCode = sec.getAttribute("data-session-code") || "";
|
| 27 |
+
|
| 28 |
+
const fileName = `${tester}_${
|
| 29 |
+
sessionName ? sessionName.trim() : sessionCode.trim()
|
| 30 |
+
}`;
|
| 31 |
+
|
| 32 |
+
zipName = `datos_sesion_${
|
| 33 |
+
sessionName ? sessionName.trim() : sessionCode.trim()
|
| 34 |
+
}`;
|
| 35 |
+
|
| 36 |
+
const table = sec.querySelector("table");
|
| 37 |
+
if (!table) return;
|
| 38 |
+
|
| 39 |
+
// Build CSV
|
| 40 |
+
const rows = [];
|
| 41 |
+
const headerCells = Array.from(table.querySelectorAll("thead tr th"));
|
| 42 |
+
const headers = headerCells.map((h) => h.textContent.trim());
|
| 43 |
+
rows.push(headers.map(escapeCsv).join(","));
|
| 44 |
+
|
| 45 |
+
const trs = table.querySelectorAll("tbody tr");
|
| 46 |
+
trs.forEach((tr) => {
|
| 47 |
+
const tds = Array.from(tr.querySelectorAll("td"));
|
| 48 |
+
const values = tds.map((td) => escapeCsv(td.textContent.trim()));
|
| 49 |
+
rows.push(values.join(","));
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
const csvContent = rows.join("\r\n");
|
| 53 |
+
zip.file(`${fileName}.csv`, csvContent);
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
try {
|
| 57 |
+
const blob = await zip.generateAsync({ type: "blob" });
|
| 58 |
+
const zipNameWithExtension = zipName + ".zip";
|
| 59 |
+
saveAs(blob, zipNameWithExtension);
|
| 60 |
+
} catch (err) {
|
| 61 |
+
console.error(err);
|
| 62 |
+
alert("Error al generar el ZIP: " + err.message);
|
| 63 |
+
}
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
const escapeCsv = (val) => {
|
| 67 |
+
if (val == null) return "";
|
| 68 |
+
let normalVal = val.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
| 69 |
+
const needsQuotes = /[",\n,]/.test(normalVal);
|
| 70 |
+
let v = String(normalVal).replace(/"/g, '""');
|
| 71 |
+
if (needsQuotes) v = `"${v}"`;
|
| 72 |
+
return v;
|
| 73 |
+
};
|
| 74 |
+
});
|
tecnicas/static/js/lists-words-testers.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
async function getListWordsTesters() {
|
| 2 |
+
const URL = "/cata/testers/api/ratingword/pf/list";
|
| 3 |
+
try {
|
| 4 |
+
const response = await fetch(URL, {
|
| 5 |
+
method: "GET",
|
| 6 |
+
});
|
| 7 |
+
|
| 8 |
+
if (!response.ok) {
|
| 9 |
+
spanNotifaction("Fallo con la respuesta recibida");
|
| 10 |
+
return false;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
const result = await response.json();
|
| 14 |
+
const messError = result.error;
|
| 15 |
+
if (messError) {
|
| 16 |
+
spanNotifaction(messError);
|
| 17 |
+
const containerWords = document.querySelector(".cts-content-list-words");
|
| 18 |
+
containerWords.innerHTML = `
|
| 19 |
+
<p class="bg-surface-sweet font-bold text-center text-lg px-4 pt-2 pb-3 rounded w-full max-sm:mx-2">
|
| 20 |
+
${messError}
|
| 21 |
+
</p>`;
|
| 22 |
+
return false;
|
| 23 |
+
}
|
| 24 |
+
spanNotifaction(result.message, false);
|
| 25 |
+
const containerWords = document.querySelector(".cts-content-list-words");
|
| 26 |
+
containerWords.innerHTML = "";
|
| 27 |
+
|
| 28 |
+
const listWordsTesters = result.lists_words;
|
| 29 |
+
|
| 30 |
+
listWordsTesters.forEach((listTester) => {
|
| 31 |
+
const username = listTester.username;
|
| 32 |
+
const words = listTester.words;
|
| 33 |
+
const status = listTester.status;
|
| 34 |
+
const listDiv = createListWords(username, words, status);
|
| 35 |
+
containerWords.appendChild(listDiv);
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
return true;
|
| 39 |
+
} catch (error) {
|
| 40 |
+
console.error(error);
|
| 41 |
+
spanNotifaction("Error del servidor con la API");
|
| 42 |
+
return false;
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
function createListWords(username, words, status = "indefinido") {
|
| 47 |
+
const div = document.createElement("div");
|
| 48 |
+
const paragraph = document.createElement("p");
|
| 49 |
+
const paragraphStatus = document.createElement("p");
|
| 50 |
+
const ul = document.createElement("ul");
|
| 51 |
+
|
| 52 |
+
div.classList.add(
|
| 53 |
+
"cts-item-list-tester",
|
| 54 |
+
"bg-surface-sweet",
|
| 55 |
+
"px-4",
|
| 56 |
+
"pt-2",
|
| 57 |
+
"pb-3",
|
| 58 |
+
"rounded",
|
| 59 |
+
"shrink-0",
|
| 60 |
+
"w-64",
|
| 61 |
+
"space-y-2"
|
| 62 |
+
);
|
| 63 |
+
|
| 64 |
+
paragraph.classList.add(
|
| 65 |
+
"bg-surface-card",
|
| 66 |
+
"text-lg",
|
| 67 |
+
"font-semibold",
|
| 68 |
+
"text-center",
|
| 69 |
+
"rounded"
|
| 70 |
+
);
|
| 71 |
+
|
| 72 |
+
paragraphStatus.classList.add(
|
| 73 |
+
"bg-surface-card",
|
| 74 |
+
"text-lg",
|
| 75 |
+
"font-semibold",
|
| 76 |
+
"text-center",
|
| 77 |
+
"rounded"
|
| 78 |
+
);
|
| 79 |
+
|
| 80 |
+
paragraph.textContent = username;
|
| 81 |
+
paragraphStatus.textContent = status;
|
| 82 |
+
|
| 83 |
+
ul.classList.add("text-center", "grid", "grid-cols-2", "gap-2", "w-full");
|
| 84 |
+
|
| 85 |
+
words.forEach((word) => {
|
| 86 |
+
const li = document.createElement("li");
|
| 87 |
+
li.classList.add(
|
| 88 |
+
"bg-surface-ligt",
|
| 89 |
+
"rounded",
|
| 90 |
+
"font-bold",
|
| 91 |
+
"py-1",
|
| 92 |
+
"px-2",
|
| 93 |
+
"truncate"
|
| 94 |
+
);
|
| 95 |
+
li.textContent = word.nombre_palabra;
|
| 96 |
+
ul.appendChild(li);
|
| 97 |
+
});
|
| 98 |
+
|
| 99 |
+
div.appendChild(paragraph);
|
| 100 |
+
div.appendChild(paragraphStatus);
|
| 101 |
+
div.appendChild(ul);
|
| 102 |
+
return div;
|
| 103 |
+
}
|
tecnicas/static/js/pf-make-list.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const FORM_DESCRIBE = document.querySelector(".cts-form-pf-word");
|
| 2 |
+
const BOX_WORDS = document.querySelector(".cts-box-words");
|
| 3 |
+
const IMG_LIST = document.querySelector(".cts-img-list");
|
| 4 |
+
const ERROR_INPUT_WORD = document.querySelector(".error-input-word");
|
| 5 |
+
const FORM_ACTION = document.querySelector(".form-actions");
|
| 6 |
+
|
| 7 |
+
const WORDS = [];
|
| 8 |
+
const STYLES_LI = [
|
| 9 |
+
"cts-item-words",
|
| 10 |
+
"bg-gray-400",
|
| 11 |
+
"text-black",
|
| 12 |
+
"rounded",
|
| 13 |
+
"font-bold",
|
| 14 |
+
"text-lg",
|
| 15 |
+
"px-4",
|
| 16 |
+
"py-3",
|
| 17 |
+
"flex",
|
| 18 |
+
"flex-wrap",
|
| 19 |
+
"flex-row",
|
| 20 |
+
"flex-1",
|
| 21 |
+
"min-w-fit",
|
| 22 |
+
"justify-center",
|
| 23 |
+
"items-center",
|
| 24 |
+
"gap-3",
|
| 25 |
+
];
|
| 26 |
+
|
| 27 |
+
const STYLES_BTN = [
|
| 28 |
+
"cts-remove-word",
|
| 29 |
+
"px-4",
|
| 30 |
+
"border-b-2",
|
| 31 |
+
"active:border-b-0",
|
| 32 |
+
"active:border-t-2",
|
| 33 |
+
"transition-all",
|
| 34 |
+
"rounded-xl",
|
| 35 |
+
"font-black",
|
| 36 |
+
"w-fit",
|
| 37 |
+
"capitalize",
|
| 38 |
+
"active:border-red-500",
|
| 39 |
+
"border-red-800",
|
| 40 |
+
"bg-red-500",
|
| 41 |
+
];
|
| 42 |
+
|
| 43 |
+
const itemWord = (wordName, index) => {
|
| 44 |
+
const btn = document.createElement("button");
|
| 45 |
+
btn.setAttribute("data-index", index);
|
| 46 |
+
btn.classList.add(...STYLES_BTN);
|
| 47 |
+
btn.textContent = "➖";
|
| 48 |
+
|
| 49 |
+
const ph = document.createElement("p");
|
| 50 |
+
ph.classList.add("ct-word-received");
|
| 51 |
+
ph.textContent = wordName;
|
| 52 |
+
|
| 53 |
+
const li = document.createElement("li");
|
| 54 |
+
li.setAttribute("id", `word-${index}`);
|
| 55 |
+
li.classList.add(...STYLES_LI);
|
| 56 |
+
|
| 57 |
+
li.appendChild(ph);
|
| 58 |
+
li.appendChild(btn);
|
| 59 |
+
|
| 60 |
+
return li;
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
function initWordsFromBox() {
|
| 64 |
+
if (!BOX_WORDS) return;
|
| 65 |
+
const current_words = BOX_WORDS.querySelectorAll(".cts-item-words");
|
| 66 |
+
if (!current_words.length) return;
|
| 67 |
+
|
| 68 |
+
WORDS.length = 0;
|
| 69 |
+
current_words.forEach((li) => {
|
| 70 |
+
const p = li.querySelector(".ct-word-received");
|
| 71 |
+
const text = p ? p.textContent.trim() : li.textContent.trim();
|
| 72 |
+
if (text) WORDS.push(text);
|
| 73 |
+
});
|
| 74 |
+
renderWords();
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
function renderWords() {
|
| 78 |
+
if (!BOX_WORDS) return;
|
| 79 |
+
if (!WORDS.length) {
|
| 80 |
+
BOX_WORDS.innerHTML = "";
|
| 81 |
+
IMG_LIST.classList.remove("hidden");
|
| 82 |
+
BOX_WORDS.appendChild(IMG_LIST);
|
| 83 |
+
return;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
BOX_WORDS.innerHTML = "";
|
| 87 |
+
WORDS.forEach((word, index) => {
|
| 88 |
+
let liElement = itemWord(word, index);
|
| 89 |
+
BOX_WORDS.appendChild(liElement);
|
| 90 |
+
});
|
| 91 |
+
|
| 92 |
+
const removeButtons = BOX_WORDS.querySelectorAll("button.cts-remove-word");
|
| 93 |
+
removeButtons.forEach((btn) => {
|
| 94 |
+
btn.addEventListener("click", (e) => {
|
| 95 |
+
const idx = parseInt(btn.dataset.index);
|
| 96 |
+
if (!Number.isNaN(idx)) {
|
| 97 |
+
WORDS.splice(idx, 1);
|
| 98 |
+
renderWords();
|
| 99 |
+
}
|
| 100 |
+
});
|
| 101 |
+
});
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
function setupDescribeFormToAddWord() {
|
| 105 |
+
if (!FORM_DESCRIBE) return;
|
| 106 |
+
FORM_DESCRIBE.addEventListener("submit", (e) => {
|
| 107 |
+
e.preventDefault();
|
| 108 |
+
|
| 109 |
+
if (!FORM_DESCRIBE.reportValidity()) {
|
| 110 |
+
return;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
const input = FORM_DESCRIBE.querySelector('input[type="text"]');
|
| 114 |
+
if (!input) return;
|
| 115 |
+
|
| 116 |
+
const value = input.value.trim();
|
| 117 |
+
if (!value) return;
|
| 118 |
+
|
| 119 |
+
if (WORDS.includes(value)) {
|
| 120 |
+
spanNotifaction("Esa palabra ya está en la lista");
|
| 121 |
+
return;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
WORDS.push(value);
|
| 125 |
+
renderWords();
|
| 126 |
+
|
| 127 |
+
input.value = "";
|
| 128 |
+
input.focus();
|
| 129 |
+
});
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
function spanNotifaction(messageError, isError = true) {
|
| 133 |
+
const span = document.createElement("span");
|
| 134 |
+
span.textContent = messageError;
|
| 135 |
+
|
| 136 |
+
const div = document.createElement("div");
|
| 137 |
+
div.classList.add("alert", isError ? "alert-error" : "alert-success");
|
| 138 |
+
div.appendChild(span);
|
| 139 |
+
|
| 140 |
+
ERROR_INPUT_WORD.append(div);
|
| 141 |
+
|
| 142 |
+
setTimeout(() => {
|
| 143 |
+
ERROR_INPUT_WORD.removeChild(div);
|
| 144 |
+
}, 3000);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
async function sendWordsToSave() {
|
| 148 |
+
if (!WORDS.length) {
|
| 149 |
+
spanNotifaction("Debe existir al menos una palabra en la lista");
|
| 150 |
+
return false;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
const currentPhase = parseInt(
|
| 154 |
+
document.querySelector(".cts-phase-pf").dataset.phase
|
| 155 |
+
);
|
| 156 |
+
|
| 157 |
+
const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
| 158 |
+
|
| 159 |
+
const requestData = {
|
| 160 |
+
phase: currentPhase,
|
| 161 |
+
words: WORDS,
|
| 162 |
+
};
|
| 163 |
+
|
| 164 |
+
const URL = "/cata/testers/api/ratingword/pf/list";
|
| 165 |
+
|
| 166 |
+
try {
|
| 167 |
+
const response = await fetch(URL, {
|
| 168 |
+
method: "POST",
|
| 169 |
+
headers: {
|
| 170 |
+
"Content-Type": "application/json",
|
| 171 |
+
"X-CSRFToken": csrfToken,
|
| 172 |
+
"X-Requested-With": "XMLHttpRequest",
|
| 173 |
+
},
|
| 174 |
+
body: JSON.stringify(requestData),
|
| 175 |
+
});
|
| 176 |
+
|
| 177 |
+
if (!response.ok) {
|
| 178 |
+
spanNotifaction("Fallo con la respuesta recibida");
|
| 179 |
+
return false;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
const result = await response.json();
|
| 183 |
+
|
| 184 |
+
const messError = result.error;
|
| 185 |
+
|
| 186 |
+
if (messError) {
|
| 187 |
+
spanNotifaction(messError);
|
| 188 |
+
return false;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
spanNotifaction(result.message, false);
|
| 192 |
+
const addedWords = result.words;
|
| 193 |
+
WORDS.length = 0;
|
| 194 |
+
addedWords.forEach((word) => WORDS.push(word));
|
| 195 |
+
renderWords();
|
| 196 |
+
return true;
|
| 197 |
+
} catch (err) {
|
| 198 |
+
console.error(err);
|
| 199 |
+
spanNotifaction("Error en la respuesta del servidor");
|
| 200 |
+
return false;
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
async function setUpFormAction() {
|
| 205 |
+
const saveWords = await sendWordsToSave();
|
| 206 |
+
if (!saveWords) {
|
| 207 |
+
return false;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
const input = FORM_ACTION.querySelector(".action-input");
|
| 211 |
+
FORM_ACTION.action = "";
|
| 212 |
+
input.value = "finish_session";
|
| 213 |
+
FORM_ACTION.submit();
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
window.addEventListener("DOMContentLoaded", async () => {
|
| 217 |
+
initWordsFromBox();
|
| 218 |
+
setupDescribeFormToAddWord();
|
| 219 |
+
|
| 220 |
+
const currentPhase = parseInt(
|
| 221 |
+
document.querySelector(".cts-phase-pf").dataset.phase
|
| 222 |
+
);
|
| 223 |
+
|
| 224 |
+
if (currentPhase == 2) await sendWordsToSave();
|
| 225 |
+
if (document.querySelector(".cts-content-list-words"))
|
| 226 |
+
await getListWordsTesters();
|
| 227 |
+
});
|
tecnicas/static/js/test-cata.js
CHANGED
|
@@ -100,8 +100,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 100 |
const result = await response.json();
|
| 101 |
const messError = result.error;
|
| 102 |
|
| 103 |
-
console.log(messError);
|
| 104 |
-
|
| 105 |
if (messError) {
|
| 106 |
message.textContent = messError;
|
| 107 |
return;
|
|
|
|
| 100 |
const result = await response.json();
|
| 101 |
const messError = result.error;
|
| 102 |
|
|
|
|
|
|
|
| 103 |
if (messError) {
|
| 104 |
message.textContent = messError;
|
| 105 |
return;
|
tecnicas/templates/tecnicas/components/form-scale-continue.html
CHANGED
|
@@ -6,10 +6,11 @@
|
|
| 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="
|
|
|
|
| 13 |
class="range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0]"
|
| 14 |
style="width: {{scale.longitud}}cm;">
|
| 15 |
|
|
|
|
| 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="{{ size_scale.max_size }}"
|
| 13 |
+
value="{{ size_scale.middle_size }}" name="rating-word"
|
| 14 |
class="range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0]"
|
| 15 |
style="width: {{scale.longitud}}cm;">
|
| 16 |
|
tecnicas/templates/tecnicas/components/item-list-words.html
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div class="cts-item-list-tester bg-surface-sweet px-4 pt-2 pb-3 rounded shrink-0 w-64 space-y-2">
|
| 2 |
+
<p class="bg-surface-card text-lg font-semibold text-center rounded">{{ list_tester.username }}</p>
|
| 3 |
+
<ul class="text-center grid grid-cols-2 gap-2 w-full">
|
| 4 |
+
{% for word in list_tester.words %}
|
| 5 |
+
<li class="bg-surface-ligt rounded font-bold py-1 px-2 truncate">{{ word.nombre_palabra }}</li>
|
| 6 |
+
{% endfor %}
|
| 7 |
+
</ul>
|
| 8 |
+
</div>
|
tecnicas/templates/tecnicas/components/table_pf.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% load static %}
|
| 2 |
+
|
| 3 |
+
<section data-tester="{{ data.tester }}" {% if session_name %} data-session-name="{{ session_name }}" {% endif %}
|
| 4 |
+
data-session-code="{{ session_code }}">
|
| 5 |
+
<h3 class="text-xl font-bold">Datos de usuario {{ data.tester }}</h3>
|
| 6 |
+
|
| 7 |
+
<div class="overflow-x-auto rounded-lg border border-surface-general">
|
| 8 |
+
<table class="convencional-table min-w-max w-full text-sm text-center border-collapse">
|
| 9 |
+
<thead class="bg-surface-sweet text-black font-semibold">
|
| 10 |
+
<tr>
|
| 11 |
+
<th class="py-2 px-3 border border-surface-general">Repetición</th>
|
| 12 |
+
<th class="py-2 px-3 border border-surface-general">Producto</th>
|
| 13 |
+
{% for word in data.words %}
|
| 14 |
+
<th class="py-2 px-3 border border-surface-general uppercase">{{ word.nombre_palabra }}</th>
|
| 15 |
+
{% endfor %}
|
| 16 |
+
</tr>
|
| 17 |
+
</thead>
|
| 18 |
+
<tbody class="bg-surface-ligt divide-y divide-gray-200">
|
| 19 |
+
{% for repeticion, data_product in data.ratings.items %}
|
| 20 |
+
{% for codigo, valores in data_product.items %}
|
| 21 |
+
<tr>
|
| 22 |
+
<td class="py-2 px-3 border border-surface-general">{{ repeticion }}</td>
|
| 23 |
+
<td class="py-2 px-3 border border-surface-general">{{ codigo }}</td>
|
| 24 |
+
{% for word in data.words %}
|
| 25 |
+
<td class="py-2 px-3 border border-surface-general">
|
| 26 |
+
{% for valor in valores %}
|
| 27 |
+
{% if valor.nombre_palabra == word.nombre_palabra %}
|
| 28 |
+
{{ valor.dato_valor }}
|
| 29 |
+
{% endif %}
|
| 30 |
+
{% endfor %}
|
| 31 |
+
</td>
|
| 32 |
+
{% endfor %}
|
| 33 |
+
</tr>
|
| 34 |
+
{% endfor %}
|
| 35 |
+
{% endfor %}
|
| 36 |
+
</tbody>
|
| 37 |
+
</table>
|
| 38 |
+
</div>
|
| 39 |
+
</section>
|
tecnicas/templates/tecnicas/forms_tester/convencional.html
CHANGED
|
@@ -104,7 +104,7 @@
|
|
| 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
|
| 108 |
{% endfor %}
|
| 109 |
{% elif type_scale == "estructurada" %}
|
| 110 |
{% for word in words %}
|
|
|
|
| 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 size_scale=size_scale %}
|
| 108 |
{% endfor %}
|
| 109 |
{% elif type_scale == "estructurada" %}
|
| 110 |
{% for word in words %}
|
tecnicas/templates/tecnicas/forms_tester/init_session_pf.html
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'tecnicas/layouts/base.html' %}
|
| 2 |
+
{% load static %}
|
| 3 |
+
|
| 4 |
+
{% block title %}Detalles Sesion{% endblock %}
|
| 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
|
| 12 |
+
</h1>
|
| 13 |
+
</header>
|
| 14 |
+
|
| 15 |
+
<article>
|
| 16 |
+
<p class="text-2xl font-medium text-center bg-surface-sweet p-4 rounded-lg">
|
| 17 |
+
Información sobre la sesión en la que participa
|
| 18 |
+
</p>
|
| 19 |
+
</article>
|
| 20 |
+
|
| 21 |
+
{% if error %}
|
| 22 |
+
{% include "../components/error-message.html" with message=error %}
|
| 23 |
+
{% endif %}
|
| 24 |
+
{% if message %}
|
| 25 |
+
{% include "../components/error-message.html" with message=message %}
|
| 26 |
+
{% endif %}
|
| 27 |
+
<hr>
|
| 28 |
+
|
| 29 |
+
<article
|
| 30 |
+
class="rounded-xl grid grid-cols-1 gap-3 text-center *:bg-surface-card *:flex *:flex-wrap *:items-center *:justify-center *:gap-x-2 *:p-4 *:rounded-2xl">
|
| 31 |
+
<section>
|
| 32 |
+
<p class="text-xl font-bold">
|
| 33 |
+
Código:
|
| 34 |
+
</p>
|
| 35 |
+
<p class="font-sans text-xl font-normal">
|
| 36 |
+
{{ session.codigo_sesion }}
|
| 37 |
+
</p>
|
| 38 |
+
</section>
|
| 39 |
+
|
| 40 |
+
<section>
|
| 41 |
+
<p class="text-xl font-bold">
|
| 42 |
+
{% if session.nombre_sesion %}
|
| 43 |
+
{{ session.nombre_sesion }}
|
| 44 |
+
{% else %}
|
| 45 |
+
Sin nombre
|
| 46 |
+
{% endif %}
|
| 47 |
+
</p>
|
| 48 |
+
</section>
|
| 49 |
+
|
| 50 |
+
<section>
|
| 51 |
+
<p class="text-xl font-medium">
|
| 52 |
+
Esta sesión usa la técnica <span class="uppercase">{{ session.tecnica.tipo_tecnica }}</span>
|
| 53 |
+
</p>
|
| 54 |
+
</section>
|
| 55 |
+
|
| 56 |
+
<section>
|
| 57 |
+
<p class="text-xl font-medium">
|
| 58 |
+
<b>Actividad:</b> {{ activity }}
|
| 59 |
+
</p>
|
| 60 |
+
</section>
|
| 61 |
+
|
| 62 |
+
{% if repetition >= 1 %}
|
| 63 |
+
<section>
|
| 64 |
+
<p class="text-xl font-medium">
|
| 65 |
+
Repetición de la calificación: {{ repetition }}
|
| 66 |
+
</p>
|
| 67 |
+
</section>
|
| 68 |
+
{% endif %}
|
| 69 |
+
|
| 70 |
+
<section>
|
| 71 |
+
<p class="text-xl">
|
| 72 |
+
{{ session.tecnica.instrucciones }}
|
| 73 |
+
</p>
|
| 74 |
+
</section>
|
| 75 |
+
</article>
|
| 76 |
+
|
| 77 |
+
<hr>
|
| 78 |
+
|
| 79 |
+
<article class="flex flex-wrap gap-10">
|
| 80 |
+
{% if has_ended %}
|
| 81 |
+
{% if repetition >= 1 %}
|
| 82 |
+
<div
|
| 83 |
+
class="text-2xl font-semibold flex-1 cts-btn-secondary p-4 flex justify-center items-center rounded-lg select-none">
|
| 84 |
+
<p class=" text-black">Finalizaste la repetición</p>
|
| 85 |
+
</div>
|
| 86 |
+
{% else %}
|
| 87 |
+
<div
|
| 88 |
+
class="text-2xl font-semibold flex-1 cts-btn-secondary p-4 flex justify-center items-center rounded-lg select-none">
|
| 89 |
+
<p class=" text-black">Finalizaste la actividad</p>
|
| 90 |
+
</div>
|
| 91 |
+
{% endif %}
|
| 92 |
+
{% else %}
|
| 93 |
+
<button
|
| 94 |
+
class="ct-btn-start-repition flex-1 uppercase text-lg tracking-wider cts-btn-general cts-btn-primary btn-push flex flex-col justify-center items-center gap-2"
|
| 95 |
+
onclick="startTest()">
|
| 96 |
+
Entrar en fase o repetición
|
| 97 |
+
<figure class="w-10">
|
| 98 |
+
<img src="{% static 'img/check.svg' %}" alt="flechas girando" class="invert">
|
| 99 |
+
</figure>
|
| 100 |
+
</button>
|
| 101 |
+
{% endif %}
|
| 102 |
+
<a href="{% url 'cata_system:catador_list_sessions' num_page=1 %}">
|
| 103 |
+
<button
|
| 104 |
+
class="flex-1 uppercase text-lg tracking-wider cts-btn-general cts-btn-error btn-push flex flex-col justify-center items-center gap-2">
|
| 105 |
+
Regresar a la lista
|
| 106 |
+
<figure class="w-10">
|
| 107 |
+
<img src="{% static 'img/exit.svg' %}" alt="bote de basura" class="invert">
|
| 108 |
+
</figure>
|
| 109 |
+
</button>
|
| 110 |
+
</a>
|
| 111 |
+
</article>
|
| 112 |
+
|
| 113 |
+
<form action="" method="post" class="hidden ct-action-form">
|
| 114 |
+
{% csrf_token %}
|
| 115 |
+
<input type="hidden" class="action-option" name="action" value="start_posting">
|
| 116 |
+
</form>
|
| 117 |
+
</article>
|
| 118 |
+
</article>
|
| 119 |
+
{% endblock %}
|
| 120 |
+
|
| 121 |
+
{% block extra_js %}
|
| 122 |
+
<script src="{% static 'js/start-tester-test.js' %}"></script>
|
| 123 |
+
{% endblock %}
|
tecnicas/templates/tecnicas/forms_tester/test_pf_list_words.html
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 max-sm:flex-col 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 |
+
{% if not initial_phase %}
|
| 37 |
+
<div role="alert" class="alert alert-info">
|
| 38 |
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
| 39 |
+
class="h-6 w-6 shrink-0 stroke-current">
|
| 40 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
| 41 |
+
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
| 42 |
+
</svg>
|
| 43 |
+
<span class="text-lg">
|
| 44 |
+
Para poder compartir tu nueva lista de palabras con los demás Catadores, debes usar el botón de
|
| 45 |
+
<b>“Guardar y comprobar palabras actuales”</b>
|
| 46 |
+
</span>
|
| 47 |
+
</div>
|
| 48 |
+
{% endif %}
|
| 49 |
+
|
| 50 |
+
<article class="rounded flex flex-col gap-4">
|
| 51 |
+
<section class="flex items-center justify-center bg-surface-ligt p-2 rounded-lg">
|
| 52 |
+
<p class="text-lg font-medium text-center">
|
| 53 |
+
{{ session.tecnica.instrucciones }}
|
| 54 |
+
</p>
|
| 55 |
+
</section>
|
| 56 |
+
<section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
|
| 57 |
+
{% if initial_phase %}
|
| 58 |
+
<p class="text-xl font-bold text-center">
|
| 59 |
+
Fase <span class="cts-phase-pf" data-phase="1">1</span>:
|
| 60 |
+
</p>
|
| 61 |
+
<p class="text-xl font-bold text-center">
|
| 62 |
+
Lista inicial
|
| 63 |
+
</p>
|
| 64 |
+
{% else %}
|
| 65 |
+
<p class="text-xl font-bold text-center">
|
| 66 |
+
Fase <span class="cts-phase-pf" data-phase="2">2</span>:
|
| 67 |
+
</p>
|
| 68 |
+
<p class="text-xl font-bold text-center">
|
| 69 |
+
Lista Final
|
| 70 |
+
</p>
|
| 71 |
+
{% endif %}
|
| 72 |
+
</section>
|
| 73 |
+
</article>
|
| 74 |
+
|
| 75 |
+
<div class="error-input-word toast toast-bottom toast-center"></div>
|
| 76 |
+
|
| 77 |
+
<section class="cts-list-words space-y-4">
|
| 78 |
+
<article class="flex gap-4 flex-wrap">
|
| 79 |
+
<section class="bg-surface-card p-4 rounded-lg text-center space-y-4 sm:w-[300px] w-full flex-shrink-0">
|
| 80 |
+
<h3 class="text-xl font-bold border-b-1">Describe el producto</h3>
|
| 81 |
+
|
| 82 |
+
<form action="" method="get" class="cts-form-pf-word flex gap-4 flex-col">
|
| 83 |
+
<label for="{{ form.nombre_palabra.id_for_label }}" class="text-left">
|
| 84 |
+
<p class="text-lg font-semibold">
|
| 85 |
+
{{ form.nombre_palabra.label }}:
|
| 86 |
+
</p>
|
| 87 |
+
{{ form.nombre_palabra }}
|
| 88 |
+
<small>
|
| 89 |
+
Solo letras minúsculas y la letra ñ, no poner acentos. No se permiten mayúsculas ni
|
| 90 |
+
caracteres especiales. Mínimo 3 letras
|
| 91 |
+
</small>
|
| 92 |
+
</label>
|
| 93 |
+
<button type="submit" class="cts-btn-general-compress cts-btn-primary btn-push w-full py-2">
|
| 94 |
+
Agregar
|
| 95 |
+
</button>
|
| 96 |
+
</form>
|
| 97 |
+
</section>
|
| 98 |
+
|
| 99 |
+
<section
|
| 100 |
+
class="bg-surface-card p-4 rounded-lg text-center flex-1 h-[400px] min-h-[400px] max-h-[600px] flex-shrink-0 overflow-y-auto">
|
| 101 |
+
<div>
|
| 102 |
+
<p class="text-xl font-bold pb-3">Lista de palabras</p>
|
| 103 |
+
<hr>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
<ul class="cts-box-words flex flex-col gap-3 overflow-y-auto mt-4">
|
| 107 |
+
{% if not words %}
|
| 108 |
+
<img src="{% static 'img/list.svg' %}" alt="lista de palabras" class="cts-img-list h-[300px]">
|
| 109 |
+
{% endif %}
|
| 110 |
+
|
| 111 |
+
{% for word in words %}
|
| 112 |
+
<img src="{% static 'img/list.svg' %}" alt="lista de palabras"
|
| 113 |
+
class="cts-img-list h-[300px] hidden">
|
| 114 |
+
|
| 115 |
+
<li class="cts-item-words bg-gray-400 text-black rounded font-bold text-lg
|
| 116 |
+
px-4 py-3 flex flex-wrap flex-row min-w-fit justify-center items-center gap-3">
|
| 117 |
+
<p class="ct-word-received">{{ word.nombre_palabra }}</p>
|
| 118 |
+
|
| 119 |
+
<button class="px-4 border-b-2 active:border-b-0 active:border-t-2
|
| 120 |
+
transition-all rounded-xl font-black capitalize
|
| 121 |
+
active:border-red-500 border-red-800 bg-red-500">
|
| 122 |
+
➖
|
| 123 |
+
</button>
|
| 124 |
+
</li>
|
| 125 |
+
{% endfor %}
|
| 126 |
+
</ul>
|
| 127 |
+
</section>
|
| 128 |
+
</article>
|
| 129 |
+
</section>
|
| 130 |
+
|
| 131 |
+
<article class="flex max-sm:flex-col gap-4">
|
| 132 |
+
<button type="button" class="cts-btn-general-compress cts-btn-primary btn-push py-2 px-4 flex-1"
|
| 133 |
+
onclick="sendWordsToSave()">
|
| 134 |
+
Guardar y comprobar palabras actuales
|
| 135 |
+
</button>
|
| 136 |
+
|
| 137 |
+
<button type="button" class="cts-btn-general cts-btn-secondary btn-push py-2 px-4 flex-1"
|
| 138 |
+
onclick="setUpFormAction()">
|
| 139 |
+
Finalizar la sesión
|
| 140 |
+
</button>
|
| 141 |
+
</article>
|
| 142 |
+
|
| 143 |
+
<div role="alert" class="alert alert-warning">
|
| 144 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
|
| 145 |
+
viewBox="0 0 24 24">
|
| 146 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
| 147 |
+
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" />
|
| 148 |
+
</svg>
|
| 149 |
+
<div class="flex-col">
|
| 150 |
+
<span class="text-lg block">
|
| 151 |
+
Si usas el botón <b>“Salir de la sesión”</b> asegúrese de guardar las palabras de lo contrario las
|
| 152 |
+
palabras no se guardaran pero podrás ingresar otra vez
|
| 153 |
+
</span>
|
| 154 |
+
<span class="text-lg block">
|
| 155 |
+
Con el botón <b>“Finalizar la sesión”</b>, se guardan las palabras, sales de la sesión y no podrás
|
| 156 |
+
ingresar otra vez
|
| 157 |
+
</span>
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
|
| 161 |
+
{% if not initial_phase %}
|
| 162 |
+
<section class="space-y-4">
|
| 163 |
+
<section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
|
| 164 |
+
<h3 class="text-xl font-bold text-center">
|
| 165 |
+
Listas de los demás participantes
|
| 166 |
+
</h3>
|
| 167 |
+
</section>
|
| 168 |
+
<article class="cts-content-list-words bg-surface-card flex gap-4 overflow-x-auto p-2"></article>
|
| 169 |
+
<article class="flex justify-center">
|
| 170 |
+
<button class="cts-btn-general cts-btn-primary btn-push" onclick="getListWordsTesters()">
|
| 171 |
+
Actualizar las listas
|
| 172 |
+
</button>
|
| 173 |
+
</article>
|
| 174 |
+
</section>
|
| 175 |
+
<script src="{% static 'js/lists-words-testers.js' %}"></script>
|
| 176 |
+
{% endif %}
|
| 177 |
+
</article>
|
| 178 |
+
</article>
|
| 179 |
+
{% endblock %}
|
| 180 |
+
|
| 181 |
+
{% block extra_js %}
|
| 182 |
+
<script src="{% static 'js/pf-make-list.js' %}"></script>
|
| 183 |
+
<script src="{% static 'js/actions-form.js' %}"></script>
|
| 184 |
+
{% endblock %}
|
tecnicas/templates/tecnicas/forms_tester/test_pf_rating_list.html
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'tecnicas/layouts/base.html' %}
|
| 2 |
+
|
| 3 |
+
{% load static %}
|
| 4 |
+
|
| 5 |
+
{% block title %}Convencional{% endblock %}
|
| 6 |
+
|
| 7 |
+
{% block extra_css %}
|
| 8 |
+
<style>
|
| 9 |
+
input[type="range"]::-webkit-slider-thumb {
|
| 10 |
+
-webkit-appearance: none;
|
| 11 |
+
appearance: none;
|
| 12 |
+
width: 24px;
|
| 13 |
+
height: 24px;
|
| 14 |
+
border-radius: 50%;
|
| 15 |
+
background: var(--color-blue-500);
|
| 16 |
+
cursor: pointer;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
input[type="range"]::-moz-range-thumb {
|
| 20 |
+
width: 24px;
|
| 21 |
+
height: 24px;
|
| 22 |
+
border-radius: 50%;
|
| 23 |
+
background: #3b82f6;
|
| 24 |
+
cursor: pointer;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
@media (width < 40rem) {
|
| 28 |
+
.container-forms {
|
| 29 |
+
width: inherit;
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
</style>
|
| 33 |
+
{% endblock %}
|
| 34 |
+
|
| 35 |
+
{% block content %}
|
| 36 |
+
<article class="cts-container-main">
|
| 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
|
| 45 |
+
</button>
|
| 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 |
+
{% include "../components/error-message.html" with message=error %}
|
| 62 |
+
{% endif %}
|
| 63 |
+
|
| 64 |
+
<article class="rounded flex flex-col gap-4">
|
| 65 |
+
<section class="flex items-center justify-center bg-surface-ligt p-2 rounded-lg">
|
| 66 |
+
<p class="text-lg font-medium text-center">
|
| 67 |
+
{{ session.tecnica.instrucciones }}
|
| 68 |
+
</p>
|
| 69 |
+
</section>
|
| 70 |
+
|
| 71 |
+
<section class="flex items-center justify-center flex-wrap gap-4 bg-surface-ligt p-2 rounded-lg">
|
| 72 |
+
<p class="text-xl font-bold text-center">
|
| 73 |
+
Fase <span class="cts-phase-pf" data-phase="3">3</span>:
|
| 74 |
+
</p>
|
| 75 |
+
<p class="text-xl font-bold text-center">
|
| 76 |
+
Calificación de listas
|
| 77 |
+
</p>
|
| 78 |
+
</section>
|
| 79 |
+
|
| 80 |
+
<section class="flex items-center justify-center flex-wrap gap-4">
|
| 81 |
+
<div class="bg-surface-ligt p-2 rounded-lg flex-1">
|
| 82 |
+
<p class="text-lg font-bold text-center">
|
| 83 |
+
Producto:
|
| 84 |
+
</p>
|
| 85 |
+
<p class="text-2xl font-bold text-center ct-product-rating">
|
| 86 |
+
<span class="code-product">{{ product }}</span>
|
| 87 |
+
<span class="hidden id-product">{{ product.id }}</span>
|
| 88 |
+
</p>
|
| 89 |
+
</div>
|
| 90 |
+
<div class="bg-surface-ligt p-2 rounded-lg flex-1">
|
| 91 |
+
<p class="text-lg font-bold text-center">
|
| 92 |
+
Repetición:
|
| 93 |
+
</p>
|
| 94 |
+
<p class="text-2xl font-bold text-center">
|
| 95 |
+
{{ repetition }}
|
| 96 |
+
</p>
|
| 97 |
+
</div>
|
| 98 |
+
</section>
|
| 99 |
+
|
| 100 |
+
<section class="flex flex-col bg-red-200 py-2 px-4 rounded-lg min-sm:hidden">
|
| 101 |
+
<p>
|
| 102 |
+
El largo de estas escalas puede afectar la vista en dispositivos móviles. Para ver el resto de la
|
| 103 |
+
escala puede deslizar con el dedo a la derecha o la izquierda sin tocar la escala.
|
| 104 |
+
</p>
|
| 105 |
+
</section>
|
| 106 |
+
</article>
|
| 107 |
+
|
| 108 |
+
<article
|
| 109 |
+
class="scales-container [&>*:not(:last-child)]:mb-5 min-lg:grid min-lg:items-start grid-cols-2 gap-3 justify-center items-center">
|
| 110 |
+
{% with path_con="../components/form-scale-continue.html" path_str="../components/form-scale-structure.html" %}
|
| 111 |
+
{% if type_scale == "continua" %}
|
| 112 |
+
{% for word in words %}
|
| 113 |
+
{% include path_con with word=word tags=tags scale=scale size_scale=size_scale %}
|
| 114 |
+
{% endfor %}
|
| 115 |
+
{% elif type_scale == "estructurada" %}
|
| 116 |
+
{% for word in words %}
|
| 117 |
+
{% include path_str with word=word tags=tags scale=scale %}
|
| 118 |
+
{% endfor %}
|
| 119 |
+
{% endif %}
|
| 120 |
+
{% endwith %}
|
| 121 |
+
</article>
|
| 122 |
+
</article>
|
| 123 |
+
</article>
|
| 124 |
+
{% endblock %}
|
| 125 |
+
|
| 126 |
+
{% block extra_js %}
|
| 127 |
+
<script src="{% static 'js/created-scale.js' %}"></script>
|
| 128 |
+
<script src="{% static 'js/actions-form.js' %}"></script>
|
| 129 |
+
{% endblock %}
|
tecnicas/templates/tecnicas/manage_sesions/details-session-pf.html
CHANGED
|
@@ -166,21 +166,72 @@
|
|
| 166 |
Datos obtenidos
|
| 167 |
</p>
|
| 168 |
|
| 169 |
-
<article class="bg-surface-card p-4 max-sm:px-2 text-black rounded">
|
| 170 |
-
<section class="
|
| 171 |
-
<
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
</section>
|
| 178 |
-
{% if existen_calificaciones %}
|
| 179 |
-
{% include "../components/table_cata.html" with calificaciones=calificaciones palabras=palabras
|
| 180 |
-
sesion=sesion %}
|
| 181 |
-
{% else %}
|
| 182 |
-
{% include "../components/error-message.html" with message='Sin calificaciones que mostrar aún' %}
|
| 183 |
-
{% endif %}
|
| 184 |
</article>
|
| 185 |
|
| 186 |
<form action="" method="post" class="form-action-session hidden">
|
|
@@ -192,6 +243,9 @@
|
|
| 192 |
{% endblock %}
|
| 193 |
|
| 194 |
{% block extra_js %}
|
|
|
|
|
|
|
|
|
|
| 195 |
<script src="{% static 'js/details-session.js' %}"></script>
|
| 196 |
<script src="{% static 'js/showHiddenElement.js' %}"></script>
|
| 197 |
{% endblock %}
|
|
|
|
| 166 |
Datos obtenidos
|
| 167 |
</p>
|
| 168 |
|
| 169 |
+
<article class="bg-surface-card p-4 max-sm:px-2 text-black rounded space-y-4">
|
| 170 |
+
<section class="">
|
| 171 |
+
<header class="text-center">
|
| 172 |
+
<h3 class="text-xl font-bold">Prmera fase</h3>
|
| 173 |
+
<p class="text-lg">Listas iniciales de Catadores</p>
|
| 174 |
+
</header>
|
| 175 |
+
|
| 176 |
+
{% if fisrt_phase %}
|
| 177 |
+
<article class="flex gap-4 overflow-x-auto py-2">
|
| 178 |
+
{% for list_tester in fisrt_phase %}
|
| 179 |
+
{% include "../components/item-list-words.html" with list_tester=list_tester %}
|
| 180 |
+
{% endfor %}
|
| 181 |
+
</article>
|
| 182 |
+
{% else %}
|
| 183 |
+
{% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
|
| 184 |
+
{% endif %}
|
| 185 |
+
</section>
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
<section class="">
|
| 189 |
+
<header class="text-center">
|
| 190 |
+
<h3 class="text-xl font-bold">Segunda fase</h3>
|
| 191 |
+
<p class="text-lg">Listas finales de Catadores</p>
|
| 192 |
+
</header>
|
| 193 |
+
|
| 194 |
+
{% if second_phase %}
|
| 195 |
+
<article class="flex gap-4 overflow-x-auto py-2">
|
| 196 |
+
{% for list_tester in second_phase %}
|
| 197 |
+
{% include "../components/item-list-words.html" with list_tester=list_tester %}
|
| 198 |
+
{% endfor %}
|
| 199 |
+
</article>
|
| 200 |
+
{% else %}
|
| 201 |
+
{% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
|
| 202 |
+
{% endif %}
|
| 203 |
+
</section>
|
| 204 |
+
|
| 205 |
+
<section class="">
|
| 206 |
+
<header class="text-center">
|
| 207 |
+
<h3 class="text-xl font-bold">Tercera fase</h3>
|
| 208 |
+
<p class="text-lg">Listas finales de Catadores</p>
|
| 209 |
+
</header>
|
| 210 |
+
|
| 211 |
+
<article class="text-xl space-y-4 space-x-4 mb-4">
|
| 212 |
+
<p class="inline italic underline font-semibold">
|
| 213 |
+
Se usa una escala <b>{{ tipo_escala }}</b> para calificar
|
| 214 |
+
</p>
|
| 215 |
+
<p class="inline italic underline font-semibold">
|
| 216 |
+
El valor máximo por calificación es <b>{{ valor_max }}</b>
|
| 217 |
+
</p>
|
| 218 |
+
</article>
|
| 219 |
+
|
| 220 |
+
{% if data_ratings %}
|
| 221 |
+
<article class="overflow-x-auto py-2 space-y-4">
|
| 222 |
+
{% for data_tester in data_ratings %}
|
| 223 |
+
{% include "../components/table_pf.html" with data=data_tester session_name=sesion.nombre_sesion session_code=sesion.codigo_sesion %}
|
| 224 |
+
{% endfor %}
|
| 225 |
+
</article>
|
| 226 |
+
<div class="flex justify-end mt-3">
|
| 227 |
+
<button id="download-csv-btn" class="cts-btn-general cts-btn-primary btn-push">
|
| 228 |
+
Descargar datos como CSV en zip
|
| 229 |
+
</button>
|
| 230 |
+
</div>
|
| 231 |
+
{% else %}
|
| 232 |
+
{% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
|
| 233 |
+
{% endif %}
|
| 234 |
</section>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
</article>
|
| 236 |
|
| 237 |
<form action="" method="post" class="form-action-session hidden">
|
|
|
|
| 243 |
{% endblock %}
|
| 244 |
|
| 245 |
{% block extra_js %}
|
| 246 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
| 247 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
| 248 |
+
<script src="{% static 'js/download-table-zip.js' %}"></script>
|
| 249 |
<script src="{% static 'js/details-session.js' %}"></script>
|
| 250 |
<script src="{% static 'js/showHiddenElement.js' %}"></script>
|
| 251 |
{% endblock %}
|
tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'tecnicas/layouts/base.html' %}
|
| 2 |
+
{% load static %}
|
| 3 |
+
|
| 4 |
+
{% block title %}Monitoreo{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<article class="cts-container-main">
|
| 8 |
+
<article class="cts-wrap-content">
|
| 9 |
+
<header class="text-center">
|
| 10 |
+
<h1 class="text-3xl font-bold text-black">Sesión sensorial en curso</h1>
|
| 11 |
+
</header>
|
| 12 |
+
|
| 13 |
+
<section class="flex flex-col sm:flex-row justify-between items-center gap-10">
|
| 14 |
+
<p class="text-xl text-center bg-surface-card p-4 text-black rounded-lg shadow-lg">
|
| 15 |
+
Código de sesión:<br>
|
| 16 |
+
<span class="font-mono text-2xl font-bold">{{ code_session }}</span>
|
| 17 |
+
</p>
|
| 18 |
+
<div class="flex flex-col gap-2">
|
| 19 |
+
<button class="uppercase cts-btn-general-compress cts-btn-secondary py-2 px-6 btn-push"
|
| 20 |
+
onclick="finishSession()">
|
| 21 |
+
Finalizar sesión
|
| 22 |
+
</button>
|
| 23 |
+
<a href="{% url 'cata_system:detalles_sesion' session_code=code_session %}" class="w-full">
|
| 24 |
+
<button class="uppercase cts-btn-general-compress cts-btn-error py-2 px-6 btn-push w-full">
|
| 25 |
+
Regresar
|
| 26 |
+
</button>
|
| 27 |
+
</a>
|
| 28 |
+
</div>
|
| 29 |
+
</section>
|
| 30 |
+
|
| 31 |
+
<form action="" method="post" class="hidden action-form">
|
| 32 |
+
<input type="hidden" name="action">
|
| 33 |
+
{% csrf_token %}
|
| 34 |
+
</form>
|
| 35 |
+
|
| 36 |
+
{% if error %}
|
| 37 |
+
{% include "../components/error-message.html" with message=error %}
|
| 38 |
+
{% endif %}
|
| 39 |
+
|
| 40 |
+
{% if message %}
|
| 41 |
+
{% include "../components/error-message.html" with message=message %}
|
| 42 |
+
{% endif %}
|
| 43 |
+
|
| 44 |
+
<section aria-labelledby="catadores-titulo">
|
| 45 |
+
<article
|
| 46 |
+
class="flex max-sm:flex-col justify-around bg-surface-card border border-gray-300 rounded-md p-3 mb-4 items-center shadow-lg">
|
| 47 |
+
<button class="uppercase cts-btn-general-compress cts-btn-tertiary py-2 px-6 btn-push"
|
| 48 |
+
onclick="reloadPage()">
|
| 49 |
+
Actualizar lista
|
| 50 |
+
</button>
|
| 51 |
+
<h2 id="catadores-titulo" class="text-2xl text-center font-bold rounded-lg text-gray-700 py-2 px-3">
|
| 52 |
+
Catadores activos
|
| 53 |
+
</h2>
|
| 54 |
+
</article>
|
| 55 |
+
|
| 56 |
+
<article
|
| 57 |
+
class="flex flex-col justify-center gap-3 bg-surface-card border border-gray-300 rounded-md p-3 mb-4 text-lg max-sm:text-sm text-gray-700 shadow-lg">
|
| 58 |
+
<div class="flex justify-center items-center">
|
| 59 |
+
<h2 class="text-xl font-semibold">Participantes</h2>
|
| 60 |
+
</div>
|
| 61 |
+
<section class="flex flex-wrap justify-around items-center">
|
| 62 |
+
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
| 63 |
+
<span class="font-semibold">Máximo:</span>
|
| 64 |
+
<span>{{ max_testers }}</span>
|
| 65 |
+
</div>
|
| 66 |
+
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
| 67 |
+
<span class="font-semibold">
|
| 68 |
+
Actuales:
|
| 69 |
+
</span>
|
| 70 |
+
<span>{{ current_testers }}</span>
|
| 71 |
+
</div>
|
| 72 |
+
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-2">
|
| 73 |
+
<span class="font-semibold">Activos:</span>
|
| 74 |
+
<span class="text-green-600 font-semibold">{{ active_testers }}</span>
|
| 75 |
+
</div>
|
| 76 |
+
</section>
|
| 77 |
+
</article>
|
| 78 |
+
|
| 79 |
+
<article class="max-sm:overflow-x-auto shadow-lg">
|
| 80 |
+
<div class="min-w-[400px] sm:min-w-0">
|
| 81 |
+
<div
|
| 82 |
+
class="grid grid-cols-4 bg-surface-sweet text-center font-semibold text-black text-sm rounded-t-lg max-sm:w-full [&_>*]:px-2">
|
| 83 |
+
<div class="py-2 border-r border-gray-400">Usuario</div>
|
| 84 |
+
<div class="py-2 border-r border-gray-400">Nombre</div>
|
| 85 |
+
<div class="py-2 border-r border-gray-400">Estado</div>
|
| 86 |
+
<div class="py-2">Finalizado</div>
|
| 87 |
+
</div>
|
| 88 |
+
|
| 89 |
+
<ul class="divide-y divide-gray-400 max-sm:w-full text-black">
|
| 90 |
+
{% for parti in participations %}
|
| 91 |
+
<li class="grid grid-cols-4 text-center bg-surface-card py-2 [&_>*]:px-2">
|
| 92 |
+
<p class="border-r border-gray-400">{{ parti.catador.user.username }}</p>
|
| 93 |
+
<p class="border-r border-gray-400">
|
| 94 |
+
{{ parti.catador.user.first_name }}
|
| 95 |
+
{{ parti.catador.user.last_name}}
|
| 96 |
+
</p>
|
| 97 |
+
|
| 98 |
+
{% if parti.activo %}
|
| 99 |
+
<p class="border-r border-gray-400 text-green-600 font-semibold">Activo</p>
|
| 100 |
+
{% else %}
|
| 101 |
+
<p class="border-r border-gray-400 text-red-500 font-semibold">No activo</p>
|
| 102 |
+
{% endif %}
|
| 103 |
+
|
| 104 |
+
{% if parti.finalizado %}
|
| 105 |
+
<p class="text-green-600 font-semibold">Si</p>
|
| 106 |
+
{% else %}
|
| 107 |
+
<p class="text-red-500 font-semibold">No</p>
|
| 108 |
+
{% endif %}
|
| 109 |
+
</li>
|
| 110 |
+
{% endfor %}
|
| 111 |
+
</ul>
|
| 112 |
+
</div>
|
| 113 |
+
</article>
|
| 114 |
+
</section>
|
| 115 |
+
</article>
|
| 116 |
+
</article>
|
| 117 |
+
{% endblock %}
|
| 118 |
+
|
| 119 |
+
{% block extra_js %}
|
| 120 |
+
<script>
|
| 121 |
+
function reloadPage () {
|
| 122 |
+
location.reload()
|
| 123 |
+
}
|
| 124 |
+
</script>
|
| 125 |
+
<script src="{% static 'js/finish-session.js' %}"></script>
|
| 126 |
+
{% endblock %}
|
tecnicas/urls.py
CHANGED
|
@@ -114,6 +114,10 @@ urlpatterns = [
|
|
| 114 |
views.cataTest,
|
| 115 |
name="session_cata"),
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
# APIs
|
| 119 |
path("presenter/api/nueva-etiqueta",
|
|
@@ -135,4 +139,8 @@ urlpatterns = [
|
|
| 135 |
path("testers/api/ratingword/cata",
|
| 136 |
views.ratingWordCata,
|
| 137 |
name="api_rating_word_cata"),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
]
|
|
|
|
| 114 |
views.cataTest,
|
| 115 |
name="session_cata"),
|
| 116 |
|
| 117 |
+
path("testers/init-session/<str:code_sesion>/perfil-flash",
|
| 118 |
+
views.pfTest,
|
| 119 |
+
name="session_pf"),
|
| 120 |
+
|
| 121 |
|
| 122 |
# APIs
|
| 123 |
path("presenter/api/nueva-etiqueta",
|
|
|
|
| 139 |
path("testers/api/ratingword/cata",
|
| 140 |
views.ratingWordCata,
|
| 141 |
name="api_rating_word_cata"),
|
| 142 |
+
|
| 143 |
+
path("testers/api/ratingword/pf/list",
|
| 144 |
+
views.apiListWordsPF,
|
| 145 |
+
name="api_rating_word_pf_list"),
|
| 146 |
]
|
tecnicas/views/__init__.py
CHANGED
|
@@ -25,6 +25,7 @@ from .vocabulary_management.list_vocabulary import listVocabulary
|
|
| 25 |
from .apis.api_tag import newTag
|
| 26 |
from .apis.api_words import words
|
| 27 |
from .apis.api_words import wordsVocabulary
|
|
|
|
| 28 |
from .apis.rating_word_scales import ratingWordScales
|
| 29 |
from .apis.rating_word_cata import ratingWordCata
|
| 30 |
|
|
@@ -34,3 +35,4 @@ from .tester_forms.login_session import loginSessionTester
|
|
| 34 |
from .tester_forms.sessions_list_tester import sessionsListTester
|
| 35 |
from .tester_forms.convencional_scales import convencionalScales
|
| 36 |
from .tester_forms.cata_test import cataTest
|
|
|
|
|
|
| 25 |
from .apis.api_tag import newTag
|
| 26 |
from .apis.api_words import words
|
| 27 |
from .apis.api_words import wordsVocabulary
|
| 28 |
+
from .apis.api_list_words_pf import apiListWordsPF
|
| 29 |
from .apis.rating_word_scales import ratingWordScales
|
| 30 |
from .apis.rating_word_cata import ratingWordCata
|
| 31 |
|
|
|
|
| 35 |
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
|
tecnicas/views/apis/api_list_words_pf.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest, JsonResponse
|
| 2 |
+
from tecnicas.utils import general_error
|
| 3 |
+
from tecnicas.controllers import RatingPFListController
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def apiListWordsPF(req: HttpRequest):
|
| 8 |
+
if req.method == "GET":
|
| 9 |
+
return RatingPFListController.getListWords(request=req)
|
| 10 |
+
elif req.method == "POST":
|
| 11 |
+
try:
|
| 12 |
+
data = json.loads(req.body.decode("utf-8"))
|
| 13 |
+
raw_words = data.get("words", [])
|
| 14 |
+
phase = data.get("phase", [])
|
| 15 |
+
|
| 16 |
+
response = RatingPFListController.saveList(
|
| 17 |
+
request=req, current_phase=phase, words=raw_words)
|
| 18 |
+
return response
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print("Error:", e)
|
| 21 |
+
return JsonResponse({"error": "Error procesando datos"}, status=400)
|
| 22 |
+
else:
|
| 23 |
+
return JsonResponse({"error": "Método no permitido"}, status=405)
|
tecnicas/views/apis/rating_word_cata.py
CHANGED
|
@@ -14,10 +14,8 @@ def ratingWordCata(req: HttpRequest):
|
|
| 14 |
response = RatingCataController.saveRatingWords(
|
| 15 |
request=req, data_words=raw_words, data_prodct=raw_product)
|
| 16 |
return response
|
| 17 |
-
return JsonResponse({"message": "Error procesando datos"})
|
| 18 |
except Exception as e:
|
| 19 |
print("Error:", e)
|
| 20 |
return JsonResponse({"error": "Error procesando datos"}, status=400)
|
| 21 |
-
|
| 22 |
else:
|
| 23 |
return JsonResponse({"error": "Método no permitido"}, status=405)
|
|
|
|
| 14 |
response = RatingCataController.saveRatingWords(
|
| 15 |
request=req, data_words=raw_words, data_prodct=raw_product)
|
| 16 |
return response
|
|
|
|
| 17 |
except Exception as e:
|
| 18 |
print("Error:", e)
|
| 19 |
return JsonResponse({"error": "Error procesando datos"}, status=400)
|
|
|
|
| 20 |
else:
|
| 21 |
return JsonResponse({"error": "Método no permitido"}, status=405)
|
tecnicas/views/sessions_management/session_details.py
CHANGED
|
@@ -55,16 +55,21 @@ def sessionDetails(req: HttpRequest, session_code: str):
|
|
| 55 |
if req.POST["action"] == "start_session":
|
| 56 |
response = controller_view.startRepetition(
|
| 57 |
presenter=req.user.user_presentador, request=req)
|
|
|
|
| 58 |
elif req.POST.get("action") == "delete_session":
|
| 59 |
controller_view.deleteSesorialSession()
|
| 60 |
response = redirect(
|
| 61 |
reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
|
|
|
|
| 62 |
else:
|
| 63 |
response = controller_view.getResponse(
|
| 64 |
error="No se reconoce la acción a realizar")
|
|
|
|
| 65 |
elif use_techinique == "perfil flash":
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
| 68 |
else:
|
| 69 |
response = noValidTechnique()
|
| 70 |
|
|
|
|
| 55 |
if req.POST["action"] == "start_session":
|
| 56 |
response = controller_view.startRepetition(
|
| 57 |
presenter=req.user.user_presentador, request=req)
|
| 58 |
+
|
| 59 |
elif req.POST.get("action") == "delete_session":
|
| 60 |
controller_view.deleteSesorialSession()
|
| 61 |
response = redirect(
|
| 62 |
reverse("cata_system:panel_sesiones", kwargs={"page": 1}))
|
| 63 |
+
|
| 64 |
else:
|
| 65 |
response = controller_view.getResponse(
|
| 66 |
error="No se reconoce la acción a realizar")
|
| 67 |
+
|
| 68 |
elif use_techinique == "perfil flash":
|
| 69 |
+
controller_view = DetallesPFController(session=sensorial_session)
|
| 70 |
+
response = controller_view.startRepetition(
|
| 71 |
+
presenter=req.user.user_presentador, request=req)
|
| 72 |
+
|
| 73 |
else:
|
| 74 |
response = noValidTechnique()
|
| 75 |
|
tecnicas/views/sessions_management/session_monitor.py
CHANGED
|
@@ -3,12 +3,11 @@ Para finalizar la sesion se debe realizar lo siguiente
|
|
| 3 |
# Obtener todas las participaciones
|
| 4 |
|
| 5 |
'''
|
| 6 |
-
|
| 7 |
from django.http import HttpRequest, JsonResponse
|
| 8 |
from django.shortcuts import render, redirect
|
| 9 |
from django.urls import reverse
|
| 10 |
from tecnicas.models import SesionSensorial
|
| 11 |
-
from tecnicas.controllers import MonitorEscalasController, MonitorRATAController
|
| 12 |
from tecnicas.utils import noValidTechnique
|
| 13 |
|
| 14 |
|
|
@@ -21,6 +20,11 @@ def sessionMonitor(req: HttpRequest, session_code: str):
|
|
| 21 |
if use_techinique == "escalas" or use_techinique == "rata" or use_techinique == "cata":
|
| 22 |
controll_view = MonitorEscalasController(sensorial_session)
|
| 23 |
response = controll_view.controllGetResponse(request=req)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
else:
|
| 25 |
response = noValidTechnique(
|
| 26 |
params={
|
|
@@ -47,6 +51,7 @@ def sessionMonitor(req: HttpRequest, session_code: str):
|
|
| 47 |
else:
|
| 48 |
response = controll_view.controlGetResponse(
|
| 49 |
request=req, error="No se ha definido la acción a realizar")
|
|
|
|
| 50 |
elif use_techinique == "rata" or use_techinique == "cata":
|
| 51 |
controll_view = MonitorRATAController(sensorial_session)
|
| 52 |
action = req.POST["action"]
|
|
@@ -57,6 +62,18 @@ def sessionMonitor(req: HttpRequest, session_code: str):
|
|
| 57 |
else:
|
| 58 |
response = controll_view.controlGetResponse(
|
| 59 |
request=req, error="No se ha definido la acción a realizar")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
else:
|
| 61 |
response = noValidTechnique(
|
| 62 |
params={
|
|
|
|
| 3 |
# Obtener todas las participaciones
|
| 4 |
|
| 5 |
'''
|
|
|
|
| 6 |
from django.http import HttpRequest, JsonResponse
|
| 7 |
from django.shortcuts import render, redirect
|
| 8 |
from django.urls import reverse
|
| 9 |
from tecnicas.models import SesionSensorial
|
| 10 |
+
from tecnicas.controllers import MonitorEscalasController, MonitorRATAController, MonitorPFController
|
| 11 |
from tecnicas.utils import noValidTechnique
|
| 12 |
|
| 13 |
|
|
|
|
| 20 |
if use_techinique == "escalas" or use_techinique == "rata" or use_techinique == "cata":
|
| 21 |
controll_view = MonitorEscalasController(sensorial_session)
|
| 22 |
response = controll_view.controllGetResponse(request=req)
|
| 23 |
+
|
| 24 |
+
elif use_techinique == "perfil flash":
|
| 25 |
+
controll_view = MonitorPFController(sensorial_session)
|
| 26 |
+
response = controll_view.controllGetResponse(request=req)
|
| 27 |
+
|
| 28 |
else:
|
| 29 |
response = noValidTechnique(
|
| 30 |
params={
|
|
|
|
| 51 |
else:
|
| 52 |
response = controll_view.controlGetResponse(
|
| 53 |
request=req, error="No se ha definido la acción a realizar")
|
| 54 |
+
|
| 55 |
elif use_techinique == "rata" or use_techinique == "cata":
|
| 56 |
controll_view = MonitorRATAController(sensorial_session)
|
| 57 |
action = req.POST["action"]
|
|
|
|
| 62 |
else:
|
| 63 |
response = controll_view.controlGetResponse(
|
| 64 |
request=req, error="No se ha definido la acción a realizar")
|
| 65 |
+
|
| 66 |
+
elif use_techinique == "perfil flash":
|
| 67 |
+
controll_view = MonitorPFController(sensorial_session)
|
| 68 |
+
action = req.POST["action"]
|
| 69 |
+
|
| 70 |
+
if action == "finish_session":
|
| 71 |
+
response = controll_view.controllPostFinishSession(
|
| 72 |
+
request=req)
|
| 73 |
+
else:
|
| 74 |
+
response = controll_view.controlGetResponse(
|
| 75 |
+
request=req, error="No se ha definido la acción a realizar")
|
| 76 |
+
|
| 77 |
else:
|
| 78 |
response = noValidTechnique(
|
| 79 |
params={
|
tecnicas/views/tester_forms/cata_test.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
from django.http import HttpRequest
|
| 2 |
from tecnicas.models import SesionSensorial
|
| 3 |
from tecnicas.controllers import TestCataController
|
| 4 |
-
from tecnicas.utils import noValidTechnique
|
| 5 |
|
| 6 |
|
| 7 |
def cataTest(req: HttpRequest, code_sesion: str):
|
|
|
|
| 1 |
from django.http import HttpRequest
|
| 2 |
from tecnicas.models import SesionSensorial
|
| 3 |
from tecnicas.controllers import TestCataController
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
def cataTest(req: HttpRequest, code_sesion: str):
|
tecnicas/views/tester_forms/init_tester_form.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
from django.http import HttpRequest, JsonResponse
|
| 2 |
-
from django.shortcuts import render
|
| 3 |
-
from
|
| 4 |
-
from tecnicas.controllers import InitSessionTesterController, ParticipacionController
|
| 5 |
from tecnicas.models import SesionSensorial
|
| 6 |
|
| 7 |
|
|
@@ -10,14 +9,22 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
|
|
| 10 |
type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
|
| 11 |
template_url = "tecnicas/forms_tester/init_session.html"
|
| 12 |
|
| 13 |
-
view_controller = InitSessionTesterController(
|
| 14 |
-
sensorial_session=session, user_tester=req.user.user_catador)
|
| 15 |
-
|
| 16 |
if req.method == "GET":
|
| 17 |
if type_technique == "escalas":
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
| 19 |
elif type_technique == "rata" or type_technique == "cata":
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
else:
|
| 22 |
context = {
|
| 23 |
"session": session,
|
|
@@ -27,10 +34,17 @@ def initTesterForm(req: HttpRequest, code_sesion: str):
|
|
| 27 |
req, template_url, context)
|
| 28 |
|
| 29 |
return response
|
|
|
|
| 30 |
elif req.method == "POST":
|
| 31 |
if type_technique == "escalas" or type_technique == "rata" or type_technique == "cata":
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
else:
|
| 35 |
context = {
|
| 36 |
"session": session,
|
|
|
|
| 1 |
from django.http import HttpRequest, JsonResponse
|
| 2 |
+
from django.shortcuts import render
|
| 3 |
+
from tecnicas.controllers import InitSessionEscalasController, InitSessionRATAController, InitSessionPFController
|
|
|
|
| 4 |
from tecnicas.models import SesionSensorial
|
| 5 |
|
| 6 |
|
|
|
|
| 9 |
type_technique = session.tecnica.tipo_tecnica.nombre_tecnica
|
| 10 |
template_url = "tecnicas/forms_tester/init_session.html"
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
if req.method == "GET":
|
| 13 |
if type_technique == "escalas":
|
| 14 |
+
view_controller = InitSessionEscalasController(
|
| 15 |
+
sensorial_session=session, user_tester=req.user.user_catador)
|
| 16 |
+
response = view_controller.controllGet(request=req)
|
| 17 |
+
|
| 18 |
elif type_technique == "rata" or type_technique == "cata":
|
| 19 |
+
view_controller = InitSessionRATAController(
|
| 20 |
+
sensorial_session=session, user_tester=req.user.user_catador)
|
| 21 |
+
response = view_controller.controllGet(request=req)
|
| 22 |
+
|
| 23 |
+
elif type_technique == "perfil flash":
|
| 24 |
+
view_controller = InitSessionPFController(
|
| 25 |
+
sensorial_session=session, user_tester=req.user.user_catador)
|
| 26 |
+
response = view_controller.controllGet(request=req)
|
| 27 |
+
|
| 28 |
else:
|
| 29 |
context = {
|
| 30 |
"session": session,
|
|
|
|
| 34 |
req, template_url, context)
|
| 35 |
|
| 36 |
return response
|
| 37 |
+
|
| 38 |
elif req.method == "POST":
|
| 39 |
if type_technique == "escalas" or type_technique == "rata" or type_technique == "cata":
|
| 40 |
+
view_controller = InitSessionEscalasController(
|
| 41 |
+
sensorial_session=session, user_tester=req.user.user_catador)
|
| 42 |
+
response = view_controller.controllPost(request=req)
|
| 43 |
|
| 44 |
+
elif type_technique == "perfil flash":
|
| 45 |
+
view_controller = InitSessionPFController(
|
| 46 |
+
sensorial_session=session, user_tester=req.user.user_catador)
|
| 47 |
+
response = view_controller.controllPost(request=req)
|
| 48 |
else:
|
| 49 |
context = {
|
| 50 |
"session": session,
|
tecnicas/views/tester_forms/login_session.py
CHANGED
|
@@ -29,8 +29,12 @@ def loginSessionTester(req: HttpRequest):
|
|
| 29 |
|
| 30 |
if type_technique == "escalas":
|
| 31 |
response = login_controller.validateEntryEscalas(request=req)
|
|
|
|
| 32 |
elif type_technique == "rata" or type_technique == "cata":
|
| 33 |
response = login_controller.validateEntryRATA(request=req)
|
|
|
|
|
|
|
|
|
|
| 34 |
else:
|
| 35 |
context = {
|
| 36 |
"error": "La técnica usada en esta sesión es invalida o no ha sido implementada para ingresar a ella"
|
|
|
|
| 29 |
|
| 30 |
if type_technique == "escalas":
|
| 31 |
response = login_controller.validateEntryEscalas(request=req)
|
| 32 |
+
|
| 33 |
elif type_technique == "rata" or type_technique == "cata":
|
| 34 |
response = login_controller.validateEntryRATA(request=req)
|
| 35 |
+
|
| 36 |
+
elif type_technique == "perfil flash":
|
| 37 |
+
response = login_controller.validateEntryRATA(request=req)
|
| 38 |
else:
|
| 39 |
context = {
|
| 40 |
"error": "La técnica usada en esta sesión es invalida o no ha sido implementada para ingresar a ella"
|
tecnicas/views/tester_forms/pf_test.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest, JsonResponse
|
| 2 |
+
from django.shortcuts import redirect
|
| 3 |
+
from django.urls import reverse
|
| 4 |
+
from tecnicas.models import SesionSensorial
|
| 5 |
+
from tecnicas.controllers import TestPFController, ParticipacionController
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def pfTest(req: HttpRequest, code_sesion: str):
|
| 9 |
+
if req.method == "GET":
|
| 10 |
+
session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
|
| 11 |
+
controll_view = TestPFController(
|
| 12 |
+
sensorial_session=session, user_tester=req.user.user_catador)
|
| 13 |
+
return controll_view.controllGet(request=req)
|
| 14 |
+
|
| 15 |
+
if req.method == "POST":
|
| 16 |
+
session = SesionSensorial.objects.get(codigo_sesion=code_sesion)
|
| 17 |
+
controll_view = TestPFController(
|
| 18 |
+
sensorial_session=session, user_tester=req.user.user_catador)
|
| 19 |
+
return controll_view.controllPost(request=req)
|