Django 2.0 - how will it change the Python development?

Developers, therefore, don’t have to constantly change the version of Django that's used in their projects. The ideal situation is to accommodate in an LTS version and develop your project through using it as a base. With that, you will have peace of mind for a while.

Django 2.0 will not be an LTS version, and the first LTS version in the 2.x will be the 2.2, which will be released in the second quarter of 2019. But it’s going to be a version with major changes that will require a lot of refactoring in many projects.

What’s new in Django 2.0

There is a huge change in Django 2.0 - it will be no longer compatible with Python 2.7. The Django 1.11.x series is the last to support Python 2.7. Also, Django 2.0 will be the last release series to support Python 3.4, which also will be left aside by March 2019. Taking into account all those dates, any project with Python 2.7 will not be supported after 2020, so, actually, we still have a lot of time to do this migration.

Now let’s take a look at the biggest changes in the codebase and new features that you will encounter in this new version.

Simplified URL routing syntax

In previous Django releases you had to declare your URLs like this:

url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),

Now you can just do this:

path('articles/<int:year>/', views.year_archive),

In the example, the view will receive the year keyword argument as an integer rather than as a string. Bye bye, regex.

Mobile-friendly contrib.admin

The admin is now responsive and supports all of the major mobile devices. Django 2.0 will implement Django Flat Responsive, which is a third party app for a mobile responsive admin. They have been flirting with this idea since Django 1.9, and now it is finally happening. It seems like prayers of many Django developers have been answered.

Django 2.0 - Login Page

Django 2.0 - Dashboard

Django 2.0 - Calendar widget

Window expressions

Window functions provide a way to apply functions on partitions. Unlike a normal aggregation function, which computes a final result for each set defined by the group, window functions operate on frames and partitions and compute the result for each row.

You can specify multiple windows in the same query, which in Django ORM would be equivalent to including multiple expressions in a QuerySet.annotate() call.

For example, to annotate each movie with the average rating for the movies made by the same studio, in the same genre, and with the same release year, you will have to do this:

>>> from django.db.models import Avg, ExtractYear, F, Window
>>> Movie.objects.annotate(
>>> avg_rating=Window(
>>> expression=Avg('rating'),
>>> partition_by=[F('studio'), F('genre')],
>>> order_by=ExtractYear('released').asc(),
>>> ),
>>> )

This makes it easy to check how particular movies are rated in comparison with each other.

The Window class is the main expression for an OVER(SQL) clause.

The expression argument is either a window function, an aggregate function, or an expression that’s compatible in a window clause.

The partition_by argument is a list of expressions (column names should be wrapped in an F-object) that control the partitioning of the rows.

The output_field is specified either as an argument or by the expression.

The order_by argument accepts a sequence of expressions on which you can call asc() and desc(). The ordering controls the order in which the expression is applied.

Minor features

The changes mentioned above have been catalogued as major, but there are a lot of small changes that will modify the way we use Django. Let’s take a look at the most important ones.

django.contrib.admin

  • The new ModelAdmin.autocomplete_fields attribute and ModelAdmin.get_autocomplete_fields() method allow using a Select2 search widget for ForeignKey and ManyToManyField.
    Autocomplete_fields is a list of ForeignKey and/or ManyToManyField fields you would like to change to Select2 autocomplete inputs.

File Storage

  • File.open() can be used as a context manager, e.g. with file.open() as f:.

Forms

  • The new date_attrs and time_attrs arguments for SplitDateTimeWidget and SplitHiddenDateTimeWidget allow specifying different HTML attributes for the DateInput and TimeInput (or hidden) subwidgets.
  • The new Form.errors.get_json_data() method returns form errors as a dictionary suitable for including in a JSON response.

Generic Views

  • The new ContextMixin.extra_context attribute allows adding context in View.as_view(). It's a convenient way of specifying some simple context in as_view(). Example usage:

from django.views.generic import TemplateView

TemplateView.as_view(extra_context={'title': 'Custom Title'})

Management Commands

  • inspectdb now translates MySQL’s unsigned integer columns to PositiveIntegerField or PositiveSmallIntegerField

Migrations

  • The new squashmigrations --squashed-name option allows naming the squashed migration. And, in case you are wondering what squashing is, it's the act of reducing an existing set of many migrations down to one (or sometimes a few) migrations which still represent the same changes.

