2009-11-07 7 views
1

Tenez compte des modèles:Django: objets de commande par les attributs de leurs enfants

class Author(models.Model): 
    name = models.CharField(max_length=200, unique=True) 

class Book(models.Model): 
    pub_date = models.DateTimeField() 
    author = models.ForeignKey(Author) 

Supposons maintenant que je veux commander tous les livres, disons, leur pub_date. J'utiliserais order_by('pub_date'). Mais que se passe-t-il si je veux une liste de tous les auteurs classés en fonction de qui a publié le plus récemment des livres?

C'est vraiment très simple quand on y pense. Il est essentiellement:

  • L'auteur sur le dessus est celui qui a récemment publié un livre
  • Le prochain est celui qui a publié des livres pas nouveaux comme la première,
  • Donc, etc.

Je pourrais probablement pirater quelque chose ensemble, mais comme cela pourrait grossir, j'ai besoin de savoir que je le fais bien.

Aide appréciée! Enfin, serait-il possible d'ajouter un nouveau champ à chacun pour montrer la date du dernier livre et juste mettre à jour que tout le temps soit meilleur?

Répondre

1

Enfin, serait la possibilité de simplement ajouter un. nouveau champ à chacun pour montrer la date du dernier livre et juste mettre à jour que tout le temps soit meilleur?

En fait, il le ferait! Ceci est une pratique de dénormalisation normale et peut se faire comme ceci:

class Author(models.Model): 
    name = models.CharField(max_length=200, unique=True) 
    latest_pub_date = models.DateTimeField(null=True, blank=True) 

    def update_pub_date(self): 
     try: 
      self.latest_pub_date = self.book_set.order_by('-pub_date')[0] 
      self.save() 
     except IndexError: 
      pass # no books yet! 

class Book(models.Model): 
    pub_date = models.DateTimeField() 
    author = models.ForeignKey(Author) 

    def save(self, **kwargs): 
     super(Book, self).save(**kwargs) 
     self.author.update_pub_date() 

    def delete(self): 
     super(Book, self).delete() 
     self.author.update_pub_date() 

Ceci est la troisième option commune, vous avez d'ailleurs deux déjà suggéré:

  • faire dans SQL avec une jointure et le regroupement
  • obtenir tous les livres à côté Python et supprimer les doublons

ces deux options choisissent de calculer pub_dates à partir de données normalisées au moment où vous lisez t ourlet. La dénormalisation effectue ce calcul pour chaque auteur au moment où vous écrivez de nouvelles données. L'idée est que la plupart des applications web lisent le plus souvent que les écritures, donc cette approche est préférable.L'un des inconvénients perçus de ceci est que, fondamentalement, vous avez les mêmes données dans des endroits différents et cela nécessite que vous restiez synchronisé. Il horrifie les gens de la base de données à la mort habituellement :-). Mais ce n'est généralement pas un problème jusqu'à ce que vous utilisiez votre modèle ORM pour travailler avec dat (ce que vous faites probablement de toute façon). Dans Django, c'est l'application qui contrôle la base de données, et non l'inverse. Un autre inconvénient (plus réaliste) est qu'avec le code naïf que j'ai montré, la mise à jour massive de livres peut être beaucoup plus lente puisqu'ils demandent à des auteurs de mettre à jour leurs données à chaque mise à jour, quoi qu'il arrive. Ceci est généralement résolu en ayant un drapeau pour désactiver temporairement l'appel update_pub_date et en l'appelant manuellement après. Fondamentalement, les données dénormalisées nécessitent plus de maintenance que normalisées.

0
def remove_duplicates(seq): 
    seen = {} 
    result = [] 
    for item in seq: 
     if item in seen: continue 
     seen[item] = 1 
     result.append(item) 
    return result 


# Get the authors of the most recent books 
query_result = Books.objects.order_by('pub_date').values('author') 
# Strip the keys from the result set and remove duplicate authors 
recent_authors = remove_duplicates(query_result.values()) 
+0

Hey, ressemble à ce dont j'ai besoin. Juste si je peux demander: 1. Comment ça marche et 2. Comment est-ce en termes de performance? – cwj

+0

Hmm réellement désolé je ne hink pas cela va fonctionner, l'appel .distinct() va interagir avec la liste, accrocher je le répare pour vous. – DevDevDev

+0

Il ya peut-être un meilleur moyen, je pourrais le faire en SQL, donc je sais que vous pouvez le faire en utilisant des modèles Django, mais je ne peux pas penser à quel point de ma tête. – DevDevDev

1

Ou, vous pouvez jouer avec quelque chose comme ceci:

Author.objects.filter(book__pub_date__isnull=False).order_by('-book__pub_date')

+0

Ne fonctionne pas, il peut y avoir beaucoup de livres avec le même auteur. –

+0

Je suis désolé, mais pourquoi cela ne fonctionne-t-il pas, que l'auteur n'ait pas de livres, un livre ou plus? – ayaz

1
from django.db.models import Max 
Author.objects.annotate(max_pub_date=Max('books__pub_date')).order_by('-max_pub_date') 

cela exige que vous utilisez django 1.1

et je suppose que vous allez ajouter un « related_name 'à votre champ d'auteur dans le modèle de livre, donc il sera appelé par Author.books au lieu de Author.book_set. c'est beaucoup plus lisible.

0

Miser sur la solution de Ayaz, qu'en est: Author.objects.filter (book__pub_date__isnull = False) .distinct() order_by ('- de book__pub_date')

Questions connexes