Search code examples
pythondjangodjango-orm

How can I generate an annotated field with list of values in Django queryset?


I need to make up an advanced query that allows me get a queryset containing dictionaries with strings for keys and lists with all possible values for values. Here is what I got:

In [26]: Offer.objects.values('partner', 'partner_id')

Out[26]: <QuerySet [{'partner': 'xex', 'partner_id': 'x_999'}, {'partner': 'xex', 'partner_id': 'x_888'}, {'partner': 'quiup', 'partner_id': 'q_888'}, {'partner': 'quiup', 'partner_id': 'q_777'}]>

Here is what I need to get:

<QuerySet [{'partner': 'xex', 'partner_ids': ['x_999', 'x_888']}, {'partner': 'quiup', 'partner_ids': ['q_888', 'q_777']}

I've tried annotations, F-expressions, `Value`, `.dictinct('partner')` but nothing brings me any closer. Any suggestions will be much appreciated and thank you very much for your attention.

UPD. Related model provided:

class Offer(models.Model):
partner = models.CharField(
    verbose_name='Partner',
    max_length=50
)
partner_id = models.CharField(
    verbose_name='Partner ID',
    max_length=100,
    unique=True,
    null=False,
    blank=False,
    default=''
)

def __str__(self):
    return f'{self.partner} - {self.partner_id}'

Solution

  • It is possible, but likely not a good idea. We can generate this with an ArrayAgg expression [Django-doc]:

    from django.contrib.postgres.aggregates import ArrayAgg
    
    Offer.objects.values('partner').annotate(
        partners_ids=ArrayAgg('partner_id')
    ).order_by('partner')

    Since the partner seems a categorical, it is better to work with a ForeignKey to something, like:

    class PartnerType(models.Model):
        id = models.CharField(primary_key=True)
    
    
    class Partner(models.Model):
        pass
    
    
    class Offer(models.Model):
        partner_type = models.ForeignKey(PartnerType, on_delete=models.PROTECT)
        parter = models.ForeignKey(Partner, on_delete=models.PROTECT)

    in that case, we can use:

    PartnerType.objects.prefech_related('offer_set')

    This will also prevent us from primitive obsession [refactoring.guru].