cata_system / tecnicas /controllers /api_controller /rating_napping_controller.py
chartManD's picture
Se guarda el progreso de napping con modalidad sort en descripcion
a4129be
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}