55 votes

django : comment effectuer une requête basée sur les champs de GenericForeignKey ?

Je suis novice dans l'utilisation de GenericForeignKey, et je n'ai pas réussi à le faire fonctionner dans une instruction de requête. Les tables sont à peu près comme suit :

class Ticket(models.Model):
    issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
    issue_id = models.PositiveIntegerField(null=True, blank=True)
    issue = generic.GenericForeignKey('issue_ct', 'issue_id')

class Issue(models.Model):
    scan = models.ForeignKey(Scan)

Une analyse crée une question, une question génère des tickets, et j'ai fait de la question une clé étrangère à la table des tickets. Maintenant j'ai un objet Scan, et je veux faire une requête pour tous les tickets qui sont liés à ce scan. J'ai d'abord essayé ceci :

tickets = Tickets.objects.filter(issue__scan=scan_obj)

ce qui ne fonctionne pas. J'ai alors essayé ceci :

issue = Issue.objects.get(scan=scan_obj)
content_type = ContentType.objects.get_for_model(Issue)
tickets = Tickets.objects.filter(content_type=content_type, issue=issue)

Ça ne marche toujours pas. J'ai besoin de savoir comment faire ce genre de requêtes dans django ? Merci.

73voto

girasquid Points 6902

El Ticket.issue que vous avez définis vous aideront à passer d'une Ticket à l'instance Issue auquel il est attaché, mais il ne vous laissera pas revenir en arrière. Vous y êtes presque avec votre deuxième exemple, mais vous devez utiliser la fonction issue_id vous ne pouvez pas effectuer de requête sur le champ GenericForeignKey (cela vous aide simplement à récupérer l'objet lorsque vous avez une Ticket instance). Essayez ceci :

from django.contrib.contenttypes.models import ContentType

issue = Issue.objects.get(scan=scan_obj)
tickets = Ticket.objects.filter(
    issue_id=issue.id,
    issue_ct=ContentType.objects.get_for_model(issue).id
    )

15voto

kreneskyp Points 31

Filtrage à travers un GenericForeignKey peut en créant un second modèle qui partage le db_table con Ticket . Tout d'abord, divisez Ticket en un modèle abstrait et un modèle concret.

class TicketBase(models.Model):
    issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
    issue_id = models.PositiveIntegerField(null=True, blank=True)

    class Meta:
        abstract = True

class Ticket(TicketBase):
    issue = generic.GenericForeignKey('issue_ct', 'issue_id')

Ensuite, créez un modèle qui sous-classe également TicketBase . Cette sous-classe aura tous les mêmes champs à l'exception de issue qui est plutôt défini comme un ForeignKey . Ajout d'un Manager permet de le filtrer sur une seule et unique ContentType .

Puisque cette sous-classe n'a pas besoin d'être synchronisée ou migrée, elle peut être créée de façon dynamique en utilisant la fonction type() .

def subclass_for_content_type(content_type):
    class Meta:
        db_table = Ticket._meta.db_table

    class Manager(models.Manager):
        """ constrain queries to a single content type """
        def get_query_set(self):
            return super(Manager, self).get_query_set().filter(issue_ct=content_type)

    attrs = {
        'related_to': models.ForeignKey(content_type.model_class()),
        '__module__': 'myapp.models',
        'Meta': Meta,
        'objects': Manager()
    }
   return type("Ticket_%s" % content_type.name, (TicketBase,), attrs)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X