pythondjangodjango-contenttypes

How to do proper reverse querying when using Django generic relations?


I have implemented django generic relations to create relations between models. My models are,

class CessPoint(BaseModel):
    ....
    title = models.CharField(max_length=100)

class BilledVehicle(BaseModel):
    ....
    source = models.ForeignKey(CessPoint, on_delete=models.CASCADE)

class Bill(BaseModel):
    .....
    content_type = models.ForeignKey(
    ContentType, on_delete=models.CASCADE, null=True)
    object_id = models.UUIDField(null=True)
    billed_item = GenericForeignKey()

class BillPayment(BaseModel):
    .....
    bill = models.ForeignKey(Bill, on_delete=models.CASCADE)        

I would like to get the payments for BilledVehicle. This is how I would like to go about it.

BillPayment.objects.filter(bill__content_type=ContentType.objects.get_for_model(BilledVehicle)).values('bill__billed_item__source__title').annotate(total_paid=Sum('amount_paid')).order_by('-total_paid')   

I am getting the error:

Field 'billed_item' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.

According to this answer, How to traverse a GenericForeignKey in Django?, defining a GenericRelation might solve my problem. But then again, I did not define a GenericRelation because adding one will cascade all relations as per the default behavior

Unlike ForeignKey, GenericForeignKey does not accept an on_delete argument to customize this behavior; if desired, you can avoid the cascade-deletion simply by not using GenericRelation, and alternate behavior can be provided via the pre_delete signal.


Solution

  • If you do not want to create a GenericRelation object in your BilledVehicle module, django will have no way of linking it directly to your Bill using values.

    You can work around this by using a dictionary to store unique CessPoint titles and their total (do the annotation yourself) from the BillPayment queryset amount paid as follows.

    qs = BillPayment.objects.filter(
        bill__content_type=ContentType.objects.get_for_model(BilledVehicle))
    ds = {}
    
    for billPayment in qs:
        title = billPayment.billed_item.source.title
        ds[title] = ds.get(title, 0) + billPayment.amount_paid