Django 1.1
a tour of (some) new features



                                      Idan Gazit
                       PyWeb-IL, August 31st 2009
bit.ly/django11
 release notes are your friend
omg i haz to rewrite mai code?
No!
   code you wrote
    for 1.0 should
      “Just Work”

http://bit.ly/djangoapi
With a few exceptions…
With a few exceptions…
constraint names on 64-bit platforms
With a few exceptions…
constraint names on 64-bit platforms

                transactions in tests
With a few exceptions…
   constraint names on 64-bit platforms

                    transactions in tests

SetRemoteAddrFromForwardedFor   middleware
With a few exceptions…
   constraint names on 64-bit platforms

                    transactions in tests

SetRemoteAddrFromForwardedFor   middleware

                saving of model formsets
With a few exceptions…
   constraint names on 64-bit platforms

                    transactions in tests

SetRemoteAddrFromForwardedFor   middleware

                saving of model formsets

                 names of uploaded files
Also…
        changed admin autodiscovery

               if your urlconf contains:
    (r'^admin/(.*)', admin.site.root),


                       change it to be:
(r'^admin/', include(admin.site.urls)),
bit.ly/django11
 release notes are your friend
1.1 new features




1290 tasty commits, 1200 bugs squashed
1.1 new features




10k new lines of documentation
That’s a lot of new code.
     (at least there’s a lot of new docs)
New Features!
 not an exhaustive list.
• Aggregates
• F() Expressions
• Model improvements
• Admin improvements
• Comment Moderation
OM
                GW
• Aggregates      TF
                     BB
• F() Expressions       Q!
                           !
• Model improvements
• Admin improvements
• Comment Moderation
Aggregates
What are aggregates?


• aggregate()
 summary for entire query
• annotate()
 summary per row returned
Aggregate Examples
                Person                   Purchases
    first_name       CharField   thing          CharField
    last_name       CharField   price          DecimalField
    age             IntField    person         FK




What is the average age for all people?
>>> Person.objects.aggregate(Avg(‘age’))
{‘age__avg’: 23.65}
Aggregate Examples
                Person                   Purchases
    first_name       CharField   thing          CharField
    last_name       CharField   price          DecimalField
    age             IntField    person         FK




What is the average age for all people?
>>> Person.objects.aggregate(Avg(‘age’), Min(‘age’),
Max(‘age’))
{‘age__avg’: 23.65, ‘age__min’: 14, ‘age__max’:87}
Aggregate Examples
                Person                   Purchases
    first_name       CharField   thing          CharField
    last_name       CharField   price          DecimalField
    age             IntField    person         FK




What is the average age for all people?
>>> Person.objects.aggregate(Avg(‘age’), Min(‘age’),
Max(‘age’))
{‘age__avg’: 23.65, ‘age__min’: 14, ‘age__max’:87}



                         No Chaining!
Aggregation functions
• Avg(field): average of field
• Min(field): min of field
• Max(field): max of field
• Sum(field): sum of all values in the field
• Count(field, distinct): num of related objects via field
• StdDev(field, sample)
• Variance(field, sample)
Annotation
                   Person                   Purchases
       first_name       CharField   thing          CharField
       last_name       CharField   price          DecimalField
       age             IntField    person         FK




People ordered by number of purchases made
q = Person.objects.annotate(Count(‘purchases’)).order_by(
‘-purchases__count’)

>>> q[0]
<Person: Material Girl>
>>> q[0].purchases__count
4592810191
Annotation
                   Person                   Purchases
       first_name       CharField   thing          CharField
       last_name       CharField   price          DecimalField
       age             IntField    person         FK




People ordered by number of purchases made
q = Person.objects.annotate(num_purchases=Count(‘purchases’))
.order_by(‘-num_purchases’)

>>> q[0]
<Person: Material Girl>
>>> q[0].num_purchases
4592810191
Annotation
                   Person                   Purchases
       first_name       CharField   thing          CharField
       last_name       CharField   price          DecimalField
       age             IntField    person         FK




Which people have <=10 purchases?
q = Person.objects.annotate(
 num_purchases=Count(‘purchases’)).filter(num_purchases__lte=10)
Annotation
                Person                   Purchases
    first_name       CharField   thing          CharField
    last_name       CharField   price          DecimalField
    age             IntField    person         FK




