1

J'ai une structure de base de données qui peut être simplifiée comme suit (version et AdditionalInfo non présentées depuis ne sont pas directement liés à ma question):Django: récupérer seul objet de la relation ManyToMany efficace

class Image(models.Model):  
    file = models.ImageField(blank=False, null=False, upload_to='images') 
    version = models.ForeignKey(Version, blank=False, null=False) 

class ExampleImage(models.Model): 
    example = models.ForeignKey('Example', blank=False, null=False) 
    image = models.ForeignKey(Image, blank=False, null=False) 
    additional_info = models.ForeignKey(AdditionalInfo, blank=True, null=True) 

class Example(models.Model): 
    name = models.Charfield(max_length=255) 
    images = models.ManyToManyField(Image, through=ExampleImage, through_fields=('example', 'image') 

    def get_default_image(self): 
     try: 
      image = self.images.get(version=Version.objects.get(name=DEFAULT_VERSION)) 
     except Image.DoesNotExist: 
      # Get some other image, this happens maybe 1 out of 10 

Maintenant, j'ai un très besoin commun d'interroger tous les objets Example avec leur image par défaut. À l'heure actuelle, je fais un peu comme suit:

example_list = [] 
examples = Example.objects.all() 

for example in examples: 
    example_dict = dict(name=example.name, image=example.get_default_image()) 
    example_list.append(example_dict) 

# Then show example_list in template 

Cette approche fonctionne mais il provoque des milliers de requêtes de base de données et prend plus d'une minute pour effectuer ce qui est peu trop de temps pour télécharger une page web! Donc, ma question est, quelle est la bonne approche pour optimiser ce type de cas d'utilisation. Je pourrais définir le champ default_image pour le modèle Example (et utiliser select_related alors, mais cela rend l'ajout d'images plus compliqué), coder en dur la méthode get_default_image (pas si flexible), faire la méthode get_default_image en cached_property etc. mais je suis pas exactement sûr quelle approche devrait être utilisée. J'ai déjà essayé de nombreux trucs mais rien ne semble aider ma situation. Je devrais trouver une solution qui me permet d'interroger à la fois l'exemple et l'image par défaut avec une grande requête, plutôt que de faire le travail dans la boucle.

Répondre

1

Est-ce que DEFAULT_VERSION est une constante de module? Si c'est le cas, vous pouvez factoriser l'appel version=Version.objects.get(name=DEFAULT_VERSION) de sorte que vous ne devez pas l'exécuter pour chaque example in examples. Vous devez également être en mesure de travailler avec un objet personnalisé Prefetch: https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.Prefetch. Quelque chose le long des lignes de ce (bien que vous devrez tester et vous tweak):

qs = Image.filter(version=Version.objects.get(name=DEFAULT_VERSION)) 
pref = Prefetch('images', queryset=qs, to_attr='image') 
examples = Example.objects.prefetch_related(pref) 

Idéalement bien sûr, vous aurait mis Example.default_image à un OneToOneField vous pouvez donc récupérer les images par défaut efficacement.

Le décorateur cached_property ne vous aidera pas vraiment, car il ne met en cache la propriété que si l'instance existe. Il aide aide si vous devez appeler instance.get_default_image() plusieurs fois, mais vous devrez toujours exécuter get_default_image une fois pour chaque instance que vous récupérez.

Espérons que ça aide

+0

Oui, DEFAULT_VERSION est une constante de module. D'après ce que je comprends, Django devrait utiliser la base de données uniquement pour le premier exemple et utiliser le résultat mis en cache pour le reste des exemples? Je vais vérifier Prefetch personnalisé plus tard aujourd'hui, merci pour votre aide! – m5seppal

+0

Django est intelligent, mais pas * ça * intelligent. Vous appelez 'get_default_image' sur chaque modèle, et cette méthode récupère un objet de la base de données chaque fois qu'elle est exécutée. Il y a une mise en cache du jeu de queues, mais seulement pour le même ensemble de queueset ** ** d'un QS, par ex. Après avoir parcouru 'examples ', appeler' examples [0] 'ne retournera plus dans la base de données tant que vous serez dans la même portée. 'Model.objects.get()' essayera toujours de récupérer un objet de la base de données. – Geotob

+0

Votre méthode fonctionne en effet, je suis maintenant capable de le faire en 4 requêtes! Mais il y a quelques problèmes. Premièrement, 'image' est une liste, j'ai donc besoin d'y accéder 'image.0.file.url' dans un template. Un autre problème est que l'image par défaut n'existe pas pour chaque objet, dans ce cas, une image de remplacement doit être récupérée (voir mon implémentation get_default_image). Mon approche actuelle ne le fait pas. – m5seppal