Integración de Qwizer con Moodle
Qwizer’s integration with Moodle
Trabajo de Fin de Grado
Curso 2022–2023
Autor
Vicentiu Tiberius Roman
José Luis Bartha de las Peñas
Director
Manuel Montenegro Montes
Grado en Ingeniería del Software
Facultad de Informática
Universidad Complutense de Madrid
Integración de Qwizer con Moodle
Qwizer’s integration with Moodle
Trabajo de Fin de Grado en Ingeniería Del Software
Autor
Vicentiu Tiberius Roman
José Luis Bartha de las Peñas
Director
Manuel Montenegro Montes
Convocatoria: Junio 2023
Grado en Ingeniería del Software
Facultad de Informática
Universidad Complutense de Madrid
29 de mayo de 2023
Agradecimientos
Primero de todo, queremos agradecer a nuestro tutor Manuel Montenegro Mon-
tes, por habernos ayudado y aconsejado a lo largo de todo el proyecto. Su enorme
paciencia para resolver nuestras dudas y su flexibilidad para mantener nuestras
reuniones a lo largo del curso, han permitido que podamos sacar este proyecto ade-
lante.
Y en segundo lugar, personalmente, yo, José Luis Bartha, quiero a agradecer a
mi compañero Tiberius por haberse involucrado tanto en este proyecto. Gracias a él
se han cumplido muchos de los objetivos planteados para este Trabajo Fin de Grado
e incluso algunos que no estaban ni planificados. Asimismo, he aprendido numerosas
tecnologías que no pensaba que iba aplicar en este proyecto, que además reconoceré,
que si no hubiera sido por él, no creo que las hubiera aprendido nunca y menos a
utilizar. Definitivamente, sin él, no hubiéramos avanzado tanto como hemos hecho.
Y por último, nos gustaría agradecer a nuestros amigos y familiares, aquellas
personas que han hecho que nuestro paso por la facultad sea lo más agradable
posible y siempre tratan de apoyarnos a seguir en los peores momentos. Si ellos no
hubiéramos sido capaces de llegar hasta aquí.
Vicentiu Tiberius Roman y José Luis Bartha de las Peñas
v
Resumen
Integración de Qwizer con Moodle
Qwizer es una aplicación web que permite la realización de cuestionarios dirigida
sobre todo al ámbito académico. A diferencia de otras herramientas como los cues-
tionarios de Moodle, añade la característica de ser una aplicación web progresiva,
es decir, permite al usuario utilizar la aplicación mientras que este no tenga una
conexión a Internet activa. En este caso el usuario/alumno tendrá la capacidad para
poder realizar los cuestionarios que tenga pendientes. Una vez que el alumno finalice
un cuestionario se le generará un código QR que el profesor tendrá que escanear para
obtener constancia de las respuestas enviadas por el estudiante. En el momento que
el usuario recupere la conexión, las soluciones se mandarán al servidor y se registrará
su intento.
Este proyecto es una ampliación de la aplicación Qwizer, en la que se han añadido
nuevas funcionalidades como las siguientes:
Aleatorización de cuestionarios: La aplicación contendrá la posibilidad
de poder entregar a los alumnos cuestionarios aleatorizados, de modo que
cada estudiante recibe una variante distinta de un cuestionario. En estos las
preguntas elegidas para el cuestionario se aleatorizarán una vez el alumno se
descargue el mismo.
Inclusión de enunciados en formato Markdown: El lenguaje Markdown
es un lenguaje de marcado que permite la inclusión de fórmulas y enuncia-
dos con distintos formatos de texto. Los cuestionarios y preguntas en Qwizer
soportarán ahora este lenguaje.
Integración con Moodle: Moodle es una de las herramientas más conocidas
relacionadas con la gestión del aprendizaje. Una de las nuevas funcionalidades
añadidas es la interoperabilidad entre Qwizer y la plataforma del Campus
Virtual de la UCM para la gestión de calificaciones y cuestionarios.
Este proyecto no ha sido solo una ampliación, si no que también se ha centrado
en una buena parte de estabilidad, refactorización y despliegue de la aplicación,
haciendo que la misma sea más robusta, segura y amigable.
Enlace al proyecto en Github: https://github.com/Qwizer-UCM/Qwizer
Palabras clave
Qwizer, Campus Virtual, aplicación web progresiva, Aleatorización, Markdown, Doc-
ker, React, Django
vii
https://github.com/Qwizer-UCM/Qwizer
Abstract
Qwizer’s integration with Moodle
Qwizer is a web application that allows the completion of tests, aimed above all
at the academic field. Unlike other tools such as Moodle quizzes, it adds the feature
of being a progressive web application, which allows the user to use the application
while they do not have an active Internet connection. In this case, the user/ student
will have the possibility to complete the tests that are pending. Once the student
completes a test, a QR code will be generated that the teacher will have to scan
to obtain proof of the answers sent by the student. As soon as the user recovers
the connection, the solutions will be sent to the server and their attempt will be
recorded.
This project is an extension of the Qwizer application, in which new features
have been added, such as the following:
Randomization of tests: The application will contain the possibility of
being able to give students randomized tests, so that each student receives a
different variant of the test. In these, the questions chosen for the test will be
randomized once the student downloads it.
Inclusion of Markdown and mathematical formulas: The Markdown
language is a markup language that allows the inclusion of formulas and state-
ments with different text formats. Tests and questions in Qwizer will now
support this language.
Integration with Moodle: Moodle is one of the best-known tools related to
learning management. One of the new features added is the interoperability
between Qwizer and the UCM Virtual Campus platform for the management
of qualifications and questionnaires.
This project has not only been an expansion, but has also focused in a good part
of stability, refactor and deployment of the application, making it more secure and
friendly to use for the users.
Link to the project on Github: https://github.com/Qwizer-UCM/Qwizer
Keywords
Qwizer, Virtual Campus, Progressive Web App, Randomization, Markdown, Docker,
React, Django
ix
https://github.com/Qwizer-UCM/Qwizer
Índice
Agradecimientos v
Resumen vii
Abstract ix
1. Introducción 1
1.1. Motivación del proyecto . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3. Plan de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2. Selección de herramientas y tecnologías 5
2.1. Lenguajes de programación, gestión de bases de datos y frameworks
utilizados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.1. Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.2. Django . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.3. JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.4. React . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.5. SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.6. Service workers . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.7. Bootstrap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2. Lenguajes de intercambio de datos y marcado . . . . . . . . . . . . . 7
2.2.1. JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.2. YAML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.3. XML-Moodle . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.4. Markdown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.3. Otras herramientas o tecnologías . . . . . . . . . . . . . . . . . . . . 9
2.3.1. Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3.2. Github . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3.3. Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3.4. PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.5. LATEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.6. Visual Studio Code . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.7. Swagger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
xi
2.4. Tecnologías descartadas . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4.1. JQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4.2. Ionic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3. Arquitectura global del sistema 13
3.1. Base de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1.1. Modelo entidad-relación . . . . . . . . . . . . . . . . . . . . . 14
3.1.2. Modelo relacional . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2. API de comunicación entre back-end y front-end . . . . . . . . . . . . 16
3.3. Aspectos de Django . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3.1. Panel de administración . . . . . . . . . . . . . . . . . . . . . 17
3.4. Librerías externas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.4.1. Librerías para el back-end . . . . . . . . . . . . . . . . . . . . 18
3.4.2. Librerías para el front-end . . . . . . . . . . . . . . . . . . . . 20
3.5. Mejoras realizadas sobre el trabajo anterior . . . . . . . . . . . . . . . 21
3.5.1. Front-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.5.1.1. Refactorización de clases a funciones . . . . . . . . . 21
3.5.1.2. Actualización de librerías . . . . . . . . . . . . . . . 21
3.5.1.3. Arreglo de errores y linting . . . . . . . . . . . . . . 22
3.5.1.4. Configuración del entorno del desarrollo . . . . . . . 22
3.5.1.5. Mejoras generales . . . . . . . . . . . . . . . . . . . . 23
3.5.2. Back-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.5.2.1. Refactorización de código . . . . . . . . . . . . . . . 24
3.5.2.2. Actualización de librerías . . . . . . . . . . . . . . . 25
3.5.2.3. Arreglo de errores y linting . . . . . . . . . . . . . . 25
3.5.2.4. Configuración del entorno del desarrollo . . . . . . . 26
3.5.2.5. Mejoras generales . . . . . . . . . . . . . . . . . . . . 26
3.5.2.6. Documentación de la API . . . . . . . . . . . . . . . 29
3.5.2.7. Testing . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.5.3. Nuevas funcionalidades extras . . . . . . . . . . . . . . . . . . 31
4. Aleatorización de cuestionarios 33
4.1. Objetivo principal / Introducción . . . . . . . . . . . . . . . . . . . . 33
4.2. Cambios en la base de datos . . . . . . . . . . . . . . . . . . . . . . . 34
4.2.1. Creación de entidad Intento e instancias . . . . . . . . . . . . 34
4.2.2. Aleatorización de preguntas . . . . . . . . . . . . . . . . . . . 36
4.2.3. Aleatorización de opciones . . . . . . . . . . . . . . . . . . . . 38
4.2.4. Fijación de preguntas y opciones . . . . . . . . . . . . . . . . 39
4.3. Preguntas aleatorias . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3.1. Nuevo modelo, SelecciónPregunta . . . . . . . . . . . . . . . 40
4.3.2. Uso dentro de la aplicación . . . . . . . . . . . . . . . . . . . . 41
5. Introducción de Markdown y fórmulas 43
5.1. Librerías utilizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.2. Gestión de imágenes . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.2.1. Subida de imágenes . . . . . . . . . . . . . . . . . . . . . . . . 44
5.2.2. Tratamiento de imágenes en Django . . . . . . . . . . . . . . . 44
5.2.3. Recuperación de imágenes . . . . . . . . . . . . . . . . . . . . 45
5.2.4. Consecuencias . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.3. Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.3.1. Subida de preguntas con Markdown . . . . . . . . . . . . . . . 46
5.3.2. Visualización de preguntas con Markdown . . . . . . . . . . . 47
6. Integración con Moodle 53
6.1. Migración de las preguntas de Moodle . . . . . . . . . . . . . . . . . 53
6.1.1. Formato Moodle XML . . . . . . . . . . . . . . . . . . . . . . 53
6.1.2. ElementTree . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.2. Exportar notas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
7. Creación de contenedores Docker 59
7.1. Despliegue con Docker Compose . . . . . . . . . . . . . . . . . . . . . 59
7.2. Servidor web con Nginx . . . . . . . . . . . . . . . . . . . . . . . . . 60
7.3. Seguridad con HTTPS . . . . . . . . . . . . . . . . . . . . . . . . . . 60
8. Conclusiones y trabajo futuro 63
8.1. Objetivos alcanzados . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8.2. Dificultades encontradas . . . . . . . . . . . . . . . . . . . . . . . . . 65
8.3. Trabajo futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
9. Contribuciones personales 69
Introduction 77
Conclusions and Future Work 81
Bibliografía 87
A. Documentación de la API 89
Índice de figuras
2.1. Ejemplo de uso de Swagger . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1. Modelo de Django implementado . . . . . . . . . . . . . . . . . . . . 14
3.2. Modelo entidad-relación Qwizer del año pasado [4] . . . . . . . . . . . 15
3.3. Modelo entidad-relación final . . . . . . . . . . . . . . . . . . . . . . . 15
3.4. Modelo relacional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.5. Vista del panel de administración de Django . . . . . . . . . . . . . . 17
3.6. Fichero CSV con usuarios . . . . . . . . . . . . . . . . . . . . . . . . 18
3.7. Fichero env de Django . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.8. Ejemplo de librería hello-pangea/dnd . . . . . . . . . . . . . . . . . . 21
3.9. Ejemplo de linter de React . . . . . . . . . . . . . . . . . . . . . . . . 22
3.10. Fragmento del fichero de comunicación con la API . . . . . . . . . . . 23
3.11. Consola avanzada de Django . . . . . . . . . . . . . . . . . . . . . . . 26
3.12. Ejemplo de un manager de la aplicación . . . . . . . . . . . . . . . . 28
3.13. Ejemplo de implementación de documentación de API . . . . . . . . 30
3.14. Ejemplo de un fichero de pruebas . . . . . . . . . . . . . . . . . . . . 31
3.15. Nueva ventana modal para la matriculación de alumnos . . . . . . . . 32
4.1. Modificación de aleatorización en creación de cuestionarios . . . . . . 37
4.2. Aleatorización en cuestionarios vía YAML . . . . . . . . . . . . . . . 37
4.3. Elección de aleatorización de opciones . . . . . . . . . . . . . . . . . . 38
4.4. Modelo InstanciaOpcionTest . . . . . . . . . . . . . . . . . . . . . . 39
4.5. Fijación en cuestionarios vía YAML . . . . . . . . . . . . . . . . . . . 40
4.6. Ejemplo de pregunta aleatoria en YAML . . . . . . . . . . . . . . . . 42
5.1. Nueva vista para subir preguntas . . . . . . . . . . . . . . . . . . . . 48
5.2. Cambios en serializadores para recuperación de imágenes . . . . . . . 48
5.3. Pregunta con texto Markdown y fórmulas matemáticas . . . . . . . . 49
5.4. Pregunta con imágenes . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.5. Visualización de preguntas con fórmulas . . . . . . . . . . . . . . . . 50
5.6. Visualización de pregunta con imágenes . . . . . . . . . . . . . . . . . 51
5.7. Visualización de pregunta con imágenes en dispositivo móvil . . . . . 52
6.1. Pregunta corta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.2. Pregunta de opción múltiple . . . . . . . . . . . . . . . . . . . . . . . 54
xv
6.3. Subida de la lista de estudiantes . . . . . . . . . . . . . . . . . . . . . 56
6.4. Lista de estudiantes generada por Moodle . . . . . . . . . . . . . . . 57
6.5. Selección de la columna y la asignatura . . . . . . . . . . . . . . . . . 57
6.6. Selección del cuestionario . . . . . . . . . . . . . . . . . . . . . . . . . 57
7.1. Fichero de configuración docker-compose.yml . . . . . . . . . . . . . . 61
7.2. Configuración de Nginx . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Capı́tulo 1
Introducción
En esta memoria hablaremos del desarrollo de la ampliación y mejora de una
aplicación progresiva para realizar cuestionarios en línea: Qwizer [4]. Empezaremos
presentando que nos llevó a seguir este proyecto, qué objetivos hemos tenido a lo
largo del proyecto y finalmente describiremos nuestro plan de trabajo a lo largo del
curso académico.
1.1. Motivación del proyecto
El año pasado nos encontrábamos en la situación de elegir un trabajo que de
verdad nos motivará a desarrollar nuestro último proyecto dentro de la facultad.
Además, como estudiantes de la Facultad de Informática, queríamos un proyecto
que de verdad impactase en la evolución de la misma y nos permitiera ayudar a
mejorar sus servicios.
Manuel nos habló de una posible mejora y ampliación de una aplicación web
progresiva basada en la realización de cuestionarios que permita realizarlos sin co-
nexión a internet: Qwizer. Esta aplicación estaba dirigida a usuarios como nosotros,
alumnos que a lo largo de nuestra trayectoria dentro de la facultad hemos tenido
que realizar numerosos cuestionarios, sin añadir, que a mitad de nuestro paso, es-
talló una pandemia mundial debido al COVID-19 y este tipo de formato para la
evaluación de nuestras asignaturas fue algo rutinario.
Inicialmente, la propuesta ya nos llamó la atención pero además identificamos
una serie de causas que nos decantaron por progresar y continuar este proyecto.
Entre ellas podemos nombrar las siguientes:
Nuevas tecnologías: Esta aplicación utilizaba tecnologías con las cua-
les no estábamos familiarizados ninguno de los dos. Tanto React como
Django eran dos tecnologías que desconocíamos pero sabíamos que iban a ser
esenciales de dominar para nuestro desarrollo personal y profesional. Además,
el hecho de ser una aplicación que funciona sin conexión a Internet, nos permite
involucrarnos en el mundo de las aplicaciones progresivas, un mercado que
hoy en día esta en auge por lo que es muy interesante desarrollar y descubrir
las posibilidades de estas aplicaciones.
Uso práctico en un futuro: Otro punto importante que nos hizo decidir-
1
2 Capítulo 1. Introducción
nos por este proyecto, fue el uso práctico que le vimos a la aplicación. Bajo
un buen desarrollo y el añadido de un mínimo de funcionalidades necesarias,
veíamos que este proyecto podría llegar a ser útil. A lo largo de la carrera,
hemos desarrollado numerosos proyectos con el único fin de ser calificados y
focalizados a una simple entrega. Posteriormente, quedan olvidados o aban-
donados por falta de tiempo o motivación para seguirlos. En cambio, con este
proyecto veíamos que si conseguíamos cumplir los objetivos podríamos hacer
una aplicación digna que podrían utilizar nuestros compañeros de la
facultad en un futuro.
Proyecto de innovación educativa de la UCM (proyectos INNOVA):
Finalmente, además de los otros motivos, Manuel nos informó, como ya hemos
comentado anteriormente, que este proyecto estaba destinado a seguir siendo
desarrollado por algunos profesores de la facultad. De hecho, habían planteado
un proyecto de innovación de la facultad, para que la aplicación fuera utilizada
en los laboratorios de algunas asignaturas para calificar exámenes. Esto nos lla-
mó bastante la atención y, ligado a lo que comentábamos al principio, veíamos
que finalmente este proyecto podría dejar huella en nuestra facultad.
1.2. Objetivos
Una vez ya teníamos la propuesta del proyecto tuvimos que pensar en unos
objetivos para desarrollar a lo largo de todo el curso académico. Esta tarea fue
algo sencilla debido a que nuestros compañeros del año pasado y también nuestro
tutor, nos ayudaron a identificar qué nuevas funcionalidades podrían añadirse a la
aplicación Qwizer. Por lo que, finalmente, entre los consejos recibidos llegamos a
plantear y establecer los siguientes primeros objetivos para el nuevo trabajo:
Aleatorización de cuestionarios: Uno de los objetivos principales a futuro
de nuestros compañeros del año pasado, fue el hecho de la aleatorización
dentro de los propios cuestionarios. Ofrecer la capacidad de aleatorizar
las preguntas de los cuestionarios es esencial en este tipo de aplicaciones para
evitar copias entre los estudiantes. Por esta razón, llegamos a la conclusión
que esta característica ofrecería a Qwizer un valor añadido.
Para la implementación de esta faceta tuvimos que pensar detenidamente co-
mo aplicaríamos la aleatorización dentro de la aplicación, dado que dentro
de un cuestionario se puede aplicar esto de numerosas maneras. Finalmente,
identificamos tres objetivos principales:
• Aleatorización a nivel de preguntas, es decir, las preguntas de un cues-
tionario deberían aparecer en un orden aleatorio para cada usuario que
realice el mismo.
• Aleatorización a nivel de opciones, es decir, las opciones dentro de las
preguntas tipo test deberían mostrarse en un orden aleatorio para cada
usuario.
1.3. Plan de trabajo 3
• Preguntas de selección aleatoria, es decir, dentro de los cuestionarios ha-
brá una o varias preguntas que para cada usuario se elegirá en base a un
subconjunto de preguntas del banco de preguntas.
Adaptación de aplicación a dispositivos móviles: El diseño adaptable es
un paso fundamental en las aplicaciones web. Dar a los usuarios que utilicen
dispositivos móviles la posibilidad de ver nuestra aplicación de manera correcta
es muy importante y más en el contexto de nuestra aplicación. Por tal motivo
decidimos intentar adaptar nuestra interfaz a los dispositivos móviles
y intentar que la navegación por la aplicación fuera lo más cómoda posible.
Inclusión de Markdown y fórmulas matemáticas: Markdown es un tipo
de lenguaje de marcado que permite añadir a la aplicación texto enri-
quecido permitiendo la inclusión de palabras en negrita, cursiva, imágenes,
etc... Nuestro tutor identificó la incorporación de Markdown como uno de
los objetivos principales del TFG y agregándole también la posibilidad de
redactar enunciados y soluciones con fórmulas matemáticas, permi-
tiendo la formulación de preguntas más complejas.
Integración con Moodle: Finalmente, el último objetivo que planteamos
fue la integración con los formatos de la plataforma Moodle. Moodle
fue una de las herramientas que inspiró el desarrollo de Qwizer, de hecho,
actualmente es la plataforma en la que se basa el Campus Virtual de la
nuestra facultad. Dado que, a día de hoy, se sigue utilizando, la capacidad
de integrar Qwizer con Moodle fue uno de nuestros objetivos más esperados.
Para ello nos quisimos centrar en que se pudieran importar preguntas del
banco de preguntas de Moodle y permitir exportar las notas de los
estudiantes que realizaron cuestionarios en Qwizer a Moodle.
1.3. Plan de trabajo
A lo largo de este curso académico, hemos pasado por numerosas fases dentro
del desarrollo de nuestro TFG. El hecho de ampliar un proyecto completo, impli-
caba etapas de estudio no solo de las nuevas tecnologías con las que no estábamos
familiarizados, sino también grandes estructuras de códigos que hacen uso de las
mismas. Debido a todo esto, necesitábamos un plan de trabajo que nos permitiera ir
estableciendo pequeños objetivos para completar las tareas que teníamos planteadas.
Gracias a las reuniones que realizábamos con nuestro tutor Manuel Montenegro,
las cuales se hacían cada dos semanas, nos organizábamos para ir cumpliendo una
serie de tareas para cada reunión. Esta metodología nos ayudaba, al principio, a ir
entendiendo poco a poco partes del código y empezar a cumplir las primeras metas,
y posteriormente, en etapas más avanzadas del proyecto, a finalizar los objetivos
principales del trabajo. Dicho esto, la distribución general de las fases de nuestro
proyecto fueron las siguientes:
4 Capítulo 1. Introducción
Refactorización, testing, actualización y despliegue de la aplicación
(septiembre de 2022 - febrero de 2023 ) Una de las etapas más largas de desa-
rrollo. Al principio, nos dedicamos a entender gran parte del código y poste-
riormente refactorizar tanto el frontend como se comenta en el capítulo 3.5.1
como en el backend como se comenta en el capítulo 3.5.2. También actualiza-
mos todas las librerías a la ultima versión intentando evitar conflictos por los
cambios de versiones. Por último, preparamos mejor el entorno de desarrollo
añadiendo linters, variables de entorno para los secretos, diseñando algunos
tests para el back-end, usando un gestor de paquetes para este último y usando
Docker para desplegar el proyecto de manera correcta.
Aleatorización de cuestionarios (marzo de 2023 ): Durante esta etapa, se
implementó la funcionalidad de aleatorización de cuestionarios. Este proceso
involucró realizar varios cambios en el modelo relacional de la aplicación y en
lógica de la aplicación como se detalla en profundidad en el capítulo 4, lo que
requirió un tiempo significativo para actualizar el código correspondiente.
Markdown, preguntas aleatorias e integración con Moodle (abril de
2023 ): Durante esta fase, se abordaron varios aspectos clave. La implementa-
ción del formato Markdown para las preguntas y la integración con Moodle
fueron relativamente sencillas, a excepción de la gestión de las imágenes en
el caso de Markdown. Sin embargo, la incorporación de preguntas aleatorias
resultó ser un desafío considerable, ya que tuvo un impacto en gran parte del
código del back-end de la aplicación. Estas incorporaciones están descritas en
los capítulos 4, 5 y 6.
Comprobaciones finales y memoria (mayo de 2023 ): Durante el último
mes de desarrollo, nos enfocamos en realizar los ajustes finales en la interfaz de
la aplicación, puliendo los detalles para lograr una experiencia de usuario me-
jorada. También dedicamos tiempo a redactar la memoria del Trabajo de Fin
de Grado (TFG), documentando todo el proceso de desarrollo, las decisiones
tomadas y los resultados obtenidos.
Capı́tulo 2
Selección de herramientas y tecnologías
En nuestro proyecto hemos hecho uso de numerosas tecnologías que nos han
ayudado a la implementación final de nuestra aplicación. Dado que este proyecto es
una continuación de uno anterior, hemos tenido que, no solo adecuarnos a muchas de
las tecnologías que nuestros compañeros utilizaron el año pasado, sino que también
hemos tenido que investigar numerosas tecnologías para implementación de nuestros
nuevos objetivos. En esta sección vamos a explicar qué herramientas y tecnologías
hemos utilizado finalmente para el desarrollo de Qwizer.
2.1. Lenguajes de programación, gestión de bases
de datos y frameworks utilizados
Primero de todo, empezaremos nombrando que tipos de lenguajes de programa-
ción, gestores de bases de datos y marcos de trabajo han sido utilizados para el
desarrollo del proyecto.
2.1.1. Python
Python es un lenguaje de programación interpretado y altamente conocido en
términos de aplicaciones web y desarrollo de software. En nuestro caso es el princi-
pal lenguaje que incorpora el marco de trabajo que hemos utilizado en la parte de
servidor de nuestra aplicación. Dado que fue una decisión de nuestros compañeros
del año pasado, este año hemos decidido seguir con este mismo lenguaje para la im-
plementación del servidor. Además, este año hemos hecho uso de entornos virtuales
con la librería Poetry, permitiéndonos no tener que instalar todas las librerías que
requiere el proyecto en nuestros equipos. Esta posibilidad nos ha ayudado a añadir
una capa de aislamiento con nuestros sistemas y hacer más liviana su instalación.
2.1.2. Django
Django [3] es uno de los frameworks, o los también conocidos como marcos de
trabajo, que más se utilizan en el desarrollo de las aplicaciones web. Su robusta y
vasta capacidad de posibilidades para el control de la seguridad sobre las vulnerabi-
5
6 Capítulo 2. Selección de herramientas y tecnologías
lidades más frecuentes en el entorno web, un panel de administración sencillo y bien
integrado y un extenso ORM fueron las razones por las que nuestros compañeros del
año pasado decidieron utilizar esta herramienta para el desarrollo de la aplicación
Qwizer en la parte del servidor. Pese a estar más familiarizados con Node.js hemos
decidido seguir con esta tecnología para aprovechar el código ya escrito.
2.1.3. JavaScript
JavaScript es un lenguaje de programación interpretado altamente conocido por
su uso en el desarrollo web. Para nosotros, es el principal lenguaje de programación
utilizado por el framework que utilizamos para el lado del cliente en la aplicación.
Además, este lenguaje es esencial para una de las funcionalidades más atractivas de
Qwizer, los Service workers. Estos permiten que la aplicación funcione aun estando
el cliente sin conexión. Esta propiedad hace que las aplicaciones web sean conocidas
como aplicaciones web progresivas. Agregado a lo anterior, dado que la aplicación
se puede utilizar en el navegador, podemos hacer uso del almacenamiento interno
del mismo gracias a este lenguaje. Con esto podemos hacer que los usuarios guar-
den datos en este pequeño almacenamiento, como son sus datos de sesión y algunos
cuestionarios descargados que podrán realizar en caso de que la aplicación se quede
sin conexión. También, gracias a su vasto número de librerías y de facilidades, Javas-
cript ofrece una estructura sólida para realizar llamadas HTTP a nuestro servidor
de manera sencilla.
2.1.4. React
React [13] es una de las bibliotecas más conocidas de Javascript para el desarrollo
de aplicaciones web. Su arquitectura basada en el modelo SPA de aplicaciones web,
consistentes en una sola página, fue una de las razones principales por la que nuestros
compañeros del año pasado decidieron utilizar esta tecnología.
Una de las ventajas más importantes de React es su estructura por componentes.
La programación por componentes se basa en la creación de pequeñas partes de
la vista con incluso lógica propia. Esto permite que se pueda reutilizar y hacer
mucho más entendible el código. La programación en React se basa en la creación
de componentes estructurados mediante clases o mediante funciones. El año pasado,
nuestros compañeros decidieron escribirlos por clases. Hoy en día las definiciones
mediante clases, han quedado prácticamente obsoletas por lo que hemos decidido
darle un enfoque funcional a estas. En este tipo de definiciones los componentes se
encapsulan en funciones y en la función de retorno es donde se escribe el código JSX
que más adelante se mostrará al usuario. El formato JSX, permite integrar código
HTML dentro de JavaScript lo cual hace mas cómodo el desarrollo con React.
Anteriormente se utilizaba create-react-app para la compilación del código.
Sin embargo, debido a los numerosos avisos que nos salían en la consola al ejecutar
la aplicación y al lento mantenimiento que se le daba a la herramienta decidimos in-
2.2. Lenguajes de intercambio de datos y marcado 7
vestigar y buscar alguna alternativa. Llegamos a una herramienta denominada Vite
que, además de estar mejor mantenida, también ofrecía mejoras en el rendimiento a
la hora de desarrollar ya que en vez de recompilar el proyecto entero cada vez que
se realizaba algún cambio, solo se recompilaban los ficheros con cambios. Al probar
la herramienta por primera vez nos sorprendimos por la rapidez con la que se ponía
el proyecto en funcionamiento.
2.1.5. SQL
SQL es un lenguaje utilizado para el acceso y gestión de las bases de datos. En
nuestro proyecto, gracias al ORM de Django, hemos podido evitar tener que utilizar
este lenguaje para muchas de las consultas de la aplicación. Aun así, en algunos casos
puntuales hemos necesitado de este lenguaje para hacer consultas muy especificas
que el ORM Django no soportaba.
2.1.6. Service workers
Un Service worker es la tecnología que permite generar una aplicación web pro-
gresiva. Básicamente, consiste en un fichero JavaScript que se ejecuta en segundo
plano, ofreciendo al cliente soluciones en caso de que se quede sin conexión a Inter-
net o el servidor al que vaya a realizar las peticiones no esté disponible. En nuestra
aplicación, utilizamos esta tecnología para guardar los recursos necesarios para que
mientras el cliente se encuentre sin conexión, pueda disfrutar de una experiencia
limitada de nuestros servicios. Otras de las tareas fundamentales que desempeña,
es la de almacenar las peticiones que realice el cliente en el periodo que este sin
conexión, y una vez que la recupere, volverá a mandar estas peticiones al servidor.
2.1.7. Bootstrap
Bootstrap [8] en una biblioteca CSS de JavaScript que hemos utilizado para el
estilado general de toda la aplicación. Aparte que nuestros compañeros ya la utiliza-
ron para este mismo motivo, nosotros hemos decidido mantenerla dado que hemos
utilizado esta herramienta en numerosas asignaturas y estamos bastante familiariza-
dos con ella. Además, nos permite generar una interfaz adaptable a las dimensiones
de cualquier dispositivo y así cumplir uno de los objetivos principales. Cabe destacar
también, que esta librería dispone de una documentación muy extensa e incluso con
ejemplos explicativos.
2.2. Lenguajes de intercambio de datos y marcado
Como segundo apartado hablaremos de los lenguajes que hemos tenido que uti-
lizar para intercambiar datos entre el servidor y el cliente de nuestra aplicación.
8 Capítulo 2. Selección de herramientas y tecnologías
Aparte también introduciremos uno de los principales lenguajes de marcado del
proyecto que ha permitido el desarrollo de uno de los objetivos de nuestro TFG.
2.2.1. JSON
JSON es un formato de fichero de texto ampliamente utilizado para el intercam-
bio de datos dentro de las aplicaciones. Dadas su flexibilidad y vasto uso dentro
de las implementaciones de las APIs y el desarrollo web hemos decidido utilizarlo
para intercambiar información a través de nuestra API. Su sintaxis es ligera, lo que
facilita los trabajos de decodificación y codificación de los datos utilizados. Como
otra ventaja a añadir, la posibilidad de ser utilizado en cualquier lenguaje de pro-
gramación nos ayuda en el tratamiento de datos entre el lado cliente y servidor de
la aplicación.
2.2.2. YAML
YAML es un formato de serialización de datos, con similitudes a los formatos
XML y JSON. El principal atractivo de este formato es su fácil lectura y legibilidad.
Dado que esta aplicación va dirigida a usuarios que a lo mejor no son expertos en
formatos algo más técnicos como pueden XML y JSON, nuestros compañeros del
curso anterior decidieron elegir este formato para la subida de archivos referentes a
la creación de cuestionarios y preguntas.
2.2.3. XML-Moodle
XML-Moodle [9] es un formato que utiliza la plataforma de Moodle para importar
y exportar preguntas. En nuestro caso lo hemos utilizado para cumplir el objetivo
de la importación de preguntas desde Moodle a Qwizer. El uso de este formato se
detalla más en el Capítulo 6.
2.2.4. Markdown
Markdown es un lenguaje de marcado que permite la creación de texto especiali-
zado sin utilizar ningún formato especifico. Este lenguaje permite escribir fórmulas
matemáticas, agregar imágenes o mejorar el estilo a textos con una sintaxis muy
sencilla. Este lenguaje es esencial para uno de nuestros objetivos del TFG, por lo
que la adición a nuestro proyecto era esencial. En capítulos posteriores explicaremos
las principales razones de su uso pero se encargará de profesionalizar más nuestra
aplicación.
2.3. Otras herramientas o tecnologías 9
2.3. Otras herramientas o tecnologías
En este último apartado añadiremos tecnologías o herramientas que no podemos
agrupar y explicaremos en general de manera independiente el uso que tienen dentro
de nuestra aplicación.
2.3.1. Git
Git es uno de los gestores más conocidos por la industria del desarrollo para el
control de versiones. En estos proyectos en los que se tratan con una alta cantidad
de archivos, llevar un control de todos ellos es algo complicado y de increíble im-
portancia. Git nos ayuda y libera de este problema, con una lista de comandos muy
sencilla y fácil de aprender. Además, con su posibilidad de restaurar cualquier ver-
sión anterior de los archivos nos permite trabajar tranquilos ante errores inesperados
o no deseados.
2.3.2. Github
Github es un plataforma de alojamiento de proyectos que utilizan el control
de versiones de Git. Dado que utilizamos Git como sistema de control de versiones,
alojar nuestro proyecto en Github era sumamente sencillo y nos aportaba numerosos
beneficios, como la mejor visualización del proyecto y la facilidad para seguir el
trabajo de ambos.
2.3.3. Docker
Docker [17] es una herramienta de despliegue de software en sus llamados con-
tenedores virtuales. Con la finalidad de desplegar nuestro proyecto, hemos utilizado
Docker para preparar la aplicación para un entorno de producción y no solo de
desarrollo.
Una parte importante de cualquier proyecto es el despliegue del mismo. Para
facilitar el despliegue de Qwizer en cualquier tipo de maquina y sin necesidad de
muchos conocimientos en las tecnologías usadas decidimos usar Docker para modu-
larizar nuestra aplicación mediante contenedores. Empezamos por investigar como
se desplegaban correctamente las aplicaciones con React y Django. Con la ayuda de
docker-compose para ejecutar varios contenedores hemos hecho uso de las siguientes
imágenes:
PostgreSQL
Node
Python
10 Capítulo 2. Selección de herramientas y tecnologías
Nginx
La creación de los contenedores se aborda en mayor detalle en el Capítulo 7,
donde se proporciona una explicación exhaustiva de este proceso.
2.3.4. PostgreSQL
PostgreSQL o Postgres, es un sistema gestor de base de datos relacional que
soporta el almacenamiento de objetos. En nuestro caso, hemos decidido utilizarlo
debido a que cumple de forma mucho más estricta con el estándar de SQL, además
de que tiene licencia de código abierto.
2.3.5. LATEX
LATEX es una herramienta de composición de textos para documentos especiali-
zados. En nuestro caso hemos decidido utilizar LATEX para redactar la memoria con
el fin de mejorar su aspecto y facilitar el proceso de redacción, a diferencia de otras
herramientas como Word o LibreOffice que son más complicadas de utilizar para el
desarrollo de un documento más extenso como es nuestro caso.
2.3.6. Visual Studio Code
Visual Studio Code es un editor de código fuente ampliamente utilizado en el
desarrollo software. Para nosotros ha sido el principal IDE donde hemos desarrollado
la mayor parte del código. Dentro de nuestras principales razones para su uso están:
Legibilidad. Es un entorno muy amigable donde su paleta de colores ayuda
en hacer un código limpio.
Sencillez. Su interfaz es sencilla y entendible, no tiene un exceso de elementos
que oculten las funcionalidades principales del entorno.
Extensiones. El amplio catalogo de extensiones que tiene facilita y ahorra
tiempo en el desarrollo. Las extensiones que más hemos utilizados son las
siguientes:
• Docker: Dado nuestro uso de contenedores para el desarrollo, Visual
Studio contiene una extensión propia de Docker que permite el manejo
de los contenedores e imágenes.
• TODO Tree: A lo largo del desarrollo hemos ido dejando pequeñas ano-
taciones con tareas pendientes por hacer para un futuro. Esta extensión
permite remarcar esas anotaciones en cada archivo y ofrece una interfaz
para saber en cada archivo dónde quedan pendientes cosas por hacer.
2.3. Otras herramientas o tecnologías 11
Figura 2.1: Ejemplo de uso de Swagger
• Github: Esta extensión permite realizar las operaciones básicas de Git
directamente en el entorno de trabajo. Además permite ver versiones
anteriores de cada archivo y ver las modificaciones realizadas por cada
uno de nosotros.
2.3.7. Swagger
Swagger [16] es un conjunto de herramientas de código abierto diseñadas para
documentar servicios web basados en arquitecturas RESTful. En nuestro proyecto,
consideramos fundamental priorizar la documentación de la API con el fin de fa-
cilitar el desarrollo futuro de la aplicación. Para lograr esto, descubrimos Swagger,
una herramienta que, siguiendo el estándar de OpenAPI 3, genera una interfaz web
donde se documenta de manera detallada la API, además de brindar la posibilidad
de realizar pruebas interactivas. En la Figura 2.1 se muestra un ejemplo de cómo se
utiliza Swagger.
El enfoque de Swagger en la generación de documentación automatizada permite
ahorrar tiempo y esfuerzo al proporcionar una representación clara y estructurada de
los puntos finales de la API, los parámetros requeridos, los tipos de datos admitidos
y las respuestas esperadas. Además, al ofrecer una interfaz interactiva, Swagger
permite a los desarrolladores probar la API directamente desde la documentación,
lo que facilita la depuración y el desarrollo ágil.
12 Capítulo 2. Selección de herramientas y tecnologías
2.4. Tecnologías descartadas
2.4.1. JQuery
JQuery [7] es una biblioteca de JavaScript que permite una interacción más
simplificada con los elementos HTML directamente desde un fichero JavaScript.
El año pasado, nuestros compañeros la utilizaron para actualizar el contenido de
algunos textos de las vistas, pero nosotros hemos eliminado todo comportamiento
relacionado con esta tecnología y hemos llegado a descartarla.
2.4.2. Ionic
Ionic [11] es un framework de estilado que permite desarrollar interfaces de usua-
rio para dispositivos móviles con tecnologías web. Consideramos utilizar esta tecno-
logía para conseguir un diseño adaptable exclusivo para los dispositivos móviles,
en vez de utilizar nuestra librería de estilado, Bootstrap. Sin embargo, por falta de
tiempo decidimos abandonar esta idea y no utilizar esta opción.
Capı́tulo 3
Arquitectura global del sistema
En este capítulo explicaremos el núcleo y la organización principal de nuestra
aplicación. Qwizer, como muchas aplicaciones web, está compuesto de un parte
diseñada en el lado del servidor, también conocida como back-end, y además otra
parte diseñada para el lado del cliente, comúnmente denominada front-end. Para el
correcto funcionamiento de la aplicación es esencial una buena comunicación entre
ambos lados. Aparte, nuestra aplicación se apoya en una base de datos que almacena
la información necesaria para que la aplicación pueda funcionar.
3.1. Base de datos
Para gestionar la base de datos decidimos utilizar PostgreSQL. Este sistema
gestor de bases de datos es uno de los más potentes, en temas de rendimiento, del
mercado. Como explicamos en el capítulo anterior, este gestor contiene una licencia
de código abierto por lo que de cara al futuro desarrollo de esta aplicación y su
despliegue va a ser un factor esencial. Además, su analizador sintáctico es algo más
precisa que los otros gestores de base de datos conocidos como pueden ser MYSQL o
MariaDB, donde ligeros errores en las consultas o modificaciones del propio lenguaje
SQL pueden funcionar sin seguir el verdadero estándar del lenguaje original SQL.
Esta estrictez nos lleva a escribir un código más fiel al que hemos aprendido en estos
años anteriores y a no depender en ningún aspecto del propio gestor de base de
datos.
Dicho esto, como también explicamos anteriormente, Django ofrece un ORM muy
extenso. Este mapeador de objetos nos presenta la posibilidad de la utilización de los
objetos llamados modelos. Estos modelos permiten una sencilla implementación de
los objetos a guardar en la base de datos, posibilitando modificar cualquier propiedad
de los atributos a guardar, como puede ser la declaración de claves externas, o las
conocidas como foreign keys, o los tipos de campos únicos, o unique.
En la figura 3.1, podemos observar la implementación de uno de los modelos de
Django.
Esta transformación que pasa el modelo a objeto SQL es lo que se conoce en
Django como una migración. Las migraciones son generadas por el propio Django
una vez ejecutamos dentro de nuestro proyecto el comando de python manage.py
makemigrations. Una vez ya tengamos todas las migraciones de nuestros mode-
13
14 Capítulo 3. Arquitectura global del sistema
los, podremos ejecutar el comando python manage.py migrate para ejecutar todo el
código SQL generado por el comando anterior en el gestor de base de datos y así
formar todas las tablas consecuentes.
El uso de esta característica de Django ayuda enormemente no solo en la crea-
ción de base de datos si no también en la propia modificación de la misma. En
nuestro caso, dado nuestros objetivos como proyecto, hemos tenido que realizar in-
contables cambios en la base de datos, por lo que tener a nuestra disponibilidad esta
herramienta ha sido de enorme ayuda.
3.1.1. Modelo entidad-relación
Como todo proyecto donde se involucre una base de datos, se debe planificar la
estructura de la misma con un modelo entidad-relación.
Como explicamos anteriormente, dadas a las especificaciones del nuestro proyec-
to, la base de datos ha sufrido numerosos cambios a lo largo de todo el transcurso del
trabajo, no solo con atributos o propiedades modificadas sino que también hemos
añadido nuevas entidades y hemos reestructurado todo el modelo en general.
Este año recibimos un modelo entidad-relación como el que podemos ver en la
figura 3.2. Este modelo contaba con numerosas entidades y relaciones que deberíamos
entender con rapidez para empezar a trabajar en el proyecto.
Tras numerosas modificaciones y ajustes llegamos al modelo entidad-relación
final que se presenta en la figura 3.3. En las siguientes secciones explicaremos más
en detalle como fue el desarrollo de las principales modificaciones de la base de datos.
3.1.2. Modelo relacional
El modelo entidad-relación es una manera de explicar la base de datos a muy
alto nivel, por lo que la creación de un modelo relacional es fundamental para el
buen entendimiento de la estructura de la base de datos. Django tiene herramientas
para generar de manera automática un modelo relacional a partir del código con
todas las relaciones y atributos explicados con alto detalle, pero su poca legibilidad
nos motivó a hacer un modelo relacional propio para, no solo entender mejor el
1 class Asignatura(models.Model):
2 objects = AsignaturaManager()
3 nombreAsignatura = models.CharField(blank=True,max_length=254,
4 verbose_name="nombreAsignatura",
5 unique=True)
6
Figura 3.1: Modelo de Django implementado
3.1. Base de datos 15
Figura 3.2: Modelo entidad-relación Qwizer del año pasado [4]
Figura 3.3: Modelo entidad-relación final
16 Capítulo 3. Arquitectura global del sistema
api_asignatura
id BIGINT
nombreAsignaturaCHARACTER VARYING(254)
api_cuestionario
id BIGINT
titulo CHARACTER VARYING(100)
duracion INTEGER
secuencial BOOLEAN
password CHARACTER VARYING(300)
fecha_visible TIMESTAMP(6) WITH TIME ZONE
fecha_aperturaTIMESTAMP(6) WITH TIME ZONE
fecha_cierre TIMESTAMP(6) WITH TIME ZONE
aleatorizar BOOLEAN
asignatura_id BIGINT
profesor_id BIGINT
api_cursa
id BIGINT
alumno_id BIGINT
asignatura_idBIGINT
api_imparte
id BIGINT
asignatura_idBIGINT
profesor_id BIGINT
api_instanciaopciontest
id BIGINT
orden SMALLINT
respuesta_idBIGINT
instancia_idBIGINT
api_instanciapregunta
id BIGINT
orden SMALLINT
intento_id BIGINT
pregunta_idBIGINT
seleccion_idBIGINT
api_instanciapreguntatest
instanciapregunta_ptr_idBIGINT
respuesta_id BIGINT
api_instanciapreguntatext
instanciapregunta_ptr_idBIGINT
respuesta CHARACTER VARYING(254)
api_intento
id BIGINT
nota NUMERIC(10,2)
hash CHARACTER VARYING(254)
hash_offline CHARACTER VARYING(254)
fecha_inicio TIMESTAMP(6) WITH TIME ZONE
fecha_fin TIMESTAMP(6) WITH TIME ZONE
estado CHARACTER VARYING(3)
cuestionario_idBIGINT
usuario_id BIGINT
api_opcionpreguntaaleatoria
id BIGINT
pregunta_id BIGINT
pregunta_aleatoria_idBIGINT
api_opciontest
id BIGINT
opcion CHARACTER VARYING(254)
orden SMALLINT
fijar BOOLEAN
pregunta_idBIGINT
api_pregunta
id BIGINT
pregunta CHARACTER VARYING(254)
titulo CHARACTER VARYING(254)
asignatura_idBIGINT
api_preguntatest
pregunta_ptr_idBIGINT
respuesta_id BIGINT
api_preguntatext
pregunta_ptr_idBIGINT
respuesta TEXT
api_seleccionpregunta
id BIGINT
puntosAciertoNUMERIC(30,2)
puntosFallo NUMERIC(30,2)
orden SMALLINT
fijar BOOLEAN
aleatorizar BOOLEAN
tipo CHARACTER VARYING(3)
cuestionario_idBIGINT
pregunta_id BIGINT
api_user
id BIGINT
password CHARACTER VARYING(128)
last_login TIMESTAMP(6) WITH TIME ZONE
is_superuserBOOLEAN
email CHARACTER VARYING(255)
first_name CHARACTER VARYING(30)
last_name CHARACTER VARYING(150)
role CHARACTER VARYING(7)
is_active BOOLEAN
is_staff BOOLEAN
date_joined TIMESTAMP(6) WITH TIME ZONE
api_user_groups
id BIGINT
user_id BIGINT
group_idINTEGER
api_user_user_permissions
id BIGINT
user_id BIGINT
permission_idINTEGER
Figura 3.4: Modelo relacional
esquema de la base de datos que heredamos de nuestros compañeros, sino también
para ayudar en un futuro a posibles compañeros que decidan proseguir con nuestro
trabajo.
Como podemos ver en la figura 3.4, realizamos un modelo relacional en la apli-
cación con la herramienta Draw.IO que nos ayudó enormemente a lo largo de este
proyecto.
3.2. API de comunicación entre back-end y front-
end
Como hemos ido anticipando, la comunicación entre los dos lados de la aplicación
es importante para el funcionamiento de la misma. Para ello se realizan las APIs,
que permiten que el back-end y el front-end se comuniquen, intercambiando datos o
ejecutando acciones especificas. Como proyecto continuación de otro anterior, here-
damos una API ya realizada con anterioridad por nuestros compañeros, que hemos
3.3. Aspectos de Django 17
Figura 3.5: Vista del panel de administración de Django
modificado y adaptado mucho mejor al entorno de Django y React, nuestras dos
tecnologías principales para cada lado respectivamente.
3.3. Aspectos de Django
En esta sección hablaremos sobre los aspectos más específicos del framework
Django que hemos utilizado para el desarrollo back-end de nuestra aplicación.
3.3.1. Panel de administración
Una de las herramientas más potentes de Django es su terminal de adminis-
tración. Esta herramienta ofrece la posibilidad a los usuarios .administradores" de
realizar consultas e inserciones en la base de datos con mucha facilidad. De hecho, tal
y como también hicieron nuestros compañeros del curso pasado, es posible delegar
en esta herramienta de administración algunas de las funcionalidades que no se han
implementado directamente en la aplicación web de Qwizer, tales como el registro
de profesores o el registro de nuevas asignaturas. En la figura 3.5 podemos ver la
vista de la herramienta.
Lo que sí hemos tratado de hacer durante este año es mejorar el uso del panel de
administración, exprimiéndola lo máximo posible y ajustándola solo a las funciona-
lidades que necesitamos que tenga. Entre las mejoras realizadas podemos encontrar
las siguientes:
Focalización en las opciones más relevantes: Anteriormente, el panel
de administración ofrecía el acceso a todas las tablas de la aplicación, donde
18 Capítulo 3. Arquitectura global del sistema
algunas de ellas no eran necesarias, ya que dentro de Qwizer estaban esas fun-
cionalidades. Por lo que decidimos eliminar el acceso a las tablas innecesarias y
dejar la interfaz mucho más limpia y escueta para los propios administradores.
Registro de usuarios: Cuando se incluye la tabla de usuarios en el panel de
administración, por defecto, se utiliza la entidad Usuarios de Django, la cual
tiene numerosos campos que nosotros no tenemos en cuenta para el almacena-
miento de los mismos. Esto hacía que la creación de usuarios fuera algo lenta
e incluso pesada. Por ello decidimos eliminar los atributos innecesarios y así
simplificar el proceso de registro.
Inserción en lote de usuarios: Una nueva funcionalidad que nos pareció
bastante útil fue el hecho de añadir la posibilidad de insertar usuarios en
lote. Anteriormente, los usuarios se tenían que crear uno a uno. Por tanto
permitimos que los administradores pudieran añadir varios usuarios de una
sola vez. Para ello, en la tabla de usuarios hay una opción de importar usuarios
donde podemos introducir un fichero CSV que contenga toda la información de
los usuarios para registrarlos directamente. Un ejemplo de esto sería la figura
3.6.
email firstname lastname password role
alumno1@ucm.es alumno 1 1 student
alumno2@ucm.es alumno 2 2 student
profesor@ucm.es profesor 3 teacher
Figura 3.6: Fichero CSV con usuarios
3.4. Librerías externas
En esta sección hablaremos sobre las librerías externas de Django y React que
hemos utilizado en la aplicación.
3.4.1. Librerías para el back-end
djangorestframework [19]: Permite la creación de una API REST de mane-
ra sencilla. El año pasado, entre otras cosas, nuestros compañeros la utilizaron
por el sistema de Tokens de sesión que utilizaba esta librería, la cual sim-
plificaba el complejo sistema de sesiones de Django y conseguía los objetivos
relacionados con el apartado offline de la aplicación. Nosotros hemos decidido
ir un poco más lejos y aprovechar más esta librería utilizando sus componentes
básicos para la creación propia de la API. Dado que esto supone una mejora
sustancial en la implementación, hablaremos más extensamente sobre el uso
de esta librería en el apartado 3.5.2.
3.4. Librerías externas 19
1 #qwizer_be/.env.sample
2 REACT_APP_API_URL=
3 REACT_HOST=
4 REACT_PORT=
5
6 #qwizer_fe/.env.sample
7 SECRET_KEY=
8 DEBUG=
9 ALLOWED_HOSTS=
10 DATABASE_ENGINE=
11 DATABASE_NAME=
12 DATABASE_USER=
13 DATABASE_PASSWORD=
14 DATABASE_HOST=
15 DATABASE_PORT=
16 CORS_ALLOW_ALL_ORIGINS=
17 CORS_ORIGIN_WHITELIST=
18 CSRF_TRUSTED_ORIGINS=
19
Figura 3.7: Fichero env de Django
django-environ [15]: Permite la creación de un entorno de desarrollo en
Django más amigable y seguro. Tanto en el despliegue como en el desarrollo
del proyecto, Django maneja numerosas variables de configuración y hacen que
sus ficheros sean difíciles de seguir. Además, el uso de esta biblioteca permite
encapsular en un solo fichero .env todas las variables de configuración que
se necesiten. Esto facilita el entendimiento general de la configuración del
proyecto y ayuda a realizar modificaciones de manera más simple. En la figura
3.7 podemos ver un ejemplo de este fichero de configuración.
django-import-export [2]: Permite importar y exportar objetos relaciona-
dos con las tablas de la base de datos. En la sección anterior hablamos del uso
del panel de administración dentro de nuestro proyecto. Esta librería es la que
nos ha permitido añadir usuarios en lote.
djoser [18]: Ofrece una serie de endpoints relacionados con acciones básicas
como son el registro, inicio y cierre de sesión. Esta librería se integra con el mo-
delo de Tokens de la biblioteca djangorestframework para intentar mejorar
el sistema de sesiones sobre todo con frameworks como React, que mantienen
un modelo SPA que dificulta el mantenimiento de las propias sesiones.
drf-spectacular [6]: Permite la documentación de la API en Django. Esta
librería, que trabaja con el estándar de Open API 3, permite la integración
con Swagger(ver subsección 2.3.7) nos ha ayudado a una generación de la
documentación de la API muy detallada.
gunicorn [1]: También conocido como Green Unicorn, es un servidor HTTP
20 Capítulo 3. Arquitectura global del sistema
compatible con Django que se utiliza para ejecutar aplicaciones web Django.
Actúa como un servidor intermediario entre el servidor web y la aplicación
Django, gestionando las solicitudes HTTP y enviando las respuestas corres-
pondientes. Gunicorn implementa el estándar WSGI (Web Server Gateway
Interface), que define una interfaz común entre los servidores web y las aplica-
ciones web en Python. En resumen, Gunicorn es una parte integral del entorno
de Django, brindando un servidor HTTP robusto y eficiente para ejecutar apli-
caciones web Django en producción.
3.4.2. Librerías para el front-end
axios [10]: Permite tener un cliente HTTP para hacer llamadas a la API de
manera más cómoda. En la sección de mejoras (ver sección 3.5) hablaremos
más extensamente de cómo hemos añadido esta librería a nuestro proyecto,
pero el año pasado, nuestros compañeros utilizaban la propia llamada fetch
de JavaScript puro, donde tenían que construir ellos sus propias peticiones
para llamar a la API. Con axios este proceso se simplifica con una sintaxis
muy sencilla y permitiendo centralizar todo el manejo de llamadas a la API
en un solo componente.
localforage [12]: Permite el acceso al almacenamiento local y a la base de
datos indexada en el navegador a través de un programa en JavaScript. En
nuestra aplicación hacemos un gran uso del almacenamiento local para guardar
cuestionarios, tokens e incluso información del usuario. Esta biblioteca nos
permite acceder a este almacenamiento y también a la base de datos indexada
que hay en los navegadores, la cual permite almacenar mucho más contenido
que los escasos 5MB del almacenamiento local. Además, la librería ofrece una
interfaz muy fácil de usar muy parecida al esquema de clave-valor que estamos
acostumbrados a usar.
react-router-dom [14]: Permite simular en React la función de servir dis-
tintas páginas al cliente desde diferentes rutas. Normalmente, en las páginas
web que no utilizan React, cuando accedemos a un ruta especifica el servidor
nos devuelve un fichero HTML único para esa ruta. Como React se basa en un
modelo SPA no podemos permitirnos esta característica, por lo que tenemos
que simularla de alguna manera. Esta librería nos permite devolver un com-
ponente de React por cada ruta instanciada en la aplicación manteniéndose
siempre en una sola página.
hello-pangea/dnd [5]: Permite un sistema de drag and drop en las listas de
nuestra aplicación. Algunas veces, dentro de Qwizer necesitamos que nuestros
usuarios coloquen una serie de preguntas en orden, por lo que con el fin de
facilitarles esta tarea hemos decidido añadir esta librería, que aparte de hacer
más estético este proceso, permite cambiar el orden entre los elementos dentro
de una lista. En la figura 3.8 podemos ver un ejemplo de esta librería en uso.
3.5. Mejoras realizadas sobre el trabajo anterior 21
Figura 3.8: Ejemplo de librería hello-pangea/dnd
3.5. Mejoras realizadas sobre el trabajo anterior
En esta sección vamos a hablar de las mejoras que hemos realizado a lo largo
del proyecto sobre los elementos que ya venían implementados en el trabajo del año
pasado.
3.5.1. Front-end
3.5.1.1. Refactorización de clases a funciones
El código de un componente en React se puede estructurar de dos maneras:
mediante clases o mediante funciones. Según los propios desarrolladores de React,
el sistema mediante clases ha quedado totalmente obsoleto debido al cambio en el
estándar de JavaScript y a problemas de rendimiento. Por esta razón, decidimos que
debíamos cambiar todos los componentes de nuestros compañeros del año pasado
a un enfoque funcional, es decir, componentes basados en funciones. Esto, además
de mejorar el rendimiento de la aplicación, nos ayudaría a actualizar el código y
programar en la manera más correcta de React.
A lo largo de la refactorización, también nos encontramos con numerosas varia-
bles declaradas con el tipo var de JavaScript que según el estándar se deberían de
evitar por su comportamiento dentro del código, por lo que fuimos cambiando las
variables a la declaraciones actuales de let y const. Otro dato a mencionar fue el
hecho de cambiar las extensiones de todos los ficheros de los componentes de React
a ficheros JSX. Anteriormente, todos los componentes estaban escritos en formato
JSX pero los ficheros estaban declarados como ficheros de JavaScript.
Aparte de estos cambios, también decidimos ordenar y organizar el código para
un mejor entendimiento a la hora de desarrollar, estructurando mejor sobre todo la
carpeta src del proyecto, separando componentes, hooks, servicios, ficheros CSS e
imágenes.
3.5.1.2. Actualización de librerías
Debido al cambio de estructura del código de React y el paso del tiempo, muchas
de las librerías que se utilizaban el proyecto del año pasado quedaron algo desactua-
22 Capítulo 3. Arquitectura global del sistema
Figura 3.9: Ejemplo de linter de React
lizadas o obsoletas. Por ese motivo necesitábamos actualizar las librerías y eliminar
las que no se utilizarán. Gracias a que cualquier proyecto de React trabaja sobre un
entorno de Node.js, podemos actualizar todas las librerías que utilicemos de manera
prácticamente instantánea ejecutando el comando de npm update, el cual actuali-
zará todas las librerías que tengamos declaradas en el fichero package.json. En el
caso que quisiéramos eliminar cualquier biblioteca, simplemente debemos ejecutar
el comando npm uninstall .
Aparte de estos cambios, queremos destacar el cambio de versión de la pro-
pia librería de React. La versión anteriormente utilizada por nuestros compañeros,
React 17, tenía numerosos problemas de ejecución con la definición de componentes
mediante funciones, por lo que tuvimos que realizar un cambio a la versión más
actualizada, React 18, en la que cambiaba como se trataba el renderizado principal
de la página.
3.5.1.3. Arreglo de errores y linting
Para el arreglo de errores generalizado durante la refactorización del código y
nuestro futuro desarrollo en la aplicación, decidimos que lo mejor sería añadir linters
a nuestro editor de texto, Visual Studio Code. Los linters son extensiones de Visual
Studio Code que permiten mostrar errores en la sintaxis o dar consejos sobre cómo
estructurar mejor el código bajo un estándar. En nuestro caso, añadimos linters
tanto de React como de JavaScript para que nos ayudasen a encontrar errores más
deprisa y evitar que cometiésemos errores innecesarios. En la figura 3.9 podemos ver
una de las recomendaciones que nos hace el linter de React.
3.5.1.4. Configuración del entorno del desarrollo
Para facilitar el desarrollo a lo largo del proyecto pensamos que una buena confi-
guración de nuestro entorno y editor de trabajo es esencial. Anteriormente, ya hemos
hablado de la mejora con los linters para la identificación de errores y mejoras en
nuestro código, pero también cabe destacar la configuración del entorno con nuestro
nuevo compilador de React, Vite.
De la misma manera que en Django, React maneja una serie de configuraciones
básicas para ejecutar su código. Vite permite, al igual que hacíamos con django-
environ (ver sección 3.4.1), almacenar en un fichero tipo .env todas las variables
de configuración necesarias, abstrayendo así todo en un simple fichero y mejorando
la legibilidad del código en general.
3.5. Mejoras realizadas sobre el trabajo anterior 23
1 export const Subjects = {
2 getAll: (_data = {}, config = {}) =>
3 client.get(’subject’, config),
4 getTests: ({ idAsignatura }, config = {}) =>
5 client.get(‘subject/${idAsignatura}/cuestionarios‘, config),
6 getQuestions: ({ idAsignatura }, config = {}) =>
7 client.get(‘subject/${idAsignatura}/preguntas‘, config),
8 getFromStudentOrTeacher: (_data = {}, config = {}) =>
9 client.get(’subject/me’, config),
10 enrollStudents: ({ alumnos, asignatura }, config = {}) =>
11 client.post(‘subject/${asignatura}/enroll‘, { alumnos }, config),
12 deleteStudentsFromSubject: ({ alumnos, asignatura }, config = {}) =>
13 client.post(‘subject/${asignatura}/delete_enroll‘, { alumnos }, config)
↪→ ,
14 };
15
Figura 3.10: Fragmento del fichero de comunicación con la API
Además, Visual Studio Code permite configurar comandos personalizados para
ejecutar dentro de nuestros proyectos. En nuestro caso hemos configurado una serie
de comandos para arrancar la aplicación, arrancando tanto el back-end como el
front-end de manera automática en sus respectivos puertos, iniciar tests de prueba
y depurar nuestra aplicación. Todo estos comandos se encuentran especificados en
el fichero tasks.json.
3.5.1.5. Mejoras generales
Abstracción de la API: Anteriormente, las llamadas a la API estaban espar-
cidas por todos los componentes de la aplicación, donde por cada llamada se
formaba la petición, repitiendo así muchísimo código y haciendo que el control
y mantenimiento de todas las llamadas fuera complicado de gestionar. Por esta
razón, primero, formamos con la nueva librería axios un método que creará
la petición para evitar repetir lineas de códigos por cada llamada a la API y
después, decidimos unificar en un fichero todas las llamadas para mejorar el
control y la expansión de la API. En la figura 3.10 podemos ver como quedó
parte del dicho fichero, donde se aprecian todas las llamadas relacionadas con
las asignaturas.
Abstracción de rutas: Anteriormente, también todas las rutas que hay en
el lado del cliente se colocaban directamente en el código, y como buena prác-
tica decidimos añadir un fichero de constantes que reuniera todas las rutas
utilizadas y así hacer más legible el código.
React Router y ProtectedRoutes: Como ya comentamos en la sección de
librerías externas (ver sección 3.4.2), el uso de react-router ayudaba a man-
24 Capítulo 3. Arquitectura global del sistema
tener la arquitectura SPA de React aunque se cambiará de ruta. Esta librería
ofrece un componente Router que engloba a todas las rutas de la API, donde a
cada una se le asigna un componente especifico de React que se debe devolver
cuando se acceda a esa ruta. El año pasado, nuestros compañeros no llegaron
a aplicar esta librería correctamente, debido a que declararon un componente
Router por cada ruta de la API y pensaban que tenían que estructurar dentro
toda la página que querían devolver al usuario. Esto desembocó en un mal uso
de esta librería y en un componente App.js demasiado extenso. Por lo que
este año hemos conseguido implementarla correctamente, reduciendo enorme-
mente el código del componente principal y hemos añadido que ciertas rutas
sean exclusivas solo para ciertos tipos de usuario. Por ejemplo, si un estudiante
tratara de acceder a la vista de crear cuestionarios introduciendo directamente
la ruta en la barra de direcciones del navegador, no se le permitiría el acceso.
useFetch, hook para llamadas asíncronas: Cuando el estado de un elemen-
to de React depende de datos asíncronos se suele utilizar el hook useEffect
de React para controlar cuándo debe de renderizarse otra vez el componente
una vez actualizado dicho elemento. Como esto es algo recurrente dentro de
nuestra aplicación, con el afán de reutilizar código decidimos crear un hook
personalizado que tratara todas estas situaciones de igual manera.
useAuth, hook de autenticación: Con el fin de centralizar toda la lógica
relacionada con la autenticación de los usuarios, decidimos crear un hook per-
sonalizado que englobara todas las operaciones de autenticación y manejara la
información del usuario que está logueado en ese momento.
3.5.2. Back-end
3.5.2.1. Refactorización de código
Lo primero que intentamos hacer al principio del proyecto, era comprender el
código del proyecto tanto a nivel de back-end como front-end. A nivel de back-end,
ya que nunca habíamos utilizado Django, entender todo el código al principio fue
una tarea bastante dura. La organización del código era algo compleja y difícil de
entender, con ficheros extensos y estructuras muy profundas. Por ello empezamos un
proceso de refactorización de código en el que nuestro objetivo era fundamentalmente
organizar el código de una manera que nosotros lo entendiéramos.
Primero de todo, empezamos organizando los nombres de las rutas de la API.
Antes, se utilizaban nombres poco específicos que no dejaban claro qué hacía cada
método. Después, empezamos a estructurar el código de una manera organizada,
eliminando redundancias y haciendo más legible cada método. Además, todas las
llamadas a la API estaban con métodos POST, cuando deberían ser GET y también
los ajustamos.
Finalmente, pasamos a estudiar más en profundidad la librería de djangorest-
framework. En esta librería explicaban el uso de algunas herramientas que nos
3.5. Mejoras realizadas sobre el trabajo anterior 25
servirían a mejorar la estructura general de la API. Dentro de estos consejos se en-
contraban los conocidos como viewsets, que explicaremos en el apartado de mejoras
generales (ver sección 3.5.2.5).
3.5.2.2. Actualización de librerías
De la misma manera que actualizamos las bibliotecas del front-end también
nos enfocamos en poner al día todas las librerías utilizadas en el back-end. Para
empezar, antes de actualizar todas las bibliotecas, decidimos implementar un entorno
virtual de Python en nuestra aplicación. Los entornos virtuales permiten tener una
instancia de Python especifica con una serie de dependencias instaladas asociadas al
proyecto, sin obligar al cliente a tener que instalar todas estas en su propio equipo.
Anteriormente, para poder trabajar sobre el proyecto teníamos que instalar todas
las dependencias y librerías asociadas una a una en nuestros equipos, con el riesgo
de tener conflictos con nuestras librerías propias instaladas y además haciendo este
proceso tedioso y molesto para los desarrolladores. Con un entorno virtual evitamos
esto y abstraemos así el entorno del proyecto de la configuración propia de nuestros
equipos.
Para mantener el proyecto de manera más segura y estable, necesitábamos un
gestor de dependencias para todas las librerías del proyecto y una herramienta que
creará el entorno virtual. Por este motivo, al principio, utilizamos pipenv. Pipenv es
un gestor de dependencias que trabaja con entornos virtuales. Permite crear entornos
virtuales y a través de un fichero Pipfile, donde se guardan todas las dependencias
del proyecto e instalar en el mismo entorno virtual todos las librerías especifica-
das. Durante parte del periodo de desarrollo, utilizamos este gestor hasta que más
adelante decidimos pasarnos a otro gestor de dependencias llamado Poetry.
Poetry, del mismo modo que pipenv, es otro gestor de dependencias, pero en este
caso permite almacenar todas las dependencias del proyecto con sus configuraciones
en un solo fichero .toml. Según los estándares de Python más actuales, la gestión
de un proyecto se debe mantener mediante este tipo de ficheros, por lo que para
mantener nuestro proyecto lo más actualizado y correcto posible decidimos cambiar
pipenv por poetry.
Por último, añadimos la actualización de Django a su versión más actualizada,
Django 4.1. En el proyecto anterior, se utilizaba Django 3, por lo que temíamos
que su actualización diera muchos problemas de compatibilidad. Aún así, no hubo
muchas dificultades en este proceso.
3.5.2.3. Arreglo de errores y linting
Al igual que en el front-end, para el arreglo de errores decidimos utilizar los linters
de Django y Python. Estos nos ayudarían a detectar errores básicos en la sintaxis
de Python, como errores de espaciados, excepciones o fallos generales. Respecto a
Django, también incluía consejos de cómo utilizar determinados elementos y dónde
26 Capítulo 3. Arquitectura global del sistema
Figura 3.11: Consola avanzada de Django
era mejor utilizarlos.
3.5.2.4. Configuración del entorno del desarrollo
Como ya explicamos en la sección de las librerías externas de back-end (ver
sección 3.4.1), el uso de django-environ nos permitió almacenar todas las variables
de configuración del proyecto en un solo fichero .env y poder utilizarlo en cualquier
lado del proyecto facilitándonos enormemente el entendimiento de la configuración
del proyecto al principio.
Además de esto, también configuramos una serie de extensiones de Django, con la
librería django-extensions. La extensión que cabe destacar y más hemos utilizado
ha sido la consola avanzada de Django. Esta consola permite ejecutar consultas en
Python sobre cualquiera de los modelos de Django y ver qué resultados devuelven,
permitiéndonos hacer pruebas y asegurarnos de nuestras decisiones. En la figura 3.11
podemos ver cómo hacemos uso de esta consola.
3.5.2.5. Mejoras generales
Dentro de las mejoras generales que hemos podido aplicar al back-end de la apli-
cación, está el uso de algunos elementos clave de la librería djangorestframework
(ver sección 3.4.1).
Serializadores : Los serializadores son herramientas que ofrece la librería para
convertir los objetos utilizados por Django, como pueden ser los querysets o los
modelos, en tipos nativos de Python, como por ejemplo JSON o XML. Esto
es clave para el tratamiento de datos en algunos elementos de esta librería y
además nos permite devolver directamente información al front-end sin tener
que transformar previamente los objetos obtenidos de la base de datos.
3.5. Mejoras realizadas sobre el trabajo anterior 27
Viewsets : Generalmente, todas las rutas que se manejan en Django se deben
colocar en el fichero de views.py, donde por cada ruta se escribe un método en
el que se debe tratar la petición, aplicar cierta lógica de negocio y devolver una
respuesta al front-end. Esta estructura, en proyectos tan amplios como este,
conlleva a que el fichero views.py sea extremadamente largo. Para solucionar
este problema hemos aplicado los viewsets de la librería djangorestframe-
work.
Los viewsets ofrecen una serie de rutas determinadas para un modelo de la
base de datos especifico, ofreciendo rutas como por ejemplo, la creación de un
objeto o el listado de todos los objetos de ese modelo. Además de las rutas que
ofrecen, se pueden añadir más rutas con el decorador de @action, en el que
escribimos un método como lo hacíamos anteriormente en el fichero views.py.
Las ventajas de los viewsets, es que permiten desacoplar las rutas en varios
ficheros y además ahorran código y trabajo con llamadas anteriormente confi-
guradas.
Managers, cambio en el ORM: Anteriormente, cuando queríamos recu-
perar información sobre cualquier modelo de nuestra aplicación, utilizábamos
directamente el ORM de Django para filtrar y conseguir solamente la informa-
ción que necesitábamos. Esto normalmente se define como una mala práctica
dado que genera numerosa redundancia y empeora el mantenimiento del códi-
go. Por ello decidimos utilizar los managers de Django.
Estas herramientas permiten generar una interfaz para las llamadas a la base
de datos de los diferentes modelos de la aplicación. Debe haber un manager
por cada modelo dentro del proyecto y en estos básicamente añadimos métodos
que nos permitan recuperar cierta información de la base de datos. Un ejemplo
de un manager es el que podemos ver en la figura 3.12.
Transacciones: Conseguir un entorno transaccional en las operaciones de una
aplicación software es algo de gran importancia para mantener la consisten-
cia en los datos almacenados en la base de datos. Por eso nosotros, gracias a
los mecanismos que ofrece Django para ello, hemos conseguido implementar
transacciones en las llamadas a la API de la aplicación. Anteriormente, nues-
tros compañeros no utilizaron transacciones, provocando que cualquier fallo en
cualquier operación resultase en el almacenado de datos erróneos en nuestra de
base de datos. Por ende, hemos trabajado en implementar transacciones en las
operaciones que podían desencadenar en el guardado de datos inconsistentes
en nuestra aplicación. Para ello hemos hecho uso de la biblioteca transaction
que ofrece Django, la cual nos permite marcar las operaciones que necesitan
transacciones con el decorador @transaction.atomic y además ofrece un sis-
tema de savepoints para volver a diferentes estados de la transacción en caso
de errores.
28 Capítulo 3. Arquitectura global del sistema
1 class InstanciaPreguntaManager(models.Manager):
2 def create_instancia(self, id_intento, id_pregunta, orden, id_seleccion
↪→ , commit=False, **extra_fields):
3 obj = self.model(intento_id=id_intento,
4 pregunta_id=id_pregunta,
5 orden=orden,
6 seleccion_id=id_seleccion,
7 **extra_fields)
8 if commit:
9 obj.save()
10 return obj
11
12 def get_by_intento_pregunta(self,
13 id_intento,
14 id_pregunta):
15 return self.get_queryset()
16 .filter(
17 intento_id=id_intento,
18 pregunta_id=id_pregunta).first()
19 def get_by_intento(self, id_intento):
20 return self.get_queryset()
21 .filter(intento_id=id_intento)
22
Figura 3.12: Ejemplo de un manager de la aplicación
3.5. Mejoras realizadas sobre el trabajo anterior 29
3.5.2.6. Documentación de la API
Un apartado fundamental en la creación de una API es su documentación. Con
el objetivo de facilitar una documentación entendible para los lectores de nuestro
trabajo y posibles futuros compañeros, utilizamos la librería drf-spectacular. Esta
biblioteca permite redactar una documentación de API bajo el estándar de Open
API 3, permitiéndonos añadir por cada llamada una descripción de la misma, los pa-
rámetros esperados y detalles sobre las posibles salidas o respuestas de cada petición.
En la figura 3.13 podemos poder ver un pequeño ejemplo de cómo se implementa
esta librería en el propio código.
Además, otra de las razones por la que utilizamos drf-spectacular es su buena
integración con la herramienta Swagger. Esta herramienta, como ya comentamos en
el capítulo de herramientas (ver capítulo 2), además de ofrecer una interfaz gráfica
donde se pueden ver todas llamadas a la API, también permite realizar pruebas
interactivas y ver cómo sería la comunicación entre back-end y front-end.
En el apéndice de la memoria adjuntaremos toda la documentación generada en
el caso de que se desee leer (ver apéndice A).
3.5.2.7. Testing
Del mismo modo que la documentación es muy importante en el desarrollo soft-
ware, también lo son las pruebas del mismo. Aunque no estaba dentro de nuestros
objetivos iniciales, queríamos añadir algunos casos de prueba a la aplicación, sobre
todo para probar que nuestra lógica de negocio era correcta y funcionaba como no-
sotros queríamos a lo largo de los diferentes tests. Asimismo, esto también ayudaría
en un futuro a mantener mejor el código y también mejorar la compresión de la
complicada lógica que a veces tienen nuestros métodos.
Con ello, lo primero que tuvimos que hacer es preparar nuestro entorno de desa-
rrollo para las pruebas. Visual Studio Code lo pone fácil ofreciendo y identificando
rápidamente las pruebas de nuestra aplicación una vez que lse introducen en el
fichero settings.json del proyecto. En este fichero se identifican, aparte de otras con-
figuraciones, dónde se encuentran los ficheros dedicados a las pruebas.
Ahora, para el desarrollo propio de las pruebas, utilizamos una clase de la librería
de djangorestframework.test, ApiTestCase. Con esto podíamos declarar una
clase que herede de ApiTestCase y dentro de esta nueva clase, tendríamos que
declarar una función preparatoria llamada setUp, donde se inicialicen todas las
variables que vayamos a utilizar en los tests y después ya escribiríamos todas las
pruebas que quisiésemos separadas cada una en funciones de Python. El esquema
general de los tests es el que se puede ver en la figura 3.14.
Finalmente, también añadimos la librería coverage, que nos permitía saber qué
porcentaje del código se probaba una vez que se lanzaban los tests de nuestra aplica-
ción, además de generar un fichero coverage.xml que contaba por cada fichero dentro
de nuestro proyecto cuántas veces los tests pasaban por cada línea.
30 Capítulo 3. Arquitectura global del sistema
1 @extend_schema_view(
2 list=extend_schema(
3 summary="Lista de asignaturas",
4 responses={
5 200: OpenApiResponse(
6 response={
7 "type": "object",
8 "properties": {"asignaturas": {"type": "array", "items
↪→ ": {"type": "object", "properties": {"id": {"type": "string"}, "
↪→ asignatura": {"type": "string"}}}}},
9 },
10 ),
11 },
12 ),
13 cuestionarios=extend_schema(
14 summary="Lista de cuestionarios de una asignatura",
15 parameters=[
16 OpenApiParameter(name="id", type=int, location=OpenApiParameter
↪→ .PATH, description="Id de la asignatura"),
17 ],
18 responses={
19 200: OpenApiResponse(
20 response={
21 "type": "object",
22 "properties": {
23 "cuestionarios": {"type": "array", "items": {"type
↪→ ": "object", "properties": {"id": {"type": "string"}, "titulo": {"
↪→ type": "string"}}}},
24 "nombre": {"type": "string"},
25 },
26 }
27 )
28 },
29 ),
30
Figura 3.13: Ejemplo de implementación de documentación de API
3.5. Mejoras realizadas sobre el trabajo anterior 31
1
2 class Question(APITestCase):
3 def setUp(self):
4 self.user = User.objects.create_user(email="test@root.com",
↪→ first_name="root", last_name="root", password="root", role="teacher
↪→ ")
5 self.client.force_authenticate(user=self.user)
6 self.asignatura = Asignatura.objects.create_asignaturas(
↪→ nombreAsignatura="Test", commit=True)
7
8 def test_upload_question(self):
9 ...
10 ...
11 self.assertEqual(response_upload.status_code, status.HTTP_200_OK)
12 self.assertEqual(response.status_code, status.HTTP_200_OK)
13
Figura 3.14: Ejemplo de un fichero de pruebas
3.5.3. Nuevas funcionalidades extras
Además de todas las mejoras incluidas en el proyecto, también hemos añadido
algunas funcionalidades nuevas que no estaban presentes en la aplicación del año
anterior. Por eso, en esta sección vamos a hablar de ellas.
Listado de alumnos matriculados: Anteriormente, en el momento en que
un profesor quería matricular un alumno dentro de una asignatura, solo podía
ver un listado de los alumnos que todavía no estaban matriculados. Ahora, en
la vista donde antes aparecían los alumnos a matricularse en una asignatura,
aparece un listado de todos los alumnos matriculados en la asignatura elegida.
Nuevo registro de usuarios en asignaturas: Aunque antes sí se podía
matricular alumnos en asignaturas desde la aplicación, este año nos hemos
visto obligados a añadir una vista para esta funcionalidad, ya que la anterior
vista que servía para matricular alumnos se ha sustituido con el listado total
de matriculados en una asignatura.
Para esta nueva vista hemos diseñado una ventana modal donde el registro se
hará de igual manera que se hacía anteriormente: aparecerán en una tabla los
alumnos sin matricular en la asignatura elegida y el profesor deberá añadir a
los alumnos que él decida. En la figura 3.15, se muestra esta nueva ventana
modal.
Baja de usuarios en asignaturas: Además de poder ver los alumnos matri-
culados en una asignatura, ahora se podrá, una vez que se marque un alumno
en el listado, dar de baja a uno o varios alumnos de la asignatura marcada.
32 Capítulo 3. Arquitectura global del sistema
Figura 3.15: Nueva ventana modal para la matriculación de alumnos
Capı́tulo 4
Aleatorización de cuestionarios
En este capítulo vamos a explicar cómo a lo largo de este proyecto hemos im-
plementado uno de los principales objetivos planteados para nuestro proyecto: la
aleatorización de cuestionarios.
4.1. Objetivo principal / Introducción
La aleatorización de los cuestionarios es un objetivo muy amplio que abarca
numerosos puntos a desarrollar. Principalmente, el objetivo inicial era ofrecer la
posibilidad de la aleatorización de las preguntas de un cuestionario, es decir, en el
caso que un profesor decidiese elaborar un cuestionario en el que sus preguntas se
mostrarían de forma aleatoria, cada estudiante vería su cuestionario en el que las
preguntas aparecen en un orden distinto al de los demás estudiantes. Poco a poco,
esta idea se fue agrandando y fuimos extendiendo este objetivo a algo más complejo,
en los que podemos identificar los siguientes objetivos:
Aleatorización de las opciones: Qwizer cuenta con un banco de preguntas
con diferentes tipos de pregunta. Dentro de los tipos que nos podemos encon-
trar, se encuentran las llamadas preguntas tipo test. En este tipo de preguntas
se le ofrece al usuario un número de opciones especifico con diferentes res-
puestas ante una pregunta. Al tratar el tema de la aleatorización, decidimos
ofrecer esta propiedad también a las opciones dentro de una pregunta de tipo
test, por lo que el profesor tendría la opción de elegir si quiere que las opciones
se barajen de manera aleatoria a los alumnos.
Fijación de preguntas y opciones: La aleatorización de las preguntas o de
las opciones de un cuestionario supone cambiar el orden relativo de las mismas.
Sin embargo, a lo mejor los profesores en la creación de sus cuestionarios
quieren mantener una pregunta o una opción en un lugar fijo. Por ejemplo, en
las opciones de una pregunta tipo test, nos podemos encontrar la opción de
Ninguna de las anteriores. En este caso, dado el significado de la misma
opción, lo correcto sería colocarla en la última opción posible. Si el profesor
aún así quiere que las opciones se dispongan de manera aleatoria, tendría que
tener la posibilidad de mantener algunas opciones en una cierta posición fija.
Preguntas aleatorias: Aparte de la aleatorización de las preguntas dentro
de un cuestionario, el profesor también agradecería la posibilidad de, en un nú-
33
34 Capítulo 4. Aleatorización de cuestionarios
mero de pregunta especifico de su cuestionario, agregar una serie de preguntas
a aparecer ante los usuarios. Por ejemplo, en un cuestionario un profesor deci-
de que en la pregunta 3 no quiere que aparezca una pregunta especifica, sino
que quiere que la aplicación elija entre un par de preguntas que él señale, es
decir, siguiendo el ejemplo anterior y teniendo en nuestro banco de preguntas
las preguntas 1,2,3 y 4, el profesor quiere que una determinada pregunta de
su cuestionario, puedan aparecer la pregunta 1,2 o 3 del banco de preguntas.
Esto es lo que nosotros hemos identificado como preguntas aleatorias.
4.2. Cambios en la base de datos
La incorporación de mecanismos de aleatorización en nuestros cuestionarios pro-
vocó grandes cambios en la estructura general de la base de datos, haciendo que la
misma pasase por numerosas modificaciones para la buena adaptación a esta nueva
característica. En esta sección iremos viendo la progresión de la base de datos a
medida que íbamos cumpliendo los objetivos marcados.
4.2.1. Creación de entidad Intento e instancias
El añadido de la aleatorización de las preguntas a un cuestionario planteaba
un grave problema ante nuestro modelo entidad-relación inicial. Anteriormente, las
respuestas de los estudiantes a las preguntas de un cuestionario se almacenaban
en una entidad llamada Respuestas enviadas. Cada vez que cualquier estudiante
respondía una pregunta de un cuestionario, se guardaba en esta tabla la respuesta
del mismo. Esta entidad no tenía ninguna relación con el cuestionario al que se
contestaba y saber el orden en el que un cuestionario se había contestado y qué
respuesta pertenecía a dicha pregunta era algo altamente complicado, por lo que por
el afán de centralizar mejor la lógica propia de los cuestionarios y sus realizaciones,
creamos la entidad Intento.
La entidad de intento almacenaría toda la información que se almacenaba antes
entre varias entidades en una sola, es decir, se guardaría que estudiante respondió
dicho intento sobre un cuestionario y qué respuestas había mandado ese estu-
diante ante las diferentes preguntas. Más adelante, decidiríamos que las respuestas
de los estudiantes se podrían interpretar como instancias de preguntas que el usuario
responde, por lo que con esto inició el concepto de las Instancias de preguntas.
Las instancias de preguntas almacenarían la respuesta de un usuario a una pre-
gunta y el orden en la que apareció en un intento. Esto nos ayudaría enormemente
después para el tratamiento de la aleatorización dentro de la aplicación, de cara, a
recuperar el orden final de las preguntas de un cuestionario para un usuario.
Con los modelos de Django, creamos estas dos nuevas entidades de una manera
muy sencilla y rápida. En el caso de las instancias tendríamos instancias para las
preguntas tipo test y las tipo text. Por lo que el modelo final de las instancias
4.2. Cambios en la base de datos 35
quedaría algo así:
1 class InstanciaPregunta(models.Model):
2 objects = InstanciaPreguntaManager()
3 intento = models.ForeignKey(Intento,
4 on_delete=models.CASCADE)
5 seleccion = models.ForeignKey(SeleccionPregunta,
6 on_delete=models.CASCADE)
7 pregunta = models.ForeignKey(Pregunta,
8 on_delete=models.CASCADE)
9 orden = models.PositiveSmallIntegerField()
10
11 class Meta:
12 ordering = ["pregunta"]
13
14 class InstanciaPreguntaTest(InstanciaPregunta):
15 objects = InstanciaPreguntaTestManager()
16 respuesta = models.ForeignKey(OpcionTest,
17 on_delete=models.CASCADE
18 ,null=True, blank=True)
19
20
21 class InstanciaPreguntaText(InstanciaPregunta):
22 objects = InstanciaPreguntaTextManager()
23 respuesta = models.CharField(null=True, blank=True,
24 max_length=254,
25 verbose_name="respuesta")
y el modelo de intento llegaría a ser algo así:
1 class Intento(models.Model):
2 objects = IntentoManager()
3 usuario = models.ForeignKey(User,
4 on_delete=models.CASCADE)
5 cuestionario = models.ForeignKey(Cuestionario,
6 on_delete=models.CASCADE)
7 nota = models.DecimalField(default=0, max_digits=10,
8 decimal_places=2,
9 verbose_name="nota")
10 hash = models.CharField(blank=True, max_length=254,
11 verbose_name="hash")
12 hash_offline = models.CharField(blank=True,max_length=254,
13 verbose_name="hash_offline")
14 fecha_inicio = models.DateTimeField(null=True,blank=False,
15 verbose_name="fecha_inicio")
16 fecha_fin = models.DateTimeField(null=True,blank=False,
17 verbose_name="fecha_fin")
18
19 class Estado(models.TextChoices):
20 PENDIENTE = ’PEN’, _(’Pendiente’)
21 ENTREGADO = ’ENT’, _(’Entregado’)
36 Capítulo 4. Aleatorización de cuestionarios
22
23 estado = models.CharField(
24 max_length=3,
25 choices=Estado.choices,
26 default=Estado.PENDIENTE,
27 )
Una vez tenemos un modelo entidad-relación más óptimo y preparado para esta
nueva característica, ya podemos seguir con nuevas propiedades.
4.2.2. Aleatorización de preguntas
Inicialmente, nuestros compañeros del año pasado, mostraban las preguntas de un
cuestionario de manera secuencial, es decir, según el orden en el que hubieran añadido
el profesor las preguntas al cuestionario en sí. Para conseguir la aleatorización de
las preguntas simplemente tuvimos que añadir un nuevo atributo en la entidad
Cuestionarios que controlase si el profesor quiere que las preguntas aparezcan de
manera aleatoria o no dentro de un cuestionario.
Como nos apoyamos en el ORM de Django, tuvimos que modificar el modelo de
la clase Cuestionario, añadiéndole el atributo nuevo de aleatorización, quedando
así un modelo como el siguiente:
1 class Cuestionario(models.Model):
2 objects = CuestionariosManager()
3 profesor = models.ForeignKey(User,
4 on_delete=models.CASCADE)
5 asignatura = models.ForeignKey("Asignatura",
6 on_delete=models.CASCADE)
7 ...
8 ...
9 aleatorizar = models.BooleanField(default=False)
Dentro de la aplicación, como podemos ver en la figura 4.1, hay un pequeño
ejemplo de cómo un profesor podría modificar este atributo a su antojo.
En el caso en que el profesor decidiese añadir un cuestionario a través de un
fichero YAML, simplemente tendría que añadir un pequeño campo de aleatorizar
como se puede ver en la figura 4.2.
Cabe recordar que, gracias a nuestro modelo basado en instancias de las pre-
guntas, podríamos guardar en qué posición apareció cada pregunta del cuestionario
en cada intento, permitiéndonos con este sistema recuperar de manera sencilla el
orden de todas las preguntas dentro de un intento.
4.2. Cambios en la base de datos 37
Figura 4.1: Modificación de aleatorización en creación de cuestionarios
1 cuestionario:
2 titulo: "Tema 8"
3 password: "1234"
4 asignatura: "Estructuras de datos"
5 secuencial: 0 #0 es que no es secuencial, 1 es secuencial
6 duracion: 10 #minutos que durara el test
7 fecha_apertura: ’21/02/20 11:00:00’ #Formato esperado: yy:mm:dd hh:mm:ss
8 fecha_cierre: ’23/03/24 11:59:59’ #Formato esperado: yy:mm:dd hh:mm:ss
9 fecha_visible: ’21/02/20 11:00:00’ #Formato esperado: yy:mm:dd hh:mm:ss
10 aleatorizar: True #Indica que se quiere aleatorizar el cuestionario
11
Figura 4.2: Aleatorización en cuestionarios vía YAML
38 Capítulo 4. Aleatorización de cuestionarios
Figura 4.3: Elección de aleatorización de opciones
4.2.3. Aleatorización de opciones
La aleatorización se debe hacer también a nivel de opciones dentro de las pregun-
tas tipo test. El profesor debería de poder elegir si las opciones de una pregunta test
deben mostrarse de manera aleatoria dentro de un intento. Al igual que nos pasó
con las preguntas dentro de los cuestionarios, nuestro modelo entidad-relación no
estaba todavía lo suficientemente preparado como para almacenar si una pregunta
dentro de un intento debería mostrar sus opciones de manera aleatoria y tampoco
estaba estructurado para guardar el orden con el que se mostrarían las opciones a
los estudiantes.
Para el primero de los problemas, añadimos un nuevo atributo en la clase
que almacenaba las preguntas pertenecientes a un cuestionario, al igual que en la
aleatorización de las preguntas, este nuevo atributo guardaría si la pregunta dentro
del cuestionario debería mostrar sus opciones de manera aleatoria. A consecuencia
de esta solución, el profesor podría marcar si las opciones de una pregunta tipo test
se mostrarían de forma aleatoria. Como podemos ver en la figura 4.3 es muy fácil
de controlar.
En el caso en que el profesor decida añadir un cuestionario a través de un fichero
YAML, lo único que tendrá que hacer es añadir un nuevo atributo en las preguntas
tipo test, como se puede ver en la figura 4.2, pero en vez de añadirlo a nivel de
cuestionario, deberá añadirlo a nivel de pregunta.
Conforme a esto podríamos controlar perfectamente el primero de los problemas,
pero para buscar una solución al segundo problema necesitamos pensar en cómo al-
macenar el distinto orden de las opciones en los diferentes intentos de los estudiantes.
Para solucionar esto utilizamos de nuevo nuestra mentalidad de las instancias y
con ello creamos así la clase de InstanciaOpcionTest. En este nuevo modelo de la
base de datos se guardaría una referencia de la opción test que guardase esa nueva
instancia y además el orden en el que apareció esta opción en el intento a la que
esta asignada.
El nuevo modelo en Django está indicado en la Figura 4.4, donde se puede
observar que guarda todos los datos anteriormente redactados:
4.3. Preguntas aleatorias 39
1 class InstanciaOpcionTest(models.Model):
2 objects = InstanciaOpcionTestManager()
3 instancia = models.ForeignKey(InstanciaPreguntaTest,
4 on_delete=models.CASCADE)
5 respuesta = models.ForeignKey(OpcionTest,
6 on_delete=models.CASCADE,
7 null=True, blank=True)
8 orden = models.PositiveSmallIntegerField()
9
Figura 4.4: Modelo InstanciaOpcionTest
4.2.4. Fijación de preguntas y opciones
Como ya hablamos con anterioridad, la aleatorización presentaba el problema
de preguntas o opciones de preguntas tipo test que el profesor quisiese que se que-
darán fijadas en una posición concreta. Por ejemplo, en el caso de la opción de
Ninguna de las anteriores, colocarla en una posición diferente a la última pro-
vocaría confusión en la propia pregunta. Por esta razón, para solucionar este pequeño
contratiempo, decidimos almacenar un nuevo campo fijar en los modelos donde se
guarda la información correspondiente a las preguntas pertenecientes a un cuestio-
nario, PerteneceCuestionario y al modelo donde se guarda la información sobre
las opciones de las preguntas de este tipo.
Gracias a esta implementación, una vez se recuperase una pregunta o una opción
de un intento se podría comprobar si la misma debería ir fijada a la posición en la
que se encuentra o no. En el apartado de la aplicación, en la vista de la creación de
un cuestionario, para la fijación de las preguntas, el profesor simplemente, una vez
elegido el orden en el que una pregunta debe aparecer, marcará si quiere que se fije
a esa posición como podemos ver en la figura 4.3 al lado del botón de aleatorizar.
En el caso en que el profesor decida subir un cuestionario mediante un fichero
YAML, una vez que ordene las preguntas en el orden canónico que quiere que apa-
rezcan, podrá añadir un nuevo campo fijar como podemos ver en las preguntas de
un cuestionario en la figura 4.5.
En relación a la fijación de las opciones de una pregunta, dado que nuestra
aplicación solo permite la incorporación de preguntas vía YAML, el protocolo para
ello es muy parecido a como se muestra en la figura anterior. Simplemente se debe
colocar el campo fijar en la opción que se quiera mantener en una posición concreta.
4.3. Preguntas aleatorias
Anteriormente, introducimos el concepto de preguntas aleatorias dentro de
nuestra aplicación Qwizer. Esta funcionalidad no estaba prevista dentro de nues-
40 Capítulo 4. Aleatorización de cuestionarios
1 preguntas:
2 - tipo: "test"
3 pregunta: "El problema de la mochila usa el esquema de :"
4 titulo: "Titulo 3"
5 opciones:
6 - op: "Divide y Venceras"
7 fijar: False
8 - op: "Vuelta atras"
9 fijar: True
10
Figura 4.5: Fijación en cuestionarios vía YAML
tros objetivos iniciales, pero, como nos comentó nuestro tutor, esta característica es
muy habitual en aplicaciones relacionados con cuestionarios, como es el ejemplo de
nuestra mayor inspiración, Moodle. La posibilidad de crear una pregunta dentro de
nuestros cuestionarios donde a cada usuario le apareciese una pregunta elegida de
un conjunto de preguntas seleccionadas específicamente para que esa pregunta, era
algo motivador y, desde nuestro punto de vista, algo complicado de realizar.
Desde un inicio, veíamos complicado la integración de esta funcionalidad dentro
de nuestro sistema, dado a varios factores, entre ellos:
Cambios en la base de datos: Desde un principio, nos dio la sensación de
que este nuevo añadido en la aplicación podría provocar otro gran cambio en
la base de datos. El hecho de tener que cambiar la estructura general después
de haber ajustado todo a un modelo mucho más limpio y útil para nuestras
funcionalidades principales, nos provocó, a primera vista, una sensación de
rechazo.
Impacto en el código: Pensando más en el código y su organización, el
añadido de las preguntas aleatorias provocaría seguramente muchos cambios en
la lógica principal de muchas de nuestras rutas en la API, provocándonos una
sensación de miedo a generar errores en algunas partes concretas del código.
Aún así con estos factores en mente y gracias a la motivación de nuestro tutor,
decidimos añadir esta funcionalidad para aumentar el valor de nuestra aplicación y
hacerla mucho más competente en su ámbito.
4.3.1. Nuevo modelo, SelecciónPregunta
Primero de todo, la implementación de esta funcionalidad provocaba, como ya
habíamos estimado, un gran cambio en la base de datos. La manera en la que se
almacenaban las preguntas pertenecientes a un cuestionario solo permitía almace-
nar preguntas del propio banco de preguntas, por lo que añadir a un cuestionario
4.3. Preguntas aleatorias 41
una pregunta que fuera de otro tipo, en este caso una pregunta aleatoria, era algo
imposible por como estaba estructurado el modelo entidad-relación.
Para ello tuvimos que cambiar la mentalidad con la que almacenaríamos las
preguntas pertenecientes a un cuestionario, llegando a la conclusión y a la ejecución
de un nuevo modelo SelecciónPregunta. Este nuevo modelo cambiaría totalmente
la perspectiva de la incorporación de preguntas a un cuestionario. Ahora, en vez de
añadir directamente preguntas del banco se añadirían selecciones de pregunta.
Estas selecciones pueden ser directamente preguntas del banco de preguntas,
o bien otro tipo de preguntas, como pueden ser las preguntas aleatorias.
Este modelo nos permitiría añadir, en un futuro, otros tipos de preguntas que no
tengan ninguna relación con el banco de preguntas o solo parcialmente, ampliando
así la capacidad de la aplicación para escalar de manera mucho más sencilla. Como
podemos apreciar en la figura 3.2 se puede ver cómo partíamos de un modelo más
escueto y pasamos a un modelo más complejo con la incorporación del modelo
SeleccionPregunta como podemos ver en la figura 3.3.
4.3.2. Uso dentro de la aplicación
Esta nueva funcionalidad está disponible para los cuestionarios que se suban vía
YAML. En el momento que los profesores quieran añadir una pregunta aleatoria,
tendrán que seguir los siguientes pasos para conseguirlo:
1. En el apartado de las preguntas del fichero YAML, añadir una pregunta nueva
con el nuevo tipo de pregunta, aleatoria.
2. A continuación, añadir el campo de preguntas_elegidas después del campo
tipo antes explicado. En este nuevo campo se introducirá el subconjunto de
preguntas del banco que queremos que puedan aparecer. Para referenciar las
preguntas del banco, recordar utilizar el campo ref y escribir el título de la
pregunta a referenciar.
3. Al igual que las preguntas regulares del banco, debemos añadir una pun-
tuación positiva y una puntuación negativa, con los campos punt_positiva
y punt_negativa respectivamente.
La Figura 4.6. muestra un ejemplo de inclusión de una pregunta aleatoria.
Además, dentro de los ejemplos de cuestionarios ya creados, se encuentra uno
con un test con una única pregunta aleatoria para que sirva de ayuda a los profesores
que comiencen a utilizar la aplicación. Este cuestionario se llama test2.yml.
42 Capítulo 4. Aleatorización de cuestionarios
1 preguntas:
2 - tipo: "aleatoria"
3 preguntas_elegidas:
4 - ref: "Titulo temporal2"
5 - ref: "Titulo temporal1"
6 punt_positiva: 10.0 #puntos que suma
7 punt_negativa: 0.0 #puntos que resta
8
Figura 4.6: Ejemplo de pregunta aleatoria en YAML
Capı́tulo 5
Introducción de Markdown y fórmulas
matemáticas en enunciados
En este capítulo abordamos unos de los principales desafíos en este trabajo: la
introducción de texto en formato Markdown, imágenes y fórmulas en los enunciados
de las preguntas. Esta parte del trabajo conllevaba una fase previa de investigación
para determinar las bibliotecas más adecuadas para la consecución de este objetivo.
5.1. Librerías utilizadas
Desde el inicio de nuestro proyecto, consideramos el uso de MathJax para im-
plantar las formulas matemáticas en nuestra aplicación. Esta biblioteca nos pareció
adecuada debido a que permite la visualización de fórmulas matemáticas en navega-
dores web mediante LaTeX, y además, nuestro tutor nos la recomendó. Sin embargo,
nos encontramos con un obstáculo al tener que renderizar no solo fórmulas mate-
máticas, sino también Markdown. Esto implicó la necesidad de distinguir dentro del
propio texto en Markdown qué partes correspondían a fórmulas matemáticas.
Después de investigar y probar diversas bibliotecas para nuestro proyecto, nos
encontramos con tres que resultaron ser la solución: react-markdown, remark-math
y rehype-katex.
react-markdown es una biblioteca de React que nos permitió renderizar
texto en formato Markdown en nuestra aplicación web. Con esta biblioteca,
pudimos mostrar el contenido enriquecido con textos en negritas, cursivas,
listas, enlaces, imágenes, entre otros elementos.
remark-math, por su parte, es una biblioteca de procesamiento de Markdown
que nos permitió renderizar fórmulas matemáticas dentro del texto escrito en
formato Markdown.
rehype-katex es una biblioteca que nos permitió procesar el HTML generado
por remark-math y renderizar las fórmulas matemáticas de una manera más
legible y estilizada. En otras palabras, rehype-katex mejoró la apariencia
visual de las fórmulas matemáticas en nuestra aplicación web.
43
44 Capítulo 5. Introducción de Markdown y fórmulas
En resumen, react-markdown nos permitió mostrar texto en formato Mark-
down, remark-math nos permitió renderizar fórmulas matemáticas dentro del texto
y rehype-katex mejoró la apariencia visual de las fórmulas matemáticas. Juntas,
estas bibliotecas nos permitieron mostrar de manera clara y atractiva el contenido
matemático y escrito en nuestra aplicación web.
5.2. Gestión de imágenes
A pesar del uso de las bibliotecas mencionadas en la sección anterior, todavía
quedaba un pequeño detalle por implementar, y era el hecho de añadir imágenes
dentro de los textos Markdown de la aplicación con el fin de mejorar los textos
enriquecidos de la misma. A priori, nos pareció algo sencillo de implementar, pero
al final, resultó en la parte más complicada de este objetivo.
5.2.1. Subida de imágenes
Primero de todo, empezamos con la implementación de la subida de imágenes
en el front-end de la aplicación. Dado que el formato Markdown se aplica solo a los
enunciados y/o a las opciones de las preguntas tipo test, nos centramos en la lógica
de la subida de preguntas.
Para conseguir este objetivo, simplemente incluimos un nuevo selector de archivos
en el que los profesores deberían añadir las imágenes que deseen que se almacenen
con las preguntas.
Posteriormente, en el momento que el profesor pulse el botón de Subir preguntas,
el contenido de ambos formularios pasarán a tratarse en el back-end.
En la figura 5.1 podemos ver el resultado de estos cambios en la vista principal
de la subida de preguntas.
5.2.2. Tratamiento de imágenes en Django
Con las imágenes ya subidas al servidor, la información de las preguntas e imá-
genes llega al back-end de la aplicación donde esta información debe ser validada y
procesada. Por consiguiente, teníamos que encontrar una manera en la que el código
pudiese validar si las imágenes procedentes del front-end eran las mismas que había
en los enunciados u opciones de las preguntas tipo test en el documento YAML.
Para conseguir este objetivo, diseñamos una expresión regular que a partir de
un texto Markdown, filtrase su contenido y devolviese los nombres de las imágenes
que se debían de guardar. Posteriormente, el código comprobaría si el profesor había
subido las imágenes detectadas anteriormente. En caso negativo, se le devolvería un
error al usuario especificándole las imágenes restantes por subir y en caso afirmativo,
tanto las preguntas como las imágenes se almacenarían en nuestro servidor.
5.2. Gestión de imágenes 45
Con respecto al almacenamiento de imágenes, hemos utilizado el sistema de
ficheros de nuestro servidor para guardar, en una carpeta llamada media, todas
las imágenes provinientes de la subida de preguntas. Para el acceso al control del
sistema de ficheros hemos hecho uso la clase de Django, FileSystemStorage. Esta
clase, una vez configurada, ofrece una serie de métodos que nos han sido útiles para
el almacenado de archivos. Entre ellos podemos encontrar:
save: Permite almacenar un archivo dentro de nuestro sistema de ficheros,
guardándolo preferiblemente con el nombre del fichero que se le pase por pa-
rámetro. En el caso que exista un archivo con el mismo nombre, el sistema de
ficheros modificará el archivo para almacenarlo con un nombre único.
En nuestro caso, esto nos puede pasar si varios profesores utilizan la misma
imagen en sus enunciados, por lo que esta funcionalidad nos ayudaría a dife-
renciar qué imágenes pertenecen a cada uno. Por esta razón, algunas veces los
profesores observarán que cuando colocan una imagen en sus enunciados, el
nombre de la misma ha sido sustituido. Esto se debe a que se ha reemplazado
por el nuevo nombre que el sistema de ficheros le dio al archivo.
path: Permite recuperar la ruta de almacenamiento de un archivo dado un
nombre. Gracias a esto podemos abrir y leer ficheros con la propia función
de Python, open que devuelve los datos de un fichero a través de una ruta.
Más adelante, esto nos ayudará a recuperar imágenes guardadas para después
mostrarlas en el front-end de la aplicación.
delete: Esta función posibilita eliminar un archivo del sistema de ficheros
dado un nombre pasado por parámetro.
5.2.3. Recuperación de imágenes
Finalmente, nos falta abordar el tema de la recuperación de las imágenes dentro
de la aplicación. En la sección anterior, ya hemos adelantado que con el método
path, podemos encontrar las rutas de las imágenes dentro de nuestro sistema de
ficheros y cómo con el método open podemos recuperar toda la información de la
misma.
Como ya comentamos en la sección de mejoras (ver sección 3.5.2.5), tenemos que
recordar que para devolver objetos al front-end utilizamos los serializadores que
transforman los objetos de la base de datos a formatos sencillos como JSON para
facilitar la comunicación entre ambos lados. Debido a esto, tuvimos que hacer un
cambio en los serializadores de las preguntas y opciones tipo test para que pudieran
devolver las imágenes correctamente.
Primero, identificamos todas las imágenes que había dentro de los textos con la
misma expresión regular que utilizamos para la subida de preguntas y después, co-
dificamos las imágenes en base64 para poder recuperar las imágenes correctamente.
Como podemos ver en la figura 5.2, este sería el nuevo código que aplicaríamos a
nuestros serializadores.
46 Capítulo 5. Introducción de Markdown y fórmulas
Como podemos observar en esa misma figura, encontramos un pequeño incon-
veniente con las imágenes de tipo svg al pasar el contenido de la imagen a base64.
El atributo src de la etiqueta img acepta los MIME types que estan recogidos en el
registro de IANA. En nuestro caso necesitábamos svg+xml y por eso tuvimos que
añadir esa parte a la extensión del fichero.
5.2.4. Consecuencias
Tras la implementación de imágenes en los cuestionarios, nos encontramos con
un importante problema: el espacio ocupado por las imágenes de los cuestionarios
descargados en localStorage.
Desde el inicio del proyecto, nos percatamos de que guardar los cuestionarios
en localStorage no era una solución adecuada, debido a que los cuestionarios son
objetos que se convierten en cadenas de texto para ser almacenados, y posteriormente
se convierten de nuevo en objetos para su uso. Después de buscar alternativas,
encontramos IndexedDB, una API de bajo nivel del lado del cliente que permite
almacenar grandes cantidades de datos.
En un principio, descartamos esta opción porque nos centramos en otras funcio-
nalidades, pero más adelante, cuando surgió el problema mencionado, retomamos
esta opción y descubrimos una biblioteca llamada localForage1 que simplificó mucho
el código ya que funcionaba de manera similar a localStorage, pero con promesas.
Finalmente, adaptamos nuestro código para que funcionara con promesas y pu-
dimos prescindir de la transformación del objeto a texto. Con esto, logramos im-
plementar la funcionalidad requerida de manera eficiente y sin los problemas del
espacio generados por las limitaciones del localStorage.
5.3. Resultados
Por último, queremos enseñar como se refleja todo lo explicado en este capítulo
dentro de nuestra aplicación.
5.3.1. Subida de preguntas con Markdown
Empezaremos enseñando como, ahora, los profesores son capaces de incluir Mark-
down en sus enunciados e incluso en las opciones de las preguntas tipo test, sin olvidar
que también pueden escribir fórmulas matemáticas (ver figura 5.3). Además cabe re-
calcar la posibilidad de incluir imágenes dentro de las preguntas, como podemos ver
en el ejemplo de la figura 5.4.
1https://github.com/localForage/localForage
https://www.iana.org/assignments/media-types/media-types.xhtml#image
https://github.com/localForage/localForage
5.3. Resultados 47
5.3.2. Visualización de preguntas con Markdown
Los profesores pueden publicar cuestionarios que contengan preguntas con texto
Markdown, que en el momento que los estudiantes realicen podrán ver renderiza-
das correctamente. Las preguntas pueden contener texto Markdown con fórmulas
matemáticas (Figura 5.5) e incluso imágenes (Figura 5.6). Asimismo, ya que hemos
tratado también de hacer nuestra aplicación lo más adaptable a distintos dispositi-
vos, las imágenes también se auto-escalan para que se puedan apreciar bien en un
dispositivo móvil (ver figura 5.7).
48 Capítulo 5. Introducción de Markdown y fórmulas
Figura 5.1: Nueva vista para subir preguntas
1 def get_opcion(self,obj):
2 fs = FileSystemStorage()
3 opcion = obj.opcion
4 imagenes_devolver = re.findall(
5 r"!\[(.*?)\]\(([\w\/\-\:\._]+?)\)", obj.opcion)
6 for img in imagenes_devolver:
7 name = str(img[1])
8 format = name.split(".")[1]
9 if format == "svg":
10 format +="+xml"
11 path = fs.path(name)
12 with open(path, "rb") as image_file:
13 encoded_string = base64.b64encode(image_file.read())
14 .decode(’utf-8’)
15 imagen_base64 = ’data:image/ %s;base64, %s’
16 % (format, encoded_string)
17 opcion = opcion.replace(name, imagen_base64)
18 return opcion
19
Figura 5.2: Cambios en serializadores para recuperación de imágenes
5.3. Resultados 49
1 - tipo: "test"
2 pregunta: "La formula de la energia es"
3 titulo: "Titulo Markdown Test 2"
4 opciones:
5 - op: "$E = mc^2$"
6 fijar: False
7 - op: "$E = mc^3$"
8 fijar: False
9 - op: "$E = mc * 2$"
10 fijar: False
11 - op: "Ninguna de las anteriores"
12 fijar: True
13 op_correcta: 0 #id de opcion correcta
14
Figura 5.3: Pregunta con texto Markdown y fórmulas matemáticas
1 preguntas:
2 - tipo: "test"
3 pregunta: "El simbolo del framework de React es:"
4 titulo: "Titulo Markdown SVG"
5 opciones:
6 - op: "No se"
7 fijar: False
8 - op: ""
9 fijar: False
10 - op: ""
11 fijar: False
12 - op: "Ninguna de las anteriores"
13 fijar: True
14 op_correcta: 1 #id de opcion correcta
15
16
Figura 5.4: Pregunta con imágenes
50 Capítulo 5. Introducción de Markdown y fórmulas
Figura 5.5: Visualización de preguntas con fórmulas
5.3. Resultados 51
Figura 5.6: Visualización de pregunta con imágenes
52 Capítulo 5. Introducción de Markdown y fórmulas
Figura 5.7: Visualización de pregunta con imágenes en dispositivo móvil
Capı́tulo 6
Integración con Moodle
Uno de los principales objetivos del Trabajo de Fin de Grado era facilitar la
migración de preguntas y calificaciones entre Moodle y Qwizer. Nos propusimos
alcanzar los siguientes objetivos:
Importar preguntas desde Qwizer generadas a partir de un banco de
preguntas de Moodle. Esto permitiría aprovechar el contenido existente en
Moodle y utilizarlo en Qwizer.
Exportar las calificaciones desde Qwizer para que puedan ser importadas
en Moodle. De esta manera, se podría mantener un seguimiento coherente de
las calificaciones de los alumnos, utilizando Qwizer para evaluar su desempeño
y luego reflejar esas notas en Moodle.
Estos objetivos estaban orientados a facilitar la interoperabilidad entre las pla-
taformas y brindar a los profesores la capacidad de aprovechar el contenido y las
calificaciones existentes en Moodle en el entorno de Qwizer, y viceversa.
6.1. Migración de las preguntas de Moodle
En esta sección, abordaremos el proceso de migración de preguntas desde Moodle
a nuestro sistema. Para llevar a cabo esta migración, utilizamos el formato de Moodle
XML, que nos brinda la capacidad de importar y exportar preguntas en Moodle.
6.1.1. Formato Moodle XML
El formato de Moodle XML1 permite importar y exportar preguntas desde Mood-
le. En nuestro caso solo necesitábamos saber como estaban definidas las preguntas
de respuesta corta (Figura 6.1) y las preguntas de opción múltiple (Figura 6.2), para
insertarlas en nuestra base de datos posteriormente.
Teniendo conocimiento de la estructura del fichero, extrajimos selectivamente los
datos necesarios para nuestra aplicación haciendo uso de la librería ElementTree
como se detalla en la sección 6.1.2.
1https://docs.moodle.org/all/es/Formato_Moodle_XML
53
https://docs.moodle.org/all/es/Formato_Moodle_XML
54 Capítulo 6. Integración con Moodle
1
2
3 La respuesta correcta
4 Correcto
5
6
7
Figura 6.1: Pregunta corta
1
2
3 Title
4
5
6 Choose one:
]]>
7
8
9 option]]
10
11
12 option]]>
13
14
15 option]]>
16
17
18 option]]>
19
20
21
Figura 6.2: Pregunta de opción múltiple
6.2. Exportar notas 55
6.1.2. ElementTree
Para procesar el archivo XML de Moodle, empleamos la biblioteca ElementTree.
Esta herramienta nos brinda la capacidad de recorrer el XML de manera jerárqui-
ca, como si estuviéramos navegando por un árbol, y nos permite buscar elementos
específicos utilizando expresiones XPath.
Gracias a estas funcionalidades, logramos realizar una manipulación eficiente
y precisa de los datos que necesitábamos extraer del archivo de Moodle. Una vez
que procesamos el archivo XML, creamos las preguntas en la base de datos, pero
antes tuvimos que realizar otro tratamiento en el texto de las preguntas. Como se
puede observar en la figura 6.2, las preguntas están en formato HTML, por lo que
tuvimos que utilizar una expresión regular adicional para eliminar todas las etiquetas
innecesarias.
Además, es importante mencionar que este proceso de manipulación de datos se
lleva a cabo de manera similar al caso del YAML, con la diferencia de que en este
caso los datos necesarios para las preguntas se extraen del archivo XML en lugar
del archivo YAML.
6.2. Exportar notas
La exportación de calificaciones de los estudiantes involucra un proceso que cons-
ta de varias etapas. Para comprender mejor este proceso, es necesario establecer
el contexto en el que se utiliza Moodle. Moodle ofrece la capacidad de importar
calificaciones en formato CSV (valores separados por comas). Esto permite a
los profesores transferir y registrar las calificaciones de los estudiantes de manera
eficiente.
Para esto Moodle puede generar una tabla que contiene un listado de es-
tudiantes, donde cada estudiante tiene su información personal como correo elec-
trónico, DNI, entre otros datos. Una de las columnas de esta tabla corresponde a
las calificaciones, y es en esta columna donde el profesor debe completar las notas
correspondientes a cada estudiante.
Una vez que el profesor ha completado las calificaciones en la columna corres-
pondiente de la tabla CSV, el siguiente paso es cargar el archivo CSV con las califi-
caciones en Moodle. Al hacer esto, Moodle incorpora la información de calificaciones
proporcionada en el archivo CSV a su base de datos.
Para poder rellenar adecuadamente las notas de los estudiantes en nuestra apli-
cación de forma sencilla y automatizada se siguen los siguientes pasos:
En Qwizer se carga el listado de estudiantes en formato CSV de Moodle, como
se muestra en la Figura 6.3. Este archivo contiene la lista de estudiantes junto
con la columna de calificaciones que se desea completar, como se ilustra en el
ejemplo de la Figura 6.4.
56 Capítulo 6. Integración con Moodle
Figura 6.3: Subida de la lista de estudiantes
En Qwizer, se selecciona la columna correspondiente a las notas a rellenar y
se indica la asignatura a la que pertenecen, como se muestra en la Figura 6.5.
Esto permite a Qwizer identificar correctamente la columna de calificaciones
en el archivo CSV, ya que puede haber varias.
A continuación, se elige el cuestionario pertinente en Qwizer, que se utilizará
para asignar las calificaciones a los estudiantes correspondientes, como se indi-
ca en la Figura 6.6. Cabe aclarar que para identificar a que alumno pertenece
cada nota usamos la columna de Dirección de correo, que es la que se usará
para identificar unívocamente a cada alumno en nuestra aplicación.
Por último, se realiza la descarga del archivo final, que incluye la columna
de calificaciones debidamente rellenada. Este archivo final puede ser cargado
nuevamente en Moodle para que las calificaciones se incorporen en la base de
datos de Moodle.
De esta manera, nuestra aplicación Qwizer facilita el proceso de importación
de notas, permitiendo a los profesores completar las calificaciones en un listado de
alumnos exportado desde Moodle para su posterior carga en la misma plataforma.
6.2. Exportar notas 57
Nombre Apellidos DNI Direccion de correo Item de calificacion
root root 1234567Q root@root.com
x x 2222222Q x@x.com
Figura 6.4: Lista de estudiantes generada por Moodle
Figura 6.5: Selección de la columna y la asignatura
Figura 6.6: Selección del cuestionario
Capı́tulo 7
Creación de contenedores Docker
El despliegue de una aplicación es una etapa fundamental en cualquier proyecto
de software, ya que permite poner en funcionamiento y hacer accesible la aplicación
a los usuarios. Es durante esta fase donde se lleva a cabo la configuración de los
recursos necesarios para ejecutar la aplicación de manera eficiente y segura.
Al revisar el proyecto del año pasado, notamos que la parte de despliegue no
había sido abordada. Conscientes de su importancia, decidimos dedicar tiempo y
esfuerzo este año para implementar adecuadamente esta etapa.
Optamos por utilizar Docker, una tecnología de contenedores, para facilitar el
proceso de despliegue. Docker nos permite crear un entorno aislado y reproducible
para nuestra aplicación, junto con todas las dependencias necesarias. Esto simplifica
la configuración y asegura la consistencia del entorno de ejecución en diferentes
plataformas.
7.1. Despliegue con Docker Compose
Hicimos uso de Docker Compose, una herramienta que nos permitió definir y ges-
tionar múltiples contenedores de Docker como un conjunto de servicios interconec-
tados. Con Docker Compose, pudimos configurar fácilmente todos los componentes
necesarios para el despliegue de nuestra aplicación en un solo archivo YAML.
En este archivo de configuración, especificamos los servicios que componen nues-
tra aplicación, junto con sus dependencias y configuraciones correspondientes. Ade-
más, pudimos definir redes virtuales para facilitar la comunicación entre los diferen-
tes servicios. El fichero de configuración esta detallado en la Figura 7.1.
Además de los servicios previamente mencionados, también incorporamos una
base de datos PostgreSQL y una versión de desarrollo tanto para el backend como
para el frontend. Si bien estos servicios no están diseñados para el despliegue en
producción, resultaron útiles para el proceso de desarrollo, permitiéndonos trabajar
de manera eficiente en la implementación de funcionalidades y en la detección de
errores.
59
60 Capítulo 7. Creación de contenedores Docker
7.2. Servidor web con Nginx
Uno de los componentes clave en nuestro despliegue fue Nginx, un servidor web de
alto rendimiento. Utilizamos Nginx como proxy inverso para enrutar las solicitudes
entrantes a los servicios apropiados, como el backend de Django y el frontend de
React. La configuración de este servidor está detallada en la Figura 7.2.
7.3. Seguridad con HTTPS
En nuestro despliegue, nos preocupamos por garantizar la seguridad de la comu-
nicación entre los usuarios y nuestra aplicación. Por ello, implementamos HTTPS
(Hypertext Transfer Protocol Secure) para cifrar la conexión y proteger la integridad
de los datos transmitidos.
Para habilitar HTTPS, obtuvimos un certificado SSL (Secure Sockets Layer) auto
firmado para realizar pruebas, que posteriormente se reemplazará con un certificado
firmado por una autoridad de certificación. Configuramos Nginx para que utilizara
este certificado y habilitara la comunicación segura a través del protocolo HTTPS.
7.3. Seguridad con HTTPS 61
1 version: ’3.9’
2 services:
3 nginx:
4 image: nginx:latest
5 restart: unless-stopped
6 ports:
7 - 80:80
8 - 443:443
9 depends_on:
10 - front
11 volumes:
12 - ./nginx/nginx.conf:/etc/nginx/nginx.conf
13 - ./nginx/ssl/site.crt:/etc/ssl/certs/site.crt
14 - ./nginx/ssl/site.key:/etc/ssl/private/site.key
15 - back:/static
16 - front:/dist
17
18 back:
19 build:
20 context: ../
21 dockerfile: ./.docker/back/Dockerfile
22 command: sh -c "./start.sh"
23 depends_on:
24 db:
25 condition: service_healthy
26 environment:
27 - DEBUG=False
28 - DATABASE_HOST=db
29 volumes:
30 - back:/app/staticfiles
31
32 front:
33 build:
34 context: ../
35 dockerfile: ./.docker/front/Dockerfile
36 environment:
37 - REACT_APP_API_URL=/api
38 - REACT_PORT=3000
39 - REACT_HOST=0.0.0.0
40 volumes:
41 - front:/app/dist
42
43 volumes:
44 pg_data:
45 back:
46 front:
47
Figura 7.1: Fichero de configuración docker-compose.yml
62 Capítulo 7. Creación de contenedores Docker
1 http{
2 access_log /var/log/nginx/qwizer.access.log;
3 error_log /var/log/nginx/qwizer.error.log;
4 server_tokens off;
5
6 server {
7 include /etc/nginx/mime.types;
8 server_name localhost;
9
10 listen 80;
11 listen 443 ssl http2;
12
13 ssl_certificate /etc/ssl/certs/site.crt;
14 ssl_certificate_key /etc/ssl/private/site.key;
15
16 location / {
17 root /dist/;
18 try_files $uri /index.html;
19 }
20
21 location /static {
22 root /;
23 #autoindex on;
24 }
25
26 location /api {
27 proxy_pass http://back:8000/api;
28 }
29
30 location /admin {
31 proxy_pass http://back:8000/admin;
32 }
33
34 location /swagger {
35 proxy_pass http://back:8000/swagger;
36 }
37 }
38 }
39
Figura 7.2: Configuración de Nginx
Capı́tulo 8
Conclusiones y trabajo futuro
En esta sección, expondremos nuestras conclusiones una vez que ha finalizado el
desarrollo del proyecto. Abordaremos temas como pueden ser: el nivel de cumpli-
miento de los objetivos principales, las dificultades encontradas durante el desarrollo
del proyecto y las posibles mejoras que podrían implementarse en el futuro.
8.1. Objetivos alcanzados
Durante el transcurso del proyecto, se lograron alcanzar todos los objetivos plan-
teados inicialmente (sección 1.2):
Aleatorización de cuestionarios: Se implementó con éxito la funcionalidad
de aleatorización tanto a nivel de preguntas como de opciones dentro de los
cuestionarios. Además, se amplió la variedad de preguntas con la adición de
las preguntas de selección aleatoria (una pregunta al azar de una serie de
preguntas elegidas). Ahora, los profesores pueden ofrecer cuestionarios únicos
a sus estudiantes lo que ayuda a evitar la copia entre ellos.
Adaptación de aplicación a dispositivos móviles: se consiguió ofrecer un
diseño adaptable a nuestra aplicación, en la que se trabajó arduamente para
poder adecuar su interfaz a todas las pantallas y mejorar la experiencia de
usuario para que fuese cómoda e intuitiva.
Inclusión de Markdown y fórmulas matemáticas: Se logró la imple-
mentación exitosa de Markdown, lo que permite a los usuarios enriquecer el
contenido con formatos como negrita, cursiva, imágenes, entre otros. Además,
se incorporó la capacidad de redactar enunciados y soluciones con fórmulas
matemáticas, lo que amplía las posibilidades para la creación de preguntas
más complejas y detalladas.
Integración con Moodle: Se logró una integración exitosa de Qwizer con
los formatos utilizados en la plataforma Moodle. Ahora los usuarios pueden
importar preguntas del banco de preguntas de Moodle, lo que facilita la re-
utilización y la migración del contenido existente. Asimismo, se implementó la
funcionalidad de exportar las notas de los cuestionarios realizados en Qwizer a
Moodle, brindando una gestión más sencilla y centralizada de las calificaciones.
63
64 Capítulo 8. Conclusiones y trabajo futuro
Por otra parte, conseguimos completar otros objetivos, los cuales fueron apa-
reciendo a medida que el proyecto avanzaba. Entre ellos podemos encontrar los
siguientes:
Cambios en el diseño principal: Inicialmente, no teníamos pensado cam-
biar el diseño de las vistas de nuestros compañeros, pero, ligado al objetivo del
diseño adaptable, nos vimos obligados a realizar algunos cambios sobre casi to-
das las vistas iniciales para mejorar la experiencia general de nuestros usuarios.
Incluso llegamos a introducir nuevas funcionalidades de las que previamente
Qwizer no disponía (sección 3.5.3).
Documentación de la API: Como ya explicamos en la parte de mejoras (ver
sección 3.5.2), una buena documentación de una API nos parece algo esencial
ya que sin ella se dificulta enormemente entender cada llamada y el porqué
de la misma. Al principio, nosotros nos encontramos en esta situación y por
el bien de unos posibles futuros compañeros o simplemente alguien que quiera
entender nuestro trabajo, hemos desarrollado una documentación extensa e
interactiva sobre cada llamada de la API, facilitando su compresión a ojos de
cualquiera.
Testing : Al igual que una buena documentación, cualquier desarrollo de soft-
ware implica una etapa de testeo, donde se hagan numerosas pruebas que com-
prueben si el código desarrollado funciona como lo esperado. Por este motivo,
desarrollamos un apartado de testing para la parte back-end de la aplicación,
en la que se probaba la lógica de las llamadas a la API.
Despliegue de aplicación: Finalmente, si queríamos probar nuestra apli-
cación en un entorno de producción teníamos que explorar la posibilidad de
prepararla para su despliegue, por lo que configuramos y adaptamos nuestro
entorno de desarrollo para desplegar la aplicación en cualquier momento. De
hecho en el capítulo 7 hemos desarrollado más en detalle la solución a este
objetivo.
Además también conseguimos completar parte de los objetivos que tenían nues-
tros compañeros planificados para trabajo a futuro. Entre ellos conseguimos imple-
mentar:
1. La realización de cuestionarios de manera secuencial, donde ahora los profeso-
res pueden elegir si un cuestionario se debe hacer seguidamente, es decir, sin
poder volver a la pregunta anterior una vez se pase a la siguiente.
2. Resolución de más de un cuestionario a la vez de manera offline. Anteriormen-
te, solo se podía resolver un cuestionario si el estudiante perdía la conexión. En
cambio, ahora gracias a la nueva gestión del almacenamiento de cuestionarios,
se puede realizar más de un cuestionario de manera offline.
3. Mejora de interfaz de la creación de cuestionarios y, como ya hemos comentado
en los objetivos principales, adaptación de interfaz a dispositivos móviles.
8.2. Dificultades encontradas 65
8.2. Dificultades encontradas
A lo largo del desarrollo del proyecto, se encontraron algunas dificultades que
afectaron el proceso de implementación de los objetivos:
Código heredado: Uno de los mayores desafíos fue lidiar con código heredado
del proyecto anterior. El uso de código preexistente presentó dificultades para
comprender su funcionamiento y adaptarlo a los nuevos requisitos, desembo-
cando en numerosas semanas de refactorización y mejoras al código inicial.
Por esta razón, el periodo de adaptación y familiarización con el proyecto se
extendió más de lo normal, impidiendo cumplir algunos objetivos secundarios
planteados.
Desconocimiento de las tecnologías: Cabe destacar que ambos integran-
tes del equipo partíamos sin una base de las tecnologías utilizadas dentro del
proyecto, por lo que la utilización de estas nuevas tecnologías y herramien-
tas durante el desarrollo del proyecto implicó un período de aprendizaje y
familiarización. Tanto Django como React eran frameworks que presentaban
mecanismos complicados de entender y asimilar al principio. Por ello podemos
agregar los principales problemas encontrados durante el desarrollo con ambos
marcos de trabajo:
• Django: Una de las librerías que se ha utilizado más a fondo este año es
la llamada djangorestframework. El uso de la misma está explicado en la
sección de 3.5. La ampliación en el uso de esta librería acarreó numerosos
problemas dado que el uso de serializadores, viewsets y otros mecanismos
de la misma fueron algo complicados de implementar en el código hereda-
do recibido. Esto, sumado a la poca compresión que teníamos de Django
en las primeras etapas de desarrollo nos provocaron grandes complicacio-
nes al inicio del proyecto, que saldamos gracias a la documentación oficial
tanto de Django como de la librería.
• React: Aparte del desconocimiento general de React, el hecho de afrontar
componentes con tanta lógica resultó algo abrumador inicialmente. Tras
varias semanas de desarrollo, este sentimiento de presión se fue reduciendo
e incluso acabó en numerosas mejoras en cada uno de los componentes.
Tiempo: El factor temporal fue otro desafío significativo. Gran parte del de-
sarrollo fue adecuar y mejorar el código anterior, y eso dio lugar a un menor
tiempo para desarrollar las nuevas funcionalidades. Aunque se cumplieran los
objetivos iniciales marcados para el trabajo, una etapa de comprobación y
testeo en un entorno real hubiera ayudado al proyecto a llegar a un nivel de
refinamiento más elevado. Todo esto sin contar la poca disponibilidad en al-
gunos momentos del proyecto debido a la presión del curso y en momentos
puntuales, de los exámenes.
66 Capítulo 8. Conclusiones y trabajo futuro
8.3. Trabajo futuro
Si bien los objetivos planteados se han logrado con éxito, consideramos que el
proyecto aún tiene margen de mejora. Para ello, proponemos las siguientes modifi-
caciones:
Accesibilidad: Aunque hemos adecuado la mayor parte de la aplicación para
todos los dispositivos, creemos que todavía se podría ampliar la accesibilidad de
nuestra aplicación permitiendo que todo tipo de usuarios, independientemente
de sus capacidades físicas o cognitivas, pueda hacer uso de Qwizer. El uso
de técnicas de accesibilidad como variación de colores, cambios en los temas,
descripciones con textos de ayuda, uso de tipografías sencillas y botones y
paneles grandes y fáciles de localizar ayudarían a ajustar Qwizer a un ámbito
más inclusivo.
Testing en el front-end : Como ya explicamos anteriormente, uno de los
objetivos secundarios cumplidos fue el hecho de documentar y testear el back-
end de nuestra aplicación. Esto ha concluido en numerosos beneficios a lo largo
del desarrollo de la aplicación. En cambio, uno de los trabajos que nos hubiera
gustado realizar y por falta de tiempo no se ha podido completar es el hecho
de realización de pruebas de la interfaz y de los componentes principales del
front-end de nuestra aplicación. En numerosas ocasiones, hemos tenido que
comprobar el correcto funcionamiento de la lógica de los componentes, en los
que a veces provocaba llamadas a la API y consecuentemente, cambios en el
estado de la base de datos. Este tedioso proceso nos hizo plantearnos realizar
una serie de pruebas a las vistas más esenciales de la interfaz, permitiéndonos
probar esta lógica y realizar las pruebas bajo una base de datos preparada
para los tests. Además, este tipo de pruebas encamina el código por un camino
más sostenible y estable, por los que futuros desarrollos se verían agradecidos
por las mismas.
Interfaz propia para móviles: A lo largo del desarrollo del proyecto, ha ha-
bido numerosas ocasiones en los que nos hemos replanteado hacer una interfaz
propia para los dispositivos móviles, porque aunque hayamos dedicado tiempo
a conseguir un diseño adaptable de nuestra aplicación, siempre quedan algunos
componentes o algunos fragmentos de las vistas que quedan ligeramente fuera
del estilo de una aplicación móvil. Para esto estudiamos Ionic, un framework
centrado en el desarrollo de interfaces de usuarios en dispositivos móviles, el
cual, dado que su sintaxis es muy parecida a nuestro principal framework de
estilado, Bootstrap, nos permitiría haber diseñado una interfaz única y exclu-
siva para los dispositivos móviles. Al final, terminamos abandonando la idea
debido a que era un objetivo muy complicado de completar en el poco tiempo
que nos quedaba, pero en un futuro, esta idea podría darle a Qwizer un valor
fundamental para su implementación dentro de las asignaturas de la facultad.
Más tipo de preguntas: Qwizer es una aplicación basada en la realización
de cuestionarios y uno de los apartados en los que todavía cojea es su pequeño
8.3. Trabajo futuro 67
catalogo de preguntas. Aunque este año hemos dedicado parte del tiempo a
la creación de un nuevo tipo de pregunta (las preguntas de selección aleato-
ria) pensamos que podrían añadirse muchos más tipos de preguntas, como por
ejemplo, preguntas de relacionar, preguntas de rellenar huecos, etc... Además,
gracias a la nueva estructura de la base de datos, añadir estos nuevos tipos de
pregunta sería algo sencillo de realizar. Por tal motivo, pensamos que esta nue-
va incorporación de preguntas podría darle otra imagen a Qwizer y impulsarla
a un entorno más llamativo.
Importar más tipos de preguntas: En relación a la importación de pre-
guntas, también tenemos numerosos frentes abiertos que podríamos abarcar,
como puede ser el hecho de importar otro tipo de preguntas de Moodle, ya
que ahora mismo, solo se pueden importar preguntas de respuesta corta y pre-
guntas de multiopción. Al mismo tiempo, nos hemos replanteado la idea de
importar preguntas con otros formatos. Actualmente, Qwizer solo permite im-
portar preguntas con formato XML-Moodle centrando toda esta funcionalidad
en un solo formato. Por lo que, en resumen, importar otros tipos de preguntas
de Moodle y aumentar la cantidad de formatos para importar preguntas, nos
parece un objetivo que se podría plantear para futuro.
Usar API de moodle. Mejorar la conectividad con Moodle: Ligado a la
exportación de las calificaciones de Qwizer a Moodle, creemos que este objetivo
se puede mejorar haciendo más cómoda la interoperabilidad entre Qwizer y
Moodle. Para ello, pensamos en que sería mejor utilizar la propia API de
Moodle para ampliar las posibilidades de integración con nuestra aplicación.
Capı́tulo 9
Contribuciones personales
En este capítulo vamos a hablar de nuestras contribuciones por separado al pro-
yecto. Ambos hemos trabajado de manera síncrona, tocando aspectos tanto del
front-end como del back-end, por lo que en nuestras explicaciones algunas veces se
combinarán tareas en las que ambos hemos trabajado. Aún así, cabe remarcar que
ambos hemos trabajado juntos en los siguientes apartados:
Refactorización del código
Actualización de librerías
Nuevo diseño de la base de datos: Los principales cambios en la estructura
general de la base de datos fueron desarrollados entre los dos.
Corrección de errores: Ambos hemos trabajado en corregir los errores que
iban surgiendo tanto en el front-end como en el back-end.
69
70 Capítulo 9. Contribuciones personales
Vicentiu Tiberius Roman
Durante el desarrollo del Trabajo de Fin de Grado he realizado las siguientes
aportaciones:
Actualizaciones: Me encargué de actualizar React, Django y todas sus de-
pendencias a las últimas versiones estables. Además elimine múltiples depen-
dencias innecesarias que no se usaban en el proyecto.
Preparación del entorno de desarrollo: Dado que íbamos a estar numero-
sos meses desarrollando este proyecto, queríamos tener un entorno de desarrollo
preparado y cómodo para trabajar. Por esta razón, preparé nuestro entorno y
editor de trabajo añadiendo lo siguiente:
• Linters para mejorar la calidad del código y mantener la consistencia del
estilo en todo el proyecto.
• Scripts para el iniciar, detener y depurar React y Django.
• Extensiones y configuraciones para facilitar el desarrollo.
Gestión segura de secretos mediante archivos .env: Identifiqué un im-
portante problema en el proyecto relacionado con la exposición de contraseñas
en el repositorio de GitHub. Para abordar esta preocupación, realicé la sepa-
ración de todos los secretos en archivos individuales, los cuales se leen pos-
teriormente para cargar su contenido. Además, proporcioné una plantilla de
estos archivos para que cada desarrollador conozca las variables necesarias, pe-
ro sin la necesidad de subirlas al repositorio. Esto garantiza una gestión segura
de los secretos en el proyecto además de permitir cambiar configuraciones del
proyecto sin tocar el código y únicamente cambiando las variables de entorno.
En la Figura 3.7 podemos ver un ejemplo de los ficheros de configuración que
gestioné.
Docker: Para facilitar el despliegue, utilicé Docker para el empaquetado y
la distribución de las aplicaciones React y Django junto con el servidor web
Nginx. Realice los ajustes necesarios para desplegar Django correctamente, ya
que no estaba preparado para esa tarea y me encargué de añadir certificados
SSL a la aplicación. Además de este proceso, también ayudé a José Luis a
configurar todo para que pudiera utilizarlo correctamente, ya que él desarrolla
en Windows y la configuración con Docker es diferente que en Linux.
Vite: Al ver la gran cantidad de advertencias que salían al iniciar la aplicación
de React, decidí probar Vite, una alternativa que resultó ser mucho más rápida
y además facilitó el desarrollo al refrescar las páginas sin eliminar el estado
anterior.
Refactorización de componentes: Dentro de la refactorización de los com-
ponentes de React, además de traducir algunos componentes de clases a fun-
ciones yo me encargué de arreglar y reescribir la lógica de los componentes
71
eliminando numerosos errores en su lógica, adaptando las variables al están-
dar más actual de JavaScript, ajustando los componentes a la sintaxis más
reciente de React y arreglando el manejo del estado de los componentes que
se habían hecho visibles a causa del cambio de versión de React, al no seguir
las recomendaciones sobre cómo tratar el estado en versiones anteriores.
Rutas en la aplicación: Previamente, el manejo de las rutas era errático. No
se utilizaba React Router, una de las soluciones más populares para integrar
las rutas en React y se realizaba de forma manual. Esto implicaba lo siguiente:
• No tener rutas reales ya que la URL de la barra de navegación no cam-
biaba en ningún momento.
• El punto de entrada a la aplicación (App.js) era de una extensión inasu-
mible al usar condiciones anidadas para saber en qué ruta se encuentra
en cada momento.
• Todo el estado de la aplicación estaba concentrado en App.js, es de-
cir, los componentes no eran independientes y tenían su estado en sus
antecesores.
Todo esto implicó un gran trabajo para reescribir casi todos los componen-
tes, separando su lógica y haciéndolos más legibles y escalables, además de
implementar de cero la gestión de los permisos para cada ruta.
Abstracción de API: Realicé una completa abstracción de la API en el
front-end de la aplicación. En lugar de tener las llamadas dispersas por varios
componentes, centralicé la comunicación entre el back-end y el front-end en
un solo archivo. Para lograr esto, primero configuré nuestro cliente axios para
incluir el token de usuario en cada llamada, en el archivo client.js. Luego,
estructuré todas las llamadas a la API en el archivo API.js. Esta implemen-
tación simplificó enormemente el desarrollo futuro, ya que cualquier cambio
en la API solo requería modificar un único archivo.
Creación de hooks personalizados: He creado también los hooks personali-
zados que explicamos en la sección de 3.5.1. De esta manera evité la repetición
del código y una mejor gestión de las llamadas a la API.
Cuestionarios: Me encargué de corregir todos los errores relacionados con la
realización y revisión de cuestionarios, además de implementar las siguientes
mejoras:
• Permitir la realización y descarga de múltiples cuestionarios de manera
simultánea.
• Agregar la posibilidad de eliminar cuestionarios descargados.
• Paso a IndexedDB para evitar la transformación de los cuestionarios a
cadenas de texto, además de evitar los problemas con el límite de alma-
cenamiento del localStorage.
72 Capítulo 9. Contribuciones personales
Interfaz de la aplicación: A lo largo del proyecto, me he encargado de
realizar múltiples cambios para adaptar la aplicación a dispositivos móviles,
además de crear los modales para la gestión de los errores en toda la aplicación.
Markdown: Con respecto al Markdown, yo me encargué de la investigación de
las librerías que nos podían ayudar a la implementación de esta funcionalidad
e hice un ejemplo de como se usarían y después fue José Luis el que se encargó
de implementarlo dentro de la aplicación.
Gestión de dependencias en Django: También me encargué de mejorar
la gestión de dependencias en Django, ya que anteriormente la única manera
de saber qué dependencias tenía el proyecto era ir añadiendo dependencias
según salían errores hasta que no faltara ninguna. Por este motivo me de-
cidí finalmente por usar Poetry lo que me permitió crear un único fichero
pyproject.toml donde están definidas todas las dependencias del proyecto y
sus configuraciones, permitiendo así instalar las mismas en un entorno aislado
para evitar conflictos con otras dependencias del equipo.
Viewsets, Managers y Serializadores: Al ir aprendiendo Django, me di
cuenta de que no se estaban usando todas sus capacidades, así que decidí re-
escribir las vistas a viewsets y crear varios serializadores para la aplicación,
además de crear managers para todos los modelos, permitiendo una refactori-
zación más sencilla en el futuro.
Documentación de la API: Una de las cosas de la que me encargué per-
sonalmente fue de cómo documentar la API. Desde el principio del proyecto,
estuve investigando cómo podía documentar la aplicación para facilitarnos el
entendimiento de la misma tanto a mí como a mi compañero. Para esta tarea,
encontré una librería denominada drf-spectacular, que por detrás utilizaba
Swagger para generar la documentación de la API. Me encargué de documentar
llamada a llamada, explicando tanto las entradas, salidas y operaciones espera-
das. Finalmente el resultado fue una documentación muy extensa y elaborada
que cualquier desarrollador que prosiga con el proyecto puede disfrutar.
Testing de API: En relación al testing de la API, asumí la responsabilidad
de investigar las mejores prácticas para realizar pruebas en nuestra aplicación,
añadiendo una opción para ver la cobertura del testing, para poder observar
qué porcentaje del código se había testado.
Aleatorización, back-end : En el tema de la aleatorización, mi rol fue so-
bre todo la preparación del back-end para la aleatorización de cuestionarios
y las opciones de las preguntas tipo test. Me centré en añadir a los modelos
los atributos necesarios para la aleatorización, preparar la lógica para devol-
ver y almacenar cuestionarios aleatorios y modificar la lógica de subida de
cuestionarios para almacenar los nuevos datos de los cuestionarios.
Preguntas de selección aleatoria: Respecto al nuevo tipo de preguntas, me
encargué sobre todo de ayudar a José Luis a entender toda la nueva lógica que
había realizado para la aleatorización de los cuestionarios y las opciones de las
73
preguntas tipo test que he explicado anteriormente. Una vez que terminó de
implementarlas le ayudé con algunos errores que habían quedado pendientes
y terminamos entre los dos esa funcionalidad.
Integración con Moodle: Mientras mi compañero trabajaba en el nuevo
tipo de preguntas, me dediqué a avanzar en nuestro siguiente objetivo, la in-
tegración con Moodle. Investigué sobre el formato XML-Moodle y sobre qué
librerías nos podrían ayudar para implementar estas funcionalidades. Durante
la investigación empecé a realizar una serie de pruebas con la librería Element-
Tree y conseguí el objetivo de importar preguntas de Moodle. Más adelante,
tras una reunión con nuestro tutor, nos explicó más en detalle que debíamos
permitir exportar calificaciones desde Qwizer a Moodle y decidí también im-
plementarlo, ya que había estado investigando en ese tema.
74 Capítulo 9. Contribuciones personales
José Luis Bartha de las Peñas
Durante el desarrollo del Trabajo de Fin de Grado he realizado las siguientes
aportaciones:
Refactorización de componentes: Como ya hemos comentado, el año pa-
sado nuestros compañeros implementaron todos sus componentes de React
mediante clases y este año por recomendación de los propios desarrolladores
de React y por su versión más actual reescribimos todos los componentes a
una implementación mediante funciones. En este caso, yo me encargué más de
refactorizar de clases a funciones, donde entre ellos cambié:
• LoginComponent
• TarjetaAsignatura
• UploadQuestions
• UploadFile
• IndexContainer
• QuestionNav
• AvailableOffline
• QrContainer
• BancoPreguntas
• CuestionarioPassword
• CrearCuestionario
• CuestionariosContainer
• RegisterContainer
• RevisionNotasContainer
Diseño adaptable de la aplicación: Conseguir un diseño adaptable fue
una tarea que nos dividimos entre Tiberius y yo. En mi caso, me encargué
más de modificar los componentes que no estuvieran adaptados a todos los
dispositivos, utilizando siempre nuestro framework de estilado, Bootstrap. Me
centré sobre todo en adaptar las siguientes secciones de la aplicación:
• Creación de cuestionarios
• Subida de preguntas y cuestionarios
• Banco de preguntas
• Realización y revisión de cuestionarios
• Componentes de representación asignatura y cuestionarios: Adap-
té los componentes donde se ve la información de las asignaturas y cues-
tionarios.
Ajuste rutas de la API: Una de las primeras cosas que quisimos cambiar
nada más comenzar el proyecto fue la nomenclatura general de las llamadas.
Normalmente, una buena práctica a la hora de crear las llamadas a la API,
es tener una nomenclatura explicativa en cada ruta para que se entienda la
funcionalidad de la misma. En cambio, el año pasado todas las rutas eran algo
complicadas de entender. Por esta razón, me encargué de darle a cada ruta un
nombre y una estructura que permitiese entenderla a cualquier persona.
75
Creación de nuevas funcionalidades y diseños: A lo largo del proyecto
hemos añadido nuevas funcionalidades como las que explicamos en la sección
3.5.3 e incluso hemos pensado en rediseñar algunas de las vistas ya existentes
dentro de la aplicación, ya que eran algo toscas de utilizar o simplemente que-
ríamos darle un toque personal y utilizar un poco de estilado propio. Por esto
mismo, me encargué personalmente de implementar estas nuevas funcionalida-
des y además de rediseñar componentes enteros como por ejemplo, la creación
y subida de cuestionarios.
Adaptación a nuevos modelos: En la etapa de refactorización, decidimos
cambiar el modelo entidad-relación heredado entre Tiberius y yo, producién-
dose cambios en los nombres, relaciones y creación de nuevas entidades. Esto
desencadenó que todos los modelos utilizados en el back-end de la aplicación
quedarán desactualizados, derivando en una refactorización a gran escala de
todos los modelos de la aplicación y sus respectivas referencias a lo largo del
código. En este caso yo me encargué de gran parte de esta refactorización con
ayuda de Tiberius también.
Testing de API: Respecto al testing de la aplicación, tras la investigación
y explicación de Tiberius, me dediqué a realizar las pruebas respectivas sobre
las rutas que tenían relación con las preguntas y asignaturas. Para conseguir
esto, generé las pruebas para todas las llamadas sobre estas entidades en los
ficheros de test-subject.py y test-question.py.
Aleatorización, front-end : En relación con la aleatorización, mi papel fue
el de implementar esta nueva funcionalidad en el front-end de la aplicación.
Mi trabajo se dividió en las siguientes partes:
• Incluir en la vista de creación de cuestionarios una manera para que los
profesores pudieran elegir si las preguntas de sus cuestionarios y/o las
opciones de una pregunta tipo test apareciesen en un orden aleatorio.
• Incluir la posibilidad para fijar preguntas a una posición especifica de un
cuestionario y fijar una opción a una posición especifica en la subida de
una pregunta tipo test.
• Ajustar los ficheros YAML de ejemplo para que todos utilicen la nueva
sintaxis, incluyendo los atributos de aleatorización y fijación.
Preguntas de selección aleatoria: Después de los cambios realizados en la
estructura de la base de datos para las preguntas de selección aleatoria, deci-
dí encargarme personalmente de implementarlo dentro de nuestra aplicación.
Tras comprender cómo funcionaba bien toda la lógica de la aleatorización que
había hecho Tiberius en el back-end, realicé primero la lógica para poder alma-
cenar este nuevo tipo de pregunta en la base de datos, cambiando los modelos
de Django y ajustando la lógica de creación de cuestionarios e intentos para
que soportará esta nueva pregunta. Finalmente, comprobé la correcta creación
de los cuestionarios con este nuevo tipo de pregunta creando un nuevo fichero
de ejemplo YAML. El fichero que creé de prueba se llama test2.yml y se ha
76 Capítulo 9. Contribuciones personales
mantenido en la carpeta de ejemplos para los usuarios que quieran probar la
aplicación.
Markdown: Con respecto al Markdown, yo me encargué de la implementación
de esta nueva funcionalidad en la aplicación. Dado que ya almacenábamos en
una cadena de caracteres los enunciados y las opciones de las preguntas tipo
test, la inclusión de fórmulas matemáticas y estilo enriquecido en los textos
fue algo trivial de hacer gracias a las bibliotecas que utilizamos. Sin embargo,
la subida de imágenes en texto Markdown, fue lo más complicado de hacer,
donde tuve que configurar la clase de Django, FileSystemStorage, que permitía
gestionar el sistema de ficheros del proyecto, cambiar la lógica de la subida de
preguntas para que soportará imágenes y además cambiar los serializadores de
Preguntas y OpcionesTest para poder devolver las imágenes almacenadas
correctamente en los enunciados y en las opciones de las preguntas. Además
de esto también generé un fichero de ejemplo con preguntas con Markdown,
tanto con fórmulas como con imágenes para los usuarios que quieran utilizar
la aplicación. El fichero se encuentra en la carpeta de ejemplos con el nombre
de questions2.yml.
Introduction
In this document we will talk about the development of the expansion and im-
provement of a progressive application to take online questionnaires: Qwizer. We
will begin by presenting what led us to follow this project, what objectives we have
had throughout the project and finally we will describe our working plan throughout
the academic year.
Background and motivation
Last year we found ourselves in the situation of choosing a project that will really
motivate us to develop our last project within the faculty. In addition, as students
of the Computer Science Faculty, we wanted a project that would really impact its
evolution and allow us to help improve its services.
Manuel told us about a possible improvement and extension of a progressive
web application based on the completion of tests without an internet connection:
Qwizer. This application was aimed at users like us, students who throughout our
studies within the faculty have had to complete numerous questionnaires, without
adding that halfway through our path into our degree, a global pandemic broke out
due to COVID-19 and this type of format for the evaluation of our subjects was
something routinary.
Initially, the proposal already caught our attention, but we also identified a series
of causes that led us to progress and continue this project. Among them we can
name the following:
New technologies: This application used technologies which neither of
us were familiar with. Both React and Django were two technologies that
we were unaware of, but we knew that they would be essential for our personal
and professional development. Besides that, the fact of being an application
that works without an Internet connection, allows us to get involved in the
world of progressive applications, a market that is glowing today, so it is
very interesting to develop and discover the possibilities of these applications.
Practical use in the future: Another important point that made us decide
on this project was the practical use that we saw for the application. Under
good development and the addition of a minimum of necessary functionalities,
we saw that this project could become useful. Throughout our studies, we
have worked in numerous projects with the sole purpose of being qualified
77
78 Capítulo 9. Contribuciones personales
and focused on a simple work delivery. Subsequently, they are forgotten or
abandoned due to lack of time or motivation to follow them. On the other
hand, with this project we saw that if we were able to meet the objectives we
could make a decent application that our colleagues could use in the
future.
Educational innovation project of the UCM (INNOVA projects):
Finally, in addition to the other reasons, Manuel informed us, as we have
already mentioned, that this project was destined to continue being developed
by some teachers of the faculty. In fact, they had proposed an innovation
project for the faculty, so that the application could be used in the laboratories
of some subjects to evaluate exams. This drew our attention and, bound to
what we were telling at the beginning, we saw that this project could finally
leave its mark on our faculty.
Goals
Once we already had the project proposal, we had to think about some objectives
to develop throughout the entire academic year. This task was somewhat easy
because our classmates from last year, and also our tutor, helped us identify what
new features could be added to the Qwizer app. Therefore, finally, among the advice
received, we came to propose and establish the following first objectives for the new
work:
Randomization of questionnaires: One of the main future objectives of
our colleagues last year was the fact of randomization within the ques-
tionnaires themselves. Offering the ability to randomize quiz questions is
essential in this type of application to avoid cheating among students. For
this reason, we came to the conclusion that this feature would offer an added
value to Qwizer.
For the implementation of this new functionality we had to think carefully
about how we would apply randomization within the application, since within
a questionnaire this can be applied in numerous ways. Finally, we identified
three main goals:
• Randomization at the question level. The questions in a quiz should
appear in a random order for each user who takes the quiz.
• Randomization at the option level. The options within the multiple
choice questions should be displayed in a random order for each user.
• Random selection questions. Within the questionnaires there will be one
or several questions that will be chosen for each user based on a subset
of questions from the question bank.
79
Application adaptation to mobile devices: Responsive design is a fun-
damental step in web applications. Giving users who use mobile devices the
possibility to see our application correctly is very important and more so in
the context of our application. For this reason we decided to try to adapt
our interface to mobile devices and try to make browsing through the
application as comfortable as possible.
Inclusion of Markdown and mathematical formulas: Markdown is a
type of markup language that allows adding rich text to the application
allowing the inclusion of words in bold, italics, images, etc... Our tutor iden-
tified the incorporation of Markdown as one of the main objectives of
this project and also adding the possibility of writing statements and
solutions with mathematical formulas, allowing the formulation of more
complex questions.
Integration with Moodle: Finally, the last objective we set was the integra-
tion with the formats of the Moodle platform. Moodle was one of the tools
that inspired the development of Qwizer, in fact, it is currently the platform
on which the Virtual Campus of our faculty is based on. Since it is still in use
to this day, the ability to integrate Qwizer with Moodle was one of our most
anticipated goals. To do this, we wanted to focus on the fact that questions
could be imported from the Moodle question bank and allow export-
ing the marks of the students who took questionnaires in Qwizer to
Moodle.
Work plan
Throughout this academic year, we have gone through numerous phases in the
development of our final degree project. The fact of extending a complete project
implied stages of study not only of the new technologies with which we were not
familiar, but also large code structures that make use of them. Due to all this, we
needed a work plan that would allow us to establish small objectives to complete
the tasks we had set.
Thanks to the meetings that we held with our tutor Manuel Montenegro, which
were held every two weeks, we organized ourselves to fulfill a series of tasks for each
meeting. This methodology helped us, at the beginning, to gradually understand
parts of the code and begin to meet the first goals, and later, in more advanced
stages of the project, to finalize the main objectives of the work. That being said,
the general distribution of the phases of our project were the following:
Refactor, testing, updating and deployment of the application (Septem-
ber 2022 - February 2023) One of the longest stages of development. At first,
we dedicated ourselves to understanding a large part of the code and later
refactor both the frontend, as discussed in chapter 3.5.1, and the backend as
80 Capítulo 9. Contribuciones personales
discussed in chapter 3.5.2. We also updated all libraries to the latest version
trying to avoid conflicts due to version control. Finally, we prepared the de-
velopment environment by adding linters, environment variables for secrets,
designing some tests for the back-end, using a package management tool and
using Docker to deploy the project correctly.
Questionnaire Randomization (March 2023): During this stage, the ques-
tionnaire randomization functionality was implemented. This process involved
making several changes to the application’s relational model and application
logic as detailed in depth in Chapter 4, which required significant time to
update the corresponding code.
Markdown, Random Questions and Moodle Integration (April 2023):
During this phase, several key aspects were addressed. The implementation of
the Markdown format for questions and the integration with Moodle were rela-
tively easy, with the exception of image management in the case of Markdown.
However, incorporating random questions proved to be a significant challenge,
as it impacted much of the application’s back-end code. These incorporations
are described in chapters 4, 5 y 6.
Final checks and memory (May 2023): During the last month of develop-
ment, we focused on making final adjustements to the app’s interface, polishing
the details for an improved user experience. We also dedicate time to writ-
ing the Final Degree Project document, documenting the entire development
process, the decisions made and the results obtained.
Conclusions and future work
In this section, we will present our conclusions once the development of the
project has finished. We will address issues such as: the level of compliance with the
main objectives, the difficulties encountered during the development of the project
and the possible improvements that could be implemented in the future.
Achieved goals
During the course of the project, all the objectives initially set (in section 1.2)
were achieved:
Questionnaire randomization: The randomization functionality was suc-
cessfully implemented both at the question and option level within the ques-
tionnaires. In addition, the variety of questions was expanded with the addi-
tion of random selection questions. Now, teachers can offer unique quizzes to
their students which prevents them from cheating.
Adaptation of the application to mobile devices: We managed to offer
an adaptable design to our application, in which we worked hard to be able
to adapt its interface to all screens and improve the user experience so that it
was comfortable and intuitive.
Inclusion of Markdown and mathematical formulas: The successful
implementation of Markdown was achieved, which allows users to enrich the
content with formats such as bold, italics, images, among others. In addition,
the ability to write statements and solutions with mathematical formulas was
added, which expands the possibilities for the creation of more complex and
detailed questions.
Integration with Moodle: A successful integration of Qwizer with the for-
mats used in the Moodle platform was achieved. Now users can import ques-
tions from the Moodle question bank, making it easy to reuse and migrate
existing content. Likewise, the functionality to export the notes of the ques-
tionnaires carried out in Qwizer to Moodle was implemented, providing a
simpler and more centralized management of qualifications.
On the other hand, we managed to complete other objectives, which appeared
as the project progressed. Among them we can find the following:
81
82 Capítulo 9. Contribuciones personales
Core design changes: Initially we did not plan to change the design of our
fellow colleagues views, but tied to the goal of responsive design, we were
forced to make some changes to almost all of the initial views to improve the
overall experience of our users. We even introduce new features that Qwizer
did not previously have (section 3.5.3).
API documentation: As we already explained in the improvements part
(see section 3.5.2), a good documentation of an API seems essential to us
since without it it is extremely difficult to understand each call and the reason
for it. At the beginning, we found ourselves in this situation and for the sake
of possible future colleagues or just someone who wants to understand our
work, we have developed an extensive and interactive documentation on each
API call, making it easy for anyone to understand.
Testing: Just like a good documentation, any software development involves
a testing phase, where numerous tests are carried out to check if the developed
code works as expected. For this reason, we developed a testing section for
the back-end part of the application, in which the logic of the API calls was
tested.
Application deployment: Finally, if we wanted to test our application in a
production environment, we had to explore the possibility of preparing it for
deployment, so we configured and adapted our development environment to
deploy the application at any time. In fact, in chapter 7 we have explained
the solution to this objective in more detail.
In addition, we also managed to complete part of the objectives that our col-
leagues had planned for future work. Among them we managed to implement:
1. The completion of questionnaires sequentially, where teachers can now choose
whether a questionnaire should be done immediately, that is, without being
able to return to the previous question once they move on to the next one.
2. Resolution of more than one questionnaire at the same time offline. Previously,
a quiz could only be solved if the student lost connection. Instead, now thanks
to the new questionnaire storage management, more than one questionnaire
can be taken offline.
3. Improvement of the interface for the creation of questionnaires and, as we
have already commented in the main objectives, adaptation of the interface to
mobile devices.
Difficulties encountered
Throughout the development of the project, some difficulties were encountered
that affected the process of implementing the objectives:
83
Legacy code: One of the biggest challenges was dealing with legacy code
from the previous project. The use of pre-existing code presented difficulties
to understand its operation and adapt it to the new requirements, leading
to many weeks of refactoring and improvements to the initial code. For this
reason, the period of adaptation and familiarization with the project lasted
longer than normal, preventing some secondary objectives from being met.
Lack of knowledge: It should be noted that both members of the team
started out without a base of the technologies used within the project, so
the use of these new technologies and tools during the development of the
project implied a period of learning and familiarization. Both Django and
React were frameworks that had complicated mechanisms to understand and
assimilate at first. Therefore we can add the main problems encountered
during development with both frameworks:
• Django: One of the libraries that has been used most extensively this
year is called djangorestframework. Its use is explained in section 3.5.
The extension in the use of this library caused numerous problems since
the use of serializers, viewsets and other mechanisms of the same were
somewhat complicated to implement in the received inherited code. This,
added to the little understanding that we had of Django in the early
stages of development, caused us great complications at the beginning
of the project, which we resolved thanks to the official documentation of
both Django and the library.
• React: Aside from the general unfamiliarity of React, dealing with com-
ponents so logically heavy was initially overwhelming. After several weeks
of development, this feeling of pressure lessened and even led to numerous
improvements in each of the components.
Time: The time factor was another significant challenge. Much of the de-
velopment was to adapt and improve the previous code, and that resulted in
less time to develop new features. Even if the initial objectives set for the
work were met, a verification and testing stage in a real environment would
have helped the project to reach a higher level of refinement. All this without
regarding the little availability at some moments of the project due to the
pressure of the course and at specific moments, of final exams.
Future work
Although the proposed objectives have been successfully achieved, we believe
that the project still has room for improvement. To this end, we propose the fol-
lowing modifications:
Accessibility: Although we have adapted most of the application for all de-
vices, we believe that the accessibility of our application could still be extended
84 Capítulo 9. Contribuciones personales
allowing all types of users, regardless of their physical or cognitive abilities,
to use Qwizer. The use of accessibility techniques such as changing colors,
changing themes, descriptions with help texts, use of simple typography, and
large, easy-to-locate buttons and panels would help adjust Qwizer to a more
inclusive environment.
Testing in the front-end: As we explained before, one of the secondary
objectives accomplished was the fact of documenting and testing the back end
of our application. This has resulted in numerous benefits throughout the
development of the application. On the other hand, one of the tasks that we
would have liked to carry out and due to lack of time has not been able to
complete is the fact of testing the interface and the main components of the
front-end of our application. On numerous occasions, we have had to check the
correct functioning of the component logic, which sometimes caused API calls
and consequently, changes in the state of the database. This tedious process
made us consider carrying out a series of tests on the most essential views of
the interface, allowing us to test this logic and carry out the tests against a
database prepared for the tests. In addition, this type of testing directs the
code on a more sustainable and stable path, for which future developments
would be grateful for them.
Own interface for mobiles: Throughout the development of the project,
there have been numerous occasions in which we have reconsidered making our
own interface for mobile devices, because even if we have spent time getting
an adaptable design of our application, there are always some components
or some fragments of the views that are slightly out of the style of a mobile
application. For this we study Ionic, a framework focused on the development
of user interfaces on mobile devices, which, since its syntax is very similar
to our main styling framework, Bootstrap, would allow us to have designed a
unique and exclusive interface for mobile devices. In the end, we ended up
giving up the idea because it was a very difficult goal to complete in the short
time we had left, but in the future, this idea could give Qwizer a fundamental
value for its implementation within the subjects of the faculty.
More types of questions: Qwizer is an application based on completing
questionnaires and one of the sections in which it still leaks is its small catalog
of questions. Although this year we have spent some time creating a new
question type (random selection questions) we thought that more question
types could be added, such as matching questions, fill-in-the-blank questions,
among others. Also, thanks to the new database structure, adding these new
question types would be easy to do. For this reason, we think that this new
addition of questions could give Qwizer another image and propel it into a
more attractive environment.
Import more types of questions: In relation to the import of questions,
we also have numerous open fronts that we could cover, such as the fact of
importing other types of questions from Moodle, since right now, only can
be imported short answer and multi-choice questions. At the same time, we
85
have rethought the idea of importing questions with other formats. Currently,
Qwizer only allows you to import questions with XML-Moodle format, con-
centrating all this functionality in a single format. Therefore, in summary,
importing other types of questions from Moodle and increasing the number
of formats to import questions, seems to us to be an objective that could be
considered for the future.
Use moodle API. Improve connectivity with Moodle: Linked to the
export of Qwizer grades to Moodle, we believe that this objective can be
improved by making interoperability between Qwizer and Moodle more com-
fortable. For this, we thought that it would be better to use Moodle’s own
API to expand the possibilities of integration with our application.
Bibliografía
[1] Benoit Chesneau. gunicorn. https://gunicorn.org/, 2023.
[2] Django Import Export. django-import-export. https://
django-import-export.readthedocs.io/, 2023.
[3] Django Software Foundation. Django. https://docs.djangoproject.
com/en/4.2/, 2023.
[4] El Fakhri Ouajih, Z. y Martínez Gamero, P. Aplicación web progresiva
para la realización de cuestionarios, 2022. Trabajo de Fin de Grado en Inge-
niería Informática, Facultad de Informática UCM, Departamento de Sistemas
Informáticos y Computación, Curso 2021/2022.
[5] Hello Pangea. hello-pangea/dnd. https://github.com/hello-pangea/
dnd, 2023.
[6] Jens Neuhalfen. drf-spectacular. https://drf-spectacular.
readthedocs.io/, 2023.
[7] John Resig. jQuery. https://api.jquery.com/, 2023.
[8] Mark Otto and Jacob Thornton. Bootstrap. https://getbootstrap.
com/docs/5.3/getting-started/introduction/, 2023.
[9] Martin Dougiamas. Moodle XML. https://docs.moodle.org/all/es/
Formato_Moodle_XML, 2023.
[10] Matt Zabriskie. axios. https://github.com/axios/axios, 2023.
[11] Max Lynch, Ben Sperry, and Adam Bradley. Ionic. https://
ionicframework.com/docs, 2023.
[12] Mozilla Contributors. localforage. https://localforage.github.io/
localForage/, 2023.
[13] React Contributors. React. https://reactjs.org/, 2023.
[14] React Training. react-router-dom. https://reactrouter.com/web/
guides/quick-start, 2023.
[15] Rolf Håvard Blindheim. django-environ. https://github.com/joke2k/
django-environ, 2023.
[16] SmartBear Software. Swagger. https://swagger.io/, 2023.
87
https://gunicorn.org/
https://django-import-export.readthedocs.io/
https://django-import-export.readthedocs.io/
https://docs.djangoproject.com/en/4.2/
https://docs.djangoproject.com/en/4.2/
https://github.com/hello-pangea/dnd
https://github.com/hello-pangea/dnd
https://drf-spectacular.readthedocs.io/
https://drf-spectacular.readthedocs.io/
https://api.jquery.com/
https://getbootstrap.com/docs/5.3/getting-started/introduction/
https://getbootstrap.com/docs/5.3/getting-started/introduction/
https://docs.moodle.org/all/es/Formato_Moodle_XML
https://docs.moodle.org/all/es/Formato_Moodle_XML
https://github.com/axios/axios
https://ionicframework.com/docs
https://ionicframework.com/docs
https://localforage.github.io/localForage/
https://localforage.github.io/localForage/
https://reactjs.org/
https://reactrouter.com/web/guides/quick-start
https://reactrouter.com/web/guides/quick-start
https://github.com/joke2k/django-environ
https://github.com/joke2k/django-environ
https://swagger.io/
88 BIBLIOGRAFÍA
[17] Solomon Hykes. Docker. https://docs.docker.com/, 2023.
[18] Sunscrapers. djoser. https://djoser.readthedocs.io/, 2023.
[19] Tom Christie. djangorestframework. https://www.
django-rest-framework.org/, 2023.
https://docs.docker.com/
https://djoser.readthedocs.io/
https://www.django-rest-framework.org/
https://www.django-rest-framework.org/
Apéndice A
Documentación de la API
En este apéndice se encuentra la documentación de la API generada a partir del
archivo JSON de Swagger y convertida en formato PDF. Sin embargo, es importante
tener en cuenta que la interfaz de Swagger es considerablemente superior a este
documento en PDF.
89
API Reference
Qwizer API
API Version: 1.0.0
This is Qwizer's official API documentation.
1 of 21
INDEX
1. AUTH 4
1.1 POST /api/auth/token/login 4
1.2 POST /api/auth/token/logout 4
1.3 GET /api/auth/user/me 4
2. ESTUDIANTES 6
2.1 GET /api/estudiantes 6
2.2 GET /api/estudiantes/{id} 6
2.3 GET /api/estudiantes/{id}/disponibles 7
3. QR 8
3.1 POST /api/qr 8
3.2 GET /api/qr/{id_usuario}/{id_cuestionario} 8
4. QUESTION 10
4.1 POST /api/question 10
4.2 PUT /api/question/{id} 10
4.3 DELETE /api/question/{id} 10
4.4 POST /api/question/imagen 11
5. SUBJECT 12
5.1 GET /api/subject 12
5.2 GET /api/subject/{id}/cuestionarios 12
5.3 POST /api/subject/{id}/delete_enroll 12
5.4 POST /api/subject/{id}/enroll 13
5.5 GET /api/subject/{id}/preguntas 14
5.6 GET /api/subject/me 14
6. TEST 16
6.1 POST /api/test 16
6.2 GET /api/test/{id} 17
6.3 POST /api/test/{id}/enviar 17
6.4 GET /api/test/{id}/info 18
6.5 GET /api/test/{id}/nota/{id_alumno} 19
6.6 GET /api/test/{id}/notas 19
6.7 POST /api/test/subir 20
2 of 21
Security and Authentication
SECURITY SCHEMES
KEY TYPE DESCRIPTION
tokenAuth apiKey Token-based authentication with required prefix "Token"
3 of 21
API
1. AUTH
Autenticación de usuarios
1.1 POST /api/auth/token/login
Use this endpoint to obtain user authentication token.
REQUEST
REQUEST BODY - application/json
{
password string
email string
}
FORM DATA PARAMETERS
NAME TYPE DESCRIPTION
password string
email string
FORM DATA PARAMETERS
NAME TYPE DESCRIPTION
password string
email string
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
password string
email string
}
1.2 POST /api/auth/token/logout
Use this endpoint to logout user (remove user authentication token).
REQUEST
No request parameters
RESPONSE
STATUS CODE - 200: No response body
1.3 GET /api/auth/user/me
4 of 21
REQUEST
No request parameters
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
first_name string max:30 chars
last_name string max:150 chars
role* enum ALLOWED:student, teacher, admin
* `student` - student
* `teacher` - teacher
* `admin` - admin
id* integer READ-ONLY
email* string READ-ONLY
}
5 of 21
2. ESTUDIANTES
Estudiantes
2.1 GET /api/estudiantes
Lista de los usuarios que tienen rol de estudiante
GET /estudiantes
REQUEST
No request parameters
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
[{
Array of object:
alumnos* [{
Array of object:
id* string
nombre* string
apellidos* string
}]
}]
2.2 GET /api/estudiantes/{id}
Lista de los usuarios que tienen rol de estudiante de una asignatura
GET /estudiantes/{id_asignatura}
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
alumnos* [{
Array of object:
id* string
nombre* string
apellidos* string
}]
}
6 of 21
2.3 GET /api/estudiantes/{id}/disponibles
Lista de los usuarios que tienen rol de estudiante que no estan cursando una asignatura
GET /estudiantes/{id_asignatura}/disponibles
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
alumnos* [{
Array of object:
id* string
nombre* string
apellidos* string
}]
}
7 of 21
3. QR
Qr
3.1 POST /api/qr
Insertar hash para un intento
REQUEST
REQUEST BODY - application/json
{
idUsuario* integer
idCuestionario* integer
hash* string
}
FORM DATA PARAMETERS
NAME TYPE DESCRIPTION
idUsuario integer
idCuestionario integer
hash string
FORM DATA PARAMETERS
NAME TYPE DESCRIPTION
idUsuario integer
idCuestionario integer
hash string
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
inserted* string
message* string
}
STATUS CODE - 400: Error: Bad Request
STATUS CODE - 403:
RESPONSE MODEL - application/json
{
inserted* string
message* string
}
3.2 GET /api/qr/{id_usuario}/{id_cuestionario}
8 of 21
Comprobación hash qr
GET /{idUsuario}/{idCuestionario}
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id_usuario integer
*id_cuestionario integer
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
alumnos* [{
Array of object:
id* string
nombre* string
apellidos* string
}]
}
9 of 21
4. QUESTION
Preguntas
4.1 POST /api/question
Crear preguntas a partir de un fichero csv o xml
REQUEST
FORM DATA PARAMETERS
NAME TYPE DESCRIPTION
file string(binary)
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
inserted string
message string
}
4.2 PUT /api/question/{id}
Actualizar una pregunta
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer Id de la pregunta
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
message string
}
4.3 DELETE /api/question/{id}
Borrar una pregunta
REQUEST
PATH PARAMETERS
10 of 21
NAME TYPE DESCRIPTION
*id integer Id de la pregunta
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
message string
}
4.4 POST /api/question/imagen
REQUEST
No request parameters
RESPONSE
STATUS CODE - 200: No response body
11 of 21
5. SUBJECT
Asignaturas
5.1 GET /api/subject
Lista de asignaturas
REQUEST
No request parameters
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
[{
Array of object:
asignaturas [{
Array of object:
id string
asignatura string
}]
}]
5.2 GET /api/subject/{id}/cuestionarios
Lista de cuestionarios de una asignatura
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer Id de la asignatura
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
cuestionarios [{
Array of object:
id string
titulo string
}]
nombre string
}
5.3 POST /api/subject/{id}/delete_enroll
Desmatricular una lista de estudiantes
12 of 21
DELETE /asignatura/{pk}/enroll
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer Id de la asignatura
REQUEST BODY - application/json
{
alumnos [{
Array of object:
id integer
nombre string
apellidos string
}]
}
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
borrados integer
errors [undefined]
}
5.4 POST /api/subject/{id}/enroll
Matricular una lista de estudiantes
POST /asignatura/{pk}/enroll
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer Id de la asignatura
REQUEST BODY - application/json
{
alumnos [{
Array of object:
id integer
nombre string
apellidos string
}]
}
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
13 of 21
{
insertados integer
errors [undefined]
}
5.5 GET /api/subject/{id}/preguntas
Lista de preguntas de una asignatura
Devuelve todas las preguntas de una asignatura para el banco de preguntas
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer Id de la asignatura
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
preguntas
ONE OF
OPTION 1{
id integer
question string
title string
type enum ALLOWED:text
correct_op string
}
OPTION 2{
id integer
question string
title string
type enum ALLOWED:test
options [{
Array of object:
id string
op integer
}]
correct_op integer
}
}
5.6 GET /api/subject/me
Estado de los cuestionarios de un usuario
REQUEST
No request parameters
RESPONSE
14 of 21
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
asignaturas [{
Array of object:
id integer
nombre string
cuestionarios {
nCuestionarios integer
nCorregidos integer
nPendientes integer
}
}]
}
15 of 21
6. TEST
Cuestionarios
6.1 POST /api/test
Crear cuestionario
REQUEST
REQUEST BODY - application/json
{
cuestionario {
testName string
testPass string
testSubject string
secuencial string
testDuration string
fechaApertura integer
fechaCierre integer
fechaVisible integer
questionList
ONE OF
OPTION 1{
id integer
question string
title string
tipo string
correct_op string
punt_positiva integer
punt_negativa integer
fijar boolean
aleatorizar boolean
}
OPTION 2{
id integer
question string
title string
tipo string
options [{
Array of object:
id integer
op string
}]
correct_op integer
punt_positiva integer
punt_negativa integer
fijar boolean
aleatorizar boolean
}
aleatorizar string
}
}
RESPONSE
16 of 21
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
inserted string
message string
}
6.2 GET /api/test/{id}
Descargar cuestionario
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer Id del cuestionario
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
id integer
titulo string
duracion integer
secuencial boolean
password string
fecha_visible string
fecha_apertura string
fecha_cierre string
aleatorizar boolean
profesor integer
asignatura integer
iv string
encrypted_message string
formatted_fecha_apertura string
formatted_fecha_cierre string
}
6.3 POST /api/test/{id}/enviar
Responder a un cuestionario
POST /enviar -> POST /tests/1/enviar
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer A unique integer value identifying this cuestionario.
REQUEST BODY - application/json
17 of 21
{
respuestas {
id
ONE OF
OPTION 1{
id integer
type enum ALLOWED:text
answr string
}
OPTION 2{
id integer
type enum ALLOWED:test
answr integer
}
}
hash string
}
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
nota number
}
6.4 GET /api/test/{id}/info
Información de un cuestionario para un alumno
POST /get-quiz-info -> GET /tests/{pk}/info
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer A unique integer value identifying this cuestionario.
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
duracion integer
formatted_fecha_apertura string
formatted_fecha_cierre string
formatted_fecha_visible string
fecha_apertura string
fecha_cierre string
fecha_visible string
corregido integer
nota number
}
18 of 21
6.5 GET /api/test/{id}/nota/{id_alumno}
Nota de un cuestionario de un estudiante
También se devuelven las respuestas del usuario
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer A unique integer value identifying this cuestionario.
*id_alumno string
PATTERN: ^\d+$
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
titulo string
nota number
questions
ONE OF
OPTION 1{
id integer
question string
type enum ALLOWED:text
correct_op string
user_op string
}
OPTION 2{
id integer
question string
type enum ALLOWED:test
correct_op integer
user_op integer
}
}
6.6 GET /api/test/{id}/notas
Lista de notas de todos los alumnos de un cuestionario
POST /get-quiz-grades -> GET /tests/{pk}/grades
REQUEST
PATH PARAMETERS
NAME TYPE DESCRIPTION
*id integer A unique integer value identifying this cuestionario.
RESPONSE
STATUS CODE - 200:
19 of 21
RESPONSE MODEL - application/json
{
notas {
email {
id integer
nombre string
apellidos string
nota number
}
}
}
6.7 POST /api/test/subir
Creación de un cuestionario a partir de un yaml
POST /upload -> POST /tests/upload
REQUEST
REQUEST BODY - application/json
{
fichero_yaml string
}
RESPONSE
STATUS CODE - 200:
RESPONSE MODEL - application/json
{
inserted string
message string
}
20 of 21
21 of 21
Página de Título
Agradecimientos
Resumen
Abstract
Índices
Tabla de Contenidos
Índice de figuras
Índice de tablas
Introducción
Motivación del proyecto
Objetivos
Plan de trabajo
Selección de herramientas y tecnologías
Lenguajes de programación, gestión de bases de datos y frameworks utilizados
Python
Django
JavaScript
React
SQL
Service workers
Bootstrap
Lenguajes de intercambio de datos y marcado
JSON
YAML
XML-Moodle
Markdown
Otras herramientas o tecnologías
Git
Github
Docker
PostgreSQL
LaTeX
Visual Studio Code
Swagger
Tecnologías descartadas
JQuery
Ionic
Arquitectura global del sistema
Base de datos
Modelo entidad-relación
Modelo relacional
API de comunicación entre back-end y front-end
Aspectos de Django
Panel de administración
Librerías externas
Librerías para el back-end
Librerías para el front-end
Mejoras realizadas sobre el trabajo anterior
Front-end
Refactorización de clases a funciones
Actualización de librerías
Arreglo de errores y linting
Configuración del entorno del desarrollo
Mejoras generales
Back-end
Refactorización de código
Actualización de librerías
Arreglo de errores y linting
Configuración del entorno del desarrollo
Mejoras generales
Documentación de la API
Testing
Nuevas funcionalidades extras
Aleatorización de cuestionarios
Objetivo principal / Introducción
Cambios en la base de datos
Creación de entidad Intento e instancias
Aleatorización de preguntas
Aleatorización de opciones
Fijación de preguntas y opciones
Preguntas aleatorias
Nuevo modelo, SelecciónPregunta
Uso dentro de la aplicación
Introducción de Markdown y fórmulas
Librerías utilizadas
Gestión de imágenes
Subida de imágenes
Tratamiento de imágenes en Django
Recuperación de imágenes
Consecuencias
Resultados
Subida de preguntas con Markdown
Visualización de preguntas con Markdown
Integración con Moodle
Migración de las preguntas de Moodle
Formato Moodle XML
ElementTree
Exportar notas
Creación de contenedores Docker
Despliegue con Docker Compose
Servidor web con Nginx
Seguridad con HTTPS
Conclusiones y trabajo futuro
Objetivos alcanzados
Dificultades encontradas
Trabajo futuro
Contribuciones personales
Introduction
Conclusions and Future Work
Bibliografía
Documentación de la API