Annotations work across JOINs:
What is the average purchase price for
purchases made by each person?
Person.objects.annotate(min_price=Min(‘purchases__price’))
Dizzy Yet?
many possibilities
Annotation



Order is important!
foo.filter().annotate() != foo.annotate().filter()
F( ) expressions
F( ) Expressions
class Stock(models.Model):
    symbol = models.CharField()

class TradingDay(models.Model):
    stock = models.ForeignKey(Stock, related_name="days")
    opening = models.DecimalField()
    closing = models.DecimalField()
    high = models.DecimalField()
    low = models.DecimalField()
F( ) Expressions
class Stock(models.Model):
    symbol = models.CharField()

class TradingDay(models.Model):
    stock = models.ForeignKey(Stock, related_name="days")
    opening = models.DecimalField()
    closing = models.DecimalField()
    high = models.DecimalField()
    low = models.DecimalField()

from django.db.models import F
# Closed at least 50% up from open
TradingDay.objects.filter(closing__gte=F('opening') * 1.5)
# All downhill
TradingDay.objects.filter(opening=F('high'))
# Works across JOINs
# Stocks that have days with <10pt loss in value
Stock.objects.filter(days__closing__gte=F('days__opening')-10.0)
F( ) Expressions
Atomic DB Increment Operations!
class MyModel():
    ...
    counter = models.IntegerField()

MyModel.objects.filter(id=someid).update(counter=F('counter')+1)
Model Improvements
Unmanaged Models

• Useful for tables or DB views which
  aren’t under Django’s control
• Work like regular models
• No table creation during syncdb,
  tests, etc.
Unmanaged Models
class MyModel():
    ...
    # some fields which match your existing table's column types

   class Meta:
       managed = False
Proxy Models

I already have a model, and
want to change its python
behavior without changing
the underlying table
structure.
Proxy Models
class MyProxy(MyModel):
    # no new fields!
    # but you can define new managers
    objects = SupaDupaProxyManager()
    proxy_specific_manager = PonyManager()

   class Meta:
       proxy = True
       ordering = ['not_the_original_ordering_field',]

   def my_proxy_method(self):
       # ... do something
admin improvements
Admin Actions
Custom Admin Actions


def add_cowbell(modeladmin, request, queryset):
    queryset.update(myfield='cowbell!')
Custom Admin Actions


def add_cowbell(modeladmin, request, queryset):
    queryset.update(myfield='cowbell!')

add_cowbell.short_description = "More Cowbell!"
Custom Admin Actions


def add_cowbell(modeladmin, request, queryset):
    for obj in queryset:
        obj.myfield += 'cowbell!'
        obj.save()

add_cowbell.short_description = "More Cowbell!"
Custom Admin Actions


def add_cowbell(modeladmin, request, queryset):
    for obj in queryset:
        obj.myfield += 'cowbell!'
        obj.save()

add_cowbell.short_description = "More Cowbell!"

class MusicAdmin(admin.ModelAdmin):
    list_display = ['artist', 'song']
    ordering = ['artist',]
    actions = [add_cowbell]
list_editable
Generic Comment Moderation
django.contrib.comments.
        moderation
class BlogPost(models.Model):
    title = models.CharField()
    body = models.TextField()
    posted = models.DateTimeField()
    enable_comments = models.BooleanField()
    is_public = models.BooleanField()
django.contrib.comments.
        moderation
class BlogPost(models.Model):
    title = models.CharField()
    body = models.TextField()
    posted = models.DateTimeField()
    enable_comments = models.BooleanField()
    is_public = models.BooleanField()

class BlogPostModerator(moderation.Moderator):
    email_notification = True
    enable_field = 'enable_comments'
    auto_close_field = 'posted'
    close_after = 14

moderation.moderator.register(BlogPost, BlogPostModerator)
django.contrib.comments.
        moderation
class BlogPostModerator(moderation.Moderator):
    email_notification = True
    enable_field = 'enable_comments'
    auto_close_field = 'posted'
    close_after = 14

   def allow(comment, content_object, request):
       # return False to delete comment

   def moderate(comment, content_object, request):
       # return True to moderate comment

moderation.moderator.register(BlogPost, BlogPostModerator)
Random
URL Namespaces



