diff --git a/tecnicas/admin.py b/tecnicas/admin.py index 44cd4bff89696421eb2f4bcfc134e5b20e34e65c..b5a2134d0ca3a93f805656ba89565671188908ea 100644 --- a/tecnicas/admin.py +++ b/tecnicas/admin.py @@ -1,20 +1,6 @@ from django.contrib import admin -from .models import CategoriaTecnica, TipoTecnica, TipoEscala, EstiloPalabra - -from .models import Catador, Presentador - -from .models import Tecnica, SesionSensorial - -from .models import EsAtributo, Palabra, Vocabulario - -from .models import Etiqueta, Escala, EtiquetasEscala - -from .models import Producto, Participacion - -from .models import Orden, Posicion - -from .models import Dato, ValorDecimal, ValorBooleano, Calificacion +from .models import CategoriaTecnica, TipoTecnica, TipoEscala, EstiloPalabra, Catador, Presentador, Tecnica, SesionSensorial, EsAtributo, Palabra, Vocabulario, Etiqueta, Escala, EtiquetasEscala, Producto, Participacion, Orden, Posicion, Dato, ValorDecimal, ValorBooleano, Calificacion, ListaPalabras, GrupoProducto, Modalidad, TecnicaModalidad, DatoPunto # Register your models here. admin.site.register(CategoriaTecnica) @@ -46,3 +32,11 @@ admin.site.register(Dato) admin.site.register(ValorDecimal) admin.site.register(ValorBooleano) admin.site.register(Calificacion) + +admin.site.register(ListaPalabras) +admin.site.register(GrupoProducto) + +admin.site.register(Modalidad) +admin.site.register(TecnicaModalidad) + +admin.site.register(DatoPunto) diff --git a/tecnicas/controllers/__init__.py b/tecnicas/controllers/__init__.py index 8e61f1aca6b63b7d8b535b90382d9bbd48311d34..b6c2ff8c68c4ee7449db34f6d1806ccafd786801 100644 --- a/tecnicas/controllers/__init__.py +++ b/tecnicas/controllers/__init__.py @@ -16,22 +16,53 @@ from .views_controller.create_session.panel_basic_controller import PanelBasicCo 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 -from .views_controller.session_management.monitor_rata_controller import MonitorRATAController +from .views_controller.create_session.panels_create.panel_create_escalas_controller import PanelCreateEscalasController +from .views_controller.create_session.panels_create.panel_create_rata_controller import PanelCreateRataController +from .views_controller.create_session.panels_create.panel_create_cata_controller import PanelCreateCataController +from .views_controller.create_session.panels_create.panel_create_pf_controller import PanelCreatePFController +from .views_controller.create_session.panels_create.panel_create_sort_controller import PanelCreateSortController +from .views_controller.create_session.panels_create.panel_create_napping_controller import PanelCreateNappingController + + +from .views_controller.session_management.details.details_controller import DetallesController +from .views_controller.session_management.details.details_escala_controller import DetallesEscalasController +from .views_controller.session_management.details.details_rata_controller import DetallesRATAController +from .views_controller.session_management.details.details_cata_controller import DetallesCATAController +from .views_controller.session_management.details.details_pf_controller import DetallesPFController +from .views_controller.session_management.details.details_sort_controller import DetallesSortController +from .views_controller.session_management.details.details_napping_controller import DetallesNappingController + +from .views_controller.session_management.monitor.monitor_escalas_controller import MonitorEscalasController +from .views_controller.session_management.monitor.monitor_rata_controller import MonitorRATAController +from .views_controller.session_management.monitor.monitor_pf_controller import MonitorPFController +from .views_controller.session_management.monitor.monitor_sort_controller import MonitorSortController +from .views_controller.session_management.monitor.monitor_napping_controller import MonitorNappingController from .views_controller.sessions_tester.login_session_tester_controller import LoginSessionTesterController -from .views_controller.sessions_tester.init_session_tester_controller import InitSessionTesterController -from .views_controller.sessions_tester.convencional_scales_controller import ConvencionalScalesController from .views_controller.sessions_tester.list_sessions_tester_controller import ListSessionsTesterController +from .views_controller.sessions_tester.tests_forms.test_scales_controller import TestScalesController +from .views_controller.sessions_tester.tests_forms.test_rata_controller import TestRataController +from .views_controller.sessions_tester.tests_forms.test_cata_controller import TestCataController +from .views_controller.sessions_tester.tests_forms.test_pf_controller import TestPFController +from .views_controller.sessions_tester.tests_forms.test_sort_controller import TestSortController +from .views_controller.sessions_tester.tests_forms.test_napping_controller import TestNappingController + +from .views_controller.sessions_tester.init_session.init_session_escalas_controller import InitSessionEscalasController +from .views_controller.sessions_tester.init_session.init_session_rata_controller import InitSessionRATAController +from .views_controller.sessions_tester.init_session.init_session_pf_controller import InitSessionPFController +from .views_controller.sessions_tester.init_session.init_session_sort_controller import InitSessionSortController +from .views_controller.sessions_tester.init_session.init_session_napping_controller import InitSessionNappingController + from .views_controller.vocabulary_manage.create_vocabulary_controller import CreateVocabularyController +from .views_controller.vocabulary_manage.view_vocabulary_controller import ViewVocabularyController from .views_controller.vocabulary_manage.list_vocabulary_controller import ListVocabularyController -from .views_controller.api_rating_controller import ApiRatingController +from .api_controller.rating_sacales_controller import RatingScalesController +from .api_controller.rating_cata_controller import RatingCataController +from .api_controller.rating_pf_list_controller import RatingPFListController from .views_controller.tester_list_controller import TesterListController +from .api_controller.rating_sort_controller import RatingSortController +from .api_controller.rating_napping_controller import RatingNappingController + diff --git a/tecnicas/controllers/api_controller/rating_cata_controller.py b/tecnicas/controllers/api_controller/rating_cata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..f263c0a7f4114d7c4b7cf82effe80214a5f5cb67 --- /dev/null +++ b/tecnicas/controllers/api_controller/rating_cata_controller.py @@ -0,0 +1,78 @@ +from django.http import JsonResponse, HttpRequest +from django.db import transaction +from tecnicas.models import Calificacion, Dato, ValorBooleano, Participacion, Palabra + + +class RatingCataController(): + def __init__(self): + pass + + @staticmethod + def saveRatingWords(request: HttpRequest, data_words: list[dict], data_prodct: dict): + try: + with transaction.atomic(): + participation = Participacion.objects.get( + id=request.session["id_participation"]) + if not participation: + raise ValueError("No está autorizado en la sesión") + + ids_words = [] + words_values = {} + + # Acoplar datos para usar + for da_wo in data_words: + ids_words.append(da_wo["id"]) + words_values[da_wo["word"]] = da_wo["is_check"] + + words_for_rating = Palabra.objects.filter(id__in=ids_words) + if not words_for_rating: + raise ValueError("No se han encontrado sus palabras") + + technique = participation.tecnica + + # Creando la calificacion + (rating, creataed) = Calificacion.objects.get_or_create( + num_repeticion=technique.repeticion, + id_producto_id=data_prodct["id"], + id_tecnica=technique, + id_catador=request.user.user_catador + ) + if not rating: + raise ValueError("Problemas al crear la calificación") + + # Guardando datos + data_for_save = [] + for word in words_for_rating: + data_for_save.append(Dato( + id_palabra=word, + id_calificacion=rating + )) + + Dato.objects.bulk_create(data_for_save) + data_saved = Dato.objects.filter( + id_calificacion=rating).only("id_palabra") + if not data_saved: + raise ValueError("Problemas al crear los datos") + + # Guardando valores de datos + values_for_save = [] + for data in data_saved: + word_for_rating = data.id_palabra.nombre_palabra + values_for_save.append( + ValorBooleano( + id_dato=data, + valor=words_values[word_for_rating] + ) + ) + + ValorBooleano.objects.bulk_create(values_for_save) + values_saves = ValorBooleano.objects.filter( + id_dato__id_calificacion=rating).count() + if not values_saves: + raise ValueError("Error al guardar los datos") + + return JsonResponse({"message": "Valores guardados"}) + + except ValueError as e: + print(f"Error de calificacion: {e}") + return JsonResponse({"error": e}, statusstatus=500) diff --git a/tecnicas/controllers/api_controller/rating_napping_controller.py b/tecnicas/controllers/api_controller/rating_napping_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..f6bcaf47aa2f766116c74d328dbd09ac06fa01aa --- /dev/null +++ b/tecnicas/controllers/api_controller/rating_napping_controller.py @@ -0,0 +1,367 @@ +from django.http import JsonResponse +from django.http import HttpRequest +from django.db import transaction +from tecnicas.models import Calificacion, DatoPunto, Producto, Participacion, Palabra, GrupoProducto, TecnicaModalidad +from tecnicas.forms import ListWordsForm + + +class RatingNappingController: + @staticmethod + def saveRatingCoordinates(request: HttpRequest, data: list | dict): + participation = Participacion.objects.get( + id=request.session["id_participation"] + ) + + name_mod = TecnicaModalidad.objects.get( + tecnica=participation.tecnica + ).modalidad.nombre.lower() + + # Branch based on modality + if name_mod == 'sorting': + return RatingNappingController.processSortingMode( + data, participation + ) + + if name_mod in ['sin modalidad', 'perfil ultra flash']: + return RatingNappingController.processNappOrPUF( + data, participation + ) + + else: + return JsonResponse({"error": "Modalidad no soportada"}) + + @staticmethod + def processSortingMode(data: dict, participation): + try: + with transaction.atomic(): + # Extract products array (always present) + products = data.get("products", []) + if not products: + return JsonResponse({"error": "No se proporcionaron productos"}) + + existing_ratings_map, products_map = RatingNappingController.savePoints( + isSorting=True, products=products, participation=participation + ) + + # Process groups if they exist + groups = data.get("groups", {}) + if groups: + RatingNappingController.processGroupsForSorting( + products, groups, participation, existing_ratings_map, products_map + ) + else: + RatingNappingController.deleteAllGroups(participation) + + return JsonResponse({"message": "Datos guardados exitosamente"}) + + except Exception as e: + print("ERROR:", e) + import traceback + traceback.print_exc() + return JsonResponse({"error": f"Error al procesar datos: {str(e)}"}) + + @staticmethod + def processNappOrPUF(data: list, participation): + try: + with transaction.atomic(): + existing_ratings_map, products_map = RatingNappingController.savePoints( + isSorting=False, products=data, participation=participation + ) + + RatingNappingController.processWordsForRatings( + data, existing_ratings_map + ) + + return JsonResponse({"message": "Datos guardados exitosamente"}) + + except Exception as e: + print("ERROR:", e) + return JsonResponse({"error": "Error al procesar datos"}) + + @staticmethod + def processGroupsForSorting(products, groups, participation, existing_ratings_map, products_map): + """Process groups for sorting mode + - Creates/updates GrupoProducto instances + - Ensures products don't belong to multiple groups + - Associates words with groups + """ + # Build mapping of product_id to group_id from products array + product_to_group = {} + for product_item in products: + group_code = product_item.get("group", "") + if group_code: # Only if product has a group assigned + product_id = int(product_item["idProduct"]) + if product_id in product_to_group: + raise ValueError( + f"Producto {product_id} pertenece a múltiples grupos") + product_to_group[product_id] = group_code + + # Get existing groups for this catador and technique + existing_groups = GrupoProducto.objects.filter( + tecnica=participation.tecnica, + catador=participation.catador + ) + + # Create a map of existing groups by their product composition + # We'll identify groups by the set of products they contain + existing_groups_map = {} + for group in existing_groups: + product_ids = set(group.productos.values_list('id', flat=True)) + key = frozenset(product_ids) + existing_groups_map[key] = group + + # Build new groups structure + groups_to_create = [] + groups_to_update = [] + group_products_map = {} # group_id -> [product_ids] + + # Organize products by group + for product_id, group_code in product_to_group.items(): + if group_code not in group_products_map: + group_products_map[group_code] = [] + group_products_map[group_code].append(product_id) + + # Process each group + for group_code, product_ids in group_products_map.items(): + product_set = frozenset(product_ids) + + # Check if this group already exists + if product_set in existing_groups_map: + group = existing_groups_map[product_set] + groups_to_update.append((group, group_code)) + else: + # Create new group + group = GrupoProducto( + tecnica=participation.tecnica, + catador=participation.catador + ) + groups_to_create.append((group, product_ids, group_code)) + + # Create new groups + created_groups = [] + for group, product_ids, group_code in groups_to_create: + group.save() # Save first to get ID for M2M + + # Add products to group + productos = [products_map[pid] + for pid in product_ids if pid in products_map] + group.productos.set(productos) + + created_groups.append((group, group_code)) + + # Combine created and existing groups for word processing + all_groups_for_words = created_groups + groups_to_update + + # Delete groups that no longer exist + current_group_sets = set(frozenset(pids) + for pids in group_products_map.values()) + for product_set, group in existing_groups_map.items(): + if product_set not in current_group_sets: + group.delete() + + # Process words for groups + if groups: + RatingNappingController.processWordsForGroups( + groups, all_groups_for_words + ) + + @staticmethod + def deleteAllGroups(participation): + GrupoProducto.objects.filter( + tecnica=participation.tecnica, + catador=participation.catador + ).delete() + + @staticmethod + def processWordsForGroups(groups_data, groups_list): + """Process and associate words to groups + - Creates words that don't exist + - Associates words to GrupoProducto instances + - Handles concurrency + """ + # Collect all unique words from all groups + all_words = set() + for group_id, words in groups_data.items(): + if words: + all_words.update(words) + + if not all_words: + # No words to process, just clear existing words from groups + for grupo, _ in groups_list: + grupo.palabras.clear() + return + + # Get existing words + existing_words = Palabra.objects.filter( + nombre_palabra__in=all_words + ) + existing_words_map = {w.nombre_palabra: w for w in existing_words} + + # Create missing words with concurrency handling + word_objects = {} + for word_name in all_words: + if word_name in existing_words_map: + word_objects[word_name] = existing_words_map[word_name] + else: + word_obj, created = Palabra.objects.get_or_create( + nombre_palabra=word_name + ) + word_objects[word_name] = word_obj + + # Associate words with groups + for grupo, group_id in groups_list: + words = groups_data.get(group_id, []) + if words: + words_to_set = [word_objects[word_name] + for word_name in words if word_name in word_objects] + grupo.palabras.set(words_to_set) + else: + grupo.palabras.clear() + + @staticmethod + def savePoints(isSorting: bool, products, participation): + try: + with transaction.atomic(): + # Get products map for validation + products_map = RatingNappingController.getProductsMap( + participation.tecnica) + + # Get existing ratings map + existing_ratings_map = RatingNappingController.getExistingRatingsMap( + participation.tecnica, participation.catador) + + if not isSorting: + validation_result = RatingNappingController.validateWords( + products) + if validation_result is not None: + return validation_result + + # Create new ratings for products that don't have them + new_ratings = [] + ids_products = products_map.keys() + for item in products: + product_id = int(item["idProduct"]) + if product_id not in existing_ratings_map and product_id in ids_products: + new_ratings.append( + Calificacion( + num_repeticion=0, + id_producto=products_map[product_id], + id_tecnica=participation.tecnica, + id_catador=participation.catador, + ) + ) + + if new_ratings: + Calificacion.objects.bulk_create(new_ratings) + existing_ratings_map = RatingNappingController.getExistingRatingsMap( + participation.tecnica, participation.catador) + + # Process DatoPunto instances from products array + existing_points_map = RatingNappingController.getExistingPointsMap( + existing_ratings_map.values()) + + points_to_create = [] + points_to_update = [] + + for item in products: + product_id = int(item["idProduct"]) + rating = existing_ratings_map.get(product_id) + + if rating: + if rating.id in existing_points_map: + point = existing_points_map[rating.id] + point.x = item["x"] + point.y = item["y"] + points_to_update.append(point) + else: + points_to_create.append( + DatoPunto( + x=item["x"], + y=item["y"], + calificacion=rating, + ) + ) + + if points_to_create: + DatoPunto.objects.bulk_create(points_to_create) + + if points_to_update: + DatoPunto.objects.bulk_update(points_to_update, ['x', 'y']) + + return (existing_ratings_map, products_map) + + except Exception as e: + print(e) + return JsonResponse({"error": "Error al guardar los puntos"}) + + @staticmethod + def validateWords(data: list): + for item in data: + words = item.get("words", []) + if words: + dic_words = {} + for index, word in enumerate(words, start=1): + dic_words[f"palabra_{index}"] = word + + form = ListWordsForm(dic_words, new_words=words) + if not form.is_valid(): + errors = [] + for field, error_list in form.errors.items(): + errors.extend(error_list) + return JsonResponse({"error": f"Error en validación de palabras: {', '.join(errors)}"}) + return None + + @staticmethod + def processWordsForRatings(data: list, existing_ratings_map: dict): + all_words = set() + for item in data: + words = item.get("words", []) + if words: + all_words.update(words) + + if not all_words: + return + + existing_words = Palabra.objects.filter( + nombre_palabra__in=all_words + ) + existing_words_map = {w.nombre_palabra: w for w in existing_words} + + word_objects = {} + for word_name in all_words: + if word_name in existing_words_map: + word_objects[word_name] = existing_words_map[word_name] + else: + word_obj, created = Palabra.objects.get_or_create( + nombre_palabra=word_name + ) + word_objects[word_name] = word_obj + + for item in data: + words = item.get("words", []) + if words: + product_id = int(item["idProduct"]) + rating = existing_ratings_map.get(product_id) + + if rating: + words_to_set = [word_objects[word_name] + for word_name in words] + rating.palabras.set(words_to_set) + + @staticmethod + def getProductsMap(id_tecnica): + products_qs = Producto.objects.filter(id_tecnica=id_tecnica) + return {p.id: p for p in products_qs} + + @staticmethod + def getExistingRatingsMap(id_tecnica, id_catador): + ratings = Calificacion.objects.filter( + id_tecnica=id_tecnica, + id_catador=id_catador, + ) + return {r.id_producto.id: r for r in ratings} + + @staticmethod + def getExistingPointsMap(ratings): + points = DatoPunto.objects.filter(calificacion__in=ratings) + return {p.calificacion.id: p for p in points} diff --git a/tecnicas/controllers/api_controller/rating_pf_list_controller.py b/tecnicas/controllers/api_controller/rating_pf_list_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..80e6686a0fe32850fe8fb33f6b35008722b384de --- /dev/null +++ b/tecnicas/controllers/api_controller/rating_pf_list_controller.py @@ -0,0 +1,221 @@ +from django.http import JsonResponse, HttpRequest +from django.db import transaction, IntegrityError +from tecnicas.models import Palabra, ListaPalabras, Participacion, Calificacion, Producto, Dato, ValorDecimal +from tecnicas.forms import ListWordsForm + + +class RatingPFListController(): + def __init__(self): + pass + + @staticmethod + def getListWords(request: HttpRequest): + participation_current_tester = Participacion.objects.get( + id=request.session["id_participation"]) + + technique = participation_current_tester.tecnica + + participations_testers = Participacion.objects.exclude( + catador=participation_current_tester.catador).filter(tecnica=technique) + + all_testers = [ + participation.catador for participation in participations_testers] + + list_words_testers = list(ListaPalabras.objects.filter( + tecnica=technique, catador__in=all_testers, es_final=True)) + + if not list_words_testers: + return JsonResponse({"error": "Aun no hay listas finales de Catadores"}) + + result = [] + for list_tester in list_words_testers: + try: + username = list_tester.catador.user.username + except Exception: + username = None + + finish = [ + participation.finalizado for participation in participations_testers if participation.catador.user.username == username][0] + + status = "Lista terminada" if finish else "Lista en proceso" + + words_qs = list_tester.palabras.all() + words = [] + for p in words_qs: + nombre = getattr(p, 'nombre_palabra', None) + words.append({ + 'id': getattr(p, 'id', None), + 'nombre_palabra': nombre + }) + + result.append({ + 'username': username, + 'words': words, + 'status': status + }) + + return JsonResponse({ + "message": "Listas encontradas", + "lists_words": result + }) + + @staticmethod + def saveList(request: HttpRequest, words: list, current_phase: int): + dic_words = {} + for index, word in enumerate(words, start=1): + dic_words[f"palabra_{index}"] = word + + form = ListWordsForm(dic_words, new_words=words) + + if form.is_valid(): + participation = Participacion.objects.get( + id=request.session["id_participation"]) + + if not participation: + return JsonResponse({"error": "No está autorizado en la sesión"}) + + technique = participation.tecnica + + list_words_tester: ListaPalabras + if current_phase == 1: + (list_words_tester, created) = ListaPalabras.objects.get_or_create( + tecnica=technique, + catador=request.user.user_catador, + es_final=False, + ) + elif current_phase == 2: + (list_words_tester, created) = ListaPalabras.objects.get_or_create( + tecnica=technique, + catador=request.user.user_catador, + es_final=True, + ) + + added_words = RatingPFListController.addWordsToListWordsTester( + list_words=words, list_tester=list_words_tester) + + response = JsonResponse({ + "message": "Palabras guardadas con exito", + "words": [word.nombre_palabra for word in added_words] + }) + else: + response = JsonResponse({"error": "Palabras invalidas"}) + + return response + + @staticmethod + def addWordsToListWordsTester(list_words: list[str], list_tester: ListaPalabras): + # Normalizar + clean_words = [s.strip() for s in list_words if s.strip()] + + # Obtener existentes + all_words = Palabra.objects.filter(nombre_palabra__in=clean_words) + + names_words_exist = set( + all_words.values_list('nombre_palabra', flat=True)) + + # Ejecutar el query para no sumar palabras repetidas + all_words = list(all_words) + + # Determinar faltantes + missing_words = [ + nombre for nombre in clean_words if nombre not in names_words_exist] + print("No save words", missing_words) + + created_words = [] + + # Intentar crear missing_words + for nombre in missing_words: + try: + with transaction.atomic(): + palabra, created = Palabra.objects.get_or_create( + nombre_palabra=nombre) + if created: + created_words.append(palabra) + except IntegrityError: + palabra = Palabra.objects.get(nombre_palabra=nombre) + created_words.append(palabra) + + # Combinar todas (all_words + created_words) + all_new_words = all_words + created_words + + list_tester.palabras.set(all_new_words) + + return all_new_words + + @staticmethod + def saveRatings(request: HttpRequest, word_rating: str, data: list): + participation = Participacion.objects.get( + id=request.session["id_participation"]) + + technique = participation.tecnica + products = Producto.objects.filter(id_tecnica=technique) + + # Crear o obtener las instancias Calificacion en la repeticion de todos los producutos + ratings = Calificacion.objects.filter( + num_repeticion=technique.repeticion, + id_tecnica=technique, + id_catador=participation.catador, + id_producto__in=products + ) + + existing_dict = {rating.id_producto_id: rating for rating in ratings} + to_create = [] + + for product in products: + if product.id not in existing_dict: + to_create.append( + Calificacion( + num_repeticion=technique.repeticion, + id_producto=product, + id_tecnica=technique, + id_catador=participation.catador + ) + ) + Calificacion.objects.bulk_create(to_create) + + ratings = Calificacion.objects.filter( + num_repeticion=technique.repeticion, + id_tecnica=technique, + id_catador=participation.catador, + id_producto__in=products + ) + + # Guardar datos con ValorDecimal + word = Palabra.objects.get(nombre_palabra=word_rating) + try: + if ( + len(data) != len(products) or + len(data) != len(ratings) or + len(products) != len(ratings) + ): + raise ValueError( + "Al parecer los datos mandados no corresponden con el total de productos") + + product_values = {info["product"]["code"]: info["value"] for info in data} + + datos_to_create = [] + values_to_create = [] + + with transaction.atomic(): + for rating in ratings: + datos_to_create.append(Dato( + id_palabra=word, + id_calificacion=rating + )) + + datos_save = Dato.objects.bulk_create(datos_to_create) + save_data = Dato.objects.filter(id_palabra=word, id_calificacion__in=ratings) + + for data_save in save_data: + values_to_create.append(ValorDecimal( + id_dato=data_save, + valor=product_values.get(data_save.id_calificacion.id_producto.codigoProducto) + )) + + ValorDecimal.objects.bulk_create(values_to_create) + + return JsonResponse({"message": "Calificaciones guardadas con exito"}) + except ValueError as e: + error_message = str(e) + print(f"Error de calificacion: {error_message}") + return JsonResponse({"error": error_message}) diff --git a/tecnicas/controllers/views_controller/api_rating_controller.py b/tecnicas/controllers/api_controller/rating_sacales_controller.py similarity index 89% rename from tecnicas/controllers/views_controller/api_rating_controller.py rename to tecnicas/controllers/api_controller/rating_sacales_controller.py index e65014b2c618238105db13e494fb5c7dcbbc52d2..e2e4948c9e3ef6cdfdc859aef4a9c44658c89908 100644 --- a/tecnicas/controllers/views_controller/api_rating_controller.py +++ b/tecnicas/controllers/api_controller/rating_sacales_controller.py @@ -1,8 +1,8 @@ -from ...controllers import CalificacionController, DatoController +from .. import CalificacionController, DatoController from ...utils import controller_error -class ApiRatingController(): +class RatingScalesController(): def __init__(self, rating_controller: CalificacionController, data_controller: DatoController): self.rating_controller = rating_controller self.data_controller = data_controller diff --git a/tecnicas/controllers/api_controller/rating_sort_controller.py b/tecnicas/controllers/api_controller/rating_sort_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..c7f105bbabb19f9b7ab091596c55a144179301c9 --- /dev/null +++ b/tecnicas/controllers/api_controller/rating_sort_controller.py @@ -0,0 +1,67 @@ +from django.http import JsonResponse, HttpRequest +from django.db import transaction +from tecnicas.models import Participacion, Palabra, GrupoProducto, Producto + + +class RatingSortController(): + def __init__(self): + pass + + @staticmethod + def saveRating(request: HttpRequest, data: list[dict]): + try: + with transaction.atomic(): + participation = Participacion.objects.get( + id=request.session["id_participation"]) + + technique = participation.tecnica + catador = participation.catador + + # Obtener productos de la técnica + technique_products = Producto.objects.filter( + id_tecnica=technique) + technique_product_ids = set(p.id for p in technique_products) + + # Recolectar IDs de productos enviados + sent_product_ids = set() + for group in data: + for product in group["products"]: + sent_product_ids.add(int(product["id"])) + + # Validar que los productos enviados existan en la técnica + if not sent_product_ids.issubset(technique_product_ids): + return JsonResponse({"error": "Productos enviados no pertenecen a la técnica"}) + + # Validar que todos los productos de la técnica estén presentes + if sent_product_ids != technique_product_ids: + return JsonResponse({"error": "Faltan productos por clasificar"}) + + for group in data: + words_data = group["words"] + products_data = group["products"] + + # Crear u obtener palabras + words_objs = [] + for word_name in words_data: + word, created = Palabra.objects.get_or_create(nombre_palabra=word_name) + words_objs.append(word) + + # Crear GrupoProducto + group_product = GrupoProducto.objects.create( + tecnica=technique, + catador=catador + ) + + # Asignar palabras + group_product.palabras.set(words_objs) + + # Asignar productos + product_ids = [p["id"] for p in products_data] + group_product.productos.set(product_ids) + + return JsonResponse({"message": "Valores guardados"}) + except Participacion.DoesNotExist: + return JsonResponse({"error": "Participación no encontrada"}) + except Exception as e: + print(f"Error de calificacion: {e}") + return JsonResponse({"error": "Error al guardar los datos"}) diff --git a/tecnicas/controllers/models_controller/calificacion_controller.py b/tecnicas/controllers/models_controller/calificacion_controller.py index 5e21f3ccc03122a99ebbe41c42a375a2343246e0..66e51b1521732fb537cea3abaad56c4e0597d053 100644 --- a/tecnicas/controllers/models_controller/calificacion_controller.py +++ b/tecnicas/controllers/models_controller/calificacion_controller.py @@ -21,7 +21,7 @@ class CalificacionController(): @staticmethod def getRatingsByTechnique(technique: Tecnica): repetition = technique.repeticion - ratings = list(Calificacion.objects.filter(id_tecnica=technique)) + ratings = list(Calificacion.objects.filter(id_tecnica=technique, num_repeticion=repetition)) return ratings @staticmethod diff --git a/tecnicas/controllers/models_controller/dato_controller.py b/tecnicas/controllers/models_controller/dato_controller.py index 9afcc498d6458644344f187b4bb68c9d6c74a608..524c324127308d9aa75b81e1559327ae6c426930 100644 --- a/tecnicas/controllers/models_controller/dato_controller.py +++ b/tecnicas/controllers/models_controller/dato_controller.py @@ -1,5 +1,5 @@ -from ...models import Calificacion, Dato, Palabra, ValorDecimal, ValorBooleano, Tecnica -from ...utils import controller_error, getId +from tecnicas.models import Calificacion, Dato, Palabra, ValorDecimal, ValorBooleano, Tecnica, Catador +from tecnicas.utils import controller_error, getId from django.core.exceptions import ValidationError from django.db.models import F @@ -12,11 +12,7 @@ class DatoController(): } self.data = Dato(**atributes) - - if isinstance(value_rating, bool): - self.value_data = ValorBooleano(valor=value_rating) - else: - self.value_data = ValorDecimal(valor=value_rating) + self.value_rating = value_rating def setRating(self, new_rating: Calificacion): try: @@ -39,15 +35,23 @@ class DatoController(): except ValidationError as e: return controller_error(e.message) - def setValue(self, new_value=None): - if new_value: - if isinstance(new_value, bool): - self.value_data = ValorBooleano(valor=new_value) - else: - self.value_data = ValorDecimal(valor=new_value) + def setValue(self): + type_technique = self.data.id_calificacion.id_tecnica.tipo_tecnica + if type_technique == "cata": + self.value_data = ValorBooleano(valor=self.value_rating) + else: - self.value_data.id_dato = self.data + type_scale = self.data.id_calificacion.id_tecnica.escala_tecnica.id_tipo_escala.nombre_escala + + if type_scale == "continua": + decimal_value = self.value_rating/100 + value_rounded = round(decimal_value) + self.value_data = ValorDecimal(valor=value_rounded) + else: + self.value_data = ValorDecimal(valor=self.value_rating) + + self.value_data.id_dato = self.data return self.value_data def saveValue(self): @@ -94,3 +98,21 @@ class DatoController(): ) return list(result) + + @staticmethod + def getWordValuesPF(technique: Tecnica, ratings: list[Calificacion], tester: Catador): + ids_ratings = [rat.id for rat in ratings] + + result = ( + ValorDecimal.objects + .filter(id_dato__id_calificacion_id__in=ids_ratings, id_dato__id_calificacion__id_catador=tester) + .values( + nombre_palabra=F("id_dato__id_palabra__nombre_palabra"), + repeticion=F("id_dato__id_calificacion__num_repeticion"), + producto_code=F( + "id_dato__id_calificacion__id_producto__codigoProducto"), + dato_valor=F("valor") + ) + ) + + return list(result) diff --git a/tecnicas/controllers/models_controller/escala_controller.py b/tecnicas/controllers/models_controller/escala_controller.py index 89a7b50c388b7f6d36f1d8ec694284ed957210dc..7b0ee1f453086ef8c4b5ff4c820bbdd30a7f89d8 100644 --- a/tecnicas/controllers/models_controller/escala_controller.py +++ b/tecnicas/controllers/models_controller/escala_controller.py @@ -7,19 +7,25 @@ class EscalaController(): scale: Escala tags_relation: dict[str, EtiquetasEscala] - def __init__(self, data): - self.scale = Escala( - id_tipo_escala=TipoEscala.objects.get(id=data["id_scale"]), - longitud=data["size"], - tecnica=data["technique"] - ) - - def setScale(self, newData): - self.scale = Escala( - id_tipo_escala=TipoEscala.objects.get(id=newData["id_scale"]), - longitud=newData["size"], - tecnica=newData["technique"] - ) + def __init__(self, data, use_scale: Escala = None): + if use_scale: + self.scale = use_scale + else: + self.scale = Escala( + id_tipo_escala=TipoEscala.objects.get(id=data["id_scale"]), + longitud=data["size"], + tecnica=data["technique"] + ) + + def setScale(self, newData, use_scale: Escala): + if use_scale: + self.scale = use_scale + else: + self.scale = Escala( + id_tipo_escala=TipoEscala.objects.get(id=data["id_scale"]), + longitud=data["size"], + tecnica=data["technique"] + ) def saveScale(self): try: diff --git a/tecnicas/controllers/models_controller/particiapacion_controller.py b/tecnicas/controllers/models_controller/particiapacion_controller.py index a902b0dbe9c9da75248eae1280820f32a1b72b22..0acfb707d84b89d5c9edf1152c3247690c32355b 100644 --- a/tecnicas/controllers/models_controller/particiapacion_controller.py +++ b/tecnicas/controllers/models_controller/particiapacion_controller.py @@ -35,24 +35,14 @@ class ParticipacionController(): return controller_error("No se ha encontrado la participación") @staticmethod - def outAllInSession(session: SesionSensorial | str): + def outAllInSession(session: SesionSensorial): try: - if isinstance(session, str): - use_session = SesionSensorial.objects.get( - codigo_sesion=session) - else: - use_session = session - participations = Participacion.objects.filter( - tecnica=use_session.tecnica) - - if not participations.exists(): - message = "No se encontraron participaciones en la sesión" - return (False, message) + tecnica=session.tecnica) participations.update(finalizado=False) - message = "Participaciones actualizadas a finalizadas" + message = "Participaciones actualizadas a finalizadas como falso" return (True, message) except Exception as e: print(f"Error al actualizar las participaciones: {str(e)}") diff --git a/tecnicas/controllers/models_controller/sesion_controller.py b/tecnicas/controllers/models_controller/sesion_controller.py index 0c5f7547577cb157cbd8b266c394d6fd1355919b..be0cea2fdde136aaef928089e3dc46d6c66d3503 100644 --- a/tecnicas/controllers/models_controller/sesion_controller.py +++ b/tecnicas/controllers/models_controller/sesion_controller.py @@ -125,12 +125,7 @@ class SesionController(): return controller_error("Presentador invalido") @staticmethod - def finishRepetion(session: SesionSensorial | str): - if isinstance(session, str): - use_session = SesionSensorial.objects.get(codigo_sesion=session) - else: - use_session = session - - use_session.activo = False - use_session.save() + def finishRepetion(session: SesionSensorial): + session.activo = False + session.save() return session diff --git a/tecnicas/controllers/models_controller/tecnica_controller.py b/tecnicas/controllers/models_controller/tecnica_controller.py index 71265258841e5c3bcbc1843d6438bfa749275262..30403356cf310c8ed2b1bf57790a2d140657eafb 100644 --- a/tecnicas/controllers/models_controller/tecnica_controller.py +++ b/tecnicas/controllers/models_controller/tecnica_controller.py @@ -16,7 +16,7 @@ class TecnicaController(): def setTechniqueFromBasicData(self, basic): self.technique = Tecnica( tipo_tecnica=TipoTecnica.objects.get(nombre_tecnica=basic["name_tecnica"]), - id_estilo=EstiloPalabra.objects.get(id=basic["estilo_palabras"]), + id_estilo=EstiloPalabra.objects.get(nombre_estilo=basic["estilo_palabras"]), repeticiones_max=basic["numero_repeticiones"] or 1, limite_catadores=basic["numero_catadores"], instrucciones=basic["instrucciones"] or "Espere instrucciones del Presentador", diff --git a/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py b/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py index b2fc94263a354656a6ede862fb5d32151f752f1f..ff5aef6023629a56c115dff41cc7b6259696c469 100644 --- a/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py +++ b/tecnicas/controllers/views_controller/create_session/panel_basic_controller.py @@ -1,4 +1,4 @@ -from tecnicas.forms import SesionBasicForm, SesionBasicCATAForm +from tecnicas.forms import SesionBasicForm, SesionBasicCATAForm, SesionBasicPFForm, SesionBasicSortForm, SesionBasicNappingForm from django.http import HttpRequest from django.shortcuts import redirect, render from django.urls import reverse @@ -10,11 +10,14 @@ class PanelBasicController(): "numero_repeticiones": 1 } - url_panel_basic = "tecnicas/create_sesion/configuracion-panel-basic.html" + url_panel_basic = "tecnicas/create_sesion/conf-panel-basic.html" url_panel_basic_cata = "tecnicas/create_sesion/panel-basic-cata.html" + url_panel_basic_pf = "tecnicas/create_sesion/panel-basic-pf.html" + url_panel_basic_sort = "tecnicas/create_sesion/panel-basic-sort.html" + url_panel_basic_napping = "tecnicas/create_sesion/panel-basic-napping.html" - url_next_panel_scales = "cata_system:panel_configuracion_tags" - url_next_panel_cata = "cata_system:panel_configuracion_codes" + url_next_panel_tags = "cata_system:panel_configuracion_tags" + url_next_panel_codes = "cata_system:panel_configuracion_codes" url_select_technique = "cata_system:seleccion_tecnica" @@ -42,7 +45,7 @@ class PanelBasicController(): if form.is_valid(): values = {} for name, value in form.cleaned_data.items(): - if name == "estilo_palabras" or name == "tipo_escala": + if name == "tipo_escala": values[name] = value.id else: values[name] = value @@ -50,7 +53,7 @@ class PanelBasicController(): values["name_tecnica"] = name_tecnica request.session['form_basic'] = values response = redirect( - reverse("cata_system:panel_configuracion_tags")) + reverse(PanelBasicController.url_next_panel_tags)) else: response = render(request, PanelBasicController.url_panel_basic, { "form_sesion": form, "error": "Información no valida"}) @@ -83,7 +86,7 @@ class PanelBasicController(): if form.is_valid(): values = {} for name, value in form.cleaned_data.items(): - if name == "estilo_palabras" or name == "tipo_escala": + if name == "tipo_escala": values[name] = value.id else: values[name] = value @@ -102,7 +105,7 @@ class PanelBasicController(): values["name_tecnica"] = name_tecnica request.session['form_basic'] = values response = redirect( - reverse("cata_system:panel_configuracion_tags")) + reverse(PanelBasicController.url_next_panel_tags)) else: response = render(request, PanelBasicController.url_panel_basic, { "form_sesion": form, "error": "Información no valida"}) @@ -131,17 +134,107 @@ class PanelBasicController(): if form.is_valid(): values = {} for name, value in form.cleaned_data.items(): - if name == "estilo_palabras": - values[name] = value.id - else: - values[name] = value + values[name] = value values["name_tecnica"] = name_tecnica request.session['form_basic'] = values response = redirect( - reverse(PanelBasicController.url_next_panel_cata)) + reverse(PanelBasicController.url_next_panel_codes)) else: response = render(request, PanelBasicController.url_panel_basic, { "form_sesion": form, "error": "Información no valida"}) return response + + @staticmethod + def controllGetPF(request: HttpRequest): + form_sesion = SesionBasicPFForm() + + view_context = { + "form_sesion": form_sesion, + "use_technique": "perfil flash" + } + + return render( + request, PanelBasicController.url_panel_basic_pf, view_context) + + @staticmethod + def controllPostPF(request: HttpRequest, name_tecnica: str): + form = SesionBasicPFForm(request.POST) + + if form.is_valid(): + values = {} + for name, value in form.cleaned_data.items(): + values[name] = value + + values["name_tecnica"] = name_tecnica + request.session['form_basic'] = values + response = redirect( + reverse(PanelBasicController.url_next_panel_codes)) + else: + response = render(request, PanelBasicController.url_panel_basic_pf, { + "form_sesion": form, "error": "Información no valida"}) + + return response + + @staticmethod + def controllGetSort(request: HttpRequest): + form_sesion = SesionBasicSortForm() + + view_context = { + "form_sesion": form_sesion, + "use_technique": "sort" + } + + return render( + request, PanelBasicController.url_panel_basic_sort, view_context) + + @staticmethod + def controllPostSort(request: HttpRequest, name_tecnica: str): + form = SesionBasicSortForm(request.POST) + + if form.is_valid(): + values = {} + for name, value in form.cleaned_data.items(): + values[name] = value + + values["name_tecnica"] = name_tecnica + request.session['form_basic'] = values + response = redirect( + reverse(PanelBasicController.url_next_panel_codes)) + else: + response = render(request, PanelBasicController.url_panel_basic_sort, { + "form_sesion": form, "error": "Información no valida"}) + + return response + + @staticmethod + def controllGetNapping(request: HttpRequest): + form_sesion = SesionBasicNappingForm() + + view_context = { + "form_sesion": form_sesion, + "use_technique": "napping" + } + + return render( + request, PanelBasicController.url_panel_basic_napping, view_context) + + @staticmethod + def controllPostNapping(request: HttpRequest, name_tecnica: str): + form = SesionBasicNappingForm(request.POST) + + if form.is_valid(): + values = {} + for name, value in form.cleaned_data.items(): + values[name] = value + + values["name_tecnica"] = name_tecnica + request.session['form_basic'] = values + response = redirect( + reverse(PanelBasicController.url_next_panel_codes)) + else: + response = render(request, PanelBasicController.url_panel_basic_napping, { + "form_sesion": form, "error": "Información no valida"}) + + 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 index 96ac24f4636d9b26291f8fe004f279b48aae6ab6..f02b9f57d813618b49bbecc87f92a0457b55873b 100644 --- a/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py +++ b/tecnicas/controllers/views_controller/create_session/panel_codes_controller.py @@ -7,8 +7,9 @@ import json class PanelCodesController(): - url_current_panel = "tecnicas/create_sesion/configuracion-panel-codes.html" - url_next_panel = "cata_system:panel_configuracion_words" + url_current_panel = "tecnicas/create_sesion/conf-panel-codes.html" + url_words = "cata_system:panel_configuracion_words" + url_create_session = "cata_system:creando_sesion" def __init__(self): pass @@ -58,14 +59,14 @@ class PanelCodesController(): codes_sort["sort_codes"] = sorts_code request.session["form_codes"] = codes_sort - return redirect(reverse(PanelCodesController.url_next_panel)) + return redirect(reverse(PanelCodesController.url_words)) else: context_codes_form["error"] = "error en los datos recibidos" return render(request, PanelCodesController.url_current_panel, context_codes_form) @staticmethod - def controllGetRATA(request: HttpRequest, data): + def controllGetWithoutOrders(request: HttpRequest, data, name_technique: str): num_products = data["numero_productos"] codes_products = generarCodigos(num_products) form_codes = CodesForm(codes=codes_products) @@ -73,13 +74,13 @@ class PanelCodesController(): context_codes_form = { "form_codes": form_codes, "num_tester": 0, - "use_technique": "rata" + "use_technique": name_technique } return render(request, PanelCodesController.url_current_panel, context_codes_form) @staticmethod - def controllPostRATA(request: HttpRequest, is_rata: True): + def controllPostWithWords(request: HttpRequest, name_technique: str): codes = [] context_codes_form = {} @@ -91,12 +92,14 @@ class PanelCodesController(): context_codes_form = { "form_codes": form_codes, - "use_technique": "rata" if is_rata else "cata" + "use_technique": name_technique } if form_codes.is_valid(): - request.session["form_codes"] = codes - return redirect(reverse(PanelCodesController.url_next_panel)) + # Extract codes from cleaned_data to ensure uppercase conversion + cleaned_codes = [value for name, value in form_codes.cleaned_data.items() if name.startswith('producto_')] + request.session["form_codes"] = cleaned_codes + return redirect(reverse(PanelCodesController.url_words)) else: context_codes_form["error"] = "error en los datos recibidos" @@ -114,3 +117,29 @@ class PanelCodesController(): } return render(request, PanelCodesController.url_current_panel, context_codes_form) + + @staticmethod + def controllPostWithoutOrdersWords(request: HttpRequest, name_technique: str): + 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": name_technique + } + + if form_codes.is_valid(): + # Extract codes from cleaned_data to ensure uppercase conversion + cleaned_codes = [value for name, value in form_codes.cleaned_data.items() if name.startswith('producto_')] + request.session["form_codes"] = cleaned_codes + return redirect(reverse(PanelCodesController.url_create_session)) + else: + context_codes_form["error"] = "error en los datos recibidos" + + return render(request, PanelCodesController.url_current_panel, context_codes_form) diff --git a/tecnicas/controllers/views_controller/create_session/panel_create_controller.py b/tecnicas/controllers/views_controller/create_session/panel_create_controller.py deleted file mode 100644 index dbab342d25a99d33a73c48615208300b7bdfcd51..0000000000000000000000000000000000000000 --- a/tecnicas/controllers/views_controller/create_session/panel_create_controller.py +++ /dev/null @@ -1,446 +0,0 @@ -from django.http import HttpRequest, JsonResponse -from django.db import transaction -from django.shortcuts import render -from tecnicas.utils import general_error -from tecnicas.models import EsAtributo, EsVocabulario, Vocabulario, Tecnica, TipoTecnica, EstiloPalabra, Producto, Palabra, SesionSensorial -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") - try: - 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: - raise ValueError("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): - raise ValueError(scale["error"]) - - dict_tags = request.session["form_tags"] - saved_related_tags = controllerScale.realteTags(dict_tags) - if "error" in saved_related_tags: - raise ValueError(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): - raise ValueError(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): - raise ValueError(saved_orders["error"]) - - seded_positions = controllerOrdes.setPositions() - if isinstance(seded_positions, dict): - raise ValueError(seded_positions["error"]) - - saved_postions = controllerOrdes.savePositions() - if isinstance(saved_postions, dict): - raise ValueError(saved_prodcuts["error"]) - - # /////////////////////////////////////////////////////// # - # - # Third step: Create relations technique with Words Style # - # - # /////////////////////////////////////////////////////// # - style_words = technique.id_estilo.nombre_estilo - if style_words == "atributos": - ids_words = request.session["form_words"] - words_controller = PalabrasController(ids=ids_words) - - words_to_use = words_controller.setWords() - if isinstance(words_to_use, dict): - raise ValueError(words_to_use["error"]) - - style_controller = EstiloPalabrasController( - technique=technique, words=words_to_use) - - instace_style = style_controller.createAndSaveInstaceStyle() - if isinstance(instace_style, dict): - raise ValueError(instace_style["error"]) - - words_using = style_controller.relatedWords() - if isinstance(words_using, dict): - raise ValueError(words_using["error"]) - elif style_words == "vocabulario": - name_vocabulary = request.session["form_words"] - vocabulary = Vocabulario.objects.get( - nombre_vocabulario=name_vocabulary) - - es_vocabulary = EsVocabulario.objects.create( - id_tecnica=technique, - id_vocabulario=vocabulary - ) - else: - raise ValueError("Estilo de palabas no permitido") - - # //////////////////////////////////////////////////////// # - # - # 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): - raise ValueError(setting_session["error"]) - - saved_session = session_controller.saveSession() - if isinstance(saved_session, dict): - raise ValueError(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) - except ValueError as e: - return general_error(f"Error: {e}") - 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") - try: - 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 - - technique = Tecnica.objects.create( - tipo_tecnica=TipoTecnica.objects.get( - nombre_tecnica=data_basic["name_tecnica"]), - id_estilo=EstiloPalabra.objects.get( - id=data_basic["estilo_palabras"]), - repeticiones_max=data_basic["numero_repeticiones"] or 1, - limite_catadores=data_basic["numero_catadores"], - instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador", - ) - - if not technique: - raise ValueError("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): - raise ValueError(scale["error"]) - - dict_tags = request.session["form_tags"] - saved_related_tags = controllerScale.realteTags(dict_tags) - if "error" in saved_related_tags: - raise ValueError(saved_related_tags["error"]) - - # ////////////////////////////////////////////// # - # - # Second step: Create productos with their codes # - # - # ////////////////////////////////////////////// # - codes = request.session["form_codes"] - - if not codes: - raise ValueError("No hay códigos de productos") - - products_without_save = [] - for code in codes: - product = Producto( - codigoProducto=code, - id_tecnica=technique - ) - products_without_save.append(product) - - Producto.objects.bulk_create(products_without_save) - - # /////////////////////////////////////////////////////// # - # - # Third step: Create relations technique with Words Style # - # - # /////////////////////////////////////////////////////// # - style_words = technique.id_estilo.nombre_estilo - - if style_words == "atributos": - raw_ids_words = request.session["form_words"] - ids_words = [int(id_w) for id_w in raw_ids_words] - - words = Palabra.objects.filter(id__in=ids_words) - - style_atribute = EsAtributo.objects.create( - id_tecnica=technique - ) - - if not style_atribute: - raise ValueError( - "Error al intentar relacionar las palabras con la técnica") - - style_atribute.palabras.set(words) - - elif style_words == "vocabulario": - name_vocabulary = request.session["form_words"] - try: - vocabulary = Vocabulario.objects.get( - nombre_vocabulario=name_vocabulary) - except Vocabulario.DoesNotExist: - raise ValueError("Vocabulario no encontrado") - - es_vocabulary = EsVocabulario.objects.create( - id_tecnica=technique, - id_vocabulario=vocabulary - ) - if not es_vocabulary: - raise ValueError( - "Error al intentar relacionar el vocabulario con la técnica") - - else: - raise ValueError("Estilo de palabas no permitido") - - # //////////////////////////////////////////////////////// # - # - # Fourth step: Create session and relat with the technique # - # - # //////////////////////////////////////////////////////// # - session = SesionSensorial.objects.create( - name_session=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None, - technique=technique, - creator=request.user.user_presentador - ) - - if not session: - raise ValueError("Error al crear sesion sensorial") - - context = { - "message": "sesión creada", - "data": { - "codigo_sesion": session.codigo_sesion, - "nombre_sesion": session.nombre_sesion - } - } - - # ////////////////////////////////// # - # - # Final step: Delete date en session # - # - # ////////////////////////////////// # - - deleteDataSession(request) - return JsonResponse(context) - except ValueError as e: - return general_error(f"Error: {e}") - else: - return general_error("No se ha establecido acción") - - @staticmethod - def controllPostCATA(request: HttpRequest): - if request.POST.get('action') == 'create_session': - if 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") - try: - with transaction.atomic(): - # //////////////////////////// # - # - # First step: Create technique # - # - # //////////////////////////// # - data_basic = request.session["form_basic"] - data_basic["numero_catadores"] = 0 - data_basic["numero_repeticiones"] = 1 - - technique = Tecnica.objects.create( - tipo_tecnica=TipoTecnica.objects.get( - nombre_tecnica=data_basic["name_tecnica"]), - id_estilo=EstiloPalabra.objects.get( - id=data_basic["estilo_palabras"]), - repeticiones_max=data_basic["numero_repeticiones"] or 1, - limite_catadores=data_basic["numero_catadores"], - instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador", - ) - - if not technique: - raise ValueError("Error al guardar la técnica") - - # ////////////////////////////////////////////// # - # - # Second step: Create productos with their codes # - # - # ////////////////////////////////////////////// # - codes = request.session["form_codes"] - - if not codes: - raise ValueError("No hay códigos de productos") - - products_without_save = [] - for code in codes: - product = Producto( - codigoProducto=code, - id_tecnica=technique - ) - products_without_save.append(product) - - Producto.objects.bulk_create(products_without_save) - - # /////////////////////////////////////////////////////// # - # - # Third step: Create relations technique with Words Style # - # - # /////////////////////////////////////////////////////// # - style_words = technique.id_estilo.nombre_estilo - - if style_words == "atributos": - raw_ids_words = request.session["form_words"] - ids_words = [int(id_w) for id_w in raw_ids_words] - - words = Palabra.objects.filter(id__in=ids_words) - - style_atribute = EsAtributo.objects.create( - id_tecnica=technique - ) - - if not style_atribute: - raise ValueError( - "Error al intentar relacionar las palabras con la técnica") - - style_atribute.palabras.set(words) - - elif style_words == "vocabulario": - name_vocabulary = request.session["form_words"] - try: - vocabulary = Vocabulario.objects.get( - nombre_vocabulario=name_vocabulary) - except Vocabulario.DoesNotExist: - raise ValueError("Vocabulario no encontrado") - - es_vocabulary = EsVocabulario.objects.create( - id_tecnica=technique, - id_vocabulario=vocabulary - ) - if not es_vocabulary: - raise ValueError( - "Error al intentar relacionar el vocabulario con la técnica") - - else: - raise ValueError("Estilo de palabas no permitido") - - # //////////////////////////////////////////////////////// # - # - # Fourth step: Create session and relat with the technique # - # - # //////////////////////////////////////////////////////// # - session = SesionSensorial.objects.create( - nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None, - tecnica=technique, - creadoPor=request.user.user_presentador - ) - - if not session: - raise ValueError("Error al crear sesion sensorial") - - context = { - "message": "sesión creada", - "data": { - "codigo_sesion": session.codigo_sesion, - "nombre_sesion": session.nombre_sesion - } - } - - # ////////////////////////////////// # - # - # Final step: Delete date en session # - # - # ////////////////////////////////// # - deleteDataSession(request) - return JsonResponse(context) - - except ValueError as e: - return general_error(f"Error: {e}") - 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 index 852bd7d245c22894b486e2fd0f5a65e462a8b201..c41da248ea799117732f622804a8918f762de421 100644 --- a/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py +++ b/tecnicas/controllers/views_controller/create_session/panel_tags_controller.py @@ -25,7 +25,7 @@ class PanelTagsController(): "form_new_tag": form_new_etiqueta } - return render(request, "tecnicas/create_sesion/configuracion-panel-tags.html", context_tags) + return render(request, "tecnicas/create_sesion/conf-panel-tags.html", context_tags) @staticmethod def controllPostEscalas(request: HttpRequest, data): @@ -54,7 +54,7 @@ class PanelTagsController(): else: context_tags["error"] = "ha ocurrido un error" response = render( - request, "tecnicas/create_sesion/configuracion-panel-tags.html", context_tags) + request, "tecnicas/create_sesion/conf-panel-tags.html", context_tags) return response diff --git a/tecnicas/controllers/views_controller/create_session/panel_words_controller.py b/tecnicas/controllers/views_controller/create_session/panel_words_controller.py index 92d454c44096a43608ec5fbe936f3d873e99d5ff..d748614782dcac27a93bedb03f1ee355447adea6 100644 --- a/tecnicas/controllers/views_controller/create_session/panel_words_controller.py +++ b/tecnicas/controllers/views_controller/create_session/panel_words_controller.py @@ -7,7 +7,7 @@ import json class PanelWordsController(): - current_url_escalas_atribute = "tecnicas/create_sesion/configuracion-panel-words.html" + current_url_escalas_atribute = "tecnicas/create_sesion/conf-panel-words.html" current_url_escalas_vocabulary = "tecnicas/create_sesion/conf-panel-vocabulary.html" def __init__(self): diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_cata_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_cata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..79a6329a92f3a51852a670792a14a9fa778ea687 --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_cata_controller.py @@ -0,0 +1,137 @@ +from .panel_create_controller import PanelCreateController +from django.http import HttpRequest, JsonResponse +from django.db import transaction +from tecnicas.models import EsVocabulario, Tecnica, TipoTecnica, EstiloPalabra, EsAtributo, Vocabulario, Palabra, SesionSensorial, Producto +from tecnicas.utils import deleteDataSession, general_error + + +class PanelCreateCataController(PanelCreateController): + def __init__(self): + super().__init__() + + @staticmethod + def controllPost(request: HttpRequest): + if request.POST.get('action') == 'create_session': + if 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") + try: + with transaction.atomic(): + # //////////////////////////// # + # + # First step: Create technique # + # + # //////////////////////////// # + data_basic = request.session["form_basic"] + data_basic["numero_catadores"] = 0 + data_basic["numero_repeticiones"] = 1 + + technique = Tecnica.objects.create( + tipo_tecnica=TipoTecnica.objects.get( + nombre_tecnica=data_basic["name_tecnica"]), + id_estilo=EstiloPalabra.objects.get( + nombre_estilo=data_basic["estilo_palabras"]), + repeticiones_max=data_basic["numero_repeticiones"] or 1, + limite_catadores=data_basic["numero_catadores"], + instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador", + ) + + if not technique: + raise ValueError("Error al guardar la técnica") + + # ////////////////////////////////////////////// # + # + # Second step: Create productos with their codes # + # + # ////////////////////////////////////////////// # + codes = request.session["form_codes"] + + if not codes: + raise ValueError("No hay códigos de productos") + + products_without_save = [] + for code in codes: + product = Producto( + codigoProducto=code, + id_tecnica=technique + ) + products_without_save.append(product) + + Producto.objects.bulk_create(products_without_save) + + # /////////////////////////////////////////////////////// # + # + # Third step: Create relations technique with Words Style # + # + # /////////////////////////////////////////////////////// # + style_words = technique.id_estilo.nombre_estilo + + if style_words == "atributos": + raw_ids_words = request.session["form_words"] + ids_words = [int(id_w) for id_w in raw_ids_words] + + words = Palabra.objects.filter(id__in=ids_words) + + style_atribute = EsAtributo.objects.create( + id_tecnica=technique + ) + + if not style_atribute: + raise ValueError( + "Error al intentar relacionar las palabras con la técnica") + + style_atribute.palabras.set(words) + + elif style_words == "vocabulario": + name_vocabulary = request.session["form_words"] + try: + vocabulary = Vocabulario.objects.get( + nombre_vocabulario=name_vocabulary) + except Vocabulario.DoesNotExist: + raise ValueError("Vocabulario no encontrado") + + es_vocabulary = EsVocabulario.objects.create( + id_tecnica=technique, + id_vocabulario=vocabulary + ) + if not es_vocabulary: + raise ValueError( + "Error al intentar relacionar el vocabulario con la técnica") + + else: + raise ValueError("Estilo de palabas no permitido") + + # //////////////////////////////////////////////////////// # + # + # Fourth step: Create session and relat with the technique # + # + # //////////////////////////////////////////////////////// # + session = SesionSensorial.objects.create( + nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None, + tecnica=technique, + creadoPor=request.user.user_presentador + ) + + if not session: + raise ValueError("Error al crear sesion sensorial") + + context = { + "message": "sesión creada", + "data": { + "codigo_sesion": session.codigo_sesion, + "nombre_sesion": session.nombre_sesion + } + } + + # ////////////////////////////////// # + # + # Final step: Delete date en session # + # + # ////////////////////////////////// # + deleteDataSession(request) + return JsonResponse(context) + + except ValueError as e: + return general_error(f"Error: {e}") + else: + return general_error("No se ha establecido acción") diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..ea4a8559c91137c4b8a6c6e27f8f1f9293461869 --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_controller.py @@ -0,0 +1,17 @@ +from django.http import JsonResponse, HttpRequest +from django.shortcuts import render + +class PanelCreateController(): + url_template = 'tecnicas/create_sesion/creating_session.html' + + def __init__(self): + pass + + @staticmethod + def controllGet(request: HttpRequest): + return render( + request, PanelCreateController.url_template) + + @staticmethod + def controllPost(request: HttpRequest): + return JsonResponse({"message": "Método no permitido"}) diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_escalas_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_escalas_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..727ebde507273373c4746ded1290bde26c3aed81 --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_escalas_controller.py @@ -0,0 +1,169 @@ +from .panel_create_controller import PanelCreateController +from django.http import HttpRequest, JsonResponse +from django.db import transaction +from tecnicas.controllers import TecnicaController, EscalaController, ProductosController, OrdenesController, PalabrasController, EstiloPalabrasController, SesionController +from tecnicas.models import EsVocabulario, Vocabulario +from tecnicas.utils import deleteDataSession, general_error + + +class PanelCreateEscalasController(PanelCreateController): + def __init__(self): + super().__init__() + + @staticmethod + def controllPost(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") + try: + 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: + raise ValueError("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): + raise ValueError(scale["error"]) + + dict_tags = request.session["form_tags"] + saved_related_tags = controllerScale.realteTags(dict_tags) + if "error" in saved_related_tags: + raise ValueError(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): + raise ValueError(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): + raise ValueError(saved_orders["error"]) + + seded_positions = controllerOrdes.setPositions() + if isinstance(seded_positions, dict): + raise ValueError(seded_positions["error"]) + + saved_postions = controllerOrdes.savePositions() + if isinstance(saved_postions, dict): + raise ValueError(saved_prodcuts["error"]) + + # /////////////////////////////////////////////////////// # + # + # Third step: Create relations technique with Words Style # + # + # /////////////////////////////////////////////////////// # + style_words = technique.id_estilo.nombre_estilo + if style_words == "atributos": + ids_words = request.session["form_words"] + words_controller = PalabrasController(ids=ids_words) + + words_to_use = words_controller.setWords() + if isinstance(words_to_use, dict): + raise ValueError(words_to_use["error"]) + + style_controller = EstiloPalabrasController( + technique=technique, words=words_to_use) + + instace_style = style_controller.createAndSaveInstaceStyle() + if isinstance(instace_style, dict): + raise ValueError(instace_style["error"]) + + words_using = style_controller.relatedWords() + if isinstance(words_using, dict): + raise ValueError(words_using["error"]) + elif style_words == "vocabulario": + name_vocabulary = request.session["form_words"] + vocabulary = Vocabulario.objects.get( + nombre_vocabulario=name_vocabulary) + + es_vocabulary = EsVocabulario.objects.create( + id_tecnica=technique, + id_vocabulario=vocabulary + ) + else: + raise ValueError("Estilo de palabas no permitido") + + # //////////////////////////////////////////////////////// # + # + # 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): + raise ValueError(setting_session["error"]) + + saved_session = session_controller.saveSession() + if isinstance(saved_session, dict): + raise ValueError(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) + except ValueError as e: + return general_error(f"Error: {e}") + else: + return general_error("No se ha establecido acción") diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_napping_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_napping_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..6b145590018dd99950c447329cec13f7eb0dbe37 --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_napping_controller.py @@ -0,0 +1,114 @@ +from .panel_create_controller import PanelCreateController +from django.http import HttpRequest, JsonResponse +from tecnicas.models import Tecnica, TipoTecnica, EstiloPalabra, Producto, SesionSensorial, Modalidad, TecnicaModalidad +from django.db import transaction +from tecnicas.utils import deleteDataSession + + +class PanelCreateNappingController(PanelCreateController): + def __init__(self): + super().__init__() + + @staticmethod + def controllPost(request: HttpRequest): + if request.POST.get('action') == 'create_session': + if not request.session.get("form_basic") or not request.session.get("form_codes"): + 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") + try: + with transaction.atomic(): + # //////////////////////////// # + # + # First step: Create technique # + # + # //////////////////////////// # + data_basic = request.session["form_basic"] + data_basic["numero_catadores"] = data_basic["numero_catadores"] or 2 + data_basic["numero_repeticiones"] = 0 + + technique = Tecnica.objects.create( + tipo_tecnica=TipoTecnica.objects.get( + nombre_tecnica=data_basic["name_tecnica"]), + id_estilo=EstiloPalabra.objects.get( + nombre_estilo="napping"), + repeticiones_max=data_basic["numero_repeticiones"], + limite_catadores=data_basic["numero_catadores"], + instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador", + ) + + if not technique: + raise ValueError("Error al guardar la técnica") + + # ////////////////////////////////////////////// # + # + # Second step: Create productos with their codes # + # + # ////////////////////////////////////////////// # + codes = request.session["form_codes"] + + if not codes: + raise ValueError("No hay códigos de productos") + + products_without_save = [] + for code in codes: + product = Producto( + codigoProducto=code, + id_tecnica=technique + ) + products_without_save.append(product) + + Producto.objects.bulk_create(products_without_save) + + # /////////////////////////////////////////////////////// # + # + # Third step: Create session and relat with the technique # + # + # /////////////////////////////////////////////////////// # + mod = Modalidad.objects.get( + nombre=data_basic["modalidad"]) + + if not mod: + raise ValueError("Modalidad no encontrada") + + technique_mod = TecnicaModalidad.objects.create( + tecnica=technique, + modalidad=mod + ) + + if not technique_mod: + raise ValueError("Error al guardar la técnica") + + # /////////////////////////////////////////////////////// # + # + # Fourth step: Create session and relat with the technique # + # + # /////////////////////////////////////////////////////// # + session = SesionSensorial.objects.create( + nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None, + tecnica=technique, + creadoPor=request.user.user_presentador + ) + + if not session: + raise ValueError("Error al crear sesion sensorial") + + context = { + "message": "sesión creada", + "data": { + "codigo_sesion": session.codigo_sesion, + "nombre_sesion": session.nombre_sesion + } + } + + # ////////////////////////////////// # + # + # Final step: Delete date en session # + # + # ////////////////////////////////// # + deleteDataSession(request) + return JsonResponse(context) + + except ValueError as e: + return general_error(f"Error: {e}") + else: + return general_error("No se ha establecido acción") diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_pf_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_pf_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..b6e368c0f62f1919f6d5c082a2b852d482aa319d --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_pf_controller.py @@ -0,0 +1,105 @@ +from .panel_create_controller import PanelCreateController +from django.http import HttpRequest, JsonResponse +from django.db import transaction +from tecnicas.models import Tecnica, TipoTecnica, EstiloPalabra, SesionSensorial, Escala, TipoEscala, Producto +from tecnicas.utils import deleteDataSession, general_error + + +class PanelCreatePFController(PanelCreateController): + def __init__(self): + super().__init__() + + @staticmethod + def controllPost(request: HttpRequest): + if request.POST.get('action') == 'create_session': + if not request.session.get("form_codes"): + 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") + try: + with transaction.atomic(): + # ////////////////////////////////////// # + # + # First step: Create technique and scale # + # + # ////////////////////////////////////// # + data_basic = request.session["form_basic"] + phases_before_reptition = 2 + + technique = Tecnica.objects.create( + tipo_tecnica=TipoTecnica.objects.get( + nombre_tecnica=data_basic["name_tecnica"]), + id_estilo=EstiloPalabra.objects.get( + nombre_estilo="perfil flash"), + repeticiones_max=data_basic["numero_repeticiones"] + + phases_before_reptition, + limite_catadores=data_basic["numero_catadores"], + instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Analista", + ) + + if not technique: + raise ValueError("Error al guardar la técnica") + + created_scale = Escala.objects.create( + id_tipo_escala=TipoEscala.objects.get( + nombre_escala="estructurada"), + longitud=data_basic["numero_productos"], + tecnica=technique + ) + + if not created_scale: + raise ValueError("No se ha podido crear la escala") + + # ////////////////////////////////////////////// # + # + # Second step: Create productos with their codes # + # + # ////////////////////////////////////////////// # + codes = request.session["form_codes"] + + if not codes: + raise ValueError("No hay códigos de productos") + + products_without_save = [] + for code in codes: + product = Producto( + codigoProducto=code, + id_tecnica=technique + ) + products_without_save.append(product) + + Producto.objects.bulk_create(products_without_save) + + # /////////////////////////////////////////////////////// # + # + # Third step: Create session and relat with the technique # + # + # /////////////////////////////////////////////////////// # + session = SesionSensorial.objects.create( + nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None, + tecnica=technique, + creadoPor=request.user.user_presentador + ) + + if not session: + raise ValueError("Error al crear sesion sensorial") + + context = { + "message": "sesión creada", + "data": { + "codigo_sesion": session.codigo_sesion, + "nombre_sesion": session.nombre_sesion + } + } + + # ////////////////////////////////// # + # + # Final step: Delete date en session # + # + # ////////////////////////////////// # + deleteDataSession(request) + return JsonResponse(context) + + except ValueError as e: + return general_error(f"Error: {e}") + else: + return general_error("No se ha establecido acción") diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_rata_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_rata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..eee8462ae591dc099c787e9d964230173cb98209 --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_rata_controller.py @@ -0,0 +1,155 @@ +from .panel_create_controller import PanelCreateController +from django.http import HttpRequest, JsonResponse +from django.db import transaction +from tecnicas.controllers import EscalaController +from tecnicas.models import EsVocabulario, Tecnica, TipoTecnica, EstiloPalabra, EsAtributo, Vocabulario, Palabra, SesionSensorial, Producto +from tecnicas.utils import deleteDataSession, general_error + + +class PanelCreateRataController(PanelCreateController): + def __init__(self): + super().__init__() + + @staticmethod + def controllPost(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") + try: + 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 + + technique = Tecnica.objects.create( + tipo_tecnica=TipoTecnica.objects.get( + nombre_tecnica=data_basic["name_tecnica"]), + id_estilo=EstiloPalabra.objects.get( + nombre_estilo=data_basic["estilo_palabras"]), + repeticiones_max=data_basic["numero_repeticiones"] or 1, + limite_catadores=data_basic["numero_catadores"], + instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador", + ) + + if not technique: + raise ValueError("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): + raise ValueError(scale["error"]) + + dict_tags = request.session["form_tags"] + saved_related_tags = controllerScale.realteTags(dict_tags) + if "error" in saved_related_tags: + raise ValueError(saved_related_tags["error"]) + + # ////////////////////////////////////////////// # + # + # Second step: Create productos with their codes # + # + # ////////////////////////////////////////////// # + codes = request.session["form_codes"] + + if not codes: + raise ValueError("No hay códigos de productos") + + products_without_save = [] + for code in codes: + product = Producto( + codigoProducto=code, + id_tecnica=technique + ) + products_without_save.append(product) + + Producto.objects.bulk_create(products_without_save) + + # /////////////////////////////////////////////////////// # + # + # Third step: Create relations technique with Words Style # + # + # /////////////////////////////////////////////////////// # + style_words = technique.id_estilo.nombre_estilo + + if style_words == "atributos": + raw_ids_words = request.session["form_words"] + ids_words = [int(id_w) for id_w in raw_ids_words] + + words = Palabra.objects.filter(id__in=ids_words) + + style_atribute = EsAtributo.objects.create( + id_tecnica=technique + ) + + if not style_atribute: + raise ValueError( + "Error al intentar relacionar las palabras con la técnica") + + style_atribute.palabras.set(words) + + elif style_words == "vocabulario": + name_vocabulary = request.session["form_words"] + try: + vocabulary = Vocabulario.objects.get( + nombre_vocabulario=name_vocabulary) + except Vocabulario.DoesNotExist: + raise ValueError("Vocabulario no encontrado") + + es_vocabulary = EsVocabulario.objects.create( + id_tecnica=technique, + id_vocabulario=vocabulary + ) + if not es_vocabulary: + raise ValueError( + "Error al intentar relacionar el vocabulario con la técnica") + + else: + raise ValueError("Estilo de palabas no permitido") + + # //////////////////////////////////////////////////////// # + # + # Fourth step: Create session and relat with the technique # + # + # //////////////////////////////////////////////////////// # + session = SesionSensorial.objects.create( + nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else "", + tecnica=technique, + creadoPor=request.user.user_presentador + ) + + if not session: + raise ValueError("Error al crear sesion sensorial") + + context = { + "message": "sesión creada", + "data": { + "codigo_sesion": session.codigo_sesion, + "nombre_sesion": session.nombre_sesion + } + } + + # ////////////////////////////////// # + # + # Final step: Delete date en session # + # + # ////////////////////////////////// # + + deleteDataSession(request) + return JsonResponse(context) + except ValueError as e: + return general_error(f"Error: {e}") + else: + return general_error("No se ha establecido acción") diff --git a/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_sort_controller.py b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_sort_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..4d98ce32fe5948f299738bf230be6783c1d13080 --- /dev/null +++ b/tecnicas/controllers/views_controller/create_session/panels_create/panel_create_sort_controller.py @@ -0,0 +1,95 @@ +from .panel_create_controller import PanelCreateController +from django.http import HttpRequest, JsonResponse +from django.db import transaction +from tecnicas.models import Tecnica, TipoTecnica, EstiloPalabra, SesionSensorial, Producto +from tecnicas.utils import deleteDataSession, general_error + + +class PanelCreateSortController(PanelCreateController): + def __init__(self): + super().__init__() + + @staticmethod + def controllPost(request: HttpRequest): + if request.POST.get('action') == 'create_session': + if not request.session.get("form_basic") or not request.session.get("form_codes"): + 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") + try: + with transaction.atomic(): + # //////////////////////////// # + # + # First step: Create technique # + # + # //////////////////////////// # + data_basic = request.session["form_basic"] + data_basic["numero_catadores"] = data_basic["numero_catadores"] or 1 + data_basic["numero_repeticiones"] = 1 + + technique = Tecnica.objects.create( + tipo_tecnica=TipoTecnica.objects.get( + nombre_tecnica=data_basic["name_tecnica"]), + id_estilo=EstiloPalabra.objects.get( + nombre_estilo="sort"), + repeticiones_max=data_basic["numero_repeticiones"], + limite_catadores=data_basic["numero_catadores"], + instrucciones=data_basic["instrucciones"] or "Espere instrucciones del Presentador", + ) + + if not technique: + raise ValueError("Error al guardar la técnica") + + # ////////////////////////////////////////////// # + # + # Second step: Create productos with their codes # + # + # ////////////////////////////////////////////// # + codes = request.session["form_codes"] + + if not codes: + raise ValueError("No hay códigos de productos") + + products_without_save = [] + for code in codes: + product = Producto( + codigoProducto=code, + id_tecnica=technique + ) + products_without_save.append(product) + + Producto.objects.bulk_create(products_without_save) + + # /////////////////////////////////////////////////////// # + # + # Third step: Create session and relat with the technique # + # + # /////////////////////////////////////////////////////// # + session = SesionSensorial.objects.create( + nombre_sesion=data_basic["nombre_sesion"] if data_basic["nombre_sesion"] != "" else None, + tecnica=technique, + creadoPor=request.user.user_presentador + ) + + if not session: + raise ValueError("Error al crear sesion sensorial") + + context = { + "message": "sesión creada", + "data": { + "codigo_sesion": session.codigo_sesion, + "nombre_sesion": session.nombre_sesion + } + } + + # ////////////////////////////////// # + # + # Final step: Delete date en session # + # + # ////////////////////////////////// # + deleteDataSession(request) + return JsonResponse(context) + + except ValueError as e: + return general_error(f"Error: {e}") + else: + return general_error("No se ha establecido acción") diff --git a/tecnicas/controllers/views_controller/session_management/details/details_cata_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_cata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..4b21cde747f701a2ed7ab95a6737339954c638a2 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details/details_cata_controller.py @@ -0,0 +1,72 @@ +from django.db.models import F +from tecnicas.models import SesionSensorial, Calificacion, ValorBooleano +from tecnicas.controllers import PalabrasController +from tecnicas.utils import defaultdict_to_dict +from .details_controller import DetallesController +from collections import defaultdict + + +class DetallesCATAController(DetallesController): + def __init__(self, session: SesionSensorial): + super().__init__(session) + self.url_template = "tecnicas/manage_sesions/details-session-cata.html" + self.url_next = "cata_system:monitor_sesion" + + def getContext(self): + technique = self.session.tecnica + + self.context = { + "sesion": self.session, + "use_technique": technique + } + + # Recuperar palabras + self.words = PalabrasController.getWordsInTechnique( + self.session.tecnica) + self.context["palabras"] = [word.nombre_palabra for word in self.words] + + # Intentar recuperar las calificaciones + ratings_for_repetition = [] + + ratings = list(Calificacion.objects.filter( + id_tecnica=technique)) + + if not ratings: + self.context["calificaciones"] = ratings_for_repetition + self.context["existen_calificaciones"] = False + return self.context + + data = ( + ValorBooleano.objects + .filter(id_dato__id_calificacion__in=ratings) + .values( + nombre_palabra=F("id_dato__id_palabra__nombre_palabra"), + repeticion=F("id_dato__id_calificacion__num_repeticion"), + producto_code=F( + "id_dato__id_calificacion__id_producto__codigoProducto"), + usuario_catador=F( + "id_dato__id_calificacion__id_catador__user__username"), + dato_valor=F("valor") + ) + ) + + ratings_for_repetition = defaultdict( + lambda: defaultdict(lambda: defaultdict(list))) + + for item in data: + user = item["usuario_catador"] + 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 + + # Se comprueba que ya no se pueda iniciar la repeticion + self.context["fin_repeticiones"] = technique.repeticion >= technique.repeticiones_max + return self.context diff --git a/tecnicas/controllers/views_controller/session_management/details/details_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..ae6b3a793672804000c0c712225cf929299b4005 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details/details_controller.py @@ -0,0 +1,70 @@ +from django.http import HttpRequest +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import SesionSensorial, Presentador, Tecnica, Participacion +from tecnicas.controllers import ParticipacionController + + +class DetallesController(): + url_template: str + url_next = "cata_system:monitor_sesion" + + def __init__(self, session: SesionSensorial): + self.session = session + + def controllGetResponse(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): + return {} + + def deleteSesorialSession(self): + technique = Tecnica.objects.get(id=self.session.tecnica.id) + technique.delete() + + def startRepetition(self, presenter: Presentador, request: HttpRequest): + creator = presenter + technique = self.session.tecnica + + if creator.user.username != self.session.creadoPor.user.username: + return self.controllGetResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición", request=request) + elif self.session.activo: + return self.controllGetResponse(error="La sesión ya está activada", request=request) + elif technique.repeticion >= technique.repeticiones_max: + return self.controllGetResponse(error="Se ha alcanzado el número de repeticiones máxima", request=request) + + is_update_participations = self.setParticipationsToNoFinished() + if not is_update_participations: + return self.controllGetResponse(error="Error al actualizar las participaciones", request=request) + + self.session.activo = True + technique.repeticion = technique.repeticion + 1 + + technique.save() + self.session.save() + + parameters = { + "session_code": self.session.codigo_sesion + } + return redirect( + reverse(self.url_next, kwargs=parameters)) + + def setParticipationsToNoFinished(self): + there_participacions = Participacion.objects.filter( + tecnica=self.session.tecnica).exists() + + if there_participacions: + (is_update_participations, + message) = ParticipacionController.outAllInSession(self.session) + + return is_update_participations + + return True \ 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/details_escala_controller.py similarity index 52% rename from tecnicas/controllers/views_controller/session_management/details_escala_controller.py rename to tecnicas/controllers/views_controller/session_management/details/details_escala_controller.py index a36c8080fa544fee777efeaccb516e4e0b49ea4e..f5838b4c1228c7d4cdfdc732eb0b0351c99168c7 100644 --- a/tecnicas/controllers/views_controller/session_management/details_escala_controller.py +++ b/tecnicas/controllers/views_controller/session_management/details/details_escala_controller.py @@ -11,40 +11,26 @@ 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, Participacion, Calificacion, Escala -from tecnicas.controllers import DatoController, PalabrasController, ParticipacionController +from tecnicas.models import SesionSensorial, Calificacion, Escala +from tecnicas.controllers import DatoController, PalabrasController from .details_controller import DetallesController -from tecnicas.utils import defaultdict_to_dict, controller_error +from tecnicas.utils import defaultdict_to_dict 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) + self.url_template = "tecnicas/manage_sesions/details-session.html" + self.url_next = "cata_system:monitor_sesion" def getContext(self): + technique = self.session.tecnica + self.context = { - "use_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + "sesion": self.session, + "use_technique": technique.tipo_tecnica.nombre_tecnica } - self.context["sesion"] = self.session - - technique = self.session.tecnica # Datos de la escala usada scale: Escala = technique.escala_tecnica @@ -94,35 +80,3 @@ class DetallesEscalasController(DetallesController): self.context["fin_repeticiones"] = technique.repeticion >= technique.repeticiones_max return self.context - - def startRepetition(self, presenter: Presentador, request: HttpRequest): - 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", request=request) - elif self.session.activo: - return self.getResponse(error="La sesión ya está activada", request=request) - elif technique.repeticion >= technique.repeticiones_max: - return self.getResponse(error="Se ha alcanzado el número de repeticiones máxima", request=request) - - there_participacions = Participacion.objects.filter( - tecnica=technique).exists() - - if there_participacions: - (is_update_participations, - message) = ParticipacionController.outAllInSession(self.session) - if not is_update_participations: - return self.getResponse(error=message, request=request) - - 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/details_napping_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_napping_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..f1f213fa5b1cb29b548c2d2cc169eb5f720add6d --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details/details_napping_controller.py @@ -0,0 +1,508 @@ +from django.http import HttpRequest +from django.shortcuts import redirect +from django.urls import reverse +from django.db.models import F +from .details_controller import DetallesController +from tecnicas.models import ( + SesionSensorial, Presentador, Modalidad, TecnicaModalidad, Catador, + Participacion, DatoPunto, Calificacion, GrupoProducto, ValorBooleano, + ValorDecimal, Producto, Escala, EsVocabulario, Vocabulario +) +from tecnicas.utils import defaultdict_to_dict +from collections import defaultdict + + +class DetallesNappingController(DetallesController): + def __init__(self, session: SesionSensorial): + super().__init__(session) + self.url_template = "tecnicas/manage_sesions/details-session-napping.html" + self.url_next = "cata_system:monitor_sesion" + self.context = {} + + def getContext(self): + self.context["session"] = self.session + + self.defineStatus() + self.setIsEndSession() + self.setDataTable() + + return self.context + + def defineStatus(self): + repetition = self.session.tecnica.repeticion + mod = TecnicaModalidad.objects.get( + tecnica=self.session.tecnica) + + self.context["mod_tech"] = mod.modalidad.nombre + self.context["mode"] = mod.modalidad.nombre + if mod.modalidad.nombre == "sin modalidad": + self.context["mod_tech"] = "No se usa modalidad" + + if not self.session.activo: + self.context["status"] = "Listo para iniciar la sesión con Napping" + elif self.session.activo: + self.context["status"] = "Sesión con en curso" + + def controllPostResponse(self, request: HttpRequest, action: str): + if action == "start_sin_modalidad": + response = self.startNapping(request=request) + + elif action == "start_perfil_ultra_flash": + response = self.startNapping(request=request) + + elif action == "start_sorting": + response = self.startNapping(request=request) + + elif action == "combine_sessions": + response = self.combineSessions(request=request) + + elif action == "delete_session": + self.deleteSesorialSession() + response = redirect( + reverse("cata_system:panel_sesiones", kwargs={"page": 1})) + + else: + response = self.controllGetResponse( + error="Modalidad sin implantar", request=request) + + return response + + def startNapping(self, request: HttpRequest): + if request.user.user_presentador.user.username != self.session.creadoPor.user.username: + return self.controllGetResponse(error="Solo el presentador que crea la sesión puede iniciar la repetición", request=request) + elif self.session.activo: + return self.controllGetResponse(error="La sesión ya está activada", request=request) + + is_update_participations = self.setParticipationsToNoFinished() + if not is_update_participations: + return self.controllGetResponse(error="Error al actualizar las participaciones", request=request) + + self.session.activo = True + self.session.save() + + parameters = { + "session_code": self.session.codigo_sesion + } + return redirect( + reverse(self.url_next, kwargs=parameters)) + + def setDataTable(self): + participations = Participacion.objects.filter( + tecnica=self.session.tecnica).select_related("catador") + testers = [participation.catador for participation in participations] + self.context["testers"] = testers + + ratings = Calificacion.objects.filter(id_tecnica=self.session.tecnica) + + coordinates = ( + DatoPunto.objects.filter(calificacion__in=ratings) + .values( + producto=F("calificacion__id_producto__codigoProducto"), + catador=F("calificacion__id_catador__user__username"), + px=F("x"), + py=F("y"), + )) + + if not coordinates.exists(): + self.context["there_data"] = False + return [] + + coordinates_by_product = defaultdict(dict) + + for coordinate in coordinates: + coordinates_by_product[coordinate["producto"]][coordinate["catador"]] = { + "px": coordinate["px"], + "py": coordinate["py"], + } + + self.context["coordinates_no_mode"] = defaultdict_to_dict( + coordinates_by_product) + + # Add word frequency data for perfil ultra flash mode + mod = TecnicaModalidad.objects.get(tecnica=self.session.tecnica) + if mod.modalidad.nombre == "perfil ultra flash": + self.setWordFrequencies(ratings) + elif mod.modalidad.nombre == "sorting": + self.setSortingData() + + self.context["there_data"] = True + + def setWordFrequencies(self, ratings): + from collections import Counter + + # Prefetch palabras to optimize queries + ratings_with_words = ratings.prefetch_related( + 'palabras').select_related('id_producto') + + # Dictionary to store word frequencies by product + word_frequencies_by_product = defaultdict(Counter) + all_words_set = set() + + for rating in ratings_with_words: + producto_code = rating.id_producto.codigoProducto + words = rating.palabras.all() + + for word in words: + word_name = word.nombre_palabra + word_frequencies_by_product[producto_code][word_name] += 1 + all_words_set.add(word_name) + + # Convert Counter objects to regular dicts and sort words alphabetically + word_frequencies_dict = { + product: dict(frequencies) + for product, frequencies in word_frequencies_by_product.items() + } + + # Sort all words alphabetically for consistent column ordering + all_words_sorted = sorted(all_words_set) + + self.context["word_frequencies"] = word_frequencies_dict + self.context["all_words"] = all_words_sorted + + def setSortingData(self): + # Get all ratings for this technique to access DatoPunto + ratings = Calificacion.objects.filter(id_tecnica=self.session.tecnica) + + # Get coordinates for all products + coordinates = ( + DatoPunto.objects.filter(calificacion__in=ratings) + .values( + producto=F("calificacion__id_producto__codigoProducto"), + producto_id=F("calificacion__id_producto__id"), + catador=F("calificacion__id_catador__user__username"), + catador_id=F("calificacion__id_catador__id"), + px=F("x"), + py=F("y"), + )) + + # Create a mapping of (catador_id, producto_id) -> coordinates + coord_map = {} + for coord in coordinates: + key = (coord["catador_id"], coord["producto_id"]) + coord_map[key] = { + "px": coord["px"], + "py": coord["py"], + "producto": coord["producto"], + "catador": coord["catador"] + } + + # Get all groups with their products and words + grupos = ( + GrupoProducto.objects.filter(tecnica=self.session.tecnica) + .prefetch_related("productos", "palabras") + .select_related("catador__user") + ) + + # Create a mapping of (catador_id, producto_id) -> words + words_map = defaultdict(list) + for grupo in grupos: + catador_id = grupo.catador.id + words = [palabra.nombre_palabra for palabra in grupo.palabras.all()] + words_str = ";".join(words) if words else "" + + for producto in grupo.productos.all(): + key = (catador_id, producto.id) + words_map[key] = words_str + + # Structure final data: product -> catador -> {px, py, words} + sorting_data = defaultdict(dict) + + for key, coord_data in coord_map.items(): + catador_id, producto_id = key + producto_code = coord_data["producto"] + catador_username = coord_data["catador"] + + sorting_data[producto_code][catador_username] = { + "px": coord_data["px"], + "py": coord_data["py"], + "words": words_map.get(key, "") + } + + self.context["sorting_data"] = defaultdict_to_dict(sorting_data) + + def setIsEndSession(self): + if not self.session.activo and self.session.tecnica.repeticion < 1: + self.context["finished"] = False + return + elif self.session.activo: + self.context["finished"] = False + return + elif not self.session.activo and self.session.tecnica.repeticion >= 1: + self.context["finished"] = True + return + + # ==================== SESSION COMBINATION METHODS ==================== + + def combineSessions(self, request: HttpRequest): + """Handle session combination request""" + session_b_code = request.POST.get("session_b_code", "").strip() + + if not session_b_code: + return self.controllGetResponse( + error="Debe proporcionar un código de sesión", request=request) + + # Validate and get session B + validation_result = self.validateSessionCombination(session_b_code) + + if validation_result.get("error"): + return self.controllGetResponse( + error=validation_result["error"], request=request) + + session_b = validation_result["session_b"] + technique_type = validation_result["technique_type"] + + # Get combined data based on technique type + if technique_type == "cata": + combined_data = self.getCombinedDataForCATA(session_b) + elif technique_type == "rata": + combined_data = self.getCombinedDataForRATA(session_b) + elif technique_type == "escalas": + combined_data = self.getCombinedDataForEscalas(session_b) + else: + return self.controllGetResponse( + error="Tipo de técnica no soportado para combinación", request=request) + + # Add combined data to context + self.context["combined_data"] = combined_data + self.context["session_b"] = session_b + self.context["session_b_technique_type"] = technique_type + + return self.controllGetResponse(request=request) + + def validateSessionCombination(self, session_b_code: str): + """Validate that Session B can be combined with Session A (Napping)""" + result = {"error": None, "session_b": None, "technique_type": None} + + # Check if Session B exists + try: + session_b = SesionSensorial.objects.select_related( + "tecnica__tipo_tecnica").get(codigo_sesion=session_b_code) + except SesionSensorial.DoesNotExist: + result["error"] = f"No existe una sesión con el código: {session_b_code}" + return result + + # Check if Session B technique is CATA, RATA, or Escalas + technique_type = session_b.tecnica.tipo_tecnica.nombre_tecnica + valid_techniques = ["cata", "rata", "escalas"] + + if technique_type not in valid_techniques: + result[ + "error"] = f"La sesión B debe usar CATA, RATA o Escalas. Técnica actual: {technique_type}" + return result + + # Check if Session B is finished + if session_b.activo: + result["error"] = "La sesión B debe estar finalizada (no activa)" + return result + + if session_b.tecnica.repeticion < 1: + result["error"] = "La sesión B debe haber completado al menos una repetición" + return result + + # Get products from both sessions + products_a = set( + Producto.objects.filter( + calificacion_producto__id_tecnica=self.session.tecnica + ).values_list("codigoProducto", flat=True).distinct() + ) + + products_b = set( + Producto.objects.filter( + calificacion_producto__id_tecnica=session_b.tecnica + ).values_list("codigoProducto", flat=True).distinct() + ) + + # Check if products match + if products_a != products_b: + result["error"] = f"Los productos no coinciden. Sesión A: {len(products_a)} productos, Sesión B: {len(products_b)} productos" + return result + + # Get tasters from both sessions + tasters_a = set( + Participacion.objects.filter( + tecnica=self.session.tecnica + ).values_list("catador__user__username", flat=True) + ) + + tasters_b = set( + Participacion.objects.filter( + tecnica=session_b.tecnica + ).values_list("catador__user__username", flat=True) + ) + + # Check if tasters match + if tasters_a != tasters_b: + result["error"] = f"Los catadores no coinciden. Sesión A: {len(tasters_a)} catadores, Sesión B: {len(tasters_b)} catadores" + return result + + result["session_b"] = session_b + result["technique_type"] = technique_type + return result + + def getCombinedDataForCATA(self, session_b: SesionSensorial): + """Get combined data for CATA technique (word frequencies)""" + from collections import Counter + + # Get all ratings for session B + ratings_b = Calificacion.objects.filter(id_tecnica=session_b.tecnica) + + # Get boolean values (CATA uses boolean) + data = ( + ValorBooleano.objects + .filter(id_dato__id_calificacion__in=ratings_b, valor=True) + .values( + palabra=F("id_dato__id_palabra__nombre_palabra"), + producto=F( + "id_dato__id_calificacion__id_producto__codigoProducto"), + ) + ) + + # Count word frequencies per product + word_frequencies = defaultdict(Counter) + all_words_set = set() + + for item in data: + palabra = item["palabra"] + producto = item["producto"] + word_frequencies[producto][palabra] += 1 + all_words_set.add(palabra) + + # Get vocabulary info if exists + vocabulary_info = self.getVocabularyInfo(session_b.tecnica) + + return { + "word_frequencies": defaultdict_to_dict(word_frequencies), + "all_words": sorted(all_words_set), + "vocabulary_info": vocabulary_info, + } + + def getCombinedDataForRATA(self, session_b: SesionSensorial): + """Get combined data for RATA technique (word averages)""" + + # Get all ratings for session B + ratings_b = Calificacion.objects.filter(id_tecnica=session_b.tecnica) + + # Get decimal values (RATA uses decimal) + data = ( + ValorDecimal.objects + .filter(id_dato__id_calificacion__in=ratings_b) + .values( + palabra=F("id_dato__id_palabra__nombre_palabra"), + producto=F( + "id_dato__id_calificacion__id_producto__codigoProducto"), + valor_decimal=F("valor"), + ) + ) + + # Calculate averages per product per word + word_sums = defaultdict(lambda: defaultdict(list)) + all_words_set = set() + + for item in data: + palabra = item["palabra"] + producto = item["producto"] + valor = item["valor_decimal"] + word_sums[producto][palabra].append(valor) + all_words_set.add(palabra) + + # Calculate averages + word_averages = {} + for producto, palabras in word_sums.items(): + word_averages[producto] = {} + for palabra, valores in palabras.items(): + word_averages[producto][palabra] = sum(valores) / len(valores) + + # Get vocabulary info if exists + vocabulary_info = self.getVocabularyInfo(session_b.tecnica) + + return { + "word_averages": word_averages, + "all_words": sorted(all_words_set), + "vocabulary_info": vocabulary_info, + } + + def getCombinedDataForEscalas(self, session_b: SesionSensorial): + """Get combined data for Escalas technique (averages across repetitions)""" + + # Get all ratings for session B + ratings_b = Calificacion.objects.filter(id_tecnica=session_b.tecnica) + + # Get decimal values grouped by repetition + data = ( + ValorDecimal.objects + .filter(id_dato__id_calificacion__in=ratings_b) + .values( + palabra=F("id_dato__id_palabra__nombre_palabra"), + producto=F( + "id_dato__id_calificacion__id_producto__codigoProducto"), + repeticion=F("id_dato__id_calificacion__num_repeticion"), + valor_decimal=F("valor"), + ) + ) + + # Calculate averages per repetition (like RATA), then average across repetitions + # Structure: {producto: {repeticion: {palabra: [valores]}}} + repetition_values = defaultdict( + lambda: defaultdict(lambda: defaultdict(list))) + all_words_set = set() + + for item in data: + palabra = item["palabra"] + producto = item["producto"] + repeticion = item["repeticion"] + valor = item["valor_decimal"] + # Collect all values for averaging + repetition_values[producto][repeticion][palabra].append(valor) + all_words_set.add(palabra) + + # Calculate average per repetition, then average across repetitions + word_averages = {} + for producto, repeticiones in repetition_values.items(): + word_averages[producto] = {} + # Get all words for this product across all repetitions + all_product_words = set() + for rep_words in repeticiones.values(): + all_product_words.update(rep_words.keys()) + + # Calculate average for each word + for palabra in all_product_words: + # Get average for each repetition + rep_averages = [] + for rep in repeticiones.keys(): + if palabra in repeticiones[rep]: + valores = repeticiones[rep][palabra] + rep_averages.append(sum(valores) / len(valores)) + + # Average the repetition averages + if rep_averages: + word_averages[producto][palabra] = sum( + rep_averages) / len(rep_averages) + + # Get scale and vocabulary info + scale = Escala.objects.get(tecnica=session_b.tecnica) + scale_info = None + if scale: + scale_info = { + "type": scale.id_tipo_escala.nombre_escala, + "size": scale.longitud + } + + vocabulary_info = self.getVocabularyInfo(session_b.tecnica) + + return { + "word_averages": word_averages, + "all_words": sorted(all_words_set), + "scale_info": scale_info, + "vocabulary_info": vocabulary_info, + "num_repetitions": session_b.tecnica.repeticion, + } + + def getVocabularyInfo(self, tecnica): + es_vocabulario = EsVocabulario.objects.filter( + id_tecnica=tecnica).first() + if es_vocabulario: + vocabulario = es_vocabulario.id_vocabulario + return { + "nombre": vocabulario.nombre_vocabulario, + } + return None diff --git a/tecnicas/controllers/views_controller/session_management/details/details_pf_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_pf_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..48c38186db76d9e44fdcfa04680a71d7594053d7 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details/details_pf_controller.py @@ -0,0 +1,215 @@ +from tecnicas.models import SesionSensorial, ListaPalabras, Calificacion, Catador +from tecnicas.controllers import DatoController +from tecnicas.utils import defaultdict_to_dict +from .details_controller import DetallesController +from collections import defaultdict + + +class DetallesPFController(DetallesController): + skip_repetition = 2 + + def __init__(self, session: SesionSensorial): + super().__init__(session) + self.url_template = "tecnicas/manage_sesions/details-session-pf.html" + self.url_next = "cata_system:monitor_sesion" + + def getContext(self): + technique = self.session.tecnica + + self.context = { + "sesion": self.session, + "use_technique": technique, + "tipo_escala": technique.escala_tecnica.id_tipo_escala.nombre_escala, + "repeticiones_max": technique.repeticiones_max - self.skip_repetition + } + + # Definir el estado de la sesion + rep = technique.repeticion + activate = self.session.activo + self.context["estado"] = self.getStatus(rep, activate) + + self.getDataPhases() + + self.isEndSession() + + return self.context + + def isEndSession(self): + current_rep = self.session.tecnica.repeticion - self.skip_repetition + max_rep = self.session.tecnica.repeticiones_max - self.skip_repetition + self.context["finished"] = current_rep >= max_rep + + def getDataPhases(self): + curren_repetition = self.session.tecnica.repeticion + + if curren_repetition == 1: + self.context["fisrt_phase"] = self.getDataFirstPhase() + self.context["repeticion"] = 0 + + elif curren_repetition == 2: + self.context["fisrt_phase"] = self.getDataFirstPhase() + self.context["second_phase"] = self.getDataSecondPhase() + self.context["repeticion"] = 0 + + elif curren_repetition >= 3: + self.context["fisrt_phase"] = self.getDataFirstPhase() + self.context["second_phase"] = self.getDataSecondPhase() + self.context["data_ratings"] = self.getDataRatings() + self.context["repeticion"] = self.session.tecnica.repeticion - self.skip_repetition + + return self.context + + def getDataFirstPhase(self): + lists_testers = ListaPalabras.objects.filter( + tecnica=self.session.tecnica, + es_final=False + ) + + result = [] + for list in lists_testers: + try: + username = list.catador.user.username + except Exception: + username = None + + words_qs = list.palabras.all() + words = [] + for p in words_qs: + nombre = getattr(p, 'nombre_palabra', None) + words.append({ + 'id': getattr(p, 'id', None), + 'nombre_palabra': nombre + }) + + result.append({ + 'username': username, + 'words': words + }) + + return result + + def getDataSecondPhase(self): + lists_testers = ListaPalabras.objects.filter( + tecnica=self.session.tecnica, + es_final=True + ) + + result = [] + for list in lists_testers: + try: + username = list.catador.user.username + except Exception: + username = None + + words_qs = list.palabras.all() + words = [] + for p in words_qs: + nombre = getattr(p, 'nombre_palabra', None) + words.append({ + 'id': getattr(p, 'id', None), + 'nombre_palabra': nombre + }) + + result.append({ + 'username': username, + 'words': words + }) + + return result + + def getDataRatings(self): + technique = self.session.tecnica + + if technique.repeticion > 3: + return self.getDataRatingsFinal() + + elif technique.repeticion == 3: + return self.getDataRatingsInitial() + + def getStatus(self, rep: int, activate: bool): + status = "" + + if rep == 0 and not activate: + status = "Listo para crear listas iniciales" + + elif rep == 1 and activate: + status = "En primera fase, creación de listas iniciales" + elif rep == 1 and not activate: + status = "Listo para crear listas finales" + + elif rep == 2 and activate: + status = "En segunda fase, creación de listas finales" + elif rep == 2 and not activate: + status = "Listo para calificaciones" + + elif rep > 2 and not activate: + status = "Listo para calificaciones" + elif rep > 2 and activate: + status = "Catadores calificando" + + return status + + def getDataRatingsInitial(self): + ratings = list(Calificacion.objects.filter(id_tecnica=self.session.tecnica, num_repeticion=3)) + + if ratings: + raw_data = DatoController.getWordValuesForConvecional( + technique=self.session.tecnica, + ratings=ratings + ) + + structured_data = defaultdict(lambda: defaultdict(list)) + + for item in raw_data: + prod_code = item["producto_code"] + username = item["usuario_catador"] + + structured_data[prod_code][username].append({ + "palabra": item["nombre_palabra"], + "valor": item["dato_valor"] + }) + + return defaultdict_to_dict(structured_data) + + def getDataRatingsFinal(self): + lists_words_testers = self.context["second_phase"] + technique = self.session.tecnica + + ratings_for_tester = [] + + for list_tester in lists_words_testers: + tester_username = list_tester["username"] + # Se recuperan las calificaciones + ratings_for_repetition = [] + + ratings = list(Calificacion.objects.filter( + id_tecnica=technique, id_catador__user__username=tester_username)) + + if not ratings: + continue + + data = DatoController.getWordValuesPF( + ratings=ratings, technique=technique, tester=Catador.objects.get(user__username=tester_username)) + + ratings_for_repetition = defaultdict(lambda: defaultdict(list)) + + # Estructurar los datos + for item in data: + rep = item["repeticion"] + prod = item["producto_code"] + + ratings_for_repetition[rep-2][prod].append({ + "nombre_palabra": item["nombre_palabra"], + "dato_valor": item["dato_valor"] + }) + + ratings_for_tester.append( + { + "tester": tester_username, + "ratings": defaultdict_to_dict( + ratings_for_repetition), + "words": list_tester["words"] + } + ) + + return ratings_for_tester \ No newline at end of file diff --git a/tecnicas/controllers/views_controller/session_management/details/details_rata_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_rata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..71dae2b469f24c2dd1a2ec969a32a329d62c574a --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details/details_rata_controller.py @@ -0,0 +1,7 @@ +from tecnicas.models import SesionSensorial +from .details_controller import DetallesController + + +class DetallesRATAController(DetallesController): + def __init__(self, session: SesionSensorial): + super().__init__(session) diff --git a/tecnicas/controllers/views_controller/session_management/details/details_sort_controller.py b/tecnicas/controllers/views_controller/session_management/details/details_sort_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..542df448c230782658ce5d12142c38e733b60405 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/details/details_sort_controller.py @@ -0,0 +1,91 @@ +from .details_controller import DetallesController +from tecnicas.models import SesionSensorial, GrupoProducto, Producto, Participacion +from collections import defaultdict + + +class DetallesSortController(DetallesController): + def __init__(self, session: SesionSensorial): + super().__init__(session) + self.url_template = "tecnicas/manage_sesions/details-session-sort.html" + self.url_next = "cata_system:monitor_sesion" + + def getContext(self): + technique = self.session.tecnica + + finished = False + status = "" + + if technique.repeticion < technique.repeticiones_max and not self.session.activo: + status = "En espera para iniciar la sesión" + elif technique.repeticion >= technique.repeticiones_max and not self.session.activo: + status = "Esta sesión ha sido finalizada" + finished = True + else: + status = "La sesión está en progreso" + + self.context = { + "sesion": self.session, + "technique": technique, + "status": status, + "finished": finished + } + + self.context["data_groups"] = { + "data": self.setDataSort(), + "testers": self.setHeaders() + } + + return self.context + + def setDataSort(self): + data = [] + technique = self.session.tecnica + + products = Producto.objects.filter(id_tecnica=technique) + + groups = GrupoProducto.objects.select_related("catador").filter( + tecnica=technique + ) + + if len(groups): + self.context["there_data"] = True + else: + self.context["there_data"] = False + return [] + + for product in products: + product_data = { + "codigo_producto": product.codigoProducto, + "palabras": {} + } + + related_groups = groups.filter(productos=product).select_related( + "catador__user" + ).prefetch_related("palabras") + + data_words = defaultdict(set) + + for group in related_groups: + catador_username = group.catador.user.username + + for word in group.palabras.all(): + data_words[catador_username].add(word.nombre_palabra) + + product_data["palabras"] = { + username: list(words) + for username, words in data_words.items() + } + + data.append(product_data) + + return data + + def setHeaders(self): + participacions = list(Participacion.objects.filter( + tecnica=self.session.tecnica + ).only("catador").select_related("catador__user")) + + testers = [ + participacion.catador.user.username for participacion in participacions] + + return testers diff --git a/tecnicas/controllers/views_controller/session_management/details_controller.py b/tecnicas/controllers/views_controller/session_management/details_controller.py deleted file mode 100644 index b98cfe897ed711c8084f45eb50e152f5641fc229..0000000000000000000000000000000000000000 --- a/tecnicas/controllers/views_controller/session_management/details_controller.py +++ /dev/null @@ -1,13 +0,0 @@ -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_rata_controller.py b/tecnicas/controllers/views_controller/session_management/details_rata_controller.py deleted file mode 100644 index c0b437791c57de37b9354b41e7d18bdd33417635..0000000000000000000000000000000000000000 --- a/tecnicas/controllers/views_controller/session_management/details_rata_controller.py +++ /dev/null @@ -1,56 +0,0 @@ -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/monitor_controller.py similarity index 64% rename from tecnicas/controllers/views_controller/session_management/monitor_controller.py rename to tecnicas/controllers/views_controller/session_management/monitor/monitor_controller.py index b0f2a19b1845b605f1bd174399e34b300e7da89b..16aa77587372d74b5abcf710927f69fce4143cc2 100644 --- a/tecnicas/controllers/views_controller/session_management/monitor_controller.py +++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_controller.py @@ -1,8 +1,7 @@ from django.http import HttpRequest -from django.shortcuts import render -from tecnicas.models import SesionSensorial, Producto, EsAtributo, EsVocabulario -from tecnicas.controllers import ParticipacionController, SesionController -from tecnicas.utils import controller_error +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import SesionSensorial, Producto, EsAtributo, EsVocabulario, Participacion class MonitorController(): @@ -12,9 +11,21 @@ class MonitorController(): def __init__(self, session: SesionSensorial): self.sensorial_session = session + def controllPostFinishSession(self, request: HttpRequest): + (is_all_end, message) = self.checkAllFinish() + if not is_all_end: + self.setContext() + self.context["error"] = message + return render(request, self.url_view, self.context) + self.finishSession() + return redirect(reverse(self.previus_view, kwargs={"session_code": self.sensorial_session.codigo_sesion})) + + def checkAllFinish(self) -> (bool, str): + return (False, "Función sin implementar") + def setContext(self): - self.participations = ParticipacionController.getParticipationsInTechinique( - self.sensorial_session.tecnica) + self.participations = Participacion.objects.filter( + tecnica=self.sensorial_session.tecnica) self.context = { "code_session": self.sensorial_session.codigo_sesion, @@ -26,7 +37,7 @@ class MonitorController(): "use_technique": self.sensorial_session.tecnica.tipo_tecnica.nombre_tecnica } - def controlGetResponse(self, request: HttpRequest, error: str = "", message: str = ""): + def controllGetResponse(self, request: HttpRequest, error: str = "", message: str = ""): self.setContext() if error != "" or error: @@ -36,7 +47,7 @@ class MonitorController(): return render(request, self.url_view, self.context) - def getExpectedRatingsEscalasRapida(self): + def getExpectedRatings(self): num_products = Producto.objects.filter( id_tecnica=self.sensorial_session.tecnica).count() style_words = self.sensorial_session.tecnica.id_estilo @@ -52,8 +63,6 @@ class MonitorController(): return num_products * num_words def finishSession(self): - response = SesionController.finishRepetion(self.sensorial_session) - if isinstance(response, dict): - return controller_error(response["error"]) - self.sensorial_session.refresh_from_db() + self.sensorial_session.activo = False + self.sensorial_session.save() return self.sensorial_session diff --git a/tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_escalas_controller.py similarity index 60% rename from tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py rename to tecnicas/controllers/views_controller/session_management/monitor/monitor_escalas_controller.py index 36b976bf9cc8038f71021cbdbdfc6413bf436fab..59c7e853d9bb96e726ab4892126ed9fb1e8f544f 100644 --- a/tecnicas/controllers/views_controller/session_management/monitor_escalas_controller.py +++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_escalas_controller.py @@ -1,6 +1,3 @@ -from django.http import HttpRequest -from django.shortcuts import render, redirect -from django.urls import reverse from tecnicas.models import Dato, Participacion from tecnicas.controllers import SesionController from .monitor_controller import MonitorController @@ -12,23 +9,10 @@ class MonitorEscalasController(MonitorController): self.url_view = "tecnicas/manage_sesions/monitor-sesion.html" self.previus_view = "cata_system:detalles_sesion" - def controllPostFinishSession(self, request: HttpRequest): - self.setContext() - (is_all_end, message) = self.checkAllFinish() - if not is_all_end: - self.context["error"] = message - return render(request, self.url_view, self.context) - response = self.finishSession() - if isinstance(response, dict): - self.context["error"] = response["error"] - return render(request, self.url_view, self.context) - self.context["message"] = message - return redirect(reverse(self.previus_view, kwargs={"session_code": self.sensorial_session.codigo_sesion})) - def checkAllFinish(self): technique = self.sensorial_session.tecnica - expected_ratings_repetition = self.getExpectedRatingsEscalasRapida() + expected_ratings_repetition = self.getExpectedRatings() all_participations = list( Participacion.objects.filter(tecnica=technique)) diff --git a/tecnicas/controllers/views_controller/session_management/monitor/monitor_napping_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_napping_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..e689f6b1f2475066c903682d6f12be3b3789b7e7 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_napping_controller.py @@ -0,0 +1,35 @@ +from tecnicas.models import SesionSensorial +from tecnicas.models import Participacion, TecnicaModalidad +from .monitor_controller import MonitorController + + +class MonitorNappingController(MonitorController): + def __init__(self, session: SesionSensorial): + super().__init__(session) + self.url_view = "tecnicas/manage_sesions/monitor-session-sort.html" + self.previus_view = "cata_system:detalles_sesion" + + def checkAllFinish(self) -> (bool, str): + technique = self.sensorial_session.tecnica + + num_participations = Participacion.objects.filter( + tecnica=technique).count() + + if num_participations < technique.limite_catadores: + return (False, "No se ha alcanzado el número máximo de catadores") + + unfinished_participations = Participacion.objects.filter( + tecnica=technique, finalizado=False).count() + + if unfinished_participations > 0: + return (False, "No todos los catadores han finalizado su evaluación") + + return (True, "Puedes finalizar la sesión") + + def finishSession(self): + technique = self.sensorial_session.tecnica + technique.repeticion = 1 + technique.save() + self.sensorial_session.activo = False + self.sensorial_session.save() + return self.sensorial_session diff --git a/tecnicas/controllers/views_controller/session_management/monitor/monitor_pf_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_pf_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..d15d1356635910a50e8ea628a7d3fcac337952a4 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_pf_controller.py @@ -0,0 +1,72 @@ +from tecnicas.models import Dato, Participacion, Catador, ListaPalabras, Producto +from tecnicas.controllers import SesionController +from .monitor_controller import MonitorController + + +class MonitorPFController(MonitorController): + def __init__(self, session: SesionController): + super().__init__(session) + self.url_view = "tecnicas/manage_sesions/monitor-session-pf.html" + self.previus_view = "cata_system:detalles_sesion" + + def checkAllFinish(self): + rep = self.sensorial_session.tecnica.repeticion + + finish_data = () + + if rep == 1 or rep == 2: + finish_data = self.checkFinishFirstPhase() + elif rep >= 3: + finish_data = self.checkFinishRepetition() + + return finish_data + + def checkFinishFirstPhase(self): + num_paricipations = Participacion.objects.filter( + tecnica=self.sensorial_session.tecnica).count() + if num_paricipations < self.sensorial_session.tecnica.limite_catadores: + return (False, "No se ha alcanzado el número máximo de catadores") + + unfinished_participations = Participacion.objects.filter( + tecnica=self.sensorial_session.tecnica, finalizado=False).count() + if unfinished_participations: + return (False, "No todos los catadores han finalizado su evaluación") + + return (True, "Puedes finalizar la sesión") + + def checkFinishRepetition(self) -> tuple[bool, str]: + technique = self.sensorial_session.tecnica + + # Revisar numero de catadores sea alcanzado + all_participations = list( + Participacion.objects.filter(tecnica=technique)) + if len(all_participations) < technique.limite_catadores: + return (False, "No se ha alcanzado el número máximo de Catadores") + + # Revisar que cada catador haya terminado de calificar sus palabras + for particiapation in all_participations: + expected_ratings_repetition = self.getExpectedRatings( + tester=particiapation.catador) + + num_ratings_now = Dato.objects.filter( + id_calificacion__num_repeticion=technique.repeticion, + id_calificacion__id_catador=particiapation.catador, + id_calificacion__id_tecnica=technique + ).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 getExpectedRatings(self, tester: Catador): + num_words = ListaPalabras.objects.get( + tecnica=self.sensorial_session.tecnica, + catador=tester, + es_final=True + ).palabras.all().count() + + num_products = Producto.objects.filter( + id_tecnica=self.sensorial_session.tecnica).count() + + return num_products * num_words diff --git a/tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_rata_controller.py similarity index 57% rename from tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py rename to tecnicas/controllers/views_controller/session_management/monitor/monitor_rata_controller.py index 3337848fe4bc36561fc5968e68c71a5e71f08a28..2629e4dde97037380cadf500456723ba729ba03f 100644 --- a/tecnicas/controllers/views_controller/session_management/monitor_rata_controller.py +++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_rata_controller.py @@ -1,6 +1,3 @@ -from django.http import HttpRequest -from django.shortcuts import render, redirect -from django.urls import reverse from tecnicas.models import Dato, Participacion from tecnicas.controllers import SesionController from .monitor_controller import MonitorController @@ -12,23 +9,10 @@ class MonitorRATAController(MonitorController): self.url_view = "tecnicas/manage_sesions/monitor-sesion.html" self.previus_view = "cata_system:detalles_sesion" - def controllPostFinishSession(self, request: HttpRequest): - self.setContext() - (is_all_end, message) = self.checkAllFinish() - if not is_all_end: - self.context["error"] = message - return render(request, self.url_view, self.context) - response = self.finishSession() - if isinstance(response, dict): - self.context["error"] = response["error"] - return render(request, self.url_view, self.context) - self.context["message"] = message - return redirect(reverse(self.previus_view, kwargs={"session_code": self.sensorial_session.codigo_sesion})) - def checkAllFinish(self): technique = self.sensorial_session.tecnica - expected_ratings_repetition = self.getExpectedRatingsEscalasRapida() + expected_ratings_repetition = self.getExpectedRatings() all_participations = list( Participacion.objects.filter(tecnica=technique)) diff --git a/tecnicas/controllers/views_controller/session_management/monitor/monitor_sort_controller.py b/tecnicas/controllers/views_controller/session_management/monitor/monitor_sort_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..faf9890bf613eb704b8a9556d6f4c54307622891 --- /dev/null +++ b/tecnicas/controllers/views_controller/session_management/monitor/monitor_sort_controller.py @@ -0,0 +1,27 @@ +from tecnicas.models import SesionSensorial +from tecnicas.models import Participacion +from .monitor_controller import MonitorController + + +class MonitorSortController(MonitorController): + def __init__(self, session: SesionSensorial): + super().__init__(session) + self.url_view = "tecnicas/manage_sesions/monitor-session-sort.html" + self.previus_view = "cata_system:detalles_sesion" + + def checkAllFinish(self): + technique = self.sensorial_session.tecnica + + num_participations = Participacion.objects.filter( + tecnica=technique).count() + + if num_participations < technique.limite_catadores: + return (False, "No se ha alcanzado el número máximo de catadores") + + unfinished_participations = Participacion.objects.filter( + tecnica=technique, finalizado=False).count() + + if unfinished_participations > 0: + return (False, "No todos los catadores han finalizado su evaluación") + + return (True, "Puedes finalizar la sesión") \ No newline at end of file diff --git a/tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py b/tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py deleted file mode 100644 index 897fa25cd4bade5a006e973de0818f5e4fbba06d..0000000000000000000000000000000000000000 --- a/tecnicas/controllers/views_controller/sessions_tester/convencional_scales_controller.py +++ /dev/null @@ -1,158 +0,0 @@ -from django.http import HttpRequest -from django.shortcuts import redirect, render -from django.urls import reverse -from tecnicas.models import SesionSensorial, Catador, Participacion, Producto, Calificacion, Palabra -from tecnicas.controllers import PosicionController, CalificacionController, ParticipacionController, PalabrasController, EscalaController, DatoController - - -class ConvencionalScalesController: - context = {} - current_directory = "tecnicas/forms_tester/convencional.html" - previus_directory = "cata_system:catador_init_session" - - def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador): - self.tester = user_tester - self.session = sensorial_session - - def controllGetEscalas(self, request: HttpRequest): - technique = self.session.tecnica - self.participation = Participacion.objects.get( - tecnica=technique, catador=request.user.user_catador) - - ctx = self.context - ctx["session"] = self.session - - # Obtener posiciones y palabras de la técnica - positions_in_order = PosicionController.getPostionsInOrder( - id_order=request.session["id_order"]) - aligned_positions_in_order = sorted( - positions_in_order, key=lambda p: p.posicion) - words = PalabrasController.getWordsInTechnique(technique=technique) - - # Comprobar siguiente posición sin calificar - (next_position, end_products) = CalificacionController.checkPositionWithoutRating( - positions=aligned_positions_in_order, - user_cata=request.user.user_catador, - repetition=technique.repeticion, - technique=technique, - num_words=len(words) - ) - - # Si no hay productos se finaliza la sesion - if end_products: - ParticipacionController.finishSession(self.participation) - params = {"code_sesion": self.session.codigo_sesion} - return redirect(reverse('cata_system:catador_init_session', kwargs=params)) - - # Si devuelve una lista, tomar el primer elemento - if isinstance(next_position, list): - next_position = next_position[0] - - # Producto a calificar ahora - product = next_position.id_producto - ctx["product"] = product - - # Revisar las palabras para calificar - try: - rating = Calificacion.objects.get( - num_repeticion=technique.repeticion, - id_producto=product, - id_tecnica=technique, - id_catador=self.tester - ) - there_rating = True - except Calificacion.DoesNotExist: - there_rating = False - - # Si no hay calificaciones previas, usar todas las palabras - if not there_rating: - ctx["words"] = words - else: - ratings_product = rating.dato_calificacion.all() - # Filtrar palabras que faltan - words_to_use = PalabrasController.getWordsWithoutData( - recoreded_data=ratings_product, - words=words - ) - ctx["words"] = words_to_use - - # Escala y etiquetas relacionadas - scale = EscalaController.getScaleByTechnique(technique=technique) - ctx["scale"] = scale - ctx["type_scale"] = scale.id_tipo_escala.nombre_escala - ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale) - - return render(request, self.current_directory, ctx) - - def controllGetRATA(self, request: HttpRequest): - technique = self.session.tecnica - self.participation = Participacion.objects.get( - tecnica=technique, catador=request.user.user_catador) - - self.context["session"] = self.session - - products_in_technique = Producto.objects.filter(id_tecnica=technique) - - words = PalabrasController.getWordsInTechnique(technique=technique) - - use_product: Producto = None - use_words: list[Palabra] = None - - # Revisamos el producto que le falten calificaciones - for current_product in products_in_technique: - try: - rating = Calificacion.objects.get( - num_repeticion=technique.repeticion, - id_producto=current_product, - id_tecnica=technique, - id_catador=self.tester - ) - there_rating = True - except Calificacion.DoesNotExist: - there_rating = False - - # Si no hay calificacion mandamos el producto actual y todas la palabras - if not there_rating: - use_product = current_product - use_words = words - break - - # Obtener los datos asociados para la calificacion para ver que palabras quedan por calificar - recoreded_data = rating.dato_calificacion.all() - - if not recoreded_data: - # Si no hay datos entonces devolver el producto con todas las palabras - use_product = current_product - use_words = words - break - else: - words_to_use = PalabrasController.getWordsWithoutData( - recoreded_data=recoreded_data, words=words) - - # Si quedan palabras por calificar mandar las palabras con el producto - if not isinstance(words_to_use, dict) and words_to_use: - use_product = current_product - use_words = words_to_use - break - - # Si no hay producto que falta por calificar finalizar sesion para el Catador - if not use_product: - updated_participation = ParticipacionController.finishSession( - self.participation) - params = { - "code_sesion": self.session.codigo_sesion - } - return redirect(reverse(self.previus_directory, kwargs=params)) - - self.context["product"] = use_product - self.context["words"] = use_words - - # Agregar informacion de la escala - scale = EscalaController.getScaleByTechnique(technique=technique) - self.context["scale"] = scale - self.context["type_scale"] = scale.id_tipo_escala.nombre_escala - - use_tags = EscalaController.getRelatedTagsInScale(scale=scale) - self.context["tags"] = use_tags - - return render(request, self.current_directory, self.context) diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session_tester_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_controller.py similarity index 51% rename from tecnicas/controllers/views_controller/sessions_tester/init_session_tester_controller.py rename to tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_controller.py index 29102a123cf00bdd6387553378bd3eef0f915724..3aae41371be88f624386caa5841f8277d6495eed 100644 --- a/tecnicas/controllers/views_controller/sessions_tester/init_session_tester_controller.py +++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_controller.py @@ -1,96 +1,23 @@ -from django.db import transaction from django.http import HttpRequest from django.shortcuts import render, redirect from django.urls import reverse -from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, Calificacion, EsVocabulario, Dato -from tecnicas.controllers import ParticipacionController +from django.db import transaction +from tecnicas.models import Catador, SesionSensorial, Orden, Participacion, Producto, EsAtributo, EsVocabulario, Dato, ListaPalabras from tecnicas.utils import controller_error, shuffleArray -class InitSessionTesterController(): +class InitSessionController(): tester: Catador session: SesionSensorial order: Orden | dict - current_direction = "tecnicas/forms_tester/init_session.html" + current_direction: str + current_direction = "tecnicas/forms_tester/init_scales_test.html" escalas_direction = "cata_system:session_convencional" def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador): self.tester = user_tester self.session = sensorial_session - def controllGetEscalas(self, request: HttpRequest): - context = { - "session": self.session, - "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica - } - - order = self.checkAndAssignOrder() - if isinstance(order, dict): - context["error"] = order["error"] - return render(request, self.current_direction, context) - - is_end = self.isEndedSessionEscalas() - - request.session["id_order"] = order.id - context["has_ended"] = is_end - - if is_end: - context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" - - if "error" in request.GET: - context["error"] = request.GET["error"] - - return render(request, self.current_direction, context) - - def controllPostEscalas(self, request: HttpRequest): - context = { - "session": self.session, - "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica - } - - if request.POST["action"] == "start_posting": - parameters = { - "code_sesion": self.session.codigo_sesion - } - - is_end = self.isEndedSessionEscalas() - if is_end: - context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" - return render(request, self.current_direction, context) - - update_participation = ParticipacionController.enterSession( - tester=request.user.user_catador, session=self.session) - if isinstance(update_participation, dict): - context["error"] = update_participation["error"] - return render(request, self.current_direction, context) - - request.session["id_participation"] = update_participation.id - return redirect(reverse(self.escalas_direction, kwargs=parameters)) - elif request.POST["action"] == "exit_session": - response = ParticipacionController.outSession( - tester=request.user.user_catador, session=self.session) - if isinstance(response, dict): - context["error"] = response["error"] - return render(request, self.current_direction, context) - else: - context["error"] = "Acción sin especificar" - return render(request, self.current_direction, context) - - def controllGetRATA(self, request: HttpRequest): - context = { - "session": self.session, - "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica - } - - is_end = self.isEndedSessionEscalas() - - context["has_ended"] = is_end - - if is_end: - context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" - - return render(request, self.current_direction, context) - def assignOrder(self): with transaction.atomic(): orders_without_tester = list(Orden.objects.select_for_update().filter( @@ -117,7 +44,7 @@ class InitSessionTesterController(): return create return self.order_to_assign - def isEndedSessionEscalas(self): + def isEndedSession(self): try: participation = Participacion.objects.get( catador=self.tester, tecnica=self.session.tecnica) diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..2fb4e970f749193123cb785775eed700a21cfa7a --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_escalas_controller.py @@ -0,0 +1,82 @@ +from django.http import HttpRequest +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import Catador, SesionSensorial, Orden +from tecnicas.controllers import ParticipacionController +from .init_session_controller import InitSessionController + + +class InitSessionEscalasController(InitSessionController): + tester: Catador + session: SesionSensorial + order: Orden | dict + + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_direction = "tecnicas/forms_tester/init_scales_test.html" + self.escalas_direction = "cata_system:session_convencional" + self.cata_direction = "cata_system:session_cata" + + def controllGet(self, request: HttpRequest): + context = { + "session": self.session, + "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + + order = self.checkAndAssignOrder() + if isinstance(order, dict): + context["error"] = order["error"] + return render(request, self.current_direction, context) + + is_end = self.isEndedSession() + + request.session["id_order"] = order.id + context["has_ended"] = is_end + + if is_end: + context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" + + if "error" in request.GET: + context["error"] = request.GET["error"] + + return render(request, self.current_direction, context) + + def controllPost(self, request: HttpRequest): + context = { + "session": self.session, + "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + + if request.POST["action"] == "start_posting": + parameters = { + "code_sesion": self.session.codigo_sesion + } + + is_end = self.isEndedSession() + if is_end: + context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" + return render(request, self.current_direction, context) + + update_participation = ParticipacionController.enterSession( + tester=request.user.user_catador, session=self.session) + if isinstance(update_participation, dict): + context["error"] = update_participation["error"] + return render(request, self.current_direction, context) + + request.session["id_participation"] = update_participation.id + + if self.session.tecnica.tipo_tecnica.nombre_tecnica == "cata": + return redirect(reverse(self.cata_direction, kwargs=parameters)) + + return redirect(reverse(self.escalas_direction, kwargs=parameters)) + + elif request.POST["action"] == "exit_session": + response = ParticipacionController.outSession( + tester=request.user.user_catador, session=self.session) + if isinstance(response, dict): + context["error"] = response["error"] + return render(request, self.current_direction, context) + + else: + context["error"] = "Acción sin especificar" + return render(request, self.current_direction, context) diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..42a12b6b23a70a9c690fece551f4f043bf2d61c3 --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_napping_controller.py @@ -0,0 +1,80 @@ +from django.http import HttpRequest +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import Participacion, TecnicaModalidad +from tecnicas.controllers import ParticipacionController +from .init_session_controller import InitSessionController + + +class InitSessionNappingController(InitSessionController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_direction = "tecnicas/forms_tester/init_test_napping.html" + self.napping_direction = "cata_system:session_napping" + + def controllGet(self, request: HttpRequest): + self.context = { + "session": self.session, + "type_technique": "napping", + "has_ended": self.isEndedSession() + } + + self.setStatusSession() + + if "error" in request.GET: + self.context["error"] = request.GET["error"] + + return render(request, self.current_direction, self.context) + + def isEndedSession(self): + return Participacion.objects.get( + tecnica=self.session.tecnica, catador=self.tester).finalizado + + def controllPost(self, request: HttpRequest): + context = { + "session": self.session, + "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + + use_action = request.POST["action"] + + if use_action == "start_posting": + parameters = { + "code_sesion": self.session.codigo_sesion + } + + is_end = self.isEndedSession() + if is_end: + context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" + return render(request, self.current_direction, context) + + update_participation = ParticipacionController.enterSession( + tester=request.user.user_catador, session=self.session) + + if isinstance(update_participation, dict): + context["error"] = update_participation["error"] + return render(request, self.current_direction, context) + + request.session["id_participation"] = update_participation.id + + return redirect(reverse(self.napping_direction, kwargs=parameters)) + + elif use_action == "exit_session": + response = ParticipacionController.outSession( + tester=request.user.user_catador, session=self.session) + if isinstance(response, dict): + context["error"] = response["error"] + return render(request, self.current_direction, context) + + else: + context["error"] = "Acción sin especificar" + return render(request, self.current_direction, context) + + def setStatusSession(self): + technique_mode = TecnicaModalidad.objects.get( + tecnica=self.session.tecnica).modalidad.nombre + + if technique_mode == "sin modalidad": + self.context["status"] = "La sesión usa Napping" + else: + self.context["status"] = f"La sesión usa Napping con modalidad {technique_mode}" diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..299272eb21efb751ecec9362a0f0cbe9b7f324a0 --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_pf_controller.py @@ -0,0 +1,144 @@ +from django.http import HttpRequest +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import Participacion, Producto, Dato, ListaPalabras +from tecnicas.controllers import ParticipacionController +from .init_session_controller import InitSessionController + + +class InitSessionPFController(InitSessionController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_direction = "tecnicas/forms_tester/init_pf_test.html" + self.pf_direction = "cata_system:session_pf" + + def controllGet(self, request: HttpRequest): + context = { + "session": self.session, + "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + + (is_end, message, rep_show) = self.isEndedSession() + + context["has_ended"] = is_end + context["activity"] = message + context["repetition"] = rep_show + + if "error" in request.GET: + context["error"] = request.GET["error"] + + return render(request, self.current_direction, context) + + def controllPost(self, request: HttpRequest): + context = { + "session": self.session, + "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + + action = request.POST["action"] + + if action == "start_posting": + parameters = { + "code_sesion": self.session.codigo_sesion + } + + (is_end, message, rep_show) = self.isEndedSession() + if is_end: + return self.controllGet(request) + + update_participation = ParticipacionController.enterSession( + tester=request.user.user_catador, session=self.session) + if isinstance(update_participation, dict): + context["error"] = update_participation["error"] + return render(request, self.current_direction, context) + + request.session["id_participation"] = update_participation.id + + return redirect(reverse(self.pf_direction, kwargs=parameters)) + + elif action == "exit_session": + response = ParticipacionController.outSession( + tester=request.user.user_catador, session=self.session) + if isinstance(response, dict): + context["error"] = response["error"] + return self.controllGet(request) + + else: + context["error"] = "Acción sin especificar" + return render(request, self.current_direction, context) + + def isEndedSession(self) -> tuple[bool, str, int]: + rep = self.session.tecnica.repeticion + + is_end = False + message = "" + repetitiom_show = 0 + + if rep == 1: + is_end = self.endedSessionMakeList() + message = "Ya has creado la Lista de palabras inicial" if is_end else "Debes crear tu lista de palabras inicial" + repetitiom_show = 0 + elif rep == 2: + is_end = self.endedSessionMakeList() + message = "Ya has creado la Lista de palabras final" if is_end else "Debes crear tu lista de palabras final" + repetitiom_show = 0 + elif rep >= 3: + is_end = self.endedSessionRepetition() + message = "Has finalizado con el proceso de calificación" if is_end else "Debe hacer tu proceso de calificación" + repetitiom_show = rep - 2 + else: + message = "Parece que la repetición es cero, no es posible hacer algo ahora mismo" + + return (is_end, message, repetitiom_show) + + def endedSessionMakeList(self): + try: + return Participacion.objects.get( + catador=self.tester, tecnica=self.session.tecnica).finalizado + except Participacion.DoesNotExist: + print("No se ha encontrado la participación") + return False + + def endedSessionRepetition(self): + try: + participation = Participacion.objects.get( + catador=self.tester, tecnica=self.session.tecnica) + self.session.refresh_from_db() + + # ////////////////////////////////////////////////////////////// # + # + # numero_datos_esperadas = num_productos * num_palabras + # Si numero_datos_esperadas es igual a numero_datos_actuales en la repetcion R + # Ha terminado la repeticion + # + # ////////////////////////////////////////////////////////////// # + + if participation.finalizado: + num_products = Producto.objects.filter( + id_tecnica=self.session.tecnica).count() + + num_words = ListaPalabras.objects.get( + tecnica=self.session.tecnica, + catador=self.tester, + es_final=True + ).palabras.all().count() + + expected_ratings_repetition = num_products * num_words + + technique = self.session.tecnica + num_ratings_now = Dato.objects.filter( + id_calificacion__id_catador=self.tester, + id_calificacion__id_tecnica=technique, + id_calificacion__num_repeticion=technique.repeticion + ).count() + + is_end = num_ratings_now >= expected_ratings_repetition + + return is_end + + else: + return participation.finalizado + + except Participacion.DoesNotExist: + print("No se ha encontrado la participación") + return False diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..405986dbfbd0a93c1b00c61f0b0d2bd7e1068876 --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_rata_controller.py @@ -0,0 +1,24 @@ +from django.http import HttpRequest +from django.shortcuts import render +from .init_session_controller import InitSessionController + + +class InitSessionRATAController(InitSessionController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_direction = "tecnicas/forms_tester/init_scales_test.html" + + def controllGet(self, request: HttpRequest): + context = { + "session": self.session, + "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + + is_end = self.isEndedSession() + + context["has_ended"] = is_end + + if is_end: + context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" + + return render(request, self.current_direction, context) diff --git a/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_sort_controller.py b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_sort_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..7d21f8c5f23157563b2dfbb826118980ac426b2f --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/init_session/init_session_sort_controller.py @@ -0,0 +1,77 @@ +from django.http import HttpRequest +from django.shortcuts import render, redirect +from django.urls import reverse +from tecnicas.models import Participacion +from tecnicas.controllers import ParticipacionController +from .init_session_controller import InitSessionController + + +class InitSessionSortController(InitSessionController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_direction = "tecnicas/forms_tester/init_test_sort.html" + self.sort_direction = "cata_system:session_sort" + + def controllGet(self, request: HttpRequest, error=""): + context = { + "session": self.session, + "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + + is_end = self.isEndedSession() + + context["has_ended"] = is_end + + if is_end: + context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" + + if "error" in request.GET: + context["error"] = request.GET["error"] + + return render(request, self.current_direction, context) + + def isEndedSession(self): + participation = Participacion.objects.get( + catador=self.tester, tecnica=self.session.tecnica) + + return participation.finalizado + + def controllPost(self, request: HttpRequest): + context = { + "session": self.session, + "type_technique": self.session.tecnica.tipo_tecnica.nombre_tecnica + } + + use_action = request.POST["action"] + + if use_action == "start_posting": + parameters = { + "code_sesion": self.session.codigo_sesion + } + + is_end = self.isEndedSession() + if is_end: + context["message"] = "El catador ha terminado de realizar su evaluación, espere instrucciones del presentador" + return render(request, self.current_direction, context) + + update_participation = ParticipacionController.enterSession( + tester=request.user.user_catador, session=self.session) + + if isinstance(update_participation, dict): + context["error"] = update_participation["error"] + return render(request, self.current_direction, context) + + request.session["id_participation"] = update_participation.id + + return redirect(reverse(self.sort_direction, kwargs=parameters)) + + elif use_action == "exit_session": + response = ParticipacionController.outSession( + tester=request.user.user_catador, session=self.session) + if isinstance(response, dict): + context["error"] = response["error"] + return render(request, self.current_direction, context) + + else: + context["error"] = "Acción sin especificar" + return render(request, self.current_direction, context) diff --git a/tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py b/tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py index 452bf5dd9c524e41223a3f78be590e209ae32f64..beec3493c6d6ac2d7590e752cf06306b3c20f0b6 100644 --- a/tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py +++ b/tecnicas/controllers/views_controller/sessions_tester/login_session_tester_controller.py @@ -1,4 +1,4 @@ -from django.http import HttpRequest, JsonResponse +from django.http import HttpRequest from django.shortcuts import render, redirect from django.urls import reverse from django.db import transaction @@ -12,6 +12,7 @@ class LoginSessionTesterController(): taster_participation: Participacion current_direcction = "tecnicas/forms_tester/login_session.html" destinity_direcction = "cata_system:catador_init_session" + context = {} def __init__(self): self.tester = Catador() @@ -28,17 +29,18 @@ class LoginSessionTesterController(): return controller_error("Credenciales inválidas") def validateEntryEscalas(self, request=HttpRequest): - context = {} + self.context = {} if not self.session.activo: - context["error"] = "La sesión no está activa actualmente" - return render(request, self.current_direcction, context) + self.context["error"] = "La sesión no está activa actualmente" + return render(request, self.current_direcction, self.context) if self.session.tecnica.repeticion == 1: try: self.taster_participation = Participacion.objects.get( tecnica=self.session.tecnica, catador=self.tester) - context["error"] = "Usted ya esta dentro de la sesión" - return render(request, self.current_direcction, context) + self.context["error"] = "Usted ya esta dentro de la sesión" + return render(request, self.current_direcction, self.context) + except Participacion.DoesNotExist: with transaction.atomic(): code_session = self.session.codigo_sesion @@ -50,8 +52,8 @@ class LoginSessionTesterController(): 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.context["error"] = "La sesión ha alcanzado el número máximo de catadores" + return render(request, self.current_direcction, self.context) self.taster_participation = Participacion.objects.create( tecnica=self.session.tecnica, @@ -63,21 +65,21 @@ class LoginSessionTesterController(): } return redirect(reverse(self.destinity_direcction, kwargs=params)) else: - context["error"] = "Ya no es posible ingresar a la sesión" - return render(request, self.current_direcction, context) + self.context["error"] = "Ya no es posible ingresar a la sesión" + return render(request, self.current_direcction, self.context) - def validateEntryRATA(self, request: HttpRequest): - context = {} + def validateEntryRataCata(self, request: HttpRequest): + self.context = {} if not self.session.activo: - context["error"] = "La sesión no está activa actualmente" - return render(request, self.current_direcction, context) + self.context["error"] = "La sesión no está activa actualmente" + return render(request, self.current_direcction, self.context) if self.session.tecnica.repeticion <= 1: try: self.taster_participation = Participacion.objects.get( tecnica=self.session.tecnica, catador=self.tester) - context["error"] = "Usted ya esta dentro de la sesión" - return render(request, self.current_direcction, context) + self.context["error"] = "Usted ya esta dentro de la sesión" + return render(request, self.current_direcction, self.context) except Participacion.DoesNotExist: with transaction.atomic(): code_session = self.session.codigo_sesion @@ -95,5 +97,67 @@ class LoginSessionTesterController(): } return redirect(reverse(self.destinity_direcction, kwargs=params)) else: - context["error"] = "Imposible acceder a esta sesión" - return render(request, self.current_direcction, context) + self.context["error"] = "Imposible acceder a esta sesión" + return render(request, self.current_direcction, self.context) + + def validateEntryLimitTesters(self, request: HttpRequest): + self.context = {} + if not self.session.activo: + self.context["error"] = "La sesión no está activa actualmente" + return render(request, self.current_direcction, self.context) + + if self.session.tecnica.repeticion == 1: + return self.entrySessionLimitTesters(request) + + else: + self.context["error"] = "Ya no es posible ingresar a la sesión" + return render(request, self.current_direcction, self.context) + + def validateEntryNapping(self, request: HttpRequest): + self.context = {} + if not self.session.activo: + self.context["error"] = "La sesión no está activa actualmente" + return render(request, self.current_direcction, self.context) + + if self.session.tecnica.repeticion == 0: + return self.entrySessionLimitTesters(request) + + else: + self.context["error"] = "Ya no es posible ingresar a la sesión" + return render(request, self.current_direcction, self.context) + + def entrySessionLimitTesters(self, request: HttpRequest): + try: + self.taster_participation = Participacion.objects.get( + tecnica=self.session.tecnica, catador=self.tester) + self.context["error"] = "Usted ya esta dentro de la sesión" + return render(request, self.current_direcction, self.context) + + except Participacion.DoesNotExist: + try: + 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: + raise ValueError( + "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 + ) + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.destinity_direcction, kwargs=params)) + + except ValueError as e: + self.context["error"] = str(e) + return render(request, self.current_direcction, self.context) diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/general_test_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/general_test_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..ddfc0941a4c6cd166456f1eb4170c232a97d1fbc --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/general_test_controller.py @@ -0,0 +1,28 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from tecnicas.models import SesionSensorial, Catador, Participacion +from tecnicas.controllers import ParticipacionController + + +class GenetalTestController(): + previus_directory = "cata_system:catador_init_session" + context = {} + current_directory: str + + def __init__(self, sensorial_session: SesionSensorial, user_tester: Catador): + self.tester = user_tester + self.session = sensorial_session + + def controllPost(self, request: HttpRequest): + action = request.POST["action"] + + if action == "finish_session": + self.participation = Participacion.objects.get( + tecnica=self.session.tecnica, catador=request.user.user_catador) + ParticipacionController.finishSession(self.participation) + params = {"code_sesion": self.session.codigo_sesion} + return redirect(reverse(self.previus_directory, kwargs=params)) + + else: + return self.controllGet(request, error="Acción no permitida") diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_cata_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_cata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..13f1badfa70504e81dce9a1819170e24031c311c --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_cata_controller.py @@ -0,0 +1,55 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from tecnicas.models import Producto, Participacion, Palabra, Calificacion +from tecnicas.controllers import PalabrasController, ParticipacionController +from .general_test_controller import GenetalTestController + + +class TestCataController(GenetalTestController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_directory = "tecnicas/forms_tester/test_cata.html" + + def controllGet(self, request: HttpRequest): + technique = self.session.tecnica + self.participation = Participacion.objects.get( + tecnica=technique, catador=request.user.user_catador) + + self.context["session"] = self.session + + products_in_technique = Producto.objects.filter(id_tecnica=technique) + + words = PalabrasController.getWordsInTechnique(technique=technique) + + use_product: Producto = None + use_words: list[Palabra] = None + + # Revisamos el producto que le falten calificaciones + for current_product in products_in_technique: + try: + rating = Calificacion.objects.get( + num_repeticion=technique.repeticion, + id_producto=current_product, + id_tecnica=technique, + id_catador=self.tester + ) + except Calificacion.DoesNotExist: + # Si no hay calificacion mandamos el producto actual y todas la palabras + use_product = current_product + use_words = words + break + + # Si no hay producto que falta por calificar finalizar sesion para el Catador + if not use_product: + updated_participation = ParticipacionController.finishSession( + self.participation) + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.previus_directory, kwargs=params)) + + self.context["product"] = use_product + self.context["words"] = use_words + + return render(request, self.current_directory, self.context) diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..80d6d2d5382f4b5544f2eb742f68ad2be96a4bd2 --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py @@ -0,0 +1,242 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from django.db.models import F +from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion, Modalidad, Palabra, GrupoProducto +from tecnicas.forms import ListWordsForm +from tecnicas.utils import noValidTechnique +from tecnicas.controllers import ParticipacionController +from .general_test_controller import GenetalTestController + + +class TestNappingController(GenetalTestController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.napping_test = "tecnicas/forms_tester/test_napping.html" + self.napping_puf_test = "tecnicas/forms_tester/test_napping_puf.html" + self.sort_direction = "tecnicas/forms_tester/test_napping_sort.html" + + def controllGet(self, request: HttpRequest): + technique = self.session.tecnica + + self.participation = Participacion.objects.get( + tecnica=technique, catador=request.user.user_catador) + + # Comprobar que el Catador no haya finalizado + if self.participation.finalizado: + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.previus_directory, kwargs=params)) + + name_mode_activate = TecnicaModalidad.objects.get( + tecnica=technique).modalidad.nombre + + if name_mode_activate == "sin modalidad": + self.context["mode"] = "sin modalidad" + return self.nappingTest(request) + + if name_mode_activate == "perfil ultra flash": + self.context["mode"] = "perfil ultra flash" + return self.nappingPufTest(request) + + if name_mode_activate == "sorting": + self.context["mode"] = "sorting" + return self.nappingSort(request) + + else: + return noValidTechnique( + name_view=self.previus_directory, + query_params={ + "error": f"Trabajando en la modalidad: {name_mode_activate}" + }, + params={ + "code_sesion": self.session.codigo_sesion + } + ) + + def nappingTest(self, request: HttpRequest): + self.context["session"] = self.session + technique = self.session.tecnica + + products_in_technique = Producto.objects.filter(id_tecnica=technique) + self.context["products"] = products_in_technique + + self.setCoordinates() + + return render(request, self.napping_test, self.context) + + def nappingPufTest(self, request: HttpRequest): + maked_previus_napping = TecnicaModalidad.objects.get( + tecnica=self.session.tecnica) + + self.context["maked_napping"] = True if maked_previus_napping else False + self.context["mode"] = "perfil ultra flash" + self.context["form"] = ListWordsForm() + + self.context["session"] = self.session + technique = self.session.tecnica + products_in_technique = Producto.objects.filter(id_tecnica=technique) + self.context["products"] = products_in_technique + self.setCoordinates() + self.setWords() + + return render(request, self.napping_puf_test, self.context) + + def nappingSort(self, request: HttpRequest): + self.context["session"] = self.session + technique = self.session.tecnica + + products_in_technique = Producto.objects.filter(id_tecnica=technique) + self.context["products"] = products_in_technique + + self.context["form"] = ListWordsForm() + self.setCoordinates() + self.setGroups() + + return render(request, self.sort_direction, self.context) + + def setCoordinates(self): + technique = self.session.tecnica + + ratings = Calificacion.objects.filter( + num_repeticion=0, + id_tecnica=technique, + id_catador=self.participation.catador + ) + + data_points = DatoPunto.objects.filter( + calificacion__in=ratings + ).values( + code=F("calificacion__id_producto__codigoProducto"), + px=F("x"), + py=F("y"), + id_product=F("calificacion__id_producto__id") + ) + + self.context["data_points"] = list(data_points) + + def setGroups(self): + technique = self.session.tecnica + + # Get all product groups for this tester + grupos_producto = GrupoProducto.objects.filter( + tecnica=technique, + catador=self.participation.catador + ).prefetch_related('productos', 'palabras') + + groups = [] + for group in grupos_producto: + # Get products in this group + products_list = [] + for product in group.productos.all(): + products_list.append({ + 'id': product.id, + 'codigoProducto': product.codigoProducto + }) + + # Get words for this group + words_list = list(group.palabras.values_list( + 'nombre_palabra', flat=True)) + + groups.append({ + 'id': group.id, + 'products': products_list, + 'words': words_list + }) + + self.context["groups"] = groups + + def setWords(self): + technique = self.session.tecnica + + ratings = Calificacion.objects.filter( + num_repeticion=0, + id_tecnica=technique, + id_catador=self.participation.catador + ).prefetch_related('palabras', 'id_producto') + + words_by_product = {} + for rating in ratings: + product_code = rating.id_producto.codigoProducto + words_list = list(rating.palabras.values_list( + 'nombre_palabra', flat=True)) + if words_list: + words_by_product[product_code] = words_list + + self.context["words_by_product"] = words_by_product + + def controllPost(self, request: HttpRequest): + action = request.POST.get("action") + + if action == "finish_session": + # Get technique and mode + technique = self.session.tecnica + self.participation = Participacion.objects.get( + tecnica=technique, catador=request.user.user_catador) + + name_mode_activate = TecnicaModalidad.objects.get( + tecnica=technique).modalidad.nombre + + # Validate based on mode + validation_error = self.validateSessionCompletion( + technique, name_mode_activate) + + if validation_error: + # Return to the appropriate template with error + if name_mode_activate == "sin modalidad": + return self.nappingTest(request) + elif name_mode_activate == "perfil ultra flash": + return self.nappingPufTest(request) + + # If validation passes, finish the session + ParticipacionController.finishSession(self.participation) + params = {"code_sesion": self.session.codigo_sesion} + return redirect(reverse(self.previus_directory, kwargs=params)) + + # For other actions, call parent's controllPost + return super().controllPost(request) + + def validateSessionCompletion(self, technique, mode_name): + # Get all products in technique + products = Producto.objects.filter(id_tecnica=technique) + product_count = products.count() + + # Get all ratings for this tester + ratings = Calificacion.objects.filter( + num_repeticion=0, + id_tecnica=technique, + id_catador=self.participation.catador + ).select_related('id_producto').prefetch_related('palabras') + + # Check if all products have ratings + if ratings.count() != product_count: + missing_count = product_count - ratings.count() + return f"Faltan {missing_count} producto(s) por evaluar." + + # Check if all ratings have DatoPunto (coordinates) + ratings_with_points = DatoPunto.objects.filter( + calificacion__in=ratings + ).values_list('calificacion_id', flat=True) + + ratings_without_points = ratings.exclude(id__in=ratings_with_points) + if ratings_without_points.exists(): + missing_products = [ + r.id_producto.codigoProducto for r in ratings_without_points + ] + return f"Los siguientes productos no tienen coordenadas: {', '.join(missing_products)}" + + # Additional validation for "perfil ultra flash" mode + if mode_name == "perfil ultra flash": + # Check that each rating has at least one word + ratings_without_words = [] + for rating in ratings: + if rating.palabras.count() < 1: + ratings_without_words.append( + rating.id_producto.codigoProducto) + + if ratings_without_words: + return f"Los siguientes productos deben tener al menos 1 palabra: {', '.join(ratings_without_words)}" + + # All validations passed + return None diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..205bf4abad4e136c0ce90ea15a481294b642cb72 --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_pf_controller.py @@ -0,0 +1,174 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from tecnicas.models import Producto, Participacion, Palabra, Calificacion, ListaPalabras, Dato +from tecnicas.controllers import ParticipacionController, PalabrasController, EscalaController +from tecnicas.forms import ListWordsForm +from .general_test_controller import GenetalTestController + + +class TestPFController(GenetalTestController): + skip_phases = 2 + + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + + def controllGet(self, request: HttpRequest, error=""): + self.participation = Participacion.objects.get( + tecnica=self.session.tecnica, catador=request.user.user_catador) + self.context["session"] = self.session + + if error: + self.context["error"] = error + + rep = self.session.tecnica.repeticion + + if rep == 1: + self.current_directory = "tecnicas/forms_tester/test_pf_list_words.html" + response = self.getFirstPhase(request) + elif rep == 2: + self.current_directory = "tecnicas/forms_tester/test_pf_list_words.html" + response = self.getSecondPhase(request) + elif rep >= 3: + self.current_directory = "tecnicas/forms_tester/test_pf_rating_list.html" + response = self.getRepetitionPhase(request) + else: + response = self.getErrorRepetition(request) + + return response + + def controllPost(self, request: HttpRequest): + action = request.POST["action"] + + if action == "finish_session": + self.participation = Participacion.objects.get( + tecnica=self.session.tecnica, catador=request.user.user_catador) + ParticipacionController.finishSession(self.participation) + params = {"code_sesion": self.session.codigo_sesion} + return redirect(reverse(self.previus_directory, kwargs=params)) + + else: + return self.controllGet(request, error="Acción no permitida") + + def getFirstPhase(self, request: HttpRequest): + self.participation.refresh_from_db() + + if self.participation.finalizado: + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.previus_directory, kwargs=params)) + + self.context["form"] = ListWordsForm() + self.context["initial_phase"] = True + + try: + tester_list = ListaPalabras.objects.get( + tecnica=self.session.tecnica, + catador=request.user.user_catador, + es_final=False + ) + list_words = list(tester_list.palabras.all()) + self.context["words"] = list_words + except ListaPalabras.DoesNotExist: + self.context["words"] = [] + + return render(request, self.current_directory, self.context) + + def getSecondPhase(self, request: HttpRequest): + self.participation.refresh_from_db() + + if self.participation.finalizado: + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.previus_directory, kwargs=params)) + + try: + tester_list = ListaPalabras.objects.get( + tecnica=self.session.tecnica, + catador=request.user.user_catador, + es_final=True + ) + except ListaPalabras.DoesNotExist: + tester_list = ListaPalabras.objects.get( + tecnica=self.session.tecnica, + catador=request.user.user_catador, + es_final=False + ) + + list_words = list(tester_list.palabras.all()) + + self.context["form"] = ListWordsForm() + self.context["initial_phase"] = False + self.context["words"] = list_words + + return render(request, self.current_directory, self.context) + + def getRepetitionPhase(self, request: HttpRequest): + ''' + - Obtener todos los productos que se evaluan en la tecnica + - Obtener todas las palabras de la lista de palabras del catador + - Para cada palabra, comprobar que el numero de Dato sea igual al numero de Productos + - Si no hay datos mandar esa palabra por contexto con todos los productos + - De las palabras que falten tomar la primera y mandarla en el contexto + - Mandar todos los productos por el contexto + Nota: Para esta fase no hay necesidad de mandar una escala, la escala se creara en el cliente + ''' + self.participation.refresh_from_db() + + if self.participation.finalizado: + params = {"code_sesion": self.session.codigo_sesion} + return redirect(reverse(self.previus_directory, kwargs=params)) + + technique = self.session.tecnica + + # Obtener todos los productos que se evaluan en la técnica + products_in_technique = Producto.objects.filter(id_tecnica=technique) + + # Obtener todas las palabras de la lista del catador (preferir lista final) + try: + words = list(ListaPalabras.objects.get( + tecnica=self.session.tecnica, + catador=request.user.user_catador, + es_final=True + ).palabras.all()) + except ListaPalabras.DoesNotExist: + words = [] + + use_word: Palabra = None + + # Revisar que palabra no ha sido calificada en todos los productos + for word in words: + current_num_data = Dato.objects.filter( + id_calificacion__num_repeticion=technique.repeticion, + id_calificacion__id_tecnica=technique, + id_calificacion__id_catador=request.user.user_catador, + id_palabra=word + ).count() + + if not current_num_data: + use_word = word + break + elif current_num_data < len(products_in_technique): + self.context["error"] = "Se ha detectado una inconsistencia en los datos que se deben calificar, algunos productos no han sido calificados" + return render(request, self.current_directory, self.context) + + if not use_word: + self.participation = Participacion.objects.get( + tecnica=self.session.tecnica, catador=request.user.user_catador) + ParticipacionController.finishSession(self.participation) + params = {"code_sesion": self.session.codigo_sesion} + return redirect(reverse(self.previus_directory, kwargs=params)) + + self.context["word"] = use_word + self.context["products"] = products_in_technique + self.context["repetition"] = technique.repeticion - self.skip_phases + + return render(request, self.current_directory, self.context) + + def getErrorRepetition(self, request: HttpRequest): + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.previus_directory, kwargs=params)) diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_rata_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_rata_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..61cfea2fe30e62cdb57ef3c2b7c3e12374f38ffd --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_rata_controller.py @@ -0,0 +1,87 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from tecnicas.models import Participacion, Producto, Calificacion, Palabra +from tecnicas.controllers import ParticipacionController, PalabrasController, EscalaController +from .general_test_controller import GenetalTestController + + +class TestRataController(GenetalTestController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_directory = "tecnicas/forms_tester/test_convencional.html" + + def controllGet(self, request: HttpRequest): + technique = self.session.tecnica + self.participation = Participacion.objects.get( + tecnica=technique, catador=request.user.user_catador) + + self.context["session"] = self.session + + products_in_technique = Producto.objects.filter(id_tecnica=technique) + + words = PalabrasController.getWordsInTechnique(technique=technique) + + use_product: Producto = None + use_words: list[Palabra] = None + + # Revisamos el producto que le falten calificaciones + for current_product in products_in_technique: + try: + rating = Calificacion.objects.get( + num_repeticion=technique.repeticion, + id_producto=current_product, + id_tecnica=technique, + id_catador=self.tester + ) + except Calificacion.DoesNotExist: + # Si no hay calificacion mandamos el producto actual y todas la palabras + use_product = current_product + use_words = words + break + + # Obtener los datos asociados para la calificacion para ver que palabras quedan por calificar + recoreded_data = rating.dato_calificacion.all() + + if not recoreded_data: + # Si no hay datos entonces devolver el producto con todas las palabras + use_product = current_product + use_words = words + break + else: + words_to_use = PalabrasController.getWordsWithoutData( + recoreded_data=recoreded_data, words=words) + + # Si quedan palabras por calificar mandar las palabras con el producto + if not isinstance(words_to_use, dict) and words_to_use: + use_product = current_product + use_words = words_to_use + break + + # Si no hay producto que falta por calificar finalizar sesion para el Catador + if not use_product: + updated_participation = ParticipacionController.finishSession( + self.participation) + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.previus_directory, kwargs=params)) + + self.context["product"] = use_product + self.context["words"] = use_words + + # Agregar informacion de la escala + scale = EscalaController.getScaleByTechnique(technique=technique) + self.context["scale"] = scale + self.context["type_scale"] = scale.id_tipo_escala.nombre_escala + + use_tags = EscalaController.getRelatedTagsInScale(scale=scale) + self.context["tags"] = use_tags + + if self.context["type_scale"] == "continua": + self.context["size_scale"] = { + "max_size": scale.longitud * 100, + "middle_size": (scale.longitud * 100)/2 + } + + return render(request, self.current_directory, self.context) diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_scales_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_scales_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..19985ddd5d666bc206c8f9e2d11f6226d96a2800 --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_scales_controller.py @@ -0,0 +1,87 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from tecnicas.models import SesionSensorial, Catador, Participacion, Producto, Calificacion, Palabra +from tecnicas.controllers import PosicionController, CalificacionController, ParticipacionController, PalabrasController, EscalaController +from .general_test_controller import GenetalTestController + + +class TestScalesController(GenetalTestController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_directory = "tecnicas/forms_tester/test_convencional.html" + + def controllGet(self, request: HttpRequest): + technique = self.session.tecnica + self.participation = Participacion.objects.get( + tecnica=technique, catador=request.user.user_catador) + + ctx = self.context + ctx["session"] = self.session + + # Obtener posiciones y palabras de la técnica + positions_in_order = PosicionController.getPostionsInOrder( + id_order=request.session["id_order"]) + aligned_positions_in_order = sorted( + positions_in_order, key=lambda p: p.posicion) + words = PalabrasController.getWordsInTechnique(technique=technique) + + # Comprobar siguiente posición sin calificar + (next_position, end_products) = CalificacionController.checkPositionWithoutRating( + positions=aligned_positions_in_order, + user_cata=request.user.user_catador, + repetition=technique.repeticion, + technique=technique, + num_words=len(words) + ) + + # Si no hay productos se finaliza la sesion + if end_products: + ParticipacionController.finishSession(self.participation) + params = {"code_sesion": self.session.codigo_sesion} + return redirect(reverse('cata_system:catador_init_session', kwargs=params)) + + # Si devuelve una lista, tomar el primer elemento + if isinstance(next_position, list): + next_position = next_position[0] + + # Producto a calificar ahora + product = next_position.id_producto + ctx["product"] = product + + # Revisar las palabras para calificar + try: + rating = Calificacion.objects.get( + num_repeticion=technique.repeticion, + id_producto=product, + id_tecnica=technique, + id_catador=self.tester + ) + there_rating = True + except Calificacion.DoesNotExist: + there_rating = False + + # Si no hay calificaciones previas, usar todas las palabras + if not there_rating: + ctx["words"] = words + else: + ratings_product = rating.dato_calificacion.all() + # Filtrar palabras que faltan + words_to_use = PalabrasController.getWordsWithoutData( + recoreded_data=ratings_product, + words=words + ) + ctx["words"] = words_to_use + + # Escala y etiquetas relacionadas + scale = EscalaController.getScaleByTechnique(technique=technique) + ctx["scale"] = scale + ctx["type_scale"] = scale.id_tipo_escala.nombre_escala + ctx["tags"] = EscalaController.getRelatedTagsInScale(scale=scale) + if ctx["type_scale"] == "continua": + ctx["size_scale"] = { + "max_size": scale.longitud * 100, + "middle_size": (scale.longitud * 100)/2 + } + + return render(request, self.current_directory, ctx) diff --git a/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_sort_controller.py b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_sort_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..f6095a7b4c7e6adaccf7a677db9e62908cf80719 --- /dev/null +++ b/tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_sort_controller.py @@ -0,0 +1,49 @@ +from django.http import HttpRequest +from django.shortcuts import redirect, render +from django.urls import reverse +from tecnicas.models import Participacion, Producto, Calificacion, Palabra, GrupoProducto +from tecnicas.controllers import ParticipacionController, PalabrasController, EscalaController +from tecnicas.forms import ListWordsForm +from .general_test_controller import GenetalTestController + + +class TestSortController(GenetalTestController): + def __init__(self, sensorial_session, user_tester): + super().__init__(sensorial_session, user_tester) + self.current_directory = "tecnicas/forms_tester/test_sort.html" + + def controllGet(self, request: HttpRequest): + ''' + Objetivo: Entregar al cliente los grupos de productos guardados hechos por el catador en una lista, de lo contrario solo mandar una lista vacia + - Comprobar que el Catador aun no finalice su la sesion + - Obtener todos los productos en la tecnica + - Obtener todos los grupos creadas por el usuario + - Si hay grupos, cada item de la lista a mandar debe incluir los productos asociados al grupo como las palabras que describen al grupo + - Si no hay grupos, solo mandar una lista vacia + - Mandar la lista de grupos como la lista de productos + - Agregar el formulario para describir los grupo + ''' + self.context["session"] = self.session + technique = self.session.tecnica + + self.participation = Participacion.objects.get( + tecnica=technique, catador=request.user.user_catador) + + # Comprobar que el Catador no haya finalizado + if self.participation.finalizado: + params = { + "code_sesion": self.session.codigo_sesion + } + return redirect(reverse(self.previus_directory, kwargs=params)) + + products_in_technique = Producto.objects.filter(id_tecnica=technique) + self.context["products"] = products_in_technique + + grups_products = GrupoProducto.objects.filter( + tecnica=technique, catador=request.user.user_catador) + + self.context["grups_products"] = grups_products or [] + + self.context["form_word"] = ListWordsForm() + + return render(request, self.current_directory, self.context) diff --git a/tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py b/tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py index bf86d76cac1508fcecb9a9933aee004a768f864a..15af699b6a7dbf1efcea7a8657294bf7b3fb04e4 100644 --- a/tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py +++ b/tecnicas/controllers/views_controller/vocabulary_manage/create_vocabulary_controller.py @@ -19,13 +19,6 @@ class CreateVocabularyController(): "form_word": WordForm(), "words": [] } - - if "name_voca" in request.GET: - current = Vocabulario.objects.get( - nombre_vocabulario=request.GET["name_voca"]) - self.context["name_vacabulary"] = current.nombre_vocabulario - self.context["words"] = current.palabras.all() - return render(request, self.current_url, self.context) def controllPost(self, request: HttpRequest): @@ -39,72 +32,28 @@ class CreateVocabularyController(): return render(request, self.current_url, self.context) new_vocabulary_name = request.POST.get("nombre_vocabulario").strip() - is_update = request.POST.get("is_update") - - print(is_update) - - if is_update: - if "original_name" not in request.POST: - self.context["error"] = "Nombre original de vocabulario requerido" - return render(request, self.current_url, self.context) - original_name = request.POST["original_name"].strip() - - if original_name != new_vocabulary_name: - if Vocabulario.objects.filter(nombre_vocabulario=new_vocabulary_name).exists(): - self.context["error"] = "Ya existe un vocabulario con el nombre pasado" - return render(request, self.current_url, self.context) + try: + new_vocababulary = Vocabulario.objects.create( + nombre_vocabulario=new_vocabulary_name) + except IntegrityError: + self.context["error"] = "Ya existe un vocabulario con ese nombre" + return render(request, self.current_url, self.context) + words_json = request.POST.get("words", "") + if words_json: try: - current_vocababulary = Vocabulario.objects.get( - nombre_vocabulario=original_name) - except Vocabulario.DoesNotExist: - self.context["error"] = "No existe un vocabulario con ese nombre" - return render(request, self.current_url, self.context) - - words_json = request.POST.get("words", "") - if words_json: - try: - words_list = json.loads(words_json) + words_list = json.loads(words_json) - ids = [int(w.get("id", 0)) - for w in words_list if str(w.get("id", "")).isdigit()] + ids = [int(w.get("id", 0)) + for w in words_list if str(w.get("id", "")).isdigit()] - words = Palabra.objects.filter(id__in=ids) + words = Palabra.objects.filter(id__in=ids) - current_vocababulary.palabras.set(words) - current_vocababulary.nombre_vocabulario = new_vocabulary_name - current_vocababulary.save() - except (json.JSONDecodeError, ValueError): - self.context["error"] = 'Ocurrió un error al revisar las palabras, revise “Ver vocabularios”, para reasignar las palabras' - return render(request, self.current_url, self.context) - - self.context["message"] = 'Vocabulario creado con éxito, puedes revisarlo en "Ver vocabularios"' - return render(request, self.current_url, self.context) - elif not is_update: - try: - new_vocababulary = Vocabulario.objects.create( - nombre_vocabulario=new_vocabulary_name) - except IntegrityError: - self.context["error"] = "Ya existe un vocabulario con ese nombre" + new_vocababulary.palabras.add(*words) + except (json.JSONDecodeError, ValueError): + self.context["error"] = 'Ocurrió un error al revisar las palabras, revise “Ver vocabularios”, para reasignar las palabras' return render(request, self.current_url, self.context) - words_json = request.POST.get("words", "") - if words_json: - try: - words_list = json.loads(words_json) - - ids = [int(w.get("id", 0)) - for w in words_list if str(w.get("id", "")).isdigit()] - - words = Palabra.objects.filter(id__in=ids) - - new_vocababulary.palabras.add(*words) - except (json.JSONDecodeError, ValueError): - self.context["error"] = 'Ocurrió un error al revisar las palabras, revise “Ver vocabularios”, para reasignar las palabras' - return render(request, self.current_url, self.context) - - self.context["message"] = 'Vocabulario actualziado con éxito, puedes revisarlo en "Ver vocabularios"' - return render(request, self.current_url, self.context) - else: - pass + self.context["message"] = 'Vocabulario creado con éxito, puedes revisarlo en "Ver vocabularios"' + return render(request, self.current_url, self.context) diff --git a/tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py b/tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py index 6b8daeaa11b4fa7abfa5259a2182a0c015f189c4..985b27972e0f33e5d72b47d4d394e2e06aa4bde0 100644 --- a/tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py +++ b/tecnicas/controllers/views_controller/vocabulary_manage/list_vocabulary_controller.py @@ -40,7 +40,7 @@ class ListVocabularyController(): return controller_error("índice inválido") if not vocabularies_in_page.object_list: - return controller_error("Sin registros de Participaciones") + return controller_error("Sin registros de Vocabularios") current_page = vocabularies_in_page.number is_last_page = not current_page < paginator.num_pages diff --git a/tecnicas/controllers/views_controller/vocabulary_manage/view_vocabulary_controller.py b/tecnicas/controllers/views_controller/vocabulary_manage/view_vocabulary_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..100d6bb35bed9695530a130ccdfdbb029451b251 --- /dev/null +++ b/tecnicas/controllers/views_controller/vocabulary_manage/view_vocabulary_controller.py @@ -0,0 +1,28 @@ +from django.shortcuts import render +from django.http import HttpRequest +from tecnicas.models import Vocabulario + + +class ViewVocabularyController(): + context = {} + current_url = "tecnicas/manage_vocabulary/view-vocabulary.html" + + def __init__(self): + self.context = {} + + def controllGet(self, request: HttpRequest, nombre_vocabulario: str): + try: + vocabulary = Vocabulario.objects.get( + nombre_vocabulario=nombre_vocabulario) + + self.context = { + "vocabulary": vocabulary, + "words": vocabulary.palabras.all().order_by('nombre_palabra') + } + + except Vocabulario.DoesNotExist: + self.context = { + "error": f"No existe un vocabulario con el nombre '{nombre_vocabulario}'" + } + + return render(request, self.current_url, self.context) diff --git a/tecnicas/forms/__init__.py b/tecnicas/forms/__init__.py index 75282f742f8f495728d7adf450e5403b5fb073a3..1add46c25cd8a274807116652797d50b80795081 100644 --- a/tecnicas/forms/__init__.py +++ b/tecnicas/forms/__init__.py @@ -1,9 +1,14 @@ from .create_session.sesion_basic_form import SesionBasicForm from .create_session.sesiob_basic_cata_form import SesionBasicCATAForm +from .create_session.sesion_basic_pf_form import SesionBasicPFForm from .create_session.sesion_tags_form import SesionTagsForm +from .create_session.sesion_basic_sort_form import SesionBasicSortForm +from .create_session.sesion_basic_napping import SesionBasicNappingForm from .etiqueta_form import EtiquetaForm from .codes_form import CodesForm from .catador_form import CatadorForm from .word_form import WordForm from .vocabulary_select import VocabularioSelectForm + +from .list_words_form import ListWordsForm diff --git a/tecnicas/forms/catador_form.py b/tecnicas/forms/catador_form.py index 465abd0de2e2ee7c2a3d976a8cff028dfde3340f..55010c735817656e3cb635af332029ef7a706fa0 100644 --- a/tecnicas/forms/catador_form.py +++ b/tecnicas/forms/catador_form.py @@ -138,8 +138,6 @@ class CatadorForm(forms.Form): 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( diff --git a/tecnicas/forms/codes_form.py b/tecnicas/forms/codes_form.py index 5d3a1c0998a7d4c13a856fb2770945f8be2067c5..15b723d6b6e1beb2d83ab9a02d687141acba68d7 100644 --- a/tecnicas/forms/codes_form.py +++ b/tecnicas/forms/codes_form.py @@ -9,3 +9,11 @@ class CodesForm(forms.Form): 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" })) + + def clean(self): + cleaned_data = super().clean() + # Convert all product codes to uppercase + for field_name, value in cleaned_data.items(): + if field_name.startswith('producto_') and value: + cleaned_data[field_name] = value.upper() + return cleaned_data diff --git a/tecnicas/forms/create_session/sesiob_basic_cata_form.py b/tecnicas/forms/create_session/sesiob_basic_cata_form.py index b578857573b1257f2dc2dc27b36d488def2f559a..4a58d9be5dcbbe55aff7bda0b2f684cce657f293 100644 --- a/tecnicas/forms/create_session/sesiob_basic_cata_form.py +++ b/tecnicas/forms/create_session/sesiob_basic_cata_form.py @@ -22,6 +22,11 @@ class SesionBasicCATAForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['estilo_palabras'] = forms.ModelChoiceField(queryset=EstiloPalabra.objects.all(), widget=forms.RadioSelect(attrs={ + options = [ + ("atributos", "atributos"), + ("vocabulario", "vocabulario") + ] + + self.fields['estilo_palabras'] = forms.ChoiceField(choices=options, 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=EstiloPalabra.objects.first()) + }), required=True, initial=options[0]) diff --git a/tecnicas/forms/create_session/sesion_basic_form.py b/tecnicas/forms/create_session/sesion_basic_form.py index 34fef6e39575562583ac21e8e470be0eeb7874af..1fd90dcac0ce54d36a342d3af94080f08aa5da47 100644 --- a/tecnicas/forms/create_session/sesion_basic_form.py +++ b/tecnicas/forms/create_session/sesion_basic_form.py @@ -38,9 +38,14 @@ class SesionBasicForm(forms.Form): if initial_conf is None: initial_conf = {} - self.fields['estilo_palabras'] = forms.ModelChoiceField(queryset=EstiloPalabra.objects.all(), widget=forms.RadioSelect(attrs={ + options = [ + ("atributos", "atributos"), + ("vocabulario", "vocabulario") + ] + + self.fields['estilo_palabras'] = forms.ChoiceField(choices=options, 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=EstiloPalabra.objects.first()) + }), required=True, initial=options[0]) 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", diff --git a/tecnicas/forms/create_session/sesion_basic_napping.py b/tecnicas/forms/create_session/sesion_basic_napping.py new file mode 100644 index 0000000000000000000000000000000000000000..5a6e76ef63428dfd50daaa0e4c4c2be9395a6af3 --- /dev/null +++ b/tecnicas/forms/create_session/sesion_basic_napping.py @@ -0,0 +1,38 @@ +from django import forms +from tecnicas.models import Modalidad + + +class SesionBasicNappingForm(forms.Form): + nombre_sesion = forms.CharField(max_length=255, widget=forms.TextInput(attrs={ + "class": "bg-surface-ligt border-b-1 text-center w-full p-1", + "name": "nombre_sesion", + "placeholder": "Ej. Mermelada de mango picante" + }), required=False) + + numero_productos = forms.IntegerField(widget=forms.NumberInput(attrs={ + "class": "bg-surface-ligt p-1 border-b-1 text-center w-full", + "placeholder": "Solo números" + }), required=True) + + numero_catadores = forms.IntegerField(widget=forms.NumberInput(attrs={ + "class": "bg-surface-ligt p-1 border-b-1 text-center w-full", + "placeholder": "Solo números" + }), required=True) + + 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, **kwargs): + super(SesionBasicNappingForm, self).__init__(*args, **kwargs) + names_mod = [ + ("sin modalidad", "sin modalidad"), + ("sorting", "sorting"), + ("perfil ultra flash", "perfil ultra flash") + ] + + self.fields['modalidad'] = forms.CharField(widget=forms.RadioSelect(choices=names_mod, attrs={ + "class": "radio radio-lg radio-info", + "placeholder": "Seleccione una modalidad", + }), required=True, initial=names_mod[0]) diff --git a/tecnicas/forms/create_session/sesion_basic_pf_form.py b/tecnicas/forms/create_session/sesion_basic_pf_form.py new file mode 100644 index 0000000000000000000000000000000000000000..d1609d4b8e95a3dc7cc405ca1b095f2fc7420ff2 --- /dev/null +++ b/tecnicas/forms/create_session/sesion_basic_pf_form.py @@ -0,0 +1,29 @@ +from django import forms + + +class SesionBasicPFForm(forms.Form): + nombre_sesion = forms.CharField(max_length=255, widget=forms.TextInput(attrs={ + "class": "bg-surface-ligt border-b-1 text-center w-full p-1", + "name": "nombre_sesion", + "placeholder": "Ej. Mermelada de mango picante" + }), required=False) + + numero_productos = forms.IntegerField(widget=forms.NumberInput(attrs={ + "class": "bg-surface-ligt p-1 border-b-1 text-center w-full", + "placeholder": "Solo números" + }), required=True) + + numero_catadores = forms.IntegerField(widget=forms.NumberInput(attrs={ + "class": "bg-surface-ligt p-1 border-b-1 text-center w-full", + "placeholder": "Solo números" + }), required=True) + + numero_repeticiones = forms.IntegerField(widget=forms.NumberInput(attrs={ + "class": "bg-surface-ligt p-1 border-b-1 text-center w-full", + "placeholder": "Solo números" + }), required=True) + + 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) diff --git a/tecnicas/forms/create_session/sesion_basic_sort_form.py b/tecnicas/forms/create_session/sesion_basic_sort_form.py new file mode 100644 index 0000000000000000000000000000000000000000..bb2f0c70775cd838a064c4670b04a2ab587d5fce --- /dev/null +++ b/tecnicas/forms/create_session/sesion_basic_sort_form.py @@ -0,0 +1,24 @@ +from django import forms + + +class SesionBasicSortForm(forms.Form): + nombre_sesion = forms.CharField(max_length=255, widget=forms.TextInput(attrs={ + "class": "bg-surface-ligt border-b-1 text-center w-full p-1", + "name": "nombre_sesion", + "placeholder": "Ej. Mermelada de mango picante" + }), required=False) + + numero_productos = forms.IntegerField(widget=forms.NumberInput(attrs={ + "class": "bg-surface-ligt p-1 border-b-1 text-center w-full", + "placeholder": "Solo números" + }), required=True) + + numero_catadores = forms.IntegerField(widget=forms.NumberInput(attrs={ + "class": "bg-surface-ligt p-1 border-b-1 text-center w-full", + "placeholder": "Solo números" + }), required=True) + + 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) diff --git a/tecnicas/forms/list_words_form.py b/tecnicas/forms/list_words_form.py new file mode 100644 index 0000000000000000000000000000000000000000..ed4e3dab52d727ae24e51497c499452abf3c1775 --- /dev/null +++ b/tecnicas/forms/list_words_form.py @@ -0,0 +1,49 @@ +from django import forms +import re + + +class ListWordsForm(forms.Form): + regex = re.compile(r'^[a-zñ]{3,}$') + + def __init__(self, *args, new_words=None, **kwargs): + super().__init__(*args, **kwargs) + + if not new_words: + self.fields['nombre_palabra'] = forms.CharField( + label='Nombre de la palabra', + max_length=255, + widget=forms.TextInput(attrs={ + 'placeholder': 'Escribe una palabra', + 'class': 'cts-input-list-word bg-surface-ligt p-1 border-b-1 text-center w-full', + 'pattern': self.regex.pattern, + 'title': 'Solo letras minúsculas y la letra ñ, no poner acentos. No se permiten mayúsculas ni caracteres especiales. Mínimo 3 letras', + }) + ) + else: + for index, name in enumerate(new_words, start=1): + self.fields[f'palabra_{index}'] = forms.CharField( + label=f'Palabra {index}', + max_length=255, + initial=name, + widget=forms.TextInput(attrs={ + 'placeholder': 'Escribe una palabra', + 'class': 'cts-input-list-word bg-surface-ligt p-1 border-b-1 text-center w-full', + 'pattern': self.regex.pattern, + 'title': 'Solo letras minúsculas y la letra ñ. No se permiten mayúsculas ni caracteres especiales.' + }) + ) + + def clean(self): + cleaned_data = super().clean() + errores = [] + + for field_name, value in cleaned_data.items(): + if not self.regex.match(value): + errores.append(f"'{value}' contiene caracteres no permitidos.") + elif len(value) > 255: + errores.append(f"'{value}' excede los 255 caracteres.") + + if errores: + raise forms.ValidationError(errores) + + return cleaned_data diff --git a/tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py b/tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py new file mode 100644 index 0000000000000000000000000000000000000000..83aaaeede2816bf72d0a15a16da7cfdf778662dc --- /dev/null +++ b/tecnicas/migrations/0022_alter_sesionsensorial_codigo_sesion.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.1 on 2025-11-12 20:16 + +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0021_rename_nomre_vocabulario_vocabulario_nombre_vocabulario_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + ] diff --git a/tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py b/tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py new file mode 100644 index 0000000000000000000000000000000000000000..2dccc7f0158c944fe9183a7ccdde5f6e94615a81 --- /dev/null +++ b/tecnicas/migrations/0023_alter_sesionsensorial_codigo_sesion_listapalabras.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.1 on 2025-11-12 20:24 + +import django.db.models.deletion +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0022_alter_sesionsensorial_codigo_sesion'), + ] + + operations = [ + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + migrations.CreateModel( + name='ListaPalabras', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('es_final', models.BooleanField(default=False)), + ('catador', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='catador_lista', to='tecnicas.catador')), + ('palabras', models.ManyToManyField(related_name='lista_palabras', to='tecnicas.palabra')), + ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_lista', to='tecnicas.tecnica')), + ], + ), + ] diff --git a/tecnicas/migrations/0024_alter_sesionsensorial_codigo_sesion_grupoproducto.py b/tecnicas/migrations/0024_alter_sesionsensorial_codigo_sesion_grupoproducto.py new file mode 100644 index 0000000000000000000000000000000000000000..0504ee42d9cada10afede1de43c835e2ff75b8ae --- /dev/null +++ b/tecnicas/migrations/0024_alter_sesionsensorial_codigo_sesion_grupoproducto.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.1 on 2025-11-25 01:18 + +import django.db.models.deletion +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0023_alter_sesionsensorial_codigo_sesion_listapalabras'), + ] + + operations = [ + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + migrations.CreateModel( + name='GrupoProducto', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('catador', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='catador_grupo_producto', to='tecnicas.catador')), + ('palabras', models.ManyToManyField(related_name='grupo_producto', to='tecnicas.palabra')), + ('productos', models.ManyToManyField(related_name='grupo_producto', to='tecnicas.producto')), + ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_grupo_producto', to='tecnicas.tecnica')), + ], + ), + ] diff --git a/tecnicas/migrations/0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more.py b/tecnicas/migrations/0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..a33121db5d3c4990e02c52c67d47ad5de57ffc2b --- /dev/null +++ b/tecnicas/migrations/0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.1 on 2025-11-28 19:09 + +import django.db.models.deletion +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0024_alter_sesionsensorial_codigo_sesion_grupoproducto'), + ] + + operations = [ + migrations.CreateModel( + name='Modalidad', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nombre', models.CharField(max_length=255)), + ], + ), + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + migrations.CreateModel( + name='TecnicaModalidad', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('usado', models.BooleanField(default=False)), + ('modalidad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modalidad_tecnica', to='tecnicas.modalidad')), + ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_modalidad', to='tecnicas.tecnica')), + ], + ), + ] diff --git a/tecnicas/migrations/0026_tecnica_modalidad_and_more.py b/tecnicas/migrations/0026_tecnica_modalidad_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..cbdf7e2c5ac09b4c0ab8c933e7c57acdc1fd476f --- /dev/null +++ b/tecnicas/migrations/0026_tecnica_modalidad_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.1 on 2025-11-28 19:39 + +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0025_modalidad_alter_sesionsensorial_codigo_sesion_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='tecnica', + name='modalidad', + field=models.ManyToManyField(related_name='modalidad_tecnica', to='tecnicas.modalidad'), + ), + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + migrations.DeleteModel( + name='TecnicaModalidad', + ), + ] diff --git a/tecnicas/migrations/0027_alter_sesionsensorial_codigo_sesion_and_more.py b/tecnicas/migrations/0027_alter_sesionsensorial_codigo_sesion_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..c762483c1bdbed1d21cab7a5c172773627d049f1 --- /dev/null +++ b/tecnicas/migrations/0027_alter_sesionsensorial_codigo_sesion_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.1 on 2025-11-28 19:59 + +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0026_tecnica_modalidad_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='tecnica', + name='modalidad', + field=models.ManyToManyField(blank=True, null=True, related_name='modalidad_tecnica', to='tecnicas.modalidad'), + ), + ] diff --git a/tecnicas/migrations/0028_alter_sesionsensorial_codigo_sesion_and_more.py b/tecnicas/migrations/0028_alter_sesionsensorial_codigo_sesion_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..21d02655ac3c77976799d744ece7d7afe374b0b3 --- /dev/null +++ b/tecnicas/migrations/0028_alter_sesionsensorial_codigo_sesion_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.1 on 2025-11-28 20:10 + +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0027_alter_sesionsensorial_codigo_sesion_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='tecnica', + name='modalidad', + field=models.ManyToManyField(blank=True, related_name='modalidad_tecnica', to='tecnicas.modalidad'), + ), + ] diff --git a/tecnicas/migrations/0029_remove_tecnica_modalidad_and_more.py b/tecnicas/migrations/0029_remove_tecnica_modalidad_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..a6b02511e78df6d82c26aa79239e2d32d38e3bf6 --- /dev/null +++ b/tecnicas/migrations/0029_remove_tecnica_modalidad_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.1 on 2025-11-28 21:48 + +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0028_alter_sesionsensorial_codigo_sesion_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='tecnica', + name='modalidad', + ), + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + ] diff --git a/tecnicas/migrations/0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad.py b/tecnicas/migrations/0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad.py new file mode 100644 index 0000000000000000000000000000000000000000..cee3160ac6460265bca627a76c20212b58412d94 --- /dev/null +++ b/tecnicas/migrations/0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.1 on 2025-11-28 21:54 + +import django.db.models.deletion +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0029_remove_tecnica_modalidad_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + migrations.CreateModel( + name='TecnicaModalidad', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('usando', models.BooleanField(default=False)), + ('modalidad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modalidad_tecnica', to='tecnicas.modalidad')), + ('tecnica', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tecnica_modalidad', to='tecnicas.tecnica')), + ], + ), + ] diff --git a/tecnicas/migrations/0031_calificacion_palabras_and_more.py b/tecnicas/migrations/0031_calificacion_palabras_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..babb767f1ce8d476d009b2eccb3c5913fdb9bf38 --- /dev/null +++ b/tecnicas/migrations/0031_calificacion_palabras_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.1 on 2025-11-29 03:05 + +import django.db.models.deletion +import shortuuid.main +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tecnicas', '0030_alter_sesionsensorial_codigo_sesion_tecnicamodalidad'), + ] + + operations = [ + migrations.AddField( + model_name='calificacion', + name='palabras', + field=models.ManyToManyField(blank=True, related_name='calificacion_palabras', to='tecnicas.palabra'), + ), + migrations.AlterField( + model_name='sesionsensorial', + name='codigo_sesion', + field=models.CharField(default=shortuuid.main.ShortUUID.uuid, editable=False, max_length=22, primary_key=True, serialize=False), + ), + migrations.CreateModel( + name='DatoPunto', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('x', models.FloatField(default=0)), + ('y', models.FloatField(default=0)), + ('calificacion', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='data_punto', to='tecnicas.calificacion')), + ], + ), + ] diff --git a/tecnicas/models/__init__.py b/tecnicas/models/__init__.py index 769e8f584dac610a81d03a42491d7ffa0a0974a7..c30014a8d0710168865efad2299b3c835c2c31ba 100644 --- a/tecnicas/models/__init__.py +++ b/tecnicas/models/__init__.py @@ -3,6 +3,7 @@ from .etiqueta import Etiqueta from .tipo_escala import TipoEscala from .tipo_tecnica import TipoTecnica from .estilo_palabra import EstiloPalabra +from .modalidad import Modalidad from .presentador import Presentador from .catador import Catador @@ -27,4 +28,9 @@ from .dato_valor import ValorBooleano from .orden import Orden from .orden import Posicion -from .participacion import Participacion \ No newline at end of file +from .participacion import Participacion + +from .lista_palabras import ListaPalabras +from .grupo_producto import GrupoProducto +from .tecnica_modalidad import TecnicaModalidad +from .dato_punto import DatoPunto \ No newline at end of file diff --git a/tecnicas/models/calificacion.py b/tecnicas/models/calificacion.py index b2ac5d4189b7ee41aeb5739093b6c59e20ed1ae8..f73d7c08fdc839a060b4879868d60df0adfc7d61 100644 --- a/tecnicas/models/calificacion.py +++ b/tecnicas/models/calificacion.py @@ -3,12 +3,20 @@ from django.db import models from .producto import Producto from .tecnica import Tecnica from .catador import Catador +from .palabra import Palabra + class Calificacion(models.Model): num_repeticion = models.IntegerField() - id_producto = models.ForeignKey(Producto, on_delete=models.CASCADE, related_name="calificacion_producto") - id_tecnica = models.ForeignKey(Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica") - id_catador = models.ForeignKey(Catador, on_delete=models.CASCADE, related_name="calificacion_catador") + id_producto = models.ForeignKey( + Producto, on_delete=models.CASCADE, related_name="calificacion_producto") + id_tecnica = models.ForeignKey( + Tecnica, on_delete=models.CASCADE, related_name="calificacion_tecnica") + id_catador = models.ForeignKey( + Catador, on_delete=models.CASCADE, related_name="calificacion_catador") + + palabras = models.ManyToManyField( + Palabra, related_name="calificacion_palabras", blank=True) def __str__(self): - return f"{self.id} - {self.id_tecnica.sesion_tecnica} - {self.num_repeticion} - {self.id_catador.user.username}" \ No newline at end of file + return f"{self.id} - {self.id_tecnica.sesion_tecnica} - {self.num_repeticion} - {self.id_catador.user.username}" diff --git a/tecnicas/models/dato_punto.py b/tecnicas/models/dato_punto.py new file mode 100644 index 0000000000000000000000000000000000000000..f0435c8ac60d31da0f6e6735b00c8fc4b47b0561 --- /dev/null +++ b/tecnicas/models/dato_punto.py @@ -0,0 +1,12 @@ +from django.db import models +from .calificacion import Calificacion + + +class DatoPunto(models.Model): + x = models.FloatField(null=False, default=0) + y = models.FloatField(null=False, default=0) + calificacion = models.ForeignKey( + Calificacion, on_delete=models.CASCADE, related_name="data_punto") + + def __str__(self): + return f"({self.calificacion.id_producto.codigoProducto}: {self.x}, {self.y})" diff --git a/tecnicas/models/grupo_producto.py b/tecnicas/models/grupo_producto.py new file mode 100644 index 0000000000000000000000000000000000000000..ae21abdb5ef2d69322bf8dec4880b74d7f5bf166 --- /dev/null +++ b/tecnicas/models/grupo_producto.py @@ -0,0 +1,19 @@ +from django.db import models +from tecnicas.models import Tecnica, Catador, Palabra, Producto + + +class GrupoProducto(models.Model): + tecnica = models.ForeignKey( + Tecnica, on_delete=models.CASCADE, related_name="tecnica_grupo_producto") + + catador = models.ForeignKey( + Catador, on_delete=models.CASCADE, related_name="catador_grupo_producto") + + productos = models.ManyToManyField( + Producto, related_name="grupo_producto") + + palabras = models.ManyToManyField( + Palabra, related_name="grupo_producto") + + def __str__(self): + return f"{self.tecnica.sesion_tecnica.codigo_sesion} - {self.catador.user.username}" diff --git a/tecnicas/models/lista_palabras.py b/tecnicas/models/lista_palabras.py new file mode 100644 index 0000000000000000000000000000000000000000..ca39976f7d7a53b0fa9824239ef5668b5d857a06 --- /dev/null +++ b/tecnicas/models/lista_palabras.py @@ -0,0 +1,15 @@ +from django.db import models +from tecnicas.models import Tecnica, Catador, Palabra + + +class ListaPalabras(models.Model): + tecnica = models.ForeignKey( + Tecnica, on_delete=models.CASCADE, related_name="tecnica_lista") + catador = models.ForeignKey( + Catador, on_delete=models.CASCADE, related_name="catador_lista") + es_final = models.BooleanField(default=False) + palabras = models.ManyToManyField( + Palabra, related_name="lista_palabras") + + def __str__(self): + return f"{self.tecnica.sesion_tecnica.codigo_sesion} - {self.catador.user.username}" diff --git a/tecnicas/models/modalidad.py b/tecnicas/models/modalidad.py new file mode 100644 index 0000000000000000000000000000000000000000..0ccd258cc698882591e8b7bc3d7990137c36573c --- /dev/null +++ b/tecnicas/models/modalidad.py @@ -0,0 +1,8 @@ +from django.db import models + + +class Modalidad(models.Model): + nombre = models.CharField(max_length=255) + + def __str__(self): + return self.nombre diff --git a/tecnicas/models/tecnica.py b/tecnicas/models/tecnica.py index 5305332c62b75c9e4e0ae5c11d37359f187320ca..7d486fe5f5064d6c8a619e003ad055b9db22f371 100644 --- a/tecnicas/models/tecnica.py +++ b/tecnicas/models/tecnica.py @@ -2,6 +2,7 @@ from django.db import models from .estilo_palabra import EstiloPalabra from .tipo_tecnica import TipoTecnica +from .modalidad import Modalidad class Tecnica(models.Model): diff --git a/tecnicas/models/tecnica_modalidad.py b/tecnicas/models/tecnica_modalidad.py new file mode 100644 index 0000000000000000000000000000000000000000..e9167a4845468b6d82bcb8b5630b5b3c16870e76 --- /dev/null +++ b/tecnicas/models/tecnica_modalidad.py @@ -0,0 +1,12 @@ +from django.db import models +from .tecnica import Tecnica +from .modalidad import Modalidad + + +class TecnicaModalidad(models.Model): + tecnica = models.ForeignKey( + Tecnica, on_delete=models.CASCADE, related_name="tecnica_modalidad") + modalidad = models.ForeignKey( + Modalidad, on_delete=models.CASCADE, related_name="modalidad_tecnica") + + usando = models.BooleanField(default=False) diff --git a/tecnicas/static/img/words.webp b/tecnicas/static/img/words.webp new file mode 100644 index 0000000000000000000000000000000000000000..0be9e89c4bc74e461a8a7aa7ff14599e1aee81e8 Binary files /dev/null and b/tecnicas/static/img/words.webp differ diff --git a/tecnicas/static/js/actions-form.js b/tecnicas/static/js/actions-form.js index f755a4313127d6006ce9a2cddaeed2d3b32a84c3..e222ebb4a88c7ea85129f9066638f27761293134 100644 --- a/tecnicas/static/js/actions-form.js +++ b/tecnicas/static/js/actions-form.js @@ -3,4 +3,12 @@ function exit_sesion(styleClass) { const action = form.querySelector(".action-input"); action.value = "exit_session"; form.submit(); -} \ No newline at end of file +} + +function finishSession(styleClass) { + const form = document.querySelector(`.${styleClass}`); + form.action = "" + const action = form.querySelector(".action-input"); + action.value = "finish_session"; + form.submit(); +} diff --git a/tecnicas/static/js/created-scale.js b/tecnicas/static/js/created-scale.js index 7854f9d6cb5e0a45aab08d577edb4dc82fca7d6a..87fc46af34d563bcdfc71c2c90ffc97ee69d46f7 100644 --- a/tecnicas/static/js/created-scale.js +++ b/tecnicas/static/js/created-scale.js @@ -44,7 +44,7 @@ async function sendRating(word) { const formRatingWord = document.querySelector(`.form-rating-${word}`); const dataForm = new FormData(formRatingWord); - const url = "/cata/testers/api/ratingword"; + const url = "/cata/testers/api/ratingword/escalas"; const codeProduct = document .querySelector(".ct-product-rating") diff --git a/tecnicas/static/js/created-vocabulary.js b/tecnicas/static/js/created-vocabulary.js index c5c25be883c35eb4dcfb7adb6d00cb1a3f585215..7f9d388d3fc8043e7b8bcad3861e6f4036e36ea7 100644 --- a/tecnicas/static/js/created-vocabulary.js +++ b/tecnicas/static/js/created-vocabulary.js @@ -1,7 +1,7 @@ // ************************************** // Create Vocabulary // ************************************** -async function submitSelectWords(classNanmeForm, update = false) { +async function submitSelectWords(classNanmeForm) { const form = document.querySelector(`.${classNanmeForm}`); const name_vocabulary = form.querySelector(".cts-name-voca").value; @@ -20,29 +20,7 @@ async function submitSelectWords(classNanmeForm, update = false) { wordsInput.name = "words"; wordsInput.value = JSON.stringify(listWordsSelect); - const [isUpdata, orinalName] = inputIsUpdateVocabulary(update); - form.appendChild(wordsInput); - form.appendChild(isUpdata); - if (orinalName) form.appendChild(orinalName); form.submit(); } - -function inputIsUpdateVocabulary(is_update = false) { - const isUpdata = document.createElement("input"); - isUpdata.type = "hidden"; - isUpdata.name = "is_update"; - isUpdata.value = is_update; - - if (is_update) { - const orinalName = document.querySelector(".cts-original-name").textContent; - const inputName = document.createElement("input"); - inputName.type = "hidden"; - inputName.name = "original_name"; - inputName.value = orinalName; - return [isUpdata, inputName]; - } - - return [isUpdata, is_update]; -} diff --git a/tecnicas/static/js/details-session.js b/tecnicas/static/js/details-session.js index fe403c00daa302d9ab518c778aa56d03d87d98bb..6e24f2549a66e442cd25035c624de9954ed5d372 100644 --- a/tecnicas/static/js/details-session.js +++ b/tecnicas/static/js/details-session.js @@ -17,4 +17,12 @@ function deleteSession() { const input = actionForm.querySelector(".action-option") input.value = "delete_session"; actionForm.submit(); -} \ No newline at end of file +} + +function startSession(nameMode) { + const nameUnderscort = nameMode.replaceAll(" ", "_"); + console.log(nameUnderscort); + const input = actionForm.querySelector(".action-option") + input.value = `start_${nameUnderscort}`; + actionForm.submit(); +} diff --git a/tecnicas/static/js/download-table-csv.js b/tecnicas/static/js/download-table-csv.js index 1e525453c2a5aa0bf5fbe1a55babcf923d65a13a..27afdf85556badce4f412b9da43405f8d50c8f16 100644 --- a/tecnicas/static/js/download-table-csv.js +++ b/tecnicas/static/js/download-table-csv.js @@ -4,7 +4,7 @@ document.addEventListener("DOMContentLoaded", function () { btn.addEventListener("click", function () { // Try set the table in the page - let table = document.getElementById("convencional-table"); + let table = document.getElementById("generic-donwload-table"); if (!table) { const section = btn.closest("section"); if (section) table = section.querySelector("table"); diff --git a/tecnicas/static/js/download-table-sort-csv.js b/tecnicas/static/js/download-table-sort-csv.js new file mode 100644 index 0000000000000000000000000000000000000000..0253a18129d6369de6a96b5b99e841800f292b03 --- /dev/null +++ b/tecnicas/static/js/download-table-sort-csv.js @@ -0,0 +1,79 @@ +document.addEventListener("DOMContentLoaded", function () { + const btn = document.getElementById("download-csv-btn"); + if (!btn) return; + + btn.addEventListener("click", function () { + // Try set the table in the page + let table = document.getElementById("generic-donwload-table"); + if (!table) { + const section = btn.closest("section"); + if (section) table = section.querySelector("table"); + } + if (!table) { + console.warn("No se encontró la tabla para descargar."); + return; + } + + // helper to trim and normalize cell text + const cellText = (cell) => { + if (!cell) return ""; + return String(cell.textContent || "").trim(); + }; + + // Collect headers + const headers = []; + const ths = table.querySelectorAll("thead th"); + ths.forEach((th) => headers.push(cellText(th))); + + // Collect rows + const rows = []; + const trs = table.querySelectorAll("tbody tr"); + trs.forEach((tr) => { + const cols = []; + const tds = tr.querySelectorAll("td"); + tds.forEach((td) => cols.push(cellText(td))); + rows.push(cols); + }); + + // Convert to CSV string (escape quotes, wrap in quotes if needed) + const escapeValue = (val) => { + if (val == null) return ""; + let normalVal = val.replace(/[\u0300-\u036f]/g, ""); + normalVal = normalVal.replace(/(\r\n|\n|\r|\s)/gm, ''); + const needsQuotes = /[",\n,;]/.test(normalVal); + let v = normalVal.replace(/"/g, '""'); + if (needsQuotes) v = `"${v}"`; + return v; + }; + + const lines = []; + if (headers.length) lines.push(headers.map(escapeValue).join(",")); + rows.forEach((r) => lines.push(r.map(escapeValue).join(","))); + + const csvContent = lines.join("\n"); + + // File name: data_{nombre_sesion or codigo_sesion} + const rawName = (btn.dataset.sessionName || "").trim(); + const code = (btn.dataset.sessionCode || "").trim() || "session"; + const namePart = rawName + ? rawName.replace(/[^a-zA-Z0-9-_áéíóúÁÉÍÓÚ ]/g, "").replace(/\s+/g, "_") + : code; + const fileName = `data_${namePart}.csv`; + + // Create blob and force download + const blob = new Blob([csvContent], { type: "text/csv;charset=UTF-8;" }); + if (navigator.msSaveBlob) { + navigator.msSaveBlob(blob, fileName); + } else { + const link = document.createElement("a"); + const url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", fileName); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + } + }); +}); diff --git a/tecnicas/static/js/download-table-xlsx.js b/tecnicas/static/js/download-table-xlsx.js new file mode 100644 index 0000000000000000000000000000000000000000..ef88ce71d72f9f00f966f42161282fae0b6f8df9 --- /dev/null +++ b/tecnicas/static/js/download-table-xlsx.js @@ -0,0 +1,70 @@ +document.addEventListener("DOMContentLoaded", function () { + const btn = document.getElementById("download-xls-btn"); + if (!btn) return; + + btn.addEventListener("click", async function () { + if (typeof XLSX === "undefined") { + alert( + "Librerías XLSX no están cargadas." + ); + return; + } + + const BOOK = XLSX.utils.book_new(); + + const sections = document.querySelectorAll("section[data-tester]"); + if (!sections || sections.length === 0) { + alert("No hay tablas para descargar."); + return; + } + + let fileXlsxName = ""; + + sections.forEach((sec) => { + const tester = sec.getAttribute("data-tester") || "tester"; + const sessionName = sec.getAttribute("data-session-name") || ""; + const sessionCode = sec.getAttribute("data-session-code") || ""; + + const sheetName = `${tester}`; + + fileXlsxName = `datos_sesion_${sessionName ? sessionName.trim() : sessionCode.trim() + }`; + + const table = sec.querySelector("table"); + if (!table) return; + + // Build CSV + const rows = []; + const headerCells = Array.from(table.querySelectorAll("thead tr th")); + const headers = headerCells.map((h) => h.textContent.trim()); + rows.push(headers.map(escapeCsv).join(",")); + + const trs = table.querySelectorAll("tbody tr"); + trs.forEach((tr) => { + const tds = Array.from(tr.querySelectorAll("td")); + const values = tds.map((td) => escapeCsv(td.textContent.trim())); + rows.push(values.join(",")); + }); + + const csvContent = rows.join("\r\n"); + const sheet = XLSX.read(csvContent, { type: "string" }).Sheets["Sheet1"]; + XLSX.utils.book_append_sheet(BOOK, sheet, sheetName); + }); + + try { + XLSX.writeFile(BOOK, `${fileXlsxName}.xlsx`); + } catch (err) { + console.error(err); + alert("Error al generar el archivo XLSX: " + err.message); + } + }); + + const escapeCsv = (val) => { + if (val == null) return ""; + let normalVal = val.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + const needsQuotes = /[",\n,]/.test(normalVal); + let v = String(normalVal).replace(/"/g, '""'); + if (needsQuotes) v = `"${v}"`; + return v; + }; +}); diff --git a/tecnicas/static/js/download-table-zip.js b/tecnicas/static/js/download-table-zip.js new file mode 100644 index 0000000000000000000000000000000000000000..b0a1a7769eaf3b76b1b4eaa69b288dbe1f1c992e --- /dev/null +++ b/tecnicas/static/js/download-table-zip.js @@ -0,0 +1,74 @@ +document.addEventListener("DOMContentLoaded", function () { + const btn = document.getElementById("download-csv-btn"); + if (!btn) return; + + btn.addEventListener("click", async function () { + if (typeof JSZip === "undefined" || typeof saveAs === "undefined") { + alert( + "Librerías JSZip o FileSaver no están cargadas. Asegúrate de incluir los CDN de JSZip y FileSaver." + ); + return; + } + + const sections = document.querySelectorAll("section[data-tester]"); + if (!sections || sections.length === 0) { + alert("No hay tablas para descargar."); + return; + } + + const zip = new JSZip(); + + let zipName = ""; + + sections.forEach((sec) => { + const tester = sec.getAttribute("data-tester") || "tester"; + const sessionName = sec.getAttribute("data-session-name") || ""; + const sessionCode = sec.getAttribute("data-session-code") || ""; + + const fileName = `${tester}_${ + sessionName ? sessionName.trim() : sessionCode.trim() + }`; + + zipName = `datos_sesion_${ + sessionName ? sessionName.trim() : sessionCode.trim() + }`; + + const table = sec.querySelector("table"); + if (!table) return; + + // Build CSV + const rows = []; + const headerCells = Array.from(table.querySelectorAll("thead tr th")); + const headers = headerCells.map((h) => h.textContent.trim()); + rows.push(headers.map(escapeCsv).join(",")); + + const trs = table.querySelectorAll("tbody tr"); + trs.forEach((tr) => { + const tds = Array.from(tr.querySelectorAll("td")); + const values = tds.map((td) => escapeCsv(td.textContent.trim())); + rows.push(values.join(",")); + }); + + const csvContent = rows.join("\r\n"); + zip.file(`${fileName}.csv`, csvContent); + }); + + try { + const blob = await zip.generateAsync({ type: "blob" }); + const zipNameWithExtension = zipName + ".zip"; + saveAs(blob, zipNameWithExtension); + } catch (err) { + console.error(err); + alert("Error al generar el ZIP: " + err.message); + } + }); + + const escapeCsv = (val) => { + if (val == null) return ""; + let normalVal = val.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + const needsQuotes = /[",\n,]/.test(normalVal); + let v = String(normalVal).replace(/"/g, '""'); + if (needsQuotes) v = `"${v}"`; + return v; + }; +}); diff --git a/tecnicas/static/js/lists-words-testers.js b/tecnicas/static/js/lists-words-testers.js new file mode 100644 index 0000000000000000000000000000000000000000..9c2f1ae1ba85b9fc5a72ec7fdaf65e39a5109419 --- /dev/null +++ b/tecnicas/static/js/lists-words-testers.js @@ -0,0 +1,103 @@ +async function getListWordsTesters() { + const URL = "/cata/testers/api/ratingword/pf/list"; + try { + const response = await fetch(URL, { + method: "GET", + }); + + if (!response.ok) { + spanNotifaction("Fallo con la respuesta recibida"); + return false; + } + + const result = await response.json(); + const messError = result.error; + if (messError) { + spanNotifaction(messError); + const containerWords = document.querySelector(".cts-content-list-words"); + containerWords.innerHTML = ` +

+ ${messError} +

`; + return false; + } + spanNotifaction(result.message, false); + const containerWords = document.querySelector(".cts-content-list-words"); + containerWords.innerHTML = ""; + + const listWordsTesters = result.lists_words; + + listWordsTesters.forEach((listTester) => { + const username = listTester.username; + const words = listTester.words; + const status = listTester.status; + const listDiv = createListWords(username, words, status); + containerWords.appendChild(listDiv); + }); + + return true; + } catch (error) { + console.error(error); + spanNotifaction("Error del servidor con la API"); + return false; + } +} + +function createListWords(username, words, status = "indefinido") { + const div = document.createElement("div"); + const paragraph = document.createElement("p"); + const paragraphStatus = document.createElement("p"); + const ul = document.createElement("ul"); + + div.classList.add( + "cts-item-list-tester", + "bg-surface-sweet", + "px-4", + "pt-2", + "pb-3", + "rounded", + "shrink-0", + "w-64", + "space-y-2" + ); + + paragraph.classList.add( + "bg-surface-card", + "text-lg", + "font-semibold", + "text-center", + "rounded" + ); + + paragraphStatus.classList.add( + "bg-surface-card", + "text-lg", + "font-semibold", + "text-center", + "rounded" + ); + + paragraph.textContent = username; + paragraphStatus.textContent = status; + + ul.classList.add("text-center", "grid", "grid-cols-2", "gap-2", "w-full"); + + words.forEach((word) => { + const li = document.createElement("li"); + li.classList.add( + "bg-surface-ligt", + "rounded", + "font-bold", + "py-1", + "px-2", + "truncate" + ); + li.textContent = word.nombre_palabra; + ul.appendChild(li); + }); + + div.appendChild(paragraph); + div.appendChild(paragraphStatus); + div.appendChild(ul); + return div; +} diff --git a/tecnicas/static/js/panel-basic-cata.js b/tecnicas/static/js/panel-basic-cata.js index ede4704608b11de3dbbe83f9638f2b2b2bee7f89..a1fbe92e44d91ff6223cc967ca28a9e739d923e5 100644 --- a/tecnicas/static/js/panel-basic-cata.js +++ b/tecnicas/static/js/panel-basic-cata.js @@ -10,6 +10,7 @@ let radiosStyleWords; function initRadiosStyleWords() { radiosStyleWords = document.getElementsByName("estilo_palabras"); + console.log(radiosStyleWords); for (let index = 0; index < radiosStyleWords.length; index++) { const radio = radiosStyleWords[index]; diff --git a/tecnicas/static/js/panel-basic.js b/tecnicas/static/js/panel-basic.js index 7520d294328b676bc274ccb3a65d30edadb8744b..2dcb4d201c08baee77e1ba03d86132664499518f 100644 --- a/tecnicas/static/js/panel-basic.js +++ b/tecnicas/static/js/panel-basic.js @@ -22,12 +22,13 @@ initPanel(); function initPanel() { initRadios(); + initSizeOptions(); } function initRadios() { - inputsScale = document.getElementsByName("tipo_escala"); inputTamano = document.getElementsByName("tamano_escala").item(0); - sizeOptionsContainer = document.getElementsByClassName("cts-options-size-scale")[0]; + inputsScale = document.getElementsByName("tipo_escala"); + inputsScale.item(0).checked = true; for (let index = 0; index < inputsScale.length; index++) { let parent = inputsScale.item(index).parentElement; @@ -52,14 +53,16 @@ function initRadios() { showDescriptionStyle(parent); } } - - initSizeOptions(); } function initSizeOptions() { + sizeOptionsContainer = document.getElementsByClassName( + "cts-options-size-scale" + )[0]; + for (let i = 0; i < inputsScale.length; i++) { const radio = inputsScale.item(i); - radio.addEventListener('change', () => { + radio.addEventListener("change", () => { const tag = getTagFromLabel(radio); populateSizeOptions(tag); }); @@ -69,17 +72,21 @@ function initSizeOptions() { } } - const form = document.querySelector('form'); + const form = document.querySelector("form"); if (!form) return; - form.addEventListener('submit', (e) => { - const chosen = document.querySelector('input[name="option_size_scale"]:checked'); + 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"]'); + const toRemove = sizeOptionsContainer.querySelectorAll( + 'input[name="option_size_scale"]' + ); toRemove.forEach((el) => el.remove()); } }); @@ -88,37 +95,42 @@ function initSizeOptions() { function getTagFromLabel(radio) { try { const parent = radio.parentElement; - if (!parent) return ''; - const text = parent.textContent || ''; + if (!parent) return ""; + const text = parent.textContent || ""; return text.trim().split(/\s+/)[0].toLowerCase(); } catch (err) { - return ''; + return ""; } } function populateSizeOptions(tag) { - const options = SIZE_OPTIONS[tag] || SIZE_OPTIONS['estructurada']; + const options = SIZE_OPTIONS[tag] || SIZE_OPTIONS["estructurada"]; if (!sizeOptionsContainer) return; - sizeOptionsContainer.innerHTML = ''; + sizeOptionsContainer.innerHTML = ""; options.forEach((val) => { - const label = document.createElement('label'); - label.className = 'flex flex-col items-center cursor-pointer'; + 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'; + 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'; + 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'; + 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); }); + + const firstRadioSize = document + .getElementsByName("option_size_scale") + .item(0); + firstRadioSize.checked = true; } function showDescriptionStyle(label) { diff --git a/tecnicas/static/js/pf-make-list.js b/tecnicas/static/js/pf-make-list.js new file mode 100644 index 0000000000000000000000000000000000000000..62ea9d99c4a51486cefe793a07d1063e6adbeca2 --- /dev/null +++ b/tecnicas/static/js/pf-make-list.js @@ -0,0 +1,227 @@ +const FORM_DESCRIBE = document.querySelector(".cts-form-pf-word"); +const BOX_WORDS = document.querySelector(".cts-box-words"); +const IMG_LIST = document.querySelector(".cts-img-list"); +const ERROR_INPUT_WORD = document.querySelector(".error-input-word"); +const FORM_ACTION = document.querySelector(".form-actions"); + +const WORDS = []; +const STYLES_LI = [ + "cts-item-words", + "bg-gray-400", + "text-black", + "rounded", + "font-bold", + "text-lg", + "px-4", + "py-3", + "flex", + "flex-wrap", + "flex-row", + "flex-1", + "min-w-fit", + "justify-center", + "items-center", + "gap-3", +]; + +const STYLES_BTN = [ + "cts-remove-word", + "px-4", + "border-b-2", + "active:border-b-0", + "active:border-t-2", + "transition-all", + "rounded-xl", + "font-black", + "w-fit", + "capitalize", + "active:border-red-500", + "border-red-800", + "bg-red-500", +]; + +const itemWord = (wordName, index) => { + const btn = document.createElement("button"); + btn.setAttribute("data-index", index); + btn.classList.add(...STYLES_BTN); + btn.textContent = "➖"; + + const ph = document.createElement("p"); + ph.classList.add("ct-word-received"); + ph.textContent = wordName; + + const li = document.createElement("li"); + li.setAttribute("id", `word-${index}`); + li.classList.add(...STYLES_LI); + + li.appendChild(ph); + li.appendChild(btn); + + return li; +}; + +function initWordsFromBox() { + if (!BOX_WORDS) return; + const current_words = BOX_WORDS.querySelectorAll(".cts-item-words"); + if (!current_words.length) return; + + WORDS.length = 0; + current_words.forEach((li) => { + const p = li.querySelector(".ct-word-received"); + const text = p ? p.textContent.trim() : li.textContent.trim(); + if (text) WORDS.push(text); + }); + renderWords(); +} + +function renderWords() { + if (!BOX_WORDS) return; + if (!WORDS.length) { + BOX_WORDS.innerHTML = ""; + IMG_LIST.classList.remove("hidden"); + BOX_WORDS.appendChild(IMG_LIST); + return; + } + + BOX_WORDS.innerHTML = ""; + WORDS.forEach((word, index) => { + let liElement = itemWord(word, index); + BOX_WORDS.appendChild(liElement); + }); + + const removeButtons = BOX_WORDS.querySelectorAll("button.cts-remove-word"); + removeButtons.forEach((btn) => { + btn.addEventListener("click", (e) => { + const idx = parseInt(btn.dataset.index); + if (!Number.isNaN(idx)) { + WORDS.splice(idx, 1); + renderWords(); + } + }); + }); +} + +function setupDescribeFormToAddWord() { + if (!FORM_DESCRIBE) return; + FORM_DESCRIBE.addEventListener("submit", (e) => { + e.preventDefault(); + + if (!FORM_DESCRIBE.reportValidity()) { + return; + } + + const input = FORM_DESCRIBE.querySelector('input[type="text"]'); + if (!input) return; + + const value = input.value.trim(); + if (!value) return; + + if (WORDS.includes(value)) { + spanNotifaction("Esa palabra ya está en la lista"); + return; + } + + WORDS.push(value); + renderWords(); + + input.value = ""; + input.focus(); + }); +} + +function spanNotifaction(messageError, isError = true) { + const span = document.createElement("span"); + span.textContent = messageError; + + const div = document.createElement("div"); + div.classList.add("alert", isError ? "alert-error" : "alert-success"); + div.appendChild(span); + + ERROR_INPUT_WORD.append(div); + + setTimeout(() => { + ERROR_INPUT_WORD.removeChild(div); + }, 3000); +} + +async function sendWordsToSave() { + if (!WORDS.length) { + spanNotifaction("Debe existir al menos una palabra en la lista"); + return false; + } + + const currentPhase = parseInt( + document.querySelector(".cts-phase-pf").dataset.phase + ); + + const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value; + + const requestData = { + phase: currentPhase, + words: WORDS, + }; + + const URL = "/cata/testers/api/ratingword/pf/list"; + + try { + const response = await fetch(URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": csrfToken, + "X-Requested-With": "XMLHttpRequest", + }, + body: JSON.stringify(requestData), + }); + + if (!response.ok) { + spanNotifaction("Fallo con la respuesta recibida"); + return false; + } + + const result = await response.json(); + + const messError = result.error; + + if (messError) { + spanNotifaction(messError); + return false; + } + + spanNotifaction(result.message, false); + const addedWords = result.words; + WORDS.length = 0; + addedWords.forEach((word) => WORDS.push(word)); + renderWords(); + return true; + } catch (err) { + console.error(err); + spanNotifaction("Error en la respuesta del servidor"); + return false; + } +} + +async function setUpFormAction() { + const saveWords = await sendWordsToSave(); + if (!saveWords) { + return false; + } + + const input = FORM_ACTION.querySelector(".action-input"); + FORM_ACTION.action = ""; + input.value = "finish_session"; + FORM_ACTION.submit(); +} + +window.addEventListener("DOMContentLoaded", async () => { + initWordsFromBox(); + setupDescribeFormToAddWord(); + + const currentPhase = parseInt( + document.querySelector(".cts-phase-pf").dataset.phase + ); + + if (currentPhase == 2) await sendWordsToSave(); + if (document.querySelector(".cts-content-list-words")) + await getListWordsTesters(); +}); diff --git a/tecnicas/static/js/span-notification.js b/tecnicas/static/js/span-notification.js new file mode 100644 index 0000000000000000000000000000000000000000..086af2c19f0c9fc33f0bb1645ae8b1aa09d0e721 --- /dev/null +++ b/tecnicas/static/js/span-notification.js @@ -0,0 +1,15 @@ +function spanNotifaction(messageError, isError = true, time = 4500) { + const span = document.createElement("span"); + span.classList.add("text-xl", "font-bold", "text-white"); + span.textContent = messageError; + + const div = document.createElement("div"); + div.classList.add("alert", isError ? "alert-error" : "alert-success"); + div.appendChild(span); + + document.querySelector(".toast").append(div); + + setTimeout(() => { + document.querySelector(".toast").removeChild(div); + }, time); +} diff --git a/tecnicas/static/js/test-cata.js b/tecnicas/static/js/test-cata.js new file mode 100644 index 0000000000000000000000000000000000000000..489bbd4474bfa8f7cc8e5b61ece41b4da1800e3d --- /dev/null +++ b/tecnicas/static/js/test-cata.js @@ -0,0 +1,126 @@ +document.addEventListener("DOMContentLoaded", () => { + const containFormData = document.querySelector(".words-check-container"); + + const form = document.getElementById("wordsForm"); + const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value; + const message = document.getElementById("response-message"); + + const modal = document.getElementById("confirmModal"); + const modalContent = document.getElementById("confirmModalContent"); + const cancelBtn = document.getElementById("cancelBtn"); + const confirmBtn = document.getElementById("confirmBtn"); + + const URL = "/cata/testers/api/ratingword/cata"; + + const checkboxes = form.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach((cb) => (cb.checked = false)); + + let wordsData = []; + + form.addEventListener("submit", (e) => { + e.preventDefault(); + + const checkboxes = form.querySelectorAll('input[type="checkbox"]'); + wordsData = Array.from(checkboxes).map((cb) => ({ + id: parseInt(cb.dataset.wordId), + word: cb.dataset.wordName, + is_check: cb.checked, + })); + + showModal(wordsData); + }); + + function showModal(words) { + const checkedWords = words.filter((w) => w.is_check); + const uncheckedWords = words.filter((w) => !w.is_check); + + modalContent.innerHTML = ` +
+

Palabras seleccionadas:

+ +
+
+

Palabras no seleccionadas:

+ +
+ `; + modal.classList.remove("hidden"); + } + + cancelBtn.addEventListener("click", () => { + modalContent.innerHTML = ""; + modal.classList.add("hidden"); + }); + + confirmBtn.addEventListener("click", async () => { + modal.classList.add("hidden"); + const dataProduct = { + id: parseInt(document.querySelector(".id-product").textContent), + code: document.querySelector(".code-product").textContent, + }; + + try { + const response = await fetch(URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": csrfToken, + "X-Requested-With": "XMLHttpRequest", + }, + body: JSON.stringify({ words: wordsData, product: dataProduct }), + }); + + if (!response.ok) { + message.textContent = "Error en la respuesta del servidor"; + message.classList.remove("hidden"); + } + + const result = await response.json(); + const messError = result.error; + + if (messError) { + message.textContent = messError; + return; + } + containFormData.innerHTML = ` +
+

+ Exito al guardar +

+

+ ${result.message} +

+ +
+ `; + } catch (err) { + console.error(err); + message.textContent = "Error en la respuesta del servidor"; + message.classList.remove("hidden"); + } + }); +}); diff --git a/tecnicas/static/js/test-napping-plane.js b/tecnicas/static/js/test-napping-plane.js new file mode 100644 index 0000000000000000000000000000000000000000..ae706fa70bc4d9a8344045f43ea3e781f53a6f67 --- /dev/null +++ b/tecnicas/static/js/test-napping-plane.js @@ -0,0 +1,253 @@ +const planeContainer = document.getElementById('napping-plane'); +const productsContainer = document.getElementById('items'); +const products = document.querySelectorAll('.item-product'); + +// Configuration for the physical dimensions of the tablecloth (in cm) +const PHYSICAL_WIDTH = 60; +const PHYSICAL_HEIGHT = 40; + +let selectedProductCode = null; +let selectedProductId = null; + +// Object to store coordinates: { "CODE": { x: 10.5, y: 20.1, id: 123 } } +window.placedPoints = {}; + +const modeElement = document.querySelector('[data-mode]'); +window.isUltraFlash = modeElement && modeElement.dataset.mode.toLowerCase().includes('ultra flash'); + +// For ultra flash mode, it starts active but will be controlled by ultra-flash.js +if (window.isUltraFlash) { + document.getElementById("question-save").classList.add("hidden"); + window.isPlacementActive = true; +} else { + // Normal mode: placement is always active + window.isPlacementActive = true; +} + +// 1. Handle Product Selection +products.forEach(product => { + product.addEventListener('click', () => { + if (!window.isPlacementActive) return; + + // Remove selection from others + products.forEach(p => p.classList.remove('ring-4', 'ring-primary')); + + // Select current + product.classList.add('ring-4', 'ring-primary'); + selectedProductCode = product.dataset.code; + selectedProductId = product.dataset.idProduct; + }); +}); + +// 2. Handle Plane Click (Placing Points) +planeContainer.addEventListener('click', (e) => { + if (!window.isPlacementActive) return; + + if (!selectedProductCode) { + spanNotifaction("Por favor, selecciona un producto primero") + return; + } + + const rect = planeContainer.getBoundingClientRect(); + + // Calculate click position relative to the container (in pixels) + const xPixel = e.clientX - rect.left; + const yPixel = e.clientY - rect.top; + + // Calculate scaled coordinates (0 to PHYSICAL_DIMENSIONS) + // X axis: 0 on left, 60 on right + const xCoord = (xPixel / rect.width) * PHYSICAL_WIDTH; + + // Y axis: 0 on bottom, 40 on top (Invert Y because DOM Y is 0 at top) + const yCoord = PHYSICAL_HEIGHT - ((yPixel / rect.height) * PHYSICAL_HEIGHT); + + // Update Data Object + window.placedPoints[selectedProductCode] = { + x: parseFloat(xCoord.toFixed(2)), + y: parseFloat(yCoord.toFixed(2)), + id: selectedProductId + }; + + // Render Point + window.renderPoint(selectedProductCode, xPixel, yPixel, xCoord, yCoord); +}); + +// Make renderPoint global +window.renderPoint = function (code, xPx, yPx, xVal, yVal) { + // Remove existing point for this product if it exists + const existingPoint = document.getElementById(`point-${code}`); + if (existingPoint) { + existingPoint.remove(); + } + + const point = document.createElement('div'); + point.id = `point-${code}`; + point.className = 'data-point absolute w-4 h-4 bg-red-600 rounded-full transform -translate-x-1/2 -translate-y-1/2 cursor-pointer border-2 border-white shadow-md group'; + point.dataset.code = code; + point.dataset.px = xVal; + point.dataset.py = yVal; + point.dataset.idProduct = window.placedPoints[code]?.id; + + point.style.left = `${xPx}px`; + point.style.top = `${yPx}px`; + + planeContainer.appendChild(point); + + const textLabel = document.createElement('span'); + textLabel.className = 'absolute top-4 left-1/2 transform -translate-x-1/2 text-xs font-bold text-gray-700 pointer-events-none'; + textLabel.innerText = code; + point.appendChild(textLabel); +} + +function checkPoints() { + const points = document.querySelectorAll('.data-point'); + + points.forEach(point => { + const code = point.dataset.code; + + const xVal = parseFloat(point.dataset.px); + const yVal = parseFloat(point.dataset.py); + + const rect = planeContainer.getBoundingClientRect(); + + const px = (xVal / PHYSICAL_WIDTH) * rect.width; + const py = ((PHYSICAL_HEIGHT - yVal) / PHYSICAL_HEIGHT) * rect.height; + + window.placedPoints[code] = { + x: xVal.toFixed(2), + y: yVal.toFixed(2), + id: point.dataset.idProduct + }; + + window.renderPoint(code, px, py, xVal, yVal); + }); +} + +setTimeout(checkPoints, 100); + +/* +//// +////// +//////// Question to finish session +////// +//// +*/ + +function showOptionsSave() { + document.getElementById("question-save").classList.add("hidden"); + document.getElementById("finish-session").classList.remove("hidden"); + document.getElementById("cancel-save").classList.remove("hidden"); +} + +function showQuestionSave() { + document.getElementById("question-save").classList.remove("hidden"); + document.getElementById("finish-session").classList.add("hidden"); + document.getElementById("cancel-save").classList.add("hidden"); +} + +document + .getElementById("question-save") + .addEventListener("click", showOptionsSave); + +document + .getElementById("cancel-save") + .addEventListener("click", showQuestionSave); + +/* +//// +////// +//////// Save data and finish session +////// +//// +*/ + +// Callback for additional validation before saving +window.beforeSaveData = null; + +// Function to get extra data for each product +window.getExtraDataForSave = null; + +window.saveData = async function (isFinishSession = false) { + const codeProducts = Object.keys(window.placedPoints); + const data = []; + + // Only validate all products placed if finishing session + if (isFinishSession && products.length != codeProducts.length) { + spanNotifaction("Por favor, coloca todos los puntos antes de finalizar la sesión") + return false; + } + + // Call beforeSaveData callback if it exists (for ultra flash validation) + if (window.beforeSaveData && typeof window.beforeSaveData === 'function') { + const validationResult = window.beforeSaveData(isFinishSession); + if (validationResult === false) { + return false; + } + } + + codeProducts.forEach((code) => { + const point = window.placedPoints[code]; + + const objData = { + code: code, + x: point.x, + y: point.y, + idProduct: point.id + }; + + // Get extra data if callback exists (for ultra flash words) + if (window.getExtraDataForSave && typeof window.getExtraDataForSave === 'function') { + const extraData = window.getExtraDataForSave(code); + Object.assign(objData, extraData); + } + + data.push(objData); + }) + + const URL = "/cata/testers/api/rating-napping" + const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; + + try { + const response = await fetch(URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": csrfToken, + }, + body: JSON.stringify(data), + }) + + if (!response.ok) { + spanNotifaction("Error en la respuesta del servidor") + return false; + } + + const result = await response.json() + + if (result.error) { + spanNotifaction(result.error) + return false + } else { + spanNotifaction(result.message, false) + return true + } + } catch (error) { + spanNotifaction("Error en proceso de guardar los datos") + return false + } +} + +// Function to finish session with validation +window.finishSession = async function () { + const success = await window.saveData(true); + if (success) { + // Submit the form to finish session + const formFinish = document.getElementById("form-finish-session") + formFinish.action = "" + formFinish.submit(); + } +} + +document + .getElementById("save-progress") + .addEventListener("click", () => window.saveData(false)); \ No newline at end of file diff --git a/tecnicas/static/js/test-napping-sort.js b/tecnicas/static/js/test-napping-sort.js new file mode 100644 index 0000000000000000000000000000000000000000..bec251888aebad8afbce0426f8724dccf6516e21 --- /dev/null +++ b/tecnicas/static/js/test-napping-sort.js @@ -0,0 +1,623 @@ +// Napping Sort Mode - Three Phase Implementation +// Phase 1: Place products on plane +// Phase 2: Create groups by selecting points +// Phase 3: Describe groups with words + +// Store groups: { "group-1": ["CODE1", "CODE2"], "group-2": ["CODE3"] } +const productGroups = {}; + +// Store group words: { "group-1": ["word1", "word2"], "group-2": ["word3"] } +const groupWords = {}; + +// Track which group each product belongs to: { "CODE1": "group-1", "CODE2": "group-1" } +const productToGroup = {}; + +// Selected points for creating a group +let selectedPoints = []; + +// Current group counter for generating IDs +let groupCounter = 1; + +// Current phase (1, 2, or 3) +let currentPhase = 1; + +// Current group being described +let currentGroupId = null; + +// Only initialize if in sort mode +const modeElementSort = document.querySelector('[data-mode]'); +const isSortMode = modeElementSort && modeElementSort.dataset.mode.toLowerCase() === 'sorting'; + +if (isSortMode) { + initSortMode(); +} + +function loadExistingGroups() { + const dataGroupContainer = document.querySelector('.data-group-products'); + if (!dataGroupContainer) return { hasGroups: false, hasWords: false }; + + const groupElements = dataGroupContainer.querySelectorAll('.item-group'); + let hasGroups = false; + let hasWords = false; + + groupElements.forEach((groupEl, index) => { + const groupId = `group-${groupCounter++}`; + const products = []; + const words = groupEl.dataset.words ? groupEl.dataset.words.split(',').map(w => w.trim()).filter(w => w) : []; + + // Get products in this group + groupEl.querySelectorAll('.item-group-product').forEach(productEl => { + const code = productEl.dataset.code; + products.push(code); + productToGroup[code] = groupId; + }); + + if (products.length > 0) { + hasGroups = true; + productGroups[groupId] = products; + groupWords[groupId] = words; + + if (words.length > 0) { + hasWords = true; + } + } + }); + + return { hasGroups, hasWords }; +} + +function initSortMode() { + const continueGroupingBtn = document.getElementById('continue-grouping'); + const continueDescriptionBtn = document.getElementById('continue-description'); + const createGroupBtn = document.getElementById('create-group-btn'); + const dissolveGroupBtn = document.getElementById('dissolve-group-btn'); + const groupControls = document.getElementById('group-controls'); + const questionSaveBtn = document.getElementById('question-save'); + + const groupWordDialog = document.getElementById('group-word-dialog'); + const groupWordForm = document.getElementById('group-word-form'); + const groupWordInput = document.getElementsByName('nombre_palabra')[0]; + groupWordInput.value = ""; + const groupWordList = document.getElementById('group-word-list'); + const dialogGroupId = document.getElementById('dialog-group-id'); + + // Hide question save button initially + questionSaveBtn.classList.add('hidden'); + + // Load existing groups from backend and determine initial phase + const { hasGroups, hasWords } = loadExistingGroups(); + + setTimeout(() => { + // Determine initial phase based on existing data + if (hasGroups && hasWords) { + // Skip to Phase 3 (Description) if groups have words + currentPhase = 3; + window.isPlacementActive = false; + groupControls.classList.remove('hidden'); + continueDescriptionBtn.classList.remove('hidden'); + + renderExistingGroups(); + startDescriptionPhase(); + } else if (hasGroups) { + // Skip to Phase 2 (Grouping) if groups exist but no words + currentPhase = 2; + renderExistingGroups(); + window.isPlacementActive = false; + groupControls.classList.remove('hidden'); + continueDescriptionBtn.classList.remove('hidden'); + + const plane = document.getElementById('napping-plane'); + plane.classList.remove('cursor-crosshair'); + plane.classList.add('cursor-default'); + + enablePointSelection(); + spanNotifaction("Continúa agrupando productos o pasa a la descripción.", false); + } else { + // Start in Phase 1 (Placement) + currentPhase = 1; + } + }, 200); + + // Phase 1: Product Placement + // Show continue to grouping button when all products are placed + setInterval(() => { + if (currentPhase === 1) { + const placedCount = Object.keys(window.placedPoints).length; + const totalProducts = document.querySelectorAll('.item-product').length; + + if (placedCount === totalProducts && placedCount > 0) { + continueGroupingBtn.classList.remove('hidden'); + } else { + continueGroupingBtn.classList.add('hidden'); + } + } + }, 500); + + // Transition to Phase 2: Grouping + continueGroupingBtn.addEventListener('click', () => { + const placedCount = Object.keys(window.placedPoints).length; + const totalProducts = document.querySelectorAll('.item-product').length; + + if (placedCount !== totalProducts) { + spanNotifaction("Por favor, coloca todos los productos antes de continuar."); + return; + } + + startGroupingPhase(); + }); + + function startGroupingPhase() { + currentPhase = 2; + window.isPlacementActive = false; + continueGroupingBtn.classList.add('hidden'); + groupControls.classList.remove('hidden'); + continueDescriptionBtn.classList.remove('hidden'); + + const plane = document.getElementById('napping-plane'); + plane.classList.remove('cursor-crosshair'); + plane.classList.add('cursor-default'); + + // Remove selection from products + document.querySelectorAll('.item-product').forEach(p => { + p.classList.remove('ring-4', 'ring-primary'); + }); + + spanNotifaction("Fase de agrupación: Selecciona puntos y crea grupos.", false); + + // Auto-save points when transitioning to Phase 2 + sortModeSaveData(false); + + // Enable point selection + enablePointSelection(); + } + + function enablePointSelection() { + // Add click handler to points for selection + document.getElementById('napping-plane').addEventListener('click', (e) => { + if (currentPhase !== 2) return; + + const point = e.target.closest('.data-point'); + if (point) { + e.stopPropagation(); + togglePointSelection(point.dataset.code); + } + }); + } + + function togglePointSelection(code) { + // Check if point is already in a group + if (productToGroup[code]) { + spanNotifaction(`El producto ${code} ya pertenece al grupo ${productToGroup[code]}`); + return; + } + + const point = document.getElementById(`point-${code}`); + const index = selectedPoints.indexOf(code); + + if (index > -1) { + // Deselect + selectedPoints.splice(index, 1); + point.classList.remove('ring-4', 'ring-blue-500'); + point.classList.add('bg-red-600'); + point.classList.remove('bg-blue-600'); + } else { + // Select + selectedPoints.push(code); + point.classList.add('ring-4', 'ring-blue-500'); + point.classList.remove('bg-red-600'); + point.classList.add('bg-blue-600'); + } + } + + // Create Group + createGroupBtn.addEventListener('click', () => { + if (selectedPoints.length === 0) { + spanNotifaction("Selecciona al menos un punto para crear un grupo"); + return; + } + + const groupId = `group-${groupCounter++}`; + productGroups[groupId] = [...selectedPoints]; + groupWords[groupId] = []; + + // Update product to group mapping + selectedPoints.forEach(code => { + productToGroup[code] = groupId; + }); + + // Visual update: change color of grouped points + const colors = ['bg-green-600', 'bg-purple-600', 'bg-yellow-600', 'bg-pink-600', 'bg-indigo-600']; + const colorIndex = (groupCounter - 2) % colors.length; + + selectedPoints.forEach(code => { + const point = document.getElementById(`point-${code}`); + point.classList.remove('bg-blue-600', 'ring-4', 'ring-blue-500'); + point.classList.add(colors[colorIndex]); + }); + + // Add group to display + addGroupToDisplay(groupId, selectedPoints, colors[colorIndex]); + + // Clear selection + selectedPoints = []; + + spanNotifaction(`Grupo "${groupId}" creado con éxito`, false); + }); + + function addGroupToDisplay(groupId, products, colorClass) { + const groupsDisplay = document.getElementById('groups-display'); + + const groupBadge = document.createElement('div'); + groupBadge.id = `display-${groupId}`; + groupBadge.className = `badge badge-lg gap-2 p-4 ${colorClass} text-white cursor-pointer font-semibold`; + groupBadge.innerHTML = ` + [ ${products.join(', ')} ] + + + + `; + + groupsDisplay.appendChild(groupBadge); + + // Add dissolve handler + groupBadge.querySelector('.dissolve-group-icon').addEventListener('click', (e) => { + e.stopPropagation(); + dissolveGroup(groupId); + }); + + // Add click handler for Phase 3 (describe group) + groupBadge.addEventListener('click', () => { + if (currentPhase === 3) { + openGroupWordDialog(groupId); + } + }); + } + + function dissolveGroup(groupId) { + if (currentPhase === 3) { + spanNotifaction("No puedes disolver un grupo en la fase de descripción"); + return; + } + + if (groupWords[groupId] && groupWords[groupId].length > 0) { + spanNotifaction("No puedes disolver un grupo que ya tiene palabras descriptivas"); + return; + } + + // Remove from product to group mapping + productGroups[groupId].forEach(code => { + delete productToGroup[code]; + const point = document.getElementById(`point-${code}`); + point.classList.remove('bg-green-600', 'bg-purple-600', 'bg-yellow-600', 'bg-pink-600', 'bg-indigo-600'); + point.classList.add('bg-red-600'); + }); + + // Remove group + delete productGroups[groupId]; + delete groupWords[groupId]; + + // Remove from display + const displayElement = document.getElementById(`display-${groupId}`); + if (displayElement) { + displayElement.remove(); + } + + spanNotifaction(`Grupo "${groupId}" disuelto`, false); + } + + // Dissolve Group button (dissolves last created group or selected group) + dissolveGroupBtn.addEventListener('click', () => { + const groupIds = Object.keys(productGroups); + if (groupIds.length === 0) { + spanNotifaction("No hay grupos para disolver"); + return; + } + + // Dissolve the last group + const lastGroupId = groupIds[groupIds.length - 1]; + dissolveGroup(lastGroupId); + }); + + // Transition to Phase 3: Description + continueDescriptionBtn.addEventListener('click', () => { + const groupIds = Object.keys(productGroups); + if (groupIds.length === 0) { + spanNotifaction("Crea al menos un grupo para continuar"); + return; + } + + // Check all products are in groups + const totalProducts = document.querySelectorAll('.item-product').length; + const groupedProducts = Object.keys(productToGroup).length; + + if (groupedProducts !== totalProducts) { + spanNotifaction("Todos los productos deben estar asignados a un grupo"); + return; + } + + startDescriptionPhase(); + }); + + function startDescriptionPhase() { + currentPhase = 3; + continueDescriptionBtn.classList.add('hidden'); + + createGroupBtn.classList.add('hidden'); + dissolveGroupBtn.classList.add('hidden'); + + questionSaveBtn.classList.remove('hidden'); + + const iconsDisolveGroup = document.querySelectorAll('.dissolve-group-icon'); + for (let index = 0; index < iconsDisolveGroup.length; index++) { + const icon = iconsDisolveGroup.item(index); + icon.remove(); + } + + spanNotifaction("Fase de descripción: Haz clic en un grupo para agregar palabras.", false); + + // Auto-save groups when transitioning to Phase 3 + sortModeSaveData(false); + } + + // Group Word Dialog Functions + function openGroupWordDialog(groupId) { + currentGroupId = groupId; + dialogGroupId.innerText = groupId; + renderGroupWordList(); + groupWordDialog.showModal(); + } + + function renderGroupWordList() { + groupWordList.innerHTML = ''; + const words = groupWords[currentGroupId] || []; + + words.forEach((word, index) => { + const badge = document.createElement('div'); + badge.className = 'badge badge-secondary gap-2 p-3'; + badge.innerHTML = ` + ${word} + + `; + + badge.querySelector('.remove-word').addEventListener('click', () => { + removeGroupWord(index); + }); + + groupWordList.appendChild(badge); + + }); + + // Update group display with word count + updateGroupDisplayWithWords(currentGroupId); + } + + function addGroupWord(word) { + if (!groupWords[currentGroupId]) { + groupWords[currentGroupId] = []; + } + + if (groupWords[currentGroupId].includes(word)) { + spanNotifaction("Palabra duplicada"); + return; + } + + groupWords[currentGroupId].push(word); + renderGroupWordList(); + } + + function removeGroupWord(index) { + if (groupWords[currentGroupId]) { + groupWords[currentGroupId].splice(index, 1); + renderGroupWordList(); + } + } + + groupWordForm.addEventListener('submit', (e) => { + e.preventDefault(); + const word = groupWordInput.value.trim(); + if (word) { + addGroupWord(word); + groupWordInput.value = ''; + groupWordInput.focus(); + } + }); + + function updateGroupDisplayWithWords(groupId) { + const displayElement = document.getElementById(`display-${groupId}`); + if (!displayElement) return; + + const words = groupWords[groupId] || []; + const products = productGroups[groupId] || []; + + // Add tooltip with words on hover + if (words.length > 0) { + let tooltip = displayElement.querySelector('.group-tooltip'); + if (!tooltip) { + tooltip = document.createElement('div'); + tooltip.className = 'group-tooltip absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-800 text-white text-xs rounded z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none hidden'; + displayElement.classList.add('group', 'relative'); + displayElement.appendChild(tooltip); + } + + const wordBadges = words.map(w => `${w}`).join(''); + tooltip.innerHTML = ` + ${groupId} +
+ ${wordBadges} +
+ `; + tooltip.style.maxWidth = '320px'; + tooltip.style.whiteSpace = 'normal'; + tooltip.classList.remove('hidden'); + } else { + let tooltip = displayElement.querySelector('.group-tooltip'); + if (tooltip) { + tooltip.remove(); + } + } + } + + function renderExistingGroups() { + const colors = ['bg-green-600', 'bg-purple-600', 'bg-yellow-600', 'bg-pink-600', 'bg-indigo-600']; + let colorIndex = 0; + + for (const [groupId, products] of Object.entries(productGroups)) { + const color = colors[colorIndex % colors.length]; + colorIndex++; + + // Update point colors + products.forEach(code => { + const point = document.getElementById(`point-${code}`); + + if (point) { + point.classList.remove('bg-red-600'); + point.classList.add(color); + } + }); + + // Add group to display + addGroupToDisplay(groupId, products, color); + + // Update display with words if they exist + if (groupWords[groupId] && groupWords[groupId].length > 0) { + updateGroupDisplayWithWords(groupId); + } + } + } + + // Set up callbacks to extend the base saveData function + window.beforeSaveData = function (isFinishSession = false) { + if (isFinishSession) { + // Validate all products are placed + const totalProducts = document.querySelectorAll('.item-product').length; + const placedCount = Object.keys(window.placedPoints).length; + + if (placedCount !== totalProducts) { + spanNotifaction("Por favor, coloca todos los productos antes de finalizar la sesión"); + return false; + } + + // Validate all products are in groups + const groupedProducts = Object.keys(productToGroup).length; + if (groupedProducts !== totalProducts) { + spanNotifaction("Todos los productos deben estar asignados a un grupo"); + return false; + } + + // Validate each group has at least one product (already guaranteed by creation logic) + const groupIds = Object.keys(productGroups); + if (groupIds.length === 0) { + spanNotifaction("Debe existir al menos un grupo"); + return false; + } + + // Validate each group has at least one word + for (const groupId of groupIds) { + const words = groupWords[groupId] || []; + if (words.length < 1) { + spanNotifaction(`El grupo ${groupId} debe tener al menos una palabra descriptiva`); + return false; + } + } + } + return true; + }; + + // Override save-progress button to use sort mode save function + const saveProgressBtn = document.getElementById('save-progress'); + // Remove existing event listener by cloning and replacing + const newSaveProgressBtn = saveProgressBtn.cloneNode(true); + newSaveProgressBtn.textContent = 'Guardar Progreso Sort'; + saveProgressBtn.parentNode.replaceChild(newSaveProgressBtn, saveProgressBtn); + + newSaveProgressBtn.addEventListener('click', async () => { + await sortModeSaveData(false); + }); + + // Override finish-session button to use sort mode save function + document.getElementById('finish-session').addEventListener('click', async (e) => { + e.preventDefault(); + e.stopPropagation(); + const success = await sortModeSaveData(true); + if (success) { + const formFinish = document.getElementById("form-finish-session"); + formFinish.action = ""; + formFinish.submit(); + } + }); + + // Sort mode specific save function + async function sortModeSaveData(isFinishSession = false) { + // Run validation callback if it exists + if (window.beforeSaveData && typeof window.beforeSaveData === 'function') { + const validationResult = window.beforeSaveData(isFinishSession); + if (validationResult === false) { + return false; + } + } + + // Build products array with basic position info and group assignment + const products = []; + for (const [code, point] of Object.entries(window.placedPoints)) { + const groupId = productToGroup[code]; + products.push({ + code: code, + x: point.x, + y: point.y, + idProduct: point.id, + group: groupId || "" // Empty string if no group assigned + }); + } + + // Build groups object with word arrays + const groups = {}; + const groupIds = Object.keys(productGroups); + + // Only include groups if they exist + if (groupIds.length > 0) { + for (const groupId of groupIds) { + groups[groupId] = groupWords[groupId] || []; + } + } + + // Build the data structure + const data = { products: products }; + + // Only add groups if they exist + if (Object.keys(groups).length > 0) { + data.groups = groups; + } + + const URL = "/cata/testers/api/rating-napping"; + const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; + + try { + const response = await fetch(URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": csrfToken, + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + spanNotifaction("Error en la respuesta del servidor"); + return false; + } + + const result = await response.json(); + + if (result.error) { + spanNotifaction(result.error); + return false; + } else { + spanNotifaction(result.message, false); + return true; + } + } catch (error) { + spanNotifaction("Error en proceso de guardar los datos"); + return false; + } + } +} diff --git a/tecnicas/static/js/test-napping-ultra-flash.js b/tecnicas/static/js/test-napping-ultra-flash.js new file mode 100644 index 0000000000000000000000000000000000000000..7b8988000980e3e52f99abf8f5b9f0233fc130ad --- /dev/null +++ b/tecnicas/static/js/test-napping-ultra-flash.js @@ -0,0 +1,235 @@ +// Store words: { "CODE": ["word1", "word2"] } +const productWords = {}; + +// Only initialize ultra flash if the mode is active +if (window.isUltraFlash) { + initUltraFlash(); +} + +function initUltraFlash() { + const continueBtn = document.getElementById('continue-description'); + const questionSaveBtn = document.getElementById('question-save'); + const dialog = document.getElementById('word-dialog'); + const wordForm = document.getElementById('word-form'); + const wordInput = document.querySelector('.cts-input-list-word'); + const wordList = document.getElementById('word-list'); + const dialogProductCode = document.getElementById('dialog-product-code'); + + let isDescriptionPhase = false; + let currentProductCode = null; + + const points = document.querySelectorAll('.data-point'); + let hasExistingWords = false; + + wordInput.value = ''; + + // Check if there are existing words from backend + points.forEach(point => { + const code = point.dataset.code; + const wordsAttr = point.dataset.words; + + if (wordsAttr && wordsAttr.trim() !== '') { + productWords[code] = wordsAttr.split(',').filter(w => w.trim() !== ''); + + if (productWords[code].length >= 1) { + hasExistingWords = true; + } + } + }); + + setTimeout(() => { + if (hasExistingWords) { + startDescriptionPhase(); + // Update all point labels to show existing words + points.forEach(point => { + const code = point.dataset.code; + if (productWords[code] && productWords[code].length > 0) { + updatePointLabel(code); + } + }); + } else { + // No existing words, show continue button for phase 1 + continueBtn.classList.remove('hidden'); + } + }, 100); + + // Check if all products are placed + continueBtn.addEventListener('click', () => { + const placedCount = Object.keys(window.placedPoints).length; + const totalProducts = document.querySelectorAll('.item-product').length; + + if (placedCount !== totalProducts) { + spanNotifaction("Por favor, coloca todos los productos antes de continuar."); + return; + } + + startDescriptionPhase(); + }); + + function startDescriptionPhase() { + isDescriptionPhase = true; + window.isPlacementActive = false; + continueBtn.classList.add('hidden'); + questionSaveBtn.classList.remove("hidden"); + spanNotifaction("Fase de descripción: Haz clic en un punto para agregar palabras.", false); + + const plane = document.getElementById('napping-plane'); + plane.classList.remove('cursor-crosshair'); + plane.classList.add('cursor-default'); + document.querySelectorAll('.item-product').forEach(p => { + p.classList.remove('ring-4', 'ring-primary'); + }); + + // Auto-save positions when transitioning to description phase + window.saveData(false); + } + + // Handle Point Click for Description + // We need to attach this to the plane or points. + // Since points are re-rendered, delegating to plane is better, or hooking into renderPoint. + // But renderPoint is in the other file. + // Let's use event delegation on the plane, but we need to catch the click on the point. + + document.getElementById('napping-plane').addEventListener('click', (e) => { + if (!isDescriptionPhase) return; + + const point = e.target.closest('.data-point'); + + if (point) { + e.stopPropagation(); + openWordDialog(point.dataset.code); + } + }); + + function openWordDialog(code) { + currentProductCode = code; + dialogProductCode.innerText = code; + renderWordListInDialog(); + dialog.showModal(); + } + + function renderWordListInDialog() { + wordList.innerHTML = ''; + const words = productWords[currentProductCode] || []; + + words.forEach((word, index) => { + const badge = document.createElement('div'); + badge.className = 'badge badge-secondary gap-2 p-3'; + badge.innerHTML = ` + ${word} + + `; + + badge.querySelector('.remove-word').addEventListener('click', () => { + removeWord(index); + }); + + wordList.appendChild(badge); + }); + + // Update visualization on the plane + updatePointLabel(currentProductCode); + } + + function addWord(word) { + if (!productWords[currentProductCode]) { + productWords[currentProductCode] = []; + } + + // No maximum limit on words + + if (productWords[currentProductCode].includes(word)) { + spanNotifaction("Palabra duplicada"); + return; + } + + productWords[currentProductCode].push(word); + renderWordListInDialog(); + } + + function removeWord(index) { + if (productWords[currentProductCode]) { + productWords[currentProductCode].splice(index, 1); + renderWordListInDialog(); + } + } + + wordForm.addEventListener('submit', (e) => { + e.preventDefault(); + const word = wordInput.value.trim(); + if (word) { + addWord(word); + wordInput.value = ''; + wordInput.focus(); + } + }); + + function updatePointLabel(code) { + const point = document.getElementById(`point-${code}`); + if (!point) return; + + const words = productWords[code] || []; + + // Remove existing tooltip if present + let tooltip = point.querySelector('.cts-tooltip'); + + if (tooltip) { + tooltip.remove(); + } + + // Only create tooltip in description phase and if there are words + if (isDescriptionPhase && words.length > 0) { + tooltip = document.createElement('div'); + tooltip.className = 'cts-tooltip absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-800 text-white text-xs rounded z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none'; + + // Display words in a 3-column grid (no coordinates) + const wordBadges = words.map(w => `${w}`).join(''); + tooltip.innerHTML = ` + ${code} +
+ ${wordBadges} +
+ `; + + // Add max-width to tooltip + tooltip.style.maxWidth = '320px'; + tooltip.style.whiteSpace = 'normal'; + point.appendChild(tooltip); + } + } + + // Set up callbacks to extend the base saveData function + // Validation callback - runs before saving + window.beforeSaveData = function (isFinishSession = false) { + // If finishing session, validate all products placed and have words + if (isFinishSession) { + const totalProducts = document.querySelectorAll('.item-product').length; + const codeProducts = Object.keys(window.placedPoints); + + // Check all products are placed + if (codeProducts.length !== totalProducts) { + spanNotifaction("Por favor, coloca todos los productos antes de finalizar la sesión."); + return false; + } + + // Check each product has at least 1 word + for (const code of codeProducts) { + const words = productWords[code] || []; + if (words.length < 1) { + spanNotifaction(`El producto ${code} debe tener al menos 1 palabra para finalizar la sesión.`); + return false; + } + } + } + // For progress save, no validation needed + return true; + }; + + // Data extension callback - adds words to each product's data + window.getExtraDataForSave = function (code) { + const words = productWords[code] || []; + return { + words: words + }; + }; +} diff --git a/tecnicas/static/js/test-sort.js b/tecnicas/static/js/test-sort.js new file mode 100644 index 0000000000000000000000000000000000000000..0d46731c1b35bc5d8c3e4d6627a5267db9579347 --- /dev/null +++ b/tecnicas/static/js/test-sort.js @@ -0,0 +1,554 @@ +let dragged = null; + +const productsPlaceHolder = ` +

+ Agrupación de productos +

`; + +const wordsPlaceHolder = ` +

+ Lista de atributos +

`; + +const products = document.querySelectorAll(".draggable"); +const zonesDrop = document.querySelectorAll(".dropzone"); + +const DATA_GRUPS = {}; + +products.forEach(manageDragables); +zonesDrop.forEach(manageDropZone); + + +/* +//// +////// +//////// Add new grups +////// +//// +*/ + +function addNewGrup() { + const container = document.getElementById("containers"); + const newGrup = document.createElement("section"); + const styles = "w-fit space-y-4 border rounded p-2 bg-surface-sweet"; + newGrup.classList.add(...styles.split(" ")); + + const formWord = document.getElementById("form-word"); + const formWordClone = formWord.cloneNode(true); + formWordClone.classList.remove("hidden"); + + newGrup.innerHTML = ` +
+ ${productsPlaceHolder} +
+ ${formWordClone.outerHTML} +
+ ${wordsPlaceHolder} +
+
+ + + +
`; + + manageDropZone(newGrup.querySelector(".dropzone")); + container.appendChild(newGrup); +} + + +/* +//// +////// +//////// Manage products' drags and remove drags +////// +//// +*/ + +/** + * + * @param {HTMLElement} zone + */ +function manageDropZone(zone) { + /* + //// + ////// Trash Zone product + //// + */ + if (zone.classList.contains("trash-products")) { + zone.addEventListener("dragover", (e) => e.preventDefault()); + + zone.addEventListener("drop", (event) => { + const codeDrag = dragged.dataset.code + const oldParent = dragged.parentNode; + const oldCodeZone = oldParent.getAttribute("id") + + if (oldParent.classList.contains("original-products")) { + spanNotifaction("No puedes borrar este producto"); + return + } + + dragged.remove() + removeProduct(codeDrag, oldCodeZone) + + spanNotifaction("Producto removido del grupo", false); + dragged = null + + if (oldParent.classList.contains("dropzone") && oldParent.children.length === 0) { + oldParent.innerHTML = productsPlaceHolder; + } + }) + return + } + + /* + //// + ////// Identificate grups and creata dinamic data + //// + */ + + // Create id for grup + const idZone = generateSimpleID() + + // Add products if there en the page + const products = [] + const productsElements = zone.querySelectorAll(".draggable") || None; + + if (productsElements) { + productsElements.forEach((proEle) => { + products.push({ + id: proEle.dataset.idProduct, + code: proEle.dataset.code + }) + }) + } + + const parentZone = zone.parentNode; + const wordsInGrup = parentZone.querySelectorAll(".item-word") || None + + // Add words if there en the page + const words = [] + if (wordsInGrup) { + wordsInGrup.forEach((wordElement) => { + words.push(wordElement.textContent) + }) + } + + DATA_GRUPS[idZone] = { + products: products, + words: words + } + + /* + //// + ////// Drop Zone product + //// + */ + + zone.setAttribute("id", idZone) + zone.addEventListener("dragover", (e) => e.preventDefault()); + + zone.addEventListener("drop", () => { + const codeDrag = dragged.dataset.code + const zoneCode = zone.getAttribute("id") + + // Check that new produck is not in currents products of the group + const obj = DATA_GRUPS[zoneCode].products.find((product) => product.code == codeDrag) + if (obj) { + spanNotifaction("Ese producto ya está en el grupo"); + return + } + + DATA_GRUPS[zoneCode].products.push({ + id: dragged.dataset.idProduct, + code: dragged.dataset.code + }) + + const oldParent = dragged.parentNode; + + const p = zone.querySelector("p"); + if (p) { + p.remove(); + } + + // Move original or clone product in zone + if (oldParent.classList.contains("dropzone")) { + zone.appendChild(dragged); + } else { + const clone = dragged.cloneNode(true); + clone.classList.remove("opacity-50"); + manageDragables(clone); + zone.appendChild(clone); + } + + if (oldParent.classList.contains("dropzone")) { + if (oldParent.children.length === 0) oldParent.innerHTML = productsPlaceHolder; + const zoneCodeOld = oldParent.getAttribute("id") + removeProduct(codeDrag, zoneCodeOld) + } + }); + + // Add listeners to elements options and WordForm for the list words + addListenersButtonQuestionGrup(zone.parentNode) + manageFormWord(parentZone.querySelector("form"), parentZone.querySelector(".words-container"), idZone) + + if (DATA_GRUPS[idZone].words.length) { + parentZone.querySelector(".words-container").innerHTML = ""; + DATA_GRUPS[idZone].words.forEach((word) => { + const itemWord = getItemWord(word, idZone); + parentZone.querySelector(".words-container").appendChild(itemWord); + }) + } +} + +/* +//// +////// +//////// Management Drags and FormWord +////// +//// +*/ + +/** + * + * @param {HTMLElement} el + */ +function manageDragables(el) { + el.addEventListener("dragstart", () => { + dragged = el; + setTimeout(() => el.classList.add("opacity-50"), 0); + }); + + el.addEventListener("dragend", () => { + dragged = null; + el.classList.remove("opacity-50"); + }); +} + +/** + * + * @param {HTMLFormElementt} form + * @param {HTMLElement} containerWords + */ +function manageFormWord(form, containerWords, codeGrup) { + form.addEventListener("submit", (e) => { + e.preventDefault(); + + if (!form.reportValidity()) { + return; + } + + const input = form.querySelector('input[type="text"]'); + + if (!input) return; + + const name = input.value.trim(); + if (!name) return; + + if (DATA_GRUPS[codeGrup].words.includes(name)) { + input.value = ""; + input.focus(); + spanNotifaction("Esa palabra ya está en la lista"); + return; + } + + DATA_GRUPS[codeGrup].words.push(name) + const wordItem = getItemWord(name, codeGrup) + + const p = containerWords.querySelector("p"); + if (p) { + p.remove(); + } + + containerWords.appendChild(wordItem) + + input.value = ""; + input.focus(); + }); +} + +/* +//// +////// +//////// Management Drags and FormWord +////// +//// +*/ + +/** + * + * @param {HTMLElement} grup + */ +function removeGrup(grup) { + delete DATA_GRUPS[grup.querySelector(".dropzone").getAttribute("id")] + grup.remove() +} + +function removeWord(code, wordName) { + const newWords = DATA_GRUPS[code].words.filter(word => word != wordName) + DATA_GRUPS[code].words = newWords + if (!newWords.length) { + const containerWords = document.getElementById(code).parentNode.querySelector(".words-container") + containerWords.innerHTML = wordsPlaceHolder; + } +} + +function removeProduct(codeProduct, codeZone) { + const newProducts = DATA_GRUPS[codeZone].products.filter((product) => product.code != codeProduct) + DATA_GRUPS[codeZone].products = newProducts +} + +function getItemWord(wordName, code) { + const STYLES_DIV = "cts-item-words bg-surface-ligt text-black rounded font-bold text-lg p-1 flex flex-wrap flex-row flex-1 min-w-fit justify-center items-center gap-3"; + const STYLES_BTN = "cts-remove-word cts-btn-general-compress px-2 cts-btn-error" + + const btn = document.createElement("button"); + btn.setAttribute("data-code", code); + btn.setAttribute("data-word", wordName); + btn.classList.add(...STYLES_BTN.split(" ")); + btn.textContent = "➖"; + + const span = document.createElement("span"); + span.classList.add("item-word"); + span.textContent = wordName; + + const div = document.createElement("div"); + div.setAttribute("id", `word-${code}-${wordName}`); + div.classList.add(...STYLES_DIV.split(" ")); + + div.appendChild(span); + div.appendChild(btn); + + btn.addEventListener("click", (e) => { + removeWord(code, wordName) + div.remove() + }) + + return div; +}; + +/** + * + * @param {HTMLElement} grupContainer + */ +function addListenersButtonQuestionGrup(grupContainer) { + // Function hidden question + grupContainer.querySelector(".cts-question").addEventListener("click", (event) => { + hiddenQuestionRemoveGroup(grupContainer) + }) + + // Function cancel remove + grupContainer.querySelector(".cts-no-remove").addEventListener("click", (event) => { + hiddenQuestionRemoveGroup(grupContainer, false) + }) + + // Function remove grup + grupContainer.querySelector(".cts-remove").addEventListener("click", (event) => { + removeGrup(grupContainer) + }) +} + +/* +//// +////// +//////// Management Options Save and remove groups +////// +//// +*/ + +/** + * + * @param {HTMLDivElement} container + * @param {boolean} hiddenQuestion + */ +function hiddenQuestionRemoveGroup(container, hiddenQuestion = true) { + const question = container.querySelector(".cts-question") + const remove = container.querySelector(".cts-remove") + const noRemove = container.querySelector(".cts-no-remove") + + + if (hiddenQuestion) { + question.classList.add("hidden") + remove.classList.remove("hidden") + noRemove.classList.remove("hidden") + } else { + question.classList.remove("hidden") + remove.classList.add("hidden") + noRemove.classList.add("hidden") + } +} + +function generateSimpleID() { + const first = Date.now().toString(35); + const second = Math.random().toString(36).slice(2); + return first + second; +} + +function showOptionsSave() { + document.getElementById("question-save").classList.add("hidden"); + document.getElementById("finish-session").classList.remove("hidden"); + document.getElementById("cancel-save").classList.remove("hidden"); +} + +function showQuestionSave() { + document.getElementById("question-save").classList.remove("hidden"); + document.getElementById("finish-session").classList.add("hidden"); + document.getElementById("cancel-save").classList.add("hidden"); +} + +document + .getElementById("question-save") + .addEventListener("click", showOptionsSave); + +document + .getElementById("cancel-save") + .addEventListener("click", showQuestionSave); + +/* +//// +////// +//////// Save data +////// +//// +*/ + +async function saveData() { + const keysDataGrups = Object.keys(DATA_GRUPS); + const allCodesProducts = new Set() + + products.forEach((productElement) => { + allCodesProducts.add(productElement.getAttribute("data-code")) + }) + + if (keysDataGrups.length === 0) { + spanNotifaction("No hay grupos para guardar") + return false; + } + + const data = [] + let thereError = false; + const codesProducts = [] + + keysDataGrups.forEach((key) => { + if (thereError) return + + const dataGrup = DATA_GRUPS[key]; + if (!dataGrup) { + spanNotifaction("No hay datos para guardar") + thereError = true; + return + } + + if (dataGrup.products.length === 0) { + spanNotifaction("Los grupos deben tener por lo menos un producto para guardar") + thereError = true; + return + } + + if (dataGrup.words.length === 0) { + spanNotifaction("Los grupos deben tener por lo menos una palabra para guardar") + thereError = true; + return + } + + const words = dataGrup.words; + const products = dataGrup.products; + + codesProducts.push(...products.map((product) => product.code)) + + data.push({ + words, + products + }) + }) + + if (thereError) return false; + + const currentCodesProducts = new Set(codesProducts) + const difference = symmetricDifference(currentCodesProducts, allCodesProducts) + + if (difference.size > 0) { + spanNotifaction("Falta un producto que debe ser ordenado") + return false + } + + const URL = "/cata/testers/api/rating-sort" + const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; + + try { + const response = await fetch(URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": csrfToken, + }, + body: JSON.stringify(data), + }) + + if (!response.ok) { + spanNotifaction("Error en la respuesta del servidor") + return false; + } + + const result = await response.json() + + if (result.error) { + spanNotifaction(result.error) + return false + } else { + spanNotifaction(result.message, false) + return true + } + } catch (error) { + spanNotifaction("Error en proceso de guardar los datos") + return false + } +} + +const buttonSaveData = document.getElementById("save-progress") +buttonSaveData.addEventListener("click", saveData) + +function symmetricDifference(setA, setB) { + let _difference = new Set(setA); + for (let elem of setB) { + if (_difference.has(elem)) { + _difference.delete(elem); + } else { + _difference.add(elem); + } + } + return _difference; +} + +/* +//// +////// +//////// Finish session +////// +//// +*/ + +document + .getElementById("finish-session") + .addEventListener("click", finishSession); + +async function finishSession() { + const save = await saveData(); + if (!save) { + spanNotifaction("Error al guardar los datos") + return + }; + + const FORM_ACTION = document.querySelector(".form-actions") + + const inputAction = FORM_ACTION.querySelector(".action-input"); + FORM_ACTION.action = ""; + inputAction.value = "finish_session"; + FORM_ACTION.submit(); +} \ No newline at end of file diff --git a/tecnicas/static/js/test_pf_rating_list.js b/tecnicas/static/js/test_pf_rating_list.js new file mode 100644 index 0000000000000000000000000000000000000000..08786f95639e7b83568df0cc97b31121f7c9322d --- /dev/null +++ b/tecnicas/static/js/test_pf_rating_list.js @@ -0,0 +1,143 @@ +let dragged = null; +const nextProduct = ` +

Éxito al guardar los datos

+
+ +
`; + +document.querySelectorAll(".draggable").forEach((el) => { + el.addEventListener("dragstart", () => { + dragged = el; + setTimeout(() => el.classList.add("opacity-50"), 0); + }); + + el.addEventListener("dragend", () => { + dragged = null; + el.classList.remove("opacity-50"); + }); +}); + +document.querySelectorAll(".dropzone").forEach((zone) => { + zone.addEventListener("dragover", (e) => e.preventDefault()); + + zone.addEventListener("drop", () => { + zone.appendChild(dragged); + }); +}); + +document + .getElementById("question-save") + .addEventListener("click", showOptionsSave); + +document + .getElementById("cancel-save") + .addEventListener("click", showQuestionSave); + +document.getElementById("save-data").addEventListener("click", async () => { + showLoading(); + const currentDataRatend = []; + + document.querySelectorAll(".dropzone").forEach((zone) => { + const index = parseInt(zone.dataset.index); + const children = zone.querySelectorAll(".draggable"); + + children.forEach((el) => { + currentDataRatend.push({ + product: { + id: el.dataset.idProduct, + code: el.dataset.code, + }, + value: index, + }); + }); + }); + + if (!currentDataRatend.length) { + cancelLoading(); + spanNotifaction("No has ordenado los productos"); + } else if ( + currentDataRatend.length != document.querySelectorAll(".draggable").length + ) { + cancelLoading(); + spanNotifaction("Faltan productos por calificar"); + } else { + saveData(currentDataRatend); + } +}); + +async function saveData(dataToSend = []) { + const URL = "/cata/testers/api/ratingword/pf/list"; + + const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value; + + const requestData = { + phase: parseInt(document.querySelector(".cts-phase-pf").dataset.phase), + word: document.querySelector(".cts-word-rating").dataset.nameWord, + data: dataToSend, + }; + + try { + const response = await fetch(URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": csrfToken, + "X-Requested-With": "XMLHttpRequest", + }, + body: JSON.stringify(requestData), + }); + + if (!response.ok) { + spanNotifaction("Fallo con la respuesta recibida"); + cancelLoading(); + return false; + } + + const result = await response.json(); + + const messError = result.error; + + if (messError) { + spanNotifaction(messError); + cancelLoading(); + return false; + } + + spanNotifaction(result.message, false); + const containerRatings = document.querySelector(".container-rating-word"); + containerRatings.innerHTML = ""; + containerRatings.innerHTML = nextProduct; + cancelLoading(); + return true; + } catch (error) { + console.log(error); + cancelLoading(); + spanNotifaction("¡Oh! Ocurrió un error al tratar de guardar los datos"); + return false; + } +} + +function showLoading() { + document.getElementById("save-data").classList.add("hidden"); + document.getElementById("cancel-save").classList.add("hidden"); + document.getElementById("loading-data-save").classList.remove("hidden"); +} + +function cancelLoading() { + document.getElementById("loading-data-save").classList.add("hidden"); + document.getElementById("question-save").classList.remove("hidden"); +} + +function showOptionsSave() { + document.getElementById("question-save").classList.add("hidden"); + document.getElementById("save-data").classList.remove("hidden"); + document.getElementById("cancel-save").classList.remove("hidden"); +} + +function showQuestionSave() { + document.getElementById("question-save").classList.remove("hidden"); + document.getElementById("save-data").classList.add("hidden"); + document.getElementById("cancel-save").classList.add("hidden"); +} diff --git a/tecnicas/templates/tecnicas/components/dialog-nap-puf.html b/tecnicas/templates/tecnicas/components/dialog-nap-puf.html new file mode 100644 index 0000000000000000000000000000000000000000..2193f9c575d94331b3c3396ebebc87db000f94ed --- /dev/null +++ b/tecnicas/templates/tecnicas/components/dialog-nap-puf.html @@ -0,0 +1,28 @@ + + + + \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/dialog-nap-sort.html b/tecnicas/templates/tecnicas/components/dialog-nap-sort.html new file mode 100644 index 0000000000000000000000000000000000000000..b44d008d3907574f2452164010d50a578dfdc15e --- /dev/null +++ b/tecnicas/templates/tecnicas/components/dialog-nap-sort.html @@ -0,0 +1,29 @@ + + + + \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/form-scale-continue.html b/tecnicas/templates/tecnicas/components/form-scale-continue.html index 81d4ceb823db8a5a565bb848a770d9f01317b36c..876422be1df76a9823274e613b72165516c84cd7 100644 --- a/tecnicas/templates/tecnicas/components/form-scale-continue.html +++ b/tecnicas/templates/tecnicas/components/form-scale-continue.html @@ -6,10 +6,11 @@ class="text-xl font-bold tracking-wide block mb-6 first-letter:uppercase">{{ word }} - +
- diff --git a/tecnicas/templates/tecnicas/components/item-list-words.html b/tecnicas/templates/tecnicas/components/item-list-words.html new file mode 100644 index 0000000000000000000000000000000000000000..49b494aaaebad1b5f543929b37b2fe621ca290ba --- /dev/null +++ b/tecnicas/templates/tecnicas/components/item-list-words.html @@ -0,0 +1,8 @@ +
+

{{ list_tester.username }}

+
    + {% for word in list_tester.words %} +
  • {{ word.nombre_palabra }}
  • + {% endfor %} +
+
\ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/item_vocabulary.html b/tecnicas/templates/tecnicas/components/item_vocabulary.html index 1d2a12ce73e3203a6fc33a767ebfe3fa68c78b73..d822f63671a342a98ed6b48c1771142dcc2daedd 100644 --- a/tecnicas/templates/tecnicas/components/item_vocabulary.html +++ b/tecnicas/templates/tecnicas/components/item_vocabulary.html @@ -10,9 +10,9 @@
- +
diff --git a/tecnicas/templates/tecnicas/components/table-napping-combined.html b/tecnicas/templates/tecnicas/components/table-napping-combined.html new file mode 100644 index 0000000000000000000000000000000000000000..62e2d614755e27193e4a7572388a764b7de79540 --- /dev/null +++ b/tecnicas/templates/tecnicas/components/table-napping-combined.html @@ -0,0 +1,174 @@ +{% load static %} +{% load custom_filters %} +
+ +
+

Información de la Sesión B

+
+

Código: {{ session_b.codigo_sesion }}

+

Técnica: {{ technique_type }}

+

Nombre: {{ session_b.nombre_sesion|default:"Sin nombre" }}

+

Catadores: {{ testers|length }}

+ + {% if combined_data.vocabulary_info %} +

+ Vocabulario: {{ combined_data.vocabulary_info.nombre }} +

+ {% endif %} + + {% if combined_data.scale_info %} +

Escala: {{ combined_data.scale_info.type }}

+

Longitud: {{ combined_data.scale_info.size }}

+ {% endif %} + + {% if combined_data.num_repetitions %} +

Repeticiones: {{ combined_data.num_repetitions }}

+ {% endif %} + +

+ Palabras utilizadas: + {{ combined_data.all_words|join:", " }} +

+
+
+ + +
+ + + + + {% for tester in testers %} + + + {% endfor %} + {% for word in combined_data.all_words %} + + {% endfor %} + + + + {% for product, coordinates_tester in coordinates_no_mode.items %} + + + {% for tester in testers %} + {% with points=coordinates_tester|get_item:tester.user.username %} + + + {% endwith %} + {% endfor %} + {% for word in combined_data.all_words %} + + {% endfor %} + + {% endfor %} + +
Producto + X{{ forloop.counter }} + + Y{{ forloop.counter }} + + {{ word }} +
{{ product }} + {{ points.px }} + + {{ points.py }} + + {% if combined_data.word_frequencies %} + {% with freq=combined_data.word_frequencies|get_item:product|get_item:word %} + {% if freq %}{{ freq }}{% else %}0{% endif %} + {% endwith %} + {% elif combined_data.word_averages %} + {% with avg=combined_data.word_averages|get_item:product|get_item:word %} + {% if avg %}{{ avg|floatformat:2 }}{% else %}0.00{% endif %} + {% endwith %} + {% else %} + 0 + {% endif %} +
+
+ +
+ +
+
+ + diff --git a/tecnicas/templates/tecnicas/components/table-napping-no-mode.html b/tecnicas/templates/tecnicas/components/table-napping-no-mode.html new file mode 100644 index 0000000000000000000000000000000000000000..956929c5b6cc5893bfaff8c37baba8f9a89f78f0 --- /dev/null +++ b/tecnicas/templates/tecnicas/components/table-napping-no-mode.html @@ -0,0 +1,51 @@ +{% load custom_filters %} +
+

+ Datos de Napping sin modalidad +

+ +
+ + + + + {% for tester in testers %} + + + {% endfor %} + + + + {% for product, coordinates_tester in coordinates_no_mode.items %} + + + {% for tester in testers %} + {% with points=coordinates_tester|get_item:tester.user.username %} + + + {% endwith %} + {% endfor %} + + {% endfor %} + +
Producto + X{{ forloop.counter }} + + Y{{ forloop.counter }} +
{{ product }} + {{ points.px }} + + {{ points.py }} +
+
+ +
+ +
+
+ +{% load static %} + \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/table-napping-puf.html b/tecnicas/templates/tecnicas/components/table-napping-puf.html new file mode 100644 index 0000000000000000000000000000000000000000..53b242dcd4ee59f673e543c55231245661ae906f --- /dev/null +++ b/tecnicas/templates/tecnicas/components/table-napping-puf.html @@ -0,0 +1,63 @@ +{% load custom_filters %} +
+

+ Datos de Napping con Perfil Ultra Flash +

+ +
+ + + + + {% for tester in testers %} + + + {% endfor %} + {% for word in all_words %} + + {% endfor %} + + + + {% for product, coordinates_tester in coordinates_no_mode.items %} + + + {% for tester in testers %} + {% with points=coordinates_tester|get_item:tester.user.username %} + + + {% endwith %} + {% endfor %} + {% for word in all_words %} + {% with word_freq=word_frequencies|get_item:product|get_item:word %} + + {% endwith %} + {% endfor %} + + {% endfor %} + +
Producto + X{{ forloop.counter }} + + Y{{ forloop.counter }} + + {{ word }} +
{{ product }} + {{ points.px }} + + {{ points.py }} + + {% if word_freq %}{{ word_freq }}{% else %}0{% endif %} +
+
+ +
+ +
+
+ +{% load static %} + diff --git a/tecnicas/templates/tecnicas/components/table-napping-sorting.html b/tecnicas/templates/tecnicas/components/table-napping-sorting.html new file mode 100644 index 0000000000000000000000000000000000000000..eb0e60b27d533d94e2e6d2e085f7a350495d235a --- /dev/null +++ b/tecnicas/templates/tecnicas/components/table-napping-sorting.html @@ -0,0 +1,57 @@ +{% load custom_filters %} +
+

+ Datos de Napping con Sorting +

+ +
+ + + + + {% for tester in testers %} + + + + {% endfor %} + + + + {% for product, data_per_tester in sorting_data.items %} + + + {% for tester in testers %} + {% with data=data_per_tester|get_item:tester.user.username %} + + + + {% endwith %} + {% endfor %} + + {% endfor %} + +
Producto + X{{ forloop.counter }} + + Y{{ forloop.counter }} + + C{{ forloop.counter }} +
{{ product }} + {{ data.px }} + + {{ data.py }} + + {{ data.words }} +
+
+ +
+ +
+
+ +{% load static %} + diff --git a/tecnicas/templates/tecnicas/components/table-convencional.html b/tecnicas/templates/tecnicas/components/table-scales.html similarity index 85% rename from tecnicas/templates/tecnicas/components/table-convencional.html rename to tecnicas/templates/tecnicas/components/table-scales.html index 0f11db4798200c6e318362f83ad5c77c6332333d..8c331debc39b94332fda6935ed2d4a825b6e45e8 100644 --- a/tecnicas/templates/tecnicas/components/table-convencional.html +++ b/tecnicas/templates/tecnicas/components/table-scales.html @@ -1,10 +1,12 @@ {% load static %}
- +
+ {% if sesion.tecnica.tipo_tecnica.nombre_tecnica != "rata" %} + {% endif %} {% for palabra in palabras %} @@ -17,7 +19,9 @@ {% for usuario, productos in catadores.items %} {% for codigo, valores in productos.items %} - + {% if sesion.tecnica.tipo_tecnica.nombre_tecnica != "rata" %} + + {% endif %} {% for palabra in palabras %} diff --git a/tecnicas/templates/tecnicas/components/table-sort.html b/tecnicas/templates/tecnicas/components/table-sort.html new file mode 100644 index 0000000000000000000000000000000000000000..9a4eb37a7d76ae20e8817c6301d2af1f53c6b666 --- /dev/null +++ b/tecnicas/templates/tecnicas/components/table-sort.html @@ -0,0 +1,42 @@ +{% load custom_filters %} +{% load static %} +
+
+
RepeticiónUsuario Producto
{{ repeticion }}{{ repeticion }}{{ usuario }} {{ codigo }}
+ + + + {% for tester in data_groups.testers %} + + {% endfor %} + + + + {% for data_words in data_groups.data %} + + + {% for tester in data_groups.testers %} + + {% endfor %} + + {% endfor %} + +
Producto + Consumidor {{ forloop.counter }}: {{ tester }} +
{{ data_words.codigo_producto }} + + {% for word in data_words.palabras|get_item:tester %} + {{word}}{% if not forloop.last %} ;{% endif %} + {% endfor %} +
+
+ +
+ +
+
+ + \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/table_cata.html b/tecnicas/templates/tecnicas/components/table_cata.html new file mode 100644 index 0000000000000000000000000000000000000000..95f8ac78dad88cd38c7955ce18324bef2c131fea --- /dev/null +++ b/tecnicas/templates/tecnicas/components/table_cata.html @@ -0,0 +1,50 @@ +{% load static %} +
+
+ + + + + + {% for palabra in palabras %} + + {% endfor %} + + + + {% for repeticion, catadores in calificaciones.items %} + {% for usuario, productos in catadores.items %} + {% for codigo, valores in productos.items %} + + + + {% for palabra in palabras %} + + {% endfor %} + + {% endfor %} + {% endfor %} + {% endfor %} + +
UsuarioProducto{{ palabra }}
{{ usuario }}{{ codigo }} + {% for valor in valores %} + {% if valor.nombre_palabra == palabra %} + {% if valor.dato_valor %} + 1 + {% else %} + 0 + {% endif %} + {% endif %} + {% endfor %} +
+
+ +
+ +
+
+ \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/table_pf.html b/tecnicas/templates/tecnicas/components/table_pf.html new file mode 100644 index 0000000000000000000000000000000000000000..6a4bff6e25eefdd0f0ab5447fcdce4a7138208f4 --- /dev/null +++ b/tecnicas/templates/tecnicas/components/table_pf.html @@ -0,0 +1,39 @@ +{% load static %} + +
+

Datos de usuario {{ data.tester }}

+ +
+ + + + + + {% for word in data.words %} + + {% endfor %} + + + + {% for repeticion, data_product in data.ratings.items %} + {% for codigo, valores in data_product.items %} + + + + {% for word in data.words %} + + {% endfor %} + + {% endfor %} + {% endfor %} + +
RepeticiónProducto{{ word.nombre_palabra }}
{{ repeticion }}{{ codigo }} + {% for valor in valores %} + {% if valor.nombre_palabra == word.nombre_palabra %} + {{ valor.dato_valor }} + {% endif %} + {% endfor %} +
+
+
\ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/table_pf_all.html b/tecnicas/templates/tecnicas/components/table_pf_all.html new file mode 100644 index 0000000000000000000000000000000000000000..bb326d7a0a96120b444debd5104377efda3dae07 --- /dev/null +++ b/tecnicas/templates/tecnicas/components/table_pf_all.html @@ -0,0 +1,51 @@ +{% load custom_filters %} +
+
+ + + + + {% for user_data in second_phase %} + + {% endfor %} + + + {% for user_data in second_phase %} + {% for word in user_data.words %} + + {% endfor %} + {% endfor %} + + + + {% for product_code, users_ratings in data_ratings.items %} + + + + {% for user_data in second_phase %} + {% with user_ratings=users_ratings|get_item:user_data.username %} + {% for word in user_data.words %} + + {% endfor %} + {% endwith %} + {% endfor %} + + {% endfor %} + +
Producto + {{ user_data.username }} +
+ {{ word.nombre_palabra }} +
{{ product_code }} + {% if user_ratings %} + {% for rating in user_ratings %} + {% if rating.palabra == word.nombre_palabra %} + {{ rating.valor }} + {% endif %} + {% endfor %} + {% else %} + - + {% endif %} +
+
+
\ No newline at end of file diff --git a/tecnicas/templates/tecnicas/components/toast-container.html b/tecnicas/templates/tecnicas/components/toast-container.html new file mode 100644 index 0000000000000000000000000000000000000000..66d06ffe54f987e33fb6218bb7dfd01759e47280 --- /dev/null +++ b/tecnicas/templates/tecnicas/components/toast-container.html @@ -0,0 +1,3 @@ +{% load static %} +
+ \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html b/tecnicas/templates/tecnicas/create_sesion/conf-panel-basic.html similarity index 96% rename from tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html rename to tecnicas/templates/tecnicas/create_sesion/conf-panel-basic.html index 7a0d941c37a532ac166f4363aa7946fff7dcc60c..cffbfc95bc88c5e475147a339e77c74336967b86 100644 --- a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-basic.html +++ b/tecnicas/templates/tecnicas/create_sesion/conf-panel-basic.html @@ -21,14 +21,14 @@

Información Basica

{% if use_technique == 'escalas' %}
diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-codes.html b/tecnicas/templates/tecnicas/create_sesion/conf-panel-codes.html similarity index 100% rename from tecnicas/templates/tecnicas/create_sesion/configuracion-panel-codes.html rename to tecnicas/templates/tecnicas/create_sesion/conf-panel-codes.html diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-tags.html b/tecnicas/templates/tecnicas/create_sesion/conf-panel-tags.html similarity index 100% rename from tecnicas/templates/tecnicas/create_sesion/configuracion-panel-tags.html rename to tecnicas/templates/tecnicas/create_sesion/conf-panel-tags.html diff --git a/tecnicas/templates/tecnicas/create_sesion/configuracion-panel-words.html b/tecnicas/templates/tecnicas/create_sesion/conf-panel-words.html similarity index 100% rename from tecnicas/templates/tecnicas/create_sesion/configuracion-panel-words.html rename to tecnicas/templates/tecnicas/create_sesion/conf-panel-words.html diff --git a/tecnicas/templates/tecnicas/create_sesion/creando_sesion.html b/tecnicas/templates/tecnicas/create_sesion/creating_session.html similarity index 100% rename from tecnicas/templates/tecnicas/create_sesion/creando_sesion.html rename to tecnicas/templates/tecnicas/create_sesion/creating_session.html diff --git a/tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html b/tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html index e46054495aea374fd0d9711c98b4b3dbe13f8a33..99bb5c41f64c6848fe94599a0fcb6566ec495fc8 100644 --- a/tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html +++ b/tecnicas/templates/tecnicas/create_sesion/panel-basic-cata.html @@ -22,15 +22,15 @@
- {% include "../components/table-convencional.html" with calificaciones=calificaciones palabras=palabras sesion=sesion %} + {% include "../components/table-scales.html" with calificaciones=calificaciones palabras=palabras sesion=sesion %} {% else %} {% include "../components/error-message.html" with message='Sin calificaciones que mostrar aún' %} diff --git a/tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html b/tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html new file mode 100644 index 0000000000000000000000000000000000000000..4af4114729217f2448b64748d59ad388c28cae5f --- /dev/null +++ b/tecnicas/templates/tecnicas/manage_sesions/monitor-session-pf.html @@ -0,0 +1,126 @@ +{% extends 'tecnicas/layouts/base.html' %} +{% load static %} + +{% block title %}Monitoreo{% endblock %} + +{% block content %} +
+
+
+

Sesión sensorial en curso

+
+ +
+

+ Código de sesión:
+ {{ code_session }} +

+
+ + + + +
+
+ + + + {% if error %} + {% include "../components/error-message.html" with message=error %} + {% endif %} + + {% if message %} + {% include "../components/error-message.html" with message=message %} + {% endif %} + +
+
+ +

+ Catadores activos +

+
+ +
+
+

Participantes

+
+
+
+ Máximo: + {{ max_testers }} +
+
+ + Actuales: + + {{ current_testers }} +
+
+ Activos: + {{ active_testers }} +
+
+
+ +
+
+
+
Usuario
+
Nombre
+
Estado
+
Finalizado
+
+ +
    + {% for parti in participations %} +
  • +

    {{ parti.catador.user.username }}

    +

    + {{ parti.catador.user.first_name }} + {{ parti.catador.user.last_name}} +

    + + {% if parti.activo %} +

    Activo

    + {% else %} +

    No activo

    + {% endif %} + + {% if parti.finalizado %} +

    Si

    + {% else %} +

    No

    + {% endif %} +
  • + {% endfor %} +
+
+
+
+
+
+{% endblock %} + +{% block extra_js %} + + +{% endblock %} \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/manage_sesions/monitor-session-sort.html b/tecnicas/templates/tecnicas/manage_sesions/monitor-session-sort.html new file mode 100644 index 0000000000000000000000000000000000000000..30c9234ba447f03a643deb32056ee760c1bfc6d8 --- /dev/null +++ b/tecnicas/templates/tecnicas/manage_sesions/monitor-session-sort.html @@ -0,0 +1,126 @@ +{% extends 'tecnicas/layouts/base.html' %} +{% load static %} + +{% block title %}Monitoreo{% endblock %} + +{% block content %} +
+
+
+

Sesión sensorial en curso

+
+ +
+

+ Código de sesión:
+ {{ code_session }} +

+
+ + + + +
+
+ + + + {% if error %} + {% include "../components/error-message.html" with message=error %} + {% endif %} + + {% if message %} + {% include "../components/error-message.html" with message=message %} + {% endif %} + +
+
+ +

+ Catadores activos +

+
+ +
+
+

Participantes

+
+
+
+ Máximo: + {{ max_testers }} +
+
+ + Actuales: + + {{ current_testers }} +
+
+ Activos: + {{ active_testers }} +
+
+
+ +
+
+
+
Usuario
+
Nombre
+
Estado
+
Finalizado
+
+ +
    + {% for parti in participations %} +
  • +

    {{ parti.catador.user.username }}

    +

    + {{ parti.catador.user.first_name }} + {{ parti.catador.user.last_name}} +

    + + {% if parti.activo %} +

    Activo

    + {% else %} +

    No activo

    + {% endif %} + + {% if parti.finalizado %} +

    Si

    + {% else %} +

    No

    + {% endif %} +
  • + {% endfor %} +
+
+
+
+
+
+{% endblock %} + +{% block extra_js %} + + +{% endblock %} \ No newline at end of file diff --git a/tecnicas/templates/tecnicas/manage_sesions/sesiones-panel.html b/tecnicas/templates/tecnicas/manage_sesions/sessions-panel.html similarity index 100% rename from tecnicas/templates/tecnicas/manage_sesions/sesiones-panel.html rename to tecnicas/templates/tecnicas/manage_sesions/sessions-panel.html diff --git a/tecnicas/templates/tecnicas/manage_tester/catador-crear.html b/tecnicas/templates/tecnicas/manage_tester/tester-create.html similarity index 100% rename from tecnicas/templates/tecnicas/manage_tester/catador-crear.html rename to tecnicas/templates/tecnicas/manage_tester/tester-create.html diff --git a/tecnicas/templates/tecnicas/manage_tester/catador-lista.html b/tecnicas/templates/tecnicas/manage_tester/tester-list.html similarity index 100% rename from tecnicas/templates/tecnicas/manage_tester/catador-lista.html rename to tecnicas/templates/tecnicas/manage_tester/tester-list.html diff --git a/tecnicas/templates/tecnicas/manage_tester/catador-buscar.html b/tecnicas/templates/tecnicas/manage_tester/tester-search.html similarity index 100% rename from tecnicas/templates/tecnicas/manage_tester/catador-buscar.html rename to tecnicas/templates/tecnicas/manage_tester/tester-search.html diff --git a/tecnicas/templates/tecnicas/manage_tester/catadores-panel.html b/tecnicas/templates/tecnicas/manage_tester/testers-panel.html similarity index 100% rename from tecnicas/templates/tecnicas/manage_tester/catadores-panel.html rename to tecnicas/templates/tecnicas/manage_tester/testers-panel.html diff --git a/tecnicas/templates/tecnicas/manage_vocabulary/create-vocabulary.html b/tecnicas/templates/tecnicas/manage_vocabulary/create-vocabulary.html index 0019103df764f6bb4b82ec4b7b187593a69c96a0..c21ccecaae9d3173f9195b1ccac0aa9d09041b6b 100644 --- a/tecnicas/templates/tecnicas/manage_vocabulary/create-vocabulary.html +++ b/tecnicas/templates/tecnicas/manage_vocabulary/create-vocabulary.html @@ -26,15 +26,9 @@
{% csrf_token %} - {% if name_vacabulary %} - - {% else %} - {% endif %}

Debajo puede buscar y seleccionar palabras para asignarlas a este vocabulario.

@@ -105,22 +99,11 @@

- {% if name_vacabulary %} - - {% endif %} -
- {% if name_vacabulary %} - - {% else %} - {% endif %}
- varias personas en fila
diff --git a/tecnicas/templates/tecnicas/manage_vocabulary/view-vocabulary.html b/tecnicas/templates/tecnicas/manage_vocabulary/view-vocabulary.html new file mode 100644 index 0000000000000000000000000000000000000000..52a4be84d6f5d0cb7207d67c060bf17ca43b617b --- /dev/null +++ b/tecnicas/templates/tecnicas/manage_vocabulary/view-vocabulary.html @@ -0,0 +1,85 @@ +{% extends 'tecnicas/layouts/base.html' %} +{% load static %} + +{% block title %}Ver Vocabulario{% endblock %} + +{% block content %} +
+
+
+
+

Detalles del Vocabulario

+
+ +
+ + {% if error %} + {% include "../components/error-message.html" with message=error %} + + + + {% else %} +
+ +
+
+

Nombre del Vocabulario:

+

{{ vocabulary.nombre_vocabulario }}

+
+ +
+

Fecha de Creación:

+

{{ vocabulary.creado|date:"d/m/Y H:i" }}

+
+ +
+

Total de Palabras:

+

{{ words|length }}

+
+
+ + +
+

Palabras del Vocabulario

+
+ + {% if words %} +
+ {% for word in words %} +
+ {{ word.nombre_palabra }} +
+ {% endfor %} +
+ {% else %} +
+

Este vocabulario no tiene palabras asociadas

+
+ {% endif %} +
+
+ + + + {% endif %} +
+
+{% endblock %} diff --git a/tecnicas/templatetags/__init__.py b/tecnicas/templatetags/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tecnicas/templatetags/custom_filters.py b/tecnicas/templatetags/custom_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..4b04b5c6456e08fae5c2cca82c5663a4c3fb9cf5 --- /dev/null +++ b/tecnicas/templatetags/custom_filters.py @@ -0,0 +1,7 @@ +from django import template + +register = template.Library() + +@register.filter +def get_item(dictionary, key): + return dictionary.get(key) diff --git a/tecnicas/urls.py b/tecnicas/urls.py index f8dee7d53ed3d8462fa2cd671aa22678a52135c2..2c058c271ca04cd1ebc2d6fb3fca60ac8caf91e1 100644 --- a/tecnicas/urls.py +++ b/tecnicas/urls.py @@ -53,6 +53,10 @@ urlpatterns = [ views.createVocabulary, name="crear_vocabulario"), + path("presenter/ver-vocabulario/", + views.viewVocabulary, + name="ver_vocabulario"), + path("presenter/lista-vocabulario/", views.listVocabulary, name="lista_vocabulario"), @@ -110,6 +114,22 @@ urlpatterns = [ views.convencionalScales, name="session_convencional"), + path("testers/init-session//cata", + views.cataTest, + name="session_cata"), + + path("testers/init-session//perfil-flash", + views.pfTest, + name="session_pf"), + + path("testers/init-session//sort", + views.sortTest, + name="session_sort"), + + path("testers/init-session//nappping", + views.nappingTest, + name="session_napping"), + # APIs path("presenter/api/nueva-etiqueta", @@ -124,7 +144,23 @@ urlpatterns = [ views.wordsVocabulary, name="api_palabras_vocabulary"), - path("testers/api/ratingword", - views.reatingWord, - name="api_rating_word"), + path("testers/api/ratingword/escalas", + views.ratingWordScales, + name="api_rating_word_scalas"), + + path("testers/api/ratingword/cata", + views.ratingWordCata, + name="api_rating_word_cata"), + + path("testers/api/ratingword/pf/list", + views.apiListWordsPF, + name="api_rating_word_pf_list"), + + path("testers/api/rating-sort", + views.ratingSort, + name="api_rating_sort"), + + path("testers/api/rating-napping", + views.ratingNapping, + name="api_rating_napping"), ] diff --git a/tecnicas/views/__init__.py b/tecnicas/views/__init__.py index 517caa7d18822de84228be05eec15659aeff42ce..e8bd6a73813b576a16379f50464cd57648b14634 100644 --- a/tecnicas/views/__init__.py +++ b/tecnicas/views/__init__.py @@ -20,15 +20,24 @@ from .tester_management.tester_list import testerList from .vocabulary_management.vocabulry_menu import vocabularyMenu from .vocabulary_management.create_vocabulary import createVocabulary +from .vocabulary_management.view_vocabulary import viewVocabulary from .vocabulary_management.list_vocabulary import listVocabulary from .apis.api_tag import newTag from .apis.api_words import words from .apis.api_words import wordsVocabulary -from .apis.rating_word import reatingWord +from .apis.api_list_words_pf import apiListWordsPF +from .apis.rating_word_scales import ratingWordScales +from .apis.rating_word_cata import ratingWordCata +from .apis.rating_sort import ratingSort +from .apis.rating_napping import ratingNapping from .tester_forms.init_tester_form import initTesterForm -from .tester_forms.convencional_scales import convencionalScales from .tester_forms.panel_main_tester import mainPanelTester from .tester_forms.login_session import loginSessionTester from .tester_forms.sessions_list_tester import sessionsListTester +from .tester_forms.convencional_scales import convencionalScales +from .tester_forms.cata_test import cataTest +from .tester_forms.pf_test import pfTest +from .tester_forms.sort_test import sortTest +from .tester_forms.napping_test import nappingTest diff --git a/tecnicas/views/apis/api_list_words_pf.py b/tecnicas/views/apis/api_list_words_pf.py new file mode 100644 index 0000000000000000000000000000000000000000..431543c1ac3b447f6b86052e0a908207e5dc8f99 --- /dev/null +++ b/tecnicas/views/apis/api_list_words_pf.py @@ -0,0 +1,30 @@ +from django.http import HttpRequest, JsonResponse +from tecnicas.utils import general_error +from tecnicas.controllers import RatingPFListController +import json + + +def apiListWordsPF(req: HttpRequest): + if req.method == "GET": + return RatingPFListController.getListWords(request=req) + elif req.method == "POST": + try: + data = json.loads(req.body.decode("utf-8")) + phase = data.get("phase", []) + if phase == 1 or phase == 2: + raw_words = data.get("words", []) + response = RatingPFListController.saveList( + request=req, current_phase=phase, words=raw_words) + + elif phase >= 3: + word = data.get("word", []) + raw_data = data.get("data", []) + response = RatingPFListController.saveRatings( + request=req, word_rating=word, data=raw_data) + + return response + except Exception as e: + print("Error:", e) + return JsonResponse({"error": "Error procesando datos"}, status=400) + else: + return JsonResponse({"error": "Método no permitido"}, status=405) diff --git a/tecnicas/views/apis/rating_napping.py b/tecnicas/views/apis/rating_napping.py new file mode 100644 index 0000000000000000000000000000000000000000..5ef98c2ab4b4d613a1f8132c6c55ecc4a77e0c90 --- /dev/null +++ b/tecnicas/views/apis/rating_napping.py @@ -0,0 +1,16 @@ +from django.http import HttpRequest, JsonResponse +from tecnicas.controllers import RatingNappingController +import json + + +def ratingNapping(req: HttpRequest): + if req.method == "POST": + try: + data = json.loads(req.body.decode("utf-8")) + response = RatingNappingController.saveRatingCoordinates( + request=req, data=data) + return response + except Exception as e: + return JsonResponse({"error": "Error al procesar datos"}) + else: + return JsonResponse({"error": "Método no permitido"}) diff --git a/tecnicas/views/apis/rating_sort.py b/tecnicas/views/apis/rating_sort.py new file mode 100644 index 0000000000000000000000000000000000000000..ff6cd36718a52cf8a2569142e9ca70ae4f48fbb8 --- /dev/null +++ b/tecnicas/views/apis/rating_sort.py @@ -0,0 +1,16 @@ +from django.http import HttpRequest, JsonResponse +from tecnicas.controllers import RatingSortController +import json + + +def ratingSort(req: HttpRequest): + if req.method == "POST": + try: + data = json.loads(req.body.decode("utf-8")) + response = RatingSortController.saveRating( + request=req, data=data) + return response + except Exception as e: + return JsonResponse({"error": "Error al procesar datos"}) + else: + return JsonResponse({"error": "Método no permitido"}) diff --git a/tecnicas/views/apis/rating_word_cata.py b/tecnicas/views/apis/rating_word_cata.py new file mode 100644 index 0000000000000000000000000000000000000000..4524703dae0396e344480864f5432b7db6153b3e --- /dev/null +++ b/tecnicas/views/apis/rating_word_cata.py @@ -0,0 +1,21 @@ +from django.http import HttpRequest, JsonResponse +from tecnicas.utils import general_error +from tecnicas.controllers import RatingCataController +import json + + +def ratingWordCata(req: HttpRequest): + if req.method == "POST": + try: + data = json.loads(req.body.decode("utf-8")) + raw_words = data.get("words", []) + raw_product = data.get("product", []) + + response = RatingCataController.saveRatingWords( + request=req, data_words=raw_words, data_prodct=raw_product) + return response + except Exception as e: + print("Error:", e) + return JsonResponse({"error": "Error procesando datos"}, status=400) + else: + return JsonResponse({"error": "Método no permitido"}, status=405) diff --git a/tecnicas/views/apis/rating_word.py b/tecnicas/views/apis/rating_word_scales.py similarity index 92% rename from tecnicas/views/apis/rating_word.py rename to tecnicas/views/apis/rating_word_scales.py index a142a7dd0823188241e4d7e217bae3b4c8fcea86..98f5ea70c64979f1a52919b4e6c87441b246c512 100644 --- a/tecnicas/views/apis/rating_word.py +++ b/tecnicas/views/apis/rating_word_scales.py @@ -28,12 +28,12 @@ * Calquier otro metodo que se maneje mandar un error ''' from django.http import HttpRequest, JsonResponse -from tecnicas.controllers import ApiRatingController, CalificacionController, DatoController +from tecnicas.controllers import RatingScalesController, CalificacionController, DatoController from tecnicas.utils import general_error import json -def reatingWord(req: HttpRequest): +def ratingWordScales(req: HttpRequest): if req.method == "POST": if not req.POST["rating-word"] or not req.POST["id-word"] or not req.POST["id-product"]: return JsonResponse({"error": "No se mandó información necesaria para la calificación"}) @@ -43,7 +43,7 @@ def reatingWord(req: HttpRequest): received_id_product = json.loads(req.POST.get("id-product")) id_technique = json.loads(req.POST.get("id-technique")) - view_controller = ApiRatingController( + view_controller = RatingScalesController( rating_controller=CalificacionController( technique=id_technique, product=received_id_product, @@ -60,4 +60,4 @@ def reatingWord(req: HttpRequest): return JsonResponse(response_data) else: - return general_error("No puede usar este método aquí") + return general_error("No puede usar este método aquí") \ No newline at end of file diff --git a/tecnicas/views/sessions_config/configuration_panel_basic.py b/tecnicas/views/sessions_config/configuration_panel_basic.py index d03f2a693a27e9855a01fc10668efdd37d1dceb3..fc0f9f9632e3534d9a0bbe895cd94de11a98792c 100644 --- a/tecnicas/views/sessions_config/configuration_panel_basic.py +++ b/tecnicas/views/sessions_config/configuration_panel_basic.py @@ -17,6 +17,12 @@ def configurationPanelBasic(req: HttpRequest): response = PanelBasicController.controllGetRATA(request=req) elif name_tecnica == "cata": response = PanelBasicController.controllGetCATA(request=req) + elif name_tecnica == "perfil flash": + response = PanelBasicController.controllGetPF(request=req) + elif name_tecnica == "sort": + response = PanelBasicController.controllGetSort(request=req) + elif name_tecnica == "napping": + response = PanelBasicController.controllGetNapping(request=req) else: response = redirect( reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida o sin implementar") @@ -34,6 +40,15 @@ def configurationPanelBasic(req: HttpRequest): elif name_tecnica == "cata": response = PanelBasicController.controllPostCATA( request=req, name_tecnica=name_tecnica) + elif name_tecnica == "perfil flash": + response = PanelBasicController.controllPostPF( + request=req, name_tecnica=name_tecnica) + elif name_tecnica == "sort": + response = PanelBasicController.controllPostSort( + request=req, name_tecnica=name_tecnica) + elif name_tecnica == "napping": + response = PanelBasicController.controllPostNapping( + request=req, name_tecnica=name_tecnica) else: response = redirect( reverse("cata_system:seleccion_tecnica") + "?error=¡Oh, vaya! Cambio de técnica repentino, vuelve a elegir otra vez") diff --git a/tecnicas/views/sessions_config/configuration_panel_codes.py b/tecnicas/views/sessions_config/configuration_panel_codes.py index 93ff4913e129ab05be1e5549f98e690a823c1a16..d68d10e94ee79a90ea35b5a1886c453183db5db0 100644 --- a/tecnicas/views/sessions_config/configuration_panel_codes.py +++ b/tecnicas/views/sessions_config/configuration_panel_codes.py @@ -18,12 +18,9 @@ def configurationPanelCodes(req: HttpRequest): if name_technique == "escalas": response = PanelCodesController.controllGetEscalas( req, data_basic) - elif name_technique == "rata": - response = PanelCodesController.controllGetRATA( - req, data_basic) - elif name_technique == "cata": - response = PanelCodesController.controllGetCATA( - req, data_basic) + elif name_technique in ["rata", "cata", "perfil flash", "sort", "napping"]: + response = PanelCodesController.controllGetWithoutOrders( + request=req, data=data_basic, name_technique=name_technique) else: response = redirect( reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida") @@ -33,11 +30,12 @@ def configurationPanelCodes(req: HttpRequest): if name_technique == "escalas": response = PanelCodesController.controllPostEscalas( req, data_basic) - elif name_technique == "rata": - response = PanelCodesController.controllPostRATA(request=req) - elif name_technique == "cata": - response = PanelCodesController.controllPostRATA( - request=req, is_rata=False) + elif name_technique in ["rata", "cata"]: + response = PanelCodesController.controllPostWithWords( + request=req, name_technique=name_technique) + elif name_technique in ["perfil flash", "sort", "napping"]: + response = PanelCodesController.controllPostWithoutOrdersWords( + request=req, name_technique=name_technique) else: response = redirect( reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida") diff --git a/tecnicas/views/sessions_config/configuration_panel_tags.py b/tecnicas/views/sessions_config/configuration_panel_tags.py index 4c727cdc7f25b3ccafad5c31daf55c9106e2d402..ece28c43ed1e95d61c7ec0ea3f276d186bf168f9 100644 --- a/tecnicas/views/sessions_config/configuration_panel_tags.py +++ b/tecnicas/views/sessions_config/configuration_panel_tags.py @@ -12,9 +12,10 @@ def configurationPanelTags(req: HttpRequest): "?error=datos requeridos no encontrados") basic_data = req.session.get("form_basic") + name_technique = basic_data["name_tecnica"] if req.method == "GET": - if basic_data["name_tecnica"] == "escalas" or basic_data["name_tecnica"] == "rata": + if name_technique == "escalas" or name_technique == "rata" or name_technique == "perfil flash": response = PanelTagsController.controllGetEscalas( request=req, data=basic_data) else: @@ -23,7 +24,7 @@ def configurationPanelTags(req: HttpRequest): return response elif req.method == "POST": - if basic_data["name_tecnica"] == "escalas" or basic_data["name_tecnica"] == "rata": + if name_technique == "escalas" or name_technique == "rata" or name_technique == "perfil flash": response = PanelTagsController.controllPostEscalas( request=req, data=basic_data) else: diff --git a/tecnicas/views/sessions_config/configuration_panel_words.py b/tecnicas/views/sessions_config/configuration_panel_words.py index 2898f6309b1e933d44e06d1980edc9a95cf6ce66..5f6d7c661f385ff56ef7838fe8b60801a38566e6 100644 --- a/tecnicas/views/sessions_config/configuration_panel_words.py +++ b/tecnicas/views/sessions_config/configuration_panel_words.py @@ -14,12 +14,10 @@ def configurationPanelWords(req: HttpRequest): basic_data = req.session["form_basic"] name_technique = basic_data["name_tecnica"] - style_words = EstiloPalabra.objects.get( - id=basic_data["estilo_palabras"]).nombre_estilo + style_words = basic_data["estilo_palabras"] if req.method == "GET": if name_technique == "escalas" or name_technique == "rata" or name_technique == "cata": - print() if style_words == "atributos": response = PanelWordsController.controllGetEscalasAtributes( req) diff --git a/tecnicas/views/sessions_config/create_session.py b/tecnicas/views/sessions_config/create_session.py index b9f9371757277bc19918d56da33c33a70938147f..7e6fce964faf7f750f1505c8a9ffc6f00ee4f243 100644 --- a/tecnicas/views/sessions_config/create_session.py +++ b/tecnicas/views/sessions_config/create_session.py @@ -1,7 +1,7 @@ from django.http import HttpRequest, JsonResponse from django.shortcuts import render, redirect from django.urls import reverse -from tecnicas.controllers import PanelCreateController +from tecnicas.controllers import PanelCreateEscalasController, PanelCreateRataController, PanelCreateCataController, PanelCreatePFController, PanelCreateSortController, PanelCreateNappingController from tecnicas.utils import deleteDataSession @@ -15,8 +15,18 @@ def createSession(req: HttpRequest): name_technique = basic_data["name_tecnica"] if req.method == "GET": - if name_technique == "escalas" or name_technique == "rata" or name_technique == "cata": - response = PanelCreateController.controllGetEscalas(req) + if name_technique == "escalas": + response = PanelCreateEscalasController.controllGet(req) + elif name_technique == "rata": + response = PanelCreateRataController.controllGet(req) + elif name_technique == "cata": + response = PanelCreateCataController.controllGet(req) + elif name_technique == "perfil flash": + response = PanelCreatePFController.controllGet(req) + elif name_technique == "sort": + response = PanelCreateSortController.controllGet(req) + elif name_technique == "napping": + response = PanelCreateNappingController.controllGet(req) else: response = redirect( reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida") @@ -24,11 +34,17 @@ def createSession(req: HttpRequest): return response if req.method == "POST": if name_technique == "escalas": - response = PanelCreateController.controllPostEscalas(req) + response = PanelCreateEscalasController.controllPost(req) elif name_technique == "rata": - response = PanelCreateController.controllPostRATA(req) + response = PanelCreateRataController.controllPost(req) elif name_technique == "cata": - response = PanelCreateController.controllPostCATA(req) + response = PanelCreateCataController.controllPost(req) + elif name_technique == "perfil flash": + response = PanelCreatePFController.controllPost(req) + elif name_technique == "sort": + response = PanelCreateSortController.controllPost(req) + elif name_technique == "napping": + response = PanelCreateNappingController.controllPost(req) else: response = redirect( reverse("cata_system:seleccion_tecnica") + "?error=Técnica no valida") diff --git a/tecnicas/views/sessions_config/seleccion_tecnica.py b/tecnicas/views/sessions_config/seleccion_tecnica.py index 3ddd04b309b1be527b98e6e2fee7f9ffebf93dce..e8f09605613f02ef029567e1f95433a3c25467e9 100644 --- a/tecnicas/views/sessions_config/seleccion_tecnica.py +++ b/tecnicas/views/sessions_config/seleccion_tecnica.py @@ -9,6 +9,6 @@ def selecionTecnica(req:HttpRequest): error = error.replace("_", " ") error = error.capitalize() - return render(req, "tecnicas/create_sesion/seleccion-tecnica.html", context={"tipos":tipos, "error":error}) + return render(req, "tecnicas/create_sesion/select-tecnica.html", context={"tipos":tipos, "error":error}) except KeyError: - return render(req, "tecnicas/create_sesion/seleccion-tecnica.html", context={"tipos":tipos}) \ No newline at end of file + return render(req, "tecnicas/create_sesion/select-tecnica.html", context={"tipos":tipos}) \ No newline at end of file diff --git a/tecnicas/views/sessions_management/session_details.py b/tecnicas/views/sessions_management/session_details.py index f292989ee61e4d5964ccfa443e7d0503f350620d..8c7a4f38b108fd2b01cf4929a277bf607f3eb09e 100644 --- a/tecnicas/views/sessions_management/session_details.py +++ b/tecnicas/views/sessions_management/session_details.py @@ -3,7 +3,7 @@ from django.shortcuts import redirect from django.urls import reverse from tecnicas.models import SesionSensorial from tecnicas.utils import noValidTechnique -from ...controllers import DetallesEscalasController +from tecnicas.controllers import DetallesController, DetallesEscalasController, DetallesCATAController, DetallesPFController, DetallesSortController, DetallesNappingController def sessionDetails(req: HttpRequest, session_code: str): @@ -13,15 +13,42 @@ def sessionDetails(req: HttpRequest, session_code: str): else: message = "" - sensorial_session = SesionSensorial.objects.get( - codigo_sesion=session_code) + try: + sensorial_session = SesionSensorial.objects.get( + codigo_sesion=session_code) + except SesionSensorial.DoesNotExist: + return noValidTechnique(params={"page": 1}, query_params={"message": "Sesión no encontrada"}, name_view="cata_system:panel_sesiones") + use_techinique = sensorial_session.tecnica.tipo_tecnica.nombre_tecnica if use_techinique == "escalas" or use_techinique == "rata": controller_view = DetallesEscalasController( session=sensorial_session) - response = controller_view.getResponse( + response = controller_view.controllGetResponse( + request=req, message=message) + + elif use_techinique == "cata": + controller_view = DetallesCATAController( + session=sensorial_session) + response = controller_view.controllGetResponse( request=req, message=message) + + elif use_techinique == "perfil flash": + controller_view = DetallesPFController(session=sensorial_session) + response = controller_view.controllGetResponse( + request=req, message=message) + + elif use_techinique == "sort": + controller_view = DetallesSortController(session=sensorial_session) + response = controller_view.controllGetResponse( + request=req, message=message) + + elif use_techinique == "napping": + controller_view = DetallesNappingController( + session=sensorial_session) + response = controller_view.controllGetResponse( + request=req, message=message) + else: response = noValidTechnique( params={"page": 1}, @@ -30,28 +57,38 @@ def sessionDetails(req: HttpRequest, session_code: str): }, name_view="cata_system:panel_sesiones" ) - + return response + elif req.method == "POST": sensorial_session = SesionSensorial.objects.get( codigo_sesion=session_code) + use_techinique = sensorial_session.tecnica.tipo_tecnica.nombre_tecnica + controller_view = DetallesController(sensorial_session) - if use_techinique == "escalas" or use_techinique == "rata": - controller_view = DetallesEscalasController(sensorial_session) + action = req.POST.get("action") - if req.POST["action"] == "start_session": + if use_techinique in ["escalas", "rata", "cata", "perfil flash", "sort"]: + if action == "start_session": response = controller_view.startRepetition( presenter=req.user.user_presentador, request=req) - elif req.POST.get("action") == "delete_session": + + elif action == "delete_session": controller_view.deleteSesorialSession() response = redirect( reverse("cata_system:panel_sesiones", kwargs={"page": 1})) - else: - response = controller_view.getResponse( - error="No se reconoce la acción a realizar") + + elif use_techinique == "napping": + controller_view = DetallesNappingController( + session=sensorial_session) + + response = controller_view.controllPostResponse( + request=req, action=action) + else: - response = noValidTechnique() + response = controller_view.controllGetResponse( + error="No se reconoce la acción a realizar") return response else: diff --git a/tecnicas/views/sessions_management/session_monitor.py b/tecnicas/views/sessions_management/session_monitor.py index 515f26b29af529c2edb747eb6b1e289f2ae22d04..3cddfc607461d23086687aec758ec7869914aba0 100644 --- a/tecnicas/views/sessions_management/session_monitor.py +++ b/tecnicas/views/sessions_management/session_monitor.py @@ -3,12 +3,11 @@ Para finalizar la sesion se debe realizar lo siguiente # Obtener todas las participaciones ''' - from django.http import HttpRequest, JsonResponse from django.shortcuts import render, redirect from django.urls import reverse from tecnicas.models import SesionSensorial -from tecnicas.controllers import MonitorEscalasController, MonitorRATAController +from tecnicas.controllers import MonitorEscalasController, MonitorRATAController, MonitorPFController, MonitorSortController, MonitorNappingController from tecnicas.utils import noValidTechnique @@ -18,9 +17,22 @@ def sessionMonitor(req: HttpRequest, session_code: str): codigo_sesion=session_code) use_techinique = sensorial_session.tecnica.tipo_tecnica.nombre_tecnica - if use_techinique == "escalas" or use_techinique == "rata": + if use_techinique in ["escalas", "rata", "cata"]: controll_view = MonitorEscalasController(sensorial_session) - response = controll_view.controlGetResponse(request=req) + response = controll_view.controllGetResponse(request=req) + + elif use_techinique == "perfil flash": + controll_view = MonitorPFController(sensorial_session) + response = controll_view.controllGetResponse(request=req) + + elif use_techinique == "sort": + controll_view = MonitorSortController(sensorial_session) + response = controll_view.controllGetResponse(request=req) + + elif use_techinique == "napping": + controll_view = MonitorNappingController(sensorial_session) + response = controll_view.controllGetResponse(request=req) + else: response = noValidTechnique( params={ @@ -31,7 +43,6 @@ def sessionMonitor(req: HttpRequest, session_code: str): }, name_view="cata_system:detalles_sesion" ) - return response elif req.method == "POST": sensorial_session = SesionSensorial.objects.get( @@ -48,7 +59,8 @@ def sessionMonitor(req: HttpRequest, session_code: str): else: response = controll_view.controlGetResponse( request=req, error="No se ha definido la acción a realizar") - elif use_techinique == "rata": + + elif use_techinique == "rata" or use_techinique == "cata": controll_view = MonitorRATAController(sensorial_session) action = req.POST["action"] @@ -58,6 +70,40 @@ def sessionMonitor(req: HttpRequest, session_code: str): else: response = controll_view.controlGetResponse( request=req, error="No se ha definido la acción a realizar") + + elif use_techinique == "perfil flash": + controll_view = MonitorPFController(sensorial_session) + action = req.POST["action"] + + if action == "finish_session": + response = controll_view.controllPostFinishSession( + request=req) + else: + response = controll_view.controlGetResponse( + request=req, error="No se ha definido la acción a realizar") + + elif use_techinique == "sort": + controll_view = MonitorSortController(sensorial_session) + action = req.POST["action"] + + if action == "finish_session": + response = controll_view.controllPostFinishSession( + request=req) + else: + response = controll_view.controlGetResponse( + request=req, error="No se ha definido la acción a realizar") + + elif use_techinique == "napping": + controll_view = MonitorNappingController(sensorial_session) + action = req.POST["action"] + + if action == "finish_session": + response = controll_view.controllPostFinishSession( + request=req) + else: + response = controll_view.controlGetResponse( + request=req, error="No se ha definido la acción a realizar") + else: response = noValidTechnique( params={ diff --git a/tecnicas/views/sessions_management/sessions_panel.py b/tecnicas/views/sessions_management/sessions_panel.py index c64ed878cf2cffd5bdbaf17bbbc6508995789330..690477e7763290161f7e430baa88a997f210fa12 100644 --- a/tecnicas/views/sessions_management/sessions_panel.py +++ b/tecnicas/views/sessions_management/sessions_panel.py @@ -12,7 +12,7 @@ def sesionsPanel(req: HttpRequest, page: int): if isinstance(response, dict): context["error"] = response["error"] - return render(req, "tecnicas/manage_sesions/sesiones-panel.html", context=context) + return render(req, "tecnicas/manage_sesions/sessions-panel.html", context=context) (sessions_in_page, is_last_page, current_page) = response @@ -22,6 +22,6 @@ def sesionsPanel(req: HttpRequest, page: int): if "message" in req.GET: context["message"] = req.GET.get("message") - return render(req, "tecnicas/manage_sesions/sesiones-panel.html", context=context) + return render(req, "tecnicas/manage_sesions/sessions-panel.html", context=context) else: return JsonResponse({"message": "Método no permitido"}) diff --git a/tecnicas/views/tester_forms/cata_test.py b/tecnicas/views/tester_forms/cata_test.py new file mode 100644 index 0000000000000000000000000000000000000000..9e857e3b8093703b146c71571da58082e5a9df59 --- /dev/null +++ b/tecnicas/views/tester_forms/cata_test.py @@ -0,0 +1,11 @@ +from django.http import HttpRequest +from tecnicas.models import SesionSensorial +from tecnicas.controllers import TestCataController + + +def cataTest(req: HttpRequest, code_sesion: str): + if req.method == "GET": + session = SesionSensorial.objects.get(codigo_sesion=code_sesion) + controll_view = TestCataController( + sensorial_session=session, user_tester=req.user.user_catador) + return controll_view.controllGet(request=req) diff --git a/tecnicas/views/tester_forms/convencional_scales.py b/tecnicas/views/tester_forms/convencional_scales.py index 7b96ccf80cc28440f313cd18b2978ea4c924b705..06eb8035bbd16134248f67dfca3d7b7cf81f11c0 100644 --- a/tecnicas/views/tester_forms/convencional_scales.py +++ b/tecnicas/views/tester_forms/convencional_scales.py @@ -55,21 +55,26 @@ - Cata segmento en el que se divide debe tener la etiqueda correspondiente por debajo ''' from django.http import HttpRequest -from tecnicas.controllers import SesionController, ConvencionalScalesController +from tecnicas.models import SesionSensorial +from tecnicas.controllers import TestRataController, TestScalesController from tecnicas.utils import noValidTechnique def convencionalScales(req: HttpRequest, code_sesion: str): - session = SesionController.getSessionByCode(code_sesion) + session = SesionSensorial.objects.get(codigo_sesion=code_sesion) type_technique = session.tecnica.tipo_tecnica.nombre_tecnica if req.method == "GET": - view_controller = ConvencionalScalesController( - sensorial_session=session, user_tester=req.user.user_catador) if type_technique == "escalas": - respose = view_controller.controllGetEscalas(request=req) + view_controller = TestScalesController( + sensorial_session=session, user_tester=req.user.user_catador) + respose = view_controller.controllGet(request=req) + elif type_technique == "rata": - respose = view_controller.controllGetRATA(request=req) + view_controller = TestRataController( + sensorial_session=session, user_tester=req.user.user_catador) + respose = view_controller.controllGet(request=req) + else: respose = noValidTechnique( name_view='cata_system:catador_init_session', diff --git a/tecnicas/views/tester_forms/init_tester_form.py b/tecnicas/views/tester_forms/init_tester_form.py index 6fc17607a93660642331df867e119773d67f22b5..9f74a6b922c0ec5401239d18e352357685242dcd 100644 --- a/tecnicas/views/tester_forms/init_tester_form.py +++ b/tecnicas/views/tester_forms/init_tester_form.py @@ -1,35 +1,71 @@ from django.http import HttpRequest, JsonResponse -from django.shortcuts import render, redirect -from django.urls import reverse -from tecnicas.controllers import InitSessionTesterController, ParticipacionController +from django.shortcuts import render +from tecnicas.controllers import InitSessionEscalasController, InitSessionRATAController, InitSessionPFController, InitSessionSortController, InitSessionNappingController from tecnicas.models import SesionSensorial def initTesterForm(req: HttpRequest, code_sesion: str): session = SesionSensorial.objects.get(codigo_sesion=code_sesion) type_technique = session.tecnica.tipo_tecnica.nombre_tecnica - template_url = "tecnicas/forms_tester/init_session.html" - - view_controller = InitSessionTesterController( - sensorial_session=session, user_tester=req.user.user_catador) + template_url = "tecnicas/forms_tester/init_scales_test.html" if req.method == "GET": if type_technique == "escalas": - response = view_controller.controllGetEscalas(request=req) - elif type_technique == "rata": - response = view_controller.controllGetRATA(request=req) + view_controller = InitSessionEscalasController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllGet(request=req) + + elif type_technique == "rata" or type_technique == "cata": + view_controller = InitSessionRATAController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllGet(request=req) + + elif type_technique == "perfil flash": + view_controller = InitSessionPFController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllGet(request=req) + + elif type_technique == "sort": + view_controller = InitSessionSortController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllGet(request=req) + + elif type_technique == "napping": + view_controller = InitSessionNappingController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllGet(request=req) + else: context = { "session": session, - "error": "La técnica usada en esta sesión o ha sido implementada para ingresar a ella" + "error": "La técnica usada en esta sesión no ha sido implementada para ingresar a ella" } response = render( req, template_url, context) return response + elif req.method == "POST": - if type_technique == "escalas" or type_technique == "rata": - response = view_controller.controllPostEscalas(request=req) + if type_technique in ["escalas", "rata", "cata"]: + view_controller = InitSessionEscalasController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllPost(request=req) + + elif type_technique == "perfil flash": + view_controller = InitSessionPFController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllPost(request=req) + + elif type_technique == "sort": + view_controller = InitSessionSortController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllPost(request=req) + + elif type_technique == "napping": + view_controller = InitSessionNappingController( + sensorial_session=session, user_tester=req.user.user_catador) + response = view_controller.controllPost(request=req) + else: context = { "session": session, diff --git a/tecnicas/views/tester_forms/login_session.py b/tecnicas/views/tester_forms/login_session.py index 94f004ef86a99337aa43b37e12897072ecd1981f..4fa8ea32e69a135a6e9ad7e77f564d0868700b54 100644 --- a/tecnicas/views/tester_forms/login_session.py +++ b/tecnicas/views/tester_forms/login_session.py @@ -27,10 +27,15 @@ def loginSessionTester(req: HttpRequest): type_technique = session.tecnica.tipo_tecnica.nombre_tecnica - if type_technique == "escalas": - response = login_controller.validateEntryEscalas(request=req) - elif type_technique == "rata": - response = login_controller.validateEntryRATA(request=req) + if type_technique in ["escalas", "perfil flash", "sort"]: + response = login_controller.validateEntryLimitTesters(request=req) + + elif type_technique in ["rata", "cata"]: + response = login_controller.validateEntryRataCata(request=req) + + elif type_technique == "napping": + response = login_controller.validateEntryNapping(request=req) + else: context = { "error": "La técnica usada en esta sesión es invalida o no ha sido implementada para ingresar a ella" diff --git a/tecnicas/views/tester_forms/napping_test.py b/tecnicas/views/tester_forms/napping_test.py new file mode 100644 index 0000000000000000000000000000000000000000..61cdbd6ec681062693544e0dd47c167da77e55d7 --- /dev/null +++ b/tecnicas/views/tester_forms/napping_test.py @@ -0,0 +1,27 @@ +from django.http import HttpRequest +from tecnicas.models import SesionSensorial +from tecnicas.controllers import TestNappingController +from tecnicas.utils import noValidTechnique + + +def nappingTest(req: HttpRequest, code_sesion: str): + if req.method == "GET": + session = SesionSensorial.objects.get(codigo_sesion=code_sesion) + controll_view = TestNappingController( + sensorial_session=session, user_tester=req.user.user_catador) + return controll_view.controllGet(request=req) + + elif req.method == "POST": + # Se usa para finalizar la sesión + session = SesionSensorial.objects.get(codigo_sesion=code_sesion) + controll_view = TestNappingController( + sensorial_session=session, user_tester=req.user.user_catador) + return controll_view.controllPost(request=req) + + else: + return noValidTechnique( + name_view="cata_system:catador_init_session", + query_params={ + "error": "Método no valido" + } + ) diff --git a/tecnicas/views/tester_forms/pf_test.py b/tecnicas/views/tester_forms/pf_test.py new file mode 100644 index 0000000000000000000000000000000000000000..32d6f762a4618e478f87285f2d7fe27c316722c9 --- /dev/null +++ b/tecnicas/views/tester_forms/pf_test.py @@ -0,0 +1,27 @@ +from django.http import HttpRequest, JsonResponse +from django.shortcuts import redirect +from django.urls import reverse +from tecnicas.models import SesionSensorial +from tecnicas.controllers import TestPFController, ParticipacionController + + +def pfTest(req: HttpRequest, code_sesion: str): + if req.method == "GET": + session = SesionSensorial.objects.get(codigo_sesion=code_sesion) + controll_view = TestPFController( + sensorial_session=session, user_tester=req.user.user_catador) + return controll_view.controllGet(request=req) + + elif req.method == "POST": + session = SesionSensorial.objects.get(codigo_sesion=code_sesion) + controll_view = TestPFController( + sensorial_session=session, user_tester=req.user.user_catador) + return controll_view.controllPost(request=req) + + else: + return noValidTechnique( + name_view="cata_system:catador_init_session", + query_params={ + "error": "Método no valido" + } + ) diff --git a/tecnicas/views/tester_forms/sort_test.py b/tecnicas/views/tester_forms/sort_test.py new file mode 100644 index 0000000000000000000000000000000000000000..3169302db67be6913e8376b28a0f7214cb889cc1 --- /dev/null +++ b/tecnicas/views/tester_forms/sort_test.py @@ -0,0 +1,26 @@ +from django.http import HttpRequest +from tecnicas.models import SesionSensorial +from tecnicas.controllers import TestSortController +from tecnicas.utils import noValidTechnique + + +def sortTest(req: HttpRequest, code_sesion: str): + if req.method == "GET": + session = SesionSensorial.objects.get(codigo_sesion=code_sesion) + controll_view = TestSortController( + sensorial_session=session, user_tester=req.user.user_catador) + return controll_view.controllGet(request=req) + + elif req.method == "POST": + session = SesionSensorial.objects.get(codigo_sesion=code_sesion) + controll_view = TestSortController( + sensorial_session=session, user_tester=req.user.user_catador) + return controll_view.controllPost(request=req) + + else: + return noValidTechnique( + name_view="cata_system:catador_init_session", + query_params={ + "error": "Método no valido" + } + ) diff --git a/tecnicas/views/tester_management/tester_create.py b/tecnicas/views/tester_management/tester_create.py index d5f61bdb86f0acd3325afc817187fe7cec45438d..9ae8e8cb2b347db2686c734222c31835b6ffd44f 100644 --- a/tecnicas/views/tester_management/tester_create.py +++ b/tecnicas/views/tester_management/tester_create.py @@ -8,6 +8,8 @@ from tecnicas.forms import CatadorForm def testerCreate(req: HttpRequest): + url_template = "tecnicas/manage_tester/tester-create.html" + if req.method == "GET": form_tester = CatadorForm() @@ -15,7 +17,7 @@ def testerCreate(req: HttpRequest): "form_cata": form_tester } - return render(req, "tecnicas/manage_tester/catador-crear.html", context) + return render(req, url_template, context) elif req.method == "POST": new_values = {} @@ -51,10 +53,10 @@ def testerCreate(req: HttpRequest): ) except (ValidationError, DatabaseError): context["error"] = "nombre de usuario en uso" - return render(req, "tecnicas/manage_tester/catador-crear.html", context) + return render(req, url_template, context) context["message"] = "Datos guardados, consúltelo en Listar Catadores" context["form_cata"] = CatadorForm() - return render(req, "tecnicas/manage_tester/catador-crear.html", context) + return render(req, url_template, context) else: context["error"] = "Datos no validos" - return render(req, "tecnicas/manage_tester/catador-crear.html", context) + return render(req, url_template, context) diff --git a/tecnicas/views/tester_management/tester_list.py b/tecnicas/views/tester_management/tester_list.py index a7be62c98b2e7bbff70a98e5d8b8b9a7a7d34812..c16b527a9683541a68fc2e55547d596880c791e4 100644 --- a/tecnicas/views/tester_management/tester_list.py +++ b/tecnicas/views/tester_management/tester_list.py @@ -8,6 +8,6 @@ def testerList(req: HttpRequest, num_page: int): if req.method == "GET": view_controller = TesterListController(page=num_page) view_context = view_controller.getContext() - return render(req, "tecnicas/manage_tester/catador-lista.html", view_context) + return render(req, "tecnicas/manage_tester/tester-list.html", view_context) else: return general_error("Método no permitido") diff --git a/tecnicas/views/tester_management/tester_menu.py b/tecnicas/views/tester_management/tester_menu.py index c36f677119d7a2ec19304d4a00e73266a0b189cc..a86adc059155e7c116d88d266b45427694fe4e28 100644 --- a/tecnicas/views/tester_management/tester_menu.py +++ b/tecnicas/views/tester_management/tester_menu.py @@ -1,4 +1,4 @@ from django.shortcuts import render def testerMenu(req): - return render(req, "tecnicas/manage_tester/catadores-panel.html") \ No newline at end of file + return render(req, "tecnicas/manage_tester/testers-panel.html") \ No newline at end of file diff --git a/tecnicas/views/tester_management/tester_search.py b/tecnicas/views/tester_management/tester_search.py index 9305fda489f064c9c928cfc1c4f0f0b3721e2f1c..d55dcaab5004767a58ad256fd85c339145e0b5f6 100644 --- a/tecnicas/views/tester_management/tester_search.py +++ b/tecnicas/views/tester_management/tester_search.py @@ -8,13 +8,14 @@ from tecnicas.models import Catador def testerSearch(req: HttpRequest): + url_template = "tecnicas/manage_tester/tester-search.html" if req.method == "GET": context = {} if "user" in req.GET: username = req.GET["user"] else: - return render(req, "tecnicas/manage_tester/catador-buscar.html") + return render(req, url_template) try: tester = Catador.objects.get(user__username=username) @@ -30,7 +31,7 @@ def testerSearch(req: HttpRequest): except Catador.DoesNotExist: context["error"] = "usuario no encontrado" - return render(req, "tecnicas/manage_tester/catador-buscar.html", context) + return render(req, url_template, context) elif req.method == "POST": context = {} @@ -63,10 +64,10 @@ def testerSearch(req: HttpRequest): tester.save() except (ValidationError, DatabaseError): context["error"] = "nombre de usuario en uso" - return render(req, "tecnicas/manage_tester/catador-crear.html", context) + return render(req, url_template, context) context["message"] = "Datos actualizados, consúltelo en Listar Catadores" context["form_cata"] = form_tester - return render(req, "tecnicas/manage_tester/catador-crear.html", context) + return render(req, url_template, context) else: context["error"] = "Datos no validos" - return render(req, "tecnicas/manage_tester/catador-crear.html", context) + return render(req, url_template, context) diff --git a/tecnicas/views/vocabulary_management/view_vocabulary.py b/tecnicas/views/vocabulary_management/view_vocabulary.py new file mode 100644 index 0000000000000000000000000000000000000000..59a74b7ac03b3e21c4dcd78574c12a24ad04d731 --- /dev/null +++ b/tecnicas/views/vocabulary_management/view_vocabulary.py @@ -0,0 +1,13 @@ +from django.shortcuts import render +from django.http import HttpRequest +from tecnicas.controllers import ViewVocabularyController + + +def viewVocabulary(req: HttpRequest, nombre_vocabulario: str): + view_controller = ViewVocabularyController() + if req.method == "GET": + response = view_controller.controllGet(req, nombre_vocabulario) + return response + else: + context = {"error": "Método no permitido"} + return render(req, "tecnicas/manage_vocabulary/view-vocabulary.html", context)