Home > Software design >  Django - Problem with DB query using foreign key
Django - Problem with DB query using foreign key

Time:10-21

I'm using Django for a Web app and I have the following data model:

class classi(models.Model):
nome = models.TextField(null=True)
class Meta:
    db_table = 'classi'

class users(models.Model):
    name = models.TextField(null=True)
    email = models.TextField(null=True)
    password = models.TextField(null=True)
    classe = models.ForeignKey(classi, db_column='classe', on_delete=models.CASCADE, null=True)
    class Meta:
        db_table = 'users'

class smartphone(models.Model):
    marca = models.TextField(null=True)
    modello = models.TextField(null=True)
    possessore = models.ForeignKey(users, db_column='possessore', on_delete=models.CASCADE, null=True)
    class Meta:
        db_table = 'smartphone'

My goal is to show, on an HTML page, all classi, and for each classi all users and for each user all smartphone.

How can I implement my view.py and my html file?

The only solution that I found is to scan all table with a for loop and, through a condition, select the row using foreign key:

   {% for c in classi %}
        <p>{{ c.nome }}</p>
        {% for u in users %}
            {% if  u.classe  == c  %}
                <p>{{ u.name }}, {{ u.email }}, {{ u.password }}</p> 
                {% for s in smartphone %}
                    {% if  s.possessore  == u  %}<p>{{ s.marca }}, {{ s.modello }}</p> {% endif %}
                {% endfor %}
            {% endif %}
        {% endfor %}
    {% endfor %}

Is there a better solution?

CodePudding user response:

You can use *prefetch_related for prefetching related object from foreign key. More information: https://docs.djangoproject.com/fr/4.1/ref/models/querysets/#prefetch-related

The view:

def view(request):
    classe = Classi.objects.all().prefetch_related("users_set").prefetch_related("users_set__smartphone_set")
    context["classe"] =) classe
    return render(request, 'template.html', context)

And the html:

{% for c in classe %}
    <p>{{ c.nome }}</p>
    {% for u in c.users_set %}
        <p>{{ u.name }}, {{ u.email }}, {{ u.password }}</p> 
        {% for s in u.smartphone_set %}
            <p>{{ s.marca }}, {{ s.modello }}</p>
        {% endfor %}
    {% endfor %}
{% endfor %}

CodePudding user response:

You could use backwards relations, which will only have items that have a connection

The general format is obj.{model}_set but you can also set it to something different in the models.py with the related_name attr in the ForeignKey field

  {% for c in classi %}
        <p>{{ c.nome }}</p>
        {% for u in c.user_set.all %}
              <p>{{ u.name }}, {{ u.email }}, {{ u.password }}</p> 
              {% for s in u.smartphone_set.all %}
                  <p>{{ s.marca }}, {{ s.modello }}</p>
              {% endfor %}
        {% endfor %}
    {% endfor %}

Or, and this is probably cleaner:
Go the reverse way by looping through the smartphones in the top and just order smartphones by nome in the view with smartphoneList = smartphone.objects.all().order_by('possessore__classe__nome') so they are grouped together

{% for s in smartphoneList %}

  <p>{{ s.possessore.classe.nome }}</p>
  <p>{{ s.possessore.name }}, {{ s.possessore.email }}, {{ s.possessore.password }}</p> 
  <p>{{ s.marca }}, {{ s.modello }}</p>

{% endfor %}

Note after writing this I noticed @Lucas Grugru as already posted the reverse relations way.. From my testing it doesn't require pre-fetching, but it's probably a good idea to do that (I also have no idea). I'm still posting this because I think looping through smartphones might be a cleaner way of doing it

  • Related