Retornando objetos aninhados no Django Rest Framework
Num dos projetos que estou trabalhando surgiu uma demanda do cliente que quer que determinado objeto, que vou chamar de ObjetoA ao ser retornado por um endpoint inclua alguns atributos de uma segunda classe, que foi chamar de ObjetoB, que possui um FK para o ObjetoA, entretanto no os atributos só devem ser retornados se o usuário que está realizando a consulta possua registros no ObjetoB, que possui um FK apontando para o usuário.
Código do Objeto A
class PremioPremiacao(Base)
titulo = models.CharField('Título', max_length=250)
descricao = models.TextField('Descrição')
foto = OptimizedImageField(upload_to='premio/foto', optimized_image_output_size=(1024, 768),
optimized_image_resize_method='cover')
pontuacao = models.PositiveSmallIntegerField('Pontuação Mínima para Resgate')
def __str__(self):
return f"{self.titulo}"
class Meta:
verbose_name = 'Prêmio Premiação'
verbose_name_plural = 'Prêmio Premiações'
ordering = ['pontuacao', ]
Código do Objeto B
class ProfissionalPremiacao(Base)
SOLICITADO = 0
APROVADO = 1
CANCELADO = 2
REJEITADO = 3
CHOICES_STATUS = [(SOLICITADO, "Solicitado"), (APROVADO, "Aprovado"), (CANCELADO, "Cancelado"),
(REJEITADO, "Rejeitado")]
fk_profissional = models.ForeignKey(Profissional, on_delete=models.PROTECT)
fk_premio = models.ForeignKey(PremioPremiacao, on_delete=models.PROTECT, default=0)
pontos = models.PositiveSmallIntegerField('Pontos solicitados para recuperação')
status = models.PositiveIntegerField("Status", choices=CHOICES_STATUS, default=0)
def __str__(self):
return f"Profissional: {self.fk_profissional.nome} | Pontos: {self.pontos} | Stattus: {self.status}"
class Meta:
verbose_name = 'Profissional Premiação'
verbose_name_plural = 'Profissional Premiações'
ordering = ['pontos', ]
Serializer do ObjetoA
class PremioPremiacaoProfissionalGetSerializer(FieldsListSerializerMixin, ModelSerializer)
"""
Class de serialização dos premios cadastrados na plataforma com um campo adicional para os prêmios
que foram solicitados pelo prestador mostrando o status da solicitação.
"""
premio_profissional = SerializerMethodField()
def get_premio_profissional(self, obj):
"""
Método para retornar uma instância do prêmio selecionado pelo prestador
obj corresponde ao objeto no momento da consulta. Por default no DRF quando
criarmos um campo do tipo SerializerMethodField e implementamos um método com o prefixo
get_NOME_DO_CAMPO o próprio DRF se encarrega de invocar o método e atribuir o valor
ao campo.
"""
profissional_id = self.context.get('profissional_id')
premio_prestador_dict = None
for premios_prestador in ProfissionalPremiacao.objects.filter(fk_profissional=profissional_id,
fk_premio=obj.pk):
premio_prestador_dict = {
'premio': premios_prestador.fk_premio.pk,
'profissional': premios_prestador.fk_profissional.pk,
'status': premios_prestador.status
}
return premio_prestador_dict
class Meta:
model = PremioPremiacao
exclude = ['enabled', 'deleted', 'created_on', 'updated_on', ]
Para revolver o problema percebi que tinha dois grandes desafios, o primeiro deles era como passar o Pk do usuário logado do endpoint para o serializer, pois precisava filtrar os itens do ObjetoB apenas dos usuário logado, o outro era como criar um objeto que pudesse receber o resultado desse filtro e serializar junto ao ObjetoA.
Desafio 1 - Como passar o valor recebido no endpoint para o serializar. A solução que encontrei foi fazer uso da possibilidade de passar dados extras para o Serializer no construtor, ficando o código assim:
...
premios_data = PremioPremiacaoProfissionalGetSerializer(
premios, many=True, context={'profissional_id': profissional_id})
...
O valor extra foi passado pelo context, sendo o profissional_id o valor recebido no endpoint pela querystring
Para recuperar o valor passado pelo context no Serializer é bem simples
profissional_id = self.context.get('profissional_id')
Depois disso realizei o filtro tranquilamente no método get_premio_profissional.
Para resolver o segundo problema utilizei o SerializerMethodField(), que recebe o retorno do método, uma observação é necessária aqui. A ligação entre o método e o campo no serializar ocorre quando o nome do método é composto pelo prefixo get_ seguido do nome do campo, no caso como o nome do campo do tipo SerializerMethodField é premio_profissional o nome do método é get_premio_profissional
def get_premio_profissional(self, obj):
# Recuperando o PK do profissional enviado pelo context do Serializer
profissional_id = self.context.get('profissional_id')
# Criando o dicionário que retornará os dados caso exista
premio_prestador_dict = None
# Percorrendo todos os prêmios solicitados pelo profissional
for premios_prestador in ProfissionalPremiacao.objects.filter(
fk_profissional=profissional_id,
fk_premio=obj.pk):
# Atribuindo os valores do filtro para o dicionário
premio_prestador_dict = {
'premio': premios_prestador.fk_premio.pk,
'profissional': premios_prestador.fk_profissional.pk,
'status': premios_prestador.status
}
# Retornando os valores para o campo premio_profissional
return premio_prestador_dict
Como sabemos na computação não existe uma única solução para o problema, essa foi a abordagem que encontrei para resolver minha demanda.
Espero que esse artigo seja útil para quem venha a passar pelo mesmo problema que eu.