Documentación para desarrolladores

A continuación se presenta información enfocada a los desarrolladores

Proceso de desarrollo

Las plataformas de Evaluador, Preparación y presentación de clases, Parque Sabios y portal de presentación de Sabios están integradas en un proyecto con aplicaciones que interactúan.

Para mantener los ambientes de producción y desarrollo funcionales con una base sólida y estable en producción y el ambiente de de desarrollo con los modelos de datos más recientes y funcionalidades actualizadas contando con la posibilidad de aplicar hotfixes a producción o adición de funcionalidades en producción se empleará dos ramas:

  • master
  • dev

La rama master con la integración continua hará el despliegue sobre producción, mientras que la rama dev apuntará a desarrollo.

Siempre que se hagan fixes en master se harán mezclas de master hacia dev, las cuales serán backports.

Estructura de datos JSON en respuestas AJAX

En la medida de lo posible, se entrega un diccionario con 3 entradas:

{
    response_message: "mensaje mostrado al usuario",
    response_status: 0,
    obj_list: [],
}

obj_list es una lista de objetos; la estructura de cada objeto en la lista está a discreción del programador.

response_status debe ser 0 si el request tuvo éxito, y diferente de 0 en otro caso. Los valores particulares (diferentes de cero) están a discreción del programador.

response_message es una cadena unicode, con el mensaje apropiado para el usuario, bien sea para casos de error o de éxito.

Usuarios

Para minimizar las consultas en la base de datos se está almacenando información de las consignaciones de puntos que se hace a los estudiantes en un campo dentro de la tabla usuario cuyo formato es::

{
  'operations': [{
    'amount': value
    'timestamp': date
    'description': char
    'game': can be None, game that issued the operation
    'user': can be None, user that issued the operation
    'transaction_type': retiro o depósito
  }]
}

Padres de familia

El padre de familia o acudiente va a tener el hijo actual guardado en la variable de session CURRENT_CHILD , cuando se autentica va a buscar la variable de sesión CURRENT_CHILD, en caso de que no esté buscará el primer hijo o acudido y lo colocará en CURRENT_CHILD.

Habrá un método que permitirá que el acudiente pueda cambiar de hijo, se establecerá el hijo que está en la sesión y en caso contrario se obtendrá el primer “hijo” disponible.

El userprofile de un padre de familia deberá proporcionar un método que ofrezca un listado de los hijos del colegio actual.

Exámenes

Para reducir la cantidad de relaciones en la base de datos, las respuestas dadas por los usuarios se serializan en el campo answers de la clase UserAnswers. Para dar un resultado más rápido en los reportes se precalculan los puntajes obtenidos por los estudiantes.

La estructura del campo answers, en el que se almacena el listado de preguntas, y sus respuestas, es:

{
   'questions': [
       {
          'question': DB id of the question,
          'subject': DB id of question's subject,
           'status': one of: QUESTION_STATUS_UNANSWERED,
                             QUESTION_STATUS_SKIPPED,
                             QUESTION_STATUS_ANSWERED
                             (defined in evaluation/data.py)
          'answers_presentation_order': randomized list of the following
                                        elements:
           [
              'answer', 'distractor_1', 'distractor_2',
              'distractor_3'
           ],
          'answer': integer, index for the 'answers_presentation_order'
                    array; range: [0, 3]; answer  will be correct if
                    element at this position matches the string 'answer'

                    When no answer has been provided, it holds the
                    None special value
       }
    ]
}

Creación automatizada de preguntas

Si se requiere creación automatizada de preguntas para hacer pruebas, se puede usar:

from django_dynamic_fixture import G
from evaluation.data import QUESTIONSET_CHOICE_NOT_GRADABLE
from evaluation.tests import _create_questions
user = User.objects.get(email='carlos@axiacore.com')
for axiscontent in AxisContent.objects.all():
  _create_questions(
      user,
      axiscontent=axiscontent,
      questionset=QUESTIONSET_CHOICE_NOT_GRADABLE
  )

Carga Masiva

Se está empleando Celery para la carga masiva de estudiantes, hay dos etapas en la carga de estudiantes:

  • Revisión de consistencia interna del archivo
  • Revisión y cargue de datos frente al sistema

La revisión de la consistencia del archivo es rápida y debe tardar menos de 30 segundos para un archivo con miles de registros.

La carga de cada registro tarda entre 2 y 3 segundos y se verifica consistencia de datos para evitar escalamiento de permisos en diferentes colegios, por ejemplo, no es admisible que un administrador sea estudiante de colegio alguno.

Para garantizar que los procesos terminan estos son encolados y solamente puede ejecutarse una carga a la vez en todo el sistema, esto está controlado por un semáforo y reintentos de Sentry.

Juegos

Comentarios de foro

