Spaces:
Sleeping
Sleeping
Resolucion de conflicos con dev
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- cata_system/settings.py +1 -1
- docker-compose.yml +7 -0
- tecnicas/admin.py +2 -1
- tecnicas/controllers/__init__.py +13 -7
- tecnicas/controllers/models_controller/calificacion_controller.py +42 -55
- tecnicas/controllers/models_controller/dato_controller.py +13 -14
- tecnicas/controllers/models_controller/palabras_controller.py +7 -8
- tecnicas/controllers/models_controller/particiapacion_controller.py +6 -9
- tecnicas/controllers/models_controller/sesion_controller.py +0 -8
- tecnicas/controllers/views_controller/api_rating_controller.py +6 -53
- tecnicas/controllers/views_controller/create_session/panel_basic_controller.py +50 -8
- tecnicas/controllers/views_controller/create_session/panel_codes_controller.py +24 -22
- tecnicas/controllers/views_controller/create_session/panel_create_controller.py +404 -232
- tecnicas/controllers/views_controller/create_session/panel_words_controller.py +36 -7
- tecnicas/controllers/views_controller/session_management/details_escala_controller.py +39 -14
- tecnicas/controllers/views_controller/session_management/monitor_controller.py +53 -4
- tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py +18 -62
- tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py +46 -0
- tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py +158 -0
- tecnicas/controllers/views_controller/{main_tester_form_controller.py → sessions_tester/init_session_tester_controller.py} +103 -23
- tecnicas/controllers/views_controller/{list_sessions_tester_controller.py → sessions_tester/list_sessions_tester_controller.py} +0 -0
- tecnicas/controllers/views_controller/{login_tester_controller.py → sessions_tester/login_session_tester_controller.py} +24 -24
- tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py +110 -0
- tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py +48 -0
- tecnicas/forms/__init__.py +6 -3
- tecnicas/forms/create_session/sesiob_basic_cata_form.py +27 -0
- tecnicas/forms/{sesion_basic_form.py → create_session/sesion_basic_form.py} +1 -3
- tecnicas/forms/{sesion_tags_form.py → create_session/sesion_tags_form.py} +1 -1
- tecnicas/forms/vocabulary_select.py +15 -0
- tecnicas/migrations/0021_rename_nomre_vocabulario_vocabulario_nombre_vocabulario_and_more.py +30 -0
- tecnicas/models/calificacion.py +4 -1
- tecnicas/models/dato.py +1 -1
- tecnicas/models/dato_valor.py +2 -2
- tecnicas/models/vocabulario.py +7 -4
- tecnicas/static/img/letters.webp +0 -0
- tecnicas/static/js/choose-vocabulary.js +67 -0
- tecnicas/static/js/created-scale.js +2 -0
- tecnicas/static/js/created-vocabulary.js +48 -0
- tecnicas/static/js/download-table-csv.js +78 -0
- tecnicas/static/js/panel-basic-cata.js +31 -0
- tecnicas/static/js/panel-words.js +1 -1
- tecnicas/templates/tecnicas/components/form-scale-continue.html +62 -0
- tecnicas/templates/tecnicas/components/form-scale-structure.html +52 -0
- tecnicas/templates/tecnicas/components/item_session_tester.html +1 -1
- tecnicas/templates/tecnicas/components/item_vocabulary.html +20 -0
- tecnicas/templates/tecnicas/components/table-convencional.html +29 -23
- tecnicas/templates/tecnicas/create_sesion/conf-panel-vocabulary.html +62 -0
- tecnicas/templates/tecnicas/create_sesion/configuracion-panel-codes.html +7 -3
- tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html +85 -0
- tecnicas/templates/tecnicas/forms_tester/convencional.html +19 -128
cata_system/settings.py
CHANGED
|
@@ -25,7 +25,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|
| 25 |
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
| 26 |
|
| 27 |
# SECURITY WARNING: keep the secret key used in production secret!
|
| 28 |
-
SECRET_KEY =
|
| 29 |
|
| 30 |
# SECURITY WARNING: don't run with debug turned on in production!
|
| 31 |
DEBUG = True
|
|
|
|
| 25 |
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
| 26 |
|
| 27 |
# SECURITY WARNING: keep the secret key used in production secret!
|
| 28 |
+
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY")
|
| 29 |
|
| 30 |
# SECURITY WARNING: don't run with debug turned on in production!
|
| 31 |
DEBUG = True
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services: # Definicion de servicios. Se convertiran en contendores [Requerido]
|
| 2 |
+
app-django-cata-system: # Nombre del servicio para control de Docker Compose [Requerido]
|
| 3 |
+
hostname: django-cata-system # Nombre del host, [No requerido]
|
| 4 |
+
container_name: django-cata-system # Nombre del contenedor para Docker [No requerido]
|
| 5 |
+
build: .
|
| 6 |
+
ports: # Mapeo de puertos -> sintaxis= puerto del host físico (no debe estar ocupado al momento de crear el contenedor):puerto del contendor [No requerido, depende de si necesita conectividad al exterior]
|
| 7 |
+
- "9495:7860" # -"[host:]container[/protocol]"
|
tecnicas/admin.py
CHANGED
|
@@ -6,7 +6,7 @@ from .models import Catador, Presentador
|
|
| 6 |
|
| 7 |
from .models import Tecnica, SesionSensorial
|
| 8 |
|
| 9 |
-
from .models import EsAtributo, Palabra
|
| 10 |
|
| 11 |
from .models import Etiqueta, Escala, EtiquetasEscala
|
| 12 |
|
|
@@ -31,6 +31,7 @@ admin.site.register(SesionSensorial)
|
|
| 31 |
|
| 32 |
admin.site.register(EsAtributo)
|
| 33 |
admin.site.register(Palabra)
|
|
|
|
| 34 |
|
| 35 |
admin.site.register(Escala)
|
| 36 |
admin.site.register(EtiquetasEscala)
|
|
|
|
| 6 |
|
| 7 |
from .models import Tecnica, SesionSensorial
|
| 8 |
|
| 9 |
+
from .models import EsAtributo, Palabra, Vocabulario
|
| 10 |
|
| 11 |
from .models import Etiqueta, Escala, EtiquetasEscala
|
| 12 |
|
|
|
|
| 31 |
|
| 32 |
admin.site.register(EsAtributo)
|
| 33 |
admin.site.register(Palabra)
|
| 34 |
+
admin.site.register(Vocabulario)
|
| 35 |
|
| 36 |
admin.site.register(Escala)
|
| 37 |
admin.site.register(EtiquetasEscala)
|
tecnicas/controllers/__init__.py
CHANGED
|
@@ -12,12 +12,6 @@ from .models_controller.posicion_controller import PosicionController
|
|
| 12 |
from .models_controller.particiapacion_controller import ParticipacionController
|
| 13 |
from .models_controller.dato_controller import DatoController
|
| 14 |
|
| 15 |
-
from .views_controller.login_tester_controller import LoginTesterController
|
| 16 |
-
from .views_controller.main_tester_form_controller import MainTesterFormController
|
| 17 |
-
from .views_controller.api_rating_controller import ApiRatingController
|
| 18 |
-
from .views_controller.tester_list_controller import TesterListController
|
| 19 |
-
from .views_controller.list_sessions_tester_controller import ListSessionsTesterController
|
| 20 |
-
|
| 21 |
from .views_controller.create_session.panel_basic_controller import PanelBasicController
|
| 22 |
from .views_controller.create_session.panel_tags_controller import PanelTagsController
|
| 23 |
from .views_controller.create_session.panel_codes_controller import PanelCodesController
|
|
@@ -28,4 +22,16 @@ from .views_controller.session_management.details_controller import DetallesCont
|
|
| 28 |
from .views_controller.session_management.details_escala_controller import DetallesEscalasController
|
| 29 |
from .views_controller.session_management.details_rata_controller import DetallesRATAController
|
| 30 |
from .views_controller.session_management.monitor_controller import MonitorController
|
| 31 |
-
from .views_controller.session_management.monitor_escalas_controller import MonitorEscalasController
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
from .models_controller.particiapacion_controller import ParticipacionController
|
| 13 |
from .models_controller.dato_controller import DatoController
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
from .views_controller.create_session.panel_basic_controller import PanelBasicController
|
| 16 |
from .views_controller.create_session.panel_tags_controller import PanelTagsController
|
| 17 |
from .views_controller.create_session.panel_codes_controller import PanelCodesController
|
|
|
|
| 22 |
from .views_controller.session_management.details_escala_controller import DetallesEscalasController
|
| 23 |
from .views_controller.session_management.details_rata_controller import DetallesRATAController
|
| 24 |
from .views_controller.session_management.monitor_controller import MonitorController
|
| 25 |
+
from .views_controller.session_management.monitor_escalas_controller import MonitorEscalasController
|
| 26 |
+
from .views_controller.session_management.monitor_rata_controller import MonitorRATAController
|
| 27 |
+
|
| 28 |
+
from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController
|
| 29 |
+
from .views_controller.sessions_tester.init_session_tester_controller import InitSessionTesterController
|
| 30 |
+
from .views_controller.sessions_tester.convencional_scales_controller import ConvencionalScalesController
|
| 31 |
+
from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController
|
| 32 |
+
|
| 33 |
+
from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController
|
| 34 |
+
from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController
|
| 35 |
+
|
| 36 |
+
from .views_controller.api_rating_controller import ApiRatingController
|
| 37 |
+
from .views_controller.tester_list_controller import TesterListController
|
tecnicas/controllers/models_controller/calificacion_controller.py
CHANGED
|
@@ -6,49 +6,22 @@ from tecnicas.utils import controller_error, getId
|
|
| 6 |
|
| 7 |
class CalificacionController():
|
| 8 |
def __init__(self, product: Producto | int, technique: Tecnica | int, tester: Catador | int):
|
|
|
|
|
|
|
|
|
|
| 9 |
atributes = {
|
| 10 |
-
"num_repeticion":
|
| 11 |
-
"
|
| 12 |
"id_producto_id": getId(product),
|
| 13 |
"id_catador_id": getId(tester),
|
| 14 |
}
|
| 15 |
|
| 16 |
-
self.rating = Calificacion(**atributes)
|
| 17 |
-
|
| 18 |
-
def validateRating(self):
|
| 19 |
-
try:
|
| 20 |
-
self.rating.clean()
|
| 21 |
-
return self.rating
|
| 22 |
-
except ValidationError as e:
|
| 23 |
-
return controller_error("No es posible validar la calificación")
|
| 24 |
-
|
| 25 |
-
def setRepetition(self, repetition: int = None) -> int | dict:
|
| 26 |
-
try:
|
| 27 |
-
if repetition is not None:
|
| 28 |
-
self.rating.num_repeticion = repetition
|
| 29 |
-
else:
|
| 30 |
-
self.rating.num_repeticion = self.rating.id_tecnica.repeticion
|
| 31 |
-
|
| 32 |
-
return self.rating.num_repeticion
|
| 33 |
-
except ValidationError as e:
|
| 34 |
-
return controller_error(e)
|
| 35 |
-
|
| 36 |
-
def saveRating(self):
|
| 37 |
-
try:
|
| 38 |
-
self.rating.save()
|
| 39 |
-
return self.rating
|
| 40 |
-
except ValidationError as e:
|
| 41 |
-
return controller_error(e)
|
| 42 |
|
| 43 |
@staticmethod
|
| 44 |
def getRatingsByTechnique(technique: Tecnica):
|
| 45 |
repetition = technique.repeticion
|
| 46 |
-
|
| 47 |
-
if not repetition:
|
| 48 |
-
return controller_error("Sin datos calificados aún")
|
| 49 |
-
|
| 50 |
ratings = list(Calificacion.objects.filter(id_tecnica=technique))
|
| 51 |
-
|
| 52 |
return ratings
|
| 53 |
|
| 54 |
@staticmethod
|
|
@@ -94,39 +67,53 @@ class CalificacionController():
|
|
| 94 |
return ratings
|
| 95 |
|
| 96 |
@staticmethod
|
| 97 |
-
def
|
| 98 |
positions: list[Posicion] = None,
|
| 99 |
-
user_cata: str = None,
|
| 100 |
repetition: int = None,
|
| 101 |
-
technique: Tecnica = None,
|
| 102 |
-
id_technique: int = None,
|
| 103 |
num_words: int = None):
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
filters = {
|
| 107 |
-
"
|
| 108 |
-
"
|
| 109 |
}
|
| 110 |
|
| 111 |
-
if
|
| 112 |
-
filters["
|
| 113 |
-
|
| 114 |
-
filters["
|
|
|
|
| 115 |
|
| 116 |
-
ratings =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
|
|
|
| 118 |
if len(ratings) == 0:
|
| 119 |
-
return positions
|
| 120 |
|
| 121 |
-
|
|
|
|
| 122 |
|
| 123 |
-
for
|
| 124 |
-
|
| 125 |
|
| 126 |
-
|
| 127 |
-
|
| 128 |
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
class CalificacionController():
|
| 8 |
def __init__(self, product: Producto | int, technique: Tecnica | int, tester: Catador | int):
|
| 9 |
+
technique = Tecnica.objects.only(
|
| 10 |
+
"repeticion").get(id=getId(technique))
|
| 11 |
+
|
| 12 |
atributes = {
|
| 13 |
+
"num_repeticion": technique.repeticion,
|
| 14 |
+
"id_tecnica": technique,
|
| 15 |
"id_producto_id": getId(product),
|
| 16 |
"id_catador_id": getId(tester),
|
| 17 |
}
|
| 18 |
|
| 19 |
+
(self.rating, created) = Calificacion.objects.get_or_create(**atributes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
@staticmethod
|
| 22 |
def getRatingsByTechnique(technique: Tecnica):
|
| 23 |
repetition = technique.repeticion
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
ratings = list(Calificacion.objects.filter(id_tecnica=technique))
|
|
|
|
| 25 |
return ratings
|
| 26 |
|
| 27 |
@staticmethod
|
|
|
|
| 67 |
return ratings
|
| 68 |
|
| 69 |
@staticmethod
|
| 70 |
+
def checkPositionWithoutRating(
|
| 71 |
positions: list[Posicion] = None,
|
| 72 |
+
user_cata: Catador | str = None,
|
| 73 |
repetition: int = None,
|
| 74 |
+
technique: Tecnica | int = None,
|
|
|
|
| 75 |
num_words: int = None):
|
| 76 |
+
end_rating = False
|
| 77 |
+
|
| 78 |
+
# Obtener lista con codigos de productos
|
| 79 |
+
check_products = [
|
| 80 |
+
position.id_producto.codigoProducto for position in positions]
|
| 81 |
|
| 82 |
filters = {
|
| 83 |
+
"num_repeticion": repetition,
|
| 84 |
+
"id_tecnica": getId(technique)
|
| 85 |
}
|
| 86 |
|
| 87 |
+
if isinstance(user_cata, Catador):
|
| 88 |
+
filters["id_catador"] = user_cata
|
| 89 |
+
else:
|
| 90 |
+
filters["id_catador"] = Catador.objects.get(
|
| 91 |
+
user__username=user_cata)
|
| 92 |
|
| 93 |
+
ratings = list(Calificacion.objects.filter(**filters).select_related(
|
| 94 |
+
"id_producto",
|
| 95 |
+
"id_tecnica",
|
| 96 |
+
"id_catador",
|
| 97 |
+
))
|
| 98 |
|
| 99 |
+
# Si no hay calificaciones regresar las posiciones
|
| 100 |
if len(ratings) == 0:
|
| 101 |
+
return (positions, end_rating)
|
| 102 |
|
| 103 |
+
rating_products = [
|
| 104 |
+
rating.id_producto.codigoProducto for rating in ratings]
|
| 105 |
|
| 106 |
+
for index, rating in enumerate(ratings):
|
| 107 |
+
data_rating = rating.dato_calificacion.all()
|
| 108 |
|
| 109 |
+
if len(data_rating) < num_words or len(data_rating) == 0:
|
| 110 |
+
return (positions[index], end_rating)
|
| 111 |
|
| 112 |
+
next_product = list(set(check_products) - set(rating_products))
|
| 113 |
+
if len(next_product) != 0:
|
| 114 |
+
next_position = [
|
| 115 |
+
position for position in positions if position.id_producto.codigoProducto == next_product[0]][0]
|
| 116 |
+
return (next_position, end_rating)
|
| 117 |
+
else:
|
| 118 |
+
end_rating = True
|
| 119 |
+
return (positions[-1], end_rating)
|
tecnicas/controllers/models_controller/dato_controller.py
CHANGED
|
@@ -12,7 +12,11 @@ class DatoController():
|
|
| 12 |
}
|
| 13 |
|
| 14 |
self.data = Dato(**atributes)
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def setRating(self, new_rating: Calificacion):
|
| 18 |
try:
|
|
@@ -35,19 +39,14 @@ class DatoController():
|
|
| 35 |
except ValidationError as e:
|
| 36 |
return controller_error(e.message)
|
| 37 |
|
| 38 |
-
def
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
valor=self.value_data.valor
|
| 45 |
-
)
|
| 46 |
else:
|
| 47 |
-
self.value_data =
|
| 48 |
-
id_dato=self.data,
|
| 49 |
-
valor=self.value_data.valor
|
| 50 |
-
)
|
| 51 |
|
| 52 |
return self.value_data
|
| 53 |
|
|
@@ -88,7 +87,7 @@ class DatoController():
|
|
| 88 |
repeticion=F("id_dato__id_calificacion__num_repeticion"),
|
| 89 |
producto_code=F(
|
| 90 |
"id_dato__id_calificacion__id_producto__codigoProducto"),
|
| 91 |
-
|
| 92 |
"id_dato__id_calificacion__id_catador__user__username"),
|
| 93 |
dato_valor=F("valor")
|
| 94 |
)
|
|
|
|
| 12 |
}
|
| 13 |
|
| 14 |
self.data = Dato(**atributes)
|
| 15 |
+
|
| 16 |
+
if isinstance(value_rating, bool):
|
| 17 |
+
self.value_data = ValorBooleano(valor=value_rating)
|
| 18 |
+
else:
|
| 19 |
+
self.value_data = ValorDecimal(valor=value_rating)
|
| 20 |
|
| 21 |
def setRating(self, new_rating: Calificacion):
|
| 22 |
try:
|
|
|
|
| 39 |
except ValidationError as e:
|
| 40 |
return controller_error(e.message)
|
| 41 |
|
| 42 |
+
def setValue(self, new_value=None):
|
| 43 |
+
if new_value:
|
| 44 |
+
if isinstance(new_value, bool):
|
| 45 |
+
self.value_data = ValorBooleano(valor=new_value)
|
| 46 |
+
else:
|
| 47 |
+
self.value_data = ValorDecimal(valor=new_value)
|
|
|
|
|
|
|
| 48 |
else:
|
| 49 |
+
self.value_data.id_dato = self.data
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
return self.value_data
|
| 52 |
|
|
|
|
| 87 |
repeticion=F("id_dato__id_calificacion__num_repeticion"),
|
| 88 |
producto_code=F(
|
| 89 |
"id_dato__id_calificacion__id_producto__codigoProducto"),
|
| 90 |
+
usuario_catador=F(
|
| 91 |
"id_dato__id_calificacion__id_catador__user__username"),
|
| 92 |
dato_valor=F("valor")
|
| 93 |
)
|
tecnicas/controllers/models_controller/palabras_controller.py
CHANGED
|
@@ -22,17 +22,16 @@ class PalabrasController():
|
|
| 22 |
@staticmethod
|
| 23 |
def getWordsInTechnique(technique: Tecnica):
|
| 24 |
if technique.id_estilo.nombre_estilo == "atributos":
|
| 25 |
-
|
| 26 |
-
|
|
|
|
| 27 |
return words
|
| 28 |
elif technique.id_estilo.nombre_estilo == "vocabulario":
|
| 29 |
-
|
| 30 |
-
palabras
|
| 31 |
-
|
| 32 |
-
)
|
| 33 |
-
return list(palabras.distinct()) if palabras.exists() else controller_error("Técnica sin palabras con vocabulario")
|
| 34 |
-
except Exception as e:
|
| 35 |
return controller_error("Técnica sin palabras con vocabulario")
|
|
|
|
| 36 |
|
| 37 |
@staticmethod
|
| 38 |
def getWordsWithoutData(recoreded_data: list[Dato], words: list[Palabra]):
|
|
|
|
| 22 |
@staticmethod
|
| 23 |
def getWordsInTechnique(technique: Tecnica):
|
| 24 |
if technique.id_estilo.nombre_estilo == "atributos":
|
| 25 |
+
words = list(technique.tecnica_esatributo.palabras.all())
|
| 26 |
+
if not words:
|
| 27 |
+
return controller_error("Técnica sin palabras")
|
| 28 |
return words
|
| 29 |
elif technique.id_estilo.nombre_estilo == "vocabulario":
|
| 30 |
+
words = list(
|
| 31 |
+
technique.tecnica_esvacabulario.id_vocabulario.palabras.all())
|
| 32 |
+
if not words:
|
|
|
|
|
|
|
|
|
|
| 33 |
return controller_error("Técnica sin palabras con vocabulario")
|
| 34 |
+
return words
|
| 35 |
|
| 36 |
@staticmethod
|
| 37 |
def getWordsWithoutData(recoreded_data: list[Dato], words: list[Palabra]):
|
tecnicas/controllers/models_controller/particiapacion_controller.py
CHANGED
|
@@ -16,15 +16,12 @@ class ParticipacionController():
|
|
| 16 |
return controller_error("No se ha encontrado la participación")
|
| 17 |
|
| 18 |
@staticmethod
|
| 19 |
-
def finishSession(
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
return participation
|
| 26 |
-
except Participacion.DoesNotExist:
|
| 27 |
-
return controller_error("No se ha encontrado la participación")
|
| 28 |
|
| 29 |
@staticmethod
|
| 30 |
def outSession(tester: Catador, session: SesionSensorial):
|
|
|
|
| 16 |
return controller_error("No se ha encontrado la participación")
|
| 17 |
|
| 18 |
@staticmethod
|
| 19 |
+
def finishSession(participation: Participacion):
|
| 20 |
+
participation.refresh_from_db()
|
| 21 |
+
participation.finalizado = True
|
| 22 |
+
participation.activo = False
|
| 23 |
+
participation.save()
|
| 24 |
+
return participation
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
@staticmethod
|
| 27 |
def outSession(tester: Catador, session: SesionSensorial):
|
tecnicas/controllers/models_controller/sesion_controller.py
CHANGED
|
@@ -131,14 +131,6 @@ class SesionController():
|
|
| 131 |
else:
|
| 132 |
use_session = session
|
| 133 |
|
| 134 |
-
(is_update_participations,
|
| 135 |
-
message) = ParticipacionController.outAllInSession(use_session)
|
| 136 |
-
|
| 137 |
-
if not is_update_participations:
|
| 138 |
-
return controller_error(message)
|
| 139 |
-
|
| 140 |
use_session.activo = False
|
| 141 |
-
|
| 142 |
use_session.save()
|
| 143 |
-
|
| 144 |
return session
|
|
|
|
| 131 |
else:
|
| 132 |
use_session = session
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
use_session.activo = False
|
|
|
|
| 135 |
use_session.save()
|
|
|
|
| 136 |
return session
|
tecnicas/controllers/views_controller/api_rating_controller.py
CHANGED
|
@@ -7,65 +7,18 @@ class ApiRatingController():
|
|
| 7 |
self.rating_controller = rating_controller
|
| 8 |
self.data_controller = data_controller
|
| 9 |
|
| 10 |
-
def
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
return repetition
|
| 15 |
|
| 16 |
-
def saveRating(self):
|
| 17 |
-
rating = self.rating_controller.saveRating()
|
| 18 |
-
if isinstance(rating, dict):
|
| 19 |
-
return controller_error(rating["error"])
|
| 20 |
-
return rating
|
| 21 |
-
|
| 22 |
-
def setRatingInData(self):
|
| 23 |
-
update_rating = self.data_controller.setRating(
|
| 24 |
-
self.rating_controller.rating)
|
| 25 |
-
if isinstance(update_rating, dict):
|
| 26 |
-
return controller_error(update_rating["error"])
|
| 27 |
-
return update_rating
|
| 28 |
-
|
| 29 |
-
def saveData(self):
|
| 30 |
data = self.data_controller.saveData()
|
| 31 |
-
if isinstance(data, dict):
|
| 32 |
-
return controller_error(data["error"])
|
| 33 |
-
return data
|
| 34 |
-
|
| 35 |
-
def setValueRating(self):
|
| 36 |
-
self.data_controller.setInstanceValue()
|
| 37 |
-
|
| 38 |
-
def saveValue(self):
|
| 39 |
-
value = self.data_controller.saveValue()
|
| 40 |
-
if isinstance(value, dict):
|
| 41 |
-
return controller_error(value["error"])
|
| 42 |
-
|
| 43 |
-
def logicView(self) -> dict:
|
| 44 |
-
validate = self.rating_controller.validateRating()
|
| 45 |
-
if isinstance(validate, dict):
|
| 46 |
-
return controller_error(validate["error"])
|
| 47 |
-
|
| 48 |
-
reptition = self.setRating()
|
| 49 |
-
if isinstance(reptition, dict):
|
| 50 |
-
return controller_error(reptition["error"])
|
| 51 |
-
|
| 52 |
-
rating = self.saveRating()
|
| 53 |
-
if isinstance(rating, dict):
|
| 54 |
-
return controller_error(rating["error"])
|
| 55 |
-
|
| 56 |
-
rating_data = self.setRatingInData()
|
| 57 |
-
if isinstance(rating_data, dict):
|
| 58 |
-
return controller_error(rating_data["error"])
|
| 59 |
-
|
| 60 |
-
data = self.saveData()
|
| 61 |
if isinstance(data, dict):
|
| 62 |
return controller_error(data["error"])
|
| 63 |
|
| 64 |
-
|
| 65 |
-
if isinstance(value, dict):
|
| 66 |
-
return controller_error(value["error"])
|
| 67 |
|
| 68 |
-
value_save = self.saveValue()
|
| 69 |
if isinstance(value_save, dict):
|
| 70 |
return controller_error(value_save["error"])
|
| 71 |
|
|
|
|
| 7 |
self.rating_controller = rating_controller
|
| 8 |
self.data_controller = data_controller
|
| 9 |
|
| 10 |
+
def controllPostScales(self) -> dict:
|
| 11 |
+
self.data_controller.setRating(
|
| 12 |
+
new_rating=self.rating_controller.rating
|
| 13 |
+
)
|
|
|
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
data = self.data_controller.saveData()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
if isinstance(data, dict):
|
| 17 |
return controller_error(data["error"])
|
| 18 |
|
| 19 |
+
self.data_controller.setValue()
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
value_save = self.data_controller.saveValue()
|
| 22 |
if isinstance(value_save, dict):
|
| 23 |
return controller_error(value_save["error"])
|
| 24 |
|
tecnicas/controllers/views_controller/create_session/panel_basic_controller.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from tecnicas.forms import SesionBasicForm
|
| 2 |
from django.http import HttpRequest
|
| 3 |
from django.shortcuts import redirect, render
|
| 4 |
from django.urls import reverse
|
|
@@ -10,6 +10,14 @@ class PanelBasicController():
|
|
| 10 |
"numero_repeticiones": 1
|
| 11 |
}
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
def __init__(self):
|
| 14 |
pass
|
| 15 |
|
|
@@ -23,7 +31,7 @@ class PanelBasicController():
|
|
| 23 |
}
|
| 24 |
|
| 25 |
response = render(
|
| 26 |
-
request,
|
| 27 |
return response
|
| 28 |
|
| 29 |
@staticmethod
|
|
@@ -44,11 +52,11 @@ class PanelBasicController():
|
|
| 44 |
response = redirect(
|
| 45 |
reverse("cata_system:panel_configuracion_tags"))
|
| 46 |
else:
|
| 47 |
-
response = render(request,
|
| 48 |
"form_sesion": form, "error": "Información no valida"})
|
| 49 |
except KeyError:
|
| 50 |
response = redirect(reverse(
|
| 51 |
-
|
| 52 |
|
| 53 |
return response
|
| 54 |
|
|
@@ -63,7 +71,7 @@ class PanelBasicController():
|
|
| 63 |
}
|
| 64 |
|
| 65 |
response = render(
|
| 66 |
-
request,
|
| 67 |
return response
|
| 68 |
|
| 69 |
@staticmethod
|
|
@@ -88,7 +96,7 @@ class PanelBasicController():
|
|
| 88 |
key, f"Valor inválido para '{key}': se esperaba {expected}, se recibió {actual}")
|
| 89 |
|
| 90 |
if form.errors:
|
| 91 |
-
response = render(request,
|
| 92 |
"form_sesion": form, "error": "No puedes modificar el número de catadores o repeticiones"})
|
| 93 |
else:
|
| 94 |
values["name_tecnica"] = name_tecnica
|
|
@@ -96,10 +104,44 @@ class PanelBasicController():
|
|
| 96 |
response = redirect(
|
| 97 |
reverse("cata_system:panel_configuracion_tags"))
|
| 98 |
else:
|
| 99 |
-
response = render(request,
|
| 100 |
"form_sesion": form, "error": "Información no valida"})
|
| 101 |
except KeyError:
|
| 102 |
response = redirect(reverse(
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
return response
|
|
|
|
| 1 |
+
from tecnicas.forms import SesionBasicForm, SesionBasicCATAForm
|
| 2 |
from django.http import HttpRequest
|
| 3 |
from django.shortcuts import redirect, render
|
| 4 |
from django.urls import reverse
|
|
|
|
| 10 |
"numero_repeticiones": 1
|
| 11 |
}
|
| 12 |
|
| 13 |
+
url_panel_basic = "tecnicas/create_sesion/configuracion-panel-basic.html"
|
| 14 |
+
url_panel_basic_cata = "tecnicas/create_sesion/panel-basic-cata.html"
|
| 15 |
+
|
| 16 |
+
url_next_panel_scales = "cata_system:panel_configuracion_tags"
|
| 17 |
+
url_next_panel_cata = "cata_system:panel_configuracion_codes"
|
| 18 |
+
|
| 19 |
+
url_select_technique = "cata_system:seleccion_tecnica"
|
| 20 |
+
|
| 21 |
def __init__(self):
|
| 22 |
pass
|
| 23 |
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
response = render(
|
| 34 |
+
request, PanelBasicController.url_panel_basic, view_context)
|
| 35 |
return response
|
| 36 |
|
| 37 |
@staticmethod
|
|
|
|
| 52 |
response = redirect(
|
| 53 |
reverse("cata_system:panel_configuracion_tags"))
|
| 54 |
else:
|
| 55 |
+
response = render(request, PanelBasicController.url_panel_basic, {
|
| 56 |
"form_sesion": form, "error": "Información no valida"})
|
| 57 |
except KeyError:
|
| 58 |
response = redirect(reverse(
|
| 59 |
+
PanelBasicController.url_select_technique) + "?error=error en datos de configuracion")
|
| 60 |
|
| 61 |
return response
|
| 62 |
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
response = render(
|
| 74 |
+
request, PanelBasicController.url_panel_basic, view_context)
|
| 75 |
return response
|
| 76 |
|
| 77 |
@staticmethod
|
|
|
|
| 96 |
key, f"Valor inválido para '{key}': se esperaba {expected}, se recibió {actual}")
|
| 97 |
|
| 98 |
if form.errors:
|
| 99 |
+
response = render(request, PanelBasicController.url_panel_basic, {
|
| 100 |
"form_sesion": form, "error": "No puedes modificar el número de catadores o repeticiones"})
|
| 101 |
else:
|
| 102 |
values["name_tecnica"] = name_tecnica
|
|
|
|
| 104 |
response = redirect(
|
| 105 |
reverse("cata_system:panel_configuracion_tags"))
|
| 106 |
else:
|
| 107 |
+
response = render(request, PanelBasicController.url_panel_basic, {
|
| 108 |
"form_sesion": form, "error": "Información no valida"})
|
| 109 |
except KeyError:
|
| 110 |
response = redirect(reverse(
|
| 111 |
+
PanelBasicController.url_select_technique) + "?error=error en datos de configuracion")
|
| 112 |
+
|
| 113 |
+
return response
|
| 114 |
+
|
| 115 |
+
@staticmethod
|
| 116 |
+
def controllGetCATA(request: HttpRequest):
|
| 117 |
+
form_sesion = SesionBasicCATAForm()
|
| 118 |
+
|
| 119 |
+
view_context = {
|
| 120 |
+
"form_sesion": form_sesion,
|
| 121 |
+
"use_technique": "cata"
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
return render(
|
| 125 |
+
request, PanelBasicController.url_panel_basic_cata, view_context)
|
| 126 |
+
|
| 127 |
+
@staticmethod
|
| 128 |
+
def controllPostCATA(request: HttpRequest, name_tecnica: str):
|
| 129 |
+
form = SesionBasicCATAForm(request.POST)
|
| 130 |
+
|
| 131 |
+
if form.is_valid():
|
| 132 |
+
values = {}
|
| 133 |
+
for name, value in form.cleaned_data.items():
|
| 134 |
+
if name == "estilo_palabras":
|
| 135 |
+
values[name] = value.id
|
| 136 |
+
else:
|
| 137 |
+
values[name] = value
|
| 138 |
+
|
| 139 |
+
values["name_tecnica"] = name_tecnica
|
| 140 |
+
request.session['form_basic'] = values
|
| 141 |
+
response = redirect(
|
| 142 |
+
reverse(PanelBasicController.url_next_panel_cata))
|
| 143 |
+
else:
|
| 144 |
+
response = render(request, PanelBasicController.url_panel_basic, {
|
| 145 |
+
"form_sesion": form, "error": "Información no valida"})
|
| 146 |
|
| 147 |
return response
|
tecnicas/controllers/views_controller/create_session/panel_codes_controller.py
CHANGED
|
@@ -7,15 +7,16 @@ import json
|
|
| 7 |
|
| 8 |
|
| 9 |
class PanelCodesController():
|
|
|
|
|
|
|
|
|
|
| 10 |
def __init__(self):
|
| 11 |
pass
|
| 12 |
|
| 13 |
@staticmethod
|
| 14 |
def controllGetEscalas(request: HttpRequest, data):
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
num_tester
|
| 18 |
-
) = PanelCodesController.defineInfoConvencional(data)
|
| 19 |
|
| 20 |
codes_products = generarCodigos(num_products)
|
| 21 |
|
|
@@ -27,14 +28,11 @@ class PanelCodesController():
|
|
| 27 |
"use_technique": "escalas"
|
| 28 |
}
|
| 29 |
|
| 30 |
-
return render(request,
|
| 31 |
|
| 32 |
@staticmethod
|
| 33 |
def controllPostEscalas(request: HttpRequest, data):
|
| 34 |
-
|
| 35 |
-
num_products,
|
| 36 |
-
num_tester
|
| 37 |
-
) = PanelCodesController.defineInfoConvencional(data)
|
| 38 |
|
| 39 |
sorts_code = json.loads(request.POST.get("sort_codes"))
|
| 40 |
codes = []
|
|
@@ -60,11 +58,11 @@ class PanelCodesController():
|
|
| 60 |
|
| 61 |
codes_sort["sort_codes"] = sorts_code
|
| 62 |
request.session["form_codes"] = codes_sort
|
| 63 |
-
return redirect(reverse(
|
| 64 |
else:
|
| 65 |
context_codes_form["error"] = "error en los datos recibidos"
|
| 66 |
|
| 67 |
-
return render(request,
|
| 68 |
|
| 69 |
@staticmethod
|
| 70 |
def controllGetRATA(request: HttpRequest, data):
|
|
@@ -78,10 +76,10 @@ class PanelCodesController():
|
|
| 78 |
"use_technique": "rata"
|
| 79 |
}
|
| 80 |
|
| 81 |
-
return render(request,
|
| 82 |
|
| 83 |
@staticmethod
|
| 84 |
-
def controllPostRATA(request: HttpRequest):
|
| 85 |
codes = []
|
| 86 |
context_codes_form = {}
|
| 87 |
|
|
@@ -93,22 +91,26 @@ class PanelCodesController():
|
|
| 93 |
|
| 94 |
context_codes_form = {
|
| 95 |
"form_codes": form_codes,
|
| 96 |
-
"use_technique": "rata"
|
| 97 |
}
|
| 98 |
|
| 99 |
if form_codes.is_valid():
|
| 100 |
request.session["form_codes"] = codes
|
| 101 |
-
return redirect(reverse(
|
| 102 |
else:
|
| 103 |
context_codes_form["error"] = "error en los datos recibidos"
|
| 104 |
|
| 105 |
-
return render(request,
|
| 106 |
|
| 107 |
@staticmethod
|
| 108 |
-
def
|
| 109 |
num_products = data["numero_productos"]
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
|
| 9 |
class PanelCodesController():
|
| 10 |
+
url_current_panel = "tecnicas/create_sesion/configuracion-panel-codes.html"
|
| 11 |
+
url_next_panel = "cata_system:panel_configuracion_words"
|
| 12 |
+
|
| 13 |
def __init__(self):
|
| 14 |
pass
|
| 15 |
|
| 16 |
@staticmethod
|
| 17 |
def controllGetEscalas(request: HttpRequest, data):
|
| 18 |
+
num_products = data["numero_productos"]
|
| 19 |
+
num_tester = data["numero_catadores"]
|
|
|
|
|
|
|
| 20 |
|
| 21 |
codes_products = generarCodigos(num_products)
|
| 22 |
|
|
|
|
| 28 |
"use_technique": "escalas"
|
| 29 |
}
|
| 30 |
|
| 31 |
+
return render(request, PanelCodesController.url_current_panel, context_codes_form)
|
| 32 |
|
| 33 |
@staticmethod
|
| 34 |
def controllPostEscalas(request: HttpRequest, data):
|
| 35 |
+
num_tester = data["numero_catadores"]
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
sorts_code = json.loads(request.POST.get("sort_codes"))
|
| 38 |
codes = []
|
|
|
|
| 58 |
|
| 59 |
codes_sort["sort_codes"] = sorts_code
|
| 60 |
request.session["form_codes"] = codes_sort
|
| 61 |
+
return redirect(reverse(PanelCodesController.url_next_panel))
|
| 62 |
else:
|
| 63 |
context_codes_form["error"] = "error en los datos recibidos"
|
| 64 |
|
| 65 |
+
return render(request, PanelCodesController.url_current_panel, context_codes_form)
|
| 66 |
|
| 67 |
@staticmethod
|
| 68 |
def controllGetRATA(request: HttpRequest, data):
|
|
|
|
| 76 |
"use_technique": "rata"
|
| 77 |
}
|
| 78 |
|
| 79 |
+
return render(request, PanelCodesController.url_current_panel, context_codes_form)
|
| 80 |
|
| 81 |
@staticmethod
|
| 82 |
+
def controllPostRATA(request: HttpRequest, is_rata: True):
|
| 83 |
codes = []
|
| 84 |
context_codes_form = {}
|
| 85 |
|
|
|
|
| 91 |
|
| 92 |
context_codes_form = {
|
| 93 |
"form_codes": form_codes,
|
| 94 |
+
"use_technique": "rata" if is_rata else "cata"
|
| 95 |
}
|
| 96 |
|
| 97 |
if form_codes.is_valid():
|
| 98 |
request.session["form_codes"] = codes
|
| 99 |
+
return redirect(reverse(PanelCodesController.url_next_panel))
|
| 100 |
else:
|
| 101 |
context_codes_form["error"] = "error en los datos recibidos"
|
| 102 |
|
| 103 |
+
return render(request, PanelCodesController.url_current_panel, context_codes_form)
|
| 104 |
|
| 105 |
@staticmethod
|
| 106 |
+
def controllGetCATA(request: HttpRequest, data):
|
| 107 |
num_products = data["numero_productos"]
|
| 108 |
+
codes_products = generarCodigos(num_products)
|
| 109 |
+
form_codes = CodesForm(codes=codes_products)
|
| 110 |
+
|
| 111 |
+
context_codes_form = {
|
| 112 |
+
"form_codes": form_codes,
|
| 113 |
+
"use_technique": "cata"
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
return render(request, PanelCodesController.url_current_panel, context_codes_form)
|
tecnicas/controllers/views_controller/create_session/panel_create_controller.py
CHANGED
|
@@ -2,6 +2,7 @@ from django.http import HttpRequest, JsonResponse
|
|
| 2 |
from django.db import transaction
|
| 3 |
from django.shortcuts import render
|
| 4 |
from tecnicas.utils import general_error
|
|
|
|
| 5 |
from tecnicas.controllers import TecnicaController, EscalaController, ProductosController, OrdenesController, EstiloPalabrasController, PalabrasController, SesionController
|
| 6 |
from tecnicas.utils import deleteDataSession
|
| 7 |
|
|
@@ -21,139 +22,155 @@ class PanelCreateController():
|
|
| 21 |
if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"):
|
| 22 |
deleteDataSession(request)
|
| 23 |
return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
"
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
context = {
|
| 142 |
-
"message": "sesión creada",
|
| 143 |
-
"data": {
|
| 144 |
-
"codigo_sesion": saved_session.codigo_sesion,
|
| 145 |
-
"nombre_sesion": saved_session.nombre_sesion
|
| 146 |
}
|
| 147 |
-
}
|
| 148 |
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
| 157 |
else:
|
| 158 |
return general_error("No se ha establecido acción")
|
| 159 |
|
|
@@ -163,112 +180,267 @@ class PanelCreateController():
|
|
| 163 |
if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"):
|
| 164 |
deleteDataSession(request)
|
| 165 |
return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
"data": {
|
| 261 |
-
"codigo_sesion": saved_session.codigo_sesion,
|
| 262 |
-
"nombre_sesion": saved_session.nombre_sesion
|
| 263 |
}
|
| 264 |
-
}
|
| 265 |
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
deleteDataSession(request)
|
| 272 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
else:
|
| 274 |
return general_error("No se ha establecido acción")
|
|
|
|
| 2 |
from django.db import transaction
|
| 3 |
from django.shortcuts import render
|
| 4 |
from tecnicas.utils import general_error
|
| 5 |
+
from tecnicas.models import EsAtributo, EsVocabulario, Vocabulario, Tecnica, TipoTecnica, EstiloPalabra, Producto, Palabra, SesionSensorial
|
| 6 |
from tecnicas.controllers import TecnicaController, EscalaController, ProductosController, OrdenesController, EstiloPalabrasController, PalabrasController, SesionController
|
| 7 |
from tecnicas.utils import deleteDataSession
|
| 8 |
|
|
|
|
| 22 |
if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"):
|
| 23 |
deleteDataSession(request)
|
| 24 |
return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
|
| 25 |
+
try:
|
| 26 |
+
with transaction.atomic():
|
| 27 |
+
# ////////////////////////////////////////////////////// #
|
| 28 |
+
#
|
| 29 |
+
# First step: Create technique and scale with their tags #
|
| 30 |
+
#
|
| 31 |
+
# ////////////////////////////////////////////////////// #
|
| 32 |
+
data_basic = request.session["form_basic"]
|
| 33 |
+
controllerTechnique = TecnicaController()
|
| 34 |
+
controllerTechnique.setTechniqueFromBasicData(
|
| 35 |
+
basic=data_basic)
|
| 36 |
+
technique = controllerTechnique.saveTechnique()
|
| 37 |
+
if not technique:
|
| 38 |
+
raise ValueError("Error al guardar la técnica")
|
| 39 |
+
|
| 40 |
+
data_scale = {
|
| 41 |
+
"id_scale": data_basic["tipo_escala"],
|
| 42 |
+
"size": data_basic["tamano_escala"],
|
| 43 |
+
"technique": technique
|
| 44 |
+
}
|
| 45 |
|
| 46 |
+
controllerScale = EscalaController(data=data_scale)
|
| 47 |
+
|
| 48 |
+
scale = controllerScale.saveScale()
|
| 49 |
+
if isinstance(scale, dict):
|
| 50 |
+
raise ValueError(scale["error"])
|
| 51 |
+
|
| 52 |
+
dict_tags = request.session["form_tags"]
|
| 53 |
+
saved_related_tags = controllerScale.realteTags(dict_tags)
|
| 54 |
+
if "error" in saved_related_tags:
|
| 55 |
+
raise ValueError(saved_related_tags["error"])
|
| 56 |
+
|
| 57 |
+
# ////////////////////////////////////////////////////////// #
|
| 58 |
+
#
|
| 59 |
+
# Second step: Create orders, productos and set the position #
|
| 60 |
+
#
|
| 61 |
+
# ////////////////////////////////////////////////////////// #
|
| 62 |
+
data_codes = request.session["form_codes"]
|
| 63 |
+
|
| 64 |
+
list_codes_dict = data_codes["product_codes"]
|
| 65 |
+
|
| 66 |
+
codes = []
|
| 67 |
+
for product in list_codes_dict:
|
| 68 |
+
code = next(iter(product.values()))
|
| 69 |
+
codes.append(code)
|
| 70 |
+
|
| 71 |
+
controllerProducts = ProductosController(
|
| 72 |
+
codes=codes,
|
| 73 |
+
technique=technique
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
controllerProducts.setProductsNoSave()
|
| 77 |
+
saved_prodcuts = controllerProducts.saveProducts()
|
| 78 |
+
if isinstance(saved_prodcuts, dict):
|
| 79 |
+
raise ValueError(saved_prodcuts["error"])
|
| 80 |
+
|
| 81 |
+
raw_sort_codes = data_codes["sort_codes"]
|
| 82 |
+
controllerOrdes = OrdenesController(
|
| 83 |
+
raw_orders=raw_sort_codes,
|
| 84 |
+
list_products=saved_prodcuts,
|
| 85 |
+
technique=technique
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
controllerOrdes.setOrdersToSave()
|
| 89 |
+
saved_orders = controllerOrdes.saveOrders()
|
| 90 |
+
if isinstance(saved_orders, dict):
|
| 91 |
+
raise ValueError(saved_orders["error"])
|
| 92 |
+
|
| 93 |
+
seded_positions = controllerOrdes.setPositions()
|
| 94 |
+
if isinstance(seded_positions, dict):
|
| 95 |
+
raise ValueError(seded_positions["error"])
|
| 96 |
+
|
| 97 |
+
saved_postions = controllerOrdes.savePositions()
|
| 98 |
+
if isinstance(saved_postions, dict):
|
| 99 |
+
raise ValueError(saved_prodcuts["error"])
|
| 100 |
+
|
| 101 |
+
# /////////////////////////////////////////////////////// #
|
| 102 |
+
#
|
| 103 |
+
# Third step: Create relations technique with Words Style #
|
| 104 |
+
#
|
| 105 |
+
# /////////////////////////////////////////////////////// #
|
| 106 |
+
style_words = technique.id_estilo.nombre_estilo
|
| 107 |
+
if style_words == "atributos":
|
| 108 |
+
ids_words = request.session["form_words"]
|
| 109 |
+
words_controller = PalabrasController(ids=ids_words)
|
| 110 |
+
|
| 111 |
+
words_to_use = words_controller.setWords()
|
| 112 |
+
if isinstance(words_to_use, dict):
|
| 113 |
+
raise ValueError(words_to_use["error"])
|
| 114 |
+
|
| 115 |
+
style_controller = EstiloPalabrasController(
|
| 116 |
+
technique=technique, words=words_to_use)
|
| 117 |
+
|
| 118 |
+
instace_style = style_controller.createAndSaveInstaceStyle()
|
| 119 |
+
if isinstance(instace_style, dict):
|
| 120 |
+
raise ValueError(instace_style["error"])
|
| 121 |
+
|
| 122 |
+
words_using = style_controller.relatedWords()
|
| 123 |
+
if isinstance(words_using, dict):
|
| 124 |
+
raise ValueError(words_using["error"])
|
| 125 |
+
elif style_words == "vocabulario":
|
| 126 |
+
name_vocabulary = request.session["form_words"]
|
| 127 |
+
vocabulary = Vocabulario.objects.get(
|
| 128 |
+
nombre_vocabulario=name_vocabulary)
|
| 129 |
+
|
| 130 |
+
es_vocabulary = EsVocabulario.objects.create(
|
| 131 |
+
id_tecnica=technique,
|
| 132 |
+
id_vocabulario=vocabulary
|
| 133 |
+
)
|
| 134 |
+
else:
|
| 135 |
+
raise ValueError("Estilo de palabas no permitido")
|
| 136 |
+
|
| 137 |
+
# //////////////////////////////////////////////////////// #
|
| 138 |
+
#
|
| 139 |
+
# Fourth step: Create session and relat with the technique #
|
| 140 |
+
#
|
| 141 |
+
# //////////////////////////////////////////////////////// #
|
| 142 |
+
session_controller = SesionController(
|
| 143 |
+
name_session=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
|
| 144 |
+
technique=technique,
|
| 145 |
+
creator=request.user.user_presentador
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
setting_session = session_controller.setSession()
|
| 149 |
+
if isinstance(setting_session, dict):
|
| 150 |
+
raise ValueError(setting_session["error"])
|
| 151 |
+
|
| 152 |
+
saved_session = session_controller.saveSession()
|
| 153 |
+
if isinstance(saved_session, dict):
|
| 154 |
+
raise ValueError(saved_session["error"])
|
| 155 |
+
|
| 156 |
+
context = {
|
| 157 |
+
"message": "sesión creada",
|
| 158 |
+
"data": {
|
| 159 |
+
"codigo_sesion": saved_session.codigo_sesion,
|
| 160 |
+
"nombre_sesion": saved_session.nombre_sesion
|
| 161 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
}
|
|
|
|
| 163 |
|
| 164 |
+
# ////////////////////////////////// #
|
| 165 |
+
#
|
| 166 |
+
# Final step: Delete date in session #
|
| 167 |
+
#
|
| 168 |
+
# ////////////////////////////////// #
|
| 169 |
|
| 170 |
+
deleteDataSession(request)
|
| 171 |
+
return JsonResponse(context)
|
| 172 |
+
except ValueError as e:
|
| 173 |
+
return general_error(f"Error: {e}")
|
| 174 |
else:
|
| 175 |
return general_error("No se ha establecido acción")
|
| 176 |
|
|
|
|
| 180 |
if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"):
|
| 181 |
deleteDataSession(request)
|
| 182 |
return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
|
| 183 |
+
try:
|
| 184 |
+
with transaction.atomic():
|
| 185 |
+
# ////////////////////////////////////////////////////// #
|
| 186 |
+
#
|
| 187 |
+
# First step: Create technique and scale with their tags #
|
| 188 |
+
#
|
| 189 |
+
# ////////////////////////////////////////////////////// #
|
| 190 |
+
data_basic = request.session["form_basic"]
|
| 191 |
+
data_basic["numero_catadores"] = 0
|
| 192 |
+
data_basic["numero_repeticiones"] = 1
|
| 193 |
+
|
| 194 |
+
technique = Tecnica.objects.create(
|
| 195 |
+
tipo_tecnica=TipoTecnica.objects.get(
|
| 196 |
+
nombre_tecnica=data_basic["name_tecnica"]),
|
| 197 |
+
id_estilo=EstiloPalabra.objects.get(
|
| 198 |
+
id=data_basic["estilo_palabras"]),
|
| 199 |
+
repeticiones_max=data_basic["numero_repeticiones"] or 1,
|
| 200 |
+
limite_catadores=data_basic["numero_catadores"],
|
| 201 |
+
instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador",
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
if not technique:
|
| 205 |
+
raise ValueError("Error al guardar la técnica")
|
| 206 |
+
|
| 207 |
+
data_scale = {
|
| 208 |
+
"id_scale": data_basic["tipo_escala"],
|
| 209 |
+
"size": data_basic["tamano_escala"],
|
| 210 |
+
"technique": technique
|
| 211 |
+
}
|
| 212 |
|
| 213 |
+
controllerScale = EscalaController(data=data_scale)
|
| 214 |
+
|
| 215 |
+
scale = controllerScale.saveScale()
|
| 216 |
+
if isinstance(scale, dict):
|
| 217 |
+
raise ValueError(scale["error"])
|
| 218 |
+
|
| 219 |
+
dict_tags = request.session["form_tags"]
|
| 220 |
+
saved_related_tags = controllerScale.realteTags(dict_tags)
|
| 221 |
+
if "error" in saved_related_tags:
|
| 222 |
+
raise ValueError(saved_related_tags["error"])
|
| 223 |
+
|
| 224 |
+
# ////////////////////////////////////////////// #
|
| 225 |
+
#
|
| 226 |
+
# Second step: Create productos with their codes #
|
| 227 |
+
#
|
| 228 |
+
# ////////////////////////////////////////////// #
|
| 229 |
+
codes = request.session["form_codes"]
|
| 230 |
+
|
| 231 |
+
if not codes:
|
| 232 |
+
raise ValueError("No hay códigos de productos")
|
| 233 |
+
|
| 234 |
+
products_without_save = []
|
| 235 |
+
for code in codes:
|
| 236 |
+
product = Producto(
|
| 237 |
+
codigoProducto=code,
|
| 238 |
+
id_tecnica=technique
|
| 239 |
+
)
|
| 240 |
+
products_without_save.append(product)
|
| 241 |
+
|
| 242 |
+
Producto.objects.bulk_create(products_without_save)
|
| 243 |
+
|
| 244 |
+
# /////////////////////////////////////////////////////// #
|
| 245 |
+
#
|
| 246 |
+
# Third step: Create relations technique with Words Style #
|
| 247 |
+
#
|
| 248 |
+
# /////////////////////////////////////////////////////// #
|
| 249 |
+
style_words = technique.id_estilo.nombre_estilo
|
| 250 |
+
|
| 251 |
+
if style_words == "atributos":
|
| 252 |
+
raw_ids_words = request.session["form_words"]
|
| 253 |
+
ids_words = [int(id_w) for id_w in raw_ids_words]
|
| 254 |
+
|
| 255 |
+
words = Palabra.objects.filter(id__in=ids_words)
|
| 256 |
+
|
| 257 |
+
style_atribute = EsAtributo.objects.create(
|
| 258 |
+
id_tecnica=technique
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
if not style_atribute:
|
| 262 |
+
raise ValueError(
|
| 263 |
+
"Error al intentar relacionar las palabras con la técnica")
|
| 264 |
+
|
| 265 |
+
style_atribute.palabras.set(words)
|
| 266 |
+
|
| 267 |
+
elif style_words == "vocabulario":
|
| 268 |
+
name_vocabulary = request.session["form_words"]
|
| 269 |
+
try:
|
| 270 |
+
vocabulary = Vocabulario.objects.get(
|
| 271 |
+
nombre_vocabulario=name_vocabulary)
|
| 272 |
+
except Vocabulario.DoesNotExist:
|
| 273 |
+
raise ValueError("Vocabulario no encontrado")
|
| 274 |
+
|
| 275 |
+
es_vocabulary = EsVocabulario.objects.create(
|
| 276 |
+
id_tecnica=technique,
|
| 277 |
+
id_vocabulario=vocabulary
|
| 278 |
+
)
|
| 279 |
+
if not es_vocabulary:
|
| 280 |
+
raise ValueError(
|
| 281 |
+
"Error al intentar relacionar el vocabulario con la técnica")
|
| 282 |
+
|
| 283 |
+
else:
|
| 284 |
+
raise ValueError("Estilo de palabas no permitido")
|
| 285 |
+
|
| 286 |
+
# //////////////////////////////////////////////////////// #
|
| 287 |
+
#
|
| 288 |
+
# Fourth step: Create session and relat with the technique #
|
| 289 |
+
#
|
| 290 |
+
# //////////////////////////////////////////////////////// #
|
| 291 |
+
session = SesionSensorial.objects.create(
|
| 292 |
+
name_session=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
|
| 293 |
+
technique=technique,
|
| 294 |
+
creator=request.user.user_presentador
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
if not session:
|
| 298 |
+
raise ValueError("Error al crear sesion sensorial")
|
| 299 |
+
|
| 300 |
+
context = {
|
| 301 |
+
"message": "sesión creada",
|
| 302 |
+
"data": {
|
| 303 |
+
"codigo_sesion": session.codigo_sesion,
|
| 304 |
+
"nombre_sesion": session.nombre_sesion
|
| 305 |
+
}
|
|
|
|
|
|
|
|
|
|
| 306 |
}
|
|
|
|
| 307 |
|
| 308 |
+
# ////////////////////////////////// #
|
| 309 |
+
#
|
| 310 |
+
# Final step: Delete date en session #
|
| 311 |
+
#
|
| 312 |
+
# ////////////////////////////////// #
|
| 313 |
+
|
| 314 |
+
deleteDataSession(request)
|
| 315 |
+
return JsonResponse(context)
|
| 316 |
+
except ValueError as e:
|
| 317 |
+
return general_error(f"Error: {e}")
|
| 318 |
+
else:
|
| 319 |
+
return general_error("No se ha establecido acción")
|
| 320 |
+
|
| 321 |
+
@staticmethod
|
| 322 |
+
def controllPostCATA(request: HttpRequest):
|
| 323 |
+
if request.POST.get('action') == 'create_session':
|
| 324 |
+
if not request.session.get("form_codes") or not request.session.get("form_words"):
|
| 325 |
deleteDataSession(request)
|
| 326 |
+
return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo")
|
| 327 |
+
try:
|
| 328 |
+
with transaction.atomic():
|
| 329 |
+
# //////////////////////////// #
|
| 330 |
+
#
|
| 331 |
+
# First step: Create technique #
|
| 332 |
+
#
|
| 333 |
+
# //////////////////////////// #
|
| 334 |
+
data_basic = request.session["form_basic"]
|
| 335 |
+
data_basic["numero_catadores"] = 0
|
| 336 |
+
data_basic["numero_repeticiones"] = 1
|
| 337 |
+
|
| 338 |
+
technique = Tecnica.objects.create(
|
| 339 |
+
tipo_tecnica=TipoTecnica.objects.get(
|
| 340 |
+
nombre_tecnica=data_basic["name_tecnica"]),
|
| 341 |
+
id_estilo=EstiloPalabra.objects.get(
|
| 342 |
+
id=data_basic["estilo_palabras"]),
|
| 343 |
+
repeticiones_max=data_basic["numero_repeticiones"] or 1,
|
| 344 |
+
limite_catadores=data_basic["numero_catadores"],
|
| 345 |
+
instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador",
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
if not technique:
|
| 349 |
+
raise ValueError("Error al guardar la técnica")
|
| 350 |
+
|
| 351 |
+
# ////////////////////////////////////////////// #
|
| 352 |
+
#
|
| 353 |
+
# Second step: Create productos with their codes #
|
| 354 |
+
#
|
| 355 |
+
# ////////////////////////////////////////////// #
|
| 356 |
+
codes = request.session["form_codes"]
|
| 357 |
+
|
| 358 |
+
if not codes:
|
| 359 |
+
raise ValueError("No hay códigos de productos")
|
| 360 |
+
|
| 361 |
+
products_without_save = []
|
| 362 |
+
for code in codes:
|
| 363 |
+
product = Producto(
|
| 364 |
+
codigoProducto=code,
|
| 365 |
+
id_tecnica=technique
|
| 366 |
+
)
|
| 367 |
+
products_without_save.append(product)
|
| 368 |
+
|
| 369 |
+
Producto.objects.bulk_create(products_without_save)
|
| 370 |
+
|
| 371 |
+
# /////////////////////////////////////////////////////// #
|
| 372 |
+
#
|
| 373 |
+
# Third step: Create relations technique with Words Style #
|
| 374 |
+
#
|
| 375 |
+
# /////////////////////////////////////////////////////// #
|
| 376 |
+
style_words = technique.id_estilo.nombre_estilo
|
| 377 |
+
|
| 378 |
+
if style_words == "atributos":
|
| 379 |
+
raw_ids_words = request.session["form_words"]
|
| 380 |
+
ids_words = [int(id_w) for id_w in raw_ids_words]
|
| 381 |
+
|
| 382 |
+
words = Palabra.objects.filter(id__in=ids_words)
|
| 383 |
+
|
| 384 |
+
style_atribute = EsAtributo.objects.create(
|
| 385 |
+
id_tecnica=technique
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
if not style_atribute:
|
| 389 |
+
raise ValueError(
|
| 390 |
+
"Error al intentar relacionar las palabras con la técnica")
|
| 391 |
+
|
| 392 |
+
style_atribute.palabras.set(words)
|
| 393 |
+
|
| 394 |
+
elif style_words == "vocabulario":
|
| 395 |
+
name_vocabulary = request.session["form_words"]
|
| 396 |
+
try:
|
| 397 |
+
vocabulary = Vocabulario.objects.get(
|
| 398 |
+
nombre_vocabulario=name_vocabulary)
|
| 399 |
+
except Vocabulario.DoesNotExist:
|
| 400 |
+
raise ValueError("Vocabulario no encontrado")
|
| 401 |
+
|
| 402 |
+
es_vocabulary = EsVocabulario.objects.create(
|
| 403 |
+
id_tecnica=technique,
|
| 404 |
+
id_vocabulario=vocabulary
|
| 405 |
+
)
|
| 406 |
+
if not es_vocabulary:
|
| 407 |
+
raise ValueError(
|
| 408 |
+
"Error al intentar relacionar el vocabulario con la técnica")
|
| 409 |
+
|
| 410 |
+
else:
|
| 411 |
+
raise ValueError("Estilo de palabas no permitido")
|
| 412 |
+
|
| 413 |
+
# //////////////////////////////////////////////////////// #
|
| 414 |
+
#
|
| 415 |
+
# Fourth step: Create session and relat with the technique #
|
| 416 |
+
#
|
| 417 |
+
# //////////////////////////////////////////////////////// #
|
| 418 |
+
session = SesionSensorial.objects.create(
|
| 419 |
+
nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None,
|
| 420 |
+
tecnica=technique,
|
| 421 |
+
creadoPor=request.user.user_presentador
|
| 422 |
+
)
|
| 423 |
+
|
| 424 |
+
if not session:
|
| 425 |
+
raise ValueError("Error al crear sesion sensorial")
|
| 426 |
+
|
| 427 |
+
context = {
|
| 428 |
+
"message": "sesión creada",
|
| 429 |
+
"data": {
|
| 430 |
+
"codigo_sesion": session.codigo_sesion,
|
| 431 |
+
"nombre_sesion": session.nombre_sesion
|
| 432 |
+
}
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
# ////////////////////////////////// #
|
| 436 |
+
#
|
| 437 |
+
# Final step: Delete date en session #
|
| 438 |
+
#
|
| 439 |
+
# ////////////////////////////////// #
|
| 440 |
+
deleteDataSession(request)
|
| 441 |
+
return JsonResponse(context)
|
| 442 |
+
|
| 443 |
+
except ValueError as e:
|
| 444 |
+
return general_error(f"Error: {e}")
|
| 445 |
else:
|
| 446 |
return general_error("No se ha establecido acción")
|
tecnicas/controllers/views_controller/create_session/panel_words_controller.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from django.http import HttpRequest
|
| 2 |
-
from tecnicas.forms import WordForm
|
| 3 |
from django.shortcuts import render, redirect
|
| 4 |
from django.urls import reverse
|
| 5 |
from tecnicas.models import Palabra
|
|
@@ -7,27 +7,36 @@ import json
|
|
| 7 |
|
| 8 |
|
| 9 |
class PanelWordsController():
|
|
|
|
|
|
|
|
|
|
| 10 |
def __init__(self):
|
| 11 |
pass
|
| 12 |
|
| 13 |
@staticmethod
|
| 14 |
-
def
|
| 15 |
form = WordForm()
|
| 16 |
context = {
|
| 17 |
"form_word": form
|
| 18 |
}
|
| 19 |
|
| 20 |
-
return render(request,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
@staticmethod
|
| 23 |
-
def
|
| 24 |
form = WordForm()
|
| 25 |
context = {
|
| 26 |
"form_word": form
|
| 27 |
}
|
| 28 |
|
| 29 |
if not request.POST.get("words"):
|
| 30 |
-
return render(request,
|
| 31 |
|
| 32 |
words = json.loads(request.POST.get("words"))
|
| 33 |
context["words"] = words
|
|
@@ -36,14 +45,34 @@ class PanelWordsController():
|
|
| 36 |
|
| 37 |
if len(ids_words) != len(set(ids_words)):
|
| 38 |
context["error"] = "existen palabras duplicadas"
|
| 39 |
-
return render(request,
|
| 40 |
|
| 41 |
exist_words = Palabra.objects.filter(
|
| 42 |
id__in=ids_words).count() == len(ids_words)
|
| 43 |
|
| 44 |
if not exist_words:
|
| 45 |
context["error"] = "algunas palabras no existen"
|
| 46 |
-
return render(request,
|
| 47 |
|
| 48 |
request.session["form_words"] = ids_words
|
| 49 |
return redirect(reverse("cata_system:creando_sesion"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from django.http import HttpRequest
|
| 2 |
+
from tecnicas.forms import WordForm, VocabularioSelectForm
|
| 3 |
from django.shortcuts import render, redirect
|
| 4 |
from django.urls import reverse
|
| 5 |
from tecnicas.models import Palabra
|
|
|
|
| 7 |
|
| 8 |
|
| 9 |
class PanelWordsController():
|
| 10 |
+
current_url_escalas_atribute = "tecnicas/create_sesion/configuracion-panel-words.html"
|
| 11 |
+
current_url_escalas_vocabulary = "tecnicas/create_sesion/conf-panel-vocabulary.html"
|
| 12 |
+
|
| 13 |
def __init__(self):
|
| 14 |
pass
|
| 15 |
|
| 16 |
@staticmethod
|
| 17 |
+
def controllGetEscalasAtributes(request: HttpRequest):
|
| 18 |
form = WordForm()
|
| 19 |
context = {
|
| 20 |
"form_word": form
|
| 21 |
}
|
| 22 |
|
| 23 |
+
return render(request, PanelWordsController.current_url_escalas_atribute, context)
|
| 24 |
+
|
| 25 |
+
@staticmethod
|
| 26 |
+
def controllGetEscalasVocabulary(request: HttpRequest):
|
| 27 |
+
form = VocabularioSelectForm()
|
| 28 |
+
context = {"form": form}
|
| 29 |
+
return render(request, PanelWordsController.current_url_escalas_vocabulary, context)
|
| 30 |
|
| 31 |
@staticmethod
|
| 32 |
+
def controllPostEscalasAtributes(request: HttpRequest):
|
| 33 |
form = WordForm()
|
| 34 |
context = {
|
| 35 |
"form_word": form
|
| 36 |
}
|
| 37 |
|
| 38 |
if not request.POST.get("words"):
|
| 39 |
+
return render(request, PanelWordsController.current_url_escalas_atribute, context)
|
| 40 |
|
| 41 |
words = json.loads(request.POST.get("words"))
|
| 42 |
context["words"] = words
|
|
|
|
| 45 |
|
| 46 |
if len(ids_words) != len(set(ids_words)):
|
| 47 |
context["error"] = "existen palabras duplicadas"
|
| 48 |
+
return render(request, PanelWordsController.current_url_escalas_atribute, context)
|
| 49 |
|
| 50 |
exist_words = Palabra.objects.filter(
|
| 51 |
id__in=ids_words).count() == len(ids_words)
|
| 52 |
|
| 53 |
if not exist_words:
|
| 54 |
context["error"] = "algunas palabras no existen"
|
| 55 |
+
return render(request, PanelWordsController.current_url_escalas_atribute, context)
|
| 56 |
|
| 57 |
request.session["form_words"] = ids_words
|
| 58 |
return redirect(reverse("cata_system:creando_sesion"))
|
| 59 |
+
|
| 60 |
+
@staticmethod
|
| 61 |
+
def controllPostEscalasVocabulary(request: HttpRequest):
|
| 62 |
+
context = {}
|
| 63 |
+
if not request.POST.get("vocabulario"):
|
| 64 |
+
context["form"] = VocabularioSelectForm()
|
| 65 |
+
context["error"] = "No hay un vocabulario seleccionado"
|
| 66 |
+
return render(request, PanelWordsController.current_url_escalas_vocabulary, context)
|
| 67 |
+
|
| 68 |
+
form = VocabularioSelectForm(request.POST)
|
| 69 |
+
vocabulary: int
|
| 70 |
+
if form.is_valid():
|
| 71 |
+
vocabulary = form.cleaned_data["vocabulario"]
|
| 72 |
+
else:
|
| 73 |
+
context["form"] = VocabularioSelectForm()
|
| 74 |
+
context["error"] = "Erro al validar el vocabulario"
|
| 75 |
+
return render(request, PanelWordsController.current_url_escalas_vocabulary, context)
|
| 76 |
+
|
| 77 |
+
request.session["form_words"] = vocabulary.nombre_vocabulario
|
| 78 |
+
return redirect(reverse("cata_system:creando_sesion"))
|
tecnicas/controllers/views_controller/session_management/details_escala_controller.py
CHANGED
|
@@ -14,8 +14,8 @@ Encabezados de como deben de aparecer los datos juntos
|
|
| 14 |
from django.http import HttpRequest
|
| 15 |
from django.shortcuts import render, redirect
|
| 16 |
from django.urls import reverse
|
| 17 |
-
from tecnicas.models import SesionSensorial, Presentador,
|
| 18 |
-
from tecnicas.controllers import DatoController,
|
| 19 |
from .details_controller import DetallesController
|
| 20 |
from tecnicas.utils import defaultdict_to_dict, controller_error
|
| 21 |
from collections import defaultdict
|
|
@@ -34,7 +34,7 @@ class DetallesEscalasController(DetallesController):
|
|
| 34 |
context["error"] = error
|
| 35 |
if message != "" or message:
|
| 36 |
context["message"] = message
|
| 37 |
-
|
| 38 |
return render(
|
| 39 |
request, self.url_template, context)
|
| 40 |
|
|
@@ -43,28 +43,41 @@ class DetallesEscalasController(DetallesController):
|
|
| 43 |
"use_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 44 |
}
|
| 45 |
self.context["sesion"] = self.session
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
self.words = PalabrasController.getWordsInTechnique(
|
| 47 |
-
|
| 48 |
self.context["palabras"] = [word.nombre_palabra for word in self.words]
|
| 49 |
|
|
|
|
| 50 |
ratings_for_repetition = []
|
| 51 |
|
| 52 |
-
ratings =
|
| 53 |
-
technique
|
| 54 |
|
| 55 |
-
if
|
| 56 |
self.context["calificaciones"] = ratings_for_repetition
|
| 57 |
self.context["existen_calificaciones"] = False
|
| 58 |
return self.context
|
| 59 |
|
| 60 |
data = DatoController.getWordValuesForConvecional(
|
| 61 |
-
ratings=ratings, technique=
|
| 62 |
|
| 63 |
ratings_for_repetition = defaultdict(
|
| 64 |
lambda: defaultdict(lambda: defaultdict(list)))
|
| 65 |
|
| 66 |
for item in data:
|
| 67 |
-
user = item["
|
| 68 |
rep = item["repeticion"]
|
| 69 |
prod = item["producto_code"]
|
| 70 |
|
|
@@ -77,18 +90,30 @@ class DetallesEscalasController(DetallesController):
|
|
| 77 |
ratings_for_repetition)
|
| 78 |
self.context["existen_calificaciones"] = True
|
| 79 |
|
|
|
|
|
|
|
|
|
|
| 80 |
return self.context
|
| 81 |
|
| 82 |
-
def startRepetition(self, presenter: Presentador):
|
| 83 |
creator = presenter
|
| 84 |
technique = self.session.tecnica
|
| 85 |
|
| 86 |
if creator.user.username != self.session.creadoPor.user.username:
|
| 87 |
-
return self.getResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición")
|
| 88 |
elif self.session.activo:
|
| 89 |
-
return self.getResponse(error="La sesión ya está activada")
|
| 90 |
-
elif technique.repeticion
|
| 91 |
-
return self.getResponse(error="Se ha alcanzado el número de repeticiones máxima")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
self.session.activo = True
|
| 94 |
technique.repeticion = technique.repeticion + 1
|
|
|
|
| 14 |
from django.http import HttpRequest
|
| 15 |
from django.shortcuts import render, redirect
|
| 16 |
from django.urls import reverse
|
| 17 |
+
from tecnicas.models import SesionSensorial, Presentador, Participacion, Calificacion, Escala
|
| 18 |
+
from tecnicas.controllers import DatoController, PalabrasController, ParticipacionController
|
| 19 |
from .details_controller import DetallesController
|
| 20 |
from tecnicas.utils import defaultdict_to_dict, controller_error
|
| 21 |
from collections import defaultdict
|
|
|
|
| 34 |
context["error"] = error
|
| 35 |
if message != "" or message:
|
| 36 |
context["message"] = message
|
| 37 |
+
|
| 38 |
return render(
|
| 39 |
request, self.url_template, context)
|
| 40 |
|
|
|
|
| 43 |
"use_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 44 |
}
|
| 45 |
self.context["sesion"] = self.session
|
| 46 |
+
|
| 47 |
+
technique = self.session.tecnica
|
| 48 |
+
|
| 49 |
+
# Datos de la escala usada
|
| 50 |
+
scale: Escala = technique.escala_tecnica
|
| 51 |
+
|
| 52 |
+
self.context["scale"] = {
|
| 53 |
+
"type": scale.id_tipo_escala.nombre_escala,
|
| 54 |
+
"size": scale.longitud
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
# Recuperar la palabras de la tecnica
|
| 58 |
self.words = PalabrasController.getWordsInTechnique(
|
| 59 |
+
technique)
|
| 60 |
self.context["palabras"] = [word.nombre_palabra for word in self.words]
|
| 61 |
|
| 62 |
+
# Se recuperan las calificaciones
|
| 63 |
ratings_for_repetition = []
|
| 64 |
|
| 65 |
+
ratings = list(Calificacion.objects.filter(
|
| 66 |
+
id_tecnica=technique))
|
| 67 |
|
| 68 |
+
if not ratings:
|
| 69 |
self.context["calificaciones"] = ratings_for_repetition
|
| 70 |
self.context["existen_calificaciones"] = False
|
| 71 |
return self.context
|
| 72 |
|
| 73 |
data = DatoController.getWordValuesForConvecional(
|
| 74 |
+
ratings=ratings, technique=technique)
|
| 75 |
|
| 76 |
ratings_for_repetition = defaultdict(
|
| 77 |
lambda: defaultdict(lambda: defaultdict(list)))
|
| 78 |
|
| 79 |
for item in data:
|
| 80 |
+
user = item["usuario_catador"]
|
| 81 |
rep = item["repeticion"]
|
| 82 |
prod = item["producto_code"]
|
| 83 |
|
|
|
|
| 90 |
ratings_for_repetition)
|
| 91 |
self.context["existen_calificaciones"] = True
|
| 92 |
|
| 93 |
+
# Se comprueba que ya no se pueda iniciar la repeticion
|
| 94 |
+
self.context["fin_repeticiones"] = technique.repeticion >= technique.repeticiones_max
|
| 95 |
+
|
| 96 |
return self.context
|
| 97 |
|
| 98 |
+
def startRepetition(self, presenter: Presentador, request: HttpRequest):
|
| 99 |
creator = presenter
|
| 100 |
technique = self.session.tecnica
|
| 101 |
|
| 102 |
if creator.user.username != self.session.creadoPor.user.username:
|
| 103 |
+
return self.getResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición", request=request)
|
| 104 |
elif self.session.activo:
|
| 105 |
+
return self.getResponse(error="La sesión ya está activada", request=request)
|
| 106 |
+
elif technique.repeticion >= technique.repeticiones_max:
|
| 107 |
+
return self.getResponse(error="Se ha alcanzado el número de repeticiones máxima", request=request)
|
| 108 |
+
|
| 109 |
+
there_participacions = Participacion.objects.filter(
|
| 110 |
+
tecnica=technique).exists()
|
| 111 |
+
|
| 112 |
+
if there_participacions:
|
| 113 |
+
(is_update_participations,
|
| 114 |
+
message) = ParticipacionController.outAllInSession(self.session)
|
| 115 |
+
if not is_update_participations:
|
| 116 |
+
return self.getResponse(error=message, request=request)
|
| 117 |
|
| 118 |
self.session.activo = True
|
| 119 |
technique.repeticion = technique.repeticion + 1
|
tecnicas/controllers/views_controller/session_management/monitor_controller.py
CHANGED
|
@@ -1,10 +1,59 @@
|
|
| 1 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
|
| 4 |
class MonitorController():
|
|
|
|
|
|
|
|
|
|
| 5 |
def __init__(self, session: SesionSensorial):
|
| 6 |
self.sensorial_session = session
|
| 7 |
|
| 8 |
-
def
|
| 9 |
-
self.
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest
|
| 2 |
+
from django.shortcuts import render
|
| 3 |
+
from tecnicas.models import SesionSensorial, Producto, EsAtributo, EsVocabulario
|
| 4 |
+
from tecnicas.controllers import ParticipacionController, SesionController
|
| 5 |
+
from tecnicas.utils import controller_error
|
| 6 |
|
| 7 |
|
| 8 |
class MonitorController():
|
| 9 |
+
url_view: str
|
| 10 |
+
previus_view: str
|
| 11 |
+
|
| 12 |
def __init__(self, session: SesionSensorial):
|
| 13 |
self.sensorial_session = session
|
| 14 |
|
| 15 |
+
def setContext(self):
|
| 16 |
+
self.participations = ParticipacionController.getParticipationsInTechinique(
|
| 17 |
+
self.sensorial_session.tecnica)
|
| 18 |
+
|
| 19 |
+
self.context = {
|
| 20 |
+
"code_session": self.sensorial_session.codigo_sesion,
|
| 21 |
+
"session_name": self.sensorial_session.nombre_sesion,
|
| 22 |
+
"max_testers": self.sensorial_session.tecnica.limite_catadores,
|
| 23 |
+
"current_testers": len(self.participations),
|
| 24 |
+
"active_testers": len([part for part in self.participations if part.activo]),
|
| 25 |
+
"participations": self.participations,
|
| 26 |
+
"use_technique": self.sensorial_session.tecnica.tipo_tecnica.nombre_tecnica
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
def controlGetResponse(self, request: HttpRequest, error: str = "", message: str = ""):
|
| 30 |
+
self.setContext()
|
| 31 |
+
|
| 32 |
+
if error != "" or error:
|
| 33 |
+
self.context["error"] = error
|
| 34 |
+
if message != "" or message:
|
| 35 |
+
self.context["message"] = message
|
| 36 |
+
|
| 37 |
+
return render(request, self.url_view, self.context)
|
| 38 |
+
|
| 39 |
+
def getExpectedRatingsEscalasRapida(self):
|
| 40 |
+
num_products = Producto.objects.filter(
|
| 41 |
+
id_tecnica=self.sensorial_session.tecnica).count()
|
| 42 |
+
style_words = self.sensorial_session.tecnica.id_estilo
|
| 43 |
+
num_words: int
|
| 44 |
+
|
| 45 |
+
if style_words.nombre_estilo == "atributos":
|
| 46 |
+
num_words = EsAtributo.objects.get(
|
| 47 |
+
id_tecnica=self.sensorial_session.tecnica).palabras.count()
|
| 48 |
+
elif style_words.nombre_estilo == "vocabulario":
|
| 49 |
+
num_words = EsVocabulario.objects.get(
|
| 50 |
+
id_tecnica=self.sensorial_session.tecnica).id_vocabulario.palabras.count()
|
| 51 |
+
|
| 52 |
+
return num_products * num_words
|
| 53 |
+
|
| 54 |
+
def finishSession(self):
|
| 55 |
+
response = SesionController.finishRepetion(self.sensorial_session)
|
| 56 |
+
if isinstance(response, dict):
|
| 57 |
+
return controller_error(response["error"])
|
| 58 |
+
self.sensorial_session.refresh_from_db()
|
| 59 |
+
return self.sensorial_session
|
tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py
CHANGED
|
@@ -1,93 +1,49 @@
|
|
| 1 |
from django.http import HttpRequest
|
| 2 |
from django.shortcuts import render, redirect
|
| 3 |
from django.urls import reverse
|
| 4 |
-
from tecnicas.models import
|
| 5 |
-
from tecnicas.controllers import
|
| 6 |
-
from tecnicas.utils import controller_error
|
| 7 |
from .monitor_controller import MonitorController
|
| 8 |
|
| 9 |
|
| 10 |
class MonitorEscalasController(MonitorController):
|
| 11 |
-
url_view = "tecnicas/manage_sesions/monitor-sesion.html"
|
| 12 |
-
|
| 13 |
def __init__(self, session: SesionController):
|
| 14 |
super().__init__(session)
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
def
|
| 17 |
-
self.setContext()
|
| 18 |
-
|
| 19 |
-
if error != "" or error:
|
| 20 |
-
self.context["error"] = error
|
| 21 |
-
if message != "" or message:
|
| 22 |
-
self.context["message"] = message
|
| 23 |
-
|
| 24 |
-
return render(request, self.url_view, self.context)
|
| 25 |
-
|
| 26 |
-
def controlPostResponseFinishSession(self, request: HttpRequest):
|
| 27 |
self.setContext()
|
| 28 |
-
(is_all_end, message) = self.
|
| 29 |
if not is_all_end:
|
| 30 |
self.context["error"] = message
|
| 31 |
-
return render(request,
|
| 32 |
response = self.finishSession()
|
| 33 |
if isinstance(response, dict):
|
| 34 |
self.context["error"] = response["error"]
|
| 35 |
-
return render(request,
|
| 36 |
self.context["message"] = message
|
| 37 |
-
return redirect(reverse(
|
| 38 |
-
|
| 39 |
-
def setContext(self):
|
| 40 |
-
self.participations = ParticipacionController.getParticipationsInTechinique(
|
| 41 |
-
self.sensorial_session.tecnica)
|
| 42 |
|
| 43 |
-
|
| 44 |
-
"code_session": self.sensorial_session.codigo_sesion,
|
| 45 |
-
"session_name": self.sensorial_session.nombre_sesion,
|
| 46 |
-
"max_testers": self.sensorial_session.tecnica.limite_catadores,
|
| 47 |
-
"current_testers": len(self.participations),
|
| 48 |
-
"active_testers": len([part for part in self.participations if part.activo]),
|
| 49 |
-
"participations": self.participations,
|
| 50 |
-
"use_technique": self.sensorial_session.tecnica.tipo_tecnica.nombre_tecnica
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
def getExpectedRatings(self):
|
| 54 |
-
num_products = Producto.objects.filter(
|
| 55 |
-
id_tecnica=self.sensorial_session.tecnica).count()
|
| 56 |
-
style_words = self.sensorial_session.tecnica.id_estilo
|
| 57 |
-
num_words: int
|
| 58 |
-
|
| 59 |
-
if style_words.nombre_estilo == "atributos":
|
| 60 |
-
num_words = EsAtributo.objects.get(
|
| 61 |
-
id_tecnica=self.sensorial_session.tecnica).palabras.count()
|
| 62 |
-
elif style_words.nombre_estilo == "vocabulario":
|
| 63 |
-
num_words = EsVocabulario.objects.get(
|
| 64 |
-
id_tecnica=self.sensorial_session.tecnica).id_vocabulario.palabras.count()
|
| 65 |
-
|
| 66 |
-
return num_products * num_words
|
| 67 |
-
|
| 68 |
-
def checkAllParticipantsEnded(self):
|
| 69 |
technique = self.sensorial_session.tecnica
|
| 70 |
|
| 71 |
-
expected_ratings_repetition = self.
|
| 72 |
|
| 73 |
-
all_participations =
|
| 74 |
-
|
| 75 |
|
| 76 |
if len(all_participations) < technique.limite_catadores:
|
| 77 |
return (False, "No se ha alcanzado el número máximo de Catadores")
|
| 78 |
|
| 79 |
for particiapation in all_participations:
|
| 80 |
-
num_ratings_now =
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
if num_ratings_now < expected_ratings_repetition:
|
| 84 |
return (False, "No todos los catadores han finalizado su evaluación")
|
| 85 |
|
| 86 |
return (True, "Puedes finalizar la sesión")
|
| 87 |
-
|
| 88 |
-
def finishSession(self):
|
| 89 |
-
response = SesionController.finishRepetion(self.sensorial_session)
|
| 90 |
-
if isinstance(response, dict):
|
| 91 |
-
return controller_error(response["error"])
|
| 92 |
-
self.updataSession()
|
| 93 |
-
return self.sensorial_session
|
|
|
|
| 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
|
| 7 |
|
| 8 |
|
| 9 |
class MonitorEscalasController(MonitorController):
|
|
|
|
|
|
|
| 10 |
def __init__(self, session: SesionController):
|
| 11 |
super().__init__(session)
|
| 12 |
+
self.url_view = "tecnicas/manage_sesions/monitor-sesion.html"
|
| 13 |
+
self.previus_view = "cata_system:detalles_sesion"
|
| 14 |
|
| 15 |
+
def controllPostFinishSession(self, request: HttpRequest):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
self.setContext()
|
| 17 |
+
(is_all_end, message) = self.checkAllFinish()
|
| 18 |
if not is_all_end:
|
| 19 |
self.context["error"] = message
|
| 20 |
+
return render(request, self.url_view, self.context)
|
| 21 |
response = self.finishSession()
|
| 22 |
if isinstance(response, dict):
|
| 23 |
self.context["error"] = response["error"]
|
| 24 |
+
return render(request, self.url_view, self.context)
|
| 25 |
self.context["message"] = message
|
| 26 |
+
return redirect(reverse(self.previus_view, kwargs={"session_code": self.sensorial_session.codigo_sesion}))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
def checkAllFinish(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
technique = self.sensorial_session.tecnica
|
| 30 |
|
| 31 |
+
expected_ratings_repetition = self.getExpectedRatingsEscalasRapida()
|
| 32 |
|
| 33 |
+
all_participations = list(
|
| 34 |
+
Participacion.objects.filter(tecnica=technique))
|
| 35 |
|
| 36 |
if len(all_participations) < technique.limite_catadores:
|
| 37 |
return (False, "No se ha alcanzado el número máximo de Catadores")
|
| 38 |
|
| 39 |
for particiapation in all_participations:
|
| 40 |
+
num_ratings_now = Dato.objects.filter(
|
| 41 |
+
id_calificacion__num_repeticion=technique.repeticion,
|
| 42 |
+
id_calificacion__id_catador=particiapation.catador,
|
| 43 |
+
id_calificacion__id_tecnica=technique
|
| 44 |
+
).count()
|
| 45 |
|
| 46 |
if num_ratings_now < expected_ratings_repetition:
|
| 47 |
return (False, "No todos los catadores han finalizado su evaluación")
|
| 48 |
|
| 49 |
return (True, "Puedes finalizar la sesión")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class MonitorRATAController(MonitorController):
|
| 10 |
+
def __init__(self, session: SesionController):
|
| 11 |
+
super().__init__(session)
|
| 12 |
+
self.url_view = "tecnicas/manage_sesions/monitor-sesion.html"
|
| 13 |
+
self.previus_view = "cata_system:detalles_sesion"
|
| 14 |
+
|
| 15 |
+
def controllPostFinishSession(self, request: HttpRequest):
|
| 16 |
+
self.setContext()
|
| 17 |
+
(is_all_end, message) = self.checkAllFinish()
|
| 18 |
+
if not is_all_end:
|
| 19 |
+
self.context["error"] = message
|
| 20 |
+
return render(request, self.url_view, self.context)
|
| 21 |
+
response = self.finishSession()
|
| 22 |
+
if isinstance(response, dict):
|
| 23 |
+
self.context["error"] = response["error"]
|
| 24 |
+
return render(request, self.url_view, self.context)
|
| 25 |
+
self.context["message"] = message
|
| 26 |
+
return redirect(reverse(self.previus_view, kwargs={"session_code": self.sensorial_session.codigo_sesion}))
|
| 27 |
+
|
| 28 |
+
def checkAllFinish(self):
|
| 29 |
+
technique = self.sensorial_session.tecnica
|
| 30 |
+
|
| 31 |
+
expected_ratings_repetition = self.getExpectedRatingsEscalasRapida()
|
| 32 |
+
|
| 33 |
+
all_participations = list(
|
| 34 |
+
Participacion.objects.filter(tecnica=technique))
|
| 35 |
+
|
| 36 |
+
for particiapation in all_participations:
|
| 37 |
+
num_ratings_now = Dato.objects.filter(
|
| 38 |
+
id_calificacion__num_repeticion=technique.repeticion,
|
| 39 |
+
id_calificacion__id_catador=particiapation.catador,
|
| 40 |
+
id_calificacion__id_tecnica=technique
|
| 41 |
+
).count()
|
| 42 |
+
|
| 43 |
+
if num_ratings_now < expected_ratings_repetition:
|
| 44 |
+
return (False, "No todos los catadores han finalizado su evaluación")
|
| 45 |
+
|
| 46 |
+
return (True, "Puedes finalizar la sesión")
|
tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.http import HttpRequest
|
| 2 |
+
from django.shortcuts import redirect, render
|
| 3 |
+
from django.urls import reverse
|
| 4 |
+
from tecnicas.models import SesionSensorial, Catador, Participacion, Producto, Calificacion, Palabra
|
| 5 |
+
from tecnicas.controllers import PosicionController, CalificacionController, ParticipacionController, PalabrasController, EscalaController, DatoController
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class ConvencionalScalesController:
|
| 9 |
+
context = {}
|
| 10 |
+
current_directory = "tecnicas/forms_tester/convencional.html"
|
| 11 |
+
previus_directory = "cata_system:catador_init_session"
|
| 12 |
+
|
| 13 |
+
def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
|
| 14 |
+
self.tester = user_tester
|
| 15 |
+
self.session = sensorial_session
|
| 16 |
+
|
| 17 |
+
def controllGetEscalas(self, request: HttpRequest):
|
| 18 |
+
technique = self.session.tecnica
|
| 19 |
+
self.participation = Participacion.objects.get(
|
| 20 |
+
tecnica=technique, catador=request.user.user_catador)
|
| 21 |
+
|
| 22 |
+
ctx = self.context
|
| 23 |
+
ctx["session"] = self.session
|
| 24 |
+
|
| 25 |
+
# Obtener posiciones y palabras de la técnica
|
| 26 |
+
positions_in_order = PosicionController.getPostionsInOrder(
|
| 27 |
+
id_order=request.session["id_order"])
|
| 28 |
+
aligned_positions_in_order = sorted(
|
| 29 |
+
positions_in_order, key=lambda p: p.posicion)
|
| 30 |
+
words = PalabrasController.getWordsInTechnique(technique=technique)
|
| 31 |
+
|
| 32 |
+
# Comprobar siguiente posición sin calificar
|
| 33 |
+
(next_position, end_products) = CalificacionController.checkPositionWithoutRating(
|
| 34 |
+
positions=aligned_positions_in_order,
|
| 35 |
+
user_cata=request.user.user_catador,
|
| 36 |
+
repetition=technique.repeticion,
|
| 37 |
+
technique=technique,
|
| 38 |
+
num_words=len(words)
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
# Si no hay productos se finaliza la sesion
|
| 42 |
+
if end_products:
|
| 43 |
+
ParticipacionController.finishSession(self.participation)
|
| 44 |
+
params = {"code_sesion": self.session.codigo_sesion}
|
| 45 |
+
return redirect(reverse('cata_system:catador_init_session', kwargs=params))
|
| 46 |
+
|
| 47 |
+
# Si devuelve una lista, tomar el primer elemento
|
| 48 |
+
if isinstance(next_position, list):
|
| 49 |
+
next_position = next_position[0]
|
| 50 |
+
|
| 51 |
+
# Producto a calificar ahora
|
| 52 |
+
product = next_position.id_producto
|
| 53 |
+
ctx["product"] = product
|
| 54 |
+
|
| 55 |
+
# Revisar las palabras para calificar
|
| 56 |
+
try:
|
| 57 |
+
rating = Calificacion.objects.get(
|
| 58 |
+
num_repeticion=technique.repeticion,
|
| 59 |
+
id_producto=product,
|
| 60 |
+
id_tecnica=technique,
|
| 61 |
+
id_catador=self.tester
|
| 62 |
+
)
|
| 63 |
+
there_rating = True
|
| 64 |
+
except Calificacion.DoesNotExist:
|
| 65 |
+
there_rating = False
|
| 66 |
+
|
| 67 |
+
# Si no hay calificaciones previas, usar todas las palabras
|
| 68 |
+
if not there_rating:
|
| 69 |
+
ctx["words"] = words
|
| 70 |
+
else:
|
| 71 |
+
ratings_product = rating.dato_calificacion.all()
|
| 72 |
+
# Filtrar palabras que faltan
|
| 73 |
+
words_to_use = PalabrasController.getWordsWithoutData(
|
| 74 |
+
recoreded_data=ratings_product,
|
| 75 |
+
words=words
|
| 76 |
+
)
|
| 77 |
+
ctx["words"] = words_to_use
|
| 78 |
+
|
| 79 |
+
# Escala y etiquetas relacionadas
|
| 80 |
+
scale = EscalaController.getScaleByTechnique(technique=technique)
|
| 81 |
+
ctx["scale"] = scale
|
| 82 |
+
ctx["type_scale"] = scale.id_tipo_escala.nombre_escala
|
| 83 |
+
ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale)
|
| 84 |
+
|
| 85 |
+
return render(request, self.current_directory, ctx)
|
| 86 |
+
|
| 87 |
+
def controllGetRATA(self, request: HttpRequest):
|
| 88 |
+
technique = self.session.tecnica
|
| 89 |
+
self.participation = Participacion.objects.get(
|
| 90 |
+
tecnica=technique, catador=request.user.user_catador)
|
| 91 |
+
|
| 92 |
+
self.context["session"] = self.session
|
| 93 |
+
|
| 94 |
+
products_in_technique = Producto.objects.filter(id_tecnica=technique)
|
| 95 |
+
|
| 96 |
+
words = PalabrasController.getWordsInTechnique(technique=technique)
|
| 97 |
+
|
| 98 |
+
use_product: Producto = None
|
| 99 |
+
use_words: list[Palabra] = None
|
| 100 |
+
|
| 101 |
+
# Revisamos el producto que le falten calificaciones
|
| 102 |
+
for current_product in products_in_technique:
|
| 103 |
+
try:
|
| 104 |
+
rating = Calificacion.objects.get(
|
| 105 |
+
num_repeticion=technique.repeticion,
|
| 106 |
+
id_producto=current_product,
|
| 107 |
+
id_tecnica=technique,
|
| 108 |
+
id_catador=self.tester
|
| 109 |
+
)
|
| 110 |
+
there_rating = True
|
| 111 |
+
except Calificacion.DoesNotExist:
|
| 112 |
+
there_rating = False
|
| 113 |
+
|
| 114 |
+
# Si no hay calificacion mandamos el producto actual y todas la palabras
|
| 115 |
+
if not there_rating:
|
| 116 |
+
use_product = current_product
|
| 117 |
+
use_words = words
|
| 118 |
+
break
|
| 119 |
+
|
| 120 |
+
# Obtener los datos asociados para la calificacion para ver que palabras quedan por calificar
|
| 121 |
+
recoreded_data = rating.dato_calificacion.all()
|
| 122 |
+
|
| 123 |
+
if not recoreded_data:
|
| 124 |
+
# Si no hay datos entonces devolver el producto con todas las palabras
|
| 125 |
+
use_product = current_product
|
| 126 |
+
use_words = words
|
| 127 |
+
break
|
| 128 |
+
else:
|
| 129 |
+
words_to_use = PalabrasController.getWordsWithoutData(
|
| 130 |
+
recoreded_data=recoreded_data, words=words)
|
| 131 |
+
|
| 132 |
+
# Si quedan palabras por calificar mandar las palabras con el producto
|
| 133 |
+
if not isinstance(words_to_use, dict) and words_to_use:
|
| 134 |
+
use_product = current_product
|
| 135 |
+
use_words = words_to_use
|
| 136 |
+
break
|
| 137 |
+
|
| 138 |
+
# Si no hay producto que falta por calificar finalizar sesion para el Catador
|
| 139 |
+
if not use_product:
|
| 140 |
+
updated_participation = ParticipacionController.finishSession(
|
| 141 |
+
self.participation)
|
| 142 |
+
params = {
|
| 143 |
+
"code_sesion": self.session.codigo_sesion
|
| 144 |
+
}
|
| 145 |
+
return redirect(reverse(self.previus_directory, kwargs=params))
|
| 146 |
+
|
| 147 |
+
self.context["product"] = use_product
|
| 148 |
+
self.context["words"] = use_words
|
| 149 |
+
|
| 150 |
+
# Agregar informacion de la escala
|
| 151 |
+
scale = EscalaController.getScaleByTechnique(technique=technique)
|
| 152 |
+
self.context["scale"] = scale
|
| 153 |
+
self.context["type_scale"] = scale.id_tipo_escala.nombre_escala
|
| 154 |
+
|
| 155 |
+
use_tags = EscalaController.getRelatedTagsInScale(scale=scale)
|
| 156 |
+
self.context["tags"] = use_tags
|
| 157 |
+
|
| 158 |
+
return render(request, self.current_directory, self.context)
|
tecnicas/controllers/views_controller/{main_tester_form_controller.py → sessions_tester/init_session_tester_controller.py}
RENAMED
|
@@ -1,28 +1,101 @@
|
|
| 1 |
-
from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, Calificacion, EsVocabulario
|
| 2 |
-
from ...utils import controller_error, shuffleArray
|
| 3 |
from django.db import transaction
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
-
class
|
| 7 |
tester: Catador
|
| 8 |
session: SesionSensorial
|
| 9 |
order: Orden | dict
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
def assignOrder(self):
|
| 20 |
with transaction.atomic():
|
| 21 |
orders_without_tester = list(Orden.objects.select_for_update().filter(
|
| 22 |
id_tecnica=self.session.tecnica, id_catador=None))
|
| 23 |
|
| 24 |
-
print(orders_without_tester)
|
| 25 |
-
|
| 26 |
if not orders_without_tester:
|
| 27 |
return controller_error("Las ordenes se han acabado")
|
| 28 |
|
|
@@ -44,15 +117,16 @@ class MainTesterFormController():
|
|
| 44 |
return create
|
| 45 |
return self.order_to_assign
|
| 46 |
|
| 47 |
-
def
|
| 48 |
try:
|
| 49 |
participation = Participacion.objects.get(
|
| 50 |
catador=self.tester, tecnica=self.session.tecnica)
|
|
|
|
| 51 |
|
| 52 |
# ////////////////////////////////////////////////////////////// #
|
| 53 |
#
|
| 54 |
-
#
|
| 55 |
-
#
|
| 56 |
# Ha terminado la repeticion
|
| 57 |
#
|
| 58 |
# ////////////////////////////////////////////////////////////// #
|
|
@@ -61,23 +135,29 @@ class MainTesterFormController():
|
|
| 61 |
num_products = Producto.objects.filter(
|
| 62 |
id_tecnica=self.session.tecnica).count()
|
| 63 |
|
| 64 |
-
|
|
|
|
| 65 |
|
| 66 |
num_words: int
|
| 67 |
|
| 68 |
-
if style_words
|
| 69 |
num_words = EsAtributo.objects.get(
|
| 70 |
id_tecnica=self.session.tecnica).palabras.count()
|
| 71 |
-
elif style_words
|
| 72 |
num_words = EsVocabulario.objects.get(
|
| 73 |
id_tecnica=self.session.tecnica).id_vocabulario.palabras.count()
|
| 74 |
|
| 75 |
-
num_ratings_now = Calificacion.objects.filter(
|
| 76 |
-
id_tecnica=self.session.tecnica, id_catador=self.tester, num_repeticion=repetition).count()
|
| 77 |
-
|
| 78 |
expected_ratings_repetition = num_products * num_words
|
| 79 |
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
else:
|
| 82 |
return participation.finalizado
|
| 83 |
except Participacion.DoesNotExist:
|
|
|
|
|
|
|
|
|
|
| 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 tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, Calificacion, EsVocabulario, Dato
|
| 6 |
+
from tecnicas.controllers import ParticipacionController
|
| 7 |
+
from tecnicas.utils import controller_error, shuffleArray
|
| 8 |
|
| 9 |
|
| 10 |
+
class InitSessionTesterController():
|
| 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 |
+
|
| 17 |
+
def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador):
|
| 18 |
+
self.tester = user_tester
|
| 19 |
+
self.session = sensorial_session
|
| 20 |
+
|
| 21 |
+
def controllGetEscalas(self, request: HttpRequest):
|
| 22 |
+
context = {
|
| 23 |
+
"session": self.session,
|
| 24 |
+
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
order = self.checkAndAssignOrder()
|
| 28 |
+
if isinstance(order, dict):
|
| 29 |
+
context["error"] = order["error"]
|
| 30 |
+
return render(request, self.current_direction, context)
|
| 31 |
+
|
| 32 |
+
is_end = self.isEndedSessionEscalas()
|
| 33 |
+
|
| 34 |
+
request.session["id_order"] = order.id
|
| 35 |
+
context["has_ended"] = is_end
|
| 36 |
+
|
| 37 |
+
if is_end:
|
| 38 |
+
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 39 |
+
|
| 40 |
+
if "error" in request.GET:
|
| 41 |
+
context["error"] = request.GET["error"]
|
| 42 |
+
|
| 43 |
+
return render(request, self.current_direction, context)
|
| 44 |
+
|
| 45 |
+
def controllPostEscalas(self, request: HttpRequest):
|
| 46 |
+
context = {
|
| 47 |
+
"session": self.session,
|
| 48 |
+
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
if request.POST["action"] == "start_posting":
|
| 52 |
+
parameters = {
|
| 53 |
+
"code_sesion": self.session.codigo_sesion
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
is_end = self.isEndedSessionEscalas()
|
| 57 |
+
if is_end:
|
| 58 |
+
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 59 |
+
return render(request, self.current_direction, context)
|
| 60 |
+
|
| 61 |
+
update_participation = ParticipacionController.enterSession(
|
| 62 |
+
tester=request.user.user_catador, session=self.session)
|
| 63 |
+
if isinstance(update_participation, dict):
|
| 64 |
+
context["error"] = update_participation["error"]
|
| 65 |
+
return render(request, self.current_direction, context)
|
| 66 |
+
|
| 67 |
+
request.session["id_participation"] = update_participation.id
|
| 68 |
+
return redirect(reverse(self.escalas_direction, kwargs=parameters))
|
| 69 |
+
elif request.POST["action"] == "exit_session":
|
| 70 |
+
response = ParticipacionController.outSession(
|
| 71 |
+
tester=request.user.user_catador, session=self.session)
|
| 72 |
+
if isinstance(response, dict):
|
| 73 |
+
context["error"] = response["error"]
|
| 74 |
+
return render(request, self.current_direction, context)
|
| 75 |
+
else:
|
| 76 |
+
context["error"] = "Acción sin especificar"
|
| 77 |
+
return render(request, self.current_direction, context)
|
| 78 |
+
|
| 79 |
+
def controllGetRATA(self, request: HttpRequest):
|
| 80 |
+
context = {
|
| 81 |
+
"session": self.session,
|
| 82 |
+
"type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
is_end = self.isEndedSessionEscalas()
|
| 86 |
+
|
| 87 |
+
context["has_ended"] = is_end
|
| 88 |
+
|
| 89 |
+
if is_end:
|
| 90 |
+
context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador"
|
| 91 |
+
|
| 92 |
+
return render(request, self.current_direction, context)
|
| 93 |
|
| 94 |
def assignOrder(self):
|
| 95 |
with transaction.atomic():
|
| 96 |
orders_without_tester = list(Orden.objects.select_for_update().filter(
|
| 97 |
id_tecnica=self.session.tecnica, id_catador=None))
|
| 98 |
|
|
|
|
|
|
|
| 99 |
if not orders_without_tester:
|
| 100 |
return controller_error("Las ordenes se han acabado")
|
| 101 |
|
|
|
|
| 117 |
return create
|
| 118 |
return self.order_to_assign
|
| 119 |
|
| 120 |
+
def isEndedSessionEscalas(self):
|
| 121 |
try:
|
| 122 |
participation = Participacion.objects.get(
|
| 123 |
catador=self.tester, tecnica=self.session.tecnica)
|
| 124 |
+
self.session.refresh_from_db()
|
| 125 |
|
| 126 |
# ////////////////////////////////////////////////////////////// #
|
| 127 |
#
|
| 128 |
+
# numero_datos_esperadas = num_productos * num_palabras
|
| 129 |
+
# Si numero_datos_esperadas es igual a numero_datos_actuales en la repetcion R
|
| 130 |
# Ha terminado la repeticion
|
| 131 |
#
|
| 132 |
# ////////////////////////////////////////////////////////////// #
|
|
|
|
| 135 |
num_products = Producto.objects.filter(
|
| 136 |
id_tecnica=self.session.tecnica).count()
|
| 137 |
|
| 138 |
+
technique = self.session.tecnica
|
| 139 |
+
style_words = technique.id_estilo.nombre_estilo
|
| 140 |
|
| 141 |
num_words: int
|
| 142 |
|
| 143 |
+
if style_words == "atributos":
|
| 144 |
num_words = EsAtributo.objects.get(
|
| 145 |
id_tecnica=self.session.tecnica).palabras.count()
|
| 146 |
+
elif style_words == "vocabulario":
|
| 147 |
num_words = EsVocabulario.objects.get(
|
| 148 |
id_tecnica=self.session.tecnica).id_vocabulario.palabras.count()
|
| 149 |
|
|
|
|
|
|
|
|
|
|
| 150 |
expected_ratings_repetition = num_products * num_words
|
| 151 |
|
| 152 |
+
num_ratings_now = Dato.objects.filter(
|
| 153 |
+
id_calificacion__id_catador=self.tester,
|
| 154 |
+
id_calificacion__id_tecnica=technique,
|
| 155 |
+
id_calificacion__num_repeticion=technique.repeticion
|
| 156 |
+
).count()
|
| 157 |
+
|
| 158 |
+
is_end = num_ratings_now >= expected_ratings_repetition
|
| 159 |
+
|
| 160 |
+
return is_end
|
| 161 |
else:
|
| 162 |
return participation.finalizado
|
| 163 |
except Participacion.DoesNotExist:
|
tecnicas/controllers/views_controller/{list_sessions_tester_controller.py → sessions_tester/list_sessions_tester_controller.py}
RENAMED
|
File without changes
|
tecnicas/controllers/views_controller/{login_tester_controller.py → sessions_tester/login_session_tester_controller.py}
RENAMED
|
@@ -6,7 +6,7 @@ from tecnicas.models import Catador, SesionSensorial, Participacion
|
|
| 6 |
from tecnicas.utils import controller_error
|
| 7 |
|
| 8 |
|
| 9 |
-
class
|
| 10 |
tester: Catador
|
| 11 |
session: SesionSensorial
|
| 12 |
taster_participation: Participacion
|
|
@@ -33,38 +33,38 @@ class LoginTesterController():
|
|
| 33 |
context["error"] = "La sesión no está activa actualmente"
|
| 34 |
return render(request, self.current_direcction, context)
|
| 35 |
|
| 36 |
-
if self.session.tecnica.repeticion
|
| 37 |
try:
|
| 38 |
self.taster_participation = Participacion.objects.get(
|
| 39 |
tecnica=self.session.tecnica, catador=self.tester)
|
| 40 |
context["error"] = "Usted ya esta dentro de la sesión"
|
| 41 |
return render(request, self.current_direcction, context)
|
| 42 |
except Participacion.DoesNotExist:
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
code_session = self.session.codigo_sesion
|
| 48 |
-
self.session = SesionSensorial.objects.select_for_update().get(
|
| 49 |
-
codigo_sesion=code_session)
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
def validateEntryRATA(self, request: HttpRequest):
|
| 70 |
context = {}
|
|
|
|
| 6 |
from tecnicas.utils import controller_error
|
| 7 |
|
| 8 |
|
| 9 |
+
class LoginSessionTesterController():
|
| 10 |
tester: Catador
|
| 11 |
session: SesionSensorial
|
| 12 |
taster_participation: Participacion
|
|
|
|
| 33 |
context["error"] = "La sesión no está activa actualmente"
|
| 34 |
return render(request, self.current_direcction, context)
|
| 35 |
|
| 36 |
+
if self.session.tecnica.repeticion == 1:
|
| 37 |
try:
|
| 38 |
self.taster_participation = Participacion.objects.get(
|
| 39 |
tecnica=self.session.tecnica, catador=self.tester)
|
| 40 |
context["error"] = "Usted ya esta dentro de la sesión"
|
| 41 |
return render(request, self.current_direcction, context)
|
| 42 |
except Participacion.DoesNotExist:
|
| 43 |
+
with transaction.atomic():
|
| 44 |
+
code_session = self.session.codigo_sesion
|
| 45 |
+
self.session = SesionSensorial.objects.select_for_update().get(
|
| 46 |
+
codigo_sesion=code_session)
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
max_testers = self.session.tecnica.limite_catadores
|
| 49 |
+
current_num_testers = Participacion.objects.filter(
|
| 50 |
+
tecnica=self.session.tecnica).count()
|
| 51 |
|
| 52 |
+
if current_num_testers >= max_testers:
|
| 53 |
+
context["error"] = "La sesión ha alcanzado el número máximo de catadores"
|
| 54 |
+
return render(request, self.current_direcction, context)
|
| 55 |
|
| 56 |
+
self.taster_participation = Participacion.objects.create(
|
| 57 |
+
tecnica=self.session.tecnica,
|
| 58 |
+
catador=self.tester,
|
| 59 |
+
finalizado=False
|
| 60 |
+
)
|
| 61 |
+
params = {
|
| 62 |
+
"code_sesion": self.session.codigo_sesion
|
| 63 |
+
}
|
| 64 |
+
return redirect(reverse(self.destinity_direcction, kwargs=params))
|
| 65 |
+
else:
|
| 66 |
+
context["error"] = "Ya no es posible ingresar a la sesión"
|
| 67 |
+
return render(request, self.current_direcction, context)
|
| 68 |
|
| 69 |
def validateEntryRATA(self, request: HttpRequest):
|
| 70 |
context = {}
|
tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render
|
| 2 |
+
from django.db import IntegrityError
|
| 3 |
+
from django.http import HttpRequest
|
| 4 |
+
from tecnicas.forms import WordForm
|
| 5 |
+
from tecnicas.models import Vocabulario, Palabra
|
| 6 |
+
import json
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CreateVocabularyController():
|
| 10 |
+
context = {}
|
| 11 |
+
current_url = "tecnicas/manage_vocabulary/create-vocabulary.html"
|
| 12 |
+
|
| 13 |
+
def __init__(self, form_word: WordForm = WordForm(), list_words: list = []):
|
| 14 |
+
self.context["form_word"] = form_word
|
| 15 |
+
self.context["words"] = list_words
|
| 16 |
+
|
| 17 |
+
def controllGet(self, request: HttpRequest):
|
| 18 |
+
self.context = {
|
| 19 |
+
"form_word": WordForm(),
|
| 20 |
+
"words": []
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
if "name_voca" in request.GET:
|
| 24 |
+
current = Vocabulario.objects.get(
|
| 25 |
+
nombre_vocabulario=request.GET["name_voca"])
|
| 26 |
+
self.context["name_vacabulary"] = current.nombre_vocabulario
|
| 27 |
+
self.context["words"] = current.palabras.all()
|
| 28 |
+
|
| 29 |
+
return render(request, self.current_url, self.context)
|
| 30 |
+
|
| 31 |
+
def controllPost(self, request: HttpRequest):
|
| 32 |
+
self.context = {
|
| 33 |
+
"form_word": self.context["form_word"],
|
| 34 |
+
"words": self.context["words"],
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
if "nombre_vocabulario" not in request.POST:
|
| 38 |
+
self.context["error"] = "Nombre de vocabulario requerido"
|
| 39 |
+
return render(request, self.current_url, self.context)
|
| 40 |
+
|
| 41 |
+
new_vocabulary_name = request.POST.get("nombre_vocabulario").strip()
|
| 42 |
+
is_update = request.POST.get("is_update")
|
| 43 |
+
|
| 44 |
+
print(is_update)
|
| 45 |
+
|
| 46 |
+
if is_update:
|
| 47 |
+
if "original_name" not in request.POST:
|
| 48 |
+
self.context["error"] = "Nombre original de vocabulario requerido"
|
| 49 |
+
return render(request, self.current_url, self.context)
|
| 50 |
+
|
| 51 |
+
original_name = request.POST["original_name"].strip()
|
| 52 |
+
|
| 53 |
+
if original_name != new_vocabulary_name:
|
| 54 |
+
if Vocabulario.objects.filter(nombre_vocabulario=new_vocabulary_name).exists():
|
| 55 |
+
self.context["error"] = "Ya existe un vocabulario con el nombre pasado"
|
| 56 |
+
return render(request, self.current_url, self.context)
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
current_vocababulary = Vocabulario.objects.get(
|
| 60 |
+
nombre_vocabulario=original_name)
|
| 61 |
+
except Vocabulario.DoesNotExist:
|
| 62 |
+
self.context["error"] = "No existe un vocabulario con ese nombre"
|
| 63 |
+
return render(request, self.current_url, self.context)
|
| 64 |
+
|
| 65 |
+
words_json = request.POST.get("words", "")
|
| 66 |
+
if words_json:
|
| 67 |
+
try:
|
| 68 |
+
words_list = json.loads(words_json)
|
| 69 |
+
|
| 70 |
+
ids = [int(w.get("id", 0))
|
| 71 |
+
for w in words_list if str(w.get("id", "")).isdigit()]
|
| 72 |
+
|
| 73 |
+
words = Palabra.objects.filter(id__in=ids)
|
| 74 |
+
|
| 75 |
+
current_vocababulary.palabras.set(words)
|
| 76 |
+
current_vocababulary.nombre_vocabulario = new_vocabulary_name
|
| 77 |
+
current_vocababulary.save()
|
| 78 |
+
except (json.JSONDecodeError, ValueError):
|
| 79 |
+
self.context["error"] = 'Ocurrió un error al revisar las palabras, revise “Ver vocabularios”, para reasignar las palabras'
|
| 80 |
+
return render(request, self.current_url, self.context)
|
| 81 |
+
|
| 82 |
+
self.context["message"] = 'Vocabulario creado con éxito, puedes revisarlo en "Ver vocabularios"'
|
| 83 |
+
return render(request, self.current_url, self.context)
|
| 84 |
+
elif not is_update:
|
| 85 |
+
try:
|
| 86 |
+
new_vocababulary = Vocabulario.objects.create(
|
| 87 |
+
nombre_vocabulario=new_vocabulary_name)
|
| 88 |
+
except IntegrityError:
|
| 89 |
+
self.context["error"] = "Ya existe un vocabulario con ese nombre"
|
| 90 |
+
return render(request, self.current_url, self.context)
|
| 91 |
+
|
| 92 |
+
words_json = request.POST.get("words", "")
|
| 93 |
+
if words_json:
|
| 94 |
+
try:
|
| 95 |
+
words_list = json.loads(words_json)
|
| 96 |
+
|
| 97 |
+
ids = [int(w.get("id", 0))
|
| 98 |
+
for w in words_list if str(w.get("id", "")).isdigit()]
|
| 99 |
+
|
| 100 |
+
words = Palabra.objects.filter(id__in=ids)
|
| 101 |
+
|
| 102 |
+
new_vocababulary.palabras.add(*words)
|
| 103 |
+
except (json.JSONDecodeError, ValueError):
|
| 104 |
+
self.context["error"] = 'Ocurrió un error al revisar las palabras, revise “Ver vocabularios”, para reasignar las palabras'
|
| 105 |
+
return render(request, self.current_url, self.context)
|
| 106 |
+
|
| 107 |
+
self.context["message"] = 'Vocabulario actualziado con éxito, puedes revisarlo en "Ver vocabularios"'
|
| 108 |
+
return render(request, self.current_url, self.context)
|
| 109 |
+
else:
|
| 110 |
+
pass
|
tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render
|
| 2 |
+
from django.http import HttpRequest
|
| 3 |
+
from django.core.paginator import Paginator, PageNotAnInteger
|
| 4 |
+
from tecnicas.models import Vocabulario
|
| 5 |
+
from tecnicas.utils import controller_error
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class ListVocabularyController():
|
| 9 |
+
current_url = "tecnicas/manage_vocabulary/list-vocabulary.html"
|
| 10 |
+
|
| 11 |
+
def __init__(self):
|
| 12 |
+
pass
|
| 13 |
+
|
| 14 |
+
def controllGet(self, request: HttpRequest, page: int):
|
| 15 |
+
context = {}
|
| 16 |
+
|
| 17 |
+
info_element_page = self.getVocabularys(page)
|
| 18 |
+
|
| 19 |
+
if isinstance(info_element_page, dict):
|
| 20 |
+
context["error"] = info_element_page["error"]
|
| 21 |
+
return render(request, self.current_url, context)
|
| 22 |
+
|
| 23 |
+
(vocabularies_in_page, is_last_page, current_page) = info_element_page
|
| 24 |
+
|
| 25 |
+
context["vocabularies"] = vocabularies_in_page
|
| 26 |
+
context["last_page"] = is_last_page
|
| 27 |
+
context["num_page"] = current_page
|
| 28 |
+
|
| 29 |
+
return render(request, self.current_url, context)
|
| 30 |
+
|
| 31 |
+
def getVocabularys(self, num_page: int):
|
| 32 |
+
elements_by_page = 6
|
| 33 |
+
|
| 34 |
+
queryset = Vocabulario.objects.all().order_by('-creado')
|
| 35 |
+
|
| 36 |
+
paginator = Paginator(queryset, elements_by_page)
|
| 37 |
+
try:
|
| 38 |
+
vocabularies_in_page = paginator.page(num_page)
|
| 39 |
+
except PageNotAnInteger:
|
| 40 |
+
return controller_error("índice inválido")
|
| 41 |
+
|
| 42 |
+
if not vocabularies_in_page.object_list:
|
| 43 |
+
return controller_error("Sin registros de Participaciones")
|
| 44 |
+
|
| 45 |
+
current_page = vocabularies_in_page.number
|
| 46 |
+
is_last_page = not current_page < paginator.num_pages
|
| 47 |
+
|
| 48 |
+
return (vocabularies_in_page, is_last_page, current_page)
|
tecnicas/forms/__init__.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
| 1 |
-
from .sesion_basic_form import SesionBasicForm
|
| 2 |
-
from .
|
|
|
|
|
|
|
| 3 |
from .etiqueta_form import EtiquetaForm
|
| 4 |
from .codes_form import CodesForm
|
| 5 |
from .catador_form import CatadorForm
|
| 6 |
-
from .word_form import WordForm
|
|
|
|
|
|
| 1 |
+
from .create_session.sesion_basic_form import SesionBasicForm
|
| 2 |
+
from .create_session.sesiob_basic_cata_form import SesionBasicCATAForm
|
| 3 |
+
from .create_session.sesion_tags_form import SesionTagsForm
|
| 4 |
+
|
| 5 |
from .etiqueta_form import EtiquetaForm
|
| 6 |
from .codes_form import CodesForm
|
| 7 |
from .catador_form import CatadorForm
|
| 8 |
+
from .word_form import WordForm
|
| 9 |
+
from .vocabulary_select import VocabularioSelectForm
|
tecnicas/forms/create_session/sesiob_basic_cata_form.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django import forms
|
| 2 |
+
from tecnicas.models import EstiloPalabra
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class SesionBasicCATAForm(forms.Form):
|
| 6 |
+
nombre_sesion = forms.CharField(max_length=255, widget=forms.TextInput(attrs={
|
| 7 |
+
"class": "bg-surface-ligt border-b-1 text-center w-full p-1",
|
| 8 |
+
"name": "nombre_sesion",
|
| 9 |
+
"placeholder": "Ej. Mermelada de mango picante"
|
| 10 |
+
}), required=False)
|
| 11 |
+
|
| 12 |
+
numero_productos = forms.IntegerField(widget=forms.NumberInput(attrs={
|
| 13 |
+
"class": "bg-surface-ligt p-1 border-b-1 text-center w-full",
|
| 14 |
+
"placeholder": "Solo números"
|
| 15 |
+
}), required=True)
|
| 16 |
+
|
| 17 |
+
instrucciones = forms.CharField(max_length=255, widget=forms.TextInput(attrs={
|
| 18 |
+
"class": "bg-surface-ligt border-b-1 text-center w-full p-1",
|
| 19 |
+
"placeholder": "Este campo es opcional"
|
| 20 |
+
}), required=False)
|
| 21 |
+
|
| 22 |
+
def __init__(self, *args, **kwargs):
|
| 23 |
+
super().__init__(*args, **kwargs)
|
| 24 |
+
|
| 25 |
+
self.fields['estilo_palabras'] = forms.ModelChoiceField(queryset=EstiloPalabra.objects.all(), widget=forms.RadioSelect(attrs={
|
| 26 |
+
"class": "uppercase text-lg tracking-wider font-medium p-2 px-4 active:px-5 transition-all rounded-xl bg-blue-500 text-white",
|
| 27 |
+
}), required=True, initial=EstiloPalabra.objects.first())
|
tecnicas/forms/{sesion_basic_form.py → create_session/sesion_basic_form.py}
RENAMED
|
@@ -1,7 +1,5 @@
|
|
| 1 |
from django import forms
|
| 2 |
-
from
|
| 3 |
-
from ..models import TipoTecnica
|
| 4 |
-
from ..models import EstiloPalabra
|
| 5 |
|
| 6 |
|
| 7 |
class SesionBasicForm(forms.Form):
|
|
|
|
| 1 |
from django import forms
|
| 2 |
+
from tecnicas.models import TipoEscala, EstiloPalabra
|
|
|
|
|
|
|
| 3 |
|
| 4 |
|
| 5 |
class SesionBasicForm(forms.Form):
|
tecnicas/forms/{sesion_tags_form.py → create_session/sesion_tags_form.py}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from django import forms
|
| 2 |
-
from
|
| 3 |
|
| 4 |
|
| 5 |
class SesionTagsForm(forms.Form):
|
|
|
|
| 1 |
from django import forms
|
| 2 |
+
from tecnicas.models import Etiqueta
|
| 3 |
|
| 4 |
|
| 5 |
class SesionTagsForm(forms.Form):
|
tecnicas/forms/vocabulary_select.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django import forms
|
| 2 |
+
from tecnicas.models import Vocabulario
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class VocabularioSelectForm(forms.Form):
|
| 6 |
+
vocabulario = forms.ModelChoiceField(
|
| 7 |
+
queryset=Vocabulario.objects.all(),
|
| 8 |
+
required=True,
|
| 9 |
+
label="Selecciona un vocabulario",
|
| 10 |
+
empty_label="-- Selecciona uno --",
|
| 11 |
+
widget=forms.Select(attrs={
|
| 12 |
+
"class": "w-full border rounded p-4 bg-surface-sweet",
|
| 13 |
+
"id": "vocabulario"
|
| 14 |
+
})
|
| 15 |
+
)
|
tecnicas/migrations/0021_rename_nomre_vocabulario_vocabulario_nombre_vocabulario_and_more.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.2.1 on 2025-11-06 21:46
|
| 2 |
+
|
| 3 |
+
import django.utils.timezone
|
| 4 |
+
import shortuuid.main
|
| 5 |
+
from django.db import migrations, models
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Migration(migrations.Migration):
|
| 9 |
+
|
| 10 |
+
dependencies = [
|
| 11 |
+
('tecnicas', '0020_remove_catador_apellido_remove_catador_correo_and_more'),
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
operations = [
|
| 15 |
+
migrations.RenameField(
|
| 16 |
+
model_name='vocabulario',
|
| 17 |
+
old_name='nomre_vocabulario',
|
| 18 |
+
new_name='nombre_vocabulario',
|
| 19 |
+
),
|
| 20 |
+
migrations.AddField(
|
| 21 |
+
model_name='vocabulario',
|
| 22 |
+
name='creado',
|
| 23 |
+
field=models.DateTimeField(default=django.utils.timezone.now),
|
| 24 |
+
),
|
| 25 |
+
migrations.AlterField(
|
| 26 |
+
model_name='sesionsensorial',
|
| 27 |
+
name='codigo_sesion',
|
| 28 |
+
field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False),
|
| 29 |
+
),
|
| 30 |
+
]
|
tecnicas/models/calificacion.py
CHANGED
|
@@ -8,4 +8,7 @@ class Calificacion(models.Model):
|
|
| 8 |
num_repeticion = models.IntegerField()
|
| 9 |
id_producto = models.ForeignKey(Producto, on_delete=models.CASCADE, related_name="calificacion_producto")
|
| 10 |
id_tecnica = models.ForeignKey(Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica")
|
| 11 |
-
id_catador = models.ForeignKey(Catador, on_delete=models.CASCADE, related_name="calificacion_catador")
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
num_repeticion = models.IntegerField()
|
| 9 |
id_producto = models.ForeignKey(Producto, on_delete=models.CASCADE, related_name="calificacion_producto")
|
| 10 |
id_tecnica = models.ForeignKey(Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica")
|
| 11 |
+
id_catador = models.ForeignKey(Catador, on_delete=models.CASCADE, related_name="calificacion_catador")
|
| 12 |
+
|
| 13 |
+
def __str__(self):
|
| 14 |
+
return f"{self.id} - {self.id_tecnica.sesion_tecnica} - {self.num_repeticion} - {self.id_catador.user.username}"
|
tecnicas/models/dato.py
CHANGED
|
@@ -11,4 +11,4 @@ class Dato(models.Model):
|
|
| 11 |
Calificacion, on_delete=models.CASCADE, related_name="dato_calificacion")
|
| 12 |
|
| 13 |
def __str__(self):
|
| 14 |
-
return f"{self.id_palabra.nombre_palabra} - {self.id_calificacion.id_producto.codigoProducto} - {self.id_calificacion.id_catador.
|
|
|
|
| 11 |
Calificacion, on_delete=models.CASCADE, related_name="dato_calificacion")
|
| 12 |
|
| 13 |
def __str__(self):
|
| 14 |
+
return f"{self.id_calificacion.id_tecnica.sesion_tecnica}- {self.id_calificacion.num_repeticion} - {self.id_palabra.nombre_palabra} - {self.id_calificacion.id_producto.codigoProducto} - {self.id_calificacion.id_catador.user.username}"
|
tecnicas/models/dato_valor.py
CHANGED
|
@@ -9,7 +9,7 @@ class ValorDecimal(models.Model):
|
|
| 9 |
valor = models.FloatField()
|
| 10 |
|
| 11 |
def __str__(self):
|
| 12 |
-
return f"{self.id} - {self.id_dato.id_palabra}: {self.valor} - {self.id_dato.id_calificacion.id_catador.
|
| 13 |
|
| 14 |
|
| 15 |
class ValorBooleano(models.Model):
|
|
@@ -18,4 +18,4 @@ class ValorBooleano(models.Model):
|
|
| 18 |
valor = models.BooleanField()
|
| 19 |
|
| 20 |
def __str__(self):
|
| 21 |
-
return f"{self.id} - {self.id_dato.id_palabra}: {self.valor} - {self.id_dato.id_calificacion.id_catador.
|
|
|
|
| 9 |
valor = models.FloatField()
|
| 10 |
|
| 11 |
def __str__(self):
|
| 12 |
+
return f"{self.id} - {self.id_dato.id_palabra}: {self.valor} - {self.id_dato.id_calificacion.id_catador.user.username}"
|
| 13 |
|
| 14 |
|
| 15 |
class ValorBooleano(models.Model):
|
|
|
|
| 18 |
valor = models.BooleanField()
|
| 19 |
|
| 20 |
def __str__(self):
|
| 21 |
+
return f"{self.id} - {self.id_dato.id_palabra}: {self.valor} - {self.id_dato.id_calificacion.id_catador.user.username}"
|
tecnicas/models/vocabulario.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
from django.db import models
|
| 2 |
-
|
| 3 |
from .palabra import Palabra
|
| 4 |
|
|
|
|
| 5 |
class Vocabulario(models.Model):
|
| 6 |
-
|
| 7 |
-
palabras = models.ManyToManyField(
|
|
|
|
|
|
|
| 8 |
|
| 9 |
def __str__(self):
|
| 10 |
-
return self.
|
|
|
|
| 1 |
from django.db import models
|
| 2 |
+
from django.utils import timezone
|
| 3 |
from .palabra import Palabra
|
| 4 |
|
| 5 |
+
|
| 6 |
class Vocabulario(models.Model):
|
| 7 |
+
nombre_vocabulario = models.CharField(max_length=255, unique=True)
|
| 8 |
+
palabras = models.ManyToManyField(
|
| 9 |
+
Palabra, related_name="vovabulario_palabras")
|
| 10 |
+
creado = models.DateTimeField(default=timezone.now)
|
| 11 |
|
| 12 |
def __str__(self):
|
| 13 |
+
return self.nombre_vocabulario
|
tecnicas/static/img/letters.webp
ADDED
|
tecnicas/static/js/choose-vocabulary.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener("DOMContentLoaded", () => {
|
| 2 |
+
const vocabSelect = document.getElementById("vocabulario");
|
| 3 |
+
const wordsList = document.getElementById("palabras-lista");
|
| 4 |
+
const formNextStep = document.getElementById("cts-create-session");
|
| 5 |
+
let vocabularyId = "";
|
| 6 |
+
|
| 7 |
+
vocabSelect.addEventListener("change", async (e) => {
|
| 8 |
+
const vocabId = e.target.value;
|
| 9 |
+
vocabularyId = vocabId;
|
| 10 |
+
console.log(e.target);
|
| 11 |
+
console.log(vocabId);
|
| 12 |
+
|
| 13 |
+
wordsList.innerHTML =
|
| 14 |
+
"<li class='text-gray-500 text-center'>Cargando...</li>";
|
| 15 |
+
|
| 16 |
+
if (!vocabId) {
|
| 17 |
+
wordsList.innerHTML =
|
| 18 |
+
"<li class='text-gray-500 text-center'>Selecciona un vocabulario para ver sus palabras</li>";
|
| 19 |
+
return;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
url_fetch = `api/vocabulario/${vocabId}/palabras`;
|
| 23 |
+
|
| 24 |
+
try {
|
| 25 |
+
const response = await fetch(url_fetch, { method: "GET" });
|
| 26 |
+
if (!response.ok) throw new Error("Error en la petición");
|
| 27 |
+
const json_response = await response.json();
|
| 28 |
+
|
| 29 |
+
words = json_response.data.words;
|
| 30 |
+
|
| 31 |
+
if (words === 0) {
|
| 32 |
+
wordsList.innerHTML =
|
| 33 |
+
"<li class='text-center text-lg'>No hay palabras asociadas</li>";
|
| 34 |
+
return;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
wordsList.innerHTML = "";
|
| 38 |
+
words.forEach((p) => {
|
| 39 |
+
const li = document.createElement("li");
|
| 40 |
+
li.textContent = p.nombre_palabra;
|
| 41 |
+
li.className =
|
| 42 |
+
"bg-surface-sweet text-black rounded font-bold text-lg px-4 py-3";
|
| 43 |
+
wordsList.appendChild(li);
|
| 44 |
+
});
|
| 45 |
+
} catch (err) {
|
| 46 |
+
wordsList.innerHTML =
|
| 47 |
+
"<li class='text-red-500 text-center text-lg'>Error al cargar las palabras</li>";
|
| 48 |
+
console.error(err);
|
| 49 |
+
}
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
formNextStep.addEventListener("submit", (e) => {
|
| 53 |
+
if (!vocabularyId) {
|
| 54 |
+
e.preventDefault();
|
| 55 |
+
wordsList.innerHTML =
|
| 56 |
+
"<li class='text-center text-lg'>Seleccione un Vocabulario</li>";
|
| 57 |
+
return;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
const useVocabulary = document.createElement("input");
|
| 61 |
+
useVocabulary.type = "hidden";
|
| 62 |
+
useVocabulary.name = "vocabulario";
|
| 63 |
+
useVocabulary.value = vocabularyId;
|
| 64 |
+
|
| 65 |
+
formNextStep.appendChild(useVocabulary);
|
| 66 |
+
});
|
| 67 |
+
});
|
tecnicas/static/js/created-scale.js
CHANGED
|
@@ -54,11 +54,13 @@ async function sendRating(word) {
|
|
| 54 |
.querySelector(".id-product").textContent;
|
| 55 |
|
| 56 |
const idWord = formRatingWord.querySelector(".id-word").textContent;
|
|
|
|
| 57 |
|
| 58 |
dataForm.set("code-product", codeProduct);
|
| 59 |
dataForm.set("id-product", idProduct);
|
| 60 |
dataForm.set("name-word", word);
|
| 61 |
dataForm.set("id-word", idWord);
|
|
|
|
| 62 |
|
| 63 |
try {
|
| 64 |
const respone = await fetch(url, {
|
|
|
|
| 54 |
.querySelector(".id-product").textContent;
|
| 55 |
|
| 56 |
const idWord = formRatingWord.querySelector(".id-word").textContent;
|
| 57 |
+
const idTechnique = document.querySelector(".ct-input-id-tech").value
|
| 58 |
|
| 59 |
dataForm.set("code-product", codeProduct);
|
| 60 |
dataForm.set("id-product", idProduct);
|
| 61 |
dataForm.set("name-word", word);
|
| 62 |
dataForm.set("id-word", idWord);
|
| 63 |
+
dataForm.set("id-technique", idTechnique);
|
| 64 |
|
| 65 |
try {
|
| 66 |
const respone = await fetch(url, {
|
tecnicas/static/js/created-vocabulary.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// **************************************
|
| 2 |
+
// Create Vocabulary
|
| 3 |
+
// **************************************
|
| 4 |
+
async function submitSelectWords(classNanmeForm, update = false) {
|
| 5 |
+
const form = document.querySelector(`.${classNanmeForm}`);
|
| 6 |
+
|
| 7 |
+
const name_vocabulary = form.querySelector(".cts-name-voca").value;
|
| 8 |
+
if (!name_vocabulary || name_vocabulary == "") {
|
| 9 |
+
spanNotificationRed("Se requiere el nombre del vocabulario");
|
| 10 |
+
return;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
if (listWordsSelect.length === 0) {
|
| 14 |
+
spanNotificationRed("Debe seleccionar al menos una palabra");
|
| 15 |
+
return;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
const wordsInput = document.createElement("input");
|
| 19 |
+
wordsInput.type = "hidden";
|
| 20 |
+
wordsInput.name = "words";
|
| 21 |
+
wordsInput.value = JSON.stringify(listWordsSelect);
|
| 22 |
+
|
| 23 |
+
const [isUpdata, orinalName] = inputIsUpdateVocabulary(update);
|
| 24 |
+
|
| 25 |
+
form.appendChild(wordsInput);
|
| 26 |
+
form.appendChild(isUpdata);
|
| 27 |
+
if (orinalName) form.appendChild(orinalName);
|
| 28 |
+
|
| 29 |
+
form.submit();
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
function inputIsUpdateVocabulary(is_update = false) {
|
| 33 |
+
const isUpdata = document.createElement("input");
|
| 34 |
+
isUpdata.type = "hidden";
|
| 35 |
+
isUpdata.name = "is_update";
|
| 36 |
+
isUpdata.value = is_update;
|
| 37 |
+
|
| 38 |
+
if (is_update) {
|
| 39 |
+
const orinalName = document.querySelector(".cts-original-name").textContent;
|
| 40 |
+
const inputName = document.createElement("input");
|
| 41 |
+
inputName.type = "hidden";
|
| 42 |
+
inputName.name = "original_name";
|
| 43 |
+
inputName.value = orinalName;
|
| 44 |
+
return [isUpdata, inputName];
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
return [isUpdata, is_update];
|
| 48 |
+
}
|
tecnicas/static/js/download-table-csv.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener("DOMContentLoaded", function () {
|
| 2 |
+
const btn = document.getElementById("download-csv-btn");
|
| 3 |
+
if (!btn) return;
|
| 4 |
+
|
| 5 |
+
btn.addEventListener("click", function () {
|
| 6 |
+
// Try set the table in the page
|
| 7 |
+
let table = document.getElementById("convencional-table");
|
| 8 |
+
if (!table) {
|
| 9 |
+
const section = btn.closest("section");
|
| 10 |
+
if (section) table = section.querySelector("table");
|
| 11 |
+
}
|
| 12 |
+
if (!table) {
|
| 13 |
+
console.warn("No se encontró la tabla para descargar.");
|
| 14 |
+
return;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
// helper to trim and normalize cell text
|
| 18 |
+
const cellText = (cell) => {
|
| 19 |
+
if (!cell) return "";
|
| 20 |
+
return String(cell.textContent || "").trim();
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
// Collect headers
|
| 24 |
+
const headers = [];
|
| 25 |
+
const ths = table.querySelectorAll("thead th");
|
| 26 |
+
ths.forEach((th) => headers.push(cellText(th)));
|
| 27 |
+
|
| 28 |
+
// Collect rows
|
| 29 |
+
const rows = [];
|
| 30 |
+
const trs = table.querySelectorAll("tbody tr");
|
| 31 |
+
trs.forEach((tr) => {
|
| 32 |
+
const cols = [];
|
| 33 |
+
const tds = tr.querySelectorAll("td");
|
| 34 |
+
tds.forEach((td) => cols.push(cellText(td)));
|
| 35 |
+
rows.push(cols);
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
// Convert to CSV string (escape quotes, wrap in quotes if needed)
|
| 39 |
+
const escapeValue = (val) => {
|
| 40 |
+
if (val == null) return "";
|
| 41 |
+
normalVal = val.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
| 42 |
+
const needsQuotes = /[",\n,]/.test(normalVal);
|
| 43 |
+
let v = String(normalVal).replace(/"/g, '""');
|
| 44 |
+
if (needsQuotes) v = `"${v}"`;
|
| 45 |
+
return v;
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
const lines = [];
|
| 49 |
+
if (headers.length) lines.push(headers.map(escapeValue).join(","));
|
| 50 |
+
rows.forEach((r) => lines.push(r.map(escapeValue).join(",")));
|
| 51 |
+
|
| 52 |
+
const csvContent = lines.join("\n");
|
| 53 |
+
|
| 54 |
+
// File name: data_{nombre_sesion or codigo_sesion}
|
| 55 |
+
const rawName = (btn.dataset.sessionName || "").trim();
|
| 56 |
+
const code = (btn.dataset.sessionCode || "").trim() || "session";
|
| 57 |
+
const namePart = rawName
|
| 58 |
+
? rawName.replace(/[^a-zA-Z0-9-_áéíóúÁÉÍÓÚ ]/g, "").replace(/\s+/g, "_")
|
| 59 |
+
: code;
|
| 60 |
+
const fileName = `data_${namePart}.csv`;
|
| 61 |
+
|
| 62 |
+
// Create blob and force download
|
| 63 |
+
const blob = new Blob([csvContent], { type: "text/csv;charset=UTF-8;" });
|
| 64 |
+
if (navigator.msSaveBlob) {
|
| 65 |
+
navigator.msSaveBlob(blob, fileName);
|
| 66 |
+
} else {
|
| 67 |
+
const link = document.createElement("a");
|
| 68 |
+
const url = URL.createObjectURL(blob);
|
| 69 |
+
link.setAttribute("href", url);
|
| 70 |
+
link.setAttribute("download", fileName);
|
| 71 |
+
link.style.visibility = "hidden";
|
| 72 |
+
document.body.appendChild(link);
|
| 73 |
+
link.click();
|
| 74 |
+
document.body.removeChild(link);
|
| 75 |
+
URL.revokeObjectURL(url);
|
| 76 |
+
}
|
| 77 |
+
});
|
| 78 |
+
});
|
tecnicas/static/js/panel-basic-cata.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const descriptons = {
|
| 2 |
+
atributos:
|
| 3 |
+
"Con el estilo atributos elijes las palabras para evaluar los productos",
|
| 4 |
+
vocabulario:
|
| 5 |
+
"Los vocabularios son un conjunto de palabras específicas para evaluar aspectos de un producto",
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
const helpStyleWords = document.querySelector(".cts-help-style-words");
|
| 9 |
+
let radiosStyleWords;
|
| 10 |
+
|
| 11 |
+
function initRadiosStyleWords() {
|
| 12 |
+
radiosStyleWords = document.getElementsByName("estilo_palabras");
|
| 13 |
+
|
| 14 |
+
for (let index = 0; index < radiosStyleWords.length; index++) {
|
| 15 |
+
const radio = radiosStyleWords[index];
|
| 16 |
+
|
| 17 |
+
if (radio.checked) {
|
| 18 |
+
const radioOption = radio.parentElement.textContent.trim();
|
| 19 |
+
const textHelp = descriptons[radioOption];
|
| 20 |
+
helpStyleWords.textContent = textHelp;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
radio.parentElement.addEventListener("click", (e) => {
|
| 24 |
+
const radioOption = radio.parentElement.textContent.trim();
|
| 25 |
+
const textHelp = descriptons[radioOption];
|
| 26 |
+
helpStyleWords.textContent = textHelp;
|
| 27 |
+
});
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
initRadiosStyleWords();
|
tecnicas/static/js/panel-words.js
CHANGED
|
@@ -28,7 +28,7 @@ async function getWordsByName(e) {
|
|
| 28 |
palabra: dataForm.get("search").trim(),
|
| 29 |
});
|
| 30 |
|
| 31 |
-
const url =
|
| 32 |
|
| 33 |
try {
|
| 34 |
const respone = await fetch(url, {
|
|
|
|
| 28 |
palabra: dataForm.get("search").trim(),
|
| 29 |
});
|
| 30 |
|
| 31 |
+
const url = `api/palabras?${params}`;
|
| 32 |
|
| 33 |
try {
|
| 34 |
const respone = await fetch(url, {
|
tecnicas/templates/tecnicas/components/form-scale-continue.html
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<form action="" method="post" class="form-rating-{{word}} w-full">
|
| 2 |
+
{% csrf_token %}
|
| 3 |
+
<article class="overflow-x-scroll w-full">
|
| 4 |
+
<article class="bg-surface-card p-6 rounded-lg mb-3 w-fit">
|
| 5 |
+
<label for="id-range-word-{{word}}"
|
| 6 |
+
class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
|
| 7 |
+
|
| 8 |
+
<span class="hidden id-word">{{ word.id }}</span>
|
| 9 |
+
|
| 10 |
+
<section class="block">
|
| 11 |
+
<div class="relative mx-6">
|
| 12 |
+
<input type="range" type="range" min="0" max="1000" value="500" name="rating-word"
|
| 13 |
+
class="range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0]"
|
| 14 |
+
style="width: {{scale.longitud}}cm;">
|
| 15 |
+
|
| 16 |
+
<div class="absolute top-0 left-0 w-0.5 transform -translate-x-1/2 h-full bg-red-500 z-10">
|
| 17 |
+
</div>
|
| 18 |
+
|
| 19 |
+
<div class="absolute top-1/5 left-1/2 w-0.5 transform -translate-x-1/2 h-4/5 bg-red-500 z-10">
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
<div class="absolute top-0 right-0 w-0.5 transform -translate-x-1/2 h-full bg-red-500 z-10">
|
| 23 |
+
</div>
|
| 24 |
+
</div>
|
| 25 |
+
|
| 26 |
+
<div class="flex justify-between mt-4">
|
| 27 |
+
<div class="flex flex-col items-start text-left w-[100px]">
|
| 28 |
+
<span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
|
| 29 |
+
{{ tags.0.id_etiqueta }}
|
| 30 |
+
</span>
|
| 31 |
+
</div>
|
| 32 |
+
<div class="flex flex-col items-start text-center w-[100px]">
|
| 33 |
+
<span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
|
| 34 |
+
{{ tags.1.id_etiqueta }}
|
| 35 |
+
</span>
|
| 36 |
+
</div>
|
| 37 |
+
<div class="flex flex-col items-start text-right w-[100px]">
|
| 38 |
+
<span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
|
| 39 |
+
{{ tags.2.id_etiqueta }}
|
| 40 |
+
</span>
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
</section>
|
| 44 |
+
</article>
|
| 45 |
+
</article>
|
| 46 |
+
<article class="flex flex-col justify-center gap-2 items-end actions-{{word}}">
|
| 47 |
+
<section class="flex justify-end items-center gap-2 btns-container">
|
| 48 |
+
<button type="button" onclick="checkSendRating('{{word}}')"
|
| 49 |
+
class="ct-btn-check-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-secondary btn-push">
|
| 50 |
+
¿Guardar calificación?
|
| 51 |
+
</button>
|
| 52 |
+
<button type="button" onclick="sendRating('{{word}}')"
|
| 53 |
+
class="ct-btn-submit-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-primary btn-push hidden">
|
| 54 |
+
Estoy seguro
|
| 55 |
+
</button>
|
| 56 |
+
<button type="button" onclick="cancelSendRating('{{word}}')"
|
| 57 |
+
class="ct-btn-cancel-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-error btn-push hidden">
|
| 58 |
+
Cancelar
|
| 59 |
+
</button>
|
| 60 |
+
</section>
|
| 61 |
+
</article>
|
| 62 |
+
</form>
|
tecnicas/templates/tecnicas/components/form-scale-structure.html
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<form action="" method="post" class="form-rating-{{ word }} w-full">
|
| 2 |
+
{% csrf_token %}
|
| 3 |
+
<article class="overflow-x-scroll w-full">
|
| 4 |
+
<article class="bg-surface-card p-6 rounded-lg mb-3 w-fit">
|
| 5 |
+
<label for="id-scale-word-{{ word }}"
|
| 6 |
+
class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
|
| 7 |
+
<span class="hidden id-word">{{ word.id }}</span>
|
| 8 |
+
|
| 9 |
+
<section>
|
| 10 |
+
<div class="w-full min-w-xs">
|
| 11 |
+
<div class="relative mx-6">
|
| 12 |
+
<input type="range" min="1" max="{{ scale.longitud }}" step="1" name="rating-word"
|
| 13 |
+
class="w-full range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0] z-5">
|
| 14 |
+
<div class="absolute top-0 left-0 w-full h-full z-10 flex justify-between pointer-events-none">
|
| 15 |
+
{% for tag in tags %}
|
| 16 |
+
<div class="w-0.5 transform -translate-x-1/2 h-full bg-red-500">
|
| 17 |
+
</div>
|
| 18 |
+
{% endfor %}
|
| 19 |
+
</div>
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
<div class="flex justify-between mt-4 text-xs gap-1">
|
| 23 |
+
{% for tag in tags %}
|
| 24 |
+
<div class="flex items-center justify-center text-center w-[70px]">
|
| 25 |
+
<span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
|
| 26 |
+
{{ tag.id_etiqueta }}
|
| 27 |
+
</span>
|
| 28 |
+
</div>
|
| 29 |
+
{% endfor %}
|
| 30 |
+
</div>
|
| 31 |
+
</div>
|
| 32 |
+
</section>
|
| 33 |
+
</article>
|
| 34 |
+
</article>
|
| 35 |
+
|
| 36 |
+
<article class="flex flex-col justify-center gap-2 items-end actions-{{ word }}">
|
| 37 |
+
<section class="flex justify-end items-center gap-2 btns-container">
|
| 38 |
+
<button type="button" onclick="checkSendRating('{{ word }}')"
|
| 39 |
+
class="ct-btn-check-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-secondary btn-push">
|
| 40 |
+
¿Guardar calificación?
|
| 41 |
+
</button>
|
| 42 |
+
<button type="button" onclick="sendRating('{{ word }}')"
|
| 43 |
+
class="ct-btn-submit-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-primary btn-push hidden">
|
| 44 |
+
Estoy seguro
|
| 45 |
+
</button>
|
| 46 |
+
<button type="button" onclick="cancelSendRating('{{ word }}')"
|
| 47 |
+
class="ct-btn-cancel-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-error btn-push hidden">
|
| 48 |
+
Cancelar
|
| 49 |
+
</button>
|
| 50 |
+
</section>
|
| 51 |
+
</article>
|
| 52 |
+
</form>
|
tecnicas/templates/tecnicas/components/item_session_tester.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
| 10 |
|
| 11 |
<article class="text-sm space-y-1">
|
| 12 |
<p><span class="font-semibold">Código:</span> {{ session.codigo_sesion }}</p>
|
| 13 |
-
<p><span class="font-semibold">Técnica:</span> {{ session.tecnica.tipo_tecnica }}</p>
|
| 14 |
<p><span class="font-semibold">Fecha:</span> {{ session.fechaCreacion }}</p>
|
| 15 |
<p>
|
| 16 |
<span class="font-semibold">Finazliado:</span>
|
|
|
|
| 10 |
|
| 11 |
<article class="text-sm space-y-1">
|
| 12 |
<p><span class="font-semibold">Código:</span> {{ session.codigo_sesion }}</p>
|
| 13 |
+
<p class="uppercase"><span class="font-semibold">Técnica:</span> {{ session.tecnica.tipo_tecnica }}</p>
|
| 14 |
<p><span class="font-semibold">Fecha:</span> {{ session.fechaCreacion }}</p>
|
| 15 |
<p>
|
| 16 |
<span class="font-semibold">Finazliado:</span>
|
tecnicas/templates/tecnicas/components/item_vocabulary.html
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div class='card bg-surface-ligt shadow-lg text-black sm:max-w-80 w-full flex justify-center items-center'>
|
| 2 |
+
<div class="card-body flex flex-col justify-between w-full">
|
| 3 |
+
<div class="flex flex-col gap-2">
|
| 4 |
+
<h2 class="text-xl font-bold text-right bg-btn-secondary text-white p-2 rounded">
|
| 5 |
+
{{ vocabulary.nombre_vocabulario }}
|
| 6 |
+
</h2>
|
| 7 |
+
<div class="text-lg bg-surface-alter-card p-2 rounded">
|
| 8 |
+
<p class="font-bold">Creado en:</p>
|
| 9 |
+
<p>{{ vocabulary.creado }}</p>
|
| 10 |
+
</div>
|
| 11 |
+
</div>
|
| 12 |
+
<div>
|
| 13 |
+
<a href="{% url 'cata_system:crear_vocabulario' %}?name_voca={{vocabulary.nombre_vocabulario}}">
|
| 14 |
+
<button class="btn bg-btn-primary border-0 block w-full btn-push">
|
| 15 |
+
Revisar Vocabulario
|
| 16 |
+
</button>
|
| 17 |
+
</a>
|
| 18 |
+
</div>
|
| 19 |
+
</div>
|
| 20 |
+
</div>
|
tecnicas/templates/tecnicas/components/table-convencional.html
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
|
|
| 1 |
<section>
|
| 2 |
-
<h2 class="text-lg font-bold mb-3">Repetición {{ repeticion }}</h2>
|
| 3 |
-
|
| 4 |
<div class="overflow-x-auto rounded-lg border border-surface-general">
|
| 5 |
-
<table class="min-w-max w-full text-sm text-center border-collapse">
|
| 6 |
<thead class="bg-surface-sweet text-black font-semibold">
|
| 7 |
<tr>
|
|
|
|
| 8 |
<th class="py-2 px-3 border border-surface-general">Usuario</th>
|
| 9 |
<th class="py-2 px-3 border border-surface-general">Producto</th>
|
| 10 |
{% for palabra in palabras %}
|
|
@@ -13,34 +13,40 @@
|
|
| 13 |
</tr>
|
| 14 |
</thead>
|
| 15 |
<tbody class="bg-surface-ligt divide-y divide-gray-200">
|
| 16 |
-
{% for
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
{% endfor %}
|
| 30 |
-
{% if not match %}0{% endif %}
|
| 31 |
-
{% endwith %}
|
| 32 |
-
</td>
|
| 33 |
{% endfor %}
|
| 34 |
-
</tr>
|
| 35 |
-
{% endfor %}
|
| 36 |
{% endfor %}
|
| 37 |
</tbody>
|
| 38 |
</table>
|
| 39 |
</div>
|
| 40 |
|
| 41 |
<div class="flex justify-end mt-3">
|
| 42 |
-
<button class="cts-btn-general cts-btn-primary btn-push"
|
|
|
|
|
|
|
| 43 |
Descargar CSV
|
| 44 |
</button>
|
| 45 |
</div>
|
| 46 |
-
</section>
|
|
|
|
|
|
| 1 |
+
{% load static %}
|
| 2 |
<section>
|
|
|
|
|
|
|
| 3 |
<div class="overflow-x-auto rounded-lg border border-surface-general">
|
| 4 |
+
<table id="convencional-table" class="min-w-max w-full text-sm text-center border-collapse">
|
| 5 |
<thead class="bg-surface-sweet text-black font-semibold">
|
| 6 |
<tr>
|
| 7 |
+
<th class="py-2 px-3 border border-surface-general">Repetición</th>
|
| 8 |
<th class="py-2 px-3 border border-surface-general">Usuario</th>
|
| 9 |
<th class="py-2 px-3 border border-surface-general">Producto</th>
|
| 10 |
{% for palabra in palabras %}
|
|
|
|
| 13 |
</tr>
|
| 14 |
</thead>
|
| 15 |
<tbody class="bg-surface-ligt divide-y divide-gray-200">
|
| 16 |
+
{% for repeticion, catadores in calificaciones.items %}
|
| 17 |
+
{% for usuario, productos in catadores.items %}
|
| 18 |
+
{% for codigo, valores in productos.items %}
|
| 19 |
+
<tr>
|
| 20 |
+
<td class="py-2 px-3 border border-surface-general">{{ repeticion }}</td>
|
| 21 |
+
<td class="py-2 px-3 border border-surface-general">{{ usuario }}</td>
|
| 22 |
+
<td class="py-2 px-3 border border-surface-general">{{ codigo }}</td>
|
| 23 |
+
{% for palabra in palabras %}
|
| 24 |
+
<td class="py-2 px-3 border border-surface-general">
|
| 25 |
+
{% with match=False %}
|
| 26 |
+
{% for valor in valores %}
|
| 27 |
+
{% if valor.nombre_palabra == palabra %}
|
| 28 |
+
{{ valor.dato_valor }}
|
| 29 |
+
{% with match=True %}{% endwith %}
|
| 30 |
+
{% endif %}
|
| 31 |
+
{% endfor %}
|
| 32 |
+
{% if match %}0{% endif %}
|
| 33 |
+
{% endwith %}
|
| 34 |
+
</td>
|
| 35 |
+
{% endfor %}
|
| 36 |
+
</tr>
|
| 37 |
{% endfor %}
|
|
|
|
|
|
|
|
|
|
| 38 |
{% endfor %}
|
|
|
|
|
|
|
| 39 |
{% endfor %}
|
| 40 |
</tbody>
|
| 41 |
</table>
|
| 42 |
</div>
|
| 43 |
|
| 44 |
<div class="flex justify-end mt-3">
|
| 45 |
+
<button id="download-csv-btn" class="cts-btn-general cts-btn-primary btn-push"
|
| 46 |
+
data-session-name="{% if sesion and sesion.nombre_sesion %}{{ sesion.nombre_sesion }}{% else %}{% endif %}"
|
| 47 |
+
data-session-code="{% if sesion and sesion.codigo_sesion %}{{ sesion.codigo_sesion }}{% else %}{% endif %}">
|
| 48 |
Descargar CSV
|
| 49 |
</button>
|
| 50 |
</div>
|
| 51 |
+
</section>
|
| 52 |
+
<script src="{% static 'js/download-table-csv.js' %}"></script>
|
tecnicas/templates/tecnicas/create_sesion/conf-panel-vocabulary.html
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'tecnicas/layouts/base.html' %}
|
| 2 |
+
{% load static %}
|
| 3 |
+
|
| 4 |
+
{% block content %}
|
| 5 |
+
<article class="cts-container-main">
|
| 6 |
+
<article class="cts-wrap-content text-black lg:w-4xl">
|
| 7 |
+
<h2 class="text-2xl font-bold text-center bg-surface-card p-4 rounded-lg">
|
| 8 |
+
Elegir vocabulario
|
| 9 |
+
</h2>
|
| 10 |
+
|
| 11 |
+
<form method="get" id="form-vocabulario" class="bg-surface-card rounded-lg p-6">
|
| 12 |
+
<label for="{{ form.vocabulario.id_for_label }}"
|
| 13 |
+
class="text-lg w-fit font-medium tracking-wide w-full space-y-4">
|
| 14 |
+
<p class="tracking-normal text-xl font-bold">Seleccione un vocabulario</p>
|
| 15 |
+
{{ form.vocabulario }}
|
| 16 |
+
</label>
|
| 17 |
+
</form>
|
| 18 |
+
|
| 19 |
+
{% if error %}
|
| 20 |
+
{% include "../components/error-message.html" with message=error %}
|
| 21 |
+
{% endif %}
|
| 22 |
+
{% if message %}
|
| 23 |
+
{% include "../components/error-message.html" with message=message %}
|
| 24 |
+
{% endif %}
|
| 25 |
+
|
| 26 |
+
<div id="palabras-container" class="rounded-lg p-3 min-h-24 max-h-64 overflow-y-auto bg-surface-card">
|
| 27 |
+
<h3 class="text-xl font-semibold">Palabras asociadas:</h3>
|
| 28 |
+
<ul id="palabras-lista" class="flex flex-wrap gap-4">
|
| 29 |
+
<li class="text-center text-lg">Selecciona un vocabulario para ver sus palabras</li>
|
| 30 |
+
</ul>
|
| 31 |
+
</div>
|
| 32 |
+
|
| 33 |
+
<section>
|
| 34 |
+
<form action="" method="post" id="cts-create-session" class="flex justify-center mt-4">
|
| 35 |
+
{% csrf_token %}
|
| 36 |
+
<article class="cs-escalas-radio w-full flex flex-col gap-4">
|
| 37 |
+
<section class="flex max-sm:flex-col flex-wrap items-center justify-center gap-4">
|
| 38 |
+
<button type="submit" name="start"
|
| 39 |
+
class="cts-btn-general cts-btn-primary btn-push flex-1 w-full">
|
| 40 |
+
Crear sesión
|
| 41 |
+
</button>
|
| 42 |
+
<a href="{% url 'cata_system:seleccion_tecnica' %}" class="flex-1 w-full">
|
| 43 |
+
<button type="button" class="cts-btn-general cts-btn-secondary btn-push w-full">
|
| 44 |
+
Paso anterior
|
| 45 |
+
</button>
|
| 46 |
+
</a>
|
| 47 |
+
</section>
|
| 48 |
+
<a href="{% url 'cata_system:seleccion_tecnica' %}" class="flex-1 w-full">
|
| 49 |
+
<button type="button" class="cts-btn-general cts-btn-error btn-push w-full">
|
| 50 |
+
Cancelar la creación
|
| 51 |
+
</button>
|
| 52 |
+
</a>
|
| 53 |
+
</article>
|
| 54 |
+
</form>
|
| 55 |
+
</section>
|
| 56 |
+
</article>
|
| 57 |
+
</article>
|
| 58 |
+
{% endblock %}
|
| 59 |
+
|
| 60 |
+
{% block extra_js %}
|
| 61 |
+
<script src="{% static 'js/choose-vocabulary.js' %}"></script>
|
| 62 |
+
{% endblock %}
|
tecnicas/templates/tecnicas/create_sesion/configuracion-panel-codes.html
CHANGED
|
@@ -7,13 +7,18 @@
|
|
| 7 |
<article class="cts-container-main">
|
| 8 |
<article class="cts-wrap-content text-black">
|
| 9 |
<h1 class="text-center font-bold text-4xl">Panel de configuración</h1>
|
|
|
|
| 10 |
{% if error %}
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
| 12 |
{% endif %}
|
|
|
|
| 13 |
<form method="post" action="" class="ct-codes-form flex flex-col gap-5">
|
| 14 |
{% csrf_token %}
|
| 15 |
<h2 class="text-2xl mb-2 font-bold">Codificar productos</h2>
|
| 16 |
-
|
| 17 |
<article class="w-full flex flex-col justify-center items-center gap-5">
|
| 18 |
<p class="text-center font-bold tracking-wide text-xl bg-surface-card px-3 py-3 pb-4 rounded max-w-4xl">
|
| 19 |
Puede dejar los campos como están para usar los códigos predefinidos.
|
|
@@ -47,7 +52,6 @@
|
|
| 47 |
</article>
|
| 48 |
{% endif %}
|
| 49 |
|
| 50 |
-
<hr>
|
| 51 |
<article class="flex flex-col items-center gap-4">
|
| 52 |
<section class="flex w-full max-sm:flex-col flex-wrap gap-4 max-w-2xl">
|
| 53 |
<button type="submit" class="cts-btn-general cts-btn-primary btn-push flex-1 w-full">
|
|
|
|
| 7 |
<article class="cts-container-main">
|
| 8 |
<article class="cts-wrap-content text-black">
|
| 9 |
<h1 class="text-center font-bold text-4xl">Panel de configuración</h1>
|
| 10 |
+
|
| 11 |
{% if error %}
|
| 12 |
+
{% include "./components/error-message.html" with message=error %}
|
| 13 |
+
{% endif %}
|
| 14 |
+
{% if message %}
|
| 15 |
+
{% include "./components/error-message.html" with message=message %}
|
| 16 |
{% endif %}
|
| 17 |
+
|
| 18 |
<form method="post" action="" class="ct-codes-form flex flex-col gap-5">
|
| 19 |
{% csrf_token %}
|
| 20 |
<h2 class="text-2xl mb-2 font-bold">Codificar productos</h2>
|
| 21 |
+
|
| 22 |
<article class="w-full flex flex-col justify-center items-center gap-5">
|
| 23 |
<p class="text-center font-bold tracking-wide text-xl bg-surface-card px-3 py-3 pb-4 rounded max-w-4xl">
|
| 24 |
Puede dejar los campos como están para usar los códigos predefinidos.
|
|
|
|
| 52 |
</article>
|
| 53 |
{% endif %}
|
| 54 |
|
|
|
|
| 55 |
<article class="flex flex-col items-center gap-4">
|
| 56 |
<section class="flex w-full max-sm:flex-col flex-wrap gap-4 max-w-2xl">
|
| 57 |
<button type="submit" class="cts-btn-general cts-btn-primary btn-push flex-1 w-full">
|
tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'tecnicas/layouts/base.html' %}
|
| 2 |
+
{% load static %}
|
| 3 |
+
|
| 4 |
+
{% block title %}Panel Configuracion{% endblock %}
|
| 5 |
+
|
| 6 |
+
{% block content %}
|
| 7 |
+
<article class="cts-container-main">
|
| 8 |
+
<article class="cts-wrap-content text-black">
|
| 9 |
+
<h1 class="text-center font-bold text-4xl">Panel de configuración</h1>
|
| 10 |
+
|
| 11 |
+
{% if error %}
|
| 12 |
+
{% include "../components/error-message.html" with message=error %}
|
| 13 |
+
{% endif %}
|
| 14 |
+
{% if message %}
|
| 15 |
+
{% include "../components/error-message.html" with message=message %}
|
| 16 |
+
{% endif %}
|
| 17 |
+
|
| 18 |
+
<form method="post" action="" class="space-y-4">
|
| 19 |
+
{% csrf_token %}
|
| 20 |
+
<article class="space-y-4">
|
| 21 |
+
<h2 class="text-2xl font-bold">Información Basica</h2>
|
| 22 |
+
|
| 23 |
+
<section class="flex justify-center items-center">
|
| 24 |
+
<label for="{{form_sesion.nombre_sesion.id_for_label}}"
|
| 25 |
+
class="text-lg flex flex-col items-center px-2 w-fit font-medium tracking-wide mb-4">
|
| 26 |
+
<p class="tracking-normal text-xl font-bold">Ingrese el nombre de la sesion si lo desea:</p>
|
| 27 |
+
{{ form_sesion.nombre_sesion }}
|
| 28 |
+
</label>
|
| 29 |
+
</section>
|
| 30 |
+
|
| 31 |
+
<section class="flex justify-center items-center">
|
| 32 |
+
<label for="{{ form_sesion.numero_productos.id_for_label }}"
|
| 33 |
+
class="text-lg flex flex-col items-center px-2 font-medium tracking-wide">
|
| 34 |
+
<p class="tracking-normal text-base font-bold">
|
| 35 |
+
Número de Productos:
|
| 36 |
+
</p>
|
| 37 |
+
{{ form_sesion.numero_productos }}
|
| 38 |
+
</label>
|
| 39 |
+
</section>
|
| 40 |
+
|
| 41 |
+
<section class="flex justify-center items-center">
|
| 42 |
+
<label for="{{form_sesion.instrucciones.id_for_label}}"
|
| 43 |
+
class="text-lg flex flex-col items-center px-2 w-fit font-medium tracking-wide mb-4">
|
| 44 |
+
<p class="tracking-normal text-base font-bold">Ingrese las instrucciones para la tecnica:</p>
|
| 45 |
+
{{ form_sesion.instrucciones }}
|
| 46 |
+
</label>
|
| 47 |
+
</section>
|
| 48 |
+
</article>
|
| 49 |
+
|
| 50 |
+
<article class="cs-escalas-radio flex flex-col gap-4">
|
| 51 |
+
<h2 class="text-2xl font-bold">Selecciona estilo para las palabras</h2>
|
| 52 |
+
<section class="flex flex-row gap-4 justify-around flex-wrap">
|
| 53 |
+
{% for estilo in form_sesion.estilo_palabras %}
|
| 54 |
+
<label for="{{estilo.id_for_label}}"
|
| 55 |
+
class="ct-radio-estilo uppercase text-lg tracking-wider font-bold p-2 px-4 rounded-xl cts-btn-secondary btn-push">
|
| 56 |
+
{{ estilo.tag }}
|
| 57 |
+
{{ estilo.choice_label }}
|
| 58 |
+
</label>
|
| 59 |
+
{% endfor %}
|
| 60 |
+
</section>
|
| 61 |
+
<section class="w-full flex justify-center items-center">
|
| 62 |
+
<p
|
| 63 |
+
class="cts-help-style-words max-w-2xl text-lg font-medium tracking-wide bg-surface-card text-center py-1 px-4 rounded-xl">
|
| 64 |
+
</p>
|
| 65 |
+
</section>
|
| 66 |
+
</article>
|
| 67 |
+
|
| 68 |
+
<article class="w-full flex max-sm:flex-col flex-wrap items-center justify-center gap-4">
|
| 69 |
+
<button type="submit" class="cts-btn-general cts-btn-primary btn-push flex-1/4 w-full">
|
| 70 |
+
Continuar
|
| 71 |
+
</button>
|
| 72 |
+
<a href="{% url 'cata_system:seleccion_tecnica' %}" class="flex-1/4 w-full">
|
| 73 |
+
<button type="button" class="cts-btn-general cts-btn-error btn-push w-full">
|
| 74 |
+
Cancelar la creación
|
| 75 |
+
</button>
|
| 76 |
+
</a>
|
| 77 |
+
</article>
|
| 78 |
+
</form>
|
| 79 |
+
</article>
|
| 80 |
+
</article>
|
| 81 |
+
{% endblock %}
|
| 82 |
+
|
| 83 |
+
{% block extra_js %}
|
| 84 |
+
<script src="{% static 'js/panel-basic-cata.js' %}"></script>
|
| 85 |
+
{% endblock %}
|
tecnicas/templates/tecnicas/forms_tester/convencional.html
CHANGED
|
@@ -37,7 +37,8 @@
|
|
| 37 |
<article class="cts-wrap-content text-black">
|
| 38 |
<header class="text-center flex-row w-full items-stretch flex justify-around flex-wrap gap-2">
|
| 39 |
<h1 class="rounded font-bold text-2xl bg-surface-ligt p-4 flex-1">
|
| 40 |
-
Sesión usando <br>técnica
|
|
|
|
| 41 |
</h1>
|
| 42 |
<button class="cts-btn-general cts-btn-error btn-push" onclick="exit_sesion('form-actions')">
|
| 43 |
Salir de la sesión
|
|
@@ -45,12 +46,17 @@
|
|
| 45 |
</header>
|
| 46 |
|
| 47 |
<article class="hidden">
|
| 48 |
-
<form action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post"
|
|
|
|
| 49 |
{% csrf_token %}
|
| 50 |
<input type="hidden" name="action" class="action-input">
|
| 51 |
</form>
|
| 52 |
</article>
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
{% if error %}
|
| 55 |
<hr>
|
| 56 |
<article class="bg-red-600 p-4 text-white rounded-xl ct-notification-error">
|
|
@@ -95,132 +101,17 @@
|
|
| 95 |
|
| 96 |
<article
|
| 97 |
class="scales-container [&>*:not(:last-child)]:mb-5 min-lg:grid min-lg:items-start grid-cols-2 gap-3 justify-center items-center">
|
| 98 |
-
{%
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
<section class="block">
|
| 110 |
-
<div class="relative mx-6">
|
| 111 |
-
<input type="range" type="range" min="0" max="1000" value="500" name="rating-word"
|
| 112 |
-
class="range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0]"
|
| 113 |
-
style="width: {{scale.longitud}}cm;">
|
| 114 |
-
|
| 115 |
-
<div
|
| 116 |
-
class="absolute top-0 left-0 w-0.5 transform -translate-x-1/2 h-full bg-red-500 z-10">
|
| 117 |
-
</div>
|
| 118 |
-
|
| 119 |
-
<div
|
| 120 |
-
class="absolute top-1/5 left-1/2 w-0.5 transform -translate-x-1/2 h-4/5 bg-red-500 z-10">
|
| 121 |
-
</div>
|
| 122 |
-
|
| 123 |
-
<div
|
| 124 |
-
class="absolute top-0 right-0 w-0.5 transform -translate-x-1/2 h-full bg-red-500 z-10">
|
| 125 |
-
</div>
|
| 126 |
-
</div>
|
| 127 |
-
|
| 128 |
-
<div class="flex justify-between mt-4">
|
| 129 |
-
<div class="flex flex-col items-start text-left w-[100px]">
|
| 130 |
-
<span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
|
| 131 |
-
{{ tags.0.id_etiqueta }}
|
| 132 |
-
</span>
|
| 133 |
-
</div>
|
| 134 |
-
<div class="flex flex-col items-start text-center w-[100px]">
|
| 135 |
-
<span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
|
| 136 |
-
{{ tags.1.id_etiqueta }}
|
| 137 |
-
</span>
|
| 138 |
-
</div>
|
| 139 |
-
<div class="flex flex-col items-start text-right w-[100px]">
|
| 140 |
-
<span class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
|
| 141 |
-
{{ tags.2.id_etiqueta }}
|
| 142 |
-
</span>
|
| 143 |
-
</div>
|
| 144 |
-
</div>
|
| 145 |
-
</section>
|
| 146 |
-
</article>
|
| 147 |
-
</article>
|
| 148 |
-
<article class="flex flex-col justify-center gap-2 items-end actions-{{word}}">
|
| 149 |
-
<section class="flex justify-end items-center gap-2 btns-container">
|
| 150 |
-
<button type="button" onclick="checkSendRating('{{word}}')"
|
| 151 |
-
class="ct-btn-check-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-secondary btn-push">
|
| 152 |
-
¿Guardar calificación?
|
| 153 |
-
</button>
|
| 154 |
-
<button type="button" onclick="sendRating('{{word}}')"
|
| 155 |
-
class="ct-btn-submit-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-primary btn-push hidden">
|
| 156 |
-
Estoy seguro
|
| 157 |
-
</button>
|
| 158 |
-
<button type="button" onclick="cancelSendRating('{{word}}')"
|
| 159 |
-
class="ct-btn-cancel-{{word}} cts-btn-general-compress py-2 px-4 cts-btn-error btn-push hidden">
|
| 160 |
-
Cancelar
|
| 161 |
-
</button>
|
| 162 |
-
</section>
|
| 163 |
-
</article>
|
| 164 |
-
</form>
|
| 165 |
-
{% endfor %}
|
| 166 |
-
{% elif type_scale == "estructurada" %}
|
| 167 |
-
{% for word in words %}
|
| 168 |
-
<form action="" method="post" class="form-rating-{{ word }} w-full">
|
| 169 |
-
{% csrf_token %}
|
| 170 |
-
<article class="overflow-x-scroll w-full">
|
| 171 |
-
<article class="bg-surface-card p-6 rounded-lg mb-3 w-fit">
|
| 172 |
-
<label for="id-scale-word-{{ word }}"
|
| 173 |
-
class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }}</label>
|
| 174 |
-
<span class="hidden id-word">{{ word.id }}</span>
|
| 175 |
-
|
| 176 |
-
<section>
|
| 177 |
-
<div class="w-full min-w-xs">
|
| 178 |
-
<div class="relative mx-6">
|
| 179 |
-
<input type="range" min="1" max="{{scale.longitud}}" step="1" name="rating-word"
|
| 180 |
-
class="w-full range range-md text-blue-400 [--range-bg:orange] [--range-thumb:blue] [--range-fill:0] z-5">
|
| 181 |
-
<div
|
| 182 |
-
class="absolute top-0 left-0 w-full h-full z-10 flex justify-between pointer-events-none">
|
| 183 |
-
{% for tag in tags %}
|
| 184 |
-
<div class="w-0.5 transform -translate-x-1/2 h-full bg-red-500">
|
| 185 |
-
</div>
|
| 186 |
-
{% endfor %}
|
| 187 |
-
</div>
|
| 188 |
-
</div>
|
| 189 |
-
|
| 190 |
-
<div class="flex justify-between mt-4 text-xs gap-1">
|
| 191 |
-
{% for tag in tags %}
|
| 192 |
-
<div class="flex items-center justify-center text-center w-[70px]">
|
| 193 |
-
<span
|
| 194 |
-
class="text-xs font-medium text-gray-700 break-words first-letter:capitalize">
|
| 195 |
-
{{ tag.id_etiqueta }}
|
| 196 |
-
</span>
|
| 197 |
-
</div>
|
| 198 |
-
{% endfor %}
|
| 199 |
-
</div>
|
| 200 |
-
</div>
|
| 201 |
-
</section>
|
| 202 |
-
</article>
|
| 203 |
-
</article>
|
| 204 |
-
|
| 205 |
-
<article class="flex flex-col justify-center gap-2 items-end actions-{{ word }}">
|
| 206 |
-
<section class="flex justify-end items-center gap-2 btns-container">
|
| 207 |
-
<button type="button" onclick="checkSendRating('{{ word }}')"
|
| 208 |
-
class="ct-btn-check-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-secondary btn-push">
|
| 209 |
-
¿Guardar calificación?
|
| 210 |
-
</button>
|
| 211 |
-
<button type="button" onclick="sendRating('{{ word }}')"
|
| 212 |
-
class="ct-btn-submit-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-primary btn-push hidden">
|
| 213 |
-
Estoy seguro
|
| 214 |
-
</button>
|
| 215 |
-
<button type="button" onclick="cancelSendRating('{{ word }}')"
|
| 216 |
-
class="ct-btn-cancel-{{ word }} cts-btn-general-compress py-2 px-4 cts-btn-error btn-push hidden">
|
| 217 |
-
Cancelar
|
| 218 |
-
</button>
|
| 219 |
-
</section>
|
| 220 |
-
</article>
|
| 221 |
-
</form>
|
| 222 |
-
{% endfor %}
|
| 223 |
-
{% endif %}
|
| 224 |
</article>
|
| 225 |
</article>
|
| 226 |
</article>
|
|
|
|
| 37 |
<article class="cts-wrap-content text-black">
|
| 38 |
<header class="text-center flex-row w-full items-stretch flex justify-around flex-wrap gap-2">
|
| 39 |
<h1 class="rounded font-bold text-2xl bg-surface-ligt p-4 flex-1">
|
| 40 |
+
Sesión usando <br>técnica
|
| 41 |
+
<span class="uppercase">{{ session.tecnica.tipo_tecnica }}</span>
|
| 42 |
</h1>
|
| 43 |
<button class="cts-btn-general cts-btn-error btn-push" onclick="exit_sesion('form-actions')">
|
| 44 |
Salir de la sesión
|
|
|
|
| 46 |
</header>
|
| 47 |
|
| 48 |
<article class="hidden">
|
| 49 |
+
<form action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post"
|
| 50 |
+
class="form-actions">
|
| 51 |
{% csrf_token %}
|
| 52 |
<input type="hidden" name="action" class="action-input">
|
| 53 |
</form>
|
| 54 |
</article>
|
| 55 |
|
| 56 |
+
<section class="hidden">
|
| 57 |
+
<input type="hidden" value="{{ session.tecnica.id }}" name="id-tecnica" class="ct-input-id-tech">
|
| 58 |
+
</section>
|
| 59 |
+
|
| 60 |
{% if error %}
|
| 61 |
<hr>
|
| 62 |
<article class="bg-red-600 p-4 text-white rounded-xl ct-notification-error">
|
|
|
|
| 101 |
|
| 102 |
<article
|
| 103 |
class="scales-container [&>*:not(:last-child)]:mb-5 min-lg:grid min-lg:items-start grid-cols-2 gap-3 justify-center items-center">
|
| 104 |
+
{% with path_con="../components/form-scale-continue.html" path_str="../components/form-scale-structure.html" %}
|
| 105 |
+
{% if type_scale == "continua" %}
|
| 106 |
+
{% for word in words %}
|
| 107 |
+
{% include path_con with word=word tags=tags scale=scale id_tecnica=session.tecnica.id %}
|
| 108 |
+
{% endfor %}
|
| 109 |
+
{% elif type_scale == "estructurada" %}
|
| 110 |
+
{% for word in words %}
|
| 111 |
+
{% include path_str with word=word tags=tags scale=scale %}
|
| 112 |
+
{% endfor %}
|
| 113 |
+
{% endif %}
|
| 114 |
+
{% endwith %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
</article>
|
| 116 |
</article>
|
| 117 |
</article>
|