Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

readme.md

Django

Criando o projeto

Para iniciarmos nosso projeto Django, vamos criar um diretório e fazer as configuração iniciais do venv e instalar o Django:

mkdir projeto-eventos
cd projeto-eventos
python -m venv venv
source venv/bin/activate
pip install Django==4.0.3

Agora vamos criar um projeto Django usando o comando:

django-admin startproject vamomarcar .

Assim iremos criar um projeto com o nome vamomarcar no diretório ., que é o atual.

Feito isso teremos um diretório com o nome do projeto e um arquivo manage.py.

Como isso já podemos rodar o nosso projeto pelo comando:

python manage.py runserver

Criando nosso app

Django é um framework voltado para projetos grandes. Então ele tem dois conceitos difrentes quando você fala de projeto e uma aplicação. Dessa forma podemos ter em um projetos vários apps ou funcionalidades divididas.

Django

Dessa forma vamos começar a criar as funcionalidades ou apps nesse nosso projeto. E para isso usamos a CLI do Django:

django-admin startapp agenda

Feito isso teremos um novo diretório, agenda, criado na raiz do nosso projeto. Lembrando que o diretório vamomarcar é o diretório padrão do nosso projeto, onde contem arquivos, como por exemplo, urls.py, que contrala as rotas da nossa aplicação.

Como acabamos de criar nosso app agenda, vamos criar uma URL para ele, mas para isso precisamos criar uma view para a agenda. No diretório da agenda no arquivo veiws.py:

def index(request):
    return HttpResponse("Oláááá Enfermeira!")

Onde o HttpResponse é importado de from django.http.response import HttpResponse.

Aqui definimos uma função que recebe uma requisição e irá retornar uma resposta HTTP.

Agora podemos definir uma rota para a agenda no arquivo urls.py, onde:

from django.contrib import admin
from django.urls import path

from agenda.views import index

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index),
]

Nesse caso, teremos que a rota raiz, irá chamar a função da nossa view no caso a da agenda, pelo import que foi feito.

Note que o Django já trouxe uma rota padrão para admim.

Uma boa prática no Django é manter as URLs de cada um dos apps isoladas dentro daquela própria aplicação.

Para isso, vamos criar um arquivo urls.py no diretório agenda:

from django.urls import path

from agenda.views import index

urlpatterns = [
    path('', index)
]

E agora no arquivo urls.py no diretório do projeto, vamomarcar, fazemos o include dessas URLs da agenda:

from django.contrib import admin
from django.urls import path
from django.urls.conf import include

from agenda.views import index
from agenda.urls import urlpatterns as agenda_urls

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(agenda_urls))
]

Exibindo Eventos

Para conseguirmos mostrar um evento, precisaremos definir um modelo(model), para a entidade evento. Note que no diretório agenda, temos um arquivo models.py, é nele que iremos definir os modelos de entidades que teremos na aplicação da agenda, vamos definir esse modelo e criar algumas instâncias:

from django.db import models

# Create your models here.
class Evento:
    def __init__(self, nome, categoria, local=None, link=None):
        self.nome = nome
        self.categoria = categoria
        self.local = local
        self.link = link
        
aula_python = Evento("Aula de Python", "Back-end", "Sinop")
aula_js = Evento("Aula de Javascript", "Fullstack", link="https://rogovski.dev")

eventos = [
    aula_python,
    aula_js
]

E para que o usuário possa ver esses dados no navegador, vamos criar mais view na aplicação da agenda:

