Liste factice

  1. LEGRAND Simon - simon@mail.com
  2. THOMAS Robert - robert@mail.com
  3. PETIT Catherine - catherine@mail.com
  4. BONNET Georges - georges@mail.com
  5. THOMAS Alfred - alfred@mail.com

Alfred et Robert ne se connaissent pas

Solution de fortune

User.objects.filter(...).order_by('last_name', 'first_name') Soyons honnête cette requête suffit dans la majorité des cas. Mais que fait elle ? Elle trie par ordre alphabétique par nom puis prénom. Donc appliquée sur la liste ci-dessus cela nous donne :

  1. BONNET Georges - georges@mail.com
  2. LEGRAND Simon - simon@mail.com
  3. PETIT Catherine - catherine@mail.com
  4. THOMAS Alfred - alfred@mail.com
  5. THOMAS Robert - robert@mail.com

Maintenant imaginons le pire. Mr LEGRAND Simon et Mme Catherine ne renseignent plus leur nom de famille. On obtient ainsi :

  1. Simon - simon@mail.com
  2. Catherine - catherine@mail.com
  3. BONNET Georges - georges@mail.com
  4. THOMAS Alfred - alfred@mail.com
  5. THOMAS Robert - robert@mail.com

Oui mais moi je veux voir Simon et Catherine à leur place c'est à dire en se basant sur le "S" de Simon et le "C" de Catherine.

Trois solutions pour trier la liste

Trois solutions, un seule retenue. Il n'y a pas de meilleure, tout dépend de votre besoin.

  1. Utiliser Raw Sql pour fusionner la colonne "last_name" et "first_name" et effectuer un tri sur cette fusion
  2. Créer un nouveau champs dans la DB, celui ci étant la fusion de "last_name" et de "first_name"
  3. Dénormaliser la requête et utiliser une méthode métier
  • Première solution : Performance moyenne et obligation d'écrire en SQL. Requête simple si cela provient directement du models "User", beaucoup plus compliqué si c'est une liste d'utilisateur extraite d'un projet. (project.get_users) .. avec potentiellement une multitude de filtres.
  • Deuxième solution : Performance haute, obligation de créer un nouvel attribut dans User Profile, surcharger la méthode save(). Si votre base est en place, vous devez générer cette colonne. Oblige également à passer par get_profile(). Le plus pratique serait de directement modifier la table "User".
  • Troisième solution: Performance moyenne, vous devez ajouter une méthode au models User (User redéfini par Meta Class) et écrire la dénormalisation.

J'ai choisi la troisième solution, l'historique du projet facilitait le choix. En effet User était déjà redéfini par MetaClass donc finalement une méthode, une dénormalisation et c'était dans la poche.

La méthode métier sur User

Nommez-la comme vous voulez.

def get_full_name_inverse(self):
        return (("%s %s") % (self.last_name.capitalize(), self.first_name.capitalize())).strip()
  • Cette méthode fusionne le nom et le prénom. Elle supprime les espace en début et fin indésirable. Très important pour le tri !
  • Cette méthode ajoute une belle majuscule au nom et au prénom. L'intérêt est uniquement visuel

La dénormalisation

 users = User.objects.filter(...) 
 users = sorted(users, key=lambda a: a.get_full_name_inverse())

Et voici la version qui permet de gérer les caractères accentués. Merci pour Fabrice pour l'astuce.

import locale
users = User.objects.filter(...) 
locale.setlocale(locale.LC_ALL, "fr_FR.UTF-8")
users = sorted(users, cmp=locale.strcoll, key=lambda a: a.get_full_name_inverse())

On obtient ainsi la liste suivante :

  1. BONNET Georges - georges@mail.com
  2. Catherine - catherine@mail.com
  3. Simon - simon@mail.com
  4. THOMAS Alfred - alfred@mail.com
  5. THOMAS Robert - robert@mail.com