(r'^myapp/', include('myapp.urls', namespace='foo', app_name='bar'))

reverse(‘bar:mynamedurl’, args=[‘xyzzy’], current_app=‘foo’)
For/Empty template tag


{% for foo in bars %}
    <p>{{ foo.name }}</p>
{% empty %}
    <p class="empty">No foos in bars!</p>
{% endfor %}
More Random!

• forms: hidden_fields() / visible_fields()
• auth using REMOTE_USER:
  IIS/mod_auth_sspi, mod_authnz_ldap, etc

• safeseq template filter
  like safe but for lists

• django.shortcuts.redirect() view:
  def my_view(request):
      ...
      return redirect('some-view-name', foo='bar')
Many More!
Fin.
   @idangazit
idan@pixane.com
Photo Credits
• http://www.flickr.com/photos/josstyk/248920216/
• http://www.flickr.com/photos/mar00ned/3274556235/
• http://www.flickr.com/photos/ilumb/361819506/
• http://www.flickr.com/photos/womanofscorn/9163061/
• http://www.flickr.com/photos/ginnerobot/2549674296/
• http://www.flickr.com/photos/squaregraph/24869936
• http://www.flickr.com/photos/aresauburnphotos/3381681226
• http://www.flickr.com/photos/lwr/105783846/
• http://www.flickr.com/photos/jurvetson/447302275
• http://www.flickr.com/photos/leecullivan/240389468
• http://www.flickr.com/photos/pulpolux/3698819113

