Django released its latest major version, Django 5.0, on December 4, 2023. This version also offers new features, deprecations, updated functionalities, and more that Django developers can utilize to enhance web application development processes. For a better understanding, let us delve deeper into this version’s core and minor features. In this article we discuss the Django 5.0: Significant Features for Web Development in 2024 those are following.
Django 5.0
Django 5.0 Facets: Managing Admin Changelist Facet Counts
Django, a high-level Python Web Framework, empowers Developers to build secure and scalable web applications easily. Its elegant syntax, rapid development tools, and vast ecosystem of readily available Django packages make it a popular choice for web development projects of all sizes and complexities. Furthermore, an active developer community continuously improves its ecosystem through major and minor version updates for optimized web application development. Minor versions deliver bug updates, adjustments, and deprecations, while major versions unveil new features, improved functionality, and bug updates, adjustments, and deprecations. Django 5.0 recently introduced the concept of Facets. Facets refer to numerical indicators showing the number of results that match each filter.
The new “ModelAdmin.show_facets” attribute enables developers to determine whether to displace facet counts in the admin changelist. The attribute, by default, is set to “ShowFacets.ALLOW,” which automatically updates/changes facet counts to display the current filter set applied in the admin changelist during the presence of a specific query string parameter. However, this default setting can increase database queries and affect an application’s performance.
Alternatively, Django programmers can use the “ModelAdmin.show_facets” attribute’s other two settings to manage the display of facet counts as required:
- ShowFacets.ALWAYS: Facet counts are always shown.
- ShowFacets.NEVER: Facet counts are never revealed.
Enabling Facets with Automatic Updates: This code shows how to enable Facets with the default behavior of automatic updates based on the current filter set:
Python3
from django.contrib import admin
from django.contrib.admin import ShowFacets
class MyModelAdmin(admin.ModelAdmin):
show_facets = ShowFacets.ALLOW
admin.site.register(MyModel, MyModelAdmin)
|
Django 5.0 introduced field group and field group templates. These concepts enable developers to render different parts of a form field easily, like errors, help texts, labels, and widgets. Developers can access each form field “{{ form.name_of_field }}” in templates in Django forms. Each field comprises an “as_field_group()” function that renders the various parts of the form field. Therefore, the “as_field_group()” function helps developers create generic templates for organizing field elements in the preferred/required layout.
Django, by default, uses the “django/forms/field.html” template and the “django/forms/div.html” form style. However, developers can customize the “django/forms/field.html” template on a Customization options:
- Per-project basis: Developers can set “field_template_name” in the project-level “FORM_RENDERER” to customize the template for an entire project.
- Per-field basis: Developers can define the field in a form class with template_name=”your_custom_template.html”.
- Per-request basis: Developers can use the “BoundField.render()” method and specify a different template name to customize the template on a request basis.
Forms in django:
Python3
class MyForm(forms.Form):
name = forms.CharField(label = 'Name' , max_length = 100 )
email = forms.EmailField(label = 'Email' )
file = forms.FileField(label = 'File' )
|
Creating Forms in Django :
Python3
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length = 100 )
message = forms.CharField(widget = forms.Textarea)
|
Customized Form Example :
Python3
from django.shortcuts import render
def my_view(request):
if request.method = = 'POST' :
form = MyForm(request.POST)
if form.is_valid():
name = form.cleaned_data[ 'name' ]
email = form.cleaned_data[ 'email' ]
age = form.cleaned_data[ 'age' ]
Else:
form = MyForm()
return render(request, 'my_template.html' , { 'form' : form})
|
Rendering Form Fields:
HTML
< form method = "post" >
{% csrf_token %}
{% for field in form %}
< div class = "form-group" >
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}
< small class = "form-text text-muted" >{{ field.help_text }}</ small >
{% endif %}
{% for error in field.errors %}
< div class = "alert alert-danger" >{{ error }}</ div >
{% endfor %}
</ div >
{% endfor %}
< button type = "submit" >Submit</ button >
</ form >
|
Django 5.0: Simplifies Database-Computed Values
The newly introduced Field.db_default parameter enables a Django developer to set a database-computed value for a particular field. The value can be a simple one or a database function like “Now”. For example, a “DateTimeField” can use “Now()” as its default value.
Developers can also set more complex defaults if those defaults are made from simple values and database functions. However, the defaults cannot refer to other fields or models. When both “db_default” and “Field.default” are specified, Django will apply the former at the database level and the latter in the Python code. This helps developers add rows from the ORM or insert new fields during migration.
Simple default value:
Python3
from django.db import models
from django.db.models.functions import Now
class MyModel(models.Model):
created_at = models.DateTimeField(db_default = Now())
priority = models.IntegerField(db_default = 0 )
|
More Complex Default Value:
Python3
from django.db import models
from django.db.models.functions import TruncMonth, Now, F
class MyModel(models.Model):
month_due = models.DateField(
db_default = TruncMonth(Now() + timedelta(days = 90 ), output_field = models.DateField())
)
end = models.IntegerField(db_default = F( "start" ) + 50 )
|
Django 5.0 : Database-Generated Columns with “GeneratedField”
The new “GeneratedField” is always computed based on the model’s other fields. It uses the GENERATED ALWAYS SQL syntax. The database itself manages and updates the “GeneratedField” class. Furthermore, it also allows developers to create database-generated columns.
The “GeneratedField” delivers two types of generated columns, including stored and virtual. The value of a stored generated column is stored in the database and calculated when data is written, added, or updated. On the other hand, the value of a virtual generated column does not require any storage and is calculated when the data is read.
The three primary attributes of the “GeneratedField” class includes:
- GeneratedField.expression: The database uses the “expression” attribute to automatically set the “GeneratedField” value during every model modification. However, the “GeneratedField.expression” should be deterministic and refer to fields from within the model that reside within the database table.
- GeneratedField.output_field: “output_field” refers to a model field instance that defines the GeneratedField’s data type.
- GeneratedField.db_persist: The “db_persist” attribute determines whether the column should occupy database storage (like a stored database column) or not occupy database storage (like a virtual database column).
Simple Area Calculation from Dimensions:
Python3
from django.db import models
from django.db.models import F
class Square(models.Model):
side = models.PositiveIntegerField()
area = models.GeneratedField(
expression = F( "side" ) * F( "side" ),
output_field = models.IntegerField(),
db_persist = True )
|
More Complex discount calculation:
Python3
from django.db import models
from django.db.models import F, Func
class Product(models.Model):
price = models.DecimalField(max_digits = 10 , decimal_places = 2 )
discount_rate = models.DecimalField(max_digits = 2 , decimal_places = 2 )
final_price = models.GeneratedField(
expression = F( "price" ) * ( 1 - Func( 'greatest' , F( "discount_rate" ), 0.1 )),
output_field = models.DecimalField(max_digits = 10 , decimal_places = 2 ),
db_persist = True
)
|
Previously, we have choices had to be a list of 2-tuples or a subclass of Enumeration types. Furthermore, the subclass must be able to access the “.choices” attribute to deliver values in expected formats. Django 5.0 enabled support for using mapping and callables for defining model fields (Field.choices) and form fields (ChoiceField.choices). This not only provides greater flexibility but also more versatility while declaring choices.
Furthermore, developers no longer need to use the “.choices” attribute directly when using Enumeration types. Django now automatically converts the provided choices into a standardized list of 2-tuples. This ensures greater consistency irrespective of how the choices are initially defined.
Python3
from django.db import models
class MyChoices(models.TextChoices):
CHOICE_A = 'A' , 'Option A'
CHOICE_B = 'B' , 'Option B'
CHOICE_C = 'C' , 'Option C'
class MyModel(models.Model):
option = models.CharField(max_length = 1 , choices = MyChoices)
|
In this example, you can directly use MyChoices when defining the choices attribute for the option field. You no longer need to access .choices explicitly. Django will automatically handle the conversion to the expected format. This change streamlines the process of using enumeration types with Django and ensures consistency in how choices are represented.
Django 5.0: Minor Features Overview
In the above sections, we have defined the core features of Django 5.0. However, the version also has some minor features in reserve, which can benefit developers significantly. Let’s have a quick look at those.
- Customizing Querysets For Log Entries: The new “AdminSite.get_log_entries()” method delivers querysets for related “LogEntry” instances as displayed on the index page of the site. It also allows developers to customize querysets for the listed log entries on the site.
- Checking User Passwords Asynchronously: The “abstractBaseUser.acheck_password()” method and “django.contrib.auth.hashers.acheck_password()” asynchronous functions enable developers to check user passwords asynchronously without blocking other application processes for greater efficiency.
- Receiving Admin Classes For Registered Model Classes: The “AdminSite.get_model_admin()” method delivers an admin class for a registered model class. However, it raises “django.contrib.admin.exceptions.NotRegistered” for an unregistered model.
- Validating the Nearest Point between Two Geometries: The “ClosestPoint()” function evaluates two geographic expressions or fields and finds the nearest two-dimensional point between both expressions/fields.
Customizing Querysets For Log Entries:
Python3
from django.contrib import admin
from django.contrib.admin.models import LogEntry, CHANGE
class MyAdminSite(admin.AdminSite):
def get_log_entries( self , request, * * kwargs):
queryset = super ().get_log_entries(request, * * kwargs)
queryset = queryset. filter (action_flag = CHANGE)
queryset = queryset.order_by( '-action_time' )
return queryset
admin_site = MyAdminSite()
|
Checking User Passwords Asynchronously:
Python3
from django.contrib import admin
from myapp.models import MyModel
model_admin = admin.site.get_model_admin(MyModel)
try :
model_admin = admin.site.get_model_admin(MyModel)
except admin.sites.NotRegistered:
print ( "MyModel is not registered with the admin site." )
|
Receiving Admin Classes For Registered Model Classes:
Python3
from django.contrib.auth.models import AbstractBaseUser
class MyUser(AbstractBaseUser):
async def check_password( self , password):
return is_valid_password
async def my_login_view(request):
if request.method = = 'POST' :
username = request.POST.get( 'username' )
password = request.POST.get( 'password' )
user = await User.objects.get_by_natural_key(username)
if user and await user.acheck_password(password):
Else:
|
Checking distance threshold –
Python3
from django.contrib.gis.measure import D
threshold = 10
point1 = MyModel.objects.first().geom
point2 = AnotherModel.objects.first().geom
closest_point = point1.ClosestPoint(point2)
if point1.distance(closest_point) < D(m = threshold):
else :
|
Analyzing Geometries’ Equivalence: The GEOSGeometry.equals_identical() method checks if two geometries are dimensionally identical and equivalent in terms of orders, structure, and values. It returns “True” if it finds both the geometries are equivalent.
Python3
from django.contrib.gis.geos import GEOSGeometry
point1 = GEOSGeometry( 'POINT(5 23)' )
point2 = GEOSGeometry( 'POINT(5 23)' )
identical = point1.equals_identical(point2)
polygon1 = GEOSGeometry( 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))' )
polygon2 = GEOSGeometry( 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))' )
identical = polygon1.equals_identical(polygon2)
different_point = GEOSGeometry( 'POINT(5.1 23)' )
identical = point1.equals_identical(different_point)
different_polygon = GEOSGeometry( 'POLYGON((0 0, 10 5, 10 10, 0 10, 0 0))' )
identical = polygon1.equals_identical(different_polygon)
|
- Reverifying Messages being Added to HttpResponse: The “MessagesTestMixin.assertMessages() assertion” method tests “messages” added to a “HttpResponse”. It declares “True” if “messages” added to a “HttpResponse” matches the “expected_messages” (a list of message objects). Although the comparison depends on the ordering, developers can change the ordered argument to “False” to turn off this setting.
- Specifying Default URL Schemes: This “assume_scheme” argument determines whether to use “http://” or “https://” when a user enters a URL manually. For example, if a user searches for “example.com”, the setting will automatically change it to “http://example.com” or “https://example.com”.
- Setting Values For Fields in New Records: This “create_defaults” argument falls under the “QuerySet.aupdate_or_create()” and “QuerySet.update_or_create()” methods. It allows you to set different values for fields specifically when creating a new record, as opposed to when updating an existing one.
- Customizing Error Codes during Model Validation: A new attribute called “violation_error_code” was introduced for “BaseConstraint”, “CheckConstraint”, and “UniqueConstraint”. The “violation_error_code” attribute enables customizing the error code when “ValidationError” is raised during model validation.
- Distinguishing Unique Constraints: This “UniqueConstraint.nulls_distinct” attribute can help developers determine whether rows containing “Null” values in a unique constraint are distinct from each other on PostgreSQL 15+.
- Getting Objects Asynchronously: These
aget_object_or_404()
and aget_list_or_404()
asynchronous shortcuts are the asynchronous version of the get_object_or_404()
and get_list_or_404()
. They enable developers to get objects asynchronously.
- Customizing Error Messages Raised by Paginators: The
django.core.paginator.Paginator.error_messages
argument enables customizing paginator-raised error messages. You must pass a dictionary containing keys like “invalid_page”, “min_page”, and “no_results” that match the error messages you want to change.
Python3
from django.core.paginator import Paginator
paginator = Paginator(my_object_list, 10 , error_messages = {
'invalid_page' : 'Please enter a valid page number.' ,
'min_page' : 'There is no page before the first page.' ,
'no_results' : 'This page does not contain any results.' ,
})
Try:
page = paginator.page(request.GET.get( 'page' ))
except Paginator.InvalidPage:
pass
|
Escaping elements of sequences: This “escapeseq template” filter allows developers to apply escape to a sequence’s elements.
{% for item in my_list %}
<p>{{ item|escape }}</p>
{% endfor %}
Detecting slowest test cases: This “test –durations” option displays the duration of the slowest test cases on Python 3.12+.
Output:
$ python manage.py test --durations
slowest tests (times in seconds):
1. (5.23s) tests.test_slow_function
2. (2.17s) tests.test_complex_calculation
3. (1.89s) tests.test_large_database_query
Simplified setting of offsets for valid values: The newly added offset argument in the “StepValueValidator” function allows developers to set offsets for valid values.
Python3
from django.db import models
class Product(models.Model):
price = models.IntegerField(
validators = [StepValueValidator( 10 , offset = 5 )]
)
|
Django 5.0 Syntax Updates
Here’s a concise overview of the notable syntax changes and additions in Django 5.0 release:
- django.contrib.messages : Added ‘MessagesTestMixin.assertMessages()’ for testing messages added to a response.
- django.contrib.postgres : Added ‘violation_error_code’ attribute to ‘ExclusionConstraint’ for customizing the code of ‘ValidationError’ raised during model validation.
- Asynchronous Views : ‘http.disconnect’ events are now handled under ASGI, allowing views to perform necessary cleanup if a client disconnects before the response is generated.
- Decorators : Several decorators now support wrapping asynchronous view functions, including ‘cache_control()’, ‘never_cache()’, ‘no_append_slash()’, ‘csrf_exempt()’, and more.
- Error Reporting : ‘sensitive_variables()’ and ‘sensitive_post_parameters()’ can now be used with asynchronous functions.
- File Storage : ‘File.open()’ now passes all positional (‘*args’) and keyword arguments (‘**kwargs’) to Python’s built-in ‘open()’.
- Forms : Introduced ‘assume_scheme’ argument for ‘URLField’ to specify a default URL scheme. Form fields include ‘aria-describedby’ and ‘aria-invalid=”true”‘ HTML attributes for improved accessibility.
- Internationalization : Added support and translations for the Uyghur language.
- Migrations : Support serialization of functions decorated with ‘functools.cache()’ or ‘functools.lru_cache()’ without the need for a custom serializer.
- Models : Various new features like create_defaults argument for ‘QuerySet.update_or_create()’ and ‘QuerySet.aupdate_or_create()’, ‘violation_error_code’ attribute for constraints, and ‘UniqueConstraint.nulls_distinct’ attribute for PostgreSQL 15+.
- Pagination : Introduced ‘django.core.paginator.Paginator.error_messages’ argument to customize error messages raised by ‘Paginator.page()’.
- Signals : New ‘Signal.asend()’ and ‘Signal.asend_robust()’ methods for asynchronous signal dispatch.
- Templates : Added ‘escapeseq’ template filter to apply ‘escape’ to each element of a sequence.
- Tests : ‘Client’ and ‘AsyncClient’ now provide asynchronous methods such as ‘asession()’, ‘alogin()’, and more. New ‘test –durations’ option to show the duration of the slowest tests on Python 3.12+.
- Validators : Added ‘offset’ argument to ‘StepValueValidator’ to specify an offset for valid values.
Additionally, Specific Feature Enhancements Were Made:
- Default values: Introduced ‘Field.db_default’ parameter for database-computed default values for model fields.
- Generated fields: Added ‘GeneratedField’ for database-generated columns in models.
- Facet filters: Active filter facet counts can be displayed in the admin change list with UI toggle, modifiable via ‘ModelAdmin.show_facets’ attribute.
- Form field: Introduced the concept of a field group along with field group templates for rendering various components associated with a Django form field.
- AsyncClient: Additional asynchronous methods to both the Client and AsyncClient for asynchronous testing of Django applications.
These changes mark a significant advancement in Django, bringing enhancements and innovative features that streamline the development process.
Conclusion
The latest version also offers changes, deprecations, and more that will help optimize web application development approaches. You can check Django’s official documentation to learn more about them. Django will offer new versions comprising more advanced features and modifications in the upcoming years. Also, Django 5.0 enhances the utility of Django packages by ensuring compatibility with the latest features, fostering a community-driven approach for robust, reusable components, and streamlining development with pre-built, easily integrable modules. So stay tuned to learn more about them.
Share your thoughts in the comments
Please Login to comment...