Recordatorios de citas con Python y Django
¡Ahoy! Ahora te recomendamos que crees tus recordatorios de citas SMS con la funcionalidad de programación de mensajes integrada de Twilio. Dirígete a la Documentación de recursos de mensajes para obtener más información sobre la programación de mensajes de SMS.
¿Estás listo para implementar recordatorios de citas por SMS en tu aplicación web de Django? Usaremos la Biblioteca auxiliar de Python de Twilio y la API de Twilio SMS para enviar recordatorios a nuestros clientes cuando se aproximen las citas. Así es como funciona a un nivel alto:
- Un administrador (nuestro usuario) crea una cita para una fecha y hora futuras y almacena el número de teléfono de un cliente en la base de datos de esa cita
- Cuando se guarda esa cita, se programa una tarea en segundo plano para enviar un recordatorio a ese cliente antes de que comience la cita
- A una hora configurada antes de la cita, la tarea en segundo plano envía un recordatorio por SMS al cliente para recordarle su cita
Comprueba cómo Yelp utiliza SMS para confirmar las reservas de restaurantes para los comensales.
Componentes básicos de los recordatorios de citas
Estas son las tecnologías que utilizaremos:
- Django para crear una aplicación web basada en bases de datos
- El recurso mensajes de la API REST de Twilio para enviar mensajes de texto
- Dramatiq para ayudarnos a programar y ejecutar tareas en segundo plano de forma recurrente
Cómo leer este tutorial
Para implementar los recordatorios de citas, trabajaremos en una serie de historias de usuarios que describen cómo implementar por completo los recordatorios de citas en una aplicación web.
Analizaremos el código necesario para satisfacer cada historia y exploraremos lo que necesitábamos añadir en cada paso.
Todo esto se puede hacer con la ayuda de Twilio en menos de media hora.
Conoce nuestra pila de recordatorios de citas de Django
Estamos creando esta app para Django 2.1 en Python 3.6. Somos grandes admiradores de Two Scoops of Django y utilizaremos muchas de las prácticas recomendadas descritas allí.
Además de Dramatiq, usaremos algunas otras bibliotecas de Python para hacer nuestra tarea más fácil:
- La biblioteca auxiliar twilio-python de Python
- django-timezone-field para darnos un campo de modelo a fin de almacenar zonas horarias
- django-bootstrap3 y django-forms-bootstrap para hacer nuestras plantillas de formulario más simples y bonitas
- La fantástica biblioteca de arrow para hacer que nuestras matemáticas de fecha y hora sean infalibles
También usaremos PostgreSQL para nuestra base de datos y Redis como nuestro agente de mensajes de Dramatiq.
Ahora que hemos definido todas nuestras dependencias, podemos empezar con nuestra primera historia de usuario: crear una cita nueva.
Creación de una cita
Como usuario, quiero crear una cita con un nombre, un número de teléfono de invitado y una hora en el futuro.
Para crear una app de recordatorios de citas automatizada, probablemente deberíamos empezar con una cita. Esta historia requiere que creemos un objeto de modelo y un poco de la interfaz de usuario para crear y guardar una Appointment
nueva en nuestro sistema.
A un nivel alto, esto es lo que necesitaremos añadir:
- Un modelo
Appointment
para almacenar información que tenemos que enviar el recordatorio - Una vista para representar nuestro formulario y aceptar datos POST de este
- Un formulario HTML para introducir detalles sobre la cita
Bien, sabemos lo que necesitamos para crear una cita nueva. Ahora, empecemos por observar el modelo, en el que decidimos qué información queremos almacenar con la cita.
El modelo de cita
Solo necesitamos almacenar cuatro datos sobre cada cita para enviar un recordatorio:
- El nombre del cliente
- Su número de teléfono
- La fecha y hora de su cita
- La zona horaria de la cita
También hemos incluido dos campos adicionales: task_id
y created
. El campo task_id
nos ayudará a realizar un seguimiento de la tarea de recordatorio correspondiente a esta cita. El campo created
es solo una marca de tiempo que se rellena cuando se crea una cita.
Por último, definimos un método __str__
para decirle a Django cómo representar instancias de nuestro modelo como texto. Este método utiliza la clave principal y el nombre del cliente para crear una representación legible de una cita.
Nuestro modelo de cita ya está configurado, el siguiente paso es escribirle una vista.
Vista de cita nueva
Django permite a los desarrolladores escribir vistas como funciones o clases.
Las vistas basadas en clases son excelentes cuando tus vistas necesitan soportar funciones sencillas de tipo CRUD, perfectas para nuestro proyecto de citas.
Para crear una vista que crea objetos de Appointment
nuevos, usaremos la clase CreateView genérica de Django.
Lo que necesitamos especificar es el modelo que debe utilizar y los campos que debe incluir. Ni siquiera necesitamos declarar un formulario, Django usará un ModelForm para nosotros en segundo plano.
Mensajes de éxito
Nuestra vista está lista con solo esas tres primeras líneas de código, pero la mejoraremos un poco al añadir el SuccessMessageMixin.
Este mixin le dice a nuestra vista que pase la propiedad success_message
de nuestra clase al marco de mensajes de Django después de una creación exitosa. Mostraremos esos mensajes al usuario en nuestras plantillas.
Ahora que tenemos una vista para crear citas nuevas, necesitamos añadir una URL nueva a nuestro distribuidor de URL a fin de que los usuarios puedan acceder a ella.
Conectar las URL
Para satisfacer la historia de usuario de creación de citas, crearemos una URL nueva en /new
y la dirigiremos a nuestra AppointmentCreateView
.
Como estamos utilizando una vista basada en clases, pasamos nuestra vista a nuestra dirección URL con el método .as_view()
en lugar de solo utilizar el nombre de la vista.
Con una vista y un modelo en su lugar, la última gran pieza que necesitamos para permitir que nuestros usuarios creen citas nuevas es el formulario HTML.
Formulario de cita nuevo
Nuestra plantilla de formulario hereda de nuestra plantilla base, lo que puedes consultar en templates/base.html
.
Estamos usando Bootstrap para la interfaz de nuestra app, y usamos la biblioteca django-forms-bootstrap a fin de ayudarnos a representar nuestro formulario con el filtro de plantilla |as_bootstrap_horizontal
.
Cuando se nombra este archivo como appointment_form.html
, nuestra AppointmentCreateView
utilizará esta plantilla de forma automática cuando represente su respuesta. Si deseas asignar otro nombre a la plantilla, puedes especificar su nombre al añadir una propiedad template_name
en nuestra clase de vista.
Todavía no terminamos con este formulario. Echemos un vistazo más de cerca a uno de sus widgets: el selector de fecha.
Selector de fecha del formulario de la cita
Para facilitar a nuestros usuarios la introducción de la fecha y la hora de una cita, utilizaremos un widget de selector de fecha de JavaScript.
En este caso, bootstrap-datetimepicker es una buena opción. Incluimos los archivos CSS y JS necesarios de las redes de entrega de contenido y, a continuación, añadimos JavaScript personalizado para inicializar el widget en el formulario de entrada de nuestro campo de tiempo.
Ahora volvamos a nuestro modelo Appointment
para ver lo que sucede después de publicar este formulario con éxito.
Añadir un método get_absolute_url()
Cuando un usuario hace clic en "Submit" en nuestro formulario de cita nuevo, AppointmentCreateView
recibirá su entrada y, a continuación, lo validará en los campos especificados en nuestro modelo Appointment
.
Si todo es correcto, Django guardará la cita nueva en la base de datos. Tenemos que decirle a nuestra AppointmentCreateView
a dónde enviar a nuestro usuario a continuación.
Podríamos especificar una propiedad success_url
en nuestra AppointmentCreateView
, pero de forma predeterminada la clase CreateView de Django usará el método get_absolute_url
del objeto recién creado para averiguar dónde ir a continuación.
Definiremos un método get_absolute_url
en nuestro modelo Appointment
, que usa la función de utilidad reverse de Django para crear una URL en la página de detalles de esta cita. Puedes ver esa plantilla en templates/reminders/appointment_detail.html
.
Y ahora todos nuestros usuarios están listos para crear citas nuevas.
Ahora podemos crear citas nuevas. A continuación, implementaremos con rapidez algunas otras funciones básicas: enumerar, actualizar y eliminar citas.
Interactuar con citas
Como usuario, deseo ver una lista de todas las citas futuras y poder editarlas y eliminarlas.
Si eres una organización que gestiona muchas citas, es probable desees poder verlas y gestionarlas en una única interfaz. Eso es lo que abordaremos en esta historia de usuario. Crearemos una interfaz de usuario para:
- Mostrar todas las citas
- Editar citas individuales
- Eliminar citas individuales
Debido a que se trata de operaciones básicas de tipo CRUD, seguiremos utilizando las vistas genéricas basadas en clases de Django para ahorrarnos mucho trabajo.
Tenemos una vista de alto nivel de la tarea, así que comencemos con una lista de todas las citas próximas.
Mostrar una lista de citas
La clase ListView de Django nació para esto.
Todo lo que debemos hacer es apuntarla a nuestro modelo Appointment
y manejará la creación de un QuerySet de todas las citas por nosotros.
Y la conexión de esta vista en nuestro módulo reminders/urls.py
es tan sencilla como nuestra AppointmentCreateView
:
from .views import AppointmentListView
re_path(r'^$', AppointmentListView.as_view(), name='list_appointments'),
Nuestra vista está lista, ahora comprobemos la plantilla para mostrar esta lista de citas.
Plantilla de lista de citas
Nuestra AppointmentListView
pasa su lista de objetos de cita a nuestra plantilla en la variable object_list
.
Si esa variable está vacía, incluimos una etiqueta <p>
que indica que no hay citas próximas.
De lo contrario, rellenamos una tabla con una fila para cada cita de la lista. Podemos utilizar nuestro práctico método get_absolute_url
de nuevo para incluir un enlace a la página de detalles de cada cita.
También utilizamos la etiqueta de la plantilla {% url %} para incluir enlaces a nuestras vistas de edición y eliminación.
Ahora que nuestro requisito de listado de citas está completo, veamos cómo podemos utilizar el formulario de cita nuevo para actualizar las citas existentes.
Ajustar nuestra plantilla de formulario
UpdateView de Django facilita la adición de una vista para actualizar citas. Sin embargo, nuestra plantilla de formulario necesita algunos ajustes para gestionar los datos rellenados previamente de una cita existente.
Django almacenará nuestras fechas y horas con precisión, pero no queremos molestar a nuestros usuarios obligándolos a elegir el segundo exacto en el que comienza una cita.
Para solucionar este problema utilizamos la opción de configuración extraFormats de bootstrap-datetimepicker.
Cuando se configura nuestro datetimepicker con un valor format
que no pregunta a los usuarios por segundos y un valor extraFormat
que acepta una fecha y hora con segundos, nuestro formulario se rellenará de forma correcta cuando Django proporcione una fecha y hora completa a nuestra plantilla.
Ahora tenemos todo para List
, Create
y Update
una Appointment
. Solo queda manejar la Delete
.
Eliminar vista
DeleteView es una clase de vista especialmente útil. Muestra a los usuarios una página de confirmación antes de eliminar el objeto especificado.
Al igual que UpdateView, DeleteView busca el objeto que se va a eliminar mediante el parámetro pk
en su URL, declarada en reminders/urls.py
:
from .views import AppointmentDeleteView
re_path(r'^/(?P[0-9]+)/delete$', AppointmentDeleteView.as_view(), name='delete_appointment'),
También necesitamos especificar una propiedad success_url
en nuestra clase de vista. Esta propiedad indica a Django dónde enviar a los usuarios después de una eliminación con éxito. En nuestro caso, las devolveremos a la lista de citas en la URL denominada list_appointments
.
Cuando un proyecto de Django comienza a ejecutarse, evalúa las vistas antes de que las URL, por lo que necesitamos usar la función de utilidad reverse_lazy para obtener la URL de nuestra lista de citas en lugar de reverse
.
De forma predeterminada, nuestra AppointmentDeleteView
buscará una plantilla denominada appointment_confirm_delete.html
. Puedes consultar la nuestra en el directorio de templates/reminders
.
Esto cierra esta historia de usuario.
Ahora nuestros usuarios tienen todo lo que necesitan para gestionar las citas; lo que queda por implementar es enviar los recordatorios.
Envío del recordatorio
Como sistema de citas, quiero notificar a un cliente mediante SMS en un intervalo arbitrario antes de una cita futura.
Para satisfacer esta historia de usuario, necesitamos hacer que nuestra aplicación móvil funcione de forma asincrónica, independiente de cualquier interacción de usuario individual.
Una de las bibliotecas de Python más populares para tareas asincrónicas es Dramatiq. Para integrar Dramatiq en nuestra aplicación móvil, necesitamos hacer algunos cambios:
- Crear una función nueva que envíe un mensaje SMS con información de un objeto
Appointment
- Registrar esa función como una tarea con Dramatiq para que se pueda ejecutar de forma asincrónica
- Ejecutar un proceso de trabajador de Dramatiq separado junto con nuestra aplicación de Django para llamar a nuestra función de recordatorio de SMS en el momento adecuado de cada cita
Si eres nuevo en Dramatiq, te recomendamos echar un vistazo a su página Introducción a Dramatiq antes de continuar.
A continuación, configuraremos Dramatiq para trabajar con nuestro proyecto.
Configurar Dramatiq
Dramatiq y Django son grandes proyectos de Python, pero pueden trabajar juntos con facilidad.
Al seguir las instrucciones en la documentación de Dramatiq, podemos incluir nuestra configuración de Dramatiq en nuestros módulos de configuración de Django. También podemos escribir nuestras tareas de Dramatiq en módulos de tasks.py
que se encuentran en nuestras apps de Django, lo que mantiene el diseño de nuestro proyecto coherente y simple.
Para usar Dramatiq, también necesitas un servicio separado para que sea tu agente de mensajería. Utilizamos Redis en este proyecto.
La configuración específica de Dramatiq en nuestro módulo de configuración common.py
es DRAMATIQ_BROKER
.
Si deseas ver todos los pasos para hacer que Django, Dramatiq, Redis y Postgres trabajen en tu equipo, consulta el archivo README de este proyecto en GitHub.
Ahora que Dramatiq está trabajando con nuestro proyecto, es el momento de escribir una tarea nueva para enviar a un cliente un mensaje SMS sobre su cita.
Crear una tarea de Dramatiq
Nuestra tarea toma el ID de una cita, su clave principal, como único argumento. Podríamos pasar el objeto Appointment
por sí solo como argumento, pero esta práctica recomendada garantiza que nuestro SMS utilizará la versión más actualizada de los datos de nuestra cita.
También nos da la oportunidad de comprobar si la cita se ha eliminado antes de enviar el recordatorio, lo que hacemos en la parte superior de nuestra función. De esta forma no enviaremos recordatorios por SMS para citas que ya no existen.
Seguiremos en nuestra tarea un poco más, porque el siguiente paso es redactar el texto de nuestro mensaje SMS.
Enviar un mensaje SMS
Utilizamos la práctica biblioteca arrow para dar formato a la hora de nuestra cita. Después de eso, usamos la biblioteca twilio-python para enviar nuestro mensaje.
Instanciamos un cliente de REST de Twilio en la parte superior del módulo, que busca las variables del entorno TWILIO_ACCOUNT_SID
y TWILIO_AUTH_TOKEN
para autenticarse. Puedes encontrar los valores correctos para ti en el panel de control de la cuenta.
Enviar el mensaje SMS en sí es tan fácil como llamar a client.messages.create()
, pasar argumentos para el cuerpo del mensaje SMS, el número de teléfono del destinatario y el número de teléfono de Twilio desde el que deseas enviar este mensaje. Twilio entregará el mensaje SMS de inmediato.
Ahora que finalizamos nuestra tarea de send_sms_reminder
, veamos cómo llamarla cuando se crean o actualizan nuestras citas.
Llamar a nuestra tarea de recordatorio
Hemos añadido un método nuevo a nuestro modelo Appointment
para ayudar a programar un recordatorio en una cita individual.
Nuestro método comienza mediante el uso de arrow de nuevo para crear una nueva fecha y hora con la time
y time_zone
de la cita.
Moverse hacia atrás en el tiempo puede ser difícil en Python normal, pero el método .replace()
de arrow nos permite quitar minutos con facilidad de nuestra appointment_time
. El valor predeterminado de REMINDER_TIME
son 30 minutos.
Terminamos al invocar nuestra tarea de Dramatiq, con el parámetro delay para decirle a Dramatiq cuándo debe ejecutarse esta tarea.
No se puede importar la tarea send_sms_reminder
en la parte superior de nuestro módulo models.py
porque el módulo tasks.py
importa el modelo Appointment
. Importarlo en nuestro método schedule_reminder
evita una dependencia circular.
Lo último que debemos hacer es asegurar de que Django llame a nuestro método schedule_reminder
cada vez que se crea o actualiza un objeto Appointment
.
Anular el método de Guardar cita
La mejor manera de hacerlo es anular el método de guardar de nuestro modelo, incluida una llamada adicional a schedule_reminder
después de que se haya asignado la clave principal del objeto.
Evitar recordatorios duplicados o inoportunos
Programar una tarea de Dramatiq cada vez que se guarda una cita tiene un efecto secundario no deseado, nuestros clientes recibirán recordatorios duplicados si una cita se ha guardado más de una vez. Y esos recordatorios podrían enviarse en el momento equivocado si el campo time
de una cita se cambiaba después de su creación.
Para corregir esto, realizamos un seguimiento de la tarea de recordatorio de cada cita mediante el campo task_id
, que almacena el identificador único de Dramatiq para cada tarea.
A continuación, buscamos una tarea programada de forma previa en la parte superior de nuestro método save
personalizado y la cancelamos si está presente.
Esto garantiza que se enviará solo un recordatorio para cada cita de nuestra base de datos y que se enviará a la time
más reciente prevista para esa cita.
Un tutorial divertido, ¿verdad? ¿Dónde podemos ir desde aquí?
Finalizar la implementación del recordatorio de cita de Django
Utilizamos las vistas basadas en clases de Django para ayudarnos a crear con rapidez las funciones a fin de soportar operaciones CRUD sencillas en nuestro modelo Appointment
.
Luego integramos Dramatiq en nuestro proyecto y usamos la biblioteca auxiliar twilio-python para enviar recordatorios de SMS sobre nuestras citas de forma asincrónica.
Encontrarás instrucciones para ejecutar este proyecto de forma local en el archivo README de GitHub.
¿Dónde ir a continuación?
Con un poco de código y una pizca de configuración, estamos listos para recibir recordatorios de citas automatizados en nuestra aplicación móvil. ¡Bien hecho!
Si eres un desarrollador de Python que trabaja con Twilio, puedes consultar otros tutoriales en Python:
Coloca un botón en tu página web que conecte a los visitantes con el servicio de asistencia en directo o con el personal de ventas por teléfono.
Autenticación de dos factores
Mejora la seguridad de la funcionalidad de inicio de sesión de la app Python al añadir autenticación de dos factores por mensajes de texto.
¿Te ayudó esto?
Gracias por consultar este tutorial. Si tienes algún comentario que compartir con nosotros, comunícate con nosotros en Twitter... nos encantaría saber lo que piensas y conocer lo que estás creando.
¿Necesitas ayuda?
Todos la necesitamos a veces; la programación es difícil. Obtén ayuda ahora de nuestro equipo de soporte, o recurre a la sabiduría de la multitud visitando Stack Overflow Collective de Twilio o navegando por la etiqueta de Twilio en Stack Overflow.