Impl expansão dinâmica de campos na API
Mixin de Serializer que implementa expansão dinâmica de campos via parâmetros de query string:
-
expand: expande os campos informados -
include: inclui apenas os campos informados -
exclude: exclui os campos informados
Exemplos:
- ?expand=campo1;campo2.sub_campo1,sub_campo2;campo3.sub_campo1.sub_sub_campo1,sub_sub_campo2
- ?include=campo1;campo2.sub_campo1,sub_campo2;campo3.sub_campo1.sub_sub_campo1,sub_sub_campo2
- ?exclude=campo1;campo2.sub_campo1,sub_campo2;campo3.sub_campo1.sub_sub_campo1,sub_sub_campo2
- ?expand=campo1&include=campo1.id,name&exclude=campo1.secret_field
Onde:
- campo1, campo2, campo3 são campos do model raiz
- sub_campo1, sub_campo2 são campos relacionados do campo2
- sub_sub_campo1, sub_sub_campo2 são campos relacionados do sub_campo1
Ou seja:
- ";" separa campos independentes
- "," separa campos relacionados do mesmo campo pai
- "." indica aprofundamento do campo
e ainda:
-
expandpode ser usado para expansão direta, ou seja, campo1.sub_campo1 já expande campo1 -
expand,includeeexcludepodem ser usados juntos na mesma requisição -
includeeexcludesó funcionam em subniveis se o campo pai estiver expandido -
includetem precedência sobreexcludee já remove todo o resto -
excluderemove o campo do resultado final, mesmo que esteja eminclude - Se nenhum dos parâmetros for informado, nenhum campo será expandido
- Filtros da API, paginação (
pageepage_size) e ordenação (o) podem ser usados normalmente para filtrar os resultados
Atenção:
- A expansão não é aplicada para o model User do Django
- A expansão não é aplicada para campos customizados que utilizam SerializerMethodField
- Uma exceção é lançada e registrada no log caso ocorra algum erro na expansão de algum campo, inclusive devido a recursão infinita
- A expansão automática de todos os campos relacionados (expand=all) está desabilitada por necessidade de controle mais refinado.
- A expansão de campos relacionados ForeignKey e OneToOne é suportada.
- A expansão de campos relacionados ManyToMany é suportada.
- A expansão de campos relacionados reversos (related_name) não é suportada, mas pode ser implementada manualmente no serializer customizado, ou vir a ser implementada no futuro.
- O mixin ApiFilterSetMixin não aplica filtros nas expansões
- selection_related e prefetch_related não são aplicados automaticamente, podendo ser implementados manualmente na viewset customizada, ou vir a ser implementados no futuro (pretendo fazer esta impl o mais rápido possível). Observo que, mesmo sem a aplicação destes recursos, a expansão é mais eficiente que sem ela, pois, invariavelmente, se o client precisar desses dados, atualmente ele o fará em uma nova requisição que fará o select da mesma forma.
Exemplos reais em produção já sendo utilizados no fork cmjatai/cmj:
Exemplo de expansão única:
https://www.jatai.go.leg.br/api/materia/tramitacao/?expand=status
Expandindo dois campos em mesmo nível:
https://www.jatai.go.leg.br/api/materia/tramitacao/?expand=status;unidade_tramitacao_local
Expansão em níveis diferentes:
https://www.jatai.go.leg.br/api/materia/tramitacao/?expand=status;unidade_tramitacao_local.orgao
Expansão com inclusão em conjunto em terceiro nível:
https://www.jatai.go.leg.br/api/materia/tramitacao/?expand=status;unidade_tramitacao_local.orgao&include=unidade_tramitacao_local.orgao.id,nome,sigla
Expansão, exclusão e inclusão em níveis diferentes: https://www.jatai.go.leg.br/api/materia/tramitacao/?expand=status;unidade_tramitacao_local.orgao&include=unidade_tramitacao_local.orgao.id,nome,sigla&exclude=unidade_tramitacao_local.link_detail_backend,comissao,parlamentar
Leitura completa deste link de exemplo:
- expand faz com que status, unidade_tramitacao_local e unidade_tramitacao_local.orgao sejam expandidos
- include informando que em orgao que ta dentro de unidade_tramitacao_local, seja recuperado apenas os campos id, nome e sigla de órgão.
- exclude informando que em unidade_tramitacao_local os campos link_detail_backend, comissão, e parlamentar devem ser retirados da serialização
os exemplos acima foram feitos só no endpoint /api/materia/tramitacao mas a dinamica já funciona na api inteira