Django 1.1 Tour

  • 1.
    Django 1.1 a tourof (some) new features Idan Gazit PyWeb-IL, August 31st 2009
  • 3.
  • 4.
    omg i hazto rewrite mai code?
  • 5.
    No! code you wrote for 1.0 should “Just Work” http://bit.ly/djangoapi
  • 6.
    With a fewexceptions…
  • 7.
    With a fewexceptions… constraint names on 64-bit platforms
  • 8.
    With a fewexceptions… constraint names on 64-bit platforms transactions in tests
  • 9.
    With a fewexceptions… constraint names on 64-bit platforms transactions in tests SetRemoteAddrFromForwardedFor middleware
  • 10.
    With a fewexceptions… constraint names on 64-bit platforms transactions in tests SetRemoteAddrFromForwardedFor middleware saving of model formsets
  • 11.
    With a fewexceptions… constraint names on 64-bit platforms transactions in tests SetRemoteAddrFromForwardedFor middleware saving of model formsets names of uploaded files
  • 12.
    Also… changed admin autodiscovery if your urlconf contains: (r'^admin/(.*)', admin.site.root), change it to be: (r'^admin/', include(admin.site.urls)),
  • 13.
  • 14.
    1.1 new features 1290tasty commits, 1200 bugs squashed
  • 15.
    1.1 new features 10knew lines of documentation
  • 16.
    That’s a lotof new code. (at least there’s a lot of new docs)
  • 17.
    New Features! notan exhaustive list.
  • 18.
    • Aggregates • F()Expressions • Model improvements • Admin improvements • Comment Moderation
  • 19.
    OM GW • Aggregates TF BB • F() Expressions Q! ! • Model improvements • Admin improvements • Comment Moderation
  • 20.
  • 21.
    What are aggregates? •aggregate() summary for entire query • annotate() summary per row returned
  • 22.
    Aggregate Examples Person Purchases first_name CharField thing CharField last_name CharField price DecimalField age IntField person FK What is the average age for all people? >>> Person.objects.aggregate(Avg(‘age’)) {‘age__avg’: 23.65}
  • 23.
    Aggregate Examples Person Purchases first_name CharField thing CharField last_name CharField price DecimalField age IntField person FK What is the average age for all people? >>> Person.objects.aggregate(Avg(‘age’), Min(‘age’), Max(‘age’)) {‘age__avg’: 23.65, ‘age__min’: 14, ‘age__max’:87}
  • 24.
    Aggregate Examples Person Purchases first_name CharField thing CharField last_name CharField price DecimalField age IntField person FK What is the average age for all people? >>> Person.objects.aggregate(Avg(‘age’), Min(‘age’), Max(‘age’)) {‘age__avg’: 23.65, ‘age__min’: 14, ‘age__max’:87} No Chaining!
  • 25.
    Aggregation functions • Avg(field):average of field • Min(field): min of field • Max(field): max of field • Sum(field): sum of all values in the field • Count(field, distinct): num of related objects via field • StdDev(field, sample) • Variance(field, sample)
  • 26.
    Annotation Person Purchases first_name CharField thing CharField last_name CharField price DecimalField age IntField person FK People ordered by number of purchases made q = Person.objects.annotate(Count(‘purchases’)).order_by( ‘-purchases__count’) >>> q[0] <Person: Material Girl> >>> q[0].purchases__count 4592810191
  • 27.
    Annotation Person Purchases first_name CharField thing CharField last_name CharField price DecimalField age IntField person FK People ordered by number of purchases made q = Person.objects.annotate(num_purchases=Count(‘purchases’)) .order_by(‘-num_purchases’) >>> q[0] <Person: Material Girl> >>> q[0].num_purchases 4592810191
  • 28.
    Annotation Person Purchases first_name CharField thing CharField last_name CharField price DecimalField age IntField person FK Which people have <=10 purchases? q = Person.objects.annotate( num_purchases=Count(‘purchases’)).filter(num_purchases__lte=10)
  • 29.
    Annotation Person Purchases first_name CharField thing CharField last_name CharField price DecimalField age IntField person FK Annotations work across JOINs: What is the average purchase price for purchases made by each person? Person.objects.annotate(min_price=Min(‘purchases__price’))
  • 30.
  • 31.
  • 32.
  • 33.
    F( ) Expressions classStock(models.Model): symbol = models.CharField() class TradingDay(models.Model): stock = models.ForeignKey(Stock, related_name="days") opening = models.DecimalField() closing = models.DecimalField() high = models.DecimalField() low = models.DecimalField()
  • 34.
    F( ) Expressions classStock(models.Model): symbol = models.CharField() class TradingDay(models.Model): stock = models.ForeignKey(Stock, related_name="days") opening = models.DecimalField() closing = models.DecimalField() high = models.DecimalField() low = models.DecimalField() from django.db.models import F # Closed at least 50% up from open TradingDay.objects.filter(closing__gte=F('opening') * 1.5) # All downhill TradingDay.objects.filter(opening=F('high')) # Works across JOINs # Stocks that have days with <10pt loss in value Stock.objects.filter(days__closing__gte=F('days__opening')-10.0)
  • 35.
    F( ) Expressions AtomicDB Increment Operations! class MyModel(): ... counter = models.IntegerField() MyModel.objects.filter(id=someid).update(counter=F('counter')+1)
  • 36.
  • 37.
    Unmanaged Models • Usefulfor tables or DB views which aren’t under Django’s control • Work like regular models • No table creation during syncdb, tests, etc.
  • 38.
    Unmanaged Models class MyModel(): ... # some fields which match your existing table's column types class Meta: managed = False
  • 39.
    Proxy Models I alreadyhave a model, and want to change its python behavior without changing the underlying table structure.
  • 40.
    Proxy Models class MyProxy(MyModel): # no new fields! # but you can define new managers objects = SupaDupaProxyManager() proxy_specific_manager = PonyManager() class Meta: proxy = True ordering = ['not_the_original_ordering_field',] def my_proxy_method(self): # ... do something
  • 41.
  • 42.
  • 43.
    Custom Admin Actions defadd_cowbell(modeladmin, request, queryset): queryset.update(myfield='cowbell!')
  • 44.
    Custom Admin Actions defadd_cowbell(modeladmin, request, queryset): queryset.update(myfield='cowbell!') add_cowbell.short_description = "More Cowbell!"
  • 45.
    Custom Admin Actions defadd_cowbell(modeladmin, request, queryset): for obj in queryset: obj.myfield += 'cowbell!' obj.save() add_cowbell.short_description = "More Cowbell!"
  • 46.
    Custom Admin Actions defadd_cowbell(modeladmin, request, queryset): for obj in queryset: obj.myfield += 'cowbell!' obj.save() add_cowbell.short_description = "More Cowbell!" class MusicAdmin(admin.ModelAdmin): list_display = ['artist', 'song'] ordering = ['artist',] actions = [add_cowbell]
  • 47.
  • 48.
  • 49.
    django.contrib.comments. moderation class BlogPost(models.Model): title = models.CharField() body = models.TextField() posted = models.DateTimeField() enable_comments = models.BooleanField() is_public = models.BooleanField()
  • 50.
    django.contrib.comments. moderation class BlogPost(models.Model): title = models.CharField() body = models.TextField() posted = models.DateTimeField() enable_comments = models.BooleanField() is_public = models.BooleanField() class BlogPostModerator(moderation.Moderator): email_notification = True enable_field = 'enable_comments' auto_close_field = 'posted' close_after = 14 moderation.moderator.register(BlogPost, BlogPostModerator)
  • 51.
    django.contrib.comments. moderation class BlogPostModerator(moderation.Moderator): email_notification = True enable_field = 'enable_comments' auto_close_field = 'posted' close_after = 14 def allow(comment, content_object, request): # return False to delete comment def moderate(comment, content_object, request): # return True to moderate comment moderation.moderator.register(BlogPost, BlogPostModerator)
  • 52.
  • 53.
    URL Namespaces (r'^myapp/', include('myapp.urls',namespace='foo', app_name='bar')) reverse(‘bar:mynamedurl’, args=[‘xyzzy’], current_app=‘foo’)
  • 54.
    For/Empty template tag {%for foo in bars %} <p>{{ foo.name }}</p> {% empty %} <p class="empty">No foos in bars!</p> {% endfor %}
  • 55.
    More Random! • forms:hidden_fields() / visible_fields() • auth using REMOTE_USER: IIS/mod_auth_sspi, mod_authnz_ldap, etc • safeseq template filter like safe but for lists • django.shortcuts.redirect() view: def my_view(request): ... return redirect('some-view-name', foo='bar')
  • 56.
  • 57.
    Fin. @idangazit idan@pixane.com
  • 58.
    Photo Credits • http://www.flickr.com/photos/josstyk/248920216/ •http://www.flickr.com/photos/mar00ned/3274556235/ • http://www.flickr.com/photos/ilumb/361819506/ • http://www.flickr.com/photos/womanofscorn/9163061/ • http://www.flickr.com/photos/ginnerobot/2549674296/ • http://www.flickr.com/photos/squaregraph/24869936 • http://www.flickr.com/photos/aresauburnphotos/3381681226 • http://www.flickr.com/photos/lwr/105783846/ • http://www.flickr.com/photos/jurvetson/447302275 • http://www.flickr.com/photos/leecullivan/240389468 • http://www.flickr.com/photos/pulpolux/3698819113

