J'utilise des Generic Foreign Key sur un de mes projets, et je rencontre quelques difficultés pour travailler avec, dès qu'il s'agit de faire des requêtes pourtant peu complexes.
J'ai en gros deux questions assez basique :
- Comment faire une requête de type monmodel.contenttype_set.all() comme on les retrouves sur une FK classique ?
- Pourquoi, lorsque je set une limit sur les content types admis, je peux quand même, en console interactive par exemple, lier la FK à un type en dehors de cette limite ?
Histoire d'être complet, j'ai développé un petit exemple.
Imaginons le schéma suivant :
from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class City(models.Model):
name = models.CharField(max_length=255)
class People(models.Model):
""" Des êtres humains, avec plus de trucs que ce qui est écrit ici bien entendu.
"""
name = models.CharField(max_length=255)
specific_attribute = models.CharField('human specific attribute')
city = models.ForeignKey(City)
class Company(models.Model):
""" Des sociétés, avec une logique métier qui leur est propre.
Bien entendu dans l'exemple on a l'impression que c'est pareil...
"""
name = models.CharField(max_length=255)
specific_attribute = models.CharField('human specific attribute')
city = models.ForeignKey(City)
class Invoice(models.Model):
""" Les factures peuvent être adressées soit à des sociétés,
soit à des humains.
"""
number = models.IntegerField()
limit = models.Q(app_label='my_app',
model='people') | models.Q(app_label='my_app',
model='company')
content_type = models.ForeignKey(ContentType,
verbose_name='adressed to',
limit_choices_to=limit,
null=False,
blank=False,)
object_id = models.PositiveIntegerField(
verbose_name='related entity',
null=False,
)
content_object = GenericForeignKey('content_type', 'object_id')
OK, situation assez basique. Imaginons que j'ai en base la ville de Nantes. Je pourrais trouver tous les gens qui y sont installés grâce à :
nantes = City.object.get(pk=1)
nantes.people_set.all()
Et de l'autre côté, c'est encore plus simple :
bertrand = People.object.get(pk=1)
bertrand.city # on a directement accès à l'objet
Maintenant, dans le cas de ma generic FK. C'est encore simple quand on part de la facture :
facture = Invoice.object.get(pk=1)
facture.content_object # directement l'objet lié
facture.content_type # le content type, histoire de savoir à quoi l'instance est liée
facture.object_id # l'identifiant de l'object stocké dans content type. Useless
En revanche, imaginons que je veuille chercher rapidement toutes les factures émises à notre ami Bertrand, déclaré un peu plus haut. Comment faire ? Contrairement à nantes et son peopleset, je n'ai pas accès à un invoiceset dans l'object people ou dans l'object company.
Quelle est la meilleure manière de faire, par exemple, la requête pour la page de listing des factures pour un particulier ?