Search code examples
djangodjango-querysetdjango-contenttypesdjango-generic-relations

How to access objects of grandparent model associated with target model through ContentType GenericForeignKey?


I'm trying to filter objects of model based on associated grandparent model. They are associated with each other through an intermediary parent model. Parent model is associated with grandparent through ContentType GenericForeignKey. How can I access all objects of target model sharing same Grandparent?

I tried to use GenericRelations on Grandparent but it did not work as it returns all the Parent Objects associated with that GrandParent Model. For that, I've to loop through querysets. Please check the code for details:

class State(models.Model):
    name = models.CharField(max_length=25)
    population = models.PositiveIntegerField()

class UnionTerritory(models.Model):
    name = models.CharField(max_length=25)
    population = models.PositiveIntegerField()

class District(models.Model):
    name = models.CharField(max_length=25)
    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type','object_id')
    population = models.PositiveIntegerField()

class Town(models.Model):
    name = models.CharField(max_length=25)
    district = models.ForeignKey(District,related_name='towns',on_delete=models.CASCADE)
    population = models.PositiveIntegerField()

"""Here, District can be connected to State or UnionTerritory but town will always be part of district."""

Now, if I select any State or UnionTerritory Object; I want to have access to all towns under it. I want to filter all Town instances who share same State or UnionTerritory. Towns can be connected to different districts which belong to same state or same UnionTerritory. How can I access UnionTerritory or State associated with Town and then filter town objects accordingly. Is there any way to avoid looping through querysets to achieve this?


Solution

  • I got an answer for above question, few days back. The trick lies in including GenericRelation() in parent model to which ContentType Foreignkey may possibly point. I used GenericRelation on Grandparent model. The code goes like this:

    #in models.py:
    
    from django.contrib.contenttypes.fields import GenericRelation
    
    class State(models.Model):
        name = models.CharField(max_length=25)
        population = models.PositiveIntegerField()
        **districts = GenericRelation(District)**
    
    """this GenericRelation allows us to access all districts under particular state using
    state.districts.all() query in case of genericforeignkey reverse relation.
    **note:** You can use GenericRelation(**'***module_name*.District**'**) to avoid any circular
    import error if District Model lies in another module as it was in my case."""
    
    # same can be done with UnionTerritory Model
    
    class UnionTerritory(models.Model):
        name = models.CharField(max_length=25)
        population = models.PositiveIntegerField()
        districts = GenericRelation(District) 
    
    #Other models required no change.
    

    The real trick goes in views.py. I'm not sure if this can be called as proper solution or work-around but it does give the intended results. Assume, I want to access list of all towns in specific state, the code goes like this:

    #in views.py,
    from django.shortcuts import get_object_or_404
    
    def state_towns(request,pk):
        target_state = get_object_or_404(State,pk=pk)
        districts_under_state = target_state.districts.all()
        towns_under_state = Town.objects.filter(name__in=districts_under_state).all()
    
    """first line gives us the state for which we want to retrieve list of towns. 
    Second line will give us all the districts under that state and third line 
    will finally filter out all towns those fall under those districts. 
    Ultimately, giving us all the towns under target state."""
    

    Guys, I am not very experienced in django. So, please inform me if there is any mistake in this code or if there is a better way to implement this. Those who have same problem like me, this can be our solution till better one comes. Thanks.