Upload
others
View
7
Download
0
Embed Size (px)
Citation preview
UNIVERSIDAD POLITÉCNICA DE MADRID Escuela Técnica Superior de Ingeniería de Sistemas Informáticos
MÁSTER EN INGENIERÍA WEB Proyecto Fin de Máster
Análisis y Desarrollo de un Sistema de Perfeccionamiento de Idiomas
Autor Jorge Rábanos Peña
Tutor Santiago Alonso Villaverde
Junio de 2016
2
1. INTRODUCCIÓN 3 1.1. RESUMEN DEL PROYECTO 3 1.2. ABSTRACT 4 1.3. OBJETIVOS 5
2. HERRAMIENTA DE INTERCAMBIO DE IDIOMAS 6 2.1. ESTRUCTURA DE LA RED SOCIAL 6 2.2. AFINIDAD DE USUARIOS 6 2.3. CÓMO SE USA 6
3. PLANIFICACIÓN DEL PROYECTO 7 3.1. METODOLOGÍA 7 3.2. TECNOLOGÍAS 7 3.3. FUNCIONALIDADES 8 3.4. CASOS DE USO 9 3.5. MODELO DE DATOS 19 3.5.1. TABLAS 20 3.6. DISEÑO DE INTERFAZ 22 3.7. PLAN DE PRUEBAS 32 3.8. ITERACIONES 33
4. DESARROLLO DEL PROYECTO 34 4.1. CONCEPTOS IMPORTANTES 34 4.1.1. API REST 34 4.1.2. WEBSOCKET 35 4.2. ARQUITECTURA 36 4.3. SERVIDOR Y API REST 38 4.3.1. HERRAMIENTAS 38 4.3.2. API REST Y RECURSOS 39 4.3.3. IMPLEMENTACIÓN DE LA API 40 4.3.4. PRUEBAS 44 4.4. SERVIDOR DE CHAT 45 4.4.1. DJANGO CHANNELS 45 4.4.2. IMPLEMENTACIÓN 46 4.4.3. ARQUITECTURA FINAL 47 4.5. CLIENTE WEB 52 4.5.1. HERRAMIENTAS 52 4.5.2. ESTRUCTURA 55 4.5.3. IMPLEMENTACIÓN DE LA INTERFAZ 55 3.5.4. WEBSOCKETS / CHAT 62
5. CONCLUSIONES 64
6. FUTURAS AMPLIACIONES 65
7. BIBLIOGRAFÍA 66
8. APÉNDICE 67 8.1. ESPECIFICACIÓN API REST 67
3
1. Introducción
1.1. Resumen del proyecto La necesidad de aprender y dominar más de una lengua es algo que se observa de forma clara en la actualidad. Ya sea para acceder a un puesto de trabajo, buscar información, viajar o incluso ver películas; saber idiomas es siempre beneficioso y su desconocimiento significa, en muchos casos, quedarse atrás. Además de la comprensión y la gramática, hay un aspecto en el que se debe hacer cierto énfasis si realmente se quiere dominar un idioma: la práctica. Los múltiples libros y cursos de idiomas a los que tenemos acceso pueden dar una base consistente y un vocabulario amplio, pero no aportan ese punto importante que todos tratamos de alcanzar, practicar una lengua hasta poder hablarla con soltura. La idea de este proyecto es la de resolver esa necesidad de practicar una lengua para afianzar los conocimientos mediante una herramienta de intercambio de idiomas. Se trata de poner en contacto a personas deseosas de practicar idiomas para realizar un intercambio: “Si te parece, hablamos un rato en inglés y después en español”. Es también el objetivo de este proyecto ofrecer a los usuarios la posibilidad de organizar quedadas grupales en lugares cercanos para practicar en persona y no a través de Internet.
4
1.2. Abstract The need to learn and master more than one language is something that is seen clearly nowadays. Whether to apply for a job, find information, travel or even watch movies; learning languages is always beneficial and to ignore them means, in most cases, to stay behind. In addition to comprehension and grammar, there is one aspect in which some emphasis should be done if you really want to master a language: practice. Multiple books and language courses we can access give a consistent basis and a wide vocabulary, but do not provide this important point we all try to reach, practice a language to be able to speak it fluently. The idea of this project is to solve this need to practice a language to strengthen understanding through a language exchange tool. It is bringing together people eager to practice languages for an exchange: "If you agree, we talk for a while in English and then in Spanish." It is also the purpose of this project to provide users the ability to organize group hangouts nearby places to go in person and not via the Internet.
5
1.3. Objetivos El objetivo principal del proyecto es el desarrollo de una red social de intercambio de idiomas. Esta red social servirá principalmente como medio para poner en contacto, a través de Internet, a usuarios que deseen practicar idiomas y para organizar quedadas. Para llevarlo a cabo, se debe definir cómo va a ser la red, qué funcionalidades debe cumplir y cómo se va a usar. Después se organizará una gestión de usuarios y un diseño de base de datos del sistema. Una vez definido lo anterior, el siguiente objetivo y parte central del proyecto es el desarrollo de una aplicación Web que ofrezca un servicio cumpliendo las funcionalidades deseadas. Para ello, se deberán desarrollar los siguientes elementos:
-‐ Aplicación de servidor en la que se desarrolle la lógica del negocio. Ésta debe ofrecer una API REST para ser usada desde aplicaciones de cliente.
-‐ Una aplicación de cliente Web que haga uso de la API anteriormente definida.
-‐ Un módulo complementario a los anteriores que permita a dos usuarios del sistema mantener conversaciones en tiempo real.
6
2. Herramienta de intercambio de idiomas
2.1. Estructura de la red social La aplicación sobre la que versa el proyecto es una herramienta que facilita la relación y comunicación entre personas a través de Internet, lo que entendemos por red social. A diferencia de otras tantas que existen a día de hoy, no se plantea el concepto de amistad o seguimiento. Simplemente se trata de poner en contacto a personas sin comprometer ni manifestar ese contacto. Es por ello que los usuarios podrán ver, a forma de listado, los perfiles de otros usuarios afines a sus necesidades en lo que a práctica de idiomas se refiere. Entre dos usuarios cualesquiera, la relación dentro de la aplicación vendrá representada en forma de sala de chat, en la que podrán mantener una conversación.
2.2. Afinidad de usuarios Para relacionar usuarios por afinidad, el sistema dispone de dos tipos de datos cruciales: qué idiomas desea practicar el usuario, y cuáles habla o domina. En función de esto, será más afín a un usuario quien más idiomas pueda intercambiar y será menos afín quien no tenga ningún punto en común. La necesidad de separar idiomas hablados e idiomas practicados surge de que se quiere enfatizar la importancia del intercambio: lo ideal es que si un usuario habla inglés y quiere practicar español se ponga en contacto con otro que hable español y desee practicar inglés.
2.3. Cómo se usa Como se ha explicado anteriormente, la parte en la que se pone en contacto a usuarios tiene un flujo muy sencillo: el usuario visualiza perfiles de otros usuario afines a él. Una vez decide con quién desea practicar idiomas, puede abrir una sala de chat en la que conversar. Respecto a la organización de quedadas, la aplicación ofrece tanto búsqueda como servicio de creación de éstas, de forma que se puedan establecer y visualizar las quedadas desde la aplicación con cierta facilidad.
7
3. Planificación del proyecto
3.1. Metodología El proyecto se ha dividido en varias iteraciones, que han permitido separarlo en partes bien diferenciadas y funcionales para ser probado y modificado de forma incremental. Las iteraciones que se iban a llevar a cabo se decidieron una vez definidas las funcionalidades que iba a tener la herramienta. Cada una de estas iteraciones tenía una serie de tareas asignadas en principio, pero a partir de las pruebas y los problemas que han ido surgiendo, se han podido añadir más tareas, mejoras y funcionalidades. Para llevar un control de las tareas y las prioridades, se ha adoptado parte de la metodología Kanban. Kanban es una metodología que permite gestionar y organizar el trabajo. Su uso en el desarrollo de software implica el uso de tarjetas que representan elementos de trabajo. Estas tarjetas permiten mostrar el proceso de desarrollo colocadas en un tablero dividido en columnas, representando cada una de ellas un estado del flujo de trabajo (análisis-‐desarrollo-‐test-‐producción, o por hacer-‐en proceso-‐hecho, etc.) En este proyecto se ha utilizado para tener un control y una visión general de la progresión del mismo. Cada tarea se ha correspondido con una tarjeta Kanban, para así poder tenerlas identificadas junto a su estado y prioridad.
3.2. Tecnologías Estas son las principales tecnologías que han establecido el entorno de trabajo en el que se ha desarrollado el proyecto:
-‐ Git: es un software de control de versiones que permite realizar un seguimiento de cambios en archivos y restauración de versiones anteriores. Se ha utilizado mediante la herramienta SourceTree de Atlassian haciendo uso de un repositorio remoto alojado en GitHub.
-‐ Taiga: plataforma de gestión de proyectos. Se ha utilizado para definir las funcionalidades, llevar un registro del desarrollo de las mismas y documentar la API REST.
-‐ OmniGraffle: una aplicación de The Omni Group para la creación de diagramas. Se ha usado tanto para diseñar la interfaz como para crear diagramas explicativos de esta memoria.
8
3.3. Funcionalidades Las funcionalidades que debe cumplir la herramienta son las siguientes: Registro y acceso Cualquier persona debe poder acceder
al sistema mediante un registro y posterior acceso con credenciales.
Indicar idiomas hablados y practicados
El usuario debe ser capaz de gestionar qué idiomas desea practicar y cuáles domina.
Edición de perfil de usuario La herramienta debe permitir la edición de los datos de usuario y avatar por el mismo.
Búsqueda de usuarios afines Se debe proporcionar al usuario información sobre otros usuarios afines a sus idiomas.
Visualización de perfiles Cada usuario puede ver el perfil público del resto de usuarios del sistema.
Abrir conversaciones El usuario debe poder abrir una conversación con otro usuario para poder hablar en tiempo real.
Buscar eventos Se debe ofrecer la posibilidad de visualizar eventos organizados cerca de la posición del usuario.
Crear eventos Cada usuario debe tener la capacidad de organizar un nuevo evento en una fecha futura desde la aplicación.
9
3.4. Casos de uso Una vez definidas las funcionalidades básicas, se plantean una serie de casos de uso que debe cumplir el sistema.
3.4.1. Registro de usuario Caso de uso Registro de usuario Actor Usuario Resumen El usuario se da de alta en el sistema a
través de un formulario Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la Web de la aplicación 2. Introduce sus datos: email, nombre y contraseña
3. Solicita alta a través de un botón 4. Comprueba la validez de los datos 5. Crea un nuevo usuario del sistema
con los datos proporcionados 6. Muestra por pantalla una
confirmación de registro Flujo alternativo 1
4. [Los datos proporcionados no son válidos]
5. Muestra por pantalla un mensaje de error
10
3.4.2. Acceso al sistema Caso de uso Acceso al sistema Actor Usuario Resumen El usuario accede al sistema haciendo
uso de sus credenciales Precondiciones El usuario se ha dado de alta
Curso típico de eventos Usuario Sistema
1. Accede a la Web de acceso 2. Introduce sus datos en el formulario de acceso
3. Solicita acceso a través de un botón 4. Comprueba la validez de los datos 5. Crea una sesión de usuario 6. Muestra la aplicación
Flujo alternativo 1 4. [Los datos proporcionados no son
válidos] 5. Muestra por pantalla un mensaje de
error
3.4.3. Salir del sistema Caso de uso Salir del sistema Actor Usuario Resumen Se cierra la sesión a petición del usuario Precondiciones El usuario debe estar autenticado
Curso típico de eventos Usuario Sistema
1. Solicita salir del sistema 2. Cierra la sesión del usuario 3. Redirige a la página de autenticación
11
3.4.4. Mostrar lista de usuarios afines Caso de uso Lista de usuarios afines Actor Usuario Resumen Se muestra al usuario una lista de otros
usuarios afines a su perfil Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la sección de usuarios afines 2. Recupera una lista de usuarios
ordenados por afinidad en función de los idiomas del usuario
3. Muestra por pantalla los datos de los usuarios
3.4.5. Visualizar perfil de usuario Caso de uso Visualizar perfil de usuario Actor Usuario Resumen Se muestran los datos de perfil de un
usuario en concreto Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede al perfil de un usuario 2. Muestra por pantalla los datos del
usuario: nombre, edad, género, descripción, idiomas que habla, idiomas que practica y avatar
3. Muestra por pantalla los datos de los usuarios
12
3.4.6. Actualizar datos de perfil Caso de uso Actualizar datos de perfil Actor Usuario Resumen El usuario debe poder actualizar sus
datos públicos de perfil Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la sección de perfil propio 2. Modifica alguno de los datos de perfil (descripción, género, fecha de nacimiento)
3. Solicita guardar los cambios 4. Actualiza el perfil del usuario 5. Muestra un mensaje de éxito
3.4.7. Cambiar imagen de perfil Caso de uso Cambiar imagen de perfil Actor Usuario Resumen El usuario actualiza o establece su
avatar o imagen de perfil Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la sección de perfil propio 2. Selecciona una imagen 3. Solicita actualizar avatar 4. Actualiza el avatar del usuario 5. Muestra un mensaje de éxito
Flujo alternativo 1 4. [El formato de imagen no es
soportado en el sistema] 5. Muestra por pantalla un mensaje de
error
13
3.4.8. Añadir idioma hablado Caso de uso Añadir idioma hablado Actor Usuario Resumen El usuario añade un idioma a su lista de
idiomas que habla o domina Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la sección de idiomas propios
2. Muestra una lista de idiomas posibles para añadir
3. Selecciona un idioma de la lista 4. Solicita añadir idioma hablado 5. Se añade el idioma a la lista de
idiomas hablados del usuario 6. Muestra una mensaje de éxito
3.4.9. Añadir idioma en práctica Caso de uso Añadir idioma en práctica Actor Usuario Resumen El usuario añade un idioma a su lista de
idiomas que desea practicar Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la sección de idiomas propios
2. Muestra una lista de idiomas posibles para añadir
3. Selecciona un idioma de la lista 4. Solicita añadir idioma en práctica 5. Se añade el idioma a la lista de
idiomas practicados del usuario 6. Muestra una mensaje de éxito
14
3.4.10. Eliminar idioma hablado Caso de uso Eliminar idioma hablado Actor Usuario Resumen El usuario elimina un idioma de su lista
de idiomas hablados Precondiciones Debe existir algún idioma en la lista de
idiomas hablados del usuario Curso típico de eventos
Usuario Sistema 1. Accede a la sección de idiomas propios
2. Selecciona un idioma de la lista de idiomas hablados del usuario
3. Solicita eliminar el idioma 4. Elimina el idioma de la lista de
idiomas hablados del usuario 5. Muestra una mensaje de éxito
3.4.11. Eliminar idioma practicado Caso de uso Eliminar idioma practicado Actor Usuario Resumen El usuario elimina un idioma de su lista
de idiomas practicados Precondiciones Debe existir algún idioma en la lista de
idiomas practicados del usuario Curso típico de eventos
Usuario Sistema 1. Accede a la sección de idiomas propios
2. Selecciona un idioma de la lista de idiomas practicados del usuario
3. Solicita eliminar el idioma 4. Elimina el idioma de la lista de
idiomas practicados del usuario 5. Muestra una mensaje de éxito
15
3.4.12. Iniciar conversación con otro usuario Caso de uso Iniciar conversación Actor Usuario Resumen El usuario comienza una conversación
en tiempo real con otro usuario Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede al perfil de un usuario 2. Solicita iniciar una conversación 3. Muestra la sección de chat 4. Muestra mensajes antiguos en caso
de existir
3.4.13. Enviar un mensaje Caso de uso Enviar mensaje Actor Usuario Resumen Un usuario envía un mensaje a otro en
una conversación Precondiciones Debe haber una conversación abierta
Curso típico de eventos Usuario Sistema
1. Abre una conversación con otro usuario
2. Escribe un mensaje en la conversación
3. Solicita enviar el mensaje 4. Muestra el mensaje como enviado y
aparece en pantalla a ambos usuarios
16
3.4.14. Mostrar conversaciones abiertas Caso de uso Mostrar conversaciones abiertas Actor Usuario Resumen El usuario solicita visualizar un listado
de conversaciones que tiene abiertas Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la sección de conversaciones
2. Muestra un listado de conversaciones en las que el usuario participa
3.4.15. Organizar un evento Caso de uso Organizar evento Actor Usuario Resumen El usuario crea un nuevo evento a una
hora y en un lugar determinado Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la sección de creación de evento
2. Selecciona un lugar, un título y una fecha para el evento
3. Solicita crear el evento 4. Valida los datos introducidos por el
usuario y crea el evento 5. Muestra el detalle del evento creado
Flujo alternativo 1 4. [La fecha proporcionada por el
usuario es una fecha ya pasada] 5. Muestra un mensaje de error
17
3.4.16. Mostrar eventos cercanos Caso de uso Mostrar eventos cercanos Actor Usuario Resumen Se muestran los eventos aun no
celebrados cercanos a la posición del usuario
Precondiciones Ninguna Curso típico de eventos
Usuario Sistema 1. Accede a la sección de eventos 2. Selecciona un rango de distancia en el que buscar
3. Muestra todos los eventos no celebrados dentro del rango seleccionado
3.4.17. Mostrar evento Caso de uso Mostrar evento Actor Usuario Resumen Visualizar el detalle de un evento Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede al detalle de un evento 2. Se muestran los datos del evento por
pantalla: lugar, fecha, título y número de asistentes
3.4.18. Apuntarse a un evento Caso de uso Apuntarse a un evento Actor Usuario Resumen El usuario se apunta a un evento Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede al detalle de un evento 2. Solicita apuntarse 3. Añade el evento a la lista de eventos a
los que asiste el usuario 4. Muestra la lista de eventos a los que
asiste el usuario
18
3.4.19. Mostrar eventos apuntados Caso de uso Eventos apuntados Actor Usuario Resumen Lista de los eventos a los que el usuario
se ha apuntado Precondiciones Ninguna
Curso típico de eventos Usuario Sistema
1. Accede a la sección de eventos apuntados
2. Muestra una lista de todos los eventos, pasados y futuros, a los que el usuario se ha apuntado
19
3.5. Modelo de datos A partir de los casos de uso, se decidió un modelo de datos para crear la base de datos con la que debe funcionar el sistema. La representación gráfica es la siguiente:
20
3.5.1. Tablas A continuación se definirán las tablas que componen la base de datos y sus columnas. Se ha obviado en todas el campo de identificación única (id). Nombre User Columnas username, email, password Claves foráneas No tiene Descripción Representa al usuario del sistema. Su gestión y creación es
responsabilidad de Django (tecnología de servidor que se ha utilizado y que se detallará más adelante) por lo que existe de forma obligatoria y permite controlar los usuarios y las sesiones.
Nombre Profile Columnas picture, description, genre, born_date Claves foráneas user Descripción Es una ampliación de la tabla User. Como se ha comentado, la
tabla User la proporciona el entorno de Django y ampliarla supone reescribir su código, lo cual no es recomendable ya que podría generar errores. Almacena datos relevantes del usuario como si descripción, género, fecha de nacimiento y la URL de su avatar.
Nombre Language Columnas code, flag, name Claves foráneas No tiene Descripción Almacena todos los idiomas disponibles en el sistema, con un
código internacional, un nombre y la URL de la imagen de la bandera que lo representa. En principio es una tabla únicamente de consulta, no debería ser alterada tras la carga inicial de datos.
Nombre User_language Columnas type Claves foráneas user, language Descripción Es una tabla intermedia que representa los idiomas hablados
y practicados por un usuario. La diferencia entre hablado y practicado se denota por la columna type. Los campos user, language y type deben ser únicos juntos.
21
Nombre Meeting Columnas title, time, position Claves foráneas creator Descripción Almacena los eventos o quedadas propuestos por los
usuarios. En cada evento se guarda su título, fecha de celebración y coordenadas del lugar de encuentro. La clave foránea creator hace referencia al usuario que ha organizado el evento.
Nombre User_attends_meeting Columnas No tiene campos propios Claves foráneas meeting, user Descripción Esta tabla representa las asistencias de los usuarios a los
eventos. Deben ser únicas juntas sus dos claves foráneas: meeting y user.
Nombre Chat Columnas label Claves foráneas user_from, user_to Descripción Almacena las conversaciones abiertas entre dos usuarios del
sistema. Nombre Message Columnas message, timestamp Claves foráneas user, chat Descripción Se utiliza para guardar un registro de los mensajes que envía
cada usuario en una conversación.
22
3.6. Diseño de interfaz Seguidamente se detallan los diseños iniciales de interfaz de usuario, basados en los casos de uso definidos, que se crearon para tener una idea más clara de cómo se iba a implementar la herramienta y una mejor visión de sus funcionalidades.
3.6.1. Registro
Formulario de registro al sistema con los campos requeridos para dar de alta a un usuario.
23
3.6.2. Login
Esta vista muestra la pantalla de acceso al sistema en la que se requieren las credenciales de usuario.
24
3.6.3. Perfil de usuario
Esta vista contiene la información pública de un usuario. Como se ve, en la izquierda se incluye una barra lateral para que actúe a modo de menú. Esto es constante en todas las vistas del sistema.
26
3.6.5. Edición de perfil
Esta vista es similar al detalle del perfil con la particularidad de que los datos, que son los propios del usuario, son editables.
27
3.6.6. Detalle evento
Muestra los datos relativos a un evento concreto. Ofrece la posibilidad de apuntarse a la asistencia del mismo.
28
3.6.7. Listado de eventos
Se muestra una lista de los eventos a los que el usuario está apuntado o ha asistido.
29
3.6.8. Creación de evento
Esta vista muestra un formulario que recoge los datos necesarios para crear un evento. El mapa debe permitir señalar un lugar de celebración para el evento.
30
3.6.9. Eventos cercanos
En esta página se muestran al usuario eventos aun no celebrados que se encuentran cerca de su posición. Debe permitir acceder al detalle de cada uno de los mismos.
32
3.7. Plan de pruebas A continuación se definen los distintos conjuntos de pruebas que se deben realizar en el proyecto así como los puntos importantes a tener en cuanta durante el desarrollo.
3.7.1. Pruebas unitarias Se realizarán a lo largo del desarrollo del back-‐end o servidor. Dentro del framework Django con el que se realizará esta parte, se hará uso del módulo unittest de Python que permite escribir pruebas unitarias sobre la aplicación. Se deberán probar las funcionalidades desarrolladas y hacer énfasis en los servicios ofrecidos por la API, ya que es una pieza fundamental para que el sistema funcione.
3.7.2. Pruebas de integración Se realizarán en la finalización del desarrollo de cada módulo de la aplicación. Deberán probar que la inclusión de un nuevo módulo, ya sea un servicio, una funcionalidad específica, o un API endpoint; no afecta a los anteriores, y todos pueden funcionar e integrarse sin afectar al resto del código.
3.7.3. Pruebas de aceptación Se deben realizar desde el momento en que la aplicación sea usable, aunque no ofrezca el total de sus funcionalidades. Un usuario debe hacer uso de la aplicación en busca de errores y mejoras de la misma.
3.7.4. Puntos críticos Los puntos críticos son aquellas partes de la aplicación que se consideran propensas a producir errores o cuyo desarrollo abarca una cierta cantidad de incertidumbre. En el caso de este proyecto se ha considerado tener en cuenta:
-‐ La gestión de datos: como se detalla más adelante, el motor de base de datos escogido es PostgreSQL, el cual nunca antes había utilizado, y supone una gran incertidumbre a la hora de trabajar en integrar con la aplicación.
-‐ Implementación de búsqueda por geo-‐localización: calcular posiciones cercanas a un punto dentro de un radio mientras se trabaja con numerosas coordenadas puede resultar un procedimiento muy pesado y costoso, por lo que su desarrollo debe cuidarse especialmente.
-‐ Desarrollo de chat: tanto la elección de tecnologías para llevar a cabo este módulo como el problema de arquitectura que plantea, suponen una incertidumbre altísima.
33
3.8. Iteraciones Con las funcionalidades y requisitos definidos, se plantea un plan de iteraciones para realizar el desarrollo del proyecto de forma incremental. Todas las iteraciones de desarrollo incluyen documentar y la realización pruebas. El orden de planificación refleja una aplicación que aumente de forma incremental sus funcionalidades de forma que se pueda ir probando a medida que éstas se añaden. El flujo de trabajo es: primero servidor, después cliente y por último el módulo de chat, ya que no es imprescindible para que el resto del sistema funcione y se podría interpretar como una aplicación aparte. Las iteraciones del proyecto han sido:
-‐ Creación del proyecto en Django -‐ Creación del modelo de datos y configuración de la base de datos -‐ Implementar funciones de usuario: autenticación, registro y salida -‐ API endpoints de Profile -‐ API endpoints de Language -‐ API endpoints de Meeting -‐ API endpoints de People -‐ Creación proyecto front-‐end -‐ Interfaz de registro y acceso -‐ Interfaz general de aplicación e implementar menú global -‐ Interfaz de perfil de usuario -‐ Interfaz de edición de perfil -‐ Interfaz listado de perfiles afines -‐ Interfaz listado y detalle de quedadas -‐ Interfaz creación de quedadas -‐ Interfaz búsqueda de quedadas por geo-‐localización -‐ Implementar módulo de chat en cliente y servidor
34
4. Desarrollo del proyecto
4.1. Conceptos importantes A continuación se detallan una serie de conceptos que son esenciales para comprender el problema de la arquitectura del proyecto y su solución.
4.1.1. API REST El término REST (REpresentational State Transfer) da nombre a un estilo de arquitectura de desarrollo Web apoyado en el estándar HTTP. Define una serie de principios para crear y trabajar la comunicación del cliente con el servidor. Permite crear aplicaciones para cualquier cliente que entienda el protocolo HTTP y es actualmente el modelo predominante en el desarrollo Web. La arquitectura REST describe una serie de puntos:
-‐ Cliente/servidor: es lo que define la arquitectura. Cliente y servidor son dos agentes independientes que aportan la separación entre la lógica del negocio y la lógica de presentación.
-‐ Sin estado: el servidor no mantiene un estado relacionado con el cliente, por lo que cada petición es independiente de las demás. Aunque esta capacidad es muchas veces una limitación, y existen mecanismos para suplirla (cookies).
-‐ Caché: las respuestas que recibe el cliente del servidor se pueden almacenar. Por ello, el cliente puede utilizar una respuesta y apoyarse en ella para una próxima petición.
-‐ Operaciones: se establece una relación entre los métodos del estándar HTTP (POST, GET, PUT, DELETE) y las operaciones CRUD (Create, Read, Update, Delete; Crear, Leer, Actualizar y Eliminar).
-‐ URI y recurso: se entiende un recurso como un elemento de información que tiene asociado un identificador único (URI, Uniform Resource Identifier) que sigue una sintaxis universal e intuitiva.
-‐ XML y JSON: se usa alguno de estos dos formatos para transferir la información.
Por otra parte, el concepto de API (interfaz de programación de aplicaciones) es, según Wikipedia: “el conjunto de subrutinas, funciones y procedimientos que ofrece cierta biblioteca para ser utilizado por otro software como una capa de abstracción”. Podemos concluir que una API REST es una librería de funciones a la que se accede a través del protocolo HTTP mediante URLs en las que se envían los datos de una consulta y se recibe información en formato XML o JSON.
35
4.1.2. Websocket La arquitectura cliente-‐servidor o petición-‐respuesta sobre la que funciona HTTP presenta una serie de inconvenientes en el desarrollo de cierto tipo de aplicaciones. La conexión dirigida por el cliente no permite desarrollar aplicaciones de baja latencia, que necesiten una comunicación casi constante e instantánea entre el cliente y el servidor. Para cubrir esa necesidad se puede hacer uso de websockets. Websocket es una tecnología que permite abrir un canal de comunicación bidireccional entre el cliente y el servidor. El cliente puede enviar mensajes y recibir respuestas controladas por eventos sin tener que consultar al servidor. Esto es: una conexión persistente entre ambas partes en la que se pueden enviar y recibir datos en cualquier momento. Esta tecnología es una evolución necesaria de técnicas como polling y long polling. Long polling es un modelo de aplicación Web en el que se mantiene abierta una petición HTTP por parte del cliente en el servidor a la espera de que el servidor necesite enviar información al cliente. Una vez la envíe, el cliente volverá a hacer una petición de nueva información y quedará a la espera. Polling es una técnica de consulta constante: cada cierta cantidad de tiempo el cliente pregunta al servidor por nueva información. Y éste responde con nueva información en caso de existir o con una respuesta vacía. Ambos casos, sobre todo polling, suponen una gran cantidad de peticiones HTTP, lo cual supone un gasto muchas veces innecesario de recursos, una posible sobrecarga de conexiones y da pie a problemas de seguridad: cada petición es una conexión TCP por lo que hay mayor probabilidad de recibir ataques man-‐in-‐the-‐middle. Websocket soluciona estos problemas: una vez se realiza la conexión entre cliente y servidor, ésta es estable y es más difícil de interceptar. Ésta conexión no es HTTP, solamente lo es el proceso de handshake o negociación. En éste proyecto se ha optado por el uso de la tecnología websocket por las ventajas que ofrece: información push desde el servidor sin necesidad de requerirla, necesita de una sola conexión y su sencilla implementación en el lado del cliente, que está incorporada en el estándar de HTML5. Además, una conexión por websocket requiere un menor tráfico de datos, ya que las cabeceras de los mensajes son mucho menores que las de una petición HTTP, lo cual es muy importante teniendo en cuenta que se espera una cantidad de mensajes elevada.
36
4.2. Arquitectura Al tratarse de un proyecto orientado a la Web, se puede dividir en dos grandes bloques: servidor y cliente. El servidor se encargará de toda la lógica de negocio mientras que el cliente sólo debe interactuar con el servidor y presentar los datos.
Tenemos por un lado un cliente Web que se ejecuta en los navegadores de cualquier dispositivo, ya sea ordenador, móvil o tablet; y éste consulta datos e interacciona con el servidor. En el servidor realmente está incluida toda la lógica de negocio, la exposición de la API REST y la base de datos. En el caso de este proyecto, existe la dificultad de desarrollar un chat o conversación en tiempo real. La necesidad de una conexión no dirigida por el cliente es la que impulsa la idea de utilizar una tecnología como los websockets, que comunicará al cliente con el servidor de forma complementaria a la conexión HTTP que hace uso de la API.
37
Ahora se puede visualizar una estructura más clara de la forma de trabajar con websockets y peticiones HTTP en paralelo. Esta arquitectura es válida, por ejemplo, para el cliente, que se puede abstraer de lo que ocurre en el servidor. Pero, como se verá más adelante, en la implementación del Chat, no es definitiva, ya que la inclusión de websockets añade cierta complejidad a la arquitectura del servidor.
38
4.3. Servidor y API REST
4.3.1. Herramientas Se procede a detallar las herramientas de software que han sido utilizadas para llevar a cabo el desarrollo de la parte de servidor y la API REST: Django es la pieza fundamental del desarrollo del servidor. Es un framework de desarrollo de aplicaciones Web escrito en Python y mantenido por Django Software Corporation. Permite construir aplicaciones escalables respetando el patrón modelo-‐vista-‐controlador. Como otros frameworks de desarrollo Web, permite al programador abstraerse de ciertos aspectos como las conexiones de red o base de datos para centrarse únicamente en el desarrollo del negocio. Django provee un ORM (Object-‐Relational Mapping, mapeo objeto-‐relacional) que permite usar los datos persistentes en la base de datos como objetos tipados en Python. En este proyecto se ha utilizado en su versión 1.9.7, la más reciente al momento de iniciar el proyecto. Django-‐Rest-‐Framework es una librería escrita en Python y pensada para incluirse en proyectos de Django. Esta librería se ha utilizado para desarrollar la API REST. Cabe destacar que Django, al igual que muchos frameworks, está pensado para recibir una petición HTTP y renderizar y devolver una plantilla HTML cuyo contenido se decide en función de la petición. Pero hoy en día esa forma de trabajar es cada vez menos usada por lo pesada que resulta. En cambio, la Web está más orientada a las Single Page Applications (aplicaciones de una sola página): una sola plantilla HTML que contiene la lógica necesaria para interactuar con el usuario, pedir al servidor a través de una API REST los datos que vaya necesitando y repintar la vista cuando los reciba. De esta manera sólo hay una carga inicial de archivos estáticos HTML, CSS y JavaScript. Esta ha sido precisamente la manera de trabajar en el proyecto: una petición inicial a través del sistema de plantillas de Django en la que se devuelve la página y, a partir de ahí, el cliente solo interactúa con el servidor pidiendo o enviando datos a través de la API. La base de datos que se ha utilizado es PostgreSQL, un sistema de gestión de bases de datos relacionales distribuido bajo licencia BSD. El framework Django posee un módulo propio llamado GeoDjango que actúa como API geográfica dentro del modelo de clases de Django. Es decir, permite trabajar con objetos situados en un punto del espacio de manera muy abstracta. Esta funcionalidad ayudaría a resolver el problema de la geolocalización que se ha planteado en los requisitos.
39
El uso de GeoDjango es el que ha impulsado la elección de PostgreSQL como motor de base de datos, ya que el desarrollo de esta librería se hizo basándose en PostGis, una extensión de PostgreSQL que añade soporte para objetos geográficos y permite consultas de localización. Añadir que el desarrollo de esta parte del proyecto se ha realizado con la herramienta PyCharm de JetBrains, diseñada para gestionar el desarrollo de proyectos en Python.
4.3.2. API REST y recursos A partir de las funcionalidades definidas anteriormente y el modelo de datos que conforma el proyecto, se han decidido una serie de recursos que componen la API REST: Profile Maneja operaciones GET y PUT/PATCH. Representa la unión
de las tablas User y Profile. Se hará uso del recurso tanto para recuperar información sobre perfiles como para su edición.
Login, logout, registration
Estos recursos no están directamente asociados a un dato o tabla de la base de datos. Se utilizan para el manejo de sesiones de cada usuario por lo que no representan una entidad única sino que el resultado de su uso puede variar en función del cliente y su estado.
Me Al igual que los anteriores, el uso de este recurso no representa una entidad única y depende del cliente que lo use. Su función es devolver datos de las tablas User y Profile que pertenezcan únicamente al usuario que realiza la petición.
Language Representa las filas de la tabla Language. Son cada una de las lenguas que hay almacenadas en el sistema y trabaja únicamente como recurso de consulta (operaciones GET).
Languages-‐speak, languages-‐practice
Este recurso va asociado a la relación que existe entre User y Language. Es decir, los idiomas que habla y practica cada usuario. A través de este recurso se pueden crear, consultar y eliminar estas relaciones.
Meeting Representa los eventos de la tabla Meeting. Se utiliza este recurso para consultar y crear eventos desde el cliente.
Attendance Son las asistencias de los usuarios a un evento, la tabla intermedia entre Meeting y User.
Chat Este recurso representa las conversaciones o chats entre usuarios y su uso es de consulta.
Related Este recurso tampoco representa una entidad única en la base de datos. Se utiliza para devolver datos de la tabla Profile, que sean los perfiles que el sistema considere afines al usuario y que son variables.
40
La implementación de la API REST se ha complementado con una documentación intuitiva en forma de tablas para hacer uso de los recursos. Esta documentación se ha incluido como un apéndice al final del documento debido a su extensión. La forma de la misma es la siguiente: REQUEST URL /api/1.0/example/ REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION None EXAMPLE {“name”: “example”}
-‐ Request URL: es la dirección o URI sobre la que se realiza la petición. -‐ Request type: el tipo de operación que se puede realizar sobre la URL para
el ejemplo explicado. -‐ Request format: tipo de formato en que se envían y reciben los datos. -‐ Request authentication: tipo de autenticación necesaria para realizar la
petición con éxito. Normalmente se pedirá tener una sesión activa. -‐ Example: un ejemplo de datos a enviar para realizar la petición.
La petición debe devolver una respuesta con un código HTTP y la información requerida en formato JSON: HTTP 201 CREATED {“id”: 1, “name”: “example”}
4.3.3. Implementación de la API A modo de ejemplo práctico, se va a explicar una pequeña parte de la implementación de la API REST, para enseñar la forma de trabajar con Django y Django-‐Rest-‐Framework. Nos vamos a centrar en el recurso Meeting, que hace referencia a los eventos organizados por los usuarios. Lo primero de todo es mostrar cómo se representa este dato en el sistema, en forma de objeto Python a través del ORM que proporciona Django: class Meeting(models.Model):
title = models.CharField(max_length=100, blank=False, null=False)
position = models.PointField(null=False, blank=False)
time = models.DateTimeField(null=False, blank=False)
creator = models.ForeignKey(User, related_name='creator', null=True)
objects = models.GeoManager()
41
Esta definición de la clase Meeting vemos que hereda de Model (una clase provista por el ORM de Django). Esto es lo que lo convierte en un objeto mapeable para el ORM que tiene que cumplir una serie de características en sus atributos. La sintaxis es muy sencilla y se identifican fácilmente los campos que componen la tabla: title, position, time y creator. El atributo objects no forma parte de la tabla y no se suele usar si no es necesario: está diciéndole al manejador del ORM que es un objeto que contiene atributos de geolocalización y que, por tanto, va a tener un comportamiento especial. Vista la definición de Meeting, ahora podemos entender mejor cómo se ha construido la API. Se debe definir una vista de Django (que es realmente un controlador) que represente el endpoint que queremos construir en torno a un recurso, de la siguiente manera: class MeetingViewSet(MultipleSerializersViewSet, RetrieveModelMixin,
CreateModelMixin, ListModelMixin, DestroyModelMixin):
queryset = Meeting.objects.all()
serializer_class = MeetingSerializer
permission_classes = (IsAuthenticated, MeetingPermission)
Esta clase representa ese endpoint. Haciendo caso primero a las clases de las que hereda:
-‐ MultipleSerializerViewSet: es una clase propia que realmente hereda de GenericViewSet, la cual es proporcionada por Django-‐Rest-‐Framework y que provee el comportamiento base necesario para construir un ViewSet, o una vista (controlador de Django) orientada a API.
-‐ RetrieveModelMixin, CreateModelMixin, ListModelMixin, DestroyModelMixin: estas clases sirven para implementar los métodos que se van a permitir en la API de forma funcional. En este caso son creación, detalle, listado y eliminación. Proveen los métodos create, retrieve, list y destroy que, si es estrictamente necesario, se pueden sobrescribir para cambiar su comportamiento por defecto, como veremos más adelante.
Siguiendo con los atributos de la clase, queryset hace referencia a la clase/modelo que se ha definido anteriormente (Meeting), relacionando con qué objetos del ORM se va a trabajar en este endpoint.
42
En serializer_class se establece qué clase se debe utilizar para serializar los objetos. En ésta clase se define la transformación del objeto JSON a Python o viceversa. También se especifica la validación y los campos que se deben incluir. El serializador es el responsable de convertir los datos entrantes a un formato acorde con el ORM y de validar los datos de acuerdo a las restricciones del modelo. Como ejemplo, ya que el serializador de Meeting es más complejo de lo habitual y no resultaría didáctico, vamos a ver un serializador sencillo encargado de los objetos User: class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'username') En esta implementación se observa que se trata de una clase que hereda de ModelSerializer y en la que se especifica un modelo: User. Con estos datos, no hace falta definir más, ya que ModelSerializer permite que se mapeen los campos entrantes, en función de su nombre, con los atributos de la tabla o modelo User, y validarlo en función de las restricciones con que se haya definido. Además se especifica qué campos deben ir incluidos para el uso que se le de (en este caso es de solo lectura, lo que significa que devolvería un JSON con únicamente los campos id, email y username). El último aspecto de la vista a explicar es el atributo permission_classes, el cual hace referencia a en qué clases se debe delegar el manejo de permisos para hacer uso del endpoint que se está definiendo. La primera, IsAuthenticated, es propia de la librería y sirve apara asegurarse que el usuario que hace la petición mantiene una sesión activa en el sistema. Veamos la otra clase, que es propia de este sistema: class MeetingPermission(CustomActionPermissions):
def has_object_permission(self, request, view, obj):
if request.user.is_superuser:
return True
if view.action == 'destroy':
return obj.creator == request.user
return True En esta clase se definen los permisos de tal manera que cualquier acción está permitida en caso de que el usuario sea administrador del sistema. Y cualquier operación está admitida a excepción de la de eliminación: sólo se puede eliminar un recurso (en este caso un evento o Meeting) si el usuario que realiza la petición es el creador de ese objeto.
43
Por último, mostrar un de los métodos del API Endpoint que se está construyendo: el de crear. Como ya se ha explicado, no suele ser necesario sobrescribir estos métodos, pero en este caso se ha necesitado debido al distinto comportamiento que tienen los objetos con atributos de geo-‐localización. En cuanto al método en sí, no aporta ninguna lógica extra al comportamiento de la creación, por lo que sirve bien de ejemplo para ver cómo funciona: def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
meeting = Meeting.objects.create(
title=serializer.validated_data.get('title'),
position=serializer.validated_data.get('position'),
time=serializer.validated_data.get('time'), creator=request.user)
serializer = MeetingSerializer(meeting)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers) El curso de acciones es: se recuperan los datos enviados por el cliente y se delegan en el serializador correspondiente; se comprueba que sean válidos y se espera una excepción en caso contrario; se instancia un objeto Meeting haciendo uso del ORM que lo almacena en la base de datos (Meeting.objects.create). Se construye un serializador con los datos del objeto creado; se definen las cabeceras de respuesta y, por último, se devuelve una respuesta HTTP con cabeceras de éxito y los datos del serializador que se ha construido. Con todo lo anterior se puede dar casi por finalizada la construcción de un API Endpoint. Sólo falta unir las piezas: asignar una URL que va a ser pública. En un archivo api_urls.py se define un Router que registra el recurso que se ha creado: router = SimpleRouter()
router.register(r'meetings', MeetingViewSet, base_name='meetings')
urlpatterns = router.urls
44
Por último, en el archivo urls.py del proyecto, que es donde se definen las URLs que Django debe servir, incluimos: from meeting import api_urls as meeting_api_urls
urlpatterns = [ url(r'^api/1.0/', include(meeting_api_urls)), ]
Con lo que ya es usable el endpoint, realizando peticiones a la URL: host/api/1.0/meetings/
4.3.4. Pruebas Para la implementación de la API REST se han ido realizando pruebas de forma incremental a medida que se implementaban funcionalidades. Los tests se han centrado en probar todos los aspectos de cada API Enpoint: permisos, autenticación, datos mal enviados, datos inválidos, persistencia de las acciones… El módulo unittest de Python permite la realización de tests unitarios en una aplicación mediante la instanciación de clases TestCase y métodos de la clase a modo de test. Un ejemplo de implementación de estas pruebas es el siguiente: class TestMeetingCreationAPI(TestCase):
urls = 'meeting.api_urls'
def test_user_not_authenticated(self):
self.create_user(username='username', password='password')
client = APIClient()
time = datetime.datetime.now() + datetime.timedelta(days=5)
data = {"title": "Meeting", "position": "POINT(40.383333 -‐3.716667)",
"time": time.strftime("%Y-‐%m-‐%dT%H:%M")}
response = client.post("/meetings/", data, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) El test recrea una petición de tipo POST al recurso Meeting, pero lo hace mediante un cliente no autenticado, por lo que espera una respuesta HTTP de código 403 (prohibido).
45
4.4. Servidor de chat En esta sección se trata de explicar cómo se ha solucionado la implementación de un chat en tiempo real y cómo ha afectado a la arquitectura del sistema.
4.4.1. Django Channels La mayoría de los frameworks de desarrollo Web, entre ellos Django, están construidos en torno al paradigma de la filosofía HTTP: cliente pide – servidor responde. Esto plantea un claro problema a la hora de desarrollar aplicaciones complejas que necesiten de una baja latencia o de una comunicación no dirigida por el cliente. Channels es un módulo desarrollado para Django que trata de solucionar ese problema: provee un marco de trabajo en el que se pueden gestionar conexiones con Websockets y HTTP2 (conexiones permanentes) así como el manejo de tareas asíncronas. Esto se suma al manejo de HTTP que siempre ha ofrecido Django y cuyo comportamiento no se ve variado con la inclusión de Channels. Algunos de los conceptos que se manejan en Channels y que son clave para entender su implementación son: Producer (productor): son eventos que deben ser escuchados y que transportan un mensaje. Estos eventos suelen ser una conexión desde un Websocket, un mensaje desde esa conexión o una desconexión. Consumer (consumidor): se trata de un proceso o función en Python pensada para recibir un mensaje y ejecutarse de forma asíncrona, en segundo plano, en el sistema. Channel (canal): es en esencia una cola de tareas. Escucha mensajes enviados por los producers y delega su acción en el proceso llamado consumer. La capa de canales, o channel layer es el mecanismo de transporte que Channels usa para pasar los mensajes de los producers a los consumers, es decir, la que maneja los channels. Esta capa está pensada para ser usada con Redis como base de datos. Group (grupo) es una clase introducida por Channels para manejar las respuestas múltiples. Los canales solo entregan mensajes a un único destinatario que está escuchando, por lo que intentar hacer broadcast implicaría enviar el mismo mensaje a numerosos destinatarios. Este problema se suple con el uso de grupos: un usuario se suscribe a un grupo y cuando se envíe un mensaje desde ese grupo, todos los usuarios suscritos lo recibirán. Lo que está haciendo Channels por debajo es simplemente evitar al desarrollador iterar sobre todos esos usuarios para enviar un mismo mensaje.
46
4.4.2. Implementación En este proyecto se ha trabajado con cuatro producers o eventos a los que escucha la channel layer, definidos en un archivo de enrutamiento routing.py: channel_routing = {
'http.request': StaticFilesConsumer(),
'websocket.connect': consumers.ws_connect,
'websocket.receive': consumers.ws_receive,
'websocket.disconnect': consumers.ws_disconnect, } El primero es el que delega en un consumer las peticiones HTTP básicas. Funcionará de forma síncrona y en un proceso aparte, para mantener el comportamiento básico de Django. Los otros tres se refieren a mensajes provenientes de websockets: delegan la conexión, desconexión y envío de mensajes en distintos procesos que serán los consumers. La forma en que se configura Django para habilitar esta channel layer es la siguiente, dentro del archivo settings.py que contiene todas las configuraciones necesarias para un proyecto: CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": {
"hosts": ["redis://localhost:6379"], },
"ROUTING": "lingvo.routing.channel_routing", },
} Como se aprecia, esta capa va asociada al enrutamiento anteriormente descrito y a una base de datos Redis. Redis es un motor de bases de datos, que guarda en memoria estructuras de datos en forma de clave-‐valor y que es usada en esta capa a modo de cola de tareas, según los mensajes vayan llegando.
47
Veamos la implementación de uno de los consumers, concretamente el que recibe un mensaje de una sala de chat: @channel_session_user def ws_receive(message): label = message.channel_session['room'] chat = Chat.objects.get(label=label) data = json.loads(message['text']) if data: Message.objects.create(chat=chat, =data['message'],
user=message.user) Group('chat-' + label, channel_layer=message.channel_)
.send({"text": str(message.user.id) + ": " + data['message']})
Este consumer, que es una función de Python, tiene una anotación (@channel_session_user) que evita que se ejecute en caso de que el usuario que envía el mensaje no esté autenticado. Recibe un mensaje con los datos de la sala (Chat) en la que participa el usuario y el mensaje que ha enviado. Mediante Message.objects.create() se añade el mensaje a la base de datos, para tener un historial. Por último, se accede al grupo asociado a esa conversación y se emite, mediante el método send(), el mensaje enviado a todos los usuarios suscritos al grupo, es decir, los participantes de la conversación.
4.4.3. Arquitectura final Una vez explicados los conceptos básicos de Django Channels y cómo se trabaja con ellos, es necesario exponer cómo afecta esta forma de trabajar a la arquitectura y por qué se ha cambiado. La forma de funcionar que ha tenido Django siempre ha sido orientada a la filosofía HTTP: recibe una petición, la resuelve en tiempo de ejecución y devuelve una respuesta HTTP, como se ve en la imagen:
48
Recuperado de: https://blog.heroku.com
De esta manera la lógica del negocio queda delegada en funciones view y es el proceso de Django corriendo en el servidor (proceso runserver) el que maneja los protocolos de petición y respuesta. La inclusión de Django Channels cambia esta vista:
49
Recuperado de: https://blog.heroku.com
El esquema ahora se ha ampliado para poder separar responsabilidades. Echando un vistazo al rectángulo inferior, para hacer más comprensible el resto: ahora todos los procesos se ejecutan como workers, de modo que cada uno tiene una responsabilidad única. En el esquema vemos que hay un proceso orientado a manejar las peticiones HTTP (que cumple el comportamiento básico de Django), otro para manejar los mensajes de los websockets, y otros relacionados con procesos en segundo plano. Una vez aclarada esta separación de responsabilidades, veamos el funcionamiento de arriba abajo: Desde un navegador Web o browser se envía una petición al servidor. Ésta puede ser a través de HTTP o con una conexión iniciada por un websocket. Por ello la
50
necesidad de un interface server (servidor de interfaz). Este servidor es realmente un proceso encargado de transformar cualquier tipo de conexión entrante en mensajes que van a los canales y despacharla según su origen. La capa de canales o channel layer es la encargada de comunicar el servidor de interfaz con los workers, haciendo uso de Redis para almacenar los mensajes encolados. Por último, los workers escuchan los mensajes que les delega la capa de canales para ejecutar los consumers correspondientes cuando reciben el mensaje. Evidentemente toda esta comunicación es bidireccional por lo que la conexión de vuelta al cliente recae finalmente en el servidor de interfaz. Esto afecta a la arquitectura del proyecto y debe ser adaptada a esta filosofía. En primer lugar, se necesita dentro del servidor un proceso que haga de servidor de interfaces. Para ello se utiliza Daphne, un servidor automático de protocolo desarrollado precisamente para Channels . Para iniciar el proceso:
daphne lingvo.asgi:channel_layer --port 8888
También es necesario tener en el servidor una base de datos de Redis corriendo, independientemente de PostgreSQL que también debe estar en funcionamiento: redis-‐server En tercer lugar, un proceso que haga de worker. Es posible instanciar varios workers para separar procesos en caso de necesitar mayor rendimiento pero en el caso de este proyecto no ha sido necesario: python manage.py runworker--settings=lingvo.settings Por último se ha instanciado un proceso de la siguiente manera:
python manage.py runserver --noworker --settings=lingvo.settings
El proceso runserver es el que se utiliza en Django por defecto para correr el servidor. Como se ha explicado, ahora esa responsabilidad recae en los workers, pero debido a la importancia de runserver, es recomendable delegarlo a un proceso aparte dedicado exclusivamente a resolver las peticiones que llegan por HTTP. Esto se especifica con el argumento –noworker. Con todo lo anterior, la arquitectura final del proyecto queda reflejada de esta manera:
52
4.5. Cliente Web Además de la parte de servidor, en este proyecto se ha desarrollado una aplicación Web que hace uso del mismo y orientada a ser usada en navegadores. Se ha desarrollado como una Single Page Application, es decir, una aplicación de una sola página que contiene cierta lógica de presentación y que solo contacta con el servidor para pedir o enviar datos, pero no para cargar archivos estáticos como HTML, CSS o JavaScript después de la carga inicial.
4.5.1. Herramientas
4.5.1.1. Herramientas básicas Como toda página o aplicación Web, se han usado las tecnologías básicas para este tipo de desarrollo: HTML, CSS y JavaScript.
4.5.1.2. AngularJS Para conseguir una Single Page Application se ha usado el framework de JavaScript AngularJS (versión 1.5.6). Es un framework de código abierto mantenido por Google que provee un entorno de trabajo orientado al uso del Modelo-‐Vista-‐Controlador (se puede considerar de tipo Modelo/Vista/Vista-‐Modelo ya que incorpora el enlace de datos automático o data binding). AngularJS consigue disociar la manipulación del DOM de HTML de la lógica de la aplicación, lo cual es un concepto relativamente novedoso en el desarrollo Web. Para entender cómo funciona AngularJS hay que explicar las piezas que lo componen:
-‐ Vista: es lo que ve el usuario, el DOM de HTML. -‐ Controlador: contiene la lógica de negocio que hay detrás de las vistas. Son
archivos JavaScript. -‐ Modelo: los datos con los que se trabaja, que se muestran en la vista y con
los que el usuario interactúa. -‐ Directiva: un HTML extendido con atributos personalizables. Una directiva
tiene su propio controlador y aporta la ventaja de ser reutilizable desde cualquier vista.
-‐ Scope: se trata de un contexto en el que los controladores, las directivas y las vistas pueden acceder al modelo. Éste contexto es el que provee el data binding.
-‐ Servicio: lógica de negocio reutilizable independiente de las vistas.
53
AngularJS permite la inyección de otras aplicaciones en la aplicación que se desarrolla, a modo de librerías.
4.5.1.3. Gestión de dependencias Para gestionar estas librerías o aplicaciones se ha hecho uso de Bower, un gestor de dependencias para aplicaciones de cliente Web que depende de npm (un gestor de paquetes JavaScript desarrollado para Node.js) Bower permite instalar paquetes alojados en Internet a través de la línea de comandos: bower install angular –-‐save Esto da como resultado la descarga del paquete llamado angular, guardarlo en una carpeta llamada bower_components y referenciar esa dependencia en un archivo de configuración bower.json, que tendría esta forma: {
"name": "lingvo",
"version": "0.0.0",
"authors": ["Jorge Rabanos"],
"license": "Copyright",
"dependencies": {"angular": "~1.5.6", "bootstrap": "~3.3.6" }
} Realmente no se acaba de gestionar la dependencia en el proyecto sino en el entorno de trabajo, y es responsabilidad del desarrollador importar o hacer uso de los archivos ahora alojados en la carpeta bower_components.
4.5.1.4. Grunt Otra herramienta que ha conformado el entorno de trabajo es Grunt. Se trata de un automatizador de tareas para JavaScript que permite al desarrollador ahorrarse repetir ciertas tareas que son imprescindibles para el desarrollo pero que resultan monótonas. A través de un archivo de configuración llamado Gruntfile.js permite establecer ciertas actividades para que sean ejecutadas constantemente o en función de una acción. En este proyecto Grunt se ha usado para varios propósitos: El primero de ellos es el de gestionar todas las hojas de JavaScript. El hecho de que AngularJS permita la separación de Modelo-‐Vista-‐Controlador provoca que se acaben creando numerosos archivos.
54
En un ejemplo reducido: app: {
src: [
// Libraries
'bower_components/angular/angular.min.js',
'bower_components/angular-‐route/angular-‐route.min.js',
// Application scripts
'static/js/app.js',
'static/js/services/*.js',
'static/js/factories/*.js',
'static/js/controllers/*.js',
'static/js/directives/*.js',
'static/js/filters/*.js'
],
dest: 'static/built/app.js'
} Esto se interpreta como que todos los archivos que hay en la lista de src que, en este ejemplo son dos librerías instaladas con bower y numerosos archivos del proyecto (el asterisco sirve para indicar que cualquier nombre de archivo es válido y se debe incluir); deben componer un archivo final alojado en la dirección indicada en dest. Este archivo se va a crear a partir de la concatenación de todos los anteriores. Cada vez que se introduzca un cambio en uno de esos archivos, Grunt lo va a identificar y va a volver a generar el archivo destino, por lo que la ventaja que ofrece es muy notable. Otro uso de Grunt es el de minificar un archivo. Minificar se refiere a la eliminación de bytes innecesarios (espacios, saltos de línea, sangrías…) en incluso cambiar nombres de variables por otros más cortos. Esto se hace con el fin de disminuir el tamaño de los archivos y por tanto reducir el tiempo de carga: uglify: {
built: {
files: {
'static/built/app.min.js': ['static/built/app.js']
}
},
} Con esta configuración se consigue que cada vez que el archivo app.js sea modificado, se cree un archivo app.min.js con el mismo contenido pero minificado.
55
4.5.1.5. Angular Material También se ha hecho uso de Angular-‐Material 1.0.9. Angular Material es un framework de interfaz de usuario desarrollado para funcionar como librería de AngularJS. Está desarrollado por Google y sigue la especificación de principios de diseño de Material Design, también de Google. Proporciona herramientas para construir un sistema visual interactivo y uniforme. Está orientado al diseño adaptativo, cuyo fin es adaptar la apariencia de las páginas Web al dispositivo que se esté utilizando para visualizarla
4.5.2. Estructura El cliente está dividido en dos partes: registro/autenticación y aplicación. La página de registro o autenticación es pública y, si se intenta acceder directamente a la aplicación si haber iniciado sesión, se produce una redirección automática a la página de autenticación (login). Esta redirección y la comprobación de autenticación se realiza desde el servidor mediante el sistema de gestión de URLs de Django. El hecho de separar el cliente en dos páginas se debe a dos razones: la primera es por peso. El acceso sólo a la página de registro es mucho más liviano ya que contiene pocos scripts, y ahorra una carga más pesada para un usuario que quizás no quiera acceder a la aplicación. Por otro lado está el tema de la seguridad: a pesar de que la API está protegida con autenticación y uso de sesión, es mejor no dar información innecesaria a un posible atacante. La parte de la aplicación está organizada de la siguiente manera: En el nivel raíz, tres carpetas:
-‐ Styles: contiene los archivos de estilo CSS. -‐ Templates: se encuentran archivos HTML, que en AngularJS son realmente
porciones de vistas asociadas a un controlador con lógica de presentación propia.
-‐ JS: en esta carpeta se encuentra la aplicación (llamada app.js) y varias carpetas con sus componentes: controllers, directives y services.
4.5.3. Implementación de la interfaz
4.5.3.1. Comunicación con el servidor Para hacer peticiones a través de la API REST se ha usado un servicio nativo de Angular denominado $http. Éste facilita la comunicación con servidores HTTP a través del objeto XMLHttpRequest, es decir, para realizar llamadas AJAX.
56
AJAX (Asynchronous JavaScript And XML) es una técnica de desarrollo Web que permite realizar peticiones de manera asíncrona y en segundo plano, por lo que se pueden actualizar los datos de una página sin necesidad de recargarla. Éste servicio devuelve una respuesta asíncrona, una función que se ejecutará una vez se haya resuelto la petición. Veamos un ejemplo: $http.post(url_login, data)
.success(function (data, status, headers, config) {window.location = "/";})
.error(function (data, status, headers, config) {$scope.errors = 'Incorrect
data'; }); Se realiza una petición POST a una URL y con unos datos especificados y, una vez resuelta (cuando se reciba la respuesta), se ejecutará la función contenida en success en caso de éxito o la contenida en error en caso de fallo.
57
4.5.3.2. Registro y autenticación Como se ha explicado anteriormente, la parte de registro y autenticación es una página aparte de la aplicación. Provee de los formularios necesarios para que un usuario se de de alta en el sistema y para que se autentique, así como un control de errores.
4.5.3.3. Main Ya en la aplicación, existe una vista principal que engloba a todas las demás. Esta vista Main provee de un menú con enlaces, común a todas las interfaces, y que sirve de navegador entre las diferentes vistas que contiene: people, my profile, search meetings, my meetings y opened chats (gente, mi perfil, buscar quedadas, mis quedadas y conversaciones abiertas).
4.5.3.4. People Esta vista presenta una lista de usuarios afines al usuario que la visita en función de sus idiomas. Se muestran algunos de los idiomas que habla y practica cada usuario de la lista.
58
4.5.3.4. Profiles Estas vistas son similares y las componen tanto el detalle de un perfil de usuario como la edición de perfil.
4.5.3.5. Meetings Las vistas relacionadas con las quedadas necesitan hacer uso de mapas. Se han desarrollado haciendo uso de la API pública de Google Maps.
59
Además, se ha hecho uso de la librería Ng-‐map, que proporciona una directiva de AngularJS para instanciar mapas de Google Maps en las vistas. Veamos un ejemplo de cómo se han usado los mapas en la creación de quedadas, que necesita de una posición específica en el mapa para ser creada: Dentro del controlador correspondiente a la vista de creación, tenemos el siguiente código: NgMap.getMap().then(function (map) {
this.map = map;
});
$scope.placeMarker = function (e) {
if (marker != null) {
marker.setMap(null);
}
marker = new google.maps.Marker({position: e.latLng, map: vm.map});
$scope.lat = marker.getPosition().lat();
$scope.lng = marker.getPosition().lng();
vm.map.panTo(e.latLng);
}
}); Éste código ha instanciado un nuevo mapa por medio de la API de la librería Ng-‐map y lo ha asignado al contexto actual (this.map = map). También ha incluido un método que se ejecuta en función de un evento: cada vez que se haga clic sobre el mapa, posicionará un marcador en las coordenadas correspondientes al punto clicado, además de guardar el valor de esas coordenadas ($scope.lat y $scope.lng) para su futuro uso. Por otro lado, se hace uso de la directiva correspondiente al mapa en HTML: <ng-‐map zoom="10" center=" 48.1321286, 11.5946726" on-‐click="placeMarker()">
</ng-‐map> Este código instanciará un mapa con centro en las coordenadas indicadas, que son editables, y con un evento on-‐click que llama a la función que se ha definido anteriormente.
60
El resultado:
Por último, en la parte de quedadas hay que destacar la búsqueda de eventos cercanos. Para realizar esta búsqueda es necesaria la localización actual del usuario. Para ello se hace uso de HTML5 Geolocation, una API de JavaScript usada para precisamente obtener la posición geográfica del cliente. Debido a que puede comprometer la privacidad del usuario, esta API se activa bajo aprobación del usuario. Su uso:
navigator.geolocation.getCurrentPosition(location) De esta manera se representa la lista de quedadas cercanas a un usuario en el mapa dentro de un radio editable que, por defecto, es de dos kilómetros:
61
3.5.3.6. Chat La implementación del chat es una interfaz básica en la que se puede leer el historial de mensajes de arriba abajo diferenciando al usuario que lo envía.
62
3.5.4. Websockets / Chat Como ya se ha explicado, websocket es una tecnología que hace posible una conexión continua entre el cliente y el servidor basada en el protocolo ws. La API de websockets está disponible para el código JavaScript y funcional en navegadores como Firefox, Chrome y Safari. Para hacer uso de ella se debe instanciar un objeto de la clase WebSocket, y especificar una dirección de conexión. Esta dirección deberá llevar el prefijo “ws:” (o “wss:” para conexiones cifradas) el cual es un nuevo esquema de URI definido en la especificación del protocolo WebSocket. En este proyecto, en el servidor, se habilitó una URI específica para recibir una petición de conexión mediante WebSocket. Creando una conexión a una dirección de la forma:
ws:host/chat/{ idUsuario } se habilitará un canal de comunicación entre el usuario que crea la conexión y el servidor, referente a una conversación entre tal usuario y el identificado por el argumento idUsuario.
63
Un proceso referente a la conexión se encargará de que los mensajes enviados sean guardados y que cualquiera de los dos usuarios que tenga abierto un canal WebSocket los reciba sin tener que pedirlos. Para abrir una conexión por lo tanto:
var socket = new WebSocket('ws://'
+ window.location.host + "/chat/" + $routeParams.id + "/"); Seguidamente hay que asociar al socket un evento que escuche los mensajes recibidos: socket.onmessage = function (message) {
var args = message.data.split(":");
var style = args[0] == $routeParams.id;
var item = {"text": args[1], "style": style};
$scope.messages.push(item);
var element = document.getElementById("chatListId");
$(element).scrollTop(parseInt($(element)[0].scrollHeight) + 200);
$scope.$apply();
}; Obviando el código en gris, por no entrar en detalles de la implementación, se observa que el mensaje que se recibe como argumento se guarda en un array ($scope.messages). Este array está presentado en la vista como una lista de mensajes. Al ser un proceso en segundo plano, que es posible que se esté ejecutando en una ventana o pestaña inactiva, es posible que esa adición de datos no se vea representada en la interfaz hasta que la ventana sea activa. Esto es un comportamiento propio de AngularJS y la última sentencia ($scope.$apply()) sirve precisamente para obligar a que se aplique inmediatamente; con un coste adicional de memoria, evidentemente. De esta manera se evita también que se pierdan datos, ya que las conexiones WebSocket no garantizan el envío de todos los mensajes y encolarlos podría provocar la pérdida de alguno. Por último, el envío de datos desde JavaScript se realiza así: socket.send(JSON.stringify({message: $scope.message})); Y, con esta base, la aplicación soporta conversaciones en tiempo real de forma totalmente funcional a través de una API nativa.
64
5. Conclusiones Este proyecto se ha desarrollado en torno a una idea personal con el objetivo tanto de realizar un seguimiento sobre el desarrollo de un software como de aprender a usar nuevas tecnologías. La variedad de herramientas utilizadas ha sido bastante amplia. Algunas de las tecnologías utilizadas ya las conocía en mayor o menor medida, pero otras han sido totalmente nuevas para mí y han resultado muy didácticas: Django-‐Channels, Websockets, Angular-‐Material y algunas librerías de AngularJS. La idea inicial era desarrollar tanto la parte de cliente como la de servidor, y un módulo acoplable para integrar un chat. La parte de servidor se debía desarrollar siguiendo la filosofía de API REST. Estos objetivos iniciales se han cumplido con éxito. Respecto al uso de Django-‐Channels y Websockets, creo que es un paso muy importante para el framework el hecho de incluir esta forma de trabajar. El desarrollo Web avanza muy deprisa y el concepto de que cada página o vista sea una petición distinta se está quedando atrás, por lo pesado que resulta y lo lento que se percibe. No sólo Django, la mayoría de los frameworks actuales de desarrollo Web trabajan de esta manera y deberían ir orientando su desarrollo al ofrecimiento de servicios a Single Page Applications mediante tecnologías como Websockets o el nuevo protocolo HTTP/2. En definitiva, el proyecto se ha realizado de forma satisfactoria cumpliendo las expectativas iniciales, con una aplicación totalmente funcional.
65
6. Futuras ampliaciones Como ampliación para esta aplicación se propone extender el módulo de conversaciones en tiempo real para que sea capaz de soportar más de dos usuarios en el mismo chat. También se propone la inclusión de conversaciones a través de vídeo mediante WebRTC (Web Real Time Communications), un proyecto abierto, iniciativa de Mozilla, Google y Opera, que hace posible la comunicación de audio y vídeo en tiempo real en navegadores Web a través de una API de JavaScript. Por último se propone aplicar el estilo Progressive Web App a esta aplicación. Esto implica adaptarla de manera que pueda funcionar como una aplicación de escritorio en smartphones Android y iOS, realizando tareas en segundo plano y declarando un archivo de manifiesto.
66
7. Bibliografía
-‐ BAUMGARTNER, Peter y MALET, Yann. High Performance Django.
Createspace, 2015.
-‐ FREEMAN, Adam. Pro AngularJS. Apress, 2014.
-‐ GODWIN, Andrew. Django Channels [librería]. Python. Disponible en
https://channels.readthedocs.io (última consulta 06/2016).
-‐ GREENFELD, Daniel y ROY, Audrey. Two Scoops of Django: Best Practices
for Django. Two Scoops Press, 2015.
-‐ HOLOVATY Adrian y KAPLAN-MOSS, Jacob. The Django Book. Apress,
Diciembre 2007.
-‐ KAPLAN-‐MOSS, Jacob. Finally, Real-‐Time Django Is Here: Get Started
with Django Channels. Disponible en https://blog.heroku.com (última
consulta 06/2016).
-‐ LUTZ Mark. Learning Python. O’Reilly Media, Julio 2013.
-‐ MARTIN, Robert C. Clean code: A Handbook of Agile Software
Craftmanship. Prentice Hall 2008.
-‐ MOZILLA DEVELOPER NETWORK. WebSockets. Disponible en:
https://developer.mozilla.org/ (última consulta 06/2016).
-‐ PERCIVAL, Harry. Test-Driven Development with Python. O’Reilly, 2014.
-‐ TIVIX INC. Django-‐Rest-‐Auth [librería]. Python. Disponible en
https://django-‐rest-‐auth.readthedocs.io (última consulta 06/2016).
-‐ WIKIPEDIA. Interfaz de Programación de Aplicaciones. Disponible en:
http://es.wikipedia.org/wiki/Interfaz_de_programacion_de_aplicaciones (última
consulta 06/2016).
67
8. Apéndice
8.1. Especificación API REST
8.1.1. Login REQUEST URL /rest-‐auth/login/ REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION None EXAMPLE {
“username”: USERNAME, “email”: EMAIL, “password”: PASSWORD }
Donde USERNAME es el nombre usuario en el sistema, EMAIL su correo electrónico y PASSWORD su contraseña de acceso. Respuesta esperada: HTTP 201 CREATED {“key”: KEY} Siendo KEY la clave de sesión.
8.1.2. Logout REQUEST URL /rest-‐auth/logout/ REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {“key”: KEY} KEY es la clave de sesión recibida en el login. Respuesta esperada: HTTP 200 OK { "success": "Successfully logged out."}
68
8.1.3. Registration REQUEST URL /rest-‐auth/registration/ REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION None EXAMPLE {
“username”: USERNAME, “email”: EMAIL, “password1”: PASSWORD, “password2”: PASSWORD }
El password se debe enviar dos veces para evitar errores del usuario. Respuesta esperada: HTTP 201 CREATED {“key”: KEY} KEY es la clave de sesión.
69
8.1.4. Profile Recuperar datos de un perfil: REQUEST URL /api/1.0/profiles/ID REQUEST TYPE GET REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {} ID es el identificador único del perfil que se desea recuperar Respuesta esperada: HTTP 200 OK { "description": "Hello!", "genre": "MSC", "born_date": "12/11/1991", "user": { "id": 6, "email": "[email protected]", "username": "Morla" }, "speaks": [ ... ], "practices": [ ... ], "picture": "pictures/homer.jpg", "id": 7 } Actualizar perfil: REQUEST URL /api/1.0/profiles/ID REQUEST TYPE PATCH / PUT REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {
"description": "Hello!", "genre": "MSC", "born_date": "12/11/1991" }
ID es el identificador único del perfil que se desea recuperar. Respuesta esperada:
70
HTTP 200 OK { "description": "Hello!", "genre": "MSC", "born_date": "12/11/1991", "user": { "id": 6, "email": "[email protected]", "username": "Morla" }, "speaks": [ ... ], "practices": [ ... ], "picture": "pictures/homer.jpg", "id": 7 } Obtener perfil propio: REQUEST URL /api/1.0/users/me/ REQUEST TYPE GET REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {} Respuesta esperada: HTTP 200 OK { "description": "Hello!", "genre": "MSC", "born_date": "12/11/1991", "user": { "id": 6, "email": "[email protected]", "username": "Morla" }, "speaks": [ ... ], "practices": [ ... ], "picture": "pictures/homer.jpg", "id": 7 }
71
8.1.5. Language Listado de idiomas: REQUEST URL /api/1.0/languages/ REQUEST TYPE GET REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE { } Respuesta esperada: HTTP 200 OK [ { "id": 48, "flag": "flags/frFlag.png", "code": "fr", "name": "French" }, { "id": 38, "flag": "flags/enFlag.png", "code": "en", "name": "English" }, … ] Añadir idioma hablado: REQUEST URL /api/1.0/languages/speak/ REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {"user":USER_ID,"language":LANGUAGE_ID} USER_ID es el identificador del usuario, y LANGUAGE_ID el del idioma que se desea añadir. Respuesta esperada: HTTP 201 CREATED { "id": 5024, "user": 112, "language": 1 }
72
Eliminar idioma hablado: REQUEST URL /api/1.0/languages/speak/ID REQUEST TYPE DELETE REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {"user":USER_ID,"language":LANGUAGE_ID} ID es el identificador de la instancia de idioma hablado. USER_ID es el identificador del usuario, y LANGUAGE_ID el del idioma que se desea añadir. Respuesta esperada: HTTP 204 NO CONTENT Añadir idioma practicado: REQUEST URL /api/1.0/languages/practice/ REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {"user":USER_ID,"language":LANGUAGE_ID} USER_ID es el identificador del usuario, y LANGUAGE_ID el del idioma que se desea añadir. Respuesta esperada: HTTP 201 CREATED { "id": 5024, "user": 112, "language": 1 } Eliminar idioma practicado: REQUEST URL /api/1.0/languages/practice/ID REQUEST TYPE DELETE REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {"user":USER_ID,"language":LANGUAGE_ID} ID es el identificador de la instancia de idioma hablado. USER_ID es el identificador del usuario, y LANGUAGE_ID el del idioma que se desea añadir. Respuesta esperada: HTTP 204 NO CONTENT
73
8.1.6. Meeting Obtener quedadas cercanas: REQUEST URL /api/1.0/meetings/?distance=DIS&lat=LAT&lon=LON REQUEST TYPE GET REQUEST FORMAT JSON REQUEST AUTHENTICATION
Session
EXAMPLE {} DIS es la distancia en metros del radio de búsqueda de quedadas. LAT y LON son las coordenadas del punto de búsqueda. Respuesta esperada: HTTP 200 OK { "type": "FeatureCollection", "features": [ { "id": 5, "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 40.383333, -‐3.716667 ] }, "properties": { "title": "Evento", "time": "2016-‐09-‐13T18:00:00Z", "creator": 6, "attendances": [], "distance": 0 } }, … ] }
74
Obtener detalle de quedada: REQUEST URL /api/1.0/meetings/ID REQUEST TYPE GET REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {} ID es el identificador del evento. Respuesta esperada: HTTP 200 OK { "id": 26, "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 40.383333, -‐3.716667 ] }, "properties": { "title": "Event title", "time": "2016-‐09-‐12T19:30:00Z", "creator": 112, "attendances": [], "distance": "" } } Crear quedada: REQUEST URL /api/1.0/meetings/ REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {
"title": “Event title”, "position": "POINT(40.383333 -‐3.716667)", "time": “2016-‐09-‐12T19:30:99” }
Respuesta esperada:
75
HTTP 201 CREATED { "id": 26, "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 40.383333, -‐3.716667 ] }, "properties": { "title": "Event title", "time": "2016-‐09-‐12T19:30:00Z", "creator": 112, "attendances": [], "distance": "" } } Eliminar quedada: REQUEST URL /api/1.0/meetings/ID REQUEST TYPE DELETE REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {} ID es el identificador del evento. Respuesta esperada: HTTP 204 NO CONTENT
8.1.7. Attendance Crear asistencia: REQUEST URL /api/1.0/attendances/ REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {
"user": USER_ID, "meeting": MEETING_ID }
76
USER_ID y MEETING_ID son los identificadores del usuario y el evento a asistir. Respuesta esperada: HTTP 201 CREATED { "user": 112, "meeting": 22, "id": 13 } Obtener asistencias de usuario: REQUEST URL /api/1.0/attendances/ REQUEST TYPE GET REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {} Respuesta esperada: HTTP 200 OK [ { "user": { "id": 112, "email": "[email protected]", "username": "user23" }, "meeting": { "id": 22, "title": "German talk", "position": { "type": "Point", "coordinates": [ 40.389115990879745, -‐3.6436843872070312 ] }, "time": "2016-‐08-‐27T18:27:33.441000Z", "creator": 5 }, "id": 13 } ]
77
Eliminar asistencia: REQUEST URL /api/1.0/attendances/ID REQUEST TYPE POST REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {} Donde ID es el identificador único de la asistencia Respuesta esperada: HTTP 204 NO CONTENT
8.1.8. Related Obtener usuarios afines: REQUEST URL /api/1.0/related/ REQUEST TYPE GET REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {} Respuesta esperada: HTTP 200 OK [ { "description": "Description", "genre": "FEM", "born_date": "2001-‐01-‐18", "user": { "id": 5, "email": "[email protected]", "username": "Monkey" }, "speaks": [ … ], "practices": [ … ], "picture": "pictures/faul.jpg", "id": 6 }, … ]
78
8.1.9. Chat Conversaciones del usuario: REQUEST URL /api/1.0/chats/ REQUEST TYPE GET REQUEST FORMAT JSON REQUEST AUTHENTICATION Session EXAMPLE {} Respuesta esperada: HTTP 200 OK [ { "id": 12, "user_from": { "id": 1, "username": "Robin" }, "user_to": { "id": 108, "username": "Batman" }, "label": "1-‐108" }, … ]