.. -*- mode: rst -*- ==================================== Nouveautés De Django 1.1 (rev 11365) ==================================== .. include:: .. include:: Django Party Dakar - 08 août 2009 Agence Universitaire de la Francophonie :Auteurs: Ousmane Wilane :Version: $Id: whats-new-djang-1.1.rst 17 2009-08-08 12:41:54Z wilane $ .. class:: left Fonctionnalités veillit ======================= L'utilisation de ``AdminSite.root()`` pour exposer les vues de l'admin est veillit On ne fait plus:: (r'^admin/(.*)', admin.site.root), On fait plutôt:: (r'^admin/', include(admin.site.urls)), Nouveautés de Django 1.1 ======================== - 1.290 commits depuis 1.0 - 1.206 bogues corrigés - Environ 10.000 lignes de documentation ORM === Deux innovations majeures. Aggrégation =========== Il est possible maintenant d'exécuter les requêtes d'aggrégation SQL (``COUNT()``, ``MAX()``, ``MIN()``, etc.). On peut recupérer les résultats des aggrégations directement ou annoter les objets. ``QuerySet.aggregate()``:: >>> from django.db.models import Avg >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35} ``all()`` est redondant dans cet exemple:: >>> Book.objects.aggregate(Avg('price')) {'price__avg': 34.35} Plusieurs Aggrégation ===================== Pour générer d'autres agrégats, il suffit de les passer en argument à ``aggregate()``:: >>> from django.db.models import Avg, Max, Min, Count >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')} Annotation ========== Lorsqu'on annote une ``QuerySet`` chaque objet sera annoté avec les valeurs spécifiées. Dans un modèle Book lié à authors par une liaison m2m:: >>> q = Book.objects.annotate(Count('authors')) >>> q[0] >>> q[0].authors__count 2 # Interrogate the second object in the queryset >>> q[1] >>> q[1].authors__count 1 Annotation nommée ================= Pour utiliser un nom différent du nom auto-génér ci-dessus:: >>> q = Book.objects.annotate(num_authors= Count('authors')) >>> q[0].num_authors 2 >>> q[1].num_authors 1 Concrairement à ``aggregate()``, ``annotate()`` *n*'est pas une clause terminale, il génère une ``QuerySet()`` qui peut être modifié par toutes les opérations des ``QuerySet()`` Jointures et aggrégation ======================== Si les champs d'aggrégation sont des champs de modèles liés, on peut encore utiliser la notation ``__``:: >>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price')) Ceci: - Récupère le modèle Store - Effectue la jointure à travers le liens m2m - Aggrége le champs prix pour avoir le min et le max Jointures et aggrégation (2) ============================ ``aggregate()`` marche pareil:: >>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price')) Jointures et aggrégation (exemple) ================================== L'age du plus jeune auteur de tous les bouquins vendus dans la boutique:: >>> Store.objects.aggregate(youngest_age= Min('books__authors__age')) Aggrégation et les autres clauses ================================= Le prix moyen des bouquins dont le titre commence par Django:: >>> Book.objects.filter(name__startswith="Django"). annotate(num_authors=Count('authors')) Les filtres contraignent les objets sur lesquels les aggrégations sont effectuées:: >>> Book.objects.filter(name__startswith="Django"). aggregate(Avg('price')) Filtres sur des objets annotés ============================== Les alias des annotations peuvent être utilisés pour dans les clauses ``filter()`` et ``exclude()`` Pour avoir la liste des bouquins écrit par plus d'un auteur:: >>> Book.objects.annotate(num_authors=Count('authors')). filter(num_authors__gt=1) Cette requête génère une ``QuerySet`` annotée sur lesquels il applique le filtre en utilisant l'alias de l'annotation. Ordre de ``annotate()`` et ``filter()`` ======================================= La clause ``annotate()`` est appliquée à une ``QuerySet()`` en l'état au moment où elle est annotée. Les deux opération ne sont pas commutatives:: >>> Publisher.objects.annotate(num_books=Count('book')). filter(book__rating__gt=3.0) est différent de :: >>> Publisher.objects.filter(book__rating__gt=3.0). annotate(num_books=Count('book')) ``order_by()`` ============== :: >>> Book.objects.annotate(num_authors=Count('authors')). order_by('num_authors') ``values()`` ============ Avec cette opération l'annotation est un peu différente. Considérons la requête qui recherche la notation moyenne d'un bouqin:: >>> Author.objects.annotate(average_rating=Avg('book__rating')) Cette requête va renvoyer un enregistrement pour chaque auteur de la base de donnée. Les resultats vont être un peu différents si on utilise ``values()``:: >>> Author.objects.values('name').annotate (average_rating=Avg('book__rating')) ``values()`` (2) ================ Cette requête va renvoyer un enregistrement pour chaque auteur de la base de donnée. Les resultats vont être un peu différents si on utilise ``values()``:: >>> Author.objects.values('name').annotate (average_rating=Avg('book__rating')) Dans ce cas les auteurs vont être regroupés par nom, autrement dis, si vous avez deux auteurs avec le même nom, leurs résultats vont être regroupés dans le même enregitrement. Ordre de ``values()`` et ``annotate()`` ======================================= L'inversion de deux clauses dans le requête précédante:: >>> Author.objects.annotate(average_rating=Avg('book__rating')). values('name', 'average_rating') Nous donne un résultat unique pour chaque auteur, mais uniquement le nom de l'auteur et sa note moyenne seront retournés. Vous noterez qu'il est obligatoire d'exopliciter la moyenne dans les champs renvoyés par ``values()`` dans ce cas. Interaction des ordres par défaut d'avec ``order_by()`` ======================================================= CF: docs Aggregating annotations ======================= Vous pouvez générer une aggrégation de résultats d'annotation:: >>> Book.objects.annotate(num_authors=Count('authors')). aggregate(Avg('num_authors')) {'num_authors__avg': 1.66} Donne le nombre moyen d'auteurs par bouquin. Expressions dans les requêtes ============================= Les Requêtes peuvent faire réfèrence à d'autres champs et même traverser les relations pour faire réfèrence à des champs sur les modèles liés :: >>> from django.db.models import F >>> Entry.objects.filter(n_pingbacks__lt=F('n_comments')) Tous les posts du blog ayant plus de commentaires que de pingbacks Opérations ========== Django supporte les opérations d'addition, de soustraction, de multiplication, de division et de modulo avec ``F()``:: >>> Entry.objects.filter(n_pingbacks__lt=F('n_comments') * 2) ou:: >>> Entry.objects.filter(rating__lt= F('n_comments') + F('n_pingbacks')) Mise à jour d'attributs ======================= Opérations simple sur un champ:: >>> product = Product.objects.get (name='Venezuelan Beaver Cheese') >>> product.number_sold += 1 >>> product.save() Mise à jour d'attributs (2) =========================== On peut faire:: >>> from django.db.models import F >>> product = Product.objects.get (name='Venezuelan Beaver Cheese') >>> product.number_sold = F('number_sold') + 1 >>> product.save() Dans ce cas Django demande à la base de faire la mise à jour en fonction de la valeur actuelle du champs. Améliorations des modèles ========================= Modèles non gérés ================= Si vous ne souhaitez pas que ``syncdb`` et autres gèrent vos schéma vous pouvez utiliser l'option ``managed``:: class MonModel(models.Model): question = models.ForeignKey(Question) # ... class Meta: managed = False Modèle Proxy ============ Si vous voulez utilisez un modèle existant à travers un autre modèle (Proxy):: from django.contrib.auth.models import User class MonUser(User): class Meta: proxy = True def yada(self): ... Dans ce cas ``MonUser`` travail sur les données de ``User``, ainsi toutes les nouvelles instances de ``User`` seront accessibles à travers ``MonUser`` et vis-versa. Champs reportés =============== Si vous désirez exécuter une requête contenant des champs complexes à traduire en type Python, vous pouvez demander que ces champs soient *reportés*:: Entry.objects.defer("body").filter(headline="Lennon").defer("lede") Blog.objects.select_related().defer("entry__lede", "entry__body") Si vous essayez d'accèder aux champs *reportés* ils seront alors construit à la vollée un à la fois pas pour tous les enregistrements. ``only()`` est le complémentaire de ``defer()``. Améliorations de l'admin ======================== Champs éditable dans les listings ================================= Vous pouvez spécifier des champs modifiable directement dans les vues de listing de l'admin:: class UserAdmin(admin.ModelAdmin): list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') list_display = ('first_name', 'last_name', 'is_staff') list_filter = ('is_staff', 'is_superuser') Actions admin ============= Exemple:: class ArticleAdmin(admin.ModelAdmin): list_display = ['title', 'status'] ordering = ['title'] actions = ['make_published'] def make_published(self, request, queryset): queryset.update(status='p') make_published.short_description = u"Marquer comme publiées" admin.site.register(Article, ArticleAdmin) GeoDjango ========= Améliorations ============= - Supporte SpatiaLite - Aggrégation géographiques (``Collect``, ``Extent``, ``MakeLine``, ``Union``) et ``F()`` - Nouvelles méthodes ``GeoQuerySet``: ``collect``, ``geojson``, et ``snap_to_grid``. Autres domaines =============== Améliorations ============= - Améliorations dans les tests - Améliorations du client test - Traitement conditionnel de vues - Espace de noms des urls Autres détails ============== Améliorations ============= - ``reverse()`` et ``{% url %}`` marchent sur l'admin - ``include()`` accepte une séquence de motifs (générées par ``patterns()``) - Vue générique ``redirect_to()`` accepte le mot-argument ``permanent`` (301) qui lorsqu'elle est ``False`` revoie 302 (temporaire). - ``{% for %}`` accepte un nouveau tag ``{% empty %}`` - Un raccourcis ``redirect()`` - etc (lire la doc)