Además de añadir una respuesta a un foro, es posible responder a una de las respuestas de un foro. Dichas respuestas anidadas, o subrespuestas/subcomentarios, se almacenan como datos JSON dentro de cada respuesta; ver campo related_answers del modelo communication.ForumAnswer. El formato de dicho campo es el siguiente:

El campo contiene un único objeto, un diccionario con una entrada (llave) ‘related_answers’, cuyo contenido es la lista de respuestas (related_answer). Es decir:

{
    related_answers: [related_answer, related_answer, ...]
}

Cada related_answer es un diccionario, que fundamentalmente imita la estructura del modelo UserAnswer:

{
    description: cadena unicode con texto markdown: el comentario.
    status:      entero. Es uno de los estados definidos en
                 communication.data.FORUM_ANS_STATUSES
    created_at:  marca de tiempo de la creación del subcomentario,
                 almacenada como una cadena de texto con la fecha en
                 formato ISO 8601 incluyendo la zona horaria
                 (YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM)
    created_by:  Entero. id del usuario (userprofile.User) que creó el
                 comentario.
}

Estilos

En esta sección se presentan algunos estilos para emplear a lo largo de la aplicación

Uso de componentes js

Uso de componente Markdown

Para que un textarea con id id_description, que esté dentro de un div con id div_id_description tenga la opción de convertirse en un control markdown se usa el siguiente código javascript:

$('#id_description').attach_markdown({
    container: '#div_id_description'
});

Para desplegar el contenido se envuelve en un div con un código similar a:

<div class=”content_text”>
{{ description|markdown }}

</div>

Uso de componentes de archivos

Para usar un componente de archivo se emplea la función configure_file_field, la cual fue inyectada en la librería jquery, para emplearla:

$.configure_file_field(
    '.section_form',
    'div.upload_image',
    '#id_image',
    '.filename_holder'
);

Donde recibe como primer parámetro un selector del contenedor del formulario para en caso de que se recargue por ajax puede mantener las reacciones, como segundo parámetro el div que reemplaza el widget nativo, como tercer parametro el selector del id del componente nativo y finalmente el widget original donde aparecería el nombre del archivo.

Uso de mensajes informativos

Para ofrecer un mensaje en la página emplee la función show_message de la siguiente forma:

$.show_message('No fue posible enviar el mensaje', 'error');

El segundo parámetro es opcional y puede ser también warning.

Uso de mensajes de confirmación

Los mensajes de confirmación se apoyan en la librería Messi, deberá usar el callback para continuar el flujo de la operación de acuerdo a la respuesta del usuario:

new Messi('¿Estás seguro de que deseas desactivar el evento?', {
          title: 'Cancelar',
          buttons: [
            {id: 0, label: 'Sí', val: 'Y'},
            {id: 1, label: 'No', val: 'N'}
          ],
          callback: function(val) {
            $.show_message('elegiste ' + val);
          }
});

Comunicaciones

Para llevar el conteo de los eventos por leer de un usuario se estructuró una API que permite:

Aumentar el conteo dada una cantidad de elementos, si no recibe parámetro se incrementa en un elemento:

add_counter(counter, users, quantity)

Donde counter es una cadena de texto ‘messages’|’events’|’news’|’memos’, users es un iterable del ids de usuarios a los que se les incrementará el conteo de eventos, quantity es la cantidad de elemntos que se añadieron para el listado de usuarios. add_counter se ejecutará como una tarea en background para evitar demorar otros procesos como el envío de una circular para todo el colegio o la notificación de una noticia.

reset_counter(counter, user)

**counter es como se especificó anteriormente y user es el usuario de la aplicación al cual se le añadirán los eventos correspondientes.

El almacenamiento de esta información se hará en redis con elemento hkeys, los nombres de las llaves principales serán:

'noti.userid'

Donde se reemplazará userid por el id del usuario.

Adicionalmente se emplea un prefijo para evitar colisionar con otros servicios, la constante que especifica tal prefijo en settings es:

COMMUNICATION_PREFIX

Para incrementar el contador de mensajes:

from communication.signals import add_counter

add_counter.send(
    sender=user,
    counter='messages',
    users=[user.id],
    quantity=3,
)

Incrementaría en 3 el contador para el usuario con user.id, cabe notar que users es una lista para permitir que varios usuarios puedan incrementar el contador.

Durante el desarrollo

En esta sección se encuentran algunos comandos frecuentes durante el desarrollo

Celery local

Se aplica el comando:

manage.py celery worker --loglevel=info

Compilar sass

Una vez instalada la aplicación localmente, se debe compilar el sass para que se muestren los estilos y las imágenes correctamente, para hacerlo deberan estar situados en el directorio del proyecto y ejecutar el siguiente comando:

compass compile app/static/