2011-02-17 3 views
3

models.pyDjango meilleures pratiques avec les requêtes clés étrangères

class Category(models.Model): 
    name = models.CharField(max_length=50) 

class SubCatergory(models.Model): 
    parent_category = models.ForeignKey(Category) 
    name = models.CharField(max_length=100) 

views.py

def all_products(request): 
c = Category.objects.all() 
s = SubCatergory.objects.all() 

return render_to_response('all_products.html', 
          {'c':c, 's':s}) 

all_products.html

{% for category in c %} 
    <h1>{{ category.name }}</h1> 
    <ul> 
     {% for sub in s %} 
     {% if category.id == sub.parent_category.id %} 
      <li>{{ sub.name }}</li> 
     {% endif %} 
     {% endfor %} 
    </ul> 
{% endfor %} 

me demandais si ce qui précède est la meilleure pratique pour la clé étrangère requêtes. Je filtre au niveau du template (si category.id == sub ...), devrais-je le déplacer au niveau du modèle ou de la vue à la place?

Répondre

5

S'il n'y a que jamais une profondeur de sous-catégorie, le code suivant ne devrait pas être un problème:

{% for category in c %} 
    <h1>{{ category.name }}</h1> 
    <ul> 
     {% for sub in category.subcatergory_set.all %} 
      <li>{{ sub.name }}</li> 
     {% endfor %} 
    </ul> 
{% endfor %} 

Mais il y a quelques astuces d'optimisation pour réduire le nombre de requêtes que vous feriez une requête par boucle . J'essaie d'y penser maintenant.

En fait, je commence à penser que c'est une question intéressante: les meilleures pratiques?

Votre méthode utilise 2 requêtes. Ma méthode utilise des pratiques django, mais fait plusieurs requêtes.

Pour éviter que plusieurs requêtes, vous auriez essentiellement à faire la même chose que vous avez fait dans votre modèle à votre avis, à savoir itérer sur SubCatergory, tirant leurs papiers d'identité en python et en regroupant chaque ID Posée sur un attribut sur Category .

Je ne connais pas la réponse à celle-ci.

+0

Merci, il n'y a qu'une seule profondeur de sous-catégorie. –

0

Pourquoi ne pas ajouter une référence dans l'autre sens, de sorte que chaque catégorie fasse référence à une liste de sous-catégories? Ensuite, vous serez capable d'écrire deux boucles imbriquées: une boucle externe pour les catégories et une boucle interne pour l'itération sur les sous-catégories de chaque catégorie.

+2

c'est la bonne idée, mais notez que Django le fait automatiquement pour vous. Il crée automatiquement une «relation inverse» pour chaque clé étrangère pointant sur un modèle, qui est (si vous ne le changez pas explicitement) le nom du modèle associé plus «_set». Voir le lien suivant pour plus d'informations: http://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects – vitorbal

+0

Il est vrai que django vous donne une API pour interroger le verso relation. Cependant, l'utilisation de la relation inverse pour récupérer les modèles associés entraînera toujours une requête SQL supplémentaire qui peut être un énorme succès de performance si elle est effectuée plusieurs fois (c'est-à-dire dans une boucle). Pour éviter cela, on peut vouloir dénormaliser la relation de sorte que dans le cas ci-dessus, on enregistre des références aux sous-catégories sur chaque catégorie. – heyman

2

Je pense qu'une bonne pratique ici serait de créer une étiquette de modèle pour ce travail. De cette façon, vous pouvez mettre en cache le modèle rendu et seulement frapper la base de données au premier rendu.

d'abord, créez la balise de modèle dans votre application

templatetags/show_categories_list.py

from django.core.cache import cache 

@register.simple_tag 
def show_categories_list(): 
    cached = cache.get('CATEGORIES_LIST_CACHE_KEY', None) 
    if cached is None: 
     categories = Category.objects.all() 
     rendered = render_to_string('all_categories.html', {'categories': categories}) 
     cache.set('CATEGORIES_LIST_CACHE_KEY', rendered) 
     return rendered 
    return cached 

Ensuite, créez le modèle à utiliser

all_categories.html

{% for category in categories %} 
    <h1>{{ category.name }}</h1> 
    <ul> 
     {% for sub in category.subcategory_set.all %} 
      <li>{{ sub.name }}</li> 
     {% endfor %} 
    </ul> 
{% endfor %} 

Remplacez la méthode de sauvegarde dans vos modèles afin de supprimer catégories e liste entrée de cache (le forçant à rendre à la prochaine demande, cela pourrait aussi être placé à la (pré | post) signal _save):

models.py

class Category(models.Model): 
    name = models.CharField(max_length=50) 

    def save(self, *args, **kwargs): 
     cache.delete('CATEGORIES_LIST_CACHE_KEY') 
     return super(Category, self).save(*args, **kwargs) 

class SubCatergory(models.Model): 
    parent_category = models.ForeignKey(Category) 
    name = models.CharField(max_length=100) 

    def save(self, *args, **kwargs): 
     cache.delete('CATEGORIES_LIST_CACHE_KEY') 
     return super(Category, self).save(*args, **kwargs) 

Et enfin l'utiliser comme ceci:

base.html

{% load show_categories_list %} 
{% show_categories_list %} 

Vous pouvez également ajouter un délai d'attente à l'entrée du cache, de sorte que vous ne devez pas remplacer la méthode de sauvegarde dans vos modèles, mais vous devrez attendre le délai d'attente pour qu'il puisse être rendu à nouveau.

Quelques références utiles:

http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/#shortcut-for-simple-tags http://docs.djangoproject.com/en/1.2/topics/cache/#the-low-level-cache-api http://docs.djangoproject.com/en/1.2/topics/signals/

Questions connexes