What's new in Django 1.8

PyWeb-IL 44

Meir Kriehli / @mkriheli

Well, At least some of it

The task is getting harder and harder, humongous change list.

So we'll discuss some notable changes.

LTS

  • At least 3 years support for security and data loss related bugs.
  • Previous LTS version, 1.4, will get updates 6 months after 1.8's release.

Crowd funding

2 new features were crowd funded:

Model._meta API

Formalized

Model._meta in the old days

Not official nor stable


for field in self._meta.fields:
    if f.name == 'foo':
        pass
              

Django 1.8


f = self._meta.get_field('foo')
all_fields_list = self._meta.get_fields()
              

Deprecated some old API endpoints.

Multiple template engines

  • Stable API for integrating template backends.
  • Built in support for Jinja2.
  • Do your own custom backends.
pip install Jinja2

In settings (new in 1.8):


TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'APP_DIRS': True,
    },
]
                
Above APP_DIRS means Jinja2 engines look for templates in the jinja2 subdirectory of installed applications.
  • Default configuration for Jinja2 is kept to a minimum on purpose.
  • Changes from Jinja2's default configuration includes setting autoescape, correct template loader for APP_DIRS, auto_reload and undefined.
  • No awareness of Django's context processors, filters etc.
Use the environment option to add Django specific api. From the docs, assuming we've created myproject/jinja2.py:

from __future__ import absolute_import  # Python 2 only

from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.urlresolvers import reverse

from jinja2 import Environment


def environment(**options):
    env = Environment(**options)
    env.globals.update({
        'static': staticfiles_storage.url,
        'url': reverse,
    })
    return env
              
Define it in the TEMPLATE options:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'APP_DIRS': True,
        'OPTIONS': {
          'environment': 'myproject.jinja2.environment',
        }
    },
]
                
Usage in the template:

Company Logo

Administration
                

Security enhancements

Integrated several features from django-secure

Security Middleware

django.middleware.security.SecurityMiddleware

Provides some security enhancements for request/response. Enable each separately using a setting.

./manage.py check --deploy

New option for check management command --deploy, check settings file to increase site security.

Database types and queries

New data types

  • UUIDField
  • DurationField

UUID field

Stored as the native uuid data type on PostgreSQL and as a fixed length character field on other backends.

Has a corresponding form field will accept any any string format accepted as the hex argument to Python's UUID constructor.

Duration Field

For storing periods of time (timedelta)

  • Stored as interval type on PostgreSQL.
  • INTERVAL DAY(9) TO SECOND(6) on oracle.
  • bigint of microseconds on other backends.
  • Has a corresponding form field.

PostgreSQL specific

django.contrib.postgres.fields has extensions for PostgreSQL specific features:

ArrayField, HStoreField, Range Fields, and unaccent lookup.

Array Field


class Post(models.Model):
    name = models.CharField(max_length=200)
    tags = ArrayField(models.CharField(max_length=200), blank=True)
                

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])
                

Querying

Multitude of query options, index, and slice transforms:

  • contains
  • contained_by
  • overlap
  • len
  • Specific index or slice

See the documentation.

HStoreField

HStoreField stores key/value mappings of strings to strings.

Need to setup the hstore extension on your db by adding a migration with HStoreExtension operation.

HStoreField example


class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = HStoreField()
                
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'})
                

Querying

  • key lookups (data__breed='collie')
  • contains
  • contained_by
  • has_key
  • has_keys
  • keys
  • values

See the documentation.

Range Fields

Five range field types, corresponding to the built-in range types in PostgreSQL.

  • IntegerRangeField
  • BigIntegerRangeField
  • FloatRangeField
  • DateTimeRangeField
  • DateRangeField

Can define custom range types.

RangeField example


class Event(models.Model):
    name = models.CharField(max_length=200)
    ages = IntegerRangeField()
                

>>> Event.objects.create(name='Soft play', ages=(0, 10))
>>> Event.objects.create(name='Pub trip', ages=(21, None))
                

Querying Range fields

Example for contains

>>> from psycopg2.extras import NumericRange
>>> Event.objects.filter(ages__contains=NumericRange(4, 5))
                

Querying Range fields

  • contains
  • contained_by
  • overlap
  • fully_lt
  • fully_gt
  • not_lt
  • not_gt
  • adjacent_to
  • startswith
  • endswith
  • isempty

unaccent

  • Perform accent-insensitive lookup
  • Activate haccent or use UnaccentExtension in a migration.
  • 
    >>> City.objects.filter(name__unaccent="México")
    ['<City: Mexico>']
    
    >>> User.objects.filter(first_name__unaccent__startswith="Jerem")
    ['<User: Jeremy>', '<User: Jérémy>', '<User: Jérémie>', '<User: Jeremie>']
                        

Query Expressions, Conditional Expressions, and Database Functions

More SQL power for us

Query Expressions

  • Custom expression classes allowed after large internal refactoring of annotate() and aggregate()by Josh Smeaton.
  • Aggregates can reference multiple fields and perform arithmetic.
  • order_by() can accept expressions.

Examples from django's docs


# Aggregates can contain complex computations also
Company.objects.annotate(num_offerings=Count(F('products') + F('services')))

