Aula 88 – Sinal Personalizado – Custom Signal e Mixin

Aula 88 – Sinal Personalizado – Custom Signal e Mixin

Loja Online - Django

Loja Online – Django

Voltar para página principal do blog

Todas as aulas desse curso

Aula 87                                   Aula 89

Redes Sociais:

facebook

Meus links de afiliados:

Hostinger

Digital Ocean

One.com

Melhore seu NETWORKING

https://digitalinnovation.one/

Participe de comunidades de desenvolvedores:

Fiquem a vontade para me adicionar ao linkedin.

E também para me seguir no https://github.com/toticavalcanti.

Código final da aula:

https://github.com/toticavalcanti

Quer aprender python3 de graça e com certificado? Acesse então:

https://workover.com.br/python-codigo-fluente

Canais do Youtube

Toti

Lofi Music Zone Beats

Backing Track / Play-Along

Código Fluente

Putz!

Vocal Techniques and Exercises

PIX para doações

PIX Nubank

PIX Nubank


 

Aula 88 – Sinal Personalizado – Custom Signal

O que são Signals?

Em Django, Signals são mensagens que permitem que partes diferentes do seu aplicativo se comuniquem entre si sem estarem diretamente acopladas.

É um mecanismo de envio e recebimento de notificações assíncronas quando certos eventos ocorrem no aplicativo.

Já utilizamos Signals no projeto, o pre_save.connect() por exemplo.

Quando o sinal pre_save é acionado antes de salvar um objeto, todas as funções conectadas a ele são chamadas, permitindo a execução de lógica personalizada antes que o objeto seja efetivamente salvo.

Sinal Personalizado

Só que agora vamos criar um Signal personalizado.

Pra que usar Signal?

Pra gente saber quando um determinado objeto foi visualizado.

O que faremos é criar um sinal específico para o objeto que está sendo rastreado e toda vez que esse objeto for visualizado, será enviado esse sinal específico.

Então, ao invés de importar o modelo ObjectViewed do django_ecommerce/e_commerce/analytics/models.py nas diferentes views, vamos importar o Signal que será enviado pelo nosso sinal personalizado, a cada vez que qualquer objeto for visualizado no site.

Obs. Não é a vizualização de página que queremos pegar por enquanto, queremos disparar um sinal quando um produto em particular for visualizado.

Digamos que um usuário com um determinado IP acesse a camiseta azul, esse evento vai disparar um sinal que será recebido pelo receptor responsável por fazer alguma coisa a partir disso.

Vamos começar criando o arquivo django_ecommerce/e_commerce/analytics/signals.py.

Antes da versão 4.0 do Django você poderia opcionalmente passar o providing_args para o Signal.

django_ecommerce/e_commerce/analytics/signals.py


from django.dispatch import Signal

object_viewed_signal = Signal(providing_args=['instance', 'request'])

IMPORTANTE. A partir da versão 4.0 do Django, o signal não recebe mais o providing_args, portanto a forma correta do signals.py é essa abaixo.

Agora como é feito a partir da versão 4.0 do Django, sem o parâmetro providing_args.

django_ecommerce/e_commerce/analytics/signals.py


from django.dispatch import Signal

object_viewed_signal = Signal()

object_viewed_signal será usado para notificar outros componentes do sistema quando um objeto (ou instância) for visualizado.

O sinal é definido usando a classe Signal do Django.

O providing_args não é mais um parâmetro opcional passado para o construtor do Signal a partir da versão 4.0 do Django, ele não aceita mais.

Ele especifica quais argumentos devem ser incluídos quando o sinal é emitido.

No caso do object_viewed_signal, os argumentos incluídos são ‘instance‘ e ‘request‘, indicando que quando esse sinal for emitido, ele fornecerá uma instância do objeto visualizado e o objeto request associado.

Vamos ver no código da django_ecommerce/e_commerce/products/views.py como é isso.

django_ecommerce/e_commerce/products/views.py 


from django.http import Http404

from django.views.generic import ListView, DetailView
from django.shortcuts import render, get_object_or_404

from analytics.signals import object_viewed_signal
from carts.models import Cart
from .models import Product

class ProductFeaturedListView(ListView):
    template_name = "products/list.html"
    
    def get_queryset(self, *args, **kwargs):
        request = self.request
        return Product.objects.featured()

class ProductFeaturedDetailView(DetailView):
    queryset = Product.objects.all().featured()
    template_name = "products/featured-detail.html"

#Class Based View
class ProductListView(ListView):
    #traz todos os produtos do banco de dados sem filtrar nada 
    queryset = Product.objects.all()
    template_name = "products/list.html"
    
    # def get_context_data(self, *args, **kwargs):
    #     context = super(ProductListView, self).get_context_data(*args, **kwargs)
    #     print(context)
    #     return context
    def get_context_data(self, *args, **kwargs):
        context = super(ProductListView,  self).get_context_data(*args, **kwargs)
        cart_obj, new_obj = Cart.objects.new_or_get(self.request)
        context['cart'] = cart_obj
        return context

    def get_context_data(self, *args, **kwargs):
        context = super(ProductListView,  self).get_context_data(*args, **kwargs)
        cart_obj, new_obj = Cart.objects.new_or_get(self.request)
        context['cart'] = cart_obj
        return context

#Function Based View
def product_list_view(request):
    queryset = Product.objects.all()
    context = {
        'object_list': queryset
    }
    return render(request, "products/list.html", context)

class ProductDetailSlugView(DetailView):
    queryset = Product.objects.all()
    template_name = "products/detail.html"

    def get_context_data(self, *args, **kwargs):
        context = super(ProductDetailSlugView,  self).get_context_data(*args, **kwargs)
        cart_obj, new_obj = Cart.objects.new_or_get(self.request)
        context['cart'] = cart_obj
        return context

    def get_object(self, *args, **kwargs):
        slug = self.kwargs.get('slug')
        #instance = get_object_or_404(Product, slug = slug, active = True)
        try:
            instance = Product.objects.get(slug = slug, active = True)
        except Product.DoesNotExist:
            raise Http404("Não encontrado!")
        except Product.MultipleObjectsReturned:
            qs = Product.objects.filter(slug = slug, active = True)
            instance =  qs.first()
        #instance is the sender
        object_viewed_signal.send(instance.__class__, instance = instance, request = request)
        return instance

#Class Based View
class ProductDetailView(DetailView):
    template_name = "products/detail.html"

    def get_context_data(self, *args, **kwargs):
        context = super(ProductDetailView, self).get_context_data(*args, **kwargs)
        print(context)
        return context

    def get_object(self, *args, **kwargs):
        pk = self.kwargs.get('pk')
        instance = Product.objects.get_by_id(pk)
        if instance is None:
            raise Http404("Esse produto não existe!")
        return instance

#Function Based View
def product_detail_view(request, pk = None, *args, **kwargs):
    instance = Product.objects.get_by_id(pk)
    print(instance)
    if instance is None:
        raise Http404("Esse produto não existe!")

    context = {
        'object': instance
    }
    return render(request, "products/detail.html", context)

Importamos o sinal personalizado e na classe ProductDetailSlugView, no método get_object(), o sinal é disparado da instância do produto que foi visualizado.

  1. object_viewed_signal: Este é um objeto do tipo Signal do Django. O sinal, object_viewed_signal é  usado para notificar outros componentes do sistema quando um objeto é visualizado.
  2. .send(instance.__class__, instance=instance, request=request): O método send é usado para enviar o sinal. Ele aceita dois argumentos principais:
    • Primeiro argumento (instance.__class__): Este é o remetente do sinal, ou seja, quem está enviando o sinal. Neste caso, instance.__class__ refere-se à classe do objeto que está sendo visualizado. Isso é usado para indicar que tipo de objeto está sendo visualizado.
    • Outros argumentos nomeados (instance=instance, request=request): Estes são os dados adicionais que estão sendo enviados junto com o sinal. No contexto de um objeto sendo visualizado, geralmente, você envia a instância específica do objeto (instance) que está sendo visualizado e a solicitação (request) associada à visualização.

Então, quando essa linha é executada, o sinal object_viewed_signal é enviado, indicando que um objeto de uma determinada classe foi visualizado.

Outras partes do código que estão ouvindo esse sinal podem realizar ações específicas, como rastrear estatísticas de visualização, registrar atividades ou qualquer outra coisa que seja relevante para o seu aplicativo.

Só que dessa forma que tá, vai ficar repetitivo.

Se você usar views baseadas em functions, você vai ter que se repetir, mas usando views baseadas em classes, você consegue fazer mixin e não precisa cair nessa repetição de código.

django_ecommerce/e_commerce/analytics/mixin.py


from .signals import object_viewed_signal 

class ObjectViewedMixin(object):
    def get_context_data(self, *args, **kwargs):
        context = super(ObjectViewedMixin, self).get_context_data(*args, **kwargs)
        request = self.request
        instance = context.get('object')
        if instance:
            object_viewed_signal.send(instance.__class__, instance = instance, request = request)
        return context

Um adendo (*args e **kwargs)

Lembrando que o *args e o **kwargs são usados para lidar com um número variável de argumentos em funções e métodos.

*args (Argumentos Posicionais):

  • O operador * antes do nome do parâmetro (args) permite que uma função aceite um número variável de argumentos posicionais. Isso significa que você pode passar zero ou mais valores posicionais para a função.

def exemplo_args(*args):
    for arg in args:
        print(arg)

exemplo_args(1, 2, 3, "quatro")

Neste exemplo, a função exemplo_args aceita qualquer número de argumentos posicionais e os imprime.

**kwargs (Argumentos de Palavras-Chave):

  • O operador ** antes do nome do parâmetro (kwargs) permite que uma função aceite um número variável de argumentos de palavras-chave. Isso permite que você passe argumentos nomeados para a função.

def exemplo_kwargs(**kwargs):
    for chave, valor in kwargs.items():
        print(f"{chave}: {valor}")

exemplo_kwargs(nome="Alice", idade=30, cidade="Wonderland")

Neste exemplo, a função exemplo_kwargs aceita qualquer número de argumentos de palavras-chave e os imprime.

Combinação de *args e **kwargs:

  • Você também pode usar ambos em uma função se precisar aceitar qualquer número de argumentos posicionais e de palavras-chave.

def exemplo_combinado(*args, **kwargs):
    for arg in args:
        print(arg)
    for chave, valor in kwargs.items():
        print(f"{chave}: {valor}")

exemplo_combinado(1, 2, 3, nome="Bob", idade=25)

Neste exemplo, a função exemplo_combinado aceita argumentos posicionais e de palavras-chave.

Esses recursos são úteis quando você precisa de flexibilidade na assinatura de funções, permitindo que você lide com diferentes quantidades de argumentos de maneira elegante.

O uso de *args e **kwargs é uma prática comum em Python, especialmente em contextos onde a interface da função pode variar.

No contexto do projeto, o mixin usa *args e **kwargs para permitir que as classes das Views, chamem o método get_context_data com seus próprios argumentos e o mixin encaminha esses argumentos para a implementação original da classe base usando super(). Isso ajuda na composição flexível de classes e na reutilização de código.

Voltando ao projeto

A classe chamada ObjectViewedMixin é destinada a ser usada como um mixin em views baseadas em classes. Vamos entender o que cada parte faz:

  1. from .signals import object_viewed_signal: Importa o sinal personalizado object_viewed_signal do módulo signals no mesmo diretório (o ponto antes de “signals” indica que o módulo está no mesmo pacote).
  2. class ObjectViewedMixin(object):: Define a classe mixin chamada ObjectViewedMixin, que herda de object.
  3. def get_context_data(self, *args, **kwargs):: Substitui o método get_context_data da classe pai. Este método é chamado para obter o contexto de dados que será passado para o template.
  4. context = super(ObjectViewedMixin, self).get_context_data(*args, **kwargs): Chama o método get_context_data da classe pai (superclasse) para obter o contexto padrão.
  5. request = self.request: Obtém a instância do objeto request associado à view.
  6. instance = context.get('object'): Tenta obter a instância do objeto associado ao contexto. Isso é comum em views baseadas em classes do Django, onde o objeto principal (por exemplo, um modelo) é frequentemente adicionado ao contexto com a chave ‘object’.
  7. if instance:: Verifica se a instância do objeto foi obtida com sucesso.
  8. object_viewed_signal.send(instance.__class__, instance=instance, request=request): Se houver uma instância, envia o sinal object_viewed_signal. Este sinal notifica outros componentes do sistema que um objeto foi visualizado. Ele envia a classe da instância, a instância do objeto e o objeto de solicitação (request) como argumentos.
  9. return context: Retorna o contexto atualizado, que incluirá qualquer adição feita pelo método mixin.

Em resumo, esse mixin foi projetado para adicionar a funcionalidade de envio de um sinal sempre que um objeto é visualizado em uma visão baseada em classe do Django. Ele é útil para rastrear eventos de views e pode ser usado para realizar ações adicionais sempre que um objeto é acessado.

Vamos modificar o django_ecommerce/e_commerce/products/views.py para usar o mixin acima.

django_ecommerce/e_commerce/products/views.py 


from django.http import Http404

from django.views.generic import ListView, DetailView
from django.shortcuts import render, get_object_or_404

from analytics.mixin import ObjectViewedMixin
from carts.models import Cart
from .models import Product

class ProductFeaturedListView(ListView):
    template_name = "products/list.html"
    
    def get_queryset(self, *args, **kwargs):
        request = self.request
        return Product.objects.featured()

class ProductFeaturedDetailView(ObjectViewedMixin, DetailView):
    queryset = Product.objects.all().featured()
    template_name = "products/featured-detail.html"

#Class Based View
class ProductListView(ListView):
    #traz todos os produtos do banco de dados sem filtrar nada 
    queryset = Product.objects.all()
    template_name = "products/list.html"
    
    # def get_context_data(self, *args, **kwargs):
    #     context = super(ProductListView, self).get_context_data(*args, **kwargs)
    #     print(context)
    #     return context
    def get_context_data(self, *args, **kwargs):
        context = super(ProductListView,  self).get_context_data(*args, **kwargs)
        cart_obj, new_obj = Cart.objects.new_or_get(self.request)
        context['cart'] = cart_obj
        return context

    def get_context_data(self, *args, **kwargs):
        context = super(ProductListView,  self).get_context_data(*args, **kwargs)
        cart_obj, new_obj = Cart.objects.new_or_get(self.request)
        context['cart'] = cart_obj
        return context

#Function Based View
def product_list_view(request):
    queryset = Product.objects.all()
    context = {
        'object_list': queryset
    }
    return render(request, "products/list.html", context)

class ProductDetailSlugView(ObjectViewedMixin, DetailView):
    queryset = Product.objects.all()
    template_name = "products/detail.html"

    def get_context_data(self, *args, **kwargs):
        context = super(ProductDetailSlugView,  self).get_context_data(*args, **kwargs)
        cart_obj, new_obj = Cart.objects.new_or_get(self.request)
        context['cart'] = cart_obj
        return context

    def get_object(self, *args, **kwargs):
        slug = self.kwargs.get('slug')
        #instance = get_object_or_404(Product, slug = slug, active = True)
        try:
            instance = Product.objects.get(slug = slug, active = True)
        except Product.DoesNotExist:
            raise Http404("Não encontrado!")
        except Product.MultipleObjectsReturned:
            qs = Product.objects.filter(slug = slug, active = True)
            instance =  qs.first()
        #instance is the sender
        #object_viewed_signal.send(instance.__class__, instance = instance, request = request)
        return instance

#Class Based View
class ProductDetailView(ObjectViewedMixin, DetailView):
    template_name = "products/detail.html"

    def get_context_data(self, *args, **kwargs):
        context = super(ProductDetailView, self).get_context_data(*args, **kwargs)
        print(context)
        return context

    def get_object(self, *args, **kwargs):
        pk = self.kwargs.get('pk')
        instance = Product.objects.get_by_id(pk)
        if instance is None:
            raise Http404("Esse produto não existe!")
        return instance

#Function Based View
def product_detail_view(request, pk = None, *args, **kwargs):
    instance = Product.objects.get_by_id(pk)
    print(instance)
    if instance is None:
        raise Http404("Esse produto não existe!")

    context = {
        'object': instance
    }
    return render(request, "products/detail.html", context)

A mudança para o uso de um mixin no código traz benefícios relacionados à reutilização de código e à manutenção.

Vamos examinar as razões para essa mudança:

1. Reutilização de Código:

  • Sem o Mixin:
    • Em cada view baseada em classe que necessitava do sinal object_viewed_signal, você precisava repetir o código que envia o sinal.
  • Com o Mixin:
    • Com o uso do mixin, o código para enviar o sinal é encapsulado no mixin ObjectViewedMixin. Agora, cada view que herda esse mixin ganha automaticamente a funcionalidade de enviar o sinal, sem a necessidade de replicar o código.

2. Centralização da Lógica:

  • Sem o Mixin:
    • A lógica para enviar o sinal estaria dispersa em várias views, tornando-a mais difícil de ser gerenciada e mantida.
  • Com o Mixin:
    • Ao centralizar essa lógica em um mixin, qualquer modificação necessária pode ser feita em um único local, proporcionando uma manutenção mais fácil e evitando erros decorrentes de alterações em várias partes do código.

3. Conformidade com o Conceito de Mixin:

  • Mixin:
    • Os mixins são uma prática comum em programação orientada a objetos, especialmente em linguagens como Python. Eles são utilizados para compartilhar funcionalidades entre classes sem a necessidade de herança múltipla.
  • Vantagens do Mixin nesse caso:
    • Os mixins proporcionam um meio flexível de estender as funcionalidades de classes, mantendo uma estrutura de código mais modular e coesa.

4. Melhor Organização do Código:

  • Sem o Mixin:
    • O código para enviar o sinal estava misturado com o código específico de cada view, tornando-o menos organizado.
  • Com o Mixin:
    • O mixin isola a funcionalidade do sinal, proporcionando uma organização mais clara do código.

5. Prevenção de Repetição:

  • Sem o Mixin :
    • Haveria uma duplicação de código nas views que precisavam enviar o sinal.
  • Com o Mixin:
    • O uso do mixin evita a repetição, promovendo uma abordagem DRY (Don’t Repeat Yourself).

6. Flexibilidade:

  • Se, no futuro, você precisar fazer alterações ou adicionar mais funcionalidades relacionadas ao sinal, pode fazer isso de maneira centralizada no mixin, afetando todas as views que o utilizam.

Em resumo, o uso de mixins neste contexto segue boas práticas de programação orientada a objetos, facilita a manutenção do código e promove a reutilização de funcionalidades em várias partes do seu aplicativo.

Por essa aula é só, na próxima a gente vai fazer o handle do sinal emitido pelos objetos visualizados.

Voltar para página principal do blog

Todas as aulas desse curso

Aula 87                                   Aula 89

Código final da aula:

https://github.com/toticavalcanti

Canais do Youtube

Toti

Backing Track / Play-Along

Código Fluente

Putz!

Vocal Techniques and Exercises

Dêem um joinha 👍 na página do Código Fluente no
Facebook.

Sigam o Código Fluente no Instagram e no TikTok.

Código Fluente no Pinterest.

Meus links de afiliados:

Hostinger

Digital Ocean

One.com

Nos vemos na próxima então, \o/  😉 Bons Estudos!

About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>