diff --git a/tecnicas/constants.py b/tecnicas/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..5ad46edecc425aea269cf8c0827b969671e487d4 --- /dev/null +++ b/tecnicas/constants.py @@ -0,0 +1,6 @@ +FORMS_TO_CREATE_SESSION = [ + "form_basic", + "form_tags", + "form_codes", + "form_words" +] diff --git a/tecnicas/controllers/__init__.py b/tecnicas/controllers/__init__.py index c3a9b62260fdba138175f771e866a66e5c8e10b4..41d8a06586110a47b5df41ef29756f8fa418421c 100644 --- a/tecnicas/controllers/__init__.py +++ b/tecnicas/controllers/__init__.py @@ -12,8 +12,20 @@ from .models_controller.posicion_controller import PosicionController from .models_controller.particiapacion_controller import ParticipacionController from .models_controller.dato_controller import DatoController -from .views_controller.detalles_sesion_controller import DetallesSesionController from .views_controller.login_tester_controller import LoginTesterController from .views_controller.main_tester_form_controller import MainTesterFormController from .views_controller.api_rating_controller import ApiRatingController -from .views_controller.monitor_sesion_controller import MonitorSesionController \ No newline at end of file +from .views_controller.tester_list_controller import TesterListController +from .views_controller.list_sessions_tester_controller import ListSessionsTesterController + +from .views_controller.create_session.panel_basic_controller import PanelBasicController +from .views_controller.create_session.panel_tags_controller import PanelTagsController +from .views_controller.create_session.panel_codes_controller import PanelCodesController +from .views_controller.create_session.panel_words_controller import PanelWordsController +from .views_controller.create_session.panel_create_controller import PanelCreateController + +from .views_controller.session_management.details_controller import DetallesController +from .views_controller.session_management.details_escala_controller import DetallesEscalasController +from .views_controller.session_management.details_rata_controller import DetallesRATAController +from .views_controller.session_management.monitor_controller import MonitorController +from .views_controller.session_management.monitor_escalas_controller import MonitorEscalasController \ No newline at end of file diff --git a/tecnicas/controllers/models_controller/calificacion_controller.py b/tecnicas/controllers/models_controller/calificacion_controller.py index f9e6b98f5b8ac8c6eaf3eca28d26a1e54a7faeda..a99f63d3ddc9ff1d3326a59e82656648a8dee480 100644 --- a/tecnicas/controllers/models_controller/calificacion_controller.py +++ b/tecnicas/controllers/models_controller/calificacion_controller.py @@ -1,8 +1,7 @@ from django.core.exceptions import ValidationError -from django.db import DatabaseError from collections import defaultdict -from ...models import Calificacion, Tecnica, Posicion, Producto, Catador -from ...utils import controller_error, getId +from tecnicas.models import Calificacion, Tecnica, Posicion, Producto, Catador +from tecnicas.utils import controller_error, getId class CalificacionController(): @@ -46,7 +45,7 @@ class CalificacionController(): repetition = technique.repeticion if not repetition: - return {"error": "sin datos calficados aun"} + return controller_error("Sin datos calificados aún") ratings = list(Calificacion.objects.filter(id_tecnica=technique)) @@ -84,7 +83,7 @@ class CalificacionController(): elif id_tester is not None: filters["id_catador__id"] = id_tester elif user_tester is not None: - filters["id_catador__usuarioCatador"] = user_tester + filters["id_catador__user__username"] = user_tester ratings = list(Calificacion.objects.filter(**filters).select_related( "id_producto", diff --git a/tecnicas/controllers/models_controller/dato_controller.py b/tecnicas/controllers/models_controller/dato_controller.py index b5d7016cd72d5ed89aa039c3cf5e366869b5500d..019cd4d731ff2a5e5f9efec921475995276fc89e 100644 --- a/tecnicas/controllers/models_controller/dato_controller.py +++ b/tecnicas/controllers/models_controller/dato_controller.py @@ -89,7 +89,7 @@ class DatoController(): producto_code=F( "id_dato__id_calificacion__id_producto__codigoProducto"), usuarioCatador=F( - "id_dato__id_calificacion__id_catador__usuarioCatador"), + "id_dato__id_calificacion__id_catador__user__username"), dato_valor=F("valor") ) ) diff --git a/tecnicas/controllers/models_controller/escala_controller.py b/tecnicas/controllers/models_controller/escala_controller.py index 3b2a36a1dd4dba74ede1fa223a546e2e07f532ad..89a7b50c388b7f6d36f1d8ec694284ed957210dc 100644 --- a/tecnicas/controllers/models_controller/escala_controller.py +++ b/tecnicas/controllers/models_controller/escala_controller.py @@ -26,7 +26,7 @@ class EscalaController(): self.scale.save() return self.scale except DatabaseError as error: - return controller_error("error al guardar la escala") + return controller_error("Error al guardar la escala") def deleteScale(self): self.scale.delete() @@ -67,7 +67,7 @@ class EscalaController(): return self.tags_relation except DatabaseError as error: self.deleteRelationshipsWithLabels() - return controller_error("error guardar relacion etiqueta escala") + return controller_error("Error en guardar la relación etiqueta escala") @staticmethod def getScaleByTechnique(technique: Tecnica = None, id_technique: int = None): diff --git a/tecnicas/controllers/models_controller/ordenes_controller.py b/tecnicas/controllers/models_controller/ordenes_controller.py index 367f0345a5446c33ddf430642d1c1909971e5578..0434e6a15664b0be05f7b281aa7ea21fe4efc497 100644 --- a/tecnicas/controllers/models_controller/ordenes_controller.py +++ b/tecnicas/controllers/models_controller/ordenes_controller.py @@ -25,13 +25,13 @@ class OrdenesController(): def saveOrders(self): if not self.orders: - return controller_error("no se han establecido las ordenes para guardar") + return controller_error("No se han establecido las órdenes para guardar") try: for order in self.orders: order.save() return self.orders except DatabaseError as error: - return controller_error("error al guardar las ordenes") + return controller_error("Error al guardar las ordenes") def setPositions(self): codes_ids_products = {} @@ -40,21 +40,21 @@ class OrdenesController(): codes_expect = list(codes_ids_products.keys()) if len(self.orders) != len(self.raw_list_orders): - return controller_error("el numero de ordenes guardados no coinciden con los recibidos") + return controller_error("El número de ordenes guardados no coinciden con los recibidos") self.positions = [] for index, order in enumerate(self.raw_list_orders): received_codes_order = list(order.keys()) if set(received_codes_order) != set(codes_expect): - return controller_error("las ordenes mandadas no contienen los productos esperados") + return controller_error("Las ordenes mandadas no contienen los productos esperados") for name, position_index in order.items(): list_product_use = [product for product in self.products if product.codigoProducto == name] if len(list_product_use) != 1: - return controller_error("no pueden existir dos productos que ocupen la misma posicion de un orden") + return controller_error("No pueden existir dos productos que ocupen la misma posición de un orden") product_use = list_product_use[0] new_position = Posicion( @@ -67,13 +67,13 @@ class OrdenesController(): def savePositions(self): if not self.positions: - return controller_error("no se han establecido posiciones para guargar") + return controller_error("No se han establecido posiciones para guarguar") try: for position in self.positions: position.save() return self.positions except DatabaseError as error: - return controller_error("error al guardar las posiciones") + return controller_error("Error al guardar las posiciones") @staticmethod def getOrderById(id: int): diff --git a/tecnicas/controllers/models_controller/palabras_controller.py b/tecnicas/controllers/models_controller/palabras_controller.py index 89889899df73da846d9a00daf02def02eef4471b..3aafd619ecd57fe10ca3746a4b49257f2ba3bd92 100644 --- a/tecnicas/controllers/models_controller/palabras_controller.py +++ b/tecnicas/controllers/models_controller/palabras_controller.py @@ -14,11 +14,9 @@ class PalabrasController(): self.ids_words = new_ids def setWords(self): - self.words = [] - searched_words = list(Palabra.objects.filter(id__in=self.ids_words)) - if not len(searched_words): - return controller_error("no se han encontrado registros") - self.words = searched_words + self.words = list(Palabra.objects.filter(id__in=self.ids_words)) + if not len(self.words): + return controller_error("No se han encontrado registros") return self.words @staticmethod diff --git a/tecnicas/controllers/models_controller/particiapacion_controller.py b/tecnicas/controllers/models_controller/particiapacion_controller.py index a0149c3d44730ca760130129f93948503e3871b1..bd0448b1ee796fe063fbe69d36ffaed8b2df65a9 100644 --- a/tecnicas/controllers/models_controller/particiapacion_controller.py +++ b/tecnicas/controllers/models_controller/particiapacion_controller.py @@ -1,12 +1,13 @@ -from ...models import Participacion, Tecnica, SesionSensorial +from ...models import Participacion, Tecnica, SesionSensorial, Catador from ...utils import controller_error class ParticipacionController(): @staticmethod - def enterSession(id_participation: int): + def enterSession(tester: Catador, session: SesionSensorial): try: - participation = Participacion.objects.get(id=id_participation) + participation = Participacion.objects.get( + catador=tester, tecnica=session.tecnica) participation.finalizado = False participation.activo = True participation.save() @@ -26,9 +27,10 @@ class ParticipacionController(): return controller_error("No se ha encontrado la participación") @staticmethod - def outSession(id_participation: int): + def outSession(tester: Catador, session: SesionSensorial): try: - participation = Participacion.objects.get(id=id_participation) + participation = Participacion.objects.get( + catador=tester, tecnica=session.tecnica) participation.activo = False participation.save() return participation diff --git a/tecnicas/controllers/models_controller/sesion_controller.py b/tecnicas/controllers/models_controller/sesion_controller.py index e43b8bee3b35be005a1c56e12ab756c3ed6101fb..0ff4c82a05ca7d5421d5e24ac3788d2b14fefc8e 100644 --- a/tecnicas/controllers/models_controller/sesion_controller.py +++ b/tecnicas/controllers/models_controller/sesion_controller.py @@ -18,9 +18,9 @@ class SesionController(): def setSession(self): if not self.presenter: - return controller_error("se requiere presentador para crear sesion") + return controller_error("Se requiere presentador para crear sesión") elif not self.technique: - return controller_error("se requiere tecnica para crear sesion") + return controller_error("Se requiere técnica para crear sesión") self.sensorial_session = SesionSensorial( tecnica=self.technique, @@ -34,22 +34,22 @@ class SesionController(): def saveSession(self): if not self.sensorial_session: - return controller_error("no se ha definido la sesion a guardar") + return controller_error("No se ha definido la sesión a guardar") try: self.sensorial_session.save() return self.sensorial_session except DatabaseError as error: - return controller_error("Error al crear la session sensorial") + return controller_error("Error al crear la sesión sensorial") @staticmethod def getSessionsSavesByCretor(user_name: str, page: int): elements_by_page = 6 try: - creator = Presentador.objects.get(nombre_usuario=user_name) + creator = Presentador.objects.get(user__username=user_name) except Presentador.DoesNotExist: - return controller_error("presentador invalido") + return controller_error("Presentador invalido") queryset = ( SesionSensorial.objects @@ -75,10 +75,14 @@ class SesionController(): sessions_in_page = paginator.page(page) except PageNotAnInteger: return controller_error("índice inválido") - except EmptyPage: - return controller_error("sin registros de sesiones") - return (sessions_in_page, not sessions_in_page.number < paginator.num_pages) + if not sessions_in_page.object_list: + return controller_error("Sin registros de sesiones") + + current_page = sessions_in_page.number + is_last_page = not current_page < paginator.num_pages + + return (sessions_in_page, is_last_page, current_page) @staticmethod def getSessionByCodePanelTester(code: str): @@ -111,14 +115,14 @@ class SesionController(): @staticmethod def getNumberSessionsByCreator(user_name: str): try: - creator = Presentador.objects.get(nombre_usuario=user_name) + creator = Presentador.objects.get(user__username=user_name) number_sessions = SesionSensorial.objects.filter( creadoPor=creator).count() return number_sessions/9 except Presentador.DoesNotExist: - return controller_error("presentador invalido") + return controller_error("Presentador invalido") @staticmethod def finishRepetion(session: SesionSensorial | str): diff --git a/tecnicas/controllers/models_controller/tecnica_controller.py b/tecnicas/controllers/models_controller/tecnica_controller.py index 6a155f4b10add6a7b4bfa01b304fbb8cb9f8ad93..71265258841e5c3bcbc1843d6438bfa749275262 100644 --- a/tecnicas/controllers/models_controller/tecnica_controller.py +++ b/tecnicas/controllers/models_controller/tecnica_controller.py @@ -6,18 +6,18 @@ from tecnicas.utils import controller_error class TecnicaController(): def setTechnique(self, **kwargs): self.technique = Tecnica( - tipo_tecnica=kwargs["tipo_tecnica"], - id_estilo=kwargs["estilo_palabras"], - repeticiones_max=kwargs["numero_repeticiones"] or 0, + tipo_tecnica=TipoTecnica.objects.get(nombre_tecnica=kwargs["name_tecnica"]), + id_estilo=EstiloPalabra.objects.get(id=kwargs["estilo_palabras"]), + repeticiones_max=kwargs["numero_repeticiones"] or 1, limite_catadores=kwargs["numero_catadores"], instrucciones=kwargs["instrucciones"], ) def setTechniqueFromBasicData(self, basic): self.technique = Tecnica( - tipo_tecnica=TipoTecnica.objects.get(id=basic["id_tecnica"]), + tipo_tecnica=TipoTecnica.objects.get(nombre_tecnica=basic["name_tecnica"]), id_estilo=EstiloPalabra.objects.get(id=basic["estilo_palabras"]), - repeticiones_max=basic["numero_repeticiones"] or 0, + repeticiones_max=basic["numero_repeticiones"] or 1, limite_catadores=basic["numero_catadores"], instrucciones=basic["instrucciones"] or "Espere instrucciones del Presentador", ) @@ -30,7 +30,7 @@ class TecnicaController(): self.technique.save() return self.technique except DatabaseError: - return controller_error("No se ha podido guardar la tecnica") + return controller_error("No se ha podido guardar la técnica") def deleteTechnique(self): self.technique.delete() diff --git a/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py b/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..7afd63508db47c0be43062860ac2fd9cfcec08da --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py @@ -0,0 +1,105 @@ +from tecnicas.forms import SesionBasicForm +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse + + +class PanelBasicController(): + conf_initial_rata = { + "numero_catadores": 0, + "numero_repeticiones": 1 + } + + def __init__(self): + pass + + @staticmethod + def controllGetEscalas(request: HttpRequest): + form_sesion = SesionBasicForm() + + view_context = { + "form_sesion": form_sesion, + "use_technique": "escalas" + } + + response = render( + request, "tecnicas/create_sesion/configuracion-panel-basic.html", view_context) + return response + + @staticmethod + def controllPostEscalas(request: HttpRequest, name_tecnica: str): + try: + form = SesionBasicForm(request.POST) + + if form.is_valid(): + values = {} + for name, value in form.cleaned_data.items(): + if name == "estilo_palabras" or name == "tipo_escala": + values[name] = value.id + else: + values[name] = value + + values["name_tecnica"] = name_tecnica + request.session['form_basic'] = values + response = redirect( + reverse("cata_system:panel_configuracion_tags")) + else: + response = render(request, "tecnicas/create_sesion/configuracion-panel-basic.html", { + "form_sesion": form, "error": "Información no valida"}) + except KeyError: + response = redirect(reverse( + "cata_system:seleccion_tecnica") + "?error=error en datos de configuracion") + + return response + + @staticmethod + def controllGetRATA(request: HttpRequest): + form_sesion = SesionBasicForm( + initial_conf=PanelBasicController.conf_initial_rata) + + view_context = { + "form_sesion": form_sesion, + "use_technique": "rata" + } + + response = render( + request, "tecnicas/create_sesion/configuracion-panel-basic.html", view_context) + return response + + @staticmethod + def controllPostRATA(request: HttpRequest, name_tecnica: str): + try: + form = SesionBasicForm( + request.POST, initial_conf=PanelBasicController.conf_initial_rata) + + if form.is_valid(): + values = {} + for name, value in form.cleaned_data.items(): + if name == "estilo_palabras" or name == "tipo_escala": + values[name] = value.id + else: + values[name] = value + + for key, expected in PanelBasicController.conf_initial_rata.items(): + actual = values.get(key) + + if actual is None or str(actual) != str(expected): + form.add_error( + key, f"Valor inválido para '{key}': se esperaba {expected}, se recibió {actual}") + + if form.errors: + response = render(request, "tecnicas/create_sesion/configuracion-panel-basic.html", { + "form_sesion": form, "error": "No puedes modificar el número de catadores o repeticiones"}) + else: + values["name_tecnica"] = name_tecnica + request.session['form_basic'] = values + response = redirect( + reverse("cata_system:panel_configuracion_tags")) + else: + response = render(request, "tecnicas/create_sesion/configuracion-panel-basic.html", { + "form_sesion": form, "error": "Información no valida"}) + except KeyError: + response = redirect(reverse( + "cata_system:seleccion_tecnica") + "?error=error en datos de configuracion") + + return response diff --git a/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py b/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..9bef632d61abfa2d6e72519ddb24780b2d3f430e --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py @@ -0,0 +1,114 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from tecnicas.forms import CodesForm +from tecnicas.utils import generarCodigos +import json + + +class PanelCodesController(): + def __init__(self): + pass + + @staticmethod + def controllGetEscalas(request: HttpRequest, data): + ( + num_products, + num_tester + ) = PanelCodesController.defineInfoConvencional(data) + + codes_products = generarCodigos(num_products) + + form_codes = CodesForm(codes=codes_products) + + context_codes_form = { + "form_codes": form_codes, + "num_tester": num_tester, + "use_technique": "escalas" + } + + return render(request, "tecnicas/create_sesion/configuracion-panel-codes.html", context_codes_form) + + @staticmethod + def controllPostEscalas(request: HttpRequest, data): + ( + num_products, + num_tester + ) = PanelCodesController.defineInfoConvencional(data) + + sorts_code = json.loads(request.POST.get("sort_codes")) + codes = [] + context_codes_form = {} + + for name, value in request.POST.items(): + if name.__contains__("producto_"): + codes.append(value) + + form_codes = CodesForm(request.POST, codes=codes) + + context_codes_form = { + "form_codes": form_codes, + "num_tester": num_tester, + "use_technique": "escalas" + } + + if form_codes.is_valid(): + codes_sort = {"product_codes": []} + + for name, value in form_codes.cleaned_data.items(): + codes_sort["product_codes"].append({name: value}) + + codes_sort["sort_codes"] = sorts_code + request.session["form_codes"] = codes_sort + return redirect(reverse("cata_system:panel_configuracion_words")) + else: + context_codes_form["error"] = "error en los datos recibidos" + + return render(request, "tecnicas/create_sesion/configuracion-panel-codes.html", context_codes_form) + + @staticmethod + def controllGetRATA(request: HttpRequest, data): + num_products = data["numero_productos"] + codes_products = generarCodigos(num_products) + form_codes = CodesForm(codes=codes_products) + + context_codes_form = { + "form_codes": form_codes, + "num_tester": 0, + "use_technique": "rata" + } + + return render(request, "tecnicas/create_sesion/configuracion-panel-codes.html", context_codes_form) + + @staticmethod + def controllPostRATA(request: HttpRequest): + codes = [] + context_codes_form = {} + + for name, value in request.POST.items(): + if name.__contains__("producto_"): + codes.append(value) + + form_codes = CodesForm(request.POST, codes=codes) + + context_codes_form = { + "form_codes": form_codes, + "use_technique": "rata" + } + + if form_codes.is_valid(): + request.session["form_codes"] = codes + return redirect(reverse("cata_system:panel_configuracion_words")) + else: + context_codes_form["error"] = "error en los datos recibidos" + + return render(request, "tecnicas/create_sesion/configuracion-panel-codes.html", context_codes_form) + + @staticmethod + def defineInfoConvencional(data): + num_products = data["numero_productos"] + num_tester = data["numero_catadores"] + return ( + num_products, + num_tester + ) diff --git a/tecnicas/controllers/views_controller/create_session/panel_create_controller.py b/tecnicas/controllers/views_controller/create_session/panel_create_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..42a3b79c3431966cf333137549af310f92dab72e --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panel_create_controller.py @@ -0,0 +1,274 @@ +from django.http import HttpRequest, JsonResponse +from django.db import transaction +from django.shortcuts import render +from tecnicas.utils import general_error +from tecnicas.controllers import TecnicaController, EscalaController, ProductosController, OrdenesController, EstiloPalabrasController, PalabrasController, SesionController +from tecnicas.utils import deleteDataSession + + +class PanelCreateController(): + def __init__(self): + pass + + @staticmethod + def controllGetEscalas(request: HttpRequest): + return render( + request, 'tecnicas/create_sesion/creando_sesion.html') + + @staticmethod + def controllPostEscalas(request: HttpRequest): + if request.POST.get('action') == 'create_session': + if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"): + deleteDataSession(request) + return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo") + + with transaction.atomic(): + # ////////////////////////////////////////////////////// # + # + # First step: Create technique and scale with their tags # + # + # ////////////////////////////////////////////////////// # + data_basic = request.session["form_basic"] + controllerTechnique = TecnicaController() + controllerTechnique.setTechniqueFromBasicData(basic=data_basic) + technique = controllerTechnique.saveTechnique() + if not technique: + return general_error("Error al guardar la técnica") + + data_scale = { + "id_scale": data_basic["tipo_escala"], + "size": data_basic["tamano_escala"], + "technique": technique + } + + controllerScale = EscalaController(data=data_scale) + + scale = controllerScale.saveScale() + if isinstance(scale, dict): + return general_error(scale["error"]) + + dict_tags = request.session["form_tags"] + saved_related_tags = controllerScale.realteTags(dict_tags) + if "error" in saved_related_tags: + return general_error(saved_related_tags["error"]) + + # ////////////////////////////////////////////////////////// # + # + # Second step: Create orders, productos and set the position # + # + # ////////////////////////////////////////////////////////// # + data_codes = request.session["form_codes"] + + list_codes_dict = data_codes["product_codes"] + + codes = [] + for product in list_codes_dict: + code = next(iter(product.values())) + codes.append(code) + + controllerProducts = ProductosController( + codes=codes, + technique=technique + ) + + controllerProducts.setProductsNoSave() + saved_prodcuts = controllerProducts.saveProducts() + if isinstance(saved_prodcuts, dict): + return general_error(saved_prodcuts["error"]) + + raw_sort_codes = data_codes["sort_codes"] + controllerOrdes = OrdenesController( + raw_orders=raw_sort_codes, + list_products=saved_prodcuts, + technique=technique + ) + + controllerOrdes.setOrdersToSave() + saved_orders = controllerOrdes.saveOrders() + if isinstance(saved_orders, dict): + return general_error(saved_orders["error"]) + + seded_positions = controllerOrdes.setPositions() + if isinstance(seded_positions, dict): + return general_error(seded_positions["error"]) + + saved_postions = controllerOrdes.savePositions() + if isinstance(saved_postions, dict): + return general_error(saved_prodcuts["error"]) + + # /////////////////////////////////////////////////////// # + # + # Third step: Create relations technique with Words Style # + # + # /////////////////////////////////////////////////////// # + ids_words = request.session["form_words"] + words_controller = PalabrasController(ids=ids_words) + + words_to_use = words_controller.setWords() + if isinstance(words_to_use, dict): + return general_error(words_to_use["error"]) + + style_controller = EstiloPalabrasController( + technique=technique, words=words_to_use) + + instace_style = style_controller.createAndSaveInstaceStyle() + if isinstance(instace_style, dict): + return general_error(instace_style["error"]) + + words_using = style_controller.relatedWords() + if isinstance(words_using, dict): + return general_error(words_using["error"]) + + # //////////////////////////////////////////////////////// # + # + # Fourth step: Create session and relat with the technique # + # + # //////////////////////////////////////////////////////// # + session_controller = SesionController( + name_session=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None, + technique=technique, + creator=request.user.user_presentador + ) + + setting_session = session_controller.setSession() + if isinstance(setting_session, dict): + return general_error(setting_session["error"]) + + saved_session = session_controller.saveSession() + if isinstance(saved_session, dict): + return general_error(saved_session["error"]) + + context = { + "message": "sesión creada", + "data": { + "codigo_sesion": saved_session.codigo_sesion, + "nombre_sesion": saved_session.nombre_sesion + } + } + + # ////////////////////////////////// # + # + # Final step: Delete date in session # + # + # ////////////////////////////////// # + + deleteDataSession(request) + return JsonResponse(context) + else: + return general_error("No se ha establecido acción") + + @staticmethod + def controllPostRATA(request: HttpRequest): + if request.POST.get('action') == 'create_session': + if not request.session.get("form_tags") or not request.session.get("form_codes") or not request.session.get("form_words"): + deleteDataSession(request) + return general_error("No se ha especificado información necesaria para la creación de la sesión, por favor, vuelve a intentarlo") + + with transaction.atomic(): + # ////////////////////////////////////////////////////// # + # + # First step: Create technique and scale with their tags # + # + # ////////////////////////////////////////////////////// # + data_basic = request.session["form_basic"] + data_basic["numero_catadores"] = 0 + data_basic["numero_repeticiones"] = 1 + controllerTechnique = TecnicaController() + controllerTechnique.setTechniqueFromBasicData(basic=data_basic) + technique = controllerTechnique.saveTechnique() + if not technique: + return general_error("Error al guardar la técnica") + + data_scale = { + "id_scale": data_basic["tipo_escala"], + "size": data_basic["tamano_escala"], + "technique": technique + } + + controllerScale = EscalaController(data=data_scale) + + scale = controllerScale.saveScale() + if isinstance(scale, dict): + return general_error(scale["error"]) + + dict_tags = request.session["form_tags"] + saved_related_tags = controllerScale.realteTags(dict_tags) + if "error" in saved_related_tags: + return general_error(saved_related_tags["error"]) + + # ////////////////////////////////////////////// # + # + # Second step: Create productos with their codes # + # + # ////////////////////////////////////////////// # + codes = request.session["form_codes"] + + controllerProducts = ProductosController( + codes=codes, + technique=technique + ) + + controllerProducts.setProductsNoSave() + saved_prodcuts = controllerProducts.saveProducts() + if isinstance(saved_prodcuts, dict): + return general_error(saved_prodcuts["error"]) + + # /////////////////////////////////////////////////////// # + # + # Third step: Create relations technique with Words Style # + # + # /////////////////////////////////////////////////////// # + ids_words = request.session["form_words"] + words_controller = PalabrasController(ids=ids_words) + + words_to_use = words_controller.setWords() + if isinstance(words_to_use, dict): + return general_error(words_to_use["error"]) + + style_controller = EstiloPalabrasController( + technique=technique, words=words_to_use) + + instace_style = style_controller.createAndSaveInstaceStyle() + if isinstance(instace_style, dict): + return general_error(instace_style["error"]) + + words_using = style_controller.relatedWords() + if isinstance(words_using, dict): + return general_error(words_using["error"]) + + # //////////////////////////////////////////////////////// # + # + # Fourth step: Create session and relat with the technique # + # + # //////////////////////////////////////////////////////// # + session_controller = SesionController( + name_session=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None, + technique=technique, + creator=request.user.user_presentador + ) + + setting_session = session_controller.setSession() + if isinstance(setting_session, dict): + return general_error(setting_session["error"]) + + saved_session = session_controller.saveSession() + if isinstance(saved_session, dict): + return general_error(saved_session["error"]) + + context = { + "message": "sesión creada", + "data": { + "codigo_sesion": saved_session.codigo_sesion, + "nombre_sesion": saved_session.nombre_sesion + } + } + + # ////////////////////////////////// # + # + # Final step: Delete date en session # + # + # ////////////////////////////////// # + deleteDataSession(request) + return JsonResponse(context) + else: + return general_error("No se ha establecido acción") diff --git a/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py b/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..852bd7d245c22894b486e2fd0f5a65e462a8b201 --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py @@ -0,0 +1,71 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from tecnicas.forms import SesionTagsForm, EtiquetaForm +from tecnicas.models import TipoEscala + + +class PanelTagsController(): + def __init__(self): + pass + + @staticmethod + def controllGetEscalas(request: HttpRequest, data): + ( + type_scale, + tamano_escala, + form_new_etiqueta + ) = PanelTagsController.defineInfoScale(data) + + form_etiqutas = SesionTagsForm( + longitud=tamano_escala, tipo_escala=type_scale.nombre_escala) + + context_tags = { + "form_tags": form_etiqutas, + "form_new_tag": form_new_etiqueta + } + + return render(request, "tecnicas/create_sesion/configuracion-panel-tags.html", context_tags) + + @staticmethod + def controllPostEscalas(request: HttpRequest, data): + ( + type_scale, + tamano_escala, + form_new_etiqueta + ) = PanelTagsController.defineInfoScale(data) + + values = {} + form = SesionTagsForm(request.POST, longitud=tamano_escala, + tipo_escala=type_scale.nombre_escala) + + context_tags = { + "form_tags": form, + "form_new_tag": form_new_etiqueta + } + + if form.is_valid(): + for name, value in form.cleaned_data.items(): + values[name] = value.id + + request.session["form_tags"] = values + response = redirect( + reverse("cata_system:panel_configuracion_codes")) + else: + context_tags["error"] = "ha ocurrido un error" + response = render( + request, "tecnicas/create_sesion/configuracion-panel-tags.html", context_tags) + + return response + + @staticmethod + def defineInfoScale(data): + type_scale = TipoEscala.objects.get(pk=data["tipo_escala"]) + tamano_escala = data["tamano_escala"] + form_new_etiqueta = EtiquetaForm() + + return ( + type_scale, + tamano_escala, + form_new_etiqueta + ) diff --git a/tecnicas/controllers/views_controller/create_session/panel_words_controller.py b/tecnicas/controllers/views_controller/create_session/panel_words_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..138897bfc993e5de157192468123479cbf5b32a6 --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panel_words_controller.py @@ -0,0 +1,49 @@ +from django.http import HttpRequest +from tecnicas.forms import WordForm +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import Palabra +import json + + +class PanelWordsController(): + def __init__(self): + pass + + @staticmethod + def controllGetEscalas(request: HttpRequest): + form = WordForm() + context = { + "form_word": form + } + + return render(request, "tecnicas/create_sesion/configuracion-panel-words.html", context) + + @staticmethod + def controllPostEscalas(request: HttpRequest): + form = WordForm() + context = { + "form_word": form + } + + if not request.POST.get("words"): + return render(request, "tecnicas/create_sesion/configuracion-panel-words.html", context) + + words = json.loads(request.POST.get("words")) + context["words"] = words + + ids_words = [word["id"] for word in words] + + if len(ids_words) != len(set(ids_words)): + context["error"] = "existen palabras duplicadas" + return render(request, "tecnicas/create_sesion/configuracion-panel-words.html", context) + + exist_words = Palabra.objects.filter( + id__in=ids_words).count() == len(ids_words) + + if not exist_words: + context["error"] = "algunas palabras no existen" + return render(request, "tecnicas/create_sesion/configuracion-panel-words.html", context) + + request.session["form_words"] = ids_words + return redirect(reverse("cata_system:creando_sesion")) diff --git a/tecnicas/controllers/views_controller/detalles_sesion_controller.py b/tecnicas/controllers/views_controller/detalles_sesion_controller.py deleted file mode 100644 index 5f8e3f91c57d245ed0ef13379fd773f100a9e1f0..0000000000000000000000000000000000000000 --- a/tecnicas/controllers/views_controller/detalles_sesion_controller.py +++ /dev/null @@ -1,95 +0,0 @@ -''' - -Para Tecnicas Convencionales, CATA, RATA, Escala Hedonica -Encabezados de como deben de aparecer los datos por repeticion - -| Repeticion: R -| Codigo Producto | Catador | P1 | P2 | P3 | Pn | - -Encabezados de como deben de aparecer los datos juntos - -| Repeticion | Codigo Producto | Catador | P1 | P2 | P3 | Pn | - -''' - -from ...models import SesionSensorial, Presentador, Tecnica, Palabra -from .. import CalificacionController, PalabrasController -from ...utils import controller_error -from collections import defaultdict -from tecnicas.controllers import DatoController -from tecnicas.utils import defaultdict_to_dict - - -class DetallesSesionController(): - def __init__(self, session_code: str): - self.session = SesionSensorial.objects.get(codigo_sesion=session_code) - - def getContextForView(self): - self.context = {} - - self.context["sesion"] = self.session - - self.words = PalabrasController.getWordsInTechnique( - self.session.tecnica) - self.context["palabras"] = [word.nombre_palabra for word in self.words] - - def getContextWithData(self): - ratings_for_repetition = [] - - ratings = CalificacionController.getRatingsByTechnique( - technique=self.session.tecnica) - - if isinstance(ratings, dict) or not ratings: - self.context["calificaciones"] = ratings_for_repetition - self.context["existen_calificaciones"] = False - return self.context - - data = DatoController.getWordValuesForConvecional( - ratings=ratings, technique=self.session.tecnica) - - ratings_for_repetition = defaultdict( - lambda: defaultdict(lambda: defaultdict(list))) - - for item in data: - user = item["usuarioCatador"] - rep = item["repeticion"] - prod = item["producto_code"] - - ratings_for_repetition[rep][user][prod].append({ - "nombre_palabra": item["nombre_palabra"], - "dato_valor": item["dato_valor"] - }) - - self.context["calificaciones"] = defaultdict_to_dict( - ratings_for_repetition) - self.context["existen_calificaciones"] = True - - return self.context - - @staticmethod - def startRepetition(session_code: str, username: str): - try: - creator = Presentador.objects.get(nombre_usuario=username) - session = SesionSensorial.objects.get(codigo_sesion=session_code) - technique = Tecnica.objects.get(id=session.tecnica.id) - except Presentador.DoesNotExist: - return controller_error("no existe presentador") - except SesionSensorial.DoesNotExist: - return controller_error("no existe sesión sensorial") - except Tecnica.DoesNotExist: - return controller_error("Ha ocurrido un error al recuperar la técnica") - - if creator.nombre_usuario != session.creadoPor.nombre_usuario: - return controller_error("solo el presentador que crea la sesión puede iniciar la repetición") - elif session.activo: - return controller_error("la sesión ya está activada") - elif technique.repeticion == technique.repeticiones_max: - return controller_error("se ha alcanzado el número de repeticiones máxima") - - session.activo = True - technique.repeticion = technique.repeticion + 1 - - technique.save() - session.save() - - return session diff --git a/tecnicas/controllers/views_controller/list_sessions_tester_controller.py b/tecnicas/controllers/views_controller/list_sessions_tester_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..2b775050b5bbe5372fe193308493782f00ca9eb8 --- /dev/null +++ b/tecnicas/controllers/views_controller/list_sessions_tester_controller.py @@ -0,0 +1,76 @@ +from django.core.paginator import Paginator, PageNotAnInteger +from django.db.models import OuterRef, Subquery, BooleanField +from tecnicas.models import Catador, SesionSensorial, Participacion +from tecnicas.utils import controller_error + + +class ListSessionsTesterController(): + def __init__(self): + pass + + def getContext(self, user: Catador, page: int): + context_view = {} + + response = ListSessionsTesterController.getSessionByTester(page, user) + if isinstance(response, dict): + return response + + (sessions, is_last_page, current_page) = response + context_view["sessions"] = sessions + context_view["last_page"] = is_last_page + context_view["page"] = current_page + + return context_view + + @staticmethod + def getSessionByTester(page: int, tester: Catador): + elements_by_page = 6 + + base_qs = SesionSensorial.objects.filter( + tecnica__tecnica_participacion__catador=tester + ) + + participacion_finalizado_subq = Subquery( + Participacion.objects.filter( + tecnica=OuterRef('tecnica_id'), + catador=tester + ).values('finalizado')[:1], + output_field=BooleanField() + ) + + participacion_activo_subq = Subquery( + Participacion.objects.filter( + tecnica=OuterRef('tecnica_id'), + catador=tester + ).values('activo')[:1], + output_field=BooleanField() + ) + + queryset = ( + base_qs + .select_related( + 'tecnica', + 'tecnica__tipo_tecnica', + 'tecnica__id_estilo', + ) + .annotate( + participacion_finalizado=participacion_finalizado_subq, + participacion_activo=participacion_activo_subq, + ) + .order_by('participacion_finalizado', '-activo', '-fechaCreacion') + .distinct() + ) + + paginator = Paginator(queryset, elements_by_page) + try: + sessions_in_page = paginator.page(page) + except PageNotAnInteger: + return controller_error("índice inválido") + + if not sessions_in_page.object_list: + return controller_error("Sin registros de Participaciones") + + current_page = sessions_in_page.number + is_last_page = not current_page < paginator.num_pages + + return (sessions_in_page, is_last_page, current_page) diff --git a/tecnicas/controllers/views_controller/login_tester_controller.py b/tecnicas/controllers/views_controller/login_tester_controller.py index 3ce9c33d9659fdb3fd4758754c2f74e0d7a6e619..1b9c7849593f2f58ae9c3849537783b4b9608bc4 100644 --- a/tecnicas/controllers/views_controller/login_tester_controller.py +++ b/tecnicas/controllers/views_controller/login_tester_controller.py @@ -1,12 +1,17 @@ -from ...models import Catador, SesionSensorial, Participacion -from ...utils import controller_error +from django.http import HttpRequest, JsonResponse +from django.shortcuts import render, redirect +from django.urls import reverse from django.db import transaction +from tecnicas.models import Catador, SesionSensorial, Participacion +from tecnicas.utils import controller_error class LoginTesterController(): tester: Catador session: SesionSensorial taster_participation: Participacion + current_direcction = "tecnicas/forms_tester/login_session.html" + destinity_direcction = "cata_system:catador_init_session" def __init__(self): self.tester = Catador() @@ -14,7 +19,7 @@ class LoginTesterController(): def existCredential(self, user_tester: str, code_session: str): try: - self.tester = Catador.objects.get(usuarioCatador=user_tester) + self.tester = Catador.objects.get(user__username=user_tester) self.session = SesionSensorial.objects.get( codigo_sesion=code_session) @@ -22,42 +27,73 @@ class LoginTesterController(): except (Catador.DoesNotExist, SesionSensorial.DoesNotExist): return controller_error("Credenciales inválidas") - def validateEntry(self): - if not self.tester.nombre or not self.session.codigo_sesion: - return controller_error("Credenciales no definidas") - + def validateEntryEscalas(self, request=HttpRequest): + context = {} if not self.session.activo: - return controller_error("La sesión no está activa actualmente") + context["error"] = "La sesión no está activa actualmente" + return render(request, self.current_direcction, context) if self.session.tecnica.repeticion > 1: try: self.taster_participation = Participacion.objects.get( tecnica=self.session.tecnica, catador=self.tester) - return self.taster_participation + context["error"] = "Usted ya esta dentro de la sesión" + return render(request, self.current_direcction, context) except Participacion.DoesNotExist: - return controller_error("No tienes permitido entrar a esta sesión") + context["error"] = "No tienes permitido entrar a esta sesión" + return render(request, self.current_direcction, context) else: + with transaction.atomic(): + code_session = self.session.codigo_sesion + self.session = SesionSensorial.objects.select_for_update().get( + codigo_sesion=code_session) + + max_testers = self.session.tecnica.limite_catadores + current_num_testers = Participacion.objects.filter( + tecnica=self.session.tecnica).count() + + if current_num_testers >= max_testers: + context["error"] = "La sesión ha alcanzado el número máximo de catadores" + return render(request, self.current_direcction, context) + + self.taster_participation = Participacion.objects.create( + tecnica=self.session.tecnica, + catador=self.tester, + finalizado=False + ) + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.destinity_direcction, kwargs=params)) + + def validateEntryRATA(self, request: HttpRequest): + context = {} + if not self.session.activo: + context["error"] = "La sesión no está activa actualmente" + return render(request, self.current_direcction, context) + + if self.session.tecnica.repeticion <= 1: try: self.taster_participation = Participacion.objects.get( tecnica=self.session.tecnica, catador=self.tester) - return self.taster_participation + context["error"] = "Usted ya esta dentro de la sesión" + return render(request, self.current_direcction, context) except Participacion.DoesNotExist: with transaction.atomic(): code_session = self.session.codigo_sesion self.session = SesionSensorial.objects.select_for_update().get( codigo_sesion=code_session) - max_testers = self.session.tecnica.limite_catadores - current_num_testers = Participacion.objects.filter( - tecnica=self.session.tecnica).count() - - if current_num_testers >= max_testers: - return controller_error("La sesión ha alcanzado el número máximo de catadores") - self.taster_participation = Participacion.objects.create( tecnica=self.session.tecnica, catador=self.tester, finalizado=False ) - return self.taster_participation + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.destinity_direcction, kwargs=params)) + else: + context["error"] = "Imposible acceder a esta sesión" + return render(request, self.current_direcction, context) diff --git a/tecnicas/controllers/views_controller/main_tester_form_controller.py b/tecnicas/controllers/views_controller/main_tester_form_controller.py index eb58535fa02bd7ae97b8cafd55d90715b5f0d471..f35d2fc57bed07f5d880476303944552fa59587d 100644 --- a/tecnicas/controllers/views_controller/main_tester_form_controller.py +++ b/tecnicas/controllers/views_controller/main_tester_form_controller.py @@ -10,7 +10,7 @@ class MainTesterFormController(): def __init__(self, code_session: str, user_tester: str): try: - self.tester = Catador.objects.get(usuarioCatador=user_tester) + self.tester = Catador.objects.get(user__username=user_tester) self.session = SesionSensorial.objects.get( codigo_sesion=code_session) except (Catador.DoesNotExist, SesionSensorial.DoesNotExist): @@ -21,6 +21,8 @@ class MainTesterFormController(): orders_without_tester = list(Orden.objects.select_for_update().filter( id_tecnica=self.session.tecnica, id_catador=None)) + print(orders_without_tester) + if not orders_without_tester: return controller_error("Las ordenes se han acabado") @@ -32,21 +34,20 @@ class MainTesterFormController(): return self.order_to_assign - def checkAssignOrder(self): - if not self.tester or not self.session: - return controller_error("Atributos no establecidos") - + def checkAndAssignOrder(self): try: - res_order = Orden.objects.get( + self.order_to_assign = Orden.objects.get( id_tecnica=self.session.tecnica, id_catador=self.tester) - self.order = res_order - return self.order except Orden.DoesNotExist: - return controller_error("Catador sin orden") + create = self.assignOrder() + if isinstance(create, dict): + return create + return self.order_to_assign - def isEndedSession(self, id_participation: int, repetition: int): + def isEndedSession(self, repetition: int): try: - participation = Participacion.objects.get(id=id_participation) + participation = Participacion.objects.get( + catador=self.tester, tecnica=self.session.tecnica) # ////////////////////////////////////////////////////////////// # # @@ -76,7 +77,7 @@ class MainTesterFormController(): expected_ratings_repetition = num_products * num_words - return num_ratings_now >= expected_ratings_repetition + return num_ratings_now >= expected_ratings_repetition else: return participation.finalizado except Participacion.DoesNotExist: diff --git a/tecnicas/controllers/views_controller/monitor_sesion_controller.py b/tecnicas/controllers/views_controller/monitor_sesion_controller.py deleted file mode 100644 index 36545334984f2abf204b1550d86b92926738c62f..0000000000000000000000000000000000000000 --- a/tecnicas/controllers/views_controller/monitor_sesion_controller.py +++ /dev/null @@ -1,82 +0,0 @@ -from tecnicas.models import SesionSensorial, EsAtributo, EsVocabulario, Producto, Calificacion -from tecnicas.controllers import ParticipacionController, SesionController -from tecnicas.utils import controller_error - - -class MonitorSesionController(): - def __init__(self, session_code: str): - self.code_session = session_code - - def defineSession(self): - self.session = SesionSensorial.objects.get( - codigo_sesion=self.code_session) - - def monitorView(self): - try: - self.sensorial_session = SesionSensorial.objects.select_related( - "tecnica" - ).only( - "nombre_sesion", - "tecnica__limite_catadores", - ).get(codigo_sesion=self.code_session) - - self.participations = ParticipacionController.getParticipationsInTechinique( - self.sensorial_session.tecnica) - except SesionSensorial.DoesNotExist as error: - return controller_error("No existe Sesión sensorial") - - context = { - "session_name": self.sensorial_session.nombre_sesion, - "max_testers": self.sensorial_session.tecnica.limite_catadores, - "current_testers": len(self.participations), - "active_testers": len([part for part in self.participations if part.activo]), - "participations": self.participations - } - - return context - - def getExpectedRatings(self): - num_products = Producto.objects.filter( - id_tecnica=self.session.tecnica).count() - - style_words = self.session.tecnica.id_estilo - - num_words: int - - if style_words.nombre_estilo == "atributos": - num_words = EsAtributo.objects.get( - id_tecnica=self.session.tecnica).palabras.count() - elif style_words.nombre_estilo == "vocabulario": - num_words = EsVocabulario.objects.get( - id_tecnica=self.session.tecnica).id_vocabulario.palabras.count() - - return num_products * num_words - - def checkAllParticipantsEnded(self): - self.defineSession() - - technique = self.session.tecnica - - expected_ratings_repetition = self.getExpectedRatings() - - all_participations = ParticipacionController.getParticipationsInTechinique( - technique=technique) - - if len(all_participations) < technique.limite_catadores: - return (False, "No se ha alcanzado el número máximo de Catadores") - - for particiapation in all_participations: - num_ratings_now = Calificacion.objects.filter( - id_tecnica=technique, id_catador=particiapation.catador, num_repeticion=technique.repeticion).count() - - if num_ratings_now < expected_ratings_repetition: - return (False, "No todos los catadores han finalizado su evaluación") - - return (True, "Puedes finalizar la sesión") - - def finishSession(self): - response = SesionController.finishRepetion(self.session) - if isinstance(response, dict): - return controller_error(response["error"]) - self.defineSession() - return self.session diff --git a/tecnicas/controllers/views_controller/session_management/details_controller.py b/tecnicas/controllers/views_controller/session_management/details_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..b98cfe897ed711c8084f45eb50e152f5641fc229 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details_controller.py @@ -0,0 +1,13 @@ +from tecnicas.models import SesionSensorial, Presentador, Tecnica +from tecnicas.utils import controller_error + + +class DetallesController(): + url_template = "tecnicas/manage_sesions/detalles-sesion.html" + + def __init__(self, session: SesionSensorial): + self.session = session + + def deleteSesorialSession(self): + technique = Tecnica.objects.get(id=self.session.tecnica.id) + technique.delete() \ No newline at end of file diff --git a/tecnicas/controllers/views_controller/session_management/details_escala_controller.py b/tecnicas/controllers/views_controller/session_management/details_escala_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..9580e3cd41c7058944b9925611789aa0f6255c5e --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details_escala_controller.py @@ -0,0 +1,103 @@ +''' + +Para Tecnicas Convencionales, CATA, RATA, Escala Hedonica +Encabezados de como deben de aparecer los datos por repeticion + +| Repeticion: R +| Codigo Producto | Catador | P1 | P2 | P3 | Pn | + +Encabezados de como deben de aparecer los datos juntos + +| Repeticion | Codigo Producto | Catador | P1 | P2 | P3 | Pn | + +''' +from django.http import HttpRequest +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import SesionSensorial, Presentador, Tecnica +from tecnicas.controllers import DatoController, CalificacionController, PalabrasController +from .details_controller import DetallesController +from tecnicas.utils import defaultdict_to_dict, controller_error +from collections import defaultdict + + +class DetallesEscalasController(DetallesController): + url_template = "tecnicas/manage_sesions/detalles-sesion.html" + + def __init__(self, session: SesionSensorial): + super().__init__(session) + + def getResponse(self, request: HttpRequest, error: str = "", message: str = ""): + context = self.getContext() + + if error != "" or error: + context["error"] = error + if message != "" or message: + context["message"] = message + + return render( + request, self.url_template, context) + + def getContext(self): + self.context = { + "use_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + self.context["sesion"] = self.session + self.words = PalabrasController.getWordsInTechnique( + self.session.tecnica) + self.context["palabras"] = [word.nombre_palabra for word in self.words] + + ratings_for_repetition = [] + + ratings = CalificacionController.getRatingsByTechnique( + technique=self.session.tecnica) + + if isinstance(ratings, dict) or not ratings: + self.context["calificaciones"] = ratings_for_repetition + self.context["existen_calificaciones"] = False + return self.context + + data = DatoController.getWordValuesForConvecional( + ratings=ratings, technique=self.session.tecnica) + + ratings_for_repetition = defaultdict( + lambda: defaultdict(lambda: defaultdict(list))) + + for item in data: + user = item["usuarioCatador"] + rep = item["repeticion"] + prod = item["producto_code"] + + ratings_for_repetition[rep][user][prod].append({ + "nombre_palabra": item["nombre_palabra"], + "dato_valor": item["dato_valor"] + }) + + self.context["calificaciones"] = defaultdict_to_dict( + ratings_for_repetition) + self.context["existen_calificaciones"] = True + + return self.context + + def startRepetition(self, presenter: Presentador): + creator = presenter + technique = self.session.tecnica + + if creator.user.username != self.session.creadoPor.user.username: + return self.getResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición") + elif self.session.activo: + return self.getResponse(error="La sesión ya está activada") + elif technique.repeticion == technique.repeticiones_max: + return self.getResponse(error="Se ha alcanzado el número de repeticiones máxima") + + self.session.activo = True + technique.repeticion = technique.repeticion + 1 + + technique.save() + self.session.save() + + parameters = { + "session_code": self.session.codigo_sesion + } + return redirect( + reverse("cata_system:monitor_sesion", kwargs=parameters)) diff --git a/tecnicas/controllers/views_controller/session_management/details_rata_controller.py b/tecnicas/controllers/views_controller/session_management/details_rata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..c0b437791c57de37b9354b41e7d18bdd33417635 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details_rata_controller.py @@ -0,0 +1,56 @@ +from django.http import HttpRequest +from django.shortcuts import render, redirect +from tecnicas.models import SesionSensorial +from tecnicas.controllers import PalabrasController, DatoController, CalificacionController +from tecnicas.utils import defaultdict_to_dict +from .details_controller import DetallesController +from collections import defaultdict + + +class DetallesRATAController(DetallesController): + def __init__(self, session: SesionSensorial): + super().__init__(session) + + def getResponse(self, request: HttpRequest, error: str = ""): + context = self.getContext() + if error != "" or error: + context["error"] = error + return render( + request, self.url_template, context) + + def getContext(self): + self.context = {} + self.context["sesion"] = self.session + self.words = PalabrasController.getWordsInTechnique( + self.session.tecnica) + self.context["palabras"] = [word.nombre_palabra for word in self.words] + + ratings_for_repetition = [] + + ratings = CalificacionController.getRatingsByTechnique( + technique=self.session.tecnica) + + if isinstance(ratings, dict) or not ratings: + self.context["calificaciones"] = ratings_for_repetition + self.context["existen_calificaciones"] = False + return self.context + + data = DatoController.getWordValuesForConvecional( + ratings=ratings, technique=self.session.tecnica) + + ratings_for_repetition = defaultdict( + lambda: defaultdict(lambda: defaultdict(list))) + + for item in data: + user = item["usuarioCatador"] + rep = item["repeticion"] + prod = item["producto_code"] + + ratings_for_repetition[rep][user][prod].append({ + "nombre_palabra": item["nombre_palabra"], + "dato_valor": item["dato_valor"] + }) + + self.context["calificaciones"] = defaultdict_to_dict( + ratings_for_repetition) + self.context["existen_calificaciones"] = True diff --git a/tecnicas/controllers/views_controller/session_management/monitor_controller.py b/tecnicas/controllers/views_controller/session_management/monitor_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..cf0dacad71a06db7a9c1d94deef1e9c7b79ec5fd --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/monitor_controller.py @@ -0,0 +1,10 @@ +from tecnicas.models import SesionSensorial + + +class MonitorController(): + def __init__(self, session: SesionSensorial): + self.sensorial_session = session + + def updataSession(self): + self.sensorial_session = SesionSensorial.objects.get( + codigo_sesion=self.sensorial_session.codigo_sesion) diff --git a/tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py b/tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..eeb47150440a00bc262f7c7e4cb5b4b1414cacf6 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py @@ -0,0 +1,93 @@ +from django.http import HttpRequest +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import SesionSensorial, EsAtributo, EsVocabulario, Producto, Calificacion +from tecnicas.controllers import ParticipacionController, SesionController +from tecnicas.utils import controller_error +from .monitor_controller import MonitorController + + +class MonitorEscalasController(MonitorController): + url_view = "tecnicas/manage_sesions/monitor-sesion.html" + + def __init__(self, session: SesionController): + super().__init__(session) + + def controlGetResponse(self, request: HttpRequest, error: str = "", message: str = ""): + self.setContext() + + if error != "" or error: + self.context["error"] = error + if message != "" or message: + self.context["message"] = message + + return render(request, self.url_view, self.context) + + def controlPostResponseFinishSession(self, request: HttpRequest): + self.setContext() + (is_all_end, message) = self.checkAllParticipantsEnded() + if not is_all_end: + self.context["error"] = message + return render(request, "tecnicas/manage_sesions/monitor-sesion.html", self.context) + response = self.finishSession() + if isinstance(response, dict): + self.context["error"] = response["error"] + return render(request, "tecnicas/manage_sesions/monitor-sesion.html", self.context) + self.context["message"] = message + return redirect(reverse("cata_system:detalles_sesion", kwargs={"session_code": self.sensorial_session.codigo_sesion})) + + def setContext(self): + self.participations = ParticipacionController.getParticipationsInTechinique( + self.sensorial_session.tecnica) + + self.context = { + "code_session": self.sensorial_session.codigo_sesion, + "session_name": self.sensorial_session.nombre_sesion, + "max_testers": self.sensorial_session.tecnica.limite_catadores, + "current_testers": len(self.participations), + "active_testers": len([part for part in self.participations if part.activo]), + "participations": self.participations, + "use_technique": self.sensorial_session.tecnica.tipo_tecnica.nombre_tecnica + } + + def getExpectedRatings(self): + num_products = Producto.objects.filter( + id_tecnica=self.sensorial_session.tecnica).count() + style_words = self.sensorial_session.tecnica.id_estilo + num_words: int + + if style_words.nombre_estilo == "atributos": + num_words = EsAtributo.objects.get( + id_tecnica=self.sensorial_session.tecnica).palabras.count() + elif style_words.nombre_estilo == "vocabulario": + num_words = EsVocabulario.objects.get( + id_tecnica=self.sensorial_session.tecnica).id_vocabulario.palabras.count() + + return num_products * num_words + + def checkAllParticipantsEnded(self): + technique = self.sensorial_session.tecnica + + expected_ratings_repetition = self.getExpectedRatings() + + all_participations = ParticipacionController.getParticipationsInTechinique( + technique=technique) + + if len(all_participations) < technique.limite_catadores: + return (False, "No se ha alcanzado el número máximo de Catadores") + + for particiapation in all_participations: + num_ratings_now = Calificacion.objects.filter( + id_tecnica=technique, id_catador=particiapation.catador, num_repeticion=technique.repeticion).count() + + if num_ratings_now < expected_ratings_repetition: + return (False, "No todos los catadores han finalizado su evaluación") + + return (True, "Puedes finalizar la sesión") + + def finishSession(self): + response = SesionController.finishRepetion(self.sensorial_session) + if isinstance(response, dict): + return controller_error(response["error"]) + self.updataSession() + return self.sensorial_session diff --git a/tecnicas/controllers/views_controller/tester_list_controller.py b/tecnicas/controllers/views_controller/tester_list_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..85a10afb61e12375a384e7db538aacd5d6b54734 --- /dev/null +++ b/tecnicas/controllers/views_controller/tester_list_controller.py @@ -0,0 +1,58 @@ +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from tecnicas.models import Catador +from tecnicas.utils import controller_error + + +class TesterListController(): + def __init__(self, page: int): + self.num_page = page + + def getContext(self): + view_context = {"num_page": self.num_page} + + res_tuple = TesterListController.getTestersByPage(page=self.num_page) + + if isinstance(res_tuple, dict): + view_context["error"] = res_tuple["error"] + return view_context + + (testers, last_page) = res_tuple + + view_context["testers"] = testers + view_context["last_page"] = last_page + + return view_context + + @staticmethod + def getTestersByPage(page: int): + elements_by_page = 6 + + queryset = ( + Catador.objects + .select_related( + "user" + ) + .only( + "user__first_name", + "user__last_name", + "user__username", + "user__email", + "genero", + "telefono", + "nacimiento" + ) + .order_by("-user__date_joined") + ) + + paginator = Paginator(queryset, elements_by_page) + try: + testers_in_page = paginator.page(page) + except PageNotAnInteger: + return controller_error("índice inválido") + + if not testers_in_page.object_list: + return controller_error("Sin registros de Catadores") + + is_last_page = not testers_in_page.number < paginator.num_pages + + return (testers_in_page, is_last_page) \ No newline at end of file diff --git a/tecnicas/decorators/required_presenter.py b/tecnicas/decorators/required_presenter.py index c7c02f16a5df7b4c1a1fd083c6faa7cce7dc8267..b86849ad4cf9c8b02d2928fe0be95aa8adbcd031 100644 --- a/tecnicas/decorators/required_presenter.py +++ b/tecnicas/decorators/required_presenter.py @@ -8,7 +8,7 @@ def required_presenter(view_func): if not request.user.is_authenticated: return redirect("cata_system:autenticacion") - if not hasattr(request.user, "presentador"): + if not hasattr(request.user, "user_presentador"): raise PermissionDenied( "Solo los presentadores pueden acceder a esta vista") return view_func(request, *args, **kwargs) diff --git a/tecnicas/forms/catador_form.py b/tecnicas/forms/catador_form.py index 98e87e370f290ec28232622d8bc3f93bdc4475c6..465abd0de2e2ee7c2a3d976a8cff028dfde3340f 100644 --- a/tecnicas/forms/catador_form.py +++ b/tecnicas/forms/catador_form.py @@ -1,25 +1,148 @@ from django import forms -from ..models import Catador - -class CatadorForm(forms.ModelForm): - class Meta: - model = Catador - fields = "__all__" - widgets = { - "usuarioCatador": forms.TextInput( attrs={ "class": "ct-inputs-pos-cata bg-surface-ligt text-center w-full p-1 rounded-lg text-black disabled:bg-surface-general" } ), - "nombre": forms.TextInput( attrs={ "class": "ct-inputs-pos-cata bg-surface-ligt text-center w-full p-1 rounded-lg text-black disabled:bg-surface-general" } ), - "apellido": forms.TextInput( attrs={ "class": "ct-inputs-pos-cata bg-surface-ligt text-center w-full p-1 rounded-lg text-black disabled:bg-surface-general" } ), - "telefono": forms.NumberInput( attrs={ "class": "ct-inputs-pos-cata bg-surface-ligt text-center w-full p-1 rounded-lg text-black disabled:bg-surface-general", "max": "10", "min": "10" } ), - "correo": forms.EmailInput( attrs={ "class": "ct-inputs-pos-cata bg-surface-ligt text-center w-full p-1 rounded-lg text-black disabled:bg-surface-general" } ), - "fechaNacimiento": forms.DateInput( attrs={ "class": "ct-inputs-pos-cata bg-surface-ligt text-center w-full p-1 rounded-lg text-black disabled:bg-surface-general", "type": "date", } ), +from django.contrib.auth.models import User +from django.core.validators import RegexValidator, EmailValidator + + +class CatadorForm(forms.Form): + styles_input = "ct-inputs-pos-cata bg-surface-ligt text-center w-full p-1 rounded-lg text-black disabled:bg-surface-alter-card" + + nombre_usuario = forms.CharField( + label="Nombre de usuario", + max_length=30, + min_length=5, + required=True, + validators=[ + RegexValidator( + regex=r'^(?![0-9._])[A-Za-z0-9._]+$', + message=( + "El nombre de usuario debe iniciar con una letra y solo puede " + "contener letras, números, puntos y guiones bajos." + ), + code='invalid_username' + ) + ], + widget=forms.TextInput( + attrs={ + "placeholder": "Ej. mario_hugo23", + "class": styles_input, + } + ) + ) + + nombre = forms.CharField( + label="Nombre del catador", + max_length=50, + required=True, + validators=[ + RegexValidator( + regex=r'^[A-Za-zÁÉÍÓÚáéíóúÑñ\s]+$', + message="El nombre solo puede contener letras y espacios." + ) + ], + widget=forms.TextInput( + attrs={ + "placeholder": "Ej. Mario", + "class": styles_input, + } + ) + ) + + apellido = forms.CharField( + label="Apellido del catador", + max_length=50, + required=True, + validators=[ + RegexValidator( + regex=r'^[A-Za-zÁÉÍÓÚáéíóúÑñ\s]+$', + message="El apellido solo puede contener letras y espacios." + ) + ], + widget=forms.TextInput( + attrs={ + "placeholder": "Ej. Sánchez", + "class": styles_input, + } + ) + ) + + telefono = forms.CharField( + label="Teléfono", + max_length=10, + required=True, + validators=[ + RegexValidator( + regex=r'^\d{10}$', + message="El teléfono debe contener solo números (puede incluir + al inicio)." + ) + ], + widget=forms.TextInput( + attrs={ + "placeholder": "Ej. 5512345678", + "class": styles_input, + "type": "tel", + } + ) + ) + + correo = forms.EmailField( + label="Correo electrónico", + required=True, + validators=[EmailValidator( + message="Introduce un correo electrónico válido.")], + widget=forms.EmailInput( + attrs={ + "placeholder": "Ej. mario@example.com", + "class": styles_input, + } + ) + ) + + fecha_nacimiento = forms.DateField( + label="Fecha de nacimiento", + required=True, + widget=forms.DateInput( + attrs={ + "type": "date", + "class": styles_input, + } + ), + error_messages={ + "invalid": "Introduce una fecha válida (DD-MM-YYYY)." } + ) + + GENERO_OPCIONES = [ + ('Hombre', 'Hombre'), + ('Mujer', 'Mujer') + ] + + genero = forms.ChoiceField( + label="Género", + required=True, + choices=GENERO_OPCIONES, + widget=forms.RadioSelect( + attrs={ + "class": "ct-inputs-pos-cata radio radio-warning checked:bg-btn-secondary mx-2 bg-surface-ligt disabled:bg-ct-error" + } + ) + ) + + is_update = forms.BooleanField( + initial=False, + required=False, + widget=forms.HiddenInput() + ) def clean(self): - data_cleand = super().clean() - - phone = data_cleand["telefono"] - size_phone = len(str(abs(phone))) - print("tamano de telefono", size_phone) - if size_phone != 10: - self.add_error("telefono", "telefono debe tener 10 digitos") - return data_cleand \ No newline at end of file + cleaned_data = super().clean() + username = cleaned_data.get("nombre_usuario") + is_update = cleaned_data.get("is_update") + + print(username, is_update) + + if not is_update: + if User.objects.filter(username__iexact=username).exists(): + raise forms.ValidationError( + "Este nombre de usuario ya está registrado.") + + return cleaned_data diff --git a/tecnicas/forms/codes_form.py b/tecnicas/forms/codes_form.py index 575c46221fd2f63420973438df247c5f1328bb2b..5d3a1c0998a7d4c13a856fb2770945f8be2067c5 100644 --- a/tecnicas/forms/codes_form.py +++ b/tecnicas/forms/codes_form.py @@ -1,10 +1,11 @@ from django import forms + class CodesForm(forms.Form): - def __init__(self, *args, codes = [], **kwargs): + def __init__(self, *args, codes=[], **kwargs): super().__init__(*args, **kwargs) for index, code in enumerate(codes): self.fields[f'producto_{index+1}'] = forms.CharField(max_length=3, required=True, min_length=3, initial=code, label=f"codigo {index+1}", widget=forms.TextInput(attrs={ "class": "ct-code bg-surface-ligt p-1 border-b-1 text-center w-full disabled:bg-surface-general uppercase" - })) \ No newline at end of file + })) diff --git a/tecnicas/forms/sesion_basic_form.py b/tecnicas/forms/sesion_basic_form.py index b724c261ae20f01e8f8d869611a22a9dae39e6df..87529c24fbe3cdcfbdcc4f4353d631d81ce9a189 100644 --- a/tecnicas/forms/sesion_basic_form.py +++ b/tecnicas/forms/sesion_basic_form.py @@ -5,7 +5,8 @@ from ..models import EstiloPalabra class SesionBasicForm(forms.Form): - id_tecnica = forms.IntegerField(widget=forms.HiddenInput()) + sizes_structure = [5, 7, 9] + sizes_continue = [9, 13, 15] nombre_sesion = forms.CharField(max_length=255, widget=forms.TextInput(attrs={ "class": "bg-surface-ligt border-b-1 text-center w-full p-1", @@ -28,36 +29,37 @@ class SesionBasicForm(forms.Form): "placeholder": "Solo números" }), required=True) - tamano_escala = forms.IntegerField(widget=forms.NumberInput(attrs={ - "class": "bg-surface-ligt p-1 border-b-1 text-center w-full", - }), required=True, min_value=5) - instrucciones = forms.CharField(max_length=255, widget=forms.TextInput(attrs={ "class": "bg-surface-ligt border-b-1 text-center w-full p-1", "placeholder": "Este campo es opcional" }), required=False) - def __init__(self, *args, id_tecnica_new=0, **kwargs): + def __init__(self, *args, initial_conf: dict = None, **kwargs): super().__init__(*args, **kwargs) + if initial_conf is None: + initial_conf = {} + self.fields['estilo_palabras'] = forms.ModelChoiceField(queryset=EstiloPalabra.objects.all(), widget=forms.RadioSelect(attrs={ - "class": "uppercase text-lg tracking-wider font-medium p-2 px-4 active:px-5 transition-all rounded-xl bg-blue-500 text-white", + "class": "uppercase text-lg tracking-wider font-medium p-2 px-4 active:px-5 transition-all rounded-xl bg-blue-500 text-white", }), required=True, initial=EstiloPalabra.objects.first()) self.fields['tipo_escala'] = forms.ModelChoiceField(queryset=TipoEscala.objects.all(), widget=forms.RadioSelect(attrs={ "class": "uppercase text-lg tracking-wider font-medium p-2 px-4 active:px-5 transition-all rounded-xl bg-blue-500 text-white", }), required=True, initial=TipoEscala.objects.first()) - if id_tecnica_new != 0: - self.fields['id_tecnica'] = forms.IntegerField( - initial=id_tecnica_new, widget=forms.HiddenInput()) + self.fields['tamano_escala'] = forms.IntegerField(widget=forms.HiddenInput(attrs={ + "class": "cts-size-input", + }), required=True) - def clean(self): - data_clean = super().clean() + if "numero_catadores" in initial_conf: + self.fields["numero_catadores"].initial = initial_conf["numero_catadores"] - sizes_estruturada = [5, 7, 9] - sizes_continua = [9, 12, 15] + if "numero_repeticiones" in initial_conf: + self.fields["numero_repeticiones"].initial = initial_conf["numero_repeticiones"] + def clean(self): + data_clean = super().clean() escala = data_clean.get("tipo_escala") if escala and not isinstance(escala, TipoEscala): @@ -70,14 +72,7 @@ class SesionBasicForm(forms.Form): tamano_escala = data_clean.get("tamano_escala") - if escala.nombre_escala == "estructurada" and not sizes_estruturada.__contains__(tamano_escala): + if escala.nombre_escala == "estructurada" and not self.sizes_structure.__contains__(tamano_escala): self.add_error("tamano_escala", "El tamaño de la escala no aplica") - elif escala.nombre_escala == "continua" and not sizes_continua.__contains__(tamano_escala): + elif escala.nombre_escala == "continua" and not self.sizes_continue.__contains__(tamano_escala): self.add_error("tamano_escala", "El tamaño de la escala no aplica") - - id_tecnica = data_clean.get("id_tecnica") - - try: - tecnica = TipoTecnica.objects.get(pk=id_tecnica) - except (ValueError, TipoTecnica.DoesNotExist): - return data_clean diff --git a/tecnicas/forms/sesion_tags_form.py b/tecnicas/forms/sesion_tags_form.py index 7f55b05513a97ba9037767f5e3dee5ffccf724e8..8b2c85603f4bed97438a7ff16a57132cfdfddbda 100644 --- a/tecnicas/forms/sesion_tags_form.py +++ b/tecnicas/forms/sesion_tags_form.py @@ -1,25 +1,35 @@ from django import forms - from ..models import Etiqueta + class SesionTagsForm(forms.Form): - def __init__(self, *args, longitud=None, tipo_escala:str=None, **kwargs): + available_struture_tags = { + "5": ["percepcion nula", "percepcion moderada", "se puede percibir", + "percepcion intensa", "percepcion total"], + "7": ["percepcion nula", "ligera percepcion", "percepcion moderada", "se puede percibir", "buena percepcion", + "percepcion intensa", "percepcion total"], + "9": ["percepcion nula", "ligera percepcion", "poca percepcion", "percepcion moderada", "se puede percibir", "buena percepcion", + "percepcion intensa", "percepcion muy intensa", "percepcion total"] + } + + def __init__(self, *args, longitud=None, tipo_escala: str = None, **kwargs): super().__init__(*args, **kwargs) if tipo_escala == "estructurada": + use_tags = self.available_struture_tags[f"{longitud}"] for i in range(longitud): - self.fields[f'segmento_{i+1}'] = forms.ModelChoiceField(queryset=Etiqueta.objects.all(), required=True, label=f"segmento {i+1}", empty_label="Selecione opcion", widget=forms.Select(attrs={ - "class":"ct-select-op p-1 max-sm:w-full bg-surface-ligt [*]:capitalize" + self.fields[f'segmento_{i+1}'] = forms.ModelChoiceField(queryset=Etiqueta.objects.all(), initial=Etiqueta.objects.get(valor_etiqueta=use_tags[i]), required=True, label=f"segmento {i+1}", empty_label="Selecione opcion", widget=forms.Select(attrs={ + "class": "ct-select-op p-1 max-sm:w-full bg-surface-ligt [*]:capitalize" })) else: - self.fields['punto_inicial'] = forms.ModelChoiceField(queryset=Etiqueta.objects.all(), required=True, label="Punto inicial", empty_label="Selecione opcion", widget=forms.Select(attrs={ - "class":"ct-select-op p-1 max-sm:w-full bg-surface-ligt [*]:capitalize" - })) - - self.fields['punto_medio'] = forms.ModelChoiceField(queryset=Etiqueta.objects.all(), required=True, label="Punto medio", empty_label="Selecione opcion", widget=forms.Select(attrs={ - "class":"ct-select-op p-1 max-sm:w-full bg-surface-ligt [*]:capitalize" - })) - - self.fields['punto_final'] = forms.ModelChoiceField(queryset=Etiqueta.objects.all(), required=True, label="Punto final", empty_label="Selecione opcion", widget=forms.Select(attrs={ - "class":"ct-select-op p-1 max-sm:w-full bg-surface-ligt [*]:capitalize" - })) \ No newline at end of file + self.fields['punto_inicial'] = forms.ModelChoiceField(queryset=Etiqueta.objects.all(), initial=Etiqueta.objects.get(valor_etiqueta="percepcion nula"), required=True, label="Punto inicial", empty_label="Selecione opcion", widget=forms.Select(attrs={ + "class": "ct-select-op p-1 max-sm:w-full bg-surface-ligt [*]:capitalize" + })) + + self.fields['punto_medio'] = forms.ModelChoiceField(queryset=Etiqueta.objects.all(), initial=Etiqueta.objects.get(valor_etiqueta="se puede percibir"), required=True, label="Punto medio", empty_label="Selecione opcion", widget=forms.Select(attrs={ + "class": "ct-select-op p-1 max-sm:w-full bg-surface-ligt [*]:capitalize" + })) + + self.fields['punto_final'] = forms.ModelChoiceField(queryset=Etiqueta.objects.all(), initial=Etiqueta.objects.get(valor_etiqueta="percepcion total"), required=True, label="Punto final", empty_label="Selecione opcion", widget=forms.Select(attrs={ + "class": "ct-select-op p-1 max-sm:w-full bg-surface-ligt [*]:capitalize" + })) diff --git a/tecnicas/middlewares/presenter_middleware.py b/tecnicas/middlewares/presenter_middleware.py index 83d71b9ef1062489c1c21400efe92c830c4804a5..4180c589ae4ca5134625de891b76167bb52d9c45 100644 --- a/tecnicas/middlewares/presenter_middleware.py +++ b/tecnicas/middlewares/presenter_middleware.py @@ -1,18 +1,17 @@ -from django.core.exceptions import PermissionDenied - class PresenterAccessMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): path = request.path_info - + if path.startswith('/cata/presenter/'): if not request.user.is_authenticated: from django.shortcuts import redirect return redirect("cata_system:autenticacion") if not hasattr(request.user, 'user_presentador'): + from django.shortcuts import redirect return redirect("cata_system:autenticacion") return self.get_response(request) diff --git a/tecnicas/middlewares/tester_middleware.py b/tecnicas/middlewares/tester_middleware.py index 21266100e0761e55dc2f6f7e4d5bb23bb29c65fa..b76339208dec73ad613505c2918e5e2726a88e4a 100644 --- a/tecnicas/middlewares/tester_middleware.py +++ b/tecnicas/middlewares/tester_middleware.py @@ -9,38 +9,13 @@ class TesterAccessMiddleware: def __call__(self, request): path = request.path_info - if path.startswith('/cata/tester/'): + if path.startswith('/cata/testers/'): if not request.user.is_authenticated: from django.shortcuts import redirect return redirect("cata_system:catador_login") - if not hasattr(request.user, 'catador'): + if not hasattr(request.user, 'user_catador'): + from django.shortcuts import redirect return redirect("cata_system:catador_login") return self.get_response(request) - -# from django.http import HttpRequest - - -# class LoginTesterMiddleware(): -# def __init__(self, get_response): -# self.get_response = get_response - -# def __call__(self, req: HttpRequest): -# base_url_protected = "/cata/testers/" - -# if req.path.startswith(base_url_protected): -# if not "cata_username" in req.session: -# id_participacion = req.COOKIES.get("id_participacion") -# if id_participacion: -# from tecnicas.controllers import ParticipacionController -# ParticipacionController.outSession(id_participacion) -# from django.shortcuts import redirect -# from django.urls import reverse -# response = redirect(reverse("cata_system:catador_login")) -# response.delete_cookie("id_participacion") -# return response - -# response = self.get_response(req) - -# return response diff --git a/tecnicas/models/participacion.py b/tecnicas/models/participacion.py index f6ff79e8fa847a93ce60b5fc5a3c678659d25570..8086129c7a6395539b5fbdc0d37f1a5f591218f9 100644 --- a/tecnicas/models/participacion.py +++ b/tecnicas/models/participacion.py @@ -12,4 +12,4 @@ class Participacion(models.Model): finalizado = models.BooleanField(default=True) def __str__(self): - return f"{self.catador.usuarioCatador} {'activo' if self.activo else 'no activo'}" + return f"{self.catador.user.username} {'activo' if self.activo else 'no activo'}" diff --git a/tecnicas/models/sesion_sensorial.py b/tecnicas/models/sesion_sensorial.py index ee9d0d9d291164b7e3af4366380ca92bddd94c58..466b9786687ebf725f1a505f2667fae19013de5b 100644 --- a/tecnicas/models/sesion_sensorial.py +++ b/tecnicas/models/sesion_sensorial.py @@ -17,4 +17,9 @@ class SesionSensorial(models.Model): Tecnica, on_delete=models.CASCADE, related_name="sesion_tecnica") def __str__(self): - return self.nombre_sesion if self.nombre_sesion else self.codigo_sesion + name_str = f"{self.codigo_sesion}" + + if self.nombre_sesion: + name_str += f" : {self.nombre_sesion}" + + return name_str diff --git a/tecnicas/static/js/catador-craete.js b/tecnicas/static/js/catador-craete.js index 40d30dd830db370d8b4ee98162a0ad0293876175..4747e8d2270d4d3e59b3b56731375795125dc193 100644 --- a/tecnicas/static/js/catador-craete.js +++ b/tecnicas/static/js/catador-craete.js @@ -7,7 +7,6 @@ for (let index = 0; index < inputs.length; index++) { function hiddenErrorAndMessages(e) { for (let index = 0; index < notifiactions.length; index++) { - console.log("Oculto"); notifiactions.item(index).classList.add("hidden"); } } diff --git a/tecnicas/static/js/create-session.js b/tecnicas/static/js/create-session.js index 8867ca652be94fdf45efd05a3ea5f2492fb9323f..4e1ea119bb960aae102f37c55ac53f7b81e777f0 100644 --- a/tecnicas/static/js/create-session.js +++ b/tecnicas/static/js/create-session.js @@ -90,7 +90,7 @@ function renderElementsResponse({ ]; const aIndex = document.createElement("a"); - aIndex.href = "/cata"; + aIndex.href = "/cata/presenter/"; aIndex.textContent = "Volver al inicio"; aIndex.classList.add( @@ -99,7 +99,7 @@ function renderElementsResponse({ ); const aDetails = document.createElement("a"); - aDetails.href = `/cata/detalles-sesion/${sessionId}`; + aDetails.href = `/cata/presenter/detalles-sesion/${sessionId}`; aDetails.textContent = "Ver detalles la sesion"; aDetails.classList.add( diff --git a/tecnicas/static/js/details-session.js b/tecnicas/static/js/details-session.js index 512af78cb783cb635cf227e2ac5519e6151686ac..fe403c00daa302d9ab518c778aa56d03d87d98bb 100644 --- a/tecnicas/static/js/details-session.js +++ b/tecnicas/static/js/details-session.js @@ -4,23 +4,17 @@ const notificationError = document.querySelector(".ct-notification-error"); if (notificationError) { setTimeout(function () { notificationError.classList.add("hidden"); - }, 2000); + }, 3000); } function startRepetition() { - const inputAction = document.createElement("input"); - inputAction.type = "hidden"; - inputAction.name = "action"; - inputAction.value = "start_session"; - - const inputUser = document.createElement("input"); - inputUser.type = "hidden"; - inputUser.name = "username"; - inputUser.value = "aguBido"; - - actionForm.appendChild(inputAction); - actionForm.appendChild(inputUser); - - actionForm.classList.remove("hidden"); + const input = actionForm.querySelector(".action-option") + input.value = "start_session"; actionForm.submit(); } + +function deleteSession() { + const input = actionForm.querySelector(".action-option") + input.value = "delete_session"; + actionForm.submit(); +} \ No newline at end of file diff --git a/tecnicas/static/js/panel-basic.js b/tecnicas/static/js/panel-basic.js index be21f50255ca95689f760d4858b43dec4e954e47..7520d294328b676bc274ccb3a65d30edadb8744b 100644 --- a/tecnicas/static/js/panel-basic.js +++ b/tecnicas/static/js/panel-basic.js @@ -1,6 +1,6 @@ const descriptons = { - estructurada: ["Establece número de segmentos:", "Puede ser 5, 7 o 9"], - continua: ["Establece la longitud de la escala:", "Puede ser 9, 12 o 15"], + estructurada: "Establece número de segmentos:", + continua: "Establece la longitud de la escala:", atributos: "Con el estilo atributos elijes las palabras para evaluar los productos", vocabulario: @@ -12,6 +12,11 @@ let inputTamano; let inputsStyle; let helpStyle; +let sizeOptionsContainer; +const SIZE_OPTIONS = { + estructurada: [5, 7, 9], + continua: [9, 13, 15], +}; initPanel(); @@ -22,6 +27,7 @@ function initPanel() { function initRadios() { inputsScale = document.getElementsByName("tipo_escala"); inputTamano = document.getElementsByName("tamano_escala").item(0); + sizeOptionsContainer = document.getElementsByClassName("cts-options-size-scale")[0]; for (let index = 0; index < inputsScale.length; index++) { let parent = inputsScale.item(index).parentElement; @@ -46,6 +52,73 @@ function initRadios() { showDescriptionStyle(parent); } } + + initSizeOptions(); +} + +function initSizeOptions() { + for (let i = 0; i < inputsScale.length; i++) { + const radio = inputsScale.item(i); + radio.addEventListener('change', () => { + const tag = getTagFromLabel(radio); + populateSizeOptions(tag); + }); + if (radio.checked) { + const tag = getTagFromLabel(radio); + populateSizeOptions(tag); + } + } + + const form = document.querySelector('form'); + if (!form) return; + + form.addEventListener('submit', (e) => { + const chosen = document.querySelector('input[name="option_size_scale"]:checked'); + if (chosen && inputTamano) { + inputTamano.value = chosen.value; + } + + if (sizeOptionsContainer) { + const toRemove = sizeOptionsContainer.querySelectorAll('input[name="option_size_scale"]'); + toRemove.forEach((el) => el.remove()); + } + }); +} + +function getTagFromLabel(radio) { + try { + const parent = radio.parentElement; + if (!parent) return ''; + const text = parent.textContent || ''; + return text.trim().split(/\s+/)[0].toLowerCase(); + } catch (err) { + return ''; + } +} + +function populateSizeOptions(tag) { + const options = SIZE_OPTIONS[tag] || SIZE_OPTIONS['estructurada']; + if (!sizeOptionsContainer) return; + sizeOptionsContainer.innerHTML = ''; + + options.forEach((val) => { + const label = document.createElement('label'); + label.className = 'flex flex-col items-center cursor-pointer'; + + const input = document.createElement('input'); + input.type = 'radio'; + input.name = 'option_size_scale'; + input.value = String(val); + input.className = 'radio radio-lg checked:bg-pink-500'; + + const span = document.createElement('span'); + span.className = 'mt-2 text-xl text-gray-700 font-medium'; + span.textContent = String(val); + + label.appendChild(input); + label.appendChild(span); + sizeOptionsContainer.appendChild(label); + }); } function showDescriptionStyle(label) { @@ -56,6 +129,5 @@ function showDescriptionStyle(label) { function showDescriptionTamanoScale(label) { const text = label.textContent.trim(); let parent = inputTamano.parentElement; - parent.getElementsByTagName("p")[0].textContent = descriptons[text][0]; - inputTamano.placeholder = descriptons[text][1]; + parent.getElementsByTagName("p")[0].textContent = descriptons[text]; } diff --git a/tecnicas/static/js/panel-words.js b/tecnicas/static/js/panel-words.js index d86d9aa0c15dd1df49e6c0a66ebbf9d2f70eac6b..2a68fefff6f0e6076562a5282eff694ab4810239 100644 --- a/tecnicas/static/js/panel-words.js +++ b/tecnicas/static/js/panel-words.js @@ -28,7 +28,7 @@ async function getWordsByName(e) { palabra: dataForm.get("search").trim(), }); - const url = `api/palabras?${params}`; + const url = `/cata/api/palabras?${params}`; try { const respone = await fetch(url, { diff --git a/tecnicas/static/js/showHiddenElement.js b/tecnicas/static/js/showHiddenElement.js new file mode 100644 index 0000000000000000000000000000000000000000..8c01000129d3408ed29b60b0f1b75b912baefdf0 --- /dev/null +++ b/tecnicas/static/js/showHiddenElement.js @@ -0,0 +1,9 @@ +function hiddenWarningDialog(styleClass) { + const element = document.querySelector(`.${styleClass}`) + element.classList.add("hidden") +} + +function showWarningDialog(styleClass) { + const element = document.querySelector(`.${styleClass}`) + element.classList.remove("hidden") +} \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/auth.html b/tecnicas/templates/tecnicas/auth.html index 093858a788307c7fb462b05d8b49938a4064c775..9e32d69bdac1fd8987a9a4b18efab0778743e3f2 100644 --- a/tecnicas/templates/tecnicas/auth.html +++ b/tecnicas/templates/tecnicas/auth.html @@ -26,7 +26,7 @@ diff --git a/tecnicas/templates/tecnicas/cata-login.html b/tecnicas/templates/tecnicas/cata-login.html index c128c8f176795fbf32791d0f810d2e3e9f0256dc..890571ac642ddaa89cb360d03c93209a52f89801 100644 --- a/tecnicas/templates/tecnicas/cata-login.html +++ b/tecnicas/templates/tecnicas/cata-login.html @@ -13,23 +13,15 @@ {% if error %} -
-

- {{ error }} -

-
+ {% include "./components/error-message.html" with message=error %} + {% endif %} + {% if message %} + {% include "./components/error-message.html" with message=message %} {% endif %}
- - - @@ -44,15 +36,4 @@
-{% endblock %} - -{% block extra_js %} - {% endblock %} \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/item-tester.html b/tecnicas/templates/tecnicas/components/item-tester.html new file mode 100644 index 0000000000000000000000000000000000000000..b0501f2e82834839cb16c81877a576e36735c337 --- /dev/null +++ b/tecnicas/templates/tecnicas/components/item-tester.html @@ -0,0 +1,39 @@ +
+
+

+ {{ tester.user.first_name }} {{ tester.user.last_name }} +

+

+ Usuario: + {{ tester.user.username }} +

+ +
+

+ Genero: + {{ tester.genero }} +

+

+ Teléfono: + {{ tester.telefono }} +

+

+ Email: + {{ tester.user.email }} +

+

+ Nacimiento: + {{ tester.nacimiento|date:"d/m/Y" }} +

+
+ + +
+
\ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/item_session.html b/tecnicas/templates/tecnicas/components/item_session.html new file mode 100644 index 0000000000000000000000000000000000000000..8e899a8810944bcc1f2204b96bfdd0be5add675d --- /dev/null +++ b/tecnicas/templates/tecnicas/components/item_session.html @@ -0,0 +1,47 @@ +
+
+
+ {% if sesion.nombre_sesion %} +

{{ sesion.nombre_sesion }}

+ Código:
{{ sesion.codigo_sesion }}
+ {% else %} + Código:
{{ sesion.codigo_sesion }}
+ {% endif %} +
+ + +
+
\ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/item_session_tester.html b/tecnicas/templates/tecnicas/components/item_session_tester.html new file mode 100644 index 0000000000000000000000000000000000000000..8224f00a46376b14ebb23b0149520c76aab0d1e6 --- /dev/null +++ b/tecnicas/templates/tecnicas/components/item_session_tester.html @@ -0,0 +1,40 @@ +
+
+

+ {% if session.nombre_sesion %} + {{ session.nombre_sesion }} + {% else %} + Sin nombre + {% endif %} +

+ +
+

Código: {{ session.codigo_sesion }}

+

Técnica: {{ session.tecnica.tipo_tecnica }}

+

Fecha: {{ session.fechaCreacion }}

+

+ Finazliado: + {% if session.participacion_finalizado %} + Se ha concluido + {% else %} + Sin concluir + {% endif %} +

+

+ Estatus: + {% if session.activo %} + En proceso + {% else %} + No activo + {% endif %} +

+
+ +
+
\ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/table-convencional.html b/tecnicas/templates/tecnicas/components/table-convencional.html index 38601feeaf50e8d1b07fae5c8946652c051dbce4..5462ea533f87edb5758525b155c0c0af31e29d56 100644 --- a/tecnicas/templates/tecnicas/components/table-convencional.html +++ b/tecnicas/templates/tecnicas/components/table-convencional.html @@ -23,7 +23,6 @@ {% with match=None %} {% for valor in valores %} {% if valor.nombre_palabra == palabra %} - {{ palabra }}
{{ valor.dato_valor }} {% with match=True %}{% endwith %} {% endif %} diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html b/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html index 6b5ceedf1adb2587154096f260273c058b516d1c..7a0d941c37a532ac166f4363aa7946fff7dcc60c 100644 --- a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html +++ b/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html @@ -13,8 +13,8 @@
{% csrf_token %} -