Executando verificação de segurança...
3

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.

Carregando publicação patrocinada...