# Expressions can also be used in order_by()
Company.objects.order_by(Length('name').asc())
Company.objects.order_by(Length('name').desc())
              

Conditional Expressions

Let you use if ... elif ... else logic within filters, annotations, aggregations, and updates

  • When
  • Case

Conditional annotation


>>> # Get the discount for each Client based on the account type
>>> Client.objects.annotate(
...     discount=Case(
...         When(account_type=Client.GOLD, then=Value('5%')),
...         When(account_type=Client.PLATINUM, then=Value('10%')),
...         default=Value('0%'),
...         output_field=CharField(),
...     ),
... ).values_list('name', 'discount')
            

Conditional Update


>>> a_month_ago = date.today() - timedelta(days=30)
>>> a_year_ago = date.today() - timedelta(days=365)
>>> # Update the account_type for each Client from the registration date
>>> Client.objects.update(
...     account_type=Case(
...         When(registered_on__lte=a_year_ago,
...              then=Value(Client.PLATINUM)),
...         When(registered_on__lte=a_month_ago,
...              then=Value(Client.GOLD)),
...         default=Value(Client.REGULAR)
...     ),
... )
>>> Client.objects.values_list('name', 'account_type')
[('Jane Doe', 'G'), ('James Smith', 'R'), ('Jack Black', 'P')]
            

Conditional Aggregation


>>> from django.db.models import IntegerField, Sum
>>> Client.objects.aggregate(
...     regular=Sum(
...         Case(When(account_type=Client.REGULAR, then=1),
...              output_field=IntegerField())
...     ),
...     gold=Sum(
...         Case(When(account_type=Client.GOLD, then=1),
...              output_field=IntegerField())
...     ),
...     platinum=Sum(
...         Case(When(account_type=Client.PLATINUM, then=1),
...              output_field=IntegerField())
...     )
... )
{'regular': 2, 'gold': 1, 'platinum': 3}
            

Database Functions

Use functions provided by the underlying database in annotations, filters, etc.

  • Coalesce
  • Concat
  • Length
  • Lower
  • Upper
  • Substr

TestCase data setup

  • Tests are wrapped with 2 nested atomic block, class level and for each test.
  • The classmethod TestCase.setUpTestData() allows setting initial data at the class level for the whole test.
  • Faster than using setup() which runs for each test.
  • Backends without transactions will still run the tests but won't benifit from improvements.
  • Fixture loading within TestCase is now performed once for the whole TestCase

Auth

  • Can customize email for PasswordResetForm by overriding send_email()
  • Permission name is now 255 chars, run migration.
  • Default password hasher iterations increased by 33%.

Admin

  • Limit module display in the admin index with has_module_permission().
  • Can override delete_view.
  • InlineModelAdmin.show_change_link.
  • AdminSite.site_url for the link to "View site", default to "/".
  • Control who may access the admin site just by overriding AdminSite.has_permission().

Forms

  • Form.has_error(field).
  • required_css_class on labels of required fields.
  • Field.label_suffix to override label_suffix per field.

Management commands

  • runserver uses daemon threads for faster reloading.
  • dumpdata --output.
  • loaddata --ignorenonexistent to ignore data for non existent models.
  • showmigrations.
  • makemigrations --name for custom migration name.
  • insepectdb outputs Meta.uniqute_together, introspects AutoField for PostgreSQL and MySQL, and introspects database views (only tables in < 1.8).

Migrations

  • RunSQL can handle parameters.
  • RunSQL.noop and RunPython.noop.
  • Can serialize model managers as part of model state.

Models

  • default_related_name for model.
  • Model.from_db() can customize model loading behaviour in the ORM.
  • Model.refresh_from_db()
  • EmailField.max_length is 254 instead of 75.

Request and Response

  • GET and POST are QueryDict instead of Dict, FILES is a MultiValueDict.
  • Added HttpResponse.charset
  • FileResponse for streaming files.
  • HttpResponse.setdefault(), allows setting a header unless already been set.

Backwards incompatible changes

  • Removed support for PostgreSQL < 9.0, MySQL < 5.5 and Oracle < 11.1.
  • select_related() checks validity of given fields instead of ignoring.
  • Querying relation lookups checks for object type.
  • Assigning unsaved objects to relations raises an error.
  • Related object operations (add(), remove(), clear(), direct assignment) are run in a transaction to reduce data corruption risk.

Removals

  • django.contrib.formtools moved to separate packages.
  • django.contrib.comments (after the deprecation in 1.6).
  • TransactionMiddleware, decorators and context managers defined in django.db.transaction (autocommit, commit_on_success, and commit_manually).

Deprecations

  • patterns() and string view referencing, instead use:
    
    from django.conf.urls import url
    from myapp import views
    
    urlpatterns = [
        url('^$', views.myview),
        url('^other/$', views.otherview),
    ]
                   
  • Some template related settings (moved to the TEMPLATES setting for multi template engines).
  • django.core.context_processors moved to django.template.context_processors.

Deprecations

  • NoArgsCommand, use BaseCommand which takes no arguments by default.
  • django.contrib.webdesign, the lorem tag is now built in.

After preparing this talk:

Tired