Editor's Notes

  • #8 constraint names: can&amp;#x2019;t ./manage.py reset on 64-bit platforms
  • #9 tests run inside transactions, subclass TransactionTestCase if you want to test transactional behavior
  • #10 middleware is gone (unreliable), too unreliable for general-purpose use
  • #11 BaseModelFormSet calls ModelForm.save(), problem if you were modifying self.initial in a model formset&amp;#x2019;s __init__
  • #12 names of uploaded files not available until after save()
  • #18 Lots of new features, many of them small improvements here-and there I&amp;#x2019;m only going to talk about five.
  • #19 Of these, the first two will change your life
  • #20 Of these, the first two will change your life
  • #21 Summaries on queries Anytime you said &amp;#x201C;we&amp;#x2019;d need to iterate over the results...&amp;#x201D;
  • #22 Easier to explain via showing
  • #23 Aggregate is like get() or values() -- it doesn&amp;#x2019;t return a queryset
  • #24 .aggregate() is like .get() -- it doesn&amp;#x2019;t return a queryset, so you can&amp;#x2019;t chain with it.
  • #27 can give names to annotations
  • #28 Filtering on annotations
  • #29 Perform JOIN&amp;#x2019;s using __ notation
  • #30 It takes a while to
  • #31 filter/annotate not commutative annotate works on a query as it is at the time of the annotation.
  • #32 allow you to reference model fields from inside a filter(), including doing arithmetic
  • #33 Perform joins using the __ notation
  • #34 Perform joins using the __ notation
  • #42 Bulk actions, django comes with &amp;#x201C;delete&amp;#x201D; Really easy to write your own
  • #44 Queryset.Update: speed, but model.save(), pre/post-save signals are bypassed.