Spaces:
Sleeping
Sleeping
Validaciones para finalizar sesion con Catador en nappgin y nappgin con ultra flash
Browse files- tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py +76 -0
- tecnicas/static/js/test-napping-plane.js +20 -8
- tecnicas/static/js/test-napping-ultra-flash.js +17 -5
- tecnicas/templates/tecnicas/forms_tester/test_napping.html +4 -3
- tecnicas/templates/tecnicas/forms_tester/test_napping_puf.html +4 -3
- tecnicas/templates/tecnicas/manage_sesions/details-session-napping.html +1 -2
tecnicas/controllers/views_controller/sessions_tester/tests_forms/test_napping_controller.py
CHANGED
|
@@ -5,6 +5,7 @@ from django.db.models import F
|
|
| 5 |
from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion, Modalidad, Palabra
|
| 6 |
from tecnicas.forms import ListWordsForm
|
| 7 |
from tecnicas.utils import noValidTechnique
|
|
|
|
| 8 |
from .general_test_controller import GenetalTestController
|
| 9 |
|
| 10 |
|
|
@@ -113,3 +114,78 @@ class TestNappingController(GenetalTestController):
|
|
| 113 |
words_by_product[product_code] = words_list
|
| 114 |
|
| 115 |
self.context["words_by_product"] = words_by_product
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
from tecnicas.models import Participacion, Producto, TecnicaModalidad, DatoPunto, Calificacion, Modalidad, Palabra
|
| 6 |
from tecnicas.forms import ListWordsForm
|
| 7 |
from tecnicas.utils import noValidTechnique
|
| 8 |
+
from tecnicas.controllers import ParticipacionController
|
| 9 |
from .general_test_controller import GenetalTestController
|
| 10 |
|
| 11 |
|
|
|
|
| 114 |
words_by_product[product_code] = words_list
|
| 115 |
|
| 116 |
self.context["words_by_product"] = words_by_product
|
| 117 |
+
|
| 118 |
+
def controllPost(self, request: HttpRequest):
|
| 119 |
+
action = request.POST.get("action")
|
| 120 |
+
|
| 121 |
+
if action == "finish_session":
|
| 122 |
+
# Get technique and mode
|
| 123 |
+
technique = self.session.tecnica
|
| 124 |
+
self.participation = Participacion.objects.get(
|
| 125 |
+
tecnica=technique, catador=request.user.user_catador)
|
| 126 |
+
|
| 127 |
+
name_mode_activate = TecnicaModalidad.objects.get(
|
| 128 |
+
tecnica=technique).modalidad.nombre
|
| 129 |
+
|
| 130 |
+
# Validate based on mode
|
| 131 |
+
validation_error = self.validateSessionCompletion(
|
| 132 |
+
technique, name_mode_activate)
|
| 133 |
+
|
| 134 |
+
if validation_error:
|
| 135 |
+
# Return to the appropriate template with error
|
| 136 |
+
if name_mode_activate == "sin modalidad":
|
| 137 |
+
return self.nappingTest(request)
|
| 138 |
+
elif name_mode_activate == "perfil ultra flash":
|
| 139 |
+
return self.nappingPufTest(request)
|
| 140 |
+
|
| 141 |
+
# If validation passes, finish the session
|
| 142 |
+
ParticipacionController.finishSession(self.participation)
|
| 143 |
+
params = {"code_sesion": self.session.codigo_sesion}
|
| 144 |
+
return redirect(reverse(self.previus_directory, kwargs=params))
|
| 145 |
+
|
| 146 |
+
# For other actions, call parent's controllPost
|
| 147 |
+
return super().controllPost(request)
|
| 148 |
+
|
| 149 |
+
def validateSessionCompletion(self, technique, mode_name):
|
| 150 |
+
# Get all products in technique
|
| 151 |
+
products = Producto.objects.filter(id_tecnica=technique)
|
| 152 |
+
product_count = products.count()
|
| 153 |
+
|
| 154 |
+
# Get all ratings for this tester
|
| 155 |
+
ratings = Calificacion.objects.filter(
|
| 156 |
+
num_repeticion=0,
|
| 157 |
+
id_tecnica=technique,
|
| 158 |
+
id_catador=self.participation.catador
|
| 159 |
+
).select_related('id_producto').prefetch_related('palabras')
|
| 160 |
+
|
| 161 |
+
# Check if all products have ratings
|
| 162 |
+
if ratings.count() != product_count:
|
| 163 |
+
missing_count = product_count - ratings.count()
|
| 164 |
+
return f"Faltan {missing_count} producto(s) por evaluar."
|
| 165 |
+
|
| 166 |
+
# Check if all ratings have DatoPunto (coordinates)
|
| 167 |
+
ratings_with_points = DatoPunto.objects.filter(
|
| 168 |
+
calificacion__in=ratings
|
| 169 |
+
).values_list('calificacion_id', flat=True)
|
| 170 |
+
|
| 171 |
+
ratings_without_points = ratings.exclude(id__in=ratings_with_points)
|
| 172 |
+
if ratings_without_points.exists():
|
| 173 |
+
missing_products = [
|
| 174 |
+
r.id_producto.codigoProducto for r in ratings_without_points
|
| 175 |
+
]
|
| 176 |
+
return f"Los siguientes productos no tienen coordenadas: {', '.join(missing_products)}"
|
| 177 |
+
|
| 178 |
+
# Additional validation for "perfil ultra flash" mode
|
| 179 |
+
if mode_name == "perfil ultra flash":
|
| 180 |
+
# Check that each rating has at least one word
|
| 181 |
+
ratings_without_words = []
|
| 182 |
+
for rating in ratings:
|
| 183 |
+
if rating.palabras.count() < 1:
|
| 184 |
+
ratings_without_words.append(
|
| 185 |
+
rating.id_producto.codigoProducto)
|
| 186 |
+
|
| 187 |
+
if ratings_without_words:
|
| 188 |
+
return f"Los siguientes productos deben tener al menos 1 palabra: {', '.join(ratings_without_words)}"
|
| 189 |
+
|
| 190 |
+
# All validations passed
|
| 191 |
+
return None
|
tecnicas/static/js/test-napping-plane.js
CHANGED
|
@@ -161,24 +161,25 @@ document
|
|
| 161 |
////
|
| 162 |
*/
|
| 163 |
|
| 164 |
-
// Callback for additional validation before saving
|
| 165 |
window.beforeSaveData = null;
|
| 166 |
|
| 167 |
-
// Function to get extra data for each product
|
| 168 |
window.getExtraDataForSave = null;
|
| 169 |
|
| 170 |
-
window.saveData = async function () {
|
| 171 |
const codeProducts = Object.keys(window.placedPoints);
|
| 172 |
const data = [];
|
| 173 |
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
| 177 |
}
|
| 178 |
|
| 179 |
// Call beforeSaveData callback if it exists (for ultra flash validation)
|
| 180 |
if (window.beforeSaveData && typeof window.beforeSaveData === 'function') {
|
| 181 |
-
const validationResult = window.beforeSaveData();
|
| 182 |
if (validationResult === false) {
|
| 183 |
return false;
|
| 184 |
}
|
|
@@ -236,6 +237,17 @@ window.saveData = async function () {
|
|
| 236 |
}
|
| 237 |
}
|
| 238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
document
|
| 240 |
.getElementById("save-progress")
|
| 241 |
-
.addEventListener("click", window.saveData);
|
|
|
|
| 161 |
////
|
| 162 |
*/
|
| 163 |
|
| 164 |
+
// Callback for additional validation before saving
|
| 165 |
window.beforeSaveData = null;
|
| 166 |
|
| 167 |
+
// Function to get extra data for each product
|
| 168 |
window.getExtraDataForSave = null;
|
| 169 |
|
| 170 |
+
window.saveData = async function (isFinishSession = false) {
|
| 171 |
const codeProducts = Object.keys(window.placedPoints);
|
| 172 |
const data = [];
|
| 173 |
|
| 174 |
+
// Only validate all products placed if finishing session
|
| 175 |
+
if (isFinishSession && products.length != codeProducts.length) {
|
| 176 |
+
spanNotifaction("Por favor, coloca todos los puntos antes de finalizar la sesión")
|
| 177 |
+
return false;
|
| 178 |
}
|
| 179 |
|
| 180 |
// Call beforeSaveData callback if it exists (for ultra flash validation)
|
| 181 |
if (window.beforeSaveData && typeof window.beforeSaveData === 'function') {
|
| 182 |
+
const validationResult = window.beforeSaveData(isFinishSession);
|
| 183 |
if (validationResult === false) {
|
| 184 |
return false;
|
| 185 |
}
|
|
|
|
| 237 |
}
|
| 238 |
}
|
| 239 |
|
| 240 |
+
// Function to finish session with validation
|
| 241 |
+
window.finishSession = async function () {
|
| 242 |
+
const success = await window.saveData(true);
|
| 243 |
+
if (success) {
|
| 244 |
+
// Submit the form to finish session
|
| 245 |
+
const formFinish = document.getElementById("form-finish-session")
|
| 246 |
+
formFinish.action = ""
|
| 247 |
+
formFinish.submit();
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
document
|
| 252 |
.getElementById("save-progress")
|
| 253 |
+
.addEventListener("click", () => window.saveData(false));
|
tecnicas/static/js/test-napping-ultra-flash.js
CHANGED
|
@@ -79,6 +79,9 @@ function initUltraFlash() {
|
|
| 79 |
document.querySelectorAll('.item-product').forEach(p => {
|
| 80 |
p.classList.remove('ring-4', 'ring-primary');
|
| 81 |
});
|
|
|
|
|
|
|
|
|
|
| 82 |
}
|
| 83 |
|
| 84 |
// Handle Point Click for Description
|
|
@@ -197,25 +200,34 @@ function initUltraFlash() {
|
|
| 197 |
|
| 198 |
// Set up callbacks to extend the base saveData function
|
| 199 |
// Validation callback - runs before saving
|
| 200 |
-
window.beforeSaveData = function () {
|
| 201 |
-
// If
|
| 202 |
-
if (
|
|
|
|
| 203 |
const codeProducts = Object.keys(window.placedPoints);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
for (const code of codeProducts) {
|
| 205 |
const words = productWords[code] || [];
|
| 206 |
if (words.length < 1) {
|
| 207 |
-
spanNotifaction(`El producto ${code} debe tener al menos 1 palabra.`);
|
| 208 |
return false;
|
| 209 |
}
|
| 210 |
}
|
| 211 |
}
|
|
|
|
| 212 |
return true;
|
| 213 |
};
|
| 214 |
|
| 215 |
// Data extension callback - adds words to each product's data
|
| 216 |
window.getExtraDataForSave = function (code) {
|
| 217 |
const words = productWords[code] || [];
|
| 218 |
-
console.log(`Getting words for ${code}:`, words);
|
| 219 |
return {
|
| 220 |
words: words
|
| 221 |
};
|
|
|
|
| 79 |
document.querySelectorAll('.item-product').forEach(p => {
|
| 80 |
p.classList.remove('ring-4', 'ring-primary');
|
| 81 |
});
|
| 82 |
+
|
| 83 |
+
// Auto-save positions when transitioning to description phase
|
| 84 |
+
window.saveData(false);
|
| 85 |
}
|
| 86 |
|
| 87 |
// Handle Point Click for Description
|
|
|
|
| 200 |
|
| 201 |
// Set up callbacks to extend the base saveData function
|
| 202 |
// Validation callback - runs before saving
|
| 203 |
+
window.beforeSaveData = function (isFinishSession = false) {
|
| 204 |
+
// If finishing session, validate all products placed and have words
|
| 205 |
+
if (isFinishSession) {
|
| 206 |
+
const totalProducts = document.querySelectorAll('.item-product').length;
|
| 207 |
const codeProducts = Object.keys(window.placedPoints);
|
| 208 |
+
|
| 209 |
+
// Check all products are placed
|
| 210 |
+
if (codeProducts.length !== totalProducts) {
|
| 211 |
+
spanNotifaction("Por favor, coloca todos los productos antes de finalizar la sesión.");
|
| 212 |
+
return false;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
// Check each product has at least 1 word
|
| 216 |
for (const code of codeProducts) {
|
| 217 |
const words = productWords[code] || [];
|
| 218 |
if (words.length < 1) {
|
| 219 |
+
spanNotifaction(`El producto ${code} debe tener al menos 1 palabra para finalizar la sesión.`);
|
| 220 |
return false;
|
| 221 |
}
|
| 222 |
}
|
| 223 |
}
|
| 224 |
+
// For progress save, no validation needed
|
| 225 |
return true;
|
| 226 |
};
|
| 227 |
|
| 228 |
// Data extension callback - adds words to each product's data
|
| 229 |
window.getExtraDataForSave = function (code) {
|
| 230 |
const words = productWords[code] || [];
|
|
|
|
| 231 |
return {
|
| 232 |
words: words
|
| 233 |
};
|
tecnicas/templates/tecnicas/forms_tester/test_napping.html
CHANGED
|
@@ -18,10 +18,11 @@
|
|
| 18 |
</header>
|
| 19 |
|
| 20 |
<article class="hidden">
|
| 21 |
-
<form
|
|
|
|
| 22 |
class="form-actions">
|
| 23 |
{% csrf_token %}
|
| 24 |
-
<input type="hidden" name="action" class="action-input">
|
| 25 |
</form>
|
| 26 |
</article>
|
| 27 |
|
|
@@ -133,7 +134,7 @@
|
|
| 133 |
¿Finalizar sesión?
|
| 134 |
</button>
|
| 135 |
<button id="finish-session" class="cts-btn-general cts-btn-tertiary btn-push hidden flex-1"
|
| 136 |
-
onclick="finishSession(
|
| 137 |
Finalizar
|
| 138 |
</button>
|
| 139 |
<button id="cancel-save" class="cts-btn-general cts-btn-error btn-push hidden flex-1">
|
|
|
|
| 18 |
</header>
|
| 19 |
|
| 20 |
<article class="hidden">
|
| 21 |
+
<form id="form-finish-session"
|
| 22 |
+
action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post"
|
| 23 |
class="form-actions">
|
| 24 |
{% csrf_token %}
|
| 25 |
+
<input type="hidden" name="action" value="finish_session" class="action-input">
|
| 26 |
</form>
|
| 27 |
</article>
|
| 28 |
|
|
|
|
| 134 |
¿Finalizar sesión?
|
| 135 |
</button>
|
| 136 |
<button id="finish-session" class="cts-btn-general cts-btn-tertiary btn-push hidden flex-1"
|
| 137 |
+
onclick="window.finishSession()">
|
| 138 |
Finalizar
|
| 139 |
</button>
|
| 140 |
<button id="cancel-save" class="cts-btn-general cts-btn-error btn-push hidden flex-1">
|
tecnicas/templates/tecnicas/forms_tester/test_napping_puf.html
CHANGED
|
@@ -18,10 +18,11 @@
|
|
| 18 |
</header>
|
| 19 |
|
| 20 |
<article class="hidden">
|
| 21 |
-
<form
|
|
|
|
| 22 |
class="form-actions">
|
| 23 |
{% csrf_token %}
|
| 24 |
-
<input type="hidden" name="action" class="action-input">
|
| 25 |
</form>
|
| 26 |
</article>
|
| 27 |
|
|
@@ -146,7 +147,7 @@
|
|
| 146 |
¿Finalizar sesión?
|
| 147 |
</button>
|
| 148 |
<button id="finish-session" class="cts-btn-general cts-btn-tertiary btn-push hidden flex-1"
|
| 149 |
-
onclick="finishSession(
|
| 150 |
Finalizar
|
| 151 |
</button>
|
| 152 |
<button id="cancel-save" class="cts-btn-general cts-btn-error btn-push hidden flex-1">
|
|
|
|
| 18 |
</header>
|
| 19 |
|
| 20 |
<article class="hidden">
|
| 21 |
+
<form id="form-finish-session"
|
| 22 |
+
action="{% url 'cata_system:catador_init_session' code_sesion=session.codigo_sesion %}" method="post"
|
| 23 |
class="form-actions">
|
| 24 |
{% csrf_token %}
|
| 25 |
+
<input type="hidden" name="action" value="finish_session" class="action-input">
|
| 26 |
</form>
|
| 27 |
</article>
|
| 28 |
|
|
|
|
| 147 |
¿Finalizar sesión?
|
| 148 |
</button>
|
| 149 |
<button id="finish-session" class="cts-btn-general cts-btn-tertiary btn-push hidden flex-1"
|
| 150 |
+
onclick="window.finishSession()">
|
| 151 |
Finalizar
|
| 152 |
</button>
|
| 153 |
<button id="cancel-save" class="cts-btn-general cts-btn-error btn-push hidden flex-1">
|
tecnicas/templates/tecnicas/manage_sesions/details-session-napping.html
CHANGED
|
@@ -192,8 +192,7 @@
|
|
| 192 |
</p>
|
| 193 |
|
| 194 |
{% if there_data %}
|
| 195 |
-
{% include "../components/table-napping-no-mode.html" with testers=testers
|
| 196 |
-
coordinates_no_mode=coordinates_no_mode %}
|
| 197 |
{% else %}
|
| 198 |
{% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
|
| 199 |
{% endif %}
|
|
|
|
| 192 |
</p>
|
| 193 |
|
| 194 |
{% if there_data %}
|
| 195 |
+
{% include "../components/table-napping-no-mode.html" with testers=testers coordinates_no_mode=coordinates_no_mode %}
|
|
|
|
| 196 |
{% else %}
|
| 197 |
{% include "../components/error-message.html" with message='Sin datos por mostrar aún' %}
|
| 198 |
{% endif %}
|