Exemplo em Django – parte 6

django-6_abertura

A quinta parte efetivamente concluiu o desenvolvimento do “Agenda de Eventos” com os acertos finais de configuração do Django, na interface de administração e na tela principal. Esta parte está reservada para a expansão da aplicação, ou seja, a inclusão de uma nova funcionalidade, que neste caso é o recurso de comentários  nos eventos.

Antes de começar…

Claro, as alterações foram aplicadas no repositório do projeto no GitHub e estão disponíveis para quem quiser clonar ou simplesmente atualizar sua cópia local.

$ git pull
...
$ git checkout master
$ source ./py3/bin/activate

Como a lista de módulos sofreu um acréscimo, é preciso executar o PIP

$ pip install -r requirements.txt

Para garantir que todos sejam devidamente instalados.

Comentários nos eventos

django-6_comentarios

Já que a opção criada para a visualização individual de eventos ficou sem utilidade, ela receberá uma passando a exibir e também permitir a inserção de comentários. Eles poderão ser inseridos por qualquer um, bastando preencher nome, endereço de e-mail e, claro, o comentário propriamente dito e serão exibidos em ordem decrescente (do mais recente para o mais antigo).

Utilizando o Gravatar (libgravatar)

Para deixar esta opção mais interessante optei por utilizar o Gravatar para identificar os autores dos comentários. Assim, se o usuário tiver uma conta no serviço a aplicação utilizará a imagem configurada no perfil, caso contrário exibirá uma imagem específica para este endereço de e-mail.

Em Python 3 há uma biblioteca específica para acessar e recuperar informações da API do Gravatar , a libgravatar:

$ pip install libgravatar
Collecting libgravatar
...
Installing collected packages: libgravatar
Successfully installed libgravatar-0.2.3

A instalação é simples, depois é necessário atualizar as dependências do projeto com:

$ pip freeze > requirements.txt

Daí é possível instalá-lo automaticamente — o que foi feito acima após a atualização do repositório.

Modelo

Primeiro, a criação de uma nova classe, a Comment, que armazenará os comentários recebidos:

from django.db import models
from django.utils import timezone
from libgravatar import Gravatar
...
class Comment(models.Model):
    author = models.CharField(max_length=80)
    email = models.EmailField()
    text = models.CharField(max_length=160)
    commented = models.DateTimeField(default=timezone.now)
    event = models.ForeignKey(Event,
        on_delete=models.CASCADE,
        related_name='comment_event')

    def avatar(self):
        g = Gravatar(self.email)
        return g.get_image(default='identicon')
 
     def __str__(self):
        return "{} commentou em {:%c}".format(
            self.author, self.commented)

Serão armazenados o nome e endereço de e-mail do autor, assim como o comentário e a data e hora em que foi enviado. A diferença com o modelo definido antes é incluir também um atributo para referenciar o evento ao qual ele pertence com a definição de uma chave estrangeira fazendo com que cada evento possa conter diversos comentários.

O “on_delete=CASCADE” significa que quando o evento for removido, os comentários a ele relacionado também serão apagados (em cascata).

O método avatar() se encarrega de transformar o endereço de e-mail do autor em uma URL para a imagem definida pelo mesmo em seu perfil no Gravatar ou então uma imagem padrão provida pela API.

Daí é gerar as alterações que serão necessárias na base de dados:

$ python manage.py makemigrations
 Migrations for 'events':
 events/migrations/0002_comment.py
 - Create model Comment

Efetuar a migração:

$ python manage.py migrate
 Operations to perform:
 Apply all migrations: admin, auth, contenttypes, events, sessions
 Running migrations:
 Applying events.0002_comment... OK

E pronto!

Formulário

Um novo formulário baseado no modelo recém definido também precisa ser criado:

from django import forms
from .models import Event, Comment
...
class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['text', 'author', 'email', 'event']

Nele o atributo ‘commented’ não precisou ser incluído pois seu valor padrão é o resultado de timezone.now() e já é justamente a informação de que se precisa.

Gabaritos

Começando justamente pelo gabarito onde ficarão os comentários, o “./events/templates/show.html”:

{% if comments|length > 0 %}
    ...
    {% for comment in comments %}
    ... img class="mr-3" src="{{ comment.avatar }}" ...
    ... {{ comment.text }} ...
    ... {{ comment.author }} | {{ comment.commented }} ...
    {% endfor %}
{% else %}
    ... Não há comentários para este evento!
    Seja o primeiro a comentar! ...
{% endif %}

Aqui, se o número de comentários no evento for maior do que zero eles serão listados do mais recente para o mais antigo (a ordem vem pronta da visão) ou, caso contrário, será impressa uma mensagem avisando que não existem comentários junto com um convite para se comentar.

Para tornar a área de comentários facilmente acessível uma pequena alteração no gabarito dos eventos de um determinado dia, o “./events/template/day.html” com a inclusão de um novo elemento:

...
{% if event.number_of_comments > 0 %}
    {{ event.number_of_comments }}
    comentário{{ event.number_of_comments|pluralize }}
{% else %}
    Comentar!
{% endif %}
...

Isto mostrará um badge contendo a quantidade de comentários ou então o texto “Comentar!” e independente do conteúdo exibido ambos apontam para o mesmo lugar.

Como a relação entre as tabelas funciona “nos dois sentidos” é possível consultar quantos comentários um determinado evento possui e para simplificar um pouco, acrescentei dentro do modelo Event, um novo método, o number_of_comments():

...
    def number_of_comments(self):
        return self.comment_event.count()
...

O atributo “comment_event” é justamente o nome dado para a relação — related_name — entre as classes. E o método count() é parte do Djando ORM e retorna o número de elementos dentro de um QuerySet.

Visão

Alterados modelo, formulário e gabarito é a vez de colocar tudo para funcionar editando a visão:

from .models import Event, Comment
from .forms import EventForm, CommentForm
...
def show(request, id: int):
    event = get_object_or_404(Event, id=id)

    if request.method == "POST":
        form = CommentForm(request.POST)
        if form.is_valid:
            form.save()
            return redirect('agenda-events-show', id=id)

    context = {
        'event': event,
        'comments': Comment.objects.filter(event=id)
            .order_by('-commented'),
        'hide_new_button': True,
        'priorities': Event.priorities_list,
        'today': localdate(),
    }

    return render(request, 'show.html', context)

Se a URL for acessada via POST, então ela contém os campos que compõe um comentário na requisição, neste caso é criada uma instância do formulário CommentForm e sendo dados válidos eles serão salvos no banco de dados e depois redirecionará para mesma página — Como assim? Isto é para evitar que a recarga da página submeta o comentário outra vez.

Dentro de ‘context’ os comentários do evento são carregados e colocados na ordem desejada. Assim como ‘hide_new_button’ recebe o valor True para que o botão de inclusão de eventos não seja exibido.

Interface de Administração

E por último o modelo Comment é registrado dentro da interface de administração.

...
admin.site.register(Comment)

Mas é claro que é possível deixá-lo igual a com Event ficou, basta copiar o que foi feito nele… 🙂

Considerações finais

Este é o fim! Espero ter conseguido tratar aqui dos conceitos básicos do Django e conseguido dar uma ideia dos principais componentes e também dos recursos que estão disponíveis neste framework. O código fonte e os demais arquivos do programa estão disponíveis no repositório do projeto no GitHub e, em caso de dúvida, basta perguntar!

Anúncios