Open In App

Django 5.0: Significant Features for Web Development in 2024

Last Updated : 11 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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-(1)

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
      
    # ... other model admin definitions
admin.site.register(MyModel, MyModelAdmin)


Django 5.0 : Form Field Rendering Enhancements

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




# Example Code for Form Types
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():
            # Process the form data
            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())  # Sets default to current time
    priority = models.IntegerField(db_default=0)       # Sets default to 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())
    # Sets default to the first day of the month 90 days from now
    end = models.IntegerField(db_default=F("start") + 50# Sets default to 50 more than the "start" field


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
    )


Django 5.0: Flexible Model and Form Field Choices

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)
        # Customize the queryset here:
        queryset = queryset.filter(action_flag=CHANGE)  # Example: Filter for only change actions
        queryset = queryset.order_by('-action_time'# Example: Order by action time descending
        return queryset
admin_site = MyAdminSite()  # Register your custom admin site


Checking User Passwords Asynchronously:

Python3




from django.contrib import admin
from myapp.models import MyModel
# Assuming MyModel is registered with the admin site:
model_admin = admin.site.get_model_admin(MyModel)  # Returns the model admin class
# If MyModel is not registered:
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):
    # ... other user model fields and methods
    async def check_password(self, password):
        # Implement your asynchronous password verification logic here
        # (e.g., compare password with hashed password stored in database)
        # …
        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):
            # Login successful
            # …
        Else:
            # Invalid credentials
            # …


Checking distance threshold –

Python3




from django.contrib.gis.measure import D
threshold = 10  # Define your acceptable distance threshold
point1 = MyModel.objects.first().geom
point2 = AnotherModel.objects.first().geom
closest_point = point1.ClosestPoint(point2)
if point1.distance(closest_point) < D(m=threshold):
    # Valid nearest point based on distance
    # …
else:
    # Invalid due to exceeding distance threshold
    # …


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)  # True
  
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)  # True
  
different_point = GEOSGeometry('POINT(5.1 23)')
identical = point1.equals_identical(different_point)  # False (different coordinates)
  
different_polygon = GEOSGeometry('POLYGON((0 0, 10 5, 10 10, 0 10, 0 0))')
identical = polygon1.equals_identical(different_polygon)  # False (different structure)


  • 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:
    # Handle invalid page error with your customized message
    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)]
    # Valid values: 5, 15, 25, 35, …


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.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads