<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eduardo Oliveira</title>
    <description>The latest articles on DEV Community by Eduardo Oliveira (@eduardojm).</description>
    <link>https://dev.to/eduardojm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F689579%2F5b75d111-2083-4d65-9874-1f5642a73e70.jpg</url>
      <title>DEV Community: Eduardo Oliveira</title>
      <link>https://dev.to/eduardojm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eduardojm"/>
    <language>en</language>
    <item>
      <title>HTMX: por que eu uso?</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Wed, 19 Jun 2024 23:29:44 +0000</pubDate>
      <link>https://dev.to/eduardojm/htmx-por-que-eu-uso-akl</link>
      <guid>https://dev.to/eduardojm/htmx-por-que-eu-uso-akl</guid>
      <description>&lt;p&gt;Nos últimos dias surgiram diversos tweets com opiniões, indicações, ou pedindo textos de referência sobre o uso do HTMX. Optei pelo uso do HTMX em alguns projetos, inclusive em produção, e pela falta de conteúdos mostrando usos reais da biblioteca, decidi começar a escrever. Esse texto, em específico, nasceu de alguns desses questionamentos do twitter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conteúdos &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Conteúdos&lt;/li&gt;
&lt;li&gt;Introdução&lt;/li&gt;
&lt;li&gt;O que é o HTMX?&lt;/li&gt;
&lt;li&gt;
Contexto e Motivos pela escolha

&lt;ul&gt;
&lt;li&gt;1. Template Engine&lt;/li&gt;
&lt;li&gt;2. Django-Admin&lt;/li&gt;
&lt;li&gt;3. Autenticação e Permissão&lt;/li&gt;
&lt;li&gt;4. Funcionalidades do Django&lt;/li&gt;
&lt;li&gt;5. Escopo&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Alguns contrapontos&lt;/li&gt;

&lt;li&gt;Quer dizer, então, que agora eu só uso HTMX?&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introdução &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Esse texto foi inspirado por uma publicação do Mario Souto (&lt;a href="https://x.com/omariosouto" rel="noopener noreferrer"&gt;omariosouto&lt;/a&gt;) no twitter ao questionar sobre a quantidade de gente indicando HTMX recentemente e se essas pessoas estavam usando HTMX em produção. &lt;/p&gt;

&lt;p&gt;O tweet pode ser visto abaixo:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1803414625276305704-138" src="https://platform.twitter.com/embed/Tweet.html?id=1803414625276305704"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1803414625276305704-138');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1803414625276305704&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Tenho trabalhado com HTMX (Django + HTMX, pra especificar a stack), inclusive em produção (Veja o texto &lt;a href="https://dev.to/eduardojm/django-htmx-e-react-usando-htmx-para-alem-de-todo-lists-3amo"&gt;Django, Htmx e React: usando HTMX para além de TODO-Lists&lt;/a&gt;) e resolvi escrever esse texto para elucidar o contexto e os motivos que me levaram a decisão de usá-lo.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é o HTMX? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext&lt;/p&gt;

&lt;p&gt;Home do &lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;htmx.org&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Resumidamente, o HTMX é uma biblioteca JavaScript que permite que você use transições, faça requisições e coisas do tipo utilizando atributos HTML para construir interfaces modernas utilizando HTML (principalmente em servidores que renderizam HTML, como é o caso do Django).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmzd8iarzxainpr4woyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmzd8iarzxainpr4woyh.png" alt="meme sobre usar html" width="800" height="847"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto e Motivos pela escolha &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Template Engine &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;O Django é, por padrão, um framework que implementa um sistema de templates que devolve HTML para o navegador. Junto com o sistema de templates, existe um sistema de gerenciamento de arquivos estáticos.&lt;/p&gt;

&lt;p&gt;Esse primeiro contexto é importante aqui pois ao utilizar a stack Django + HTMX, não é necessário implementar, adaptar ou configurar nenhum sistema de templates.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Django-Admin &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;O &lt;a href="https://docs.djangoproject.com/en/5.0/ref/contrib/admin/" rel="noopener noreferrer"&gt;Django-Admin&lt;/a&gt; é uma aplicação (ou um módulo, por assim chamar), incluída junto a distribuição django e que fornece uma interface de administração fácil de ser utilizada para gerenciar os &lt;strong&gt;models&lt;/strong&gt; criados dentro dos projetos.&lt;/p&gt;

&lt;p&gt;A partir do item anterior, já é possível imaginar que o Django-Admin usa, fundamentalmente, templates HTML e renderização server-side (que devolve o HTML).&lt;/p&gt;

&lt;p&gt;Em alguns casos, quando há a necessidade de alguma funcionalidade com maior dinamização dentro do django-admin é mais factível e rápido usar uma biblioteca que faça a ponte para o mesmo sistema de templates HTML que utilizar um framework ou biblioteca front-end, como o React.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Autenticação e Permissão &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Assim como o conjunto de utilitários para o painel administrativo, o django traz por padrão um sistema de autenticação e permissões configurável e utilizar o HTMX remove a necessidade de inserir outros tipos de autenticação (como JWT amplamente usada para API's).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Outro ponto relevante sobre a autenticação e permissão é que ao utilizar o Django-Admin e uma aplicação Django padrão server-side, a autenticação entre ambiente de admin e aplicação é compartilhada de forma simples, já que, na prática, é o mesmo site.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Funcionalidades do Django &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;O Django possui facilitadores como sistema de formulários e widgets que se integram muito bem com a Template Engine e facilitam coisas como validar os dados e, consequentemente, armazenar no banco de dados.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Escopo &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Tudo o que descrevi acima foi bastante decisivo para a escolha do HTMX, mas, talvez, o que tenha sido mais decisivo foram os escopos das aplicações e funcionalidades onde o HTMX foi utilizado. São escopos onde os formulários são simples, não exigiam muitas manipulações ou validações. Não existem formulários com muita complexidade (por exemplo, um formulário de cadastro de cliente onde você pode adicionar vários dependentes no cadastro, etc.).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mesmo que na maioria dos formulários complexos eu considere como falha de decisões, nem sempre essas decisões são dos devs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dados os motivos acima, ter conhecimento do framework de back-end / server-side ao qual se vai trabalhar, aliado ao HTMX, é importante para decidir se os requisitos dos formulários e outras necessidades podem, ou não, se tornar empecilhos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alguns contrapontos &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Nem tudo são flores. &lt;/p&gt;

&lt;p&gt;Em algumas situações é difícil fazer o que se quer fazer com a biblioteca, principalmente pela baixa quantidade, atualmente, de conteúdos.&lt;/p&gt;

&lt;p&gt;Assim como em framework's front-end, como o react, lidar com redirecionamento de URL's pode ser bastante complexo, por exemplo pra preservar filtros em requisições via HTMX.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Existem algumas complexidades com que eu preciso lidar que eu, no atual momento, não transfiro essa responsabilidade ao HTMX e, portanto, não irei listá-las aqui.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Quer dizer, então, que agora eu só uso HTMX? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Bem, não. Por mais que eu tenha gostado bastante do desenvolvimento com o HTMX integrado ao Django, uso em poucos projetos e, na maioria das vezes, não é a única forma de dinamização das páginas (em alguns casos é preciso utilizar bastante JavaScript Vanilla ou outras bibliotecas como o React).&lt;/p&gt;

&lt;p&gt;Além disso, algumas aplicações com as quais trabalho continuam sendo feitas com API's Rest usando JSON e front-end's como SPA's. &lt;/p&gt;

&lt;p&gt;Tudo depende da análise dos pontos listados acima e de contextos das necessidades do negócio, se o projeto já está em andamento, se existe time, se é um projeto que farei sozinho como freelancer, dentre outras questões.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Django, Htmx e React: usando HTMX para além de TODO-Lists</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Tue, 09 Apr 2024 23:40:57 +0000</pubDate>
      <link>https://dev.to/eduardojm/django-htmx-e-react-usando-htmx-para-alem-de-todo-lists-3amo</link>
      <guid>https://dev.to/eduardojm/django-htmx-e-react-usando-htmx-para-alem-de-todo-lists-3amo</guid>
      <description>&lt;p&gt;Já faz algum tempo que o &lt;strong&gt;HTMX&lt;/strong&gt;, que me foi apresentado pelo &lt;a href="https://twitter.com/JeffQuesado"&gt;@JeffQuesado&lt;/a&gt;, me pareceu uma ideia muito legal para se trabalhar junto com Django, principalmente o &lt;strong&gt;Django-Admin&lt;/strong&gt; que já tem uma estrutura fortemente baseada em &lt;em&gt;server-side&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conteúdos &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Conteúdos&lt;/li&gt;
&lt;li&gt;Introdução&lt;/li&gt;
&lt;li&gt;Contextualização da Plataforma Atual&lt;/li&gt;
&lt;li&gt;Entendendo o Problema&lt;/li&gt;
&lt;li&gt;Por que HTMX se já usávamos React?&lt;/li&gt;
&lt;li&gt;
Questões técnicas

&lt;ul&gt;
&lt;li&gt;Comunicação entre HTMX e React&lt;/li&gt;
&lt;li&gt;Confirmação para execução de ações&lt;/li&gt;
&lt;li&gt;Como é que isso ai funciona?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;E o futuro?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introdução &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Em uma demanda de um dos projetos em que atuo, precisávamos de uma funcionalidade para a qual o uso do &lt;strong&gt;HTMX&lt;/strong&gt; se encaixaria perfeitamente e esse texto tem como objetivo levantar questões pelas quais foi decidido o uso do &lt;strong&gt;HTMX&lt;/strong&gt; e não de outras ferramentas e elucidar como foram feitas integrações entre &lt;strong&gt;Django&lt;/strong&gt;, &lt;strong&gt;HTMX&lt;/strong&gt; e &lt;strong&gt;React&lt;/strong&gt; para construir ferramentas reais.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O título é uma inspiração de um &lt;em&gt;tweet&lt;/em&gt; que vi, alguns dias atrás, que questionava se alguém estava utilizando o &lt;strong&gt;HTMX&lt;/strong&gt; para algo mais além de criar TODO-Lists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Contextualização da Plataforma Atual &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;O back-end do projeto utiliza o &lt;strong&gt;Django&lt;/strong&gt; com &lt;strong&gt;Django-Rest-Framework&lt;/strong&gt; para servir uma API de integração para um app mobile e, para gerenciamento dos conteúdos, o &lt;strong&gt;Django-Admin&lt;/strong&gt; é utilizado.&lt;/p&gt;

&lt;p&gt;O django-admin possuí, nas páginas de formulário, uma estrutura parecida com a seguinte: um cabeçalho ocupado toda a largura da página, e logo a baixo uma sidebar e o conteúdo principal, que no caso é um formulário.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3jj3a904ikbj4vitvzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3jj3a904ikbj4vitvzy.png" alt="Cabeçalho ocupando toda a largura da página e logo abaixo uma sidebar e o conteúdo principal, que no caso é um formulário" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Em alguns formulários, a estrutura e arquitetura de informação dos &lt;em&gt;models&lt;/em&gt; demandava um controle de carregamento e de exibição de itens complexo usando &lt;em&gt;lazy-loading&lt;/em&gt;. Desse modo, quando o ferramental do &lt;strong&gt;django&lt;/strong&gt; e do &lt;strong&gt;django-admin&lt;/strong&gt; deixou de suprir as necessidades, parte desses formulários foi construída utilizando aplicações pequenas com &lt;strong&gt;React&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Essas aplicações &lt;strong&gt;React&lt;/strong&gt; que eram carregadas apenas em páginas específicas não demandavam, por exemplo, o uso de &lt;em&gt;routers&lt;/em&gt; e coisas do tipo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entendendo o Problema &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;O painel de administração é utilizado para algo próximo a um CMS (&lt;em&gt;content management system&lt;/em&gt;) para um app de idiomas. Durante a criação de algumas "entidades" dentro da plataforma, seria necessário uma ferramenta, em &lt;em&gt;popup&lt;/em&gt;, para consultar as "entidades" já cadastradas, pois esse processo atualmente era gerenciado com planilhas externas, aumentando a complexidade no uso do painel.&lt;/p&gt;

&lt;p&gt;Essa mesma ferramenta deveria, também, fornecer a opção de clonar entidades para a página em que se estava trabalhando (alternado a entidade paí/chave estrangeira).&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que HTMX se já usávamos React? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Quando começamos a avaliar a funcionalidade, a primeira discussão foi: será que o &lt;strong&gt;HTMX&lt;/strong&gt; se encaixa bem aqui? ou será melhor lidar com &lt;strong&gt;React&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Um dos principais pontos levados em consideração: a estrutura do &lt;strong&gt;django&lt;/strong&gt; e do &lt;strong&gt;django-admin&lt;/strong&gt; já trazia uma quantidade enorme de código e funcionalidades que poderiam ser reutilizadas para a exibição, como paginação, etc. na aplicação &lt;strong&gt;react&lt;/strong&gt; que roda nessa aplicação não lidava com paginação nem com diversas outras coisas que demandaria muito mais tempo para implementar que utilizando partes dos &lt;em&gt;templates&lt;/em&gt; do &lt;strong&gt;django-admin&lt;/strong&gt; adaptados.&lt;/p&gt;

&lt;p&gt;Como essa funcionalidade não seria, sempre, inserida nas mesmas páginas que o formulário &lt;strong&gt;react&lt;/strong&gt;, haveria muito trabalho pra conciliar o uso do &lt;strong&gt;react&lt;/strong&gt;, &lt;strong&gt;bundlers&lt;/strong&gt;, etc. &lt;/p&gt;

&lt;p&gt;Esse conjunto de fatores foram os principais motivantes para a adoção do &lt;strong&gt;HTMX&lt;/strong&gt;, que diferente do &lt;strong&gt;react&lt;/strong&gt; que teriamos que lidar com &lt;strong&gt;bundler&lt;/strong&gt;, só importamos o script onde fosse necessário utilizar as tags do &lt;strong&gt;HTMX&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A simplicidade que o &lt;strong&gt;HTMX&lt;/strong&gt; trouxe para essa implementação, talvez tivesse sido utilizada no lugar do &lt;strong&gt;react&lt;/strong&gt; se a conhecêssemos na época.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Questões técnicas &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Algumas questões que foram foco dos esforços no trabalho com essa funcionalidade serão tratadas nessa seção. Essas questões não foram, necessariamente, complexas ou problemáticas. São apenas pontos aos quais gostaria de comentar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comunicação entre HTMX e React &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Como comentado, em algum lugar no texto acima, era necessário clonar itens para o formulário atual. Formulário esse, construído em &lt;strong&gt;React&lt;/strong&gt;. Assim, ao integrar uma ação para clonar o item, utilizando o &lt;code&gt;hx-post&lt;/code&gt;, é necessário que algum &lt;em&gt;trigger&lt;/em&gt; seja disparado para que alguma parte da app &lt;strong&gt;React&lt;/strong&gt; possa processar. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Um detalhe, ao qual não vou adentrar aqui, é que esses "links" para ações estão dentro de um formulário, então na chamada para o &lt;code&gt;hx-post&lt;/code&gt; todos os campos do formulário também são enviados.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt;
  &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/admin/my-action"&lt;/span&gt;
  &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"confirmed"&lt;/span&gt;
  &lt;span class="na"&gt;hx-indicator=&lt;/span&gt;&lt;span class="s"&gt;".htmx-indicator"&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Clonar"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;hx-trigger="confirmed"&lt;/code&gt; se deve a seção abaixo, onde tratamos sobre a confirmação para a execução da ação. Fora isso, no lado do servidor, utilizando &lt;code&gt;django-htmx&lt;/code&gt; podemos inserir uma header &lt;code&gt;HX-Trigger&lt;/code&gt; para disparar um &lt;em&gt;trigger&lt;/em&gt; quando a ação do &lt;strong&gt;HTMX&lt;/strong&gt; for finalizada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my_template.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HX-Trigger&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;completeClone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dentro do cliente &lt;strong&gt;react&lt;/strong&gt; podemos adicionar o evento ao &lt;code&gt;document&lt;/code&gt; devido ao event bubbling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completeClone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completeClone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Confirmação para execução de ações &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Para exibir confirmações de execução das ações, utilizamos a biblioteca SweetAlert2, que já estava no projeto pra ser utilizada em alguns outros momentos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;confirmClone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deseja clonar o item &amp;lt;strong&amp;gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&amp;lt;/strong&amp;gt;? Essa ação não poderá ser desfeita!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;Swal&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fire&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tem certeza?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;showCancelButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;confirmButtonText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sim, Clonar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cancelButtonText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cancelar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isConfirmed&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;htmx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirmed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A linha &lt;code&gt;htmx.trigger(element, 'confirmed');&lt;/code&gt; dispara o &lt;em&gt;trigger&lt;/em&gt; que está no &lt;code&gt;hx-trigger="confirmed"&lt;/code&gt; do &lt;code&gt;span&lt;/code&gt;. Aqui é preciso completar, também, a tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt;
  &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/admin/my-action"&lt;/span&gt;
  &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"confirmed"&lt;/span&gt;
  &lt;span class="na"&gt;hx-indicator=&lt;/span&gt;&lt;span class="s"&gt;".htmx-indicator"&lt;/span&gt;
  &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"confirmClone(this, 'título do item!!!!')"&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Clonar"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Como é que isso ai funciona? &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A implementação com &lt;strong&gt;HTMX&lt;/strong&gt; fluiu muito bem, mas em diversos momentos foi necessária muita experimentação por falta de clareza do que, exatamente, os comandos fazem ou como lidar para que eles fizessem exatamente o que queria.&lt;/p&gt;

&lt;p&gt;Mas, esse pode ser uma &lt;em&gt;skill issue&lt;/em&gt; do prazo e de ser a primeira vez utilizando a documentação, tanto que as duas seções acima foram fortemente baseadas pelos seus exemplos (mas não pela sua documentação).&lt;/p&gt;

&lt;h2&gt;
  
  
  E o futuro? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Bom, desenvolver uma funcionalidade real com &lt;strong&gt;HTMX&lt;/strong&gt; foi uma experiência muito boa e creio que o projeto tem muito potêncial, apesar que em diversos momentos, a depender da estrutura e do tamanho do projeto, eu continue achando que uma API (Rest ou &lt;em&gt;intent based&lt;/em&gt;) com um cliente front-end ainda seja mais simples de se manipular.&lt;/p&gt;

&lt;p&gt;Ainda assim, espero ter mais chances de usar o &lt;strong&gt;HTMX&lt;/strong&gt; integrado ao &lt;strong&gt;django-admin&lt;/strong&gt; que é, fundamentalmente, &lt;strong&gt;server-side&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>django</category>
      <category>htmx</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Sua aplicação é um labirinto? Usabilidade e URL's</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Thu, 15 Feb 2024 11:06:33 +0000</pubDate>
      <link>https://dev.to/eduardojm/sua-aplicacao-e-um-labirinto-usabilidade-e-urls-98p</link>
      <guid>https://dev.to/eduardojm/sua-aplicacao-e-um-labirinto-usabilidade-e-urls-98p</guid>
      <description>&lt;p&gt;De tempos em tempos, aparecem pessoas comentando sobre a possibilidade do uso de &lt;em&gt;query strings&lt;/em&gt; (&lt;a href="https://en.wikipedia.org/wiki/Query_string"&gt;referência&lt;/a&gt;) para armazenar estados ao invés de usar estados "invisíveis" e não rastreáveis (mais adiante você entenderá o que eu quero dizer com não rastreáveis) como se fosse algo super inovador. &lt;/p&gt;

&lt;p&gt;A verdade é que estamos voltando às origens.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introdução&lt;/li&gt;
&lt;li&gt;
Estados Invisíveis

&lt;ul&gt;
&lt;li&gt;O problema&lt;/li&gt;
&lt;li&gt;As soluções&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Infinite Loading&lt;/li&gt;
&lt;li&gt;
Navegação em Abas

&lt;ul&gt;
&lt;li&gt;Contraponto: O Contexto que importa&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Filtros de busca

&lt;ul&gt;
&lt;li&gt;Atualizando a listagem filtro por filtro&lt;/li&gt;
&lt;li&gt;Selecionando todos os filtros e aplicando&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Modais e Caixas de Diálogos&lt;/li&gt;
&lt;li&gt;
Dicas de como melhorar esses pontos da usabilidade

&lt;ul&gt;
&lt;li&gt;Estude sobre UX&lt;/li&gt;
&lt;li&gt;Benchmarking&lt;/li&gt;
&lt;li&gt;Pense como um usuário&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Referências&lt;/li&gt;
&lt;li&gt;Produtos Mostrados no Texto&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introdução &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Esse texto tem como objetivo mostrar, de forma conceitual, casos de uso em que esse padrão é bastante utilizado e que, ao longo do tempo, começou a ser negligenciado, principalmente em SPA's (Single Page Applications).&lt;/p&gt;

&lt;p&gt;Não será objetivo, aqui, explicar como devem ser feitas as implementações desses comportamentos em frameworks ou bibliotecas. Ainda assim, quando possível, citações e links de referências para documentações serão adicionados.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Partes do texto serão ilustradas com imagens e GIF's de aplicações, reais, em produção. O objetivo não é críticar o trabalho dos seus desenvolvedores.&lt;/p&gt;

&lt;p&gt;É importante dizer, também, que algumas dessas animações, em GIF's, podem demorar a carregar, porém esse é um "manual" ilustrado que não seria a mesma coisa sem elas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Estados Invisíveis &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;O primeiro tema que iremos discutir são os estados invisíveis e não rastreáveis (esse nome é, de fato, ruim e não sei o motivo pelo qual decidi usar, mas quando falo de rastreabilidade é, basicamente, a rastreabilidade quanto ao histórico do navegador e nos próximos tópicos você entenderá isso).&lt;/p&gt;

&lt;h3&gt;
  
  
  O problema &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Durante o processo de criação de aplicações front-end é comum que sejam utilizados estados (&lt;a href="https://react.dev/reference/react/useState"&gt;useState&lt;/a&gt; no &lt;strong&gt;React&lt;/strong&gt; ou &lt;a href="https://angular.io/guide/architecture-components"&gt;variáveis ou propriedades das classes&lt;/a&gt; no &lt;strong&gt;Angular&lt;/strong&gt;) para gerenciar o estado de componentes que lidam com fluxo de dados, como por exemplo &lt;strong&gt;filtros&lt;/strong&gt; e &lt;strong&gt;paginação&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Isso resolve o problema: lida com o gerenciamento de estado do fluxo de dados desses componentes. Porém, isso também introduz um novo problema: esses estados são invisíveis e não registram histórico no navegador.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Por exemplo, imagine que tenhamos uma página de estoque, com uma lista de produtos, paginados. Ao entrar nessa página, clicamos para abrir a página 2 e, então, clicamos em voltar, no navegador, voltamos pra página inicial. Veja o GIF abaixo:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqc2l37ei8yih65e103b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqc2l37ei8yih65e103b.gif" alt="Problema ao clicar em voltar numa página" width="1195" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Isso é uma falha na usabilidade do produto, que atinge, inclusive, páginas famosas. Vejam, por exemplo, a página &lt;strong&gt;Todos os Cursos&lt;/strong&gt; do &lt;a href="https://www.eucapacito.com.br/todos-os-cursos/"&gt;Eu Capacito&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjj5yff9jk8ppnv4x8ock.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjj5yff9jk8ppnv4x8ock.gif" alt="Todos os Cursos do Eu Capacito" width="1195" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Percebam que um fator em comum nesses exemplos é que ao clicarmos em algum número de página a URL não é alterada.&lt;/p&gt;

&lt;h3&gt;
  
  
  As soluções &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Existem, pelo menos, duas soluções ideais para esse problema. Ambas passam pelo uso da URL como estado da página.&lt;/p&gt;

&lt;h4&gt;
  
  
  Query String &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;A primeira solução é utilizar a &lt;em&gt;query string&lt;/em&gt; para determinar estados. Por exemplo: podemos acrescentar &lt;code&gt;?page=2&lt;/code&gt; ou &lt;code&gt;?p=2&lt;/code&gt; na URL para indicar a página e a aplicação consegue lidar com esse parâmetro para determinar qual a página que deve ser exibida.&lt;/p&gt;

&lt;p&gt;Um exemplo de produto bastante conhecido, que utiliza &lt;em&gt;query strings&lt;/em&gt; para paginar os dados é o &lt;strong&gt;GitHub Issues&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F109zvamlvgjuglgww2pn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F109zvamlvgjuglgww2pn.gif" alt="Paginação por query parameter" width="1169" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essa solução pode ser implementada em diversos &lt;strong&gt;frameworks&lt;/strong&gt;, como, por exemplo, em &lt;a href="https://angular.io/guide/router#accessing-query-parameters-and-fragments"&gt;Angular&lt;/a&gt; e em &lt;strong&gt;React&lt;/strong&gt; com &lt;a href="https://reactrouter.com/en/main/hooks/use-search-params"&gt;react-router-dom&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pathname &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Outra forma de solucionar esse problema é utilizar o &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname"&gt;pathname&lt;/a&gt; para armazenar esses estados. Por exemplo, imagine as seguintes rotas: &lt;code&gt;categoria/marketing/&lt;/code&gt; e &lt;code&gt;categoria/marketing/page/2/&lt;/code&gt;. É claramente perceptível que a primeira rota indica a página 1 e que a segunda rota indica a página 2.&lt;/p&gt;

&lt;p&gt;Esse exemplo foi retirado do blog &lt;a href="https://rockcontent.com/br/blog/categoria/marketing/page/2/"&gt;RockContent&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infinite Loading &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Existe um outro método de paginação de dados que é o &lt;em&gt;infinite loading&lt;/em&gt; que carrega e agrega novos itens a parte de baixo a medida que é necessário (seja pelo scroll, seja por um botão de carregar mais). Esse tipo de solução é bastante utilizado quando o conteúdo não tem o próposito de ser bem estruturado (ter começo, meio e fim, por assim dizer). Complicado? vamos explicar com mais calma:&lt;/p&gt;

&lt;p&gt;Imagine que você entra na home-page de um site de notícias: você faz isso pra descrobrir as notícias mais recentes e não quer saber quantas páginas de notícia existem.&lt;/p&gt;

&lt;p&gt;Você quer rolar a tela até chegar numa data, ou numa notícia que já viu, e ai saberá que o que vem abaixo não lhe interessa mais.&lt;/p&gt;

&lt;p&gt;Agora, por outro lado, para que você entenda que certo e errado depende do contexto, imagine que o usuário, vendedor em um marketplace, queira analisar todas as vendas que fez, então ele começa a rolar a página, mais itens são carregados, continua rolando, mais itens são carregados, mas perceba que ele não sabe nem quantos itens ainda faltam pra serem carregados e, o pior ainda, se ele apertar no botão de voltar, como em praticamente todo sistema que usa essa estratégia, ele voltará para a página anterior, e não para a última parcela das compras que foi carregada, pois não há marcação de paginação na URL.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Um exemplo disso pode ser visto no feed do &lt;strong&gt;Twitter&lt;/strong&gt; ou do &lt;strong&gt;LinkedIn&lt;/strong&gt;, percebam que a ideia do feed é a descoberta de novos itens, logo, sem estrutura de começo, meio e fim.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Navegação em Abas &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Outro lugar onde esse é um problema de usabilidade claro são no uso das abas, veja o seguinte comportamento:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6h06pdpl1kl4bvflq63x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6h06pdpl1kl4bvflq63x.gif" alt="Orion Business" width="883" height="818"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perceba que, após alterar a aba, que é o conteúdo principal da página, e clicar em voltar, no navegador, voltamos a página inicial e não a aba anterior. Em alguns casos, mesmo que haja o mapeamento para as URL's, o comportamento não é padrão no browser, veja o comportamento na seguinte página (observe a URL e o comportamento do clique no botão de voltar):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg2qj369udbw8g0uut37x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg2qj369udbw8g0uut37x.gif" alt="Woovi" width="701" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Veja um caso de uso onde o mapeamento em URL e o comportamento do navegador funcionam de forma satisfatória:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpriv9c2wz82wiz81e6ss.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpriv9c2wz82wiz81e6ss.gif" alt="Shopee" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Contraponto: O Contexto que importa &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;O contexto é o que mais importa. Manter a aba ativa mapeada na URL é uma tática muito legal quando o conteúdo principal é o componente de abas. Agora, imagine que o componente de abas aparece no meio de uma página, entre diversos outros conteúdos: guardar essa aba ativa na URL não faz a menor diferença. Veja o caso da loja da &lt;a href="https://store.steampowered.com/"&gt;Steam&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgznmtn69wd20yi9v5kg2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgznmtn69wd20yi9v5kg2.gif" alt="Exemplo de Usabilidade da Steam" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nesse caso, vale a mesma lógica que comentamos sobre o uso de abas para descoberta de conteúdo no &lt;em&gt;Infinite Loading&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtros de busca &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Comentamos muito sobre paginação, navegação em abas, mas o mesmo se aplica para filtros. Como exemplo, podemos olhar a página de produtos, em quase todos os e-commerce com as filtragens: geralmente todas são mapeadas na &lt;em&gt;query string&lt;/em&gt; da URL.&lt;/p&gt;

&lt;p&gt;Ao mapear os filtros é importante levar em consideração o comportamento dos filtros com relação a filtragem:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;sempre quando o usuário alterar um filtro, a listagem já se atualiza com os novos dados;&lt;/li&gt;
&lt;li&gt;o usuário altera todos os campos e depois clica em aplicar os filtros.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Algumas das características desses dois comportamentos serão discutidos agora.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atualizando a listagem filtro por filtro &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Observe que a filtragem de um e-commerce, como por exemplo o site da Hering, geralmente se aparenta com categorias:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi833uv6sbij5iy2k4ake.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi833uv6sbij5iy2k4ake.png" alt="E-commerce da Hering" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nesse caso, geralmente, usam-se o comportamento de a cada item selecionado a listagem já é atualizada. Percebam, também, que, por conta da categorização dos filtros, o histórico é preservado:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Clicamos em Feminino -&amp;gt; Alfaiataria -&amp;gt; Alongado. Após esse fluxo, clicamos em voltar no navegador e voltamos para somente Alfaiataria. Clicamos novamente em voltar e ficamos somente na página de Feminino.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É visível que, a depender da quantidade de filtros e da forma como se pretenda usar esses filtros, essa pode não ser a melhor escolha.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selecionando todos os filtros e aplicando &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Esse comportamento é, geralmente, mais utilizado quando os filtros possuem uma aparência de filtragem e não de categoria. &lt;/p&gt;

&lt;p&gt;Por exemplo, quando os filtros incluem datas de início e de fim, tem-se o seguinte caso de uso: altera-se a data de início pra depois da data de fim. A nova data de início e a data de fim que já estava preenchida não faz sentido.&lt;/p&gt;

&lt;p&gt;Um exemplo disso é a tela de extrato da NuInvest:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbzv3wkgi04xmjbwgu1b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftbzv3wkgi04xmjbwgu1b.png" alt="Extrato da NuInvest" width="563" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Outro exemplo de uso, bastante comum, para esse tipo de comportamento é quando a API que devolve a listagem é pesada, mal desenhada, não otimizada, etc. e demora muito pra carregar os dados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modais e Caixas de Diálogos &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Modais e caixas de diálogos são muito bons em diversas situações, mas as vezes esquecemos um detalhe muito importante sobre eles: eles são, basicamente, uma nova página dentro da nossa própria página.&lt;/p&gt;

&lt;p&gt;Desse modo, perceba o seguinte comportamento ao tentar escrever uma publicação no LinkedIn e resolver voltar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmb8kgkdz7wt1mibjkgyd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmb8kgkdz7wt1mibjkgyd.gif" alt="Comportamento do LinkedIn" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Um dos passos percorridos pelo usuário para chegar ao modal foi perdido ao clicar em voltar.&lt;/p&gt;

&lt;p&gt;Para evitar esse tipo de problema, o estado dos modais também devem ser mapeados na URL, de alguma forma. Existem diversas formas de se implementar isso e um exemplo, não convencional, é o Trello que usa o &lt;code&gt;id&lt;/code&gt; do card na barra de endereços:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femcrcc2m4bcvq5ddb6v0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femcrcc2m4bcvq5ddb6v0.png" alt="Modal no Trello" width="619" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dicas de como melhorar esses pontos da usabilidade &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Existem algumas formas de melhorar a usabilidade de aplicações quanto ao mapeamento de URL's. Nas seções abaixo iremos falar sobre algumas dicas de como melhorar isso.&lt;/p&gt;

&lt;h3&gt;
  
  
  Estude sobre UX &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Ler sobre UX é uma boa forma de aprender o comportamento de determinadas interfaces. Isso é interessante mas não resolve, de todo, o problema. Vide o texto &lt;a href="https://medium.com/@allanroubertie/ux-modais-quando-e-quando-n%C3%A3o-us%C3%A1-los-8079d8200a26"&gt;UX: Modais, quando (e quando não) usá-los&lt;/a&gt;, texto oriundo da &lt;strong&gt;Nielsen Norman Group&lt;/strong&gt;, que não cita o uso das URL's.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmarking &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Faça benchmarking em produtos que usa no seu dia a dia. Entre, investigue o comportamento, clique em voltar, clique em avançar, feche o modal clicando no ícone de fechar e clique em voltar. Selecione os filtros, clique em voltar, avançar, mude os filtros um a um, tente mudar a data pra datas inválidas (a data de início posterior a data de fim, por exemplo).&lt;/p&gt;

&lt;p&gt;Entenda como os componentes reagem a todos esses casos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pense como um usuário &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Além de fazer benchmarking, pense como o seu usuário final. O que você achou do comportamento que você encontrou nos teus benchmarkings?&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Query_string"&gt;Query String&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.dev/reference/react/useState"&gt;useState&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://angular.io/guide/architecture-components"&gt;Introduction to components and templates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://angular.io/guide/router#accessing-query-parameters-and-fragments"&gt;Common Routing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactrouter.com/en/main/hooks/use-search-params"&gt;useSearchParams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname"&gt;pathname&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@allanroubertie/ux-modais-quando-e-quando-n%C3%A3o-us%C3%A1-los-8079d8200a26"&gt;UX: Modais, quando (e quando não) usá-los&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Produtos Mostrados no Texto &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://catalogo.stayapp.com.br/"&gt;Catálogo StayApp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.eucapacito.com.br/todos-os-cursos/"&gt;Eu Capacito&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rockcontent.com"&gt;RockContent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orioncomplex.com.br/"&gt;Orion Complex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://woovi.com/"&gt;Woovi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shopee.com.br/"&gt;Shopee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/"&gt;Steam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hering.com.br/"&gt;Hering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nuinvest.com.br/"&gt;NuInvest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trello.com/"&gt;Trello&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Foto de &lt;a href="https://unsplash.com/pt-br/@aronvisuals?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Aron Visuals&lt;/a&gt; na &lt;a href="https://unsplash.com/pt-br/fotografias/pessoa-segurando-bussola-preta-e-verde-apontando-para-oeste-3jBU9TbKW7o?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>ui</category>
      <category>ux</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Lidando com regressão visual: enfrentando desafios com Django, Selenium e Pillow</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Mon, 11 Sep 2023 01:15:58 +0000</pubDate>
      <link>https://dev.to/eduardojm/lidando-com-regressao-visual-enfrentando-desafios-com-django-selenium-e-pillow-o8d</link>
      <guid>https://dev.to/eduardojm/lidando-com-regressao-visual-enfrentando-desafios-com-django-selenium-e-pillow-o8d</guid>
      <description>&lt;p&gt;O &lt;strong&gt;django-image-uploader-widget&lt;/strong&gt; é um projeto de componente de upload de imagem para o &lt;strong&gt;django&lt;/strong&gt;. Por motivos de retrocompatibilidade entre as versões 3.x e 4.x do &lt;strong&gt;django&lt;/strong&gt;, foi decidido implementar testes de regressão visual utilizando &lt;strong&gt;Selenium&lt;/strong&gt; e &lt;strong&gt;Pillow&lt;/strong&gt;. Nesse texto vamos trabalhar esse tema e seus desafios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Índice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introdução&lt;/li&gt;
&lt;li&gt;O suporte do django a suas versões&lt;/li&gt;
&lt;li&gt;O que é um teste de regressão visual?&lt;/li&gt;
&lt;li&gt;O Browser e o Selenium&lt;/li&gt;
&lt;li&gt;Pillow&lt;/li&gt;
&lt;li&gt;Juntando as Peças&lt;/li&gt;
&lt;li&gt;
Desafios

&lt;ul&gt;
&lt;li&gt;O problema inicial&lt;/li&gt;
&lt;li&gt;Alteração nas fontes&lt;/li&gt;
&lt;li&gt;Animações, transições e threshold&lt;/li&gt;
&lt;li&gt;Theme Toggle&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Referências&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introdução &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;O &lt;a href="https://github.com/inventare/django-image-uploader-widget" rel="noopener noreferrer"&gt;django-image-uploader-widget&lt;/a&gt; é um projeto de componente de upload de imagem para o &lt;strong&gt;django&lt;/strong&gt;, criado para melhorar a estética dos &lt;em&gt;uploaders&lt;/em&gt; originais. É mantido como open-source desde o início, inclusive recebendo colaborações de outros desenvolvedores.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0pzhx6vm8kwio4xf5i9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0pzhx6vm8kwio4xf5i9.png" alt="Visualização do Componente"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Com o lançamento das versões 4.x do django, o componente passou a ter diferenças significativas no visual entre algumas de suas versões, conforme registrado na issue &lt;a href="https://github.com/inventare/django-image-uploader-widget/issues/96#issuecomment-1690740705" rel="noopener noreferrer"&gt;#96&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A partir dessa diferença visual, em diferentes versões do &lt;strong&gt;django&lt;/strong&gt;, entra no radar do projeto a possibilidade de implementação de testes de regressão visual. Nesse artigo vamos explorar os desafios enfrentados durante essa implementação.&lt;/p&gt;

&lt;h2&gt;
  
  
  O suporte do django a suas versões &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Um ponto que vale ser levado em consideração, para justificar a decisão de implementar testes de regressão visual, é o fato de que o &lt;strong&gt;django&lt;/strong&gt; mantem, por muito tempo, suporte a diversas de suas versões (&lt;a href="https://www.djangoproject.com/download/" rel="noopener noreferrer"&gt;Ver Referência&lt;/a&gt;), como na imagem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03mlns5lyp7k2s16drdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03mlns5lyp7k2s16drdr.png" alt="django supported versions roadmap"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é um teste de regressão visual? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Como o Guilherme Ananias comenta no seu texto (&lt;a href="https://dev.to/woovi/avoiding-ui-regressions-with-jest-49f3"&gt;Ver Referência&lt;/a&gt;), um teste de regressão visual é um tipo de teste que irá garantir que qualquer alteração no código não irá alterar a forma como o usuário final irá verá esse componente ou página.&lt;/p&gt;

&lt;p&gt;Dado o contexto de suporte a múltiplas versões do &lt;strong&gt;django&lt;/strong&gt; e, principalmente, do &lt;strong&gt;django-admin&lt;/strong&gt;, com alterações de estilização entre as versões, esse tipo de teste pode, também, ser utilizado para garantir retrocompatibilidade entre o componente e as versões do &lt;strong&gt;django&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Browser e o Selenium &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;O código desse componente já possuía uma suite de testes, funcionais, rodando com o &lt;strong&gt;Selenium&lt;/strong&gt; e o &lt;strong&gt;Chrome Driver&lt;/strong&gt;. A justificativa para o uso de testes funcionais pode ser lida nas &lt;a href="https://inventare.github.io/django-image-uploader-widget/docs/development/tests/" rel="noopener noreferrer"&gt;documentações do projeto&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tendo o browser e, mais especificamente, o &lt;strong&gt;selenium&lt;/strong&gt; disponíveis, é possível utilizar o método &lt;code&gt;screenshot(filename)&lt;/code&gt; para salvar um &lt;em&gt;screenshot&lt;/em&gt; de um elemento da página, conforme &lt;a href="https://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webelement.WebElement.screenshot" rel="noopener noreferrer"&gt;documentação&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pillow &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A utilização dos campos de imagem do &lt;strong&gt;django&lt;/strong&gt; só é possível com a biblioteca &lt;strong&gt;Pillow&lt;/strong&gt; instalada no projeto. Essa biblioteca traz, com a sua instalação, métodos para lidar com a diferença entre imagens (&lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageChops.html#PIL.ImageChops.difference" rel="noopener noreferrer"&gt;Ver Referência&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Juntando as Peças &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Ao utilizar o &lt;strong&gt;selenium&lt;/strong&gt; e a lib &lt;strong&gt;Pillow&lt;/strong&gt; é possível, de forma bem simples, tirar &lt;em&gt;screenshot&lt;/em&gt; dos elementos e comparar com outro &lt;em&gt;screenshot&lt;/em&gt; já armazenado (&lt;a href="https://github.com/inventare/django-image-uploader-widget/blob/main/utils/tests/snapshot.py#L188" rel="noopener noreferrer"&gt;ver implementação&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Desafios &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Foram enfrentados diversos desafios durante a implementação dos testes de regressão visual. Alguns desses desafios foram causados pela natureza das escolhas para a implementação desses testes e outros pela natureza do &lt;strong&gt;django&lt;/strong&gt; e &lt;strong&gt;django-admin&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  O problema inicial &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Toda a implementação dos testes de regressão visual nasceram devido a um problema entre versões do django, vide &lt;a href="https://github.com/inventare/django-image-uploader-widget/issues/96#issuecomment-1690740705" rel="noopener noreferrer"&gt;#96&lt;/a&gt;, onde, devido a alteração de estilos do &lt;strong&gt;django-admin&lt;/strong&gt;, adicionando &lt;code&gt;flexbox&lt;/code&gt;, causou a quebra da estilização.&lt;/p&gt;

&lt;p&gt;Assim, é necessário dizer que um dos primeiros desafios é da natureza do &lt;strong&gt;django&lt;/strong&gt; e, principalmente, do suporte a múltiplas versões do mesmo.&lt;/p&gt;

&lt;p&gt;A solução, pra esse caso, em específico, foi simples e corriqueira fazendo o tratamento tanto pro &lt;strong&gt;css&lt;/strong&gt; das versões 3.x do &lt;strong&gt;django-admin&lt;/strong&gt; quanto para as novas, que utilizam &lt;code&gt;flexbox&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alteração nas fontes &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Complementando o problema inicial, logo nos primeiros testes, foi identificado uma alteração no padrão visual do componente (ou &lt;em&gt;widget&lt;/em&gt;), em uma das versões mais recentes do &lt;strong&gt;django&lt;/strong&gt;. As fontes do &lt;em&gt;widget&lt;/em&gt; estavam diferentes. Ao buscar pelas &lt;em&gt;releases notes&lt;/em&gt; do &lt;strong&gt;django&lt;/strong&gt;, é observado a seguinte descrição (&lt;a href="https://docs.djangoproject.com/en/4.2/releases/4.2/" rel="noopener noreferrer"&gt;referência&lt;/a&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The admin’s font stack now prefers system UI fonts and no longer requires downloading fonts. Additionally, CSS variables are available to more easily override the default font families.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Uma solução, não necessariamente a melhor, porém a mais simples foi a de adiantar essa alteração das fontes, para o componente de upload de imagens, independente da versão do &lt;strong&gt;django&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Animações, transições e threshold &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Da natureza da comparação das imagens, junto ao fato de ser uma aplicação &lt;em&gt;web&lt;/em&gt; executando, tem-se o problema de ter alguns pixeis de diferença em algumas situações, provavelmente causado por animações e transições.&lt;/p&gt;

&lt;p&gt;Para aguardar o fim das transições e animações, o método &lt;code&gt;time.sleep()&lt;/code&gt; do Python foi utilizado. Por conta da utilização do &lt;code&gt;time.sleep()&lt;/code&gt; é, natural, que o tempo de execução dos testes aumente significativamente.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nesse ponto, é sempre importante deixar pontuado que, não existe certo ou errado, ou solução pra todos os problemas, ou, ainda, solução sem contrapontos. Toda técnica, ou escolha, geralmente, tem suas vantagens e desvantagens. Cabe ponderar se as vantagens superam as desvantagens no contexto ao qual se quer utilizar a ferramenta.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Por fim, após a implementação do &lt;code&gt;time.sleep()&lt;/code&gt; raras se tornaram as vezes que as imagens divergiam em locais que não deveriam, porém, por garantia, ainda foi implementado um sistema de comparação com &lt;em&gt;threshold&lt;/em&gt; (limiar) para esses casos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Theme Toggle &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Ainda nas &lt;em&gt;release notes&lt;/em&gt; da versão &lt;strong&gt;4.2&lt;/strong&gt; do &lt;strong&gt;django&lt;/strong&gt; é possível verificar a adição de um &lt;em&gt;theme toggle&lt;/em&gt; no admin. Quando o componente foi, inicialmente, desenvolvido, as versões 4.x do &lt;em&gt;django&lt;/em&gt; ainda não tinham assumido o status de &lt;strong&gt;LTS&lt;/strong&gt;. Além disso, por um tempo, apenas as manutenções básicas foram dadas ao projeto. Dados esses contextos, o componente não tinha suporte a mudança de tema pela interface de usuário.&lt;/p&gt;

&lt;p&gt;Esse problema só foi percebido, de fato, com a implementação dos testes de regressão visual e, para a solução, foi aberta uma nova issue no GitHub (&lt;a href="https://github.com/inventare/django-image-uploader-widget/issues/103" rel="noopener noreferrer"&gt;#103&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Esse foi o único caso em que foi implementada uma condicional da versão do &lt;strong&gt;django&lt;/strong&gt; em que os testes estavam sendo executados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InlineEditorUIRegressionTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUWTestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_ui_initialized_toggle_dark_theme_inverted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dark_mode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;major&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VERSION&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;major&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;minor&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Referências &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;As referências citadas no texto, listadas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/inventare/django-image-uploader-widget" rel="noopener noreferrer"&gt;django-image-uploader-widget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/inventare/django-image-uploader-widget/issues/96#issuecomment-1690740705" rel="noopener noreferrer"&gt;django-image-uploader-widget#96&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.djangoproject.com/download/" rel="noopener noreferrer"&gt;Download - Django&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/woovi/avoiding-ui-regressions-with-jest-49f3"&gt;Avoiding UI Regressions With Jest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://inventare.github.io/django-image-uploader-widget/docs/development/tests/" rel="noopener noreferrer"&gt;Tests - django-image-uploader-widget docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webelement.WebElement.screenshot" rel="noopener noreferrer"&gt;Selenium Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageChops.html#PIL.ImageChops.difference" rel="noopener noreferrer"&gt;Pillow Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/inventare/django-image-uploader-widget/blob/main/utils/tests/snapshot.py#L188" rel="noopener noreferrer"&gt;django-image-uploader-widget/snapshot.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/en/4.2/releases/4.2/" rel="noopener noreferrer"&gt;Django - Django 4.2 Release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/inventare/django-image-uploader-widget/issues/103" rel="noopener noreferrer"&gt;django-image-uploader-widget#103&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Foto de &lt;a href="https://unsplash.com/pt-br/@buudkaanaa?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Budka Damdinsuren&lt;/a&gt; na &lt;a href="https://unsplash.com/pt-br/fotografias/xihqiK6rD9k?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>ui</category>
      <category>django</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Full-Text Search: Criando um Back-End de Filtro para o Django Rest-Framework</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Sat, 22 Apr 2023 13:43:53 +0000</pubDate>
      <link>https://dev.to/eduardojm/full-text-search-criando-um-back-end-de-filtro-para-o-django-rest-framework-2ikl</link>
      <guid>https://dev.to/eduardojm/full-text-search-criando-um-back-end-de-filtro-para-o-django-rest-framework-2ikl</guid>
      <description>&lt;p&gt;O texto &lt;code&gt;Full-Text Search: Implementando com Postgres e Django&lt;/code&gt; [1] comenta sobre a implementação do sistema de &lt;em&gt;Full-Text Search&lt;/em&gt; do &lt;strong&gt;Postgres&lt;/strong&gt;, trazido pelo Leandro Proença no texto &lt;code&gt;A powerful full-text search in PostgreSQL in less than 20 lines&lt;/code&gt; [2], utilizando o &lt;strong&gt;django&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O projeto está no GitHub [3] e, para complementá-lo, esse texto tem por objetivo, construir um back-end de filtro, i.e. um &lt;em&gt;adapter&lt;/em&gt; de filtro, para lidar com o &lt;em&gt;full-text search&lt;/em&gt;, como no algoritmo do texto anterior dentro do &lt;em&gt;rest-framework&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Pra poder adicionar esse suporte, da melhor forma possível, podemos criar um &lt;em&gt;filter back-end&lt;/em&gt; customizado. São utilizados, como referência, o &lt;code&gt;SearchFilter&lt;/code&gt; original do &lt;strong&gt;django&lt;/strong&gt; [4] e [5].&lt;/p&gt;




&lt;h2&gt;
  
  
  Mostre-me o código
&lt;/h2&gt;

&lt;p&gt;O código desenvolvido nesse texto está disponível no repositório &lt;a href="https://github.com/EduardoJM/django-full-text-search" rel="noopener noreferrer"&gt;django-full-text-search&lt;/a&gt; no Github.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer:&lt;/p&gt;

&lt;p&gt;O código da versão desse texto está disponível na branch &lt;a href="https://github.com/EduardoJM/django-full-text-search/tree/texto-2" rel="noopener noreferrer"&gt;texto-2&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Implementando o BaseFilterBackend
&lt;/h2&gt;

&lt;p&gt;Para criar o back-end de filtro, é preciso implementar a classe &lt;code&gt;rest_framework.filters.BaseFilterBackend&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.filters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseFilterBackend&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FullTextSearchFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseFilterBackend&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Obtendo os parâmetros
&lt;/h3&gt;

&lt;p&gt;Os primeiros métodos que serão implementados na classe acima são apenas métodos que buscam atributos na requisição, como o parâmetro &lt;code&gt;?search&lt;/code&gt;, ou no &lt;code&gt;ModelViewSet&lt;/code&gt; como, por exemplo, o &lt;code&gt;search_fields&lt;/code&gt;. Esse código é bem parecido com o da referência em [5]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.filters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseFilterBackend&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;api_settings&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FullTextSearchFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseFilterBackend&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;search_param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SEARCH_PARAM&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_search_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_similarity_threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;similarity_threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_search_term&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search_param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# strip null characters
&lt;/span&gt;        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fazendo a Busca
&lt;/h3&gt;

&lt;p&gt;O método mais importante dessa classe é, sem dúvidas, o &lt;code&gt;filter_queryset&lt;/code&gt; que é o método que faz as alterações em um  &lt;code&gt;queryset&lt;/code&gt; para devolver a resposta da API.&lt;/p&gt;

&lt;p&gt;É preciso, antes de tudo, obter os parâmetros para fazer nossa busca, por meio dos métodos implementados acima:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_search_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;search_term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_search_term&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_similarity_threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um primeiro ponto, que deve ser levado em consideração, é que, caso a variável &lt;code&gt;search_fields&lt;/code&gt; ou a &lt;code&gt;search_term&lt;/code&gt; não esteja preenchida, podemos retornar o &lt;code&gt;queryset&lt;/code&gt; sem fazer alteração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;search_term&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;search_fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O restante do método é bem parecido com o que já implementamos no texto anterior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="n"&gt;search_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;search_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;search_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;TrigramSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_term&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similarity__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-similarity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Faz-se importante denotar que o &lt;code&gt;search_fields&lt;/code&gt; aqui é usado como &lt;code&gt;*search_fields&lt;/code&gt; para "desconstruir" o array. Assim, se &lt;code&gt;search_fields = ["name", "description"]&lt;/code&gt;, a criação da instância &lt;code&gt;SearchVector&lt;/code&gt; seria feita como &lt;code&gt;SearchVector("name", "description", config=config)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Por fim, a classe, completa, será:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FullTextSearchFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseFilterBackend&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;search_param&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SEARCH_PARAM&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_search_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_similarity_threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;similarity_threshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_search_term&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search_param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# strip null characters
&lt;/span&gt;        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_search_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;search_term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_search_term&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_similarity_threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;search_term&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;search_fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;

        &lt;span class="n"&gt;search_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;search_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;search_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;TrigramSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_term&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similarity__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-similarity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Usando o FullTextSearchFilter
&lt;/h2&gt;

&lt;p&gt;A classe &lt;code&gt;FullTextSearchFilter&lt;/code&gt; pode ser utilizada nos &lt;code&gt;filter_backends&lt;/code&gt; dos &lt;code&gt;ModelViewSet&lt;/code&gt; do &lt;em&gt;django-rest-framework&lt;/em&gt;. Simplificando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.viewsets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModelViewSet&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;texto.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;core.filters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FullTextSearchFilter&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SingerSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__all__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SingerViewSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelViewSet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SingerSerializer&lt;/span&gt;
    &lt;span class="n"&gt;filter_backends&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FullTextSearchFilter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;search_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ao registrar o &lt;code&gt;SingerViewSet&lt;/code&gt; nas &lt;code&gt;urls&lt;/code&gt; do projeto já é possível fazer chamadas para o &lt;em&gt;endpoint&lt;/em&gt; utilizando o &lt;code&gt;?search&lt;/code&gt; como &lt;em&gt;full-text search&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.routers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SimpleRouter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.viewsets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SingerViewSet&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;singer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SingerViewSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Singer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb26xw4dsetso6c79lbun.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb26xw4dsetso6c79lbun.png" alt="Exemplo de chamada para a API com ?search=Marrone e mostrando os resultados filtrados e ordenados de modo correto"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Mostrando o Rank e Similarity no retorno da API
&lt;/h2&gt;

&lt;p&gt;É possível, inclusive, exibir os dados de &lt;code&gt;rank&lt;/code&gt; e &lt;code&gt;similarity&lt;/code&gt; no retorno da API. Como esses dados estão sendo anotados, i.e. acrescentados, na entidade, é possível, apenas, alterar o &lt;code&gt;ModelSerializer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SingerSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FloatField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FloatField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__all__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrywp8nx8oq1co8y0jwo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrywp8nx8oq1co8y0jwo.png" alt="Exemplo de chamada para a API com ?search=Marrone e mostrando os resultados com os campos rank e similarity sendo exibidos"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mas, e sem a busca?
&lt;/h3&gt;

&lt;p&gt;Acrescentar, apenas, o &lt;code&gt;rank&lt;/code&gt; e &lt;code&gt;similarity&lt;/code&gt; no &lt;code&gt;ModelSerializer&lt;/code&gt; traz um problema: quando o &lt;em&gt;endpoint&lt;/em&gt; é chamado sem o &lt;code&gt;?search&lt;/code&gt; os dados de &lt;code&gt;rank&lt;/code&gt; e &lt;code&gt;similarity&lt;/code&gt; não são retornados:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqynslo81lw3nxvz54ca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqynslo81lw3nxvz54ca.png" alt="Exemplo de retorno da API sem utilizar o parâmetro ?search na URL e que os itens são retornados sem o campo rank e similarity"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Isso pode ser resolvido, acrescentando, no construtor do FloatField, o parâmetro &lt;code&gt;default=0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SingerSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FloatField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FloatField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__all__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Filtrando por Similaridade
&lt;/h2&gt;

&lt;p&gt;Por fim, para filtrar por similaridade, é possível definir a variável &lt;code&gt;similarity_threshold&lt;/code&gt; no &lt;code&gt;ModelViewSet&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SingerViewSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelViewSet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SingerSerializer&lt;/span&gt;
    &lt;span class="n"&gt;filter_backends&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FullTextSearchFilter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;search_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;similarity_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqfo6l4j1denf98zguyrs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqfo6l4j1denf98zguyrs.png" alt="Exemplo de chamada para a API com  raw `?search=Bruninho` endraw  exibindo apenas os itens com o campo "&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;p&gt;[1] &lt;a href="https://dev.to/eduardojm/full-text-search-implementando-com-postgres-e-django-kmf"&gt;Full-Text Search: Implementando com Postgres e Django&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] &lt;a href="https://leandronsp.com/a-powerful-full-text-search-in-postgresql-in-less-than-20-lines" rel="noopener noreferrer"&gt;A powerful full-text search in PostgreSQL in less than 20 lines&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] &lt;a href="https://github.com/EduardoJM/django-full-text-search" rel="noopener noreferrer"&gt;django-full-text-search&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] &lt;a href="https://www.django-rest-framework.org/api-guide/filtering/#searchfilter" rel="noopener noreferrer"&gt;Filtering - SearchFilter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] &lt;a href="https://github.com/encode/django-rest-framework/blob/master/rest_framework/filters.py#L39" rel="noopener noreferrer"&gt;rest_framework/filters.py&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://unsplash.com/pt-br/fotografias/ehyV_XOZ4iA" rel="noopener noreferrer"&gt;Foto de Capa por Douglas Lopes no Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>python</category>
      <category>django</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Full-Text Search: Implementando com Postgres e Django</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Mon, 10 Apr 2023 11:29:58 +0000</pubDate>
      <link>https://dev.to/eduardojm/full-text-search-implementando-com-postgres-e-django-kmf</link>
      <guid>https://dev.to/eduardojm/full-text-search-implementando-com-postgres-e-django-kmf</guid>
      <description>&lt;p&gt;Algum tempo atrás vi o texto "A powerful full-text search in PostgreSQL in less than 20 lines" do Leandro Proença [1] e quis implementar algo assim pra projetos que não demandam o poder de um Apache Lucene ou de um Elastic Search.&lt;/p&gt;

&lt;p&gt;O django já possui, em seu &lt;em&gt;core&lt;/em&gt;, uma aplicação com métodos que são utilizados apenas com o &lt;strong&gt;Postgres&lt;/strong&gt; e, para a minha surpresa, todos os conceitos de &lt;em&gt;full-text search&lt;/em&gt; já estavam disponíveis nesse &lt;em&gt;app&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Restou, nesse caso, tentar reproduzir, por assim dizer, a &lt;em&gt;query&lt;/em&gt; do texto original utilizando o &lt;strong&gt;ORM&lt;/strong&gt; do django e os métodos do &lt;em&gt;full-text search&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Esse texto tem, por objetivo, trazer explicações sobre como essa implementação foi feita. Fundamentalmente, esse texto será uma versão explicada &lt;a href="https://twitter.com/goticodocalypso/status/1645248408066613248" rel="noopener noreferrer"&gt;dessa thread no twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mostre-me o código
&lt;/h2&gt;

&lt;p&gt;Todo o código-fonte do projeto está disponível no GitHub, &lt;a href="https://github.com/EduardoJM/django-full-text-search" rel="noopener noreferrer"&gt;nesse repositório&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer:&lt;/p&gt;

&lt;p&gt;O código da versão desse texto está disponível na branch &lt;a href="https://github.com/EduardoJM/django-full-text-search/tree/texto-1" rel="noopener noreferrer"&gt;texto-1&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Adicionando configurações necessárias
&lt;/h2&gt;

&lt;p&gt;Dentro do &lt;code&gt;settings.py&lt;/code&gt; do projeto, precisamos adicionar a aplicação &lt;code&gt;django.contrib.postgres&lt;/code&gt; dentro da variável de &lt;code&gt;INSTALLED_APPS&lt;/code&gt; para que possamos utilizar as ferramentas do &lt;strong&gt;django&lt;/strong&gt; próprias para o &lt;strong&gt;Postgres&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.postgres&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Criando o model
&lt;/h2&gt;

&lt;p&gt;Precisamos criar um &lt;em&gt;model&lt;/em&gt; para poder utilizar os conceitos da busca dentro dele. Para simplificar, esse caso, utilizamos um &lt;em&gt;model&lt;/em&gt; com um único campo de texto para as buscas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cantor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cantor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cantores&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Criando uma view
&lt;/h2&gt;

&lt;p&gt;Para testar os conceitos de &lt;em&gt;full-text search&lt;/em&gt;, podemos criar uma &lt;em&gt;view&lt;/em&gt;. Antes, é necessário dizer que nesse texto estou usando &lt;em&gt;views&lt;/em&gt; padrão do &lt;strong&gt;django&lt;/strong&gt; com &lt;em&gt;templates&lt;/em&gt; em HTML para não adicionar mais complexidade lidando com o &lt;em&gt;Rest Framework&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Podemos criar uma &lt;em&gt;view&lt;/em&gt; que recebe uma &lt;em&gt;query string&lt;/em&gt; para fazer a busca:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_singer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO: fazer busca aqui
&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;singers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;singers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;singers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;term&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cantor.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;em&gt;template&lt;/em&gt; &lt;code&gt;cantor.html&lt;/code&gt; que estou utilizando é bem simples apenas para permitir testes de forma mais fácil:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Buscando Cantores&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"q"&lt;/span&gt; &lt;span class="err"&gt;{%&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="na"&gt;term&lt;/span&gt; &lt;span class="err"&gt;%}&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"{{ term }}"&lt;/span&gt; &lt;span class="err"&gt;{%&lt;/span&gt; &lt;span class="na"&gt;endif&lt;/span&gt; &lt;span class="err"&gt;%}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Pesquisar&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    {% if singers %}
        &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
            {% for item in singers %}
                &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;{{item.name}}&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
                    {% if item.rank or item.similarity %}
                        &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
                            Rank: {{item.rank}}, Similaridade: {{item.similarity}}
                        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    {% endif %}
                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            {% endfor %}
        &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    {% endif %}

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Full-Text Search
&lt;/h2&gt;

&lt;p&gt;Precisamos, primeiro, criar um &lt;strong&gt;SearchVector&lt;/strong&gt; (&lt;em&gt;ts_vector&lt;/em&gt;) e um &lt;strong&gt;SearchQuery&lt;/strong&gt; (&lt;em&gt;tsquery&lt;/em&gt;). Assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchQuery&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O vector é feito assim pra utilizar a coluna "name" do model &lt;strong&gt;Singer&lt;/strong&gt;. A query é feita para processar a variável &lt;em&gt;term&lt;/em&gt; recebida no código da &lt;em&gt;view&lt;/em&gt; acima.&lt;/p&gt;

&lt;p&gt;O próximo ponto é criar &lt;em&gt;annotations&lt;/em&gt; para fazer o &lt;em&gt;select&lt;/em&gt; de campos como o &lt;em&gt;to_tsvector&lt;/em&gt; e o &lt;em&gt;ts_rank&lt;/em&gt; (o método &lt;code&gt;.annotate&lt;/code&gt; do &lt;em&gt;Django ORM&lt;/em&gt; faz o &lt;em&gt;select&lt;/em&gt; de outros campos e agrega eles a entidade):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchRank&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;singers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adicionando o código dentro da view, passamos a ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchRank&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_singer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;singers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;singers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;singers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;singers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;term&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cantor.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Utilizando um pequeno grupo de dados para teste:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q9z2lxkuhesa8k4m6uk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q9z2lxkuhesa8k4m6uk.png" alt="Dados sem Busca" width="394" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Podeos testar e verificar que passamos a ter uma busca funcional:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdqoa3bysd9hip1sulwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcdqoa3bysd9hip1sulwp.png" alt="Resultado de Busca" width="368" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Porém, ainda temos alguns problemas, pois, por exemplo, na busca por palavras incompletas, perdemos o ranqueamento:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1d2o1jgh3oh5mdh8z64l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1d2o1jgh3oh5mdh8z64l.png" alt="Busca incompleta" width="377" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nesse ponto, entra a busca por similaridade que, combinada com o &lt;em&gt;Full-Text Search&lt;/em&gt; nos permitirá fazer uma busca mais funcional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Busca por Similaridade
&lt;/h2&gt;

&lt;p&gt;Precisamos, primeiro, adicionar a extensão &lt;strong&gt;pg_trgm&lt;/strong&gt; no banco de dados. Podemos fazer isso manualmente ou podemos criar uma &lt;em&gt;migration&lt;/em&gt; vazia e adicionar essa extensão na &lt;em&gt;migration&lt;/em&gt;. Vou seguir pela segunda opção. Para a primeira, basta executar o comando no banco de dados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;pg_trgm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para a segunda abordagem, podemos executar o comando &lt;code&gt;python manage.py makemigrations nome_do_app --empty&lt;/code&gt; e ele criará uma -&lt;em&gt;migration&lt;/em&gt; vazia. A partir da &lt;em&gt;migration&lt;/em&gt; vazia, podemos adicionar o import ao CreateExtension e adicionar dentro de operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.operations&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CreateExtension&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;texto&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0003_alter_feat_music&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;CreateExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_trgm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basta agora executar &lt;code&gt;python manage.py migrate&lt;/code&gt; e teremos a extensão criada no banco de dados.&lt;/p&gt;

&lt;p&gt;Agora, dentro da nossa busca, podemos fazer o uso do &lt;strong&gt;TrigramSimilarity&lt;/strong&gt; para melhorar nossos resultados. Primeiro, vamos adicionar dentro do &lt;code&gt;.annotate&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TrigramSimilarity&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="n"&gt;singers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;TrigramSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Precisamos, também, alterar o &lt;code&gt;.filter&lt;/code&gt; para utilizar de um operador lógico &lt;strong&gt;OU&lt;/strong&gt;. Para isso, precisamos fazer uso do &lt;code&gt;Q(condição 1) | Q(condição 2)&lt;/code&gt; do &lt;strong&gt;django&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TrigramSimilarity&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="n"&gt;singers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;TrigramSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similarity__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-similarity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui, o que fazemos é adicionar o campo de &lt;em&gt;similarity&lt;/em&gt; na nossa &lt;em&gt;query&lt;/em&gt; e filtrar pra "o full-text search encontrou" ou "a similaridade é maior que zero". A partir desse momento, fazendo a mesma busca de um dos prints acima:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglgc1u2dox9t75zvp4jd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglgc1u2dox9t75zvp4jd.png" alt="Busca por Similaridade" width="331" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Por fim, nossa &lt;em&gt;view&lt;/em&gt; passa a ter o código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TrigramSimilarity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_singer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;singers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchRank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;TrigramSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similarity__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-similarity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;singers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;singers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;singers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;term&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cantor.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É possível utilizar tanto o &lt;em&gt;rank&lt;/em&gt; ou o &lt;em&gt;similarity&lt;/em&gt; para cortar valores, conforme exemplos da documentação.&lt;/p&gt;

&lt;p&gt;Por último, podemos adicionar um índice dentro do nosso &lt;em&gt;model&lt;/em&gt; para lidar com performance das queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.indexes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GinIndex&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.postgres.search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchVector&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Singer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cantor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cantor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cantores&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;GinIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;SearchVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portuguese&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;singer_search_vector_idx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Todo o código-fonte do projeto está disponível no GitHub, &lt;a href="https://github.com/EduardoJM/django-full-text-search" rel="noopener noreferrer"&gt;nesse repositório&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer:&lt;/p&gt;

&lt;p&gt;O código da versão desse texto está disponível na branch &lt;a href="https://github.com/EduardoJM/django-full-text-search/tree/texto-1" rel="noopener noreferrer"&gt;texto-1&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;p&gt;1 - &lt;a href="https://leandronsp.com/a-powerful-full-text-search-in-postgresql-in-less-than-20-lines" rel="noopener noreferrer"&gt;A powerful full-text search in PostgreSQL in less than 20 lines&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2 - &lt;a href="https://docs.djangoproject.com/en/4.2/ref/contrib/postgres/search/" rel="noopener noreferrer"&gt;Full text search - Django Documentation&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://unsplash.com/pt-br/fotografias/eQ2Z9ay9Wws" rel="noopener noreferrer"&gt;Foto de capa por Mick Haupt no Unsplash&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>How to Hide Features from Production Environment in React</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Fri, 31 Mar 2023 23:09:16 +0000</pubDate>
      <link>https://dev.to/eduardojm/how-to-hide-features-from-production-environment-in-react-1mcn</link>
      <guid>https://dev.to/eduardojm/how-to-hide-features-from-production-environment-in-react-1mcn</guid>
      <description>&lt;p&gt;I writed, in brazilian portuguese, at Inventare, an article about &lt;a href="https://dev.to/inventare/redesign-do-revisae-testando-em-producao-15op"&gt;testing into production&lt;/a&gt; with feature flag. Currently, we have an old project writed in React (create-react-app) with two environments, test (validation) and production and, in some cases, we need to hide some things from the production environment.&lt;/p&gt;

&lt;p&gt;The project use &lt;code&gt;env-cmd&lt;/code&gt; package to run the react scripts with different &lt;code&gt;.env&lt;/code&gt; files to get the projects :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"scripts"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"env-cmd -f ./.env react-scripts start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"env-cmd -f ./.env.production react-scripts build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build:test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"env-cmd -f ./.env react-scripts build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, into the &lt;code&gt;.env&lt;/code&gt; and into the &lt;code&gt;.env.production&lt;/code&gt; files we can add an variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env
...
REACT_APP_TEST_ENV=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env.production
...
REACT_APP_TEST_ENV=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can create an component to monitor this env variable and conditional render an element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TestOnly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isTestingEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_TEST_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isTestingEnv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;TestOnly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, finally, we can encapsulates the feature with the &lt;code&gt;&amp;lt;TestOnly /&amp;gt;&lt;/code&gt; component, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TestOnly&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;This is visible only in test env.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;TestOnly&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>react</category>
      <category>braziliandevs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Django-Rest-Framework e Code Coverage: uma métrica complexa e que não diz muita coisa</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Fri, 30 Dec 2022 14:32:57 +0000</pubDate>
      <link>https://dev.to/eduardojm/django-rest-framework-e-code-coverage-uma-metrica-complexa-e-que-nao-diz-muita-coisa-4phn</link>
      <guid>https://dev.to/eduardojm/django-rest-framework-e-code-coverage-uma-metrica-complexa-e-que-nao-diz-muita-coisa-4phn</guid>
      <description>&lt;p&gt;Uma métrica bastante utilizada para direcionar a escrita de testes, principalmente quando os testes estão sendo escritos para ajudar a garantir a qualidade de uma aplicação já escrita, é a porcentagem de cobertura do código (ou, em inglês, o &lt;em&gt;code coverage&lt;/em&gt;). &lt;/p&gt;

&lt;p&gt;Segundo [1], &lt;em&gt;code coverage&lt;/em&gt; é uma métrica que determina o número de linhas do código que foi validada pelos testes. Essa é uma definição simplista e que não é muito verdadeira.&lt;/p&gt;

&lt;p&gt;As ferramentas de &lt;em&gt;code coverage&lt;/em&gt; não determinam, de fato, se uma linha foi validada pelos testes, mas sim se ela foi executada durante os testes e existe uma diferença enorme entre ela ser executada e ser validada. Para determinar se essa linha foi, efetivamente, validada é preciso analisar as asserções feitas pelo testes e, se possível, verificar todos os (ou a maioria dos) casos de uso.&lt;/p&gt;

&lt;p&gt;Assim sendo, é extramente complexo que uma ferramenta automatizada verifique se uma linha foi, de fato, validada.&lt;/p&gt;

&lt;p&gt;Essa introdução levanta alguns pontos sobre ferramentas de cobertura de código em geral que servem para embasar os apontamentos sobre testes com &lt;em&gt;django-rest-framework&lt;/em&gt; e essas ferramentas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que verificar a cobertura do código?
&lt;/h2&gt;

&lt;p&gt;O artigo [2] levanta algumas dicas sobre como começar a lidar com &lt;em&gt;code coverage&lt;/em&gt; e, dentro dessas dicas, levanta um ponto que é um dos principais motivos para se usar essas ferramentas, mesmo com as ""deficiências"" comentadas acima:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use coverage reports to identify critical misses in testing&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Traduzindo, e adequando, esse trecho quer dizer para "usar relatórios de cobertura para identificar locais críticos sem testes". Por exemplo, veja o caso de uso da imagem abaixo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---M9SFXlX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jxu2yyhycbyshrj0kbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---M9SFXlX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jxu2yyhycbyshrj0kbf.png" alt="relatório de coverage de um exemplo de autenticação" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Casos onde não foi possível gerar o Token de autenticação, por algum motivo, não foram testados. Assim, é possível analisar o relatório e decidir se esse será um caso recorrente de erro e que deverá estar coberto com novos testes ou se será algum caso raro e que não deve preocupar o time de desenvolvimento.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django-Rest-Framework
&lt;/h2&gt;

&lt;p&gt;A relação entre os pontos comentados acima com o &lt;em&gt;django-rest-framework&lt;/em&gt; é que, o &lt;em&gt;django&lt;/em&gt;, principalmente em API's com o &lt;em&gt;rest-framework&lt;/em&gt;, adota uma perspectiva declarativa para rotas de API com os &lt;em&gt;ModelViewSet&lt;/em&gt; [3] e isso tem algumas implicações importantes na cobertura de códigos.&lt;/p&gt;

&lt;p&gt;Quando um CRUD de algum &lt;em&gt;model&lt;/em&gt; é definido, de forma declarativa, utilizando o &lt;code&gt;ModelViewSet&lt;/code&gt;, conforme o exemplo retirado de [3], o que acontece é que não temos dados de cobertura de código sobre todas as rotas (GET, POST, PUT, PATCH, DELETE) desse determinado recurso:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5lmEc7xP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xzoa3hmrgz074e8nmui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5lmEc7xP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xzoa3hmrgz074e8nmui.png" alt="Exemplo de ModelViewSet" width="800" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Desse modo, os relatórios de cobertura de código não dizem muita coisa sobre o que está acontecendo dentro do nosso &lt;code&gt;ModelViewSet&lt;/code&gt;. Porém, ainda assim, servem para casos onde você precisa de funcionalidades que sobrescrevem métodos em &lt;em&gt;serializers&lt;/em&gt;, &lt;em&gt;models&lt;/em&gt; ou no próprio &lt;em&gt;viewset&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;A conclusão é que, assim como diversos programadores sempre alertam, cobertura de código é uma métrica que traz problemas se olhada cegamente. Para aplicações que utilizam o &lt;em&gt;django&lt;/em&gt; e &lt;em&gt;django-rest-framework&lt;/em&gt; (&lt;em&gt;drf&lt;/em&gt;) isso deve ser levado, ainda mais, em consideração.&lt;/p&gt;

&lt;p&gt;Olhar para um plano de testes (de integração principalmente) tendo o relatório de cobertura de código como uma métrica secundária dentro de aplicações &lt;em&gt;django&lt;/em&gt; + &lt;em&gt;drf&lt;/em&gt; faz muito mais sentido que olhar apenas para relatório de cobertura para definir o que testar. &lt;/p&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.codegrip.tech/productivity/everything-you-need-to-know-about-code-coverage"&gt;Everything you need to know about code coverage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.atlassian.com/continuous-delivery/software-testing/code-coverage"&gt;What is code coverage?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset"&gt;ViewSets&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>testing</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>django-storages + S3: lidando com arquivos de mesmo nome</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Sat, 10 Dec 2022 23:41:01 +0000</pubDate>
      <link>https://dev.to/eduardojm/django-storages-s3-lidando-com-arquivos-de-mesmo-nome-4eo1</link>
      <guid>https://dev.to/eduardojm/django-storages-s3-lidando-com-arquivos-de-mesmo-nome-4eo1</guid>
      <description>&lt;p&gt;O &lt;em&gt;django&lt;/em&gt; é um &lt;em&gt;framework&lt;/em&gt; para desenvolvimento web na linguagem Python e seu funcionamento é, majoritariamente, baseado em alguns padrões de design, como, por exemplo, os &lt;em&gt;decorators&lt;/em&gt; e &lt;em&gt;adapters&lt;/em&gt;. O sistema de &lt;em&gt;storage&lt;/em&gt; de arquivos de mídia e de arquivos estáticos funciona, de modo simples, seguindo o padrão &lt;em&gt;adapter&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Arquivos de Mídia são os arquivos enviados pelos usuários por meio dos campos de upload de arquivo (&lt;code&gt;ImageField&lt;/code&gt; e &lt;code&gt;FileField&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Arquivos Estáticos são os arquivos para serem usados nos layouts (como, por exemplo, &lt;em&gt;scripts&lt;/em&gt;, &lt;em&gt;styles&lt;/em&gt; e &lt;em&gt;assets&lt;/em&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O &lt;code&gt;django-storages&lt;/code&gt; é uma biblioteca que fornece alguns &lt;em&gt;adapters&lt;/em&gt; para o sistema de armazenamento do &lt;em&gt;django&lt;/em&gt;, enviando arquivos para provedores de nuvem, como a &lt;strong&gt;AWS&lt;/strong&gt; (&lt;em&gt;Amazon Web Service&lt;/em&gt;) e a &lt;strong&gt;GCP&lt;/strong&gt; (&lt;em&gt;Google Cloud Platform&lt;/em&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  O problema
&lt;/h2&gt;

&lt;p&gt;Utilizando o sistema original de armazenamento, em disco, do &lt;em&gt;django&lt;/em&gt; para arquivos estáticos e para arquivos de mídia ao mesmo tempo existem alguns comportamentos que são padrão e que acabam passando sem serem notados:&lt;/p&gt;

&lt;p&gt;Durante o upload de arquivos (&lt;code&gt;FileField&lt;/code&gt; ou &lt;code&gt;ImageField&lt;/code&gt;) quando enviamos dois arquivos com o mesmo nome, por padrão, o &lt;em&gt;adapter&lt;/em&gt; de storage adiciona uma parte aleatória no fim do nome do arquivo, permitindo, dessa forma, o upload de ambos sem conflitarem.&lt;/p&gt;

&lt;p&gt;Já os arquivos estáticos, aqueles que são "coletados" utilizando o comando &lt;code&gt;python manage.py collectstatic&lt;/code&gt;, os conflitos de nomes são resolvidos de uma forma diferente: "o arquivo que for localizado primeiro em uma das localizações especificadas será utilizado" (Ver Referência [1]). &lt;/p&gt;

&lt;p&gt;Essa diferença entre os comportamentos, configurações padrões  para ambos os &lt;em&gt;adapters&lt;/em&gt;, é de suma importância pois, não faz sentido adicionar um &lt;code&gt;jquery_xyz.js&lt;/code&gt;, por exemplo, a cada vez que o comando &lt;code&gt;python manage.py collectstatic&lt;/code&gt; for executado.&lt;/p&gt;

&lt;h2&gt;
  
  
  django-storages e a unificação do comportamento
&lt;/h2&gt;

&lt;p&gt;O django-storages, mais especificamente seus &lt;em&gt;adapters&lt;/em&gt; &lt;code&gt;S3Boto3Storage&lt;/code&gt; e &lt;code&gt;S3StaticStorage&lt;/code&gt;, unifica esses dois comportamentos em uma configuração &lt;code&gt;AWS_S3_FILE_OVERWRITE&lt;/code&gt; que por padrão é &lt;code&gt;True&lt;/code&gt; e de acordo com [2], "por padrão, arquivos com o mesmo nome serão sobrescritos um ao outro. Mude para &lt;code&gt;False&lt;/code&gt; para adicionar caracteres extra adicionados".&lt;/p&gt;

&lt;p&gt;O problema ainda persiste, pois o comportamento precisa ser diferente para cada um dos &lt;em&gt;adapter&lt;/em&gt;. A solução é não alterar a propriedade &lt;code&gt;AWS_S3_FILE_OVERWRITE&lt;/code&gt; e sobrescrever o &lt;em&gt;adapter&lt;/em&gt; de upload de arquivos de mídia definindo essa configuração localmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# core/storages.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;storages.backends.s3boto3&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MediaS3Boto3Storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S3Boto3Storage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;file_overwrite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assim, alterando no &lt;code&gt;settings.py&lt;/code&gt; a configuração principal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DEFAULT_FILE_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'core.storages.MediaS3Boto3Storage'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No exemplo, abaixo, o mesmo arquivo foi enviado duas vezes para o bucket S3 e o segundo aparece já com os caracteres acrescentados ao fim do nome:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0IxuomVW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j23ywo6y6e2vpi4asg2y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0IxuomVW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j23ywo6y6e2vpi4asg2y.png" alt="Exemplo do Bucket" width="355" height="68"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Observações
&lt;/h2&gt;

&lt;p&gt;Segundo o comentário em [3], não existe a necessidade em, de fato, substituir o &lt;em&gt;adapter&lt;/em&gt; e a configuração &lt;code&gt;AWS_S3_FILE_OVERWRITE = False&lt;/code&gt; poderia ser utilizada devido ao fato de que o django remove o arquivo antes de criar o novo ao executar o comando &lt;code&gt;python manage.py collectstatic&lt;/code&gt;, porém essa é uma questão não confirmada e que, além disso, deixa preocupações, por exemplo, em futuras atualizações do próprio &lt;em&gt;django&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;[1] &lt;a href="https://docs.djangoproject.com/en/4.1/ref/contrib/staticfiles/#collectstatic"&gt;The staticfiles app&lt;/a&gt; - Django Documentation.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;[2] &lt;a href="https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html"&gt;Amazon S3&lt;/a&gt; - Django-storages Documentation.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;[3] &lt;a href="https://github.com/jschneier/django-storages/issues/50#issuecomment-319615823"&gt;jschneier on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>s3</category>
      <category>aws</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>django-admin: criando um AdminSite customizado sem perder o registro automático dos Models</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Fri, 11 Nov 2022 20:50:52 +0000</pubDate>
      <link>https://dev.to/eduardojm/django-admin-criando-um-adminsite-customizado-sem-perder-o-registro-automatico-dos-models-3f3k</link>
      <guid>https://dev.to/eduardojm/django-admin-criando-um-adminsite-customizado-sem-perder-o-registro-automatico-dos-models-3f3k</guid>
      <description>&lt;p&gt;Ao trabalhar com &lt;code&gt;django&lt;/code&gt;, principalmente utilizando o &lt;code&gt;django-admin&lt;/code&gt;, é bastante comum não nos preocuparmos, sempre, com os &lt;em&gt;models&lt;/em&gt; que são registrados ao painel de &lt;em&gt;admin&lt;/em&gt; padrão.&lt;/p&gt;

&lt;p&gt;O motivo disso é bem simples: na maioria dos casos não precisamos tocar na instância padrão da classe &lt;code&gt;AdminSite&lt;/code&gt;, que é a classe que define como o painel &lt;em&gt;admin&lt;/em&gt; irá ser exibido. &lt;/p&gt;

&lt;p&gt;Quando não precisamos trocar a instância do &lt;code&gt;AdminSite&lt;/code&gt; que utilizamos, o registro automático dos &lt;em&gt;models&lt;/em&gt; sempre nos satisfaz.&lt;/p&gt;

&lt;p&gt;Porém, ao surgir a necessidade de sobrescrever o &lt;code&gt;AdminSite&lt;/code&gt; padrão, costumamos perder o registro automático dos &lt;em&gt;models&lt;/em&gt; feitos pelos diversos pacotes (&lt;em&gt;libs&lt;/em&gt;, bibliotecas, etc.) que instalamos no nosso projeto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivação
&lt;/h2&gt;

&lt;p&gt;A motivação pra se lidar com esse problema, é que em algumas situações precisamos alterar o &lt;code&gt;AdminSite&lt;/code&gt; padrão. Um exemplo clássico e que costuma surgir bastante é: quando é preciso ordenar os &lt;em&gt;Apps&lt;/em&gt; e os &lt;em&gt;Models&lt;/em&gt; na página inicial do nosso &lt;em&gt;admin&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O AdminSite Padrão
&lt;/h2&gt;

&lt;p&gt;Quando executamos o comando abaixo, para iniciar um novo projeto (existem outras variações do comando, não se preocupe), por padrão, teremos o arquivo &lt;code&gt;core/urls.py&lt;/code&gt; do código a seguir&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;django-admin startproject core &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# core/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'admin/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse arquivo, é registrada uma instância padrão do &lt;code&gt;AdminSite&lt;/code&gt; que, por sua vez, é importada do caminho: &lt;code&gt;django.contrib.admin.site&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Com o projeto em execução (&lt;code&gt;python manage.py runserver&lt;/code&gt;), ao realizar login no painel de admin, teremos a seguinte tela:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WXRhtJJl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hl0vgdlwhjnjm5b22kzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WXRhtJJl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hl0vgdlwhjnjm5b22kzi.png" alt="tela inicial do django-admin" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Alterando o AdminSite
&lt;/h2&gt;

&lt;p&gt;Para alterar o &lt;code&gt;AdminSite&lt;/code&gt; é bem simples, criando um arquivo &lt;code&gt;core/site.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# core/site.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.admin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AdminSite&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomSite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AdminSite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CustomSite&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E alterando, então, o &lt;code&gt;core/urls.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# core/urls.py
from django.urls import path
from .site import site

urlpatterns = [
    path('admin/', site.urls),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tela inicial do &lt;em&gt;admin&lt;/em&gt; nesse momento, assume uma outra estética, sem permissão pra visualizar ou editar nada (pois não há nenhum &lt;em&gt;model&lt;/em&gt; registrado):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K8Cag491--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcir5ojotolsi2do243n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K8Cag491--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcir5ojotolsi2do243n.png" alt="página inicial do admin do django sem models" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AdminSite Padrão
&lt;/h2&gt;

&lt;p&gt;No exemplo acima, substituímos o import do &lt;code&gt;admin.site&lt;/code&gt; original pelo &lt;code&gt;.site.admin&lt;/code&gt;. Podemos alterar, porém, esse código para substituir o &lt;code&gt;AdminSite&lt;/code&gt; padrão do projeto e, dessa forma, não perderemos os &lt;em&gt;models&lt;/em&gt; registrados automáticamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# core/site.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.admin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AdminSite&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomSite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AdminSite&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_app_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""
        Return a sorted list of all the installed apps that have been
        registered in this site.
        """&lt;/span&gt;
        &lt;span class="n"&gt;app_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_build_app_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Sort the apps alphabetically.
&lt;/span&gt;        &lt;span class="n"&gt;app_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="c1"&gt;# Sort the models alphabetically within each app.
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;app_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'models'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'models'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;app_list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foi adicionado, aqui, um código apenas para alterar a ordem dos &lt;em&gt;models&lt;/em&gt; dentro dos &lt;em&gt;apps&lt;/em&gt; para que possamos ver efeitos visuais na tela (Referência do código &lt;a href="https://books.agiliq.com/projects/django-admin-cookbook/en/latest/set_ordering.html"&gt;aqui&lt;/a&gt;). Além disso, foi removida aquela chamada &lt;code&gt;site = CustomSite()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A partir de agora, é preciso reverter o &lt;code&gt;core/urls.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# core/urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'admin/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Além desses dois passos, é preciso criar um &lt;code&gt;core/apps.py&lt;/code&gt; onde será criada uma instância da classe &lt;code&gt;AdminConfig&lt;/code&gt; definindo quem é o &lt;code&gt;AdminSite&lt;/code&gt; padrão:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# core/apps.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.admin.apps&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AdminConfig&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAdminConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AdminConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;default_site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'core.site.CustomSite'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por último, no &lt;code&gt;settings.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# core/settings.py
&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.auth'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.contenttypes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.sessions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.messages'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.staticfiles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É preciso substituir o &lt;code&gt;django.contrib.admin&lt;/code&gt; pelo &lt;code&gt;core.apps.MyAdminConfig&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# core/settings.py
&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;'core.apps.MyAdminConfig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.auth'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.contenttypes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.sessions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.messages'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'django.contrib.staticfiles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resultados
&lt;/h2&gt;

&lt;p&gt;Quando entramos na página inicial do &lt;em&gt;admin&lt;/em&gt; agora, temos a seguinte exibição:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vXXUxCXY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3htlmaxbne69hapujfre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vXXUxCXY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3htlmaxbne69hapujfre.png" alt="django-admin" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quando alteramos o código comentando a seguinte linha, passamos a ter outro resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app['models'].reverse()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FtuZvuyI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy6kfk1f89ax032zc62e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FtuZvuyI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy6kfk1f89ax032zc62e.png" alt="django-admin" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O código fonte final desse texto está disponível no &lt;a href="https://github.com/EduardoJM/code-of-articles/tree/main/custom-admin-site"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/4.1/ref/contrib/admin"&gt;The Django admin site&lt;/a&gt; - Django Documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://books.agiliq.com/projects/django-admin-cookbook/en/latest/set_ordering.html"&gt;5. How to set ordering of Apps and models in Django admin dashboard&lt;/a&gt; - Django Admin Cookbook&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>django</category>
      <category>webdev</category>
      <category>python</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Django + Celery: testando sistemas com filas</title>
      <dc:creator>Eduardo Oliveira</dc:creator>
      <pubDate>Tue, 27 Sep 2022 01:45:35 +0000</pubDate>
      <link>https://dev.to/eduardojm/django-celery-testando-sistemas-com-filas-3e1n</link>
      <guid>https://dev.to/eduardojm/django-celery-testando-sistemas-com-filas-3e1n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Esse texto tem o objetivo de descrever como foram implementados os testes com o django e o celery rodando juntos no back-end de um MVP de um produto. Não é objetivo desse texto descrever ou discutir as melhores práticas para testes com filas (&lt;em&gt;melhores práticas, geralmente, dependem de contextos mais amplos&lt;/em&gt;), mas sim exemplificar um caso de uso em que precisávamos testar as nossas tarefas de segundo plano.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conteúdos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introdução&lt;/li&gt;
&lt;li&gt;Objetivos&lt;/li&gt;
&lt;li&gt;Exemplo de Usos&lt;/li&gt;
&lt;li&gt;Estratégias&lt;/li&gt;
&lt;li&gt;Mocks com Unittest&lt;/li&gt;
&lt;li&gt;Referências&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introdução &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;celery&lt;/strong&gt; é, de forma bem resumida, um sistema de filas que permite a execução de tarefas em segundo plano. &lt;/p&gt;

&lt;p&gt;Imagine, por exemplo, uma aplicação de um fórum que, quando você envia uma requisição &lt;code&gt;POST&lt;/code&gt; para comentar um tópico, seu sistema precise enviar um e-mail de notificação para todos os membros que já comentaram no tópico.&lt;/p&gt;

&lt;p&gt;Suponha, seguindo o exemplo, que o envio de cada e-mail leve 500 milissegundos e que seu sistema precise enviar 15 e-mails. Perceba que, nessa lógica, se você faz essa requisição e espera o envio dos e-mails, comentar no tópico do seu fórum vai levar cerca de 7 segundos, o que é um tempo muito grande.&lt;/p&gt;

&lt;p&gt;Uma forma de contornar isso: filas e processamento paralelo (com o que a gente costuma chamar de &lt;code&gt;workers&lt;/code&gt;). O sistema agenda numa fila o envio dos e-mails e termina a requisição devolvendo as informações para o app/front-end. Nesse contexto, o envio dos e-mails será feito "em segundo plano".&lt;/p&gt;

&lt;h2&gt;
  
  
  Objetivos &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Foi feita, até aqui, uma breve descrição do que é o &lt;strong&gt;celery&lt;/strong&gt; e, agora, é importante deixarmos claro algumas coisas: esse texto não tem como objetivo ensinar você a usar o &lt;strong&gt;celery&lt;/strong&gt; em conjunto (ou separado) com o &lt;strong&gt;django&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O objetivo desse texto é tratar sobre algumas estratégias de como escrever testes para o &lt;strong&gt;django&lt;/strong&gt; junto com o &lt;strong&gt;celery&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exemplo de Uso &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Existem dois conceitos (ou duas formas de fazer a mesma coisa) que precisamos levar em consideração para implementar nossos testes e, portanto, precisamos detalhar isso com um exemplo de uso.&lt;/p&gt;

&lt;p&gt;Suponha um &lt;em&gt;model&lt;/em&gt; de compras que registra o preço da compra e suponha um outro &lt;em&gt;model&lt;/em&gt; de produto que indica o valor vendido em produtos daquele tipo (a modelagem de dados abaixo é só pra exemplificar e não foi elaborada da melhor forma possível) e, para completar, uma tarefa (entenda tarefa como um método, uma função) que executa um cálculo de soma de valores vendidos desse produto específico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DecimalField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_digits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decimal_places&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total_purchased_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DecimalField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_digits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decimal_places&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DecimalField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_digits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decimal_places&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Purchase&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Purchases&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/tasks.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calculate_total_purchased_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total_purchased_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id_product&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Purchase&lt;/span&gt;

    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;id_product&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;purchases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;purchase&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;purchases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purchase_final_value&lt;/span&gt;

    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_purchased_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Até aqui, é importante perceber que não chamamos a nossa tarefa em nenhum momento e, também, que ela faz um cálculo bem mal feito e que existem formas melhores de fazer isso (&lt;a href="https://docs.djangoproject.com/en/4.0/topics/db/aggregation/#following-relationships-backwards" rel="noopener noreferrer"&gt;Referência Aqui&lt;/a&gt;), porém esse exemplo tem apenas o objetivo de mostrar como testar.&lt;/p&gt;

&lt;p&gt;Outra questão pertinente é que o import dos models (&lt;code&gt;from app.models import Product, Purchase&lt;/code&gt;) em &lt;code&gt;app/tasks.py&lt;/code&gt; foi feito dentro do método &lt;code&gt;calculate_total_purchased_value&lt;/code&gt; com o objetivo de evitar problemas de dependências circulares, tendo em vista que iremos importar esse método no &lt;code&gt;app/models.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Por fim, para a chamada da nossa tarefa ao criar novas &lt;em&gt;purchases&lt;/em&gt;, podemos fazer o seguinte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/models.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;calculate_total_purchased_value&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DecimalField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_digits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decimal_places&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total_purchased_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DecimalField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_digits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decimal_places&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DecimalField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_digits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decimal_places&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;calculate_total_purchased_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;countdown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Purchase&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Purchases&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui vamos apenas chamar o método quando algum model Purchase for salvo pois é um exemplo e não precisamos lidar com outros casos de uso (deletar, etc.). A outra consideração é: existem duas formas de executar a tarefa em segundo plano:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;.apply_async()&lt;/code&gt;: mais completa, os argumentos devem ser passados como o parâmetro &lt;code&gt;args&lt;/code&gt; e permite que eu adicione um countdown (tempo para que ela seja executada, &lt;a href="https://docs.celeryq.dev/en/stable/userguide/calling.html#eta-and-countdown" rel="noopener noreferrer"&gt;referência aqui&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.delay()&lt;/code&gt;: não permite countdown ou alguns outros parâmetros, a chamada é bem parecida com a chamada de uma função normal: &lt;code&gt;calculate_total_purchased_value.delay(self.product.pk)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;É importante para os próximos passos desse texto, sobre os testes, ter em mente qual dos dois métodos foi usado pois para a estratégia que será utilizada, terá algumas diferenças entre o uso de um ou de outro. &lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégias &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Podemos pensar, de forma básica, em duas estratégias para escrever testes (testar) o nosso processamento paralelo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Testar se as tarefas estão sendo "enfileiradas", isso é, se o método &lt;code&gt;apply_async&lt;/code&gt; ou &lt;code&gt;delay&lt;/code&gt; está sendo executado e, depois, testar o método &lt;code&gt;calculate_total_purchased_value&lt;/code&gt; separadamente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fazer com que ao invés de rodar o processamento paralelo, ele seja executado síncrono e então conseguimos fazer todos os testes como se não existisse processamento em segundo plano.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nesse texto, iremos tratar da primeira estratégia. Nem sempre é a melhor estratégia, porém, é a que teve menor custo (adequações, cognitivo e de tempo) para ser implementada no projeto no momento em que foi decidido adicionar testes de integração no back-end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocks com unittest &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Pra usarmos a estratégia comentada acima (Item 1), o que faremos é: criar um &lt;code&gt;mock&lt;/code&gt; para a função &lt;code&gt;apply_async&lt;/code&gt; (ou &lt;code&gt;delay&lt;/code&gt;) que é adicionada na função da nossa tarefa (pelo &lt;em&gt;decorator&lt;/em&gt; do &lt;em&gt;celery&lt;/em&gt;). Podemos começar criando um &lt;strong&gt;TestCase&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/tests.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;products.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Purchase&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;First Product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_save_purchase_call_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A próxima coisa que precisamos fazer é criar nosso mock, podemos utilizar o &lt;em&gt;decorator&lt;/em&gt; &lt;code&gt;@patch&lt;/code&gt; do unittest (&lt;a href="https://docs.python.org/3/library/unittest.mock.html#quick-guide" rel="noopener noreferrer"&gt;referência&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/tests.py
# ...
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;
   &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products.tasks.calculate_total_purchased_value.apply_async&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_save_purchase_call_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_async_mock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceba que o parâmetro utilizado dentro do &lt;code&gt;@patch&lt;/code&gt;. É o caminho absoluto de importação do app (app do django), arquivo &lt;code&gt;tasks.py&lt;/code&gt;, método &lt;code&gt;calculate_total_purchased_value&lt;/code&gt;, método &lt;code&gt;apply_async&lt;/code&gt; adicionado pelo &lt;em&gt;decorator&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Agora, precisamos criar um item do &lt;em&gt;Purchase&lt;/em&gt; para podermos validar se nossa tarefa foi executada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/tests.py
# ...
&lt;/span&gt;
    &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products.tasks.calculate_total_purchased_value.apply_async&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_save_purchase_call_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_async_mock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finalmente, precisamos fazer a nossa &lt;code&gt;assertion&lt;/code&gt; sobre a função &lt;code&gt;apply_async&lt;/code&gt; ter sido chamada ou não:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/tests.py
# ...
&lt;/span&gt;
    &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products.tasks.calculate_total_purchased_value.apply_async&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_save_purchase_call_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_async_mock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;apply_async_mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;countdown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finalmente, rodando um comando &lt;code&gt;python manage.py test&lt;/code&gt;, obtemos o incrível resultado esperado:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy241p1sjjyfjur58ojne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy241p1sjjyfjur58ojne.png" alt="Terminal mostrando testes passando"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agora, podemos testar o outro lado da história (verificar se a tarefa &lt;code&gt;calculate_total_purchased_value&lt;/code&gt; faz o que desejamos que ela faça). Vamos continuar utilizando o &lt;code&gt;@patch&lt;/code&gt; nesse momento pois vamos precisar criar &lt;em&gt;models&lt;/em&gt; &lt;strong&gt;Purchase&lt;/strong&gt; e não queremos que ele chame a tarefa original nesse momento. Fora isso, o processo de testar agora é bem parecido com testar uma função comum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# products/tests.py
# ...
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;products.tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;calculate_total_purchased_value&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;

    &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products.tasks.calculate_total_purchased_value.apply_async&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_background_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_async_mock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;purchase1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;purchase1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
        &lt;span class="n"&gt;purchase1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
        &lt;span class="n"&gt;purchase1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;purchase2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;purchase2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
        &lt;span class="n"&gt;purchase2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
        &lt;span class="n"&gt;purchase2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="nf"&gt;calculate_total_purchased_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_purchased_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executando nossos testes novamente, vemos agora dois testes passando:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffn8pvie75psf6tveqy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffn8pvie75psf6tveqy8.png" alt="Dois testes passando"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nosso arquivo &lt;code&gt;tests.py&lt;/code&gt; final ficou dessa forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;products.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Purchase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;products.tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;calculate_total_purchased_value&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;First Product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products.tasks.calculate_total_purchased_value.apply_async&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_save_purchase_call_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_async_mock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;apply_async_mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;countdown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products.tasks.calculate_total_purchased_value.apply_async&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_background_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apply_async_mock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;purchase1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;purchase1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
        &lt;span class="n"&gt;purchase1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
        &lt;span class="n"&gt;purchase1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;purchase2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;purchase2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;
        &lt;span class="n"&gt;purchase2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purchase_final_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
        &lt;span class="n"&gt;purchase2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="nf"&gt;calculate_total_purchased_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_purchased_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceba que aqui só escrevemos testes extremamente simples e não estamos nos preocupando com cenários de testes, efeitos colaterais nem nada do gênero, afinal esse texto é apenas um exemplo.&lt;/p&gt;

&lt;p&gt;O código escrito nesse texto pode ser encontrado no seguinte repositório: &lt;a href="https://github.com/EduardoJM/code-of-articles/tree/main/testing-django-celery-tasks" rel="noopener noreferrer"&gt;testing-django-celery-tasks&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Referências &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/en/4.0/topics/db/aggregation/#following-relationships-backwards" rel="noopener noreferrer"&gt;Aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.celeryq.dev/en/stable/userguide/calling.html#eta-and-countdown" rel="noopener noreferrer"&gt;Calling Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/unittest.mock.html#quick-guide" rel="noopener noreferrer"&gt;unittest.mock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>django</category>
      <category>celery</category>
      <category>testing</category>
      <category>braziliandevs</category>
    </item>
  </channel>
</rss>