Models

  • The new StrIndex database function finds the starting index of a string inside another string.
    Returns a positive integer corresponding to the 1-indexed position of the first occurrence of substring inside string, or 0 if substring is not found.
    >>> from django.db.models import Value as V
    >>> from django.db.models.functions import StrIndex
    >>> Author.objects.create(name='Margaret Smith')
    >>> Author.objects.create(name='Smith, Margaret')
    >>> Author.objects.create(name='Margaret Jackson')
    >>> Author.objects.filter(name='Margaret Jackson').annotate(
    ... smith_index=StrIndex('name', V('Smith'))
    ... ).get().smith_index
    0
    >>> authors = Author.objects.annotate(
    ... smith_index=StrIndex('name', V('Smith'))
    ... ).filter(smith_index__gt=0)
    <QuerySet [<Author: Margaret Smith>, <Author: Smith, Margaret>]>
  • The new chunk_size parameter of QuerySet.iterator() controls the number of rows fetched by the Python database client when streaming results from the database. For databases that don’t support server-side cursors, it controls the number of results Django fetches from the database adapter.
  • QuerySet.earliest(), QuerySet.latest(), and Meta.get_latest_by now allow ordering by several fields.
    Entry.objects.latest('pub_date', '-expire_date')
  • The new field_name parameter of QuerySet.in_bulk() allows fetching results based on any unique model field.
  • The new filter argument for built-in aggregates allows adding different conditionals to multiple aggregations over the same fields or relations.
    >>> from django.db.models import IntegerField, Sum
    >>> Client.objects.aggregate(
    ... regular=Count('pk', filter=Q(account_type=Client.REGULAR)),
    ... gold=Count('pk', filter=Q(account_type=Client.GOLD)),
    ... platinum=Count('pk', filter=Q(account_type=Client.PLATINUM)),
    ... )
    {'regular': 2, 'gold': 1, 'platinum': 3}
  • The new named parameter of QuerySet.values_list() allows fetching results as named tuples.
    >>> Entry.objects.values_list('id', 'headline', named=True)
    <QuerySet [Row(id=1, headline='First entry'), ...]>
  • The new FilteredRelation class allows adding an ON clause to querysets. FilteredRelation is used with annotate() to create an ON clause when a JOIN is performed.
    >>> from django.db.models import FilteredRelation, Q
    >>> Restaurant.objects.annotate(
    ... pizzas_vegetarian=FilteredRelation(
    ... 'pizzas', condition=Q(pizzas__vegetarian=True),
    ... ),
    ... ).filter(pizzas_vegetarian__name__icontains='mozzarella')

Pagination

  • Added Paginator.get_page() to provide the documented pattern of handling invalid page numbers.
  • Returns a Page object with the given 1-based index, while also handling out of range and invalid page numbers.
  • If the page isn’t a number, it returns the first page. If the page number is negative or greater than the number of pages, it returns the last page.
  • It raises an exception (EmptyPage) only if you specify Paginator(..., allow_empty_first_page=False) and the object_list is empty.

Requests and Responses

  • The runserver Web server supports HTTP 1.1.

Templates

  • To increase the usefulness of Engine.get_default() in third-party apps, it now returns the first engine if multiple DjangoTemplates engines are configured in TEMPLATES rather than raising ImproperlyConfigured.
  • Custom template tags may now accept keyword-only arguments.

Validators

  • The new ProhibitNullCharactersValidator disallows the null character in the input of the CharField form field and its subclasses. Raises a ValidationError if str(value) contains one or more nulls characters ('\x00').

Backwards incompatible changes in 2.0

Apart from new things, in Django 2.0 we have a lot of deprecated features and changes that may make your code incompatible with this new version. There are hundreds of changes in this section, so with that in mind, I will try to keep it short and talk only about those that are used on a daily basis for Django web development.

Removed support for bytestrings in some places

To support native Python 2 strings, older Django versions had to accept both bytestrings and unicode strings. Now that Python 2 support is dropped, bytestrings should only be encountered around input/output boundaries (handling of binary fields or HTTP streams, for example). You might have to update your code to limit bytestring usage to a minimum, as Django no longer accepts bytestrings in certain code paths.

QuerySet.reverse() and last() are prohibited after slicing

>>> Model.objects.all()[:2].reverse()
Traceback (most recent call last):
...
TypeError: Cannot reverse a query once a slice has been taken.

Form fields no longer accept optional arguments as positional arguments.

forms.IntegerField(25, 10)

raises an exception and should be replaced with:

forms.IntegerField(max_value=25, min_value=10)

Indexes no longer accept positional arguments.

For example:

models.Index(['headline', '-pub_date'], 'index_name')

raises an exception and should be replaced with

models.Index(fields=['headline', '-pub_date'], name='index_name')

Features removed in 2.0

  • The app_name argument to include() is removed.
  • The django.core.urlresolvers module is removed.
  • Using User.is_authenticated() and User.is_anonymous() as methods rather than properties is no longer supported.
  • FileField methods get_directory_name() and get_filename() are removed.
  • Support for old-style middleware using settings.MIDDLEWARE_CLASSES is removed.

Conclusion

Django 2.0 brings us a lot of new features and tools but also deprecates and removes many of the already used ones. I wouldn’t recommend migrating huge projects to Django 2.0 unless you have strong reasons to do that since it can be really problematic to find all the incompatible lines in your code. For projects after 1.8 version I would recommend to stay in 1.11 version since you will have more than 2 years of support, which should be enough to finish your project. But if you are starting a new project when Django 2.0 is already out, use it. In the future, it will make things easier for you.

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .