Lembretes de agendamentos com Python e Django
Ahoy! Agora recomendamos que você crie seus lembretes de agendamento por SMS com a funcionalidade de programação de mensagens integrada da Twilio. Acesse a documentação de recursos de mensagens para saber mais sobre como programar mensagens SMS!
Pronto para implementar lembretes de agendamentos de SMS no seu aplicativo da web do Django? Usaremos a Biblioteca auxiliar Python da Twilio e a API Twilio SMS para enviar por push lembretes aos nossos clientes quando os agendamentos estiverem próximos. Veja como funciona em um nível alto:
- Um administrador (nosso usuário) cria um agendamento para uma data e hora futuras e armazena o número de telefone de um cliente no banco de dados para esse agendamento
- Quando esse agendamento é salvo, uma tarefa em segundo plano é programada para enviar um lembrete para esse cliente antes do início do agendamento
- Em um horário configurado antes do agendamento, a tarefa em segundo plano envia um lembrete SMS ao cliente para lembrá‐lo de seu agendamento
Confira como a Yelp usa o SMS para confirmar reservas de restaurantes para clientes.
Blocos de construção do lembrete de agendamento
Aqui estão as tecnologias que usaremos:
- Django para criar um aplicativo da web orientado por banco de dados
- O recurso de mensagens da API REST da Twilio para enviar mensagens de texto
- Dramatiq para nos ajudar a programar e executar tarefas em segundo plano de forma recorrente
Como ler este tutorial
Para implementar lembretes de agendamentos, trabalharemos em uma série de histórias de usuários que descrevem como implementar totalmente lembretes de agendamentos em um aplicativo da web.
Veremos o código necessário para satisfazer cada história e exploraremos o que precisávamos adicionar em cada etapa.
Tudo isso pode ser feito com a ajuda da Twilio em menos de meia hora.
Conheça nossa pilha de lembretes de agendamento do Django
Estamos criando este app para Django 2.1 no Python 3.6. Somos grandes fãs do livro Two Scoops of Django e usaremos muitas das práticas recomendadas descritas nele.
Além do Dramatiq, usaremos algumas outras bibliotecas Python para facilitar nossa tarefa:
- A biblioteca auxiliar Python twilio-python
- O django-timezone-field para nos dar um campo de modelo para o armazenamento de fusos horários
- django-bootstrap3 e django-forms-bootstrap para tornar nossos modelos de formulário mais simples e mais bonitos
- A fantástica biblioteca arrow para tornar nossa matemática de data e hora infalível
Também usaremos o PostgreSQL para nosso banco de dados e o Redis como nosso agente de mensagens do Dramatiq.
Agora que temos todas as nossas dependências definidas, podemos começar com a nossa primeira história de utilizador: criar um novo agendamento.
Criar um agendamento
Como usuário, quero criar um agendamento com um nome, número de telefone de convidado e um horário no futuro.
Para criar um app de lembrete de agendamento automatizado, provavelmente devemos começar com um agendamento. Esta história requer que criemos um objeto de modelo e um pouco da interface do usuário para criar e salvar um novo Appointment
em nosso sistema.
Em um nível alto, vamos adicionar o que precisamos:
- Um modelo
Appointment
para armazenar as informações necessárias para enviar o lembrete - Uma exibição para renderizar nosso formulário e aceitar dados POST dele
- Um formulário HTML para inserir detalhes sobre o agendamento
Muito bem, então sabemos o que precisamos para criar um novo agendamento. Agora, vamos começar analisando o modelo, onde decidimos quais informações queremos armazenar com o agendamento.
O modelo de agendamento
Só precisamos armazenar quatro dados sobre cada agendamento para enviar um lembrete:
- O nome do cliente
- Seu número de telefone
- A data e a hora do agendamento
- O fuso horário do agendamento
Também incluímos dois campos adicionais: task_id
e created
. O campo task_id
nos ajudará a acompanhar a tarefa de lembrete correspondente para este agendamento. O campo created
é apenas um carimbo de data/hora preenchido quando um agendamento é criado.
Finalmente, definimos um método __str__
para dizer ao Django como representar instâncias de nosso modelo como texto. Esse método usa a chave primária e o nome do cliente para criar uma representação legível de um agendamento.
Nosso modelo de agendamento agora está configurado, o passo seguinte é escrever uma exibição para ele.
Nova exibição do agendamento
O Django permite que os desenvolvedores escrevam exibições como funções ou classes.
As visualizações baseadas em classe são excelentes é necessário oferecer suporte a recursos simples, do tipo CRUD, perfeitos para o nosso projeto de agendamentos.
Para fazer com que uma exibição crie novos objetos Appointment
, usaremos a classe genérica CreateView do Django.
Tudo o que precisamos especificar é o modelo que ele deve usar e quais campos ele deve incluir. Nem sequer precisamos declarar um formulário, o Django usará um ModelForm para nós nos bastidores.
Mensagens de sucesso
Nossa exibição está pronta para ser usada apenas com as três primeiras linhas de código, mas vamos torná‐la um pouco melhor adicionando o SuccessMessageMixin.
Este mixin diz a nossa exibição para passar a propriedade success_message
de nossa classe para o framework de mensagens do Django após uma criação bem‐sucedida. Exibiremos essas mensagens para o usuário em nossos modelos.
Agora que temos uma exibição para criar novos agendamentos, precisamos adicionar um novo URL a nosso despachante de URL para que os usuários possam chegar até ele.
Conectar os URLs
Para satisfazer a história do usuário de criação de agendamento, criaremos um novo URL em /new
e apontamos para nosso AppointmentCreateView
.
Como estamos usando uma exibição baseada em classe, passamos nossa exibição para nosso URL com o método .as_view()
em vez de apenas usar o nome da exibição.
Com uma exibição e um modelo em vigor, a última grande parte que precisamos para permitir que nossos usuários criem novos agendamentos é o formulário HTML.
Novo formulário de agendamento
Nosso modelo de formulário herda de nosso modelo básico, que você pode conferir em templates/base.html
.
Estamos usando o Bootstrap para o front‐end de nosso app, e usamos a biblioteca django-forms-bootstrap para nos ajudar a processar nosso formulário com o filtro de modelo |as_bootstrap_horizontal
.
Ao nomear este arquivo appointment_form.html
, nosso AppointmentCreateView
usará automaticamente esse modelo ao renderizar sua resposta. Se quiser nomear seu modelo como algo mais, você pode especificar seu nome adicionando uma propriedade template_name
em nossa classe de exibição.
Ainda não estamos saindo deste formulário. Em vez disso, vamos dar uma olhada em um de seus widgets: o datepicker.
Datepicker do formulário de agendamento
Para facilitar a nossos usuários a inserção de data e hora de um agendamento, usaremos um widget de datepicker do JavaScript.
Nesse caso, bootstrap-datetimepicker é uma boa opção. Incluímos os arquivos CSS e JS necessários das redes de entrega de conteúdo e, em seguida, adicionamos um pouco de JavaScript personalizado para inicializar o widget na entrada do formulário para nosso campo de tempo.
Agora, vamos voltar ao nosso modelo Appointment
para ver o que acontece depois de publicarmos este formulário com sucesso.
Adicionar um método get_absolute_url()
Quando um usuário clica em "Submit" em nosso novo formulário de agendamento, sua entrada será recebida pela nossa AppointmentCreateView
e validada nos campos especificados em nosso modelo Appointment
.
Se tudo estiver certo, o Django salvará o novo agendamento no banco de dados. Precisamos dizer ao AppointmentCreateView
para onde enviar nosso usuário em seguida.
Poderíamos especificar uma propriedade success_url
em nossa AppointmentCreateView
, mas por padrão a classe CreateView do Django usará o método get_absolute_url
do objeto recém‐criado para descobrir aonde ir em seguida.
Assim, definiremos um método get_absolute_url
em nosso modelo Appointment
, que usa a função de utilidade reversa do Django para criar um URL para a página de detalhes deste agendamento. Você pode ver esse modelo em templates/reminders/appointment_detail.html
.
E agora nossos usuários estão todos configurados para criar novos agendamentos.
Agora, podemos criar novos agendamentos. Em seguida, vamos implementar rapidamente alguns outros recursos básicos: listar, atualizar e excluir agendamentos.
Interação com agendamentos
Como usuário, quero exibir uma lista de todos os agendamentos futuros e poder editar e excluir esses agendamentos.
Se você é uma organização que lida com muitos agendamentos, provavelmente deseja poder visualizá‐los e gerenciá‐los em uma única interface. É isso que vamos abordar nesta história do usuário. Criaremos uma IU para:
- Mostrar todos os agendamentos
- Editar agendamentos individuais
- Excluir agendamentos individuais
Como essas são operações do tipo CRUD básicas, vamos continuar usando as exibições genéricas de Django baseadas em classes para facilitar muito nosso trabalho.
Temos a visão de alto nível da tarefa, então vamos começar listando todos os agendamentos futuros.
Mostrar uma lista de agendamentos
A classe ListView do Django nasceu para isso.
Tudo o que precisamos fazer é apontá‐la em nosso modelo Appointment
e ele lidará com a construção de um QuerySet de todos os agendamentos para nós.
E conectar esta exibição em nosso módulo reminders/urls.py
é tão fácil quanto nosso AppointmentCreateView
:
from .views import AppointmentListView
re_path(r'^$', AppointmentListView.as_view(), name='list_appointments'),
Nossa exibição está pronta, agora vamos conferir o modelo para exibir esta lista de agendamentos.
Modelo da lista de agendamentos
Nossa AppointmentListView
passa sua lista de objetos de agendamento para nosso modelo na variável object_list
.
Se essa variável estiver vazia, incluímos uma tag <p>
dizendo que não há agendamentos futuros.
Caso contrário, preenchemos uma tabela com uma linha para cada agendamento em nossa lista. Podemos usar nosso método prático get_absolute_url
novamente para incluir um link para a página de detalhes de cada agendamento.
Também usamos a tag do modelo {% url %} para incluir links para as nossas exibições de edição e exclusão.
E agora que nosso requisito de listagem de agendamentos está completo, vamos ver como podemos usar o novo formulário de Agendamento para atualizar agendamentos existentes.
Ajustando nosso modelo de formulário
A UpdateView do Django facilita a adição de uma exibição para a atualização de agendamentos. No entanto, nosso modelo de formulário precisa de alguns ajustes para lidar com dados pré‐preenchidos de um agendamento existente.
O Django armazenará nossos datetimes com precisão, até o segundo, mas não queremos incomodar nossos usuários forçando‐os a escolher o segundo exato um agendamento começa.
Para corrigir esse problema, usamos a opção de configuração extraFormats do bootstrap-datetimepicker.
Ao configurar nosso datetimepicker com um valor de format
que não pede aos usuários os segundos e um valor extraFormat
que aceita datetimes com segundos, nosso formulário será preenchido corretamente quando o Django fornecer uma data e hora completa para nosso modelo.
Agora temos tudo para usar as classesList
, Create
e Update
de um Appointment
. Agora, só falta o tratamento do Delete
.
Exibição do Delete
O DeleteView é uma classe de exibição especialmente útil. Ela mostra aos usuários uma página de confirmação antes de excluir o objeto especificado.
Como UpdateView, DeleteView localiza o objeto a ser excluído usando o parâmetro pk
em seu URL, declarado no reminders/urls.py
:
from .views import AppointmentDeleteView
re_path(r'^/(?P[0-9]+)/delete$', AppointmentDeleteView.as_view(), name='delete_appointment'),
Também precisamos especificar uma propriedade success_url
em nossa classe de exibição. Esta propriedade informa ao Django onde enviar usuários após uma exclusão bem‐sucedida. Em nosso caso, vamos enviá‐los de volta para a lista de agendamentos no URL chamado list_appointments
.
Quando um projeto do Django começa a funcionar, ele avalia as exibições antes das URLs, então precisamos usar a função de utilitário reverse_lazy para obter o URL da lista de agendamentos em vez reverse
.
Por padrão, vamos procurar um modelo AppointmentDeleteView
chamado appointment_confirm_delete.html
. Você pode fazer o check‐out em nosso diretório templates/reminders
.
E isso encerra esta história de usuário.
Nossos usuários agora têm tudo o que precisam para gerenciar agendamentos, tudo o que resta para implementar está enviando os lembretes.
Enviar o lembrete
Como um sistema de agendamentos, desejo notificar um cliente via SMS sobre um intervalo arbitrário antes de um agendamento futuro.
Para satisfazer essa história de usuário, precisamos fazer com que nosso aplicativo funcione de forma assíncrona, independentemente de qualquer interação individual do usuário.
Uma das bibliotecas Python mais populares para tarefas assíncronas é o Dramatiq. Para integrar o Dramatiq com nosso aplicativo, precisamos fazer algumas alterações:
- Crie uma nova função que envie uma mensagem SMS usando informações de um objeto
Appointment
- Registre essa função como uma tarefa com o Dramatiq para que ela possa ser executada de forma assíncrona
- Execute um processo de trabalhador do Dramatiq separado junto com nosso aplicativo do Django para chamar nossa função de lembrete de SMS no momento certo para cada agendamento
Se você é novo no Dramatiq, pode querer dar uma olhada na página Introdução ao Dramatiq antes de continuar.
Em seguida, configuraremos o Dramatiq para trabalhar com nosso projeto.
Configuração do Dramatiq
Dramatiq e Django são ambos grandes projetos Python, mas eles podem trabalhar juntos facilmente.
Seguindo as instruções nos documentos do Dramatiq, podemos incluir nossas configurações do Dramatiq em nossos módulos de configuração do Django. Também podemos escrever nossas tarefas do Dramatiq nos módulos tasks.py
que vivem dentro de nossos aplicativos do Django, o que mantém nosso layout de projeto consistente e simples.
Para usar o Dramatiq, você também precisa de um serviço separado para ser seu agente de mensagens. Usamos o Redis para este projeto.
As configurações específicas do Dramatiq em nosso módulo de configurações common.py
sãoDRAMATIQ_BROKER
.
Se quiser ver todas as etapas para fazer com que o Django, Dramatiq, Redis e Postgres trabalhem em sua máquina Confira o README do projeto no GitHub.
Agora que o Dramatiq está trabalhando com nosso projeto, é hora de escrever uma nova tarefa para enviar uma mensagem SMS a um cliente sobre seu agendamento.
Criando uma tarefa do Dramatiq
Nossa tarefa toma o ID de um agendamento, é a chave principal, como seu único argumento. Poderíamos passar o próprio objeto Appointment
como argumento, mas essa prática recomendada garante que nosso SMS usará a versão mais atualizada dos dados de nosso agendamento.
Ela também nos dá a oportunidade de verificar se o agendamento foi excluído antes do envio do lembrete, o que fazemos no topo de nossa função. Dessa forma, não enviaremos lembretes de SMS para agendamentos que não existem mais.
Vamos ficar um pouco mais em nossa tarefa, porque o próximo passo é compor o texto da nossa mensagem SMS.
Como enviar uma mensagem SMS
Usamos a prática biblioteca arrow para formatar o horário do nosso agendamento. Depois disso, usamos a biblioteca twilio-python para enviar nossa mensagem.
Instanciamos um cliente Twilio REST na parte superior do módulo, que procura as variáveis de ambiente TWILIO_ACCOUNT_SID
para TWILIO_AUTH_TOKEN
se autenticar. Você pode encontrar os valores corretos para você no dashboard de sua conta.
Enviar a mensagem SMS em si é tão fácil quanto chamar client.messages.create()
, passar argumentos para o corpo da mensagem SMS, o número de telefone do destinatário e o número de telefone da Twilio do qual você deseja enviar esta mensagem. A Twilio enviará a mensagem SMS imediatamente.
Com nossa tarefa send_sms_reminder
concluída, vamos ver como chamá‐la quando nossos agendamentos forem criados ou atualizados.
Chamar nossa tarefa de lembrete
Adicionamos um novo método em nosso modelo Appointment
para ajudar a agendar um lembrete para um agendamento individual.
Nosso método começa usando arrow novamente para criar uma nova datetime com time
e time_zone
do agendamento.
Retroceder no tempo pode ser complicado no Python normal, mas o método .replace()
do arrow permite subtrair facilmente minutos de nosso appointment_time
. A configuração padrão REMINDER_TIME
é 30 minutos.
Terminamos invocando nossa tarefa do Dramatiq, usando o parâmetro delay (atrasar) para dizer ao Dramatiq quando esta tarefa deve ser executada.
Não é possível importar a tarefa send_sms_reminder
na parte superior de nosso módulo models.py
porque o módulo tasks.py
importa o modelo Appointment
. A importação em nosso método schedule_reminder
evita uma dependência circular.
A última coisa que precisamos fazer é garantir que o Django chame nosso método schedule_reminder
sempre que um objeto Appointment
é criado ou atualizado.
Substituir o método Save do agendamento
A melhor maneira de fazer isso é substituir o método de salvamento do nosso modelo, incluindo uma chamada extra para schedule_reminder
depois que a chave primária do objeto tiver sido atribuída.
Evitar lembretes duplicados ou com tempo incorreto
Agendar uma tarefa do Dramatiq sempre que um agendamento for salvo tem um efeito colateral lamentável, nossos clientes receberão lembretes duplicados se um agendamento for salvo mais de uma vez. E esses lembretes poderão ser enviados no momento errado se o campo time
de um agendamento tiver sido alterado após sua criação.
Para corrigir isso, acompanhamos a tarefa de lembrete de cada agendamento no campo task_id
, que armazena o identificador exclusivo do Dramatiq para cada tarefa.
Em seguida, procuramos uma tarefa previamente agendada na parte superior do nosso método save
personalizado e cancelamos essa tarefa, se houver.
Isso garante que um e exatamente um lembrete será enviado para cada agendamento em nosso banco de dados, e que ele será enviado no time
mais recente fornecido para esse agendamento.
Tutorial divertido, certo? Onde podemos levá‐lo a partir daqui?
Concluir a implementação do lembrete de agendamento do Django
Usamos as exibições baseadas em classe do Django para nos ajudar a desenvolver rapidamente os recursos para dar suporte a operações CRUD simples em nosso modelo Appointment
.
Em seguida, integramos o Dramatiq em nosso projeto e usamos a biblioteca auxiliar twilio-python para enviar lembretes SMS sobre nossos agendamentos de forma assíncrona.
Você encontrará instruções para executar este projeto localmente no README do GitHub.
Para onde ir em seguida?
E com um pequeno código e um pouco de configuração, estamos prontos para receber lembretes automatizados de agendamentos disparados em nosso aplicativo. Bom trabalho!
Se você for um desenvolvedor Python que trabalha com a Twilio, talvez queira conferir outros tutoriais em Python:
Coloque um botão em sua página da web que conecta os visitantes ao suporte ao vivo ou à equipe de vendas por telefone.
Melhore a segurança da funcionalidade de login do seu app Python adicionando autenticação de 2 fatores por mensagem de texto.
Isso ajudou?
Obrigado por conferir este tutorial! Se você tiver algum feedback para compartilhar, entre em contato pelo Twitter... adoraríamos ouvir suas ideias e saber o que você está desenvolvendo!
Precisa de ajuda?
Às vezes, todos nós precisamos; a programação é difícil. Receba ajuda agora da nossa equipe de suporte, ou confie na sabedoria da multidão navegando pelo Stack Overflow Collective da Twilio ou buscando a tag Twilio no Stack Overflow.