def exibir_evento(request):
    evento = eventos[0]
    
    return HttpResponse(f""""
        <html>
        <h1>Evento: {evento.nome}</h1>
        <p>Categoria: {evento.categoria}</p>
        <p>Local: {evento.local}</p>
        <p>Link: <a href='{evento.link} target='_blank'>Acessar</a></p>
        </html>
    """)
  • Note que usamos """ três vezes, para abrir e fechar as strings, isso é chamado de multiline strings, assim podemos ter quebras de linhas no texto que o Python identifica como parte do conteúdo. Além disso incluímos o f (f strings) para podermos fazer a interpolação do dados que queremos apresentar na view. Feito isso, vamos nas URLs da aplicação agenda para incluir o path para essa view:
from django.urls import path

from agenda.views import exibir_evento, index

urlpatterns = [
    path('', index),
    path('evento', exibir_evento)
]

Utilizando Django template

Não é uma boa prática mistrar o seu código HTML com o seu código python. O idel é extrair isso para um arquivo separaddo. E para isso, no Django usamos o Django Templates. Para fazer usso dos templates, vamos no diretório da nossa aplicação agenda e criamos a estrutra de diretórios templates\agenda.

Django

Agora vamos copiara a string que declaramos anteriormente na views.py em exibir_evento e levar para o nosso template. E teremos que fazer alguns ajustes para que o Django faça a interpolação das variáveis que passarmos para esse template, onde antes tinahmos as variáveis envolvidas por { evento.nome }, agora temos {{ evento.nome }}, por exemplo:

<html>
    <h1>Evento: {{ evento.nome }}</h1>
    <p>Categoria: {{ evento.categoria }}</p>
    <p>Local: {{ evento.local }}</p>
    <p>Link: <a href='{{ evento.link }}' target='_blank'>Acessar</a></p>
</html>

E agora passar informar para a nossa função exibir_evento utilizar esse arquivo html:

def exibir_evento(request):
    evento = eventos[1]
    template = loader.get_template("agenda/exibir_evento.html")
    rendered_template = template.render(context={ "evento": evento }, request=request)
    
    return HttpResponse(rendered_template)

Perceba que tiver que fazer o import from django.template import loader para utilizar uma função que carrega o template que desejamos usar, template = loader.get_template("agenda/exibir_evento.html").

Normalmente o Django espera que dentro do diretório de cada aplicação, no nosso caso a agenda tenho um diretório templates, então para isso criamos um subdirtetório agenda para que o Django saiba de qual aplicação ele deve buscar o template.

Na função render, informamos como parâmetros, o contexto que iremos passar para o template e a requisição. No caso do contexto é um objeto que tem uma propriedade justamente com o nome que é usado para fazer a interpolação.

Mas se tentar acessar essa rota agora, teremos um erro TemplateDoesNotExist at /evento, pois o Django necessita de um configuração para informar quais aplicações estão instaladas. Isso é feito no arquivo settings.py no diretório do projeto vamomarcar e adidionamos a linha 'agenda.apps.AgendaConfig',:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'agenda.apps.AgendaConfig',
]

AgendaConfig é uma classe que fica no arquivo agenda/apps.py:

class AgendaConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'agenda'

Esse fluxo acontece da seguinte forma:

Django

Por ser algo que aconte com frequência no Django podemos os shortcuts que vem do import from django.shortcuts import render para reduzir o nosso código:

def exibir_evento(request):
    evento = eventos[1]
    
    return render(request=request, context={ "evento": evento }, template_name="agenda/exibir_evento.html")

O template engine do Django também permite o usa de algumas lógicas simples de código. Por exemplo, se uma variável estiver sem valor(None, o null do Python), podemos tratar para mostar ou não essa informação:

<html>
    <h1>Evento: {{ evento.nome }}</h1>
    <p>Categoria: {{ evento.categoria }}</p>
    {% if evento.local %}<p>Local: {{ evento.local }}</p>{% endif %}
    {% if evento.link %}<p>Link: <a href="{{ evento.link }}" target="_blank">Acessar</a></p>{% endif %}
</html>

Para saber mais sobre templates.

Django ORM e migrações

Mapeando as entidades

Vamos começar usando o sqlite como banco de dados e depois vamos migrar nossos dados para um Postgres.

Perceba que na raiz do nosso projeto, temos um arquivo chamado db.sqlite3, que é onde o sqlite irá salvar nossos dados.

Django

E para saber qual banco de dados nosso projeto, está usando, podemos ver isso em vamomarcar/settings.py:

Django

Para fazer a inserção de dados nesse banco de dados, poderiamos fazer isso de maneira manual. Mas o Django ORM que faz o mapeamentos de nossas entidades e seus atributos.

Para isso precisamos refatorar um pouco o nosso código e fazer com que nossas classes herdem de models.Model do Django:

class Categoria(models.Model):
    nome: models.CharField(max_length=256, unique=True)

class Evento(models.Model):
    nome = models.CharField(max_length=256)
    categoria = models.ForeignKey(Categoria, on_delete=models.SET_NULL, null=True)
    local = models.CharField(max_length=256, blank=True)
    link = models.CharField(max_length=256, blank=True)

Nessa refatoração, criamos uma nova entidade, a classe Categoria que herda de models.Model, assim como a Evento. Agora passamos a usar as funcionalidades do Django ORM para mapear nossos atritubos, informandos os tipos deles e algumas propriedade como, o tamanho máximo, se esse campo é uma PK(Primary Key) do nosso banco de dados, assim não aceita registros duplicados, como no caso de nome: models.CharField(max_length=256, unique=True).

E também fizemos um relacionamento de Eventos com Categoria, categoria = models.ForeignKey(Categoria, on_delete=models.SET_NULL, null=True), informando quand a entidade é a ForeignKey e o que deve ser feito ao deletar um registro de Categoria, da qual Evento tenha um relacionamento. Assim ao deletar um registro em Categoria e o mesmo tenha uma relação como Evento, o valor passará a ser null, pois informamos que esse atributo pode ser null em null=True.

Para saber mais sobre models.

Migrações

Todo processo de alteração em um banco de dados, é feito em duas etapas no Django, primeiro declaramos em código qual é a sua estrutura, as tabelas represntadas pelas entidades, e colunas representadas pelos atributos dessas entidades.

Feito os mapeados podemos usar as migrações(Migrations) do Django para criar a tabelas em nosso banco de dados:

python manage.py makemigrations

Feito isso o Django criará um arquivo em agenda/migrations/0001_initiial_py:

Django

Esse arquivo é como a versão do nosso banco de dados. Que contém a estrutura das nossas mudanças no banco de dados. Conforme vamos fazendo alteraçõe no nosso banco de dados, o Django gerar arquivos de migração de forma sequencial, de modo que mantemos um histórico das alterações que foram feitas no nosso banco de dados, também conseguirmos voltar no "tempo" para uma terminada "versão" que o banco de dados estava.

Depois de geradas essas mudanças, podemos aplicá-las em nosso banco de dados:

python manage.py migrate

Isso gera outras migrations que um projeto Django também precisa, e junto delas está a nossa migration:

Django

Feito isso, podemos conectar no nosso cliente de banco de dados por uma ferramenta do próprio Django:

python manage.py dbshell

Assim podemos inspecionar esse banco, como no caso é o sqlite, usamos o comando .tables para listar nossas tabelas:

Django

Fazendo consultas pelo shell

Para acessar o shell do Django, usamos:

python manage.py shell

feito isso, acessaremos um terminal python:

Django

Do qual podemos criar registros de nossas entidades no banco de dados.

Importar as nossas entidades:

from agenda.models import Evento, Categoria

Criar uma entidade no banco de dados:

Categoria.objects.create(nome="Back-end")

Listar todas as entidades criadas:

Categoria.objects.all()

Criar um registro no banco de dados e armazenar em uma variável:

categoria = Categoria.objects.create(nome="Front-end")

Imprir o valor de uma propriedade da variável que foir armazenda:

categoria.nome

Agora de se acessarmos o nosso cliente de banco de dados pelo comando:

python manage.py dbshell

E fizermos consultas em SQL puro, podemos ver os registros criados:

.headers ON
select * from agenda_categoria;

A opção .headers ON habilita o nome das colunas das tabelas:

Django

Ou melhor ainda, crie um arquivo em ~/.sqliterc:

.mode column
.headers on
.separator ROW "\n"
.nullvalue NULL
.timer on
.changes on

Assim toda vez que acessar o CLI do sqlite o output será um pouco prettier. 😁

Perceba então, que temos duas formas de fazer consulta ao nosso banco de dados, pelo Django ORM ou pelo dbshell que se conecta com o cliente do banco de dados.

Outro exemplo para persistir dados no nosso banco de dados usando o Django ORM é criando novas intâncias e memória para depois persistir no banco de dados:

python manage.py shell
from agenda.models import Evento, Categoria
categoria3 = Categoria(nome="Fullstack)
categoria3.save()

Como a nossa classe Categoria uma extensão de models.Model, ela herda vários atributos e métodos são herdados também. Um deles é o save(), que persiste no banco de dados a instância que criamos em memória.

Também podemos fazer consultas filtrando nossos valores, equivalente ao where do SQL:

evento = Evento(nome="Aula de Django ORM", categoria="Back-end")

Django

Categoria.objects.filter(nome='Back-end')
Categoria.objects.filter(nome__contains='end')
evento = Evento(nome="Aula de Django ORM", categoria="Back-end")

Note que tentar ciar um evento, com a categoria sendo uma string, teremos um erro, dizendo que Evento.categoria deve ser uma instância de Categoria.

Django

O correto é referenciarmos um instância de Categoria para Evento.categoria, para isso vamos buscar do banco de dados um categoria pelo seu id e para criar uma nova instância e usá-la para o novo evento:

categoria_backend = Categoria.objects.get(id=1)
evento = Evento(nome="Aula de Django ORM", categoria=categoria_backend)
evento.save()

Lembrando que o atribuito id é herdado de models.Model.

Agora podemos acessar também atributos de uma entidade que é referenciada em outra:

evento.categoria.nome

E também fazermos consultar mais personalizadas:

Evento.objects.filter(categoria=categoria_backend)
Evento.objects.filter(categoria__nome="Back-end")

Para saber mais sobre queries

Django Admin

O Django dispõe de um painel administrativo, chamdo Django Admin. Para utilizá-lo precisamos ir no diretório do nosso projeto no arquivo urls.pye deixar o path para a rota admin habilitada:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(agenda_urls))
]

Feito isso ao acessar http://localhost:8000/admin, iremos para uma tela de login. Porém ainda não temos um usuário e senha criados para acessar essa aplicação.

Para isso precisamos usar uma outra funcionalidade do manage.py, o createsuperuser:

python manage.py createsuperuser

Que ira solicitar o nome do usuário, endereço de e-mail e a senha. Caso seja uma senha fraca, o Django solicita uma confirmação para continuar.

Django

Feito isso podemos fazer o log in para acessar o painel administrativo do Django.

Ao acessar veremos duas tabelas, que já são criadas por padrão em projetos Django, a Groups e Users.

Se clicarmos em Users temos os dados que existem nessa tabela. E o nosso usuário, que acabos de criar está lá, como super user que pode ser conferido pela própriedade STAFF STATUS.

Para que nossas entites no Admin precisamos registrar essas classes na administração do Django. Isso pode ser feito no diretório da nossa aplicação agenda no arquivo admin.py:

from django.contrib import admin

from agenda.models import Categoria, Evento

# Register your models here.
admin.site.register(Evento)
admin.site.register(Categoria)

Agora ao atualizarmos o painel de administração teremos acesso a essas tabelas. Assim temos uma CRUD para acessar nossos dados.

Note que algumas informações são apresentadas como Evento object(1) ou Categoria object(1), por conta da método __str__ que tentar converter nosso objeto para uma string, para isso podemos sobreescrever esse método para:

Listagem de eventos

No nosso arquivo agenda/urls.py, vamos alterar o path("", index) para uma view que traga a listagem de nossos eventos, vamos renomear index para listar_eventos com o F2, assim renomeamos também todas as suas referências.

urlpatterns = [
    path('', listar_eventos),
    path('evento', exibir_evento)
]

E também faremos alterações na nossa view de listagem:

def listar_eventos(request):
    eventos = Evento.objects.all()

    for evento in eventos:
      evento.random = '{:0>3}'.format(randrange(1, 120))

    return render(
        request=request, 
        context={ "eventos": eventos, 'get_random': get_random }, 
        template_name="agenda/listar_eventos.html"
    )

E por último o nosso template:

{% load tailwind_tags %}
<!DOCTYPE html>
<html>
  {% tailwind_css %}

<div class="container my-24 px-6 mx-auto">

    <section class="mb-32 text-gray-800 text-center">
  
      <h2 class="text-3xl font-bold mb-12 pb-4 text-center">Últimos Eventos</h2>
  
      <div class="grid lg:grid-cols-3 gap-6 xl:gap-x-12">

        {% for evento in eventos %}
  
        <div class="mb-0">
          <div class="relative block bg-white rounded-lg shadow-lg">
            <div class="flex">
              <div
                class="relative overflow-hidden bg-no-repeat bg-cover relative overflow-hidden bg-no-repeat bg-cover shadow-lg rounded-lg mx-4 -mt-4"
                data-mdb-ripple="true" data-mdb-ripple-color="light">
                <img src="https://mdbcdn.b-cdn.net/img/new/standard/city/{{ evento.random }}.webp" class="w-full" />
                <a href="#!">
                  <div
                    class="absolute top-0 right-0 bottom-0 left-0 w-full h-full overflow-hidden bg-fixed opacity-0 hover:opacity-100 transition duration-300 ease-in-out"
                    style="background-color: rgba(251, 251, 251, 0.15)"></div>
                </a>
              </div>
            </div>
            <div class="p-6">
              <h5 class="font-bold text-lg mb-3">{{ evento.nome }}</h5>
              <p class="text-gray-500 mb-4">
                <small>Published <u>{% now 'd/m/Y H:i' %}</u> by
                  <a href="" class="text-gray-900">Rodrigo Rogovski</a></small>
              </p>
              <p class="mb-4 pb-2">
                {{ evento.categoria.nome }}
              </p>
              <p class="text-gray-900 mb-4 pb-2">
                <small>
                  Local: {% firstof evento.local evento.link 'A definir' %}
                </small>
              </p>
              <a href="#!" data-mdb-ripple="true" data-mdb-ripple-color="light"
                class="inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out">Saiba mais</a>
            </div>
          </div>
        </div>

        {% endfor %}

      </div>
  
    </section>
  
  </div>

</html>

Extra: TailwindCSS

Para incluir o TailwindCSS no projeto Django veja aqui

Adicionando data aos eventos

Para que possamos adicionar data aos nossos eventos, vamos alterar o nosso modelo Evento e incluir o atributo data:

data = models.DateField(null=True)

Se tentarmos acessar a página, teremos um erro:

OperationalError at /

no such column: agenda_evento.data

Pois ainda não fizemos nossa migração para as novas alterações serem aplicadas no banco de dados. Para isso execute:

python manage.py makemigrations

Assim teremos um novo arquivo de migração, 0002_evento_data.py, onde esse tem uma depedência, o arquivo 0001_initial:

# Generated by Django 4.0.3 on 2022-03-27 15:12

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('agenda', '0001_initial'),
    ]

    operations = [
        migrations.AddField(
            model_name='evento',
            name='data',
            field=models.DateField(null=True),
        ),
    ]

E depois vamos aplicar essa migração no banco de dados:

python manage.py migrate

Para formatar a data no template, podemos usar alguns filtros:

<!-- Para campo do tipo date -->
{{ evento.data | date:'d/m/Y' }}
 <!-- Para campo do tipo datetime  -->
 {{ evento.data | date:'d/m/Y H:i' }}

Vamos também implementar um filtro para não trazer eventos que já aconteceram. Para isso vamos usar um módulo do Python de datas, para usarmos uma função que retorna a data de hoje, date.today():

from datetime import date

Para isso vamos fazer, vamos alterar a nossa função listar_eventos:

def listar_eventos(request):
    eventos = Evento.objects.filter(data__gte=date.today()).order_by('data')

    for evento in eventos:
      evento.random = '{:0>3}'.format(randrange(1, 120))
    
    get_random = '{:0>3}'.format(randrange(120))
    return render(
        request=request, 
        context={ "eventos": eventos, 'get_random': get_random }, 
        template_name="agenda/listar_eventos.html"
    )

Perceba que também fizemos um ordenação por data crescente. Caso queria a order inversa, basta:

eventos = Evento.objects.filter(data__gte=date.today()).order_by('data').reverse()

Buscando e exibindo evento

Agora fazar a view que trará o detalhe de um evento. Para isso vamos começar alterando a nossas URLs da agenda:

urlpatterns = [
    path('', listar_eventos),
    path('eventos/<int:id>', exibir_evento)
]

A rota que leva para o detalhe de um evento, recebe como parâmetro um id, que defininos ser parte da URL e informando o tipo, <int:id>.

Agora podemos recuperar esse id na função que irá receber a requisição dessa rota em views.py da agenda:

def exibir_evento(request, id):
    evento = Evento.objects.get(id=id)
    
    return render(
        request=request, 
        context={ "evento": evento }, 
        template_name="agenda/exibir_evento.html"
    )

E vamos também fazer uma tratativa para quando tentarmos acessar um recurso que não exista, levendo para uma página de erro 404. Para isso vamos importar get_objects_or_404 de django.shortcuts. E agora alteramos nossa função exibir_evento:

def exibir_evento(request, id):
    evento = get_object_or_404(Evento, id=id)
    
    return render(
        request=request, 
        context={ "evento": evento }, 
        template_name="agenda/exibir_evento.html"
    )

Navegando entre páginas da aplicação

Para isso vamos alterar o nosso template e incluir o link na tag a no atribudo href="":

<a href="/eventos/{{ evento.id }}">Saiba Mais</a>

Mas o Django tem uma funcionalidade quer permiti fazermos de outra forma:

<a href="{% url 'exibir_evento' evento.id %}">Saiba Mais</a>

Mas par que isso funcione precisamos alterar as URLs da agenda e registrar um nome para cada rota:

urlpatterns = [
    path('', listar_eventos, name="listar_eventos"),
    path('eventos/<int:id>', exibir_evento, name="exibir_evento")
]

Assim, mesmo que eu mude a URL na nossa aplicação, mas mantemos o nome, a navegação continua funcionando.

Enviado um formulário

Primeiro vamos alterar nosso modelo para receber os dados de participates, que nesse caso será uma lista de e-mails, no caso participantes:

class Categoria(models.Model):
    nome = models.CharField(max_length=256, unique=True)
    
    def __str__(self):
        return f"{self.id} - {self.nome}"

class Participante(models.Model):
    email = models.CharField(max_length=256)
    
    def __str__(self):
        return f"{self.id} - {self.email}" 
class Evento(models.Model):
    nome = models.CharField(max_length=256)
    categoria = models.ForeignKey(Categoria, on_delete=models.SET_NULL, null=True)
    local = models.CharField(max_length=256, blank=True)
    link = models.CharField(max_length=256, blank=True)
    data = models.DateField(null=True)
    participantes = models.ManyToManyField(Participante, through='EventoParticipante')
    
    def __str__(self):
        return f"{self.id} - {self.nome}"    
class EventoParticipante(models.Model):
    participante = models.ForeignKey(Participante, on_delete=models.CASCADE)
    evento = models.ForeignKey(Evento, on_delete=models.CASCADE)
    
    def __str__(self):
        return f"{self.id} - {self.evento.nome} | {self.participante.email}"

Agora temos uma classe que reprenta os participantes e mais uma classe para funcionar como a tabela pivô para o relacionamento de evento com participantes, pois um evento pode ter vários participantes.

Para saber mais sobre relacionamentos no Django.

Agora vamos preparar nosso formulário com a tag form e um input com o atributo type="hidden", para enviar o nosso id do evento e outro input para o e-mail do participante e também o crsf_token do Django por conta da segurança do CSRF:

<form action="{% url 'evento_participar' %}" method="post">
            {% csrf_token %}
            <input type="hidden" name="evento-id" id="evento-id" value="{{ evento.id }}">
            <div class="md:mt-5 md:flex flex-row">
              <input
                type="text"
                id="email"
                name="email"
                class="form-control block w-full px-4 py-2 mb-2 md:mb-0 md:mr-2 text-xl font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none"
                placeholder="Enter your email"
              />
              <button
                type="submit"
                class="inline-block px-7 py-3 bg-blue-600 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
                data-mdb-ripple="true"
                data-mdb-ripple-color="light"
              >
                Subscribe
              </button>
            </div>
          </form>

E também vamos alterar a views.py da agenda para receber a rota de participar do evento:

def evento_participar(request):
    evento_id = request.POST.get('evento-id')
    email = request.POST.get('email')
    print(f'evento_id => {evento_id}')
    print(f'email => {email}')
    evento = get_object_or_404(Evento, id=evento_id)
    participante = Participante()
    participante.email = email
    participante.save()
    evento_participante = EventoParticipante()
    evento_participante.evento = evento
    evento_participante.participante = participante
    evento_participante.save()
        
    return HttpResponseRedirect(reverse('exibir_evento', args=(evento_id,)))

Perceba que ao final da requisição é feito um redirecionamento para a view de exibir evento. Para impedir que ao atualizar a página a última ação seja feita novamente.

E declarar essa rota no arquivo urls.py da agenda:

urlpatterns = [
    path('', listar_eventos, name="listar_eventos"),
    path('eventos/<int:id>', exibir_evento, name="exibir_evento"),
    path('participar', evento_participar, name="evento_participar")
]