Search code examples
django-models

Retrive Django related table data based on entity type and id


I am having some difficulties to write this django queries. I have the following scenario.

I have three django models

  • User
  • Visitor
  • Participants

For each interaction of a user and a visitor, I add some participants

participant_id entity_type entity_id 
    1             user        1
    2             visitor     1
    3             user        1
    4             visitor     2
    5             user        1
    6             visitor     3

Entity type can be either user or visitor. How can I get related user and visitor data from entity id and entity type?


Solution

  • If you can refactor your solution to generic-relations, the answer is easy, because django already implements solution for you:

    class Participant(Model):
    
        entity_type = ForeignKey(ContentType)
        entity_id = CharField(_('Sourced object id'))
    
        content_object = GenericFields('entity_type', 'entity_id')
    

    And after:

    user_or_visitor = participant.content_object 
    

    more here: https://docs.djangoproject.com/en/5.0/ref/contrib/contenttypes/#generic-relations

    Don't forget that you are getting here the “N+1 queries” performance problem "by default" and should take care of it with GenericPrefetch(). more here: https://docs.djangoproject.com/en/5.0/ref/contrib/contenttypes/#genericprefetch

    If you can not change current project models:

    Preparing data:

    participants = Participant.objects.all()
    
    visitors_ids = participants.objects.filter(entity_type="visitor").values_list('pk', flat=True)
    users_ids = participants.objects.filter(entity_type="user").values_list('pk', flat=True)
    
    users = User.objects.filter(id__in=users_ids).in_bulk()
    visitors = Visitors.objects.filter(id__in=visitors_ids).in_bulk()
    

    Here you have made only 2 database queries, but you have stored the objects in memory.

    Somewhere later:

    for participant in participants.objects.all():
        participant.entity = None
        if participant.entity_type == "visitor":
            participant.entity = visitors[participant.entity_id]
        elif participant.entity_type == "user":
            participant.entity = users[participant.entity_id]
    

    Here you have made only one request to db.

    Of course, I can offer a more complex queryset request solution, but in my opinion, simple is better than complex.