Torna al blog

Django 6.0: Content Security Policy, Template Partials e Python Email API

Esplora Django 6.0: supporto CSP integrato, template partials per componenti riusabili, modern email API, AsyncPaginator e miglioramenti GIS.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

9 min di lettura
Django 6.0: Content Security Policy, Template Partials e Python Email API

Django 6.0 introduce supporto integrato per Content Security Policy, template partials per componenti riusabili, modern Python email API, AsyncPaginator per paginazione asincrona e miglioramenti GIS. Questa release migliora sicurezza, modularità e performance per applicazioni web Django.

🎯 Novità Principali

Content Security Policy Support

CSP integrato per protezione XSS:

# ✅ Django 6.0 - Built-in CSP

# settings.py
from django.utils.csp import CSP

MIDDLEWARE = [
    # ...
    'django.middleware.csp.ContentSecurityPolicyMiddleware',
]

SECURE_CSP = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF, CSP.NONCE],
    "img-src": [CSP.SELF, "https:"],
    "style-src": [CSP.SELF, CSP.NONCE],
}

# Risultato header:
# Content-Security-Policy:
#   default-src 'self';
#   script-src 'self' 'nonce-SECRET';
#   img-src 'self' https:;
#   style-src 'self' 'nonce-SECRET'

Template nonces:

<!-- ✅ Automatic nonce injection -->

{% load csp %}

<!DOCTYPE html>
<html>
  <head>
    <!-- Nonce automatico per inline scripts -->
    <script {% csp_nonce %}>
      console.log("Protected inline script");
    </script>

    <!-- Nonce automatico per inline styles -->
    <style {% csp_nonce %}>
      .protected {
        color: blue;
      }
    </style>
  </head>
  <body>
    <h1>Protected Page</h1>
  </body>
</html>

Report-only mode:

# ✅ Test CSP senza bloccare

# settings.py
SECURE_CSP_REPORT_ONLY = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF],
    "report-uri": ["/csp-report/"],
}

# Monitora violazioni senza bloccare
# Perfetto per testing e gradual rollout

Per-view policy:

# ✅ Override CSP per view specifiche

from django.views.decorators.csp import csp

@csp(
    script_src=[CSP.SELF, "cdn.example.com"],
    img_src=[CSP.SELF, "images.example.com"],
)
def my_view(request):
    return render(request, 'template.html')

# Disabilita CSP per view specifica
from django.views.decorators.csp import csp_exempt

@csp_exempt
def public_api(request):
    return JsonResponse({'data': 'public'})

Template Partials

Componenti riusabili dentro template:

<!-- ✅ Django 6.0 - Template Partials -->

<!-- article_list.html -->
{% partialdef article_card %}
<article class="card">
  <h3>{{ article.title }}</h3>
  <p>{{ article.excerpt }}</p>
  <span class="date">{{ article.pub_date|date:"Y-m-d" }}</span>
</article>
{% endpartialdef %}

<!-- Usa il partial -->
<div class="articles">
  {% for article in articles %} {% partial article_card with article=article %}
  {% endfor %}
</div>

<!-- Riusa in altri template -->
{% include "article_list.html#article_card" with article=featured_article %}

Partial from Python:

# ✅ Render partial da view

from django.template.loader import get_template

def article_detail(request, article_id):
    article = Article.objects.get(id=article_id)

    # Render solo il partial
    template = get_template('article_list.html#article_card')
    html = template.render({'article': article})

    return JsonResponse({'html': html})

# Perfetto per HTMX/Alpine.js!

Nested partials:

<!-- ✅ Partials annidati -->

{% partialdef user_profile %}
<div class="profile">
  <h2>{{ user.username }}</h2>

  {% partialdef user_stats %}
  <div class="stats">
    <span>Posts: {{ user.post_count }}</span>
    <span>Followers: {{ user.follower_count }}</span>
  </div>
  {% endpartialdef %} {% partial user_stats %}
</div>
{% endpartialdef %}

<!-- Reference nested partial -->
{% include "users.html#user_profile#user_stats" %}

Modern Email API

Python 3.6+ email API:

# ❌ Prima: API legacy

from django.core.mail import EmailMessage

msg = EmailMessage(
    'Subject',
    'Body',
    'from@example.com',
    ['to@example.com'],
)
msg.attach_file('document.pdf')
msg.send()

# message() ritornava SafeMIMEText/SafeMIMEMultipart
# ✅ Django 6.0 - Modern API

from django.core.mail import EmailMessage

msg = EmailMessage(
    subject='Subject',
    body='Body',
    from_email='from@example.com',
    to=['to@example.com'],
)
msg.attach_file('document.pdf')
msg.send()

# message() ritorna email.message.EmailMessage
# API più pulita e Unicode-friendly

Policy parameter:

# ✅ Custom email policy

from django.core.mail import EmailMessage
from email.policy import default

msg = EmailMessage(
    subject='Test',
    body='Hello',
    from_email='test@example.com',
    to=['user@example.com'],
)

# Specifica policy
message = msg.message(policy=default)

# Controllo fine su serializzazione

MIMEPart support:

# ✅ Modern MIME parts

from django.core.mail import EmailMessage
from email.message import MIMEPart

msg = EmailMessage(
    subject='Newsletter',
    from_email='news@example.com',
    to=['user@example.com'],
)

# Attach modern MIMEPart
part = MIMEPart()
part.set_content('HTML content', subtype='html')
msg.attach(part)

msg.send()

AsyncPaginator

Paginazione asincrona:

# ✅ Django 6.0 - Async pagination

from django.core.paginator import AsyncPaginator
from django.http import JsonResponse

async def article_list(request):
    articles = Article.objects.all()

    paginator = AsyncPaginator(articles, per_page=25)
    page_number = request.GET.get('page', 1)

    page = await paginator.aget_page(page_number)

    return JsonResponse({
        'articles': [
            {'title': a.title, 'id': a.id}
            async for a in page
        ],
        'has_next': page.has_next(),
        'has_previous': page.has_previous(),
    })

AsyncPage:

# ✅ Async iteration

from django.core.paginator import AsyncPaginator

async def process_large_dataset(request):
    data = LargeModel.objects.all()

    paginator = AsyncPaginator(data, per_page=100)

    results = []
    for page_num in paginator.page_range:
        page = await paginator.aget_page(page_num)

        async for item in page:
            processed = await process_item(item)
            results.append(processed)

    return JsonResponse({'results': results})

🔧 Miglioramenti Django

Admin Improvements

# ✅ Font Awesome 6.7.2 icons

# Nuove icone nell'interfaccia admin
# Più moderne e accessibili

# Custom password change form
from django.contrib.admin import AdminSite

class MyAdminSite(AdminSite):
    password_change_form = MyCustomPasswordChangeForm

admin_site = MyAdminSite(name='myadmin')

Auth Improvements

# ✅ PBKDF2 iterations aumentate

# Django 5.2: 1,000,000 iterations
# Django 6.0: 1,200,000 iterations

# Maggiore sicurezza password hashing
# Automatic upgrade su password change

GIS Enhancements

# ✅ Nuove funzionalità GIS

from django.contrib.gis.db.models import F
from django.contrib.gis.db.models.functions import (
    Rotate, GeometryType, IsValid, Collect, GeoHash
)

# Check dimensione M
if geometry.hasm:
    print("Has M dimension")

# Rotate geometry
rotated = MyModel.objects.annotate(
    rotated_geom=Rotate(F('geom'), 45)
)

# Geometry type lookup
points = MyModel.objects.filter(geom__geom_type='POINT')

# MariaDB 12.0.1+ support
# - coveredby lookup
# - isvalid lookup
# - Collect aggregation
# - GeoHash function
# - IsValid function

Models Improvements

# ✅ StringAgg aggregate universale

from django.db.models import StringAgg

# Prima: solo PostgreSQL
# Ora: tutti i database!

articles = Article.objects.aggregate(
    tags=StringAgg('tags__name', delimiter=', ')
)
# {'tags': 'python, django, web'}

# order_by support
articles = Article.objects.aggregate(
    tags=StringAgg(
        'tags__name',
        delimiter=', ',
        order_by='tags__name'
    )
)

CompositePrimaryKey support:

# ✅ Composite primary keys

from django.db import models

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.IntegerField()

    class Meta:
        constraints = [
            models.CompositePrimaryKey(
                fields=['order', 'product']
            )
        ]

# QuerySet.raw() support
items = OrderItem.objects.raw(
    'SELECT * FROM order_items WHERE order_id = %s',
    [order_id]
)

# Subquery support
expensive_items = OrderItem.objects.filter(
    id__in=Subquery(
        OrderItem.objects.filter(
            price__gt=100
        ).values('id')
    )
)

AnyValue aggregate:

# ✅ Arbitrary value aggregation

from django.db.models import AnyValue

# Ottieni valore arbitrario dal gruppo
results = Order.objects.values('customer_id').annotate(
    sample_order_id=AnyValue('id'),
    sample_total=AnyValue('total'),
)

# Utile per SELECT DISTINCT con campi extra
# Supportato: SQLite, MySQL, Oracle, PostgreSQL 16+

JSONField Enhancements

# ✅ Negative array indexing su SQLite

from django.db.models import Q

# Accedi elementi dall'ultimo
last_item = MyModel.objects.filter(
    data__items__-1='last_value'
)

# Prima: solo PostgreSQL
# Ora: anche SQLite!

View Improvements

# ✅ forloop.length in templates

{% for item in items %}
    <p>Item {{ forloop.counter }} of {{ forloop.length }}</p>
{% endfor %}

# forloop.length: numero totale iterazioni
# Più chiaro di forloop.revcounter

querystring tag:

# ✅ Query string helpers

<!-- Aggiungi parametri query string -->
<a href="?{% querystring page=2 %}">Page 2</a>

<!-- Con ? prefix automatico -->
<a href="{% querystring page=2 %}?param=value">Next</a>

<!-- Multiple arguments (merge dicts) -->
{% querystring {'page': 2, 'sort': 'name'} {'filter': 'active'} %}

🎨 Pattern Pratici

Pattern 1: CSP Gradual Rollout

# ✅ Rollout graduale CSP

# settings.py
from django.utils.csp import CSP

# Step 1: Report-only
SECURE_CSP_REPORT_ONLY = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF, CSP.NONCE],
    "report-uri": ["/csp-violations/"],
}

# Step 2: Monitora violations
# views.py
@csrf_exempt
def csp_report(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        logger.warning(f"CSP violation: {data}")
    return HttpResponse(status=204)

# Step 3: Enforce dopo fixing violations
SECURE_CSP = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF, CSP.NONCE],
}

Pattern 2: Component-Based Templates

<!-- ✅ Partial components library -->

<!-- components/button.html -->
{% partialdef button %}
<button
  class="btn btn-{{ variant|default:'primary' }}"
  {%
  if
  disabled
  %}disabled{%
  endif
  %}>
  {{ label }}
</button>
{% endpartialdef %}

<!-- components/card.html -->
{% partialdef card %}
<div class="card">
  {% if title %}
  <h3 class="card-title">{{ title }}</h3>
  {% endif %}
  <div class="card-body">{{ body }}</div>
</div>
{% endpartialdef %}

<!-- usage.html -->
{% include "components/button.html#button" with label="Submit" variant="success"
%} {% include "components/card.html#card" with title="Welcome" body="Hello
World" %}

Pattern 3: HTMX + Partials

# ✅ HTMX endpoints con partials

# views.py
from django.template.loader import get_template

def load_more_articles(request):
    page = request.GET.get('page', 1)
    articles = Article.objects.all()

    paginator = AsyncPaginator(articles, 10)
    page_obj = await paginator.aget_page(page)

    # Render solo il partial
    template = get_template('articles.html#article_card')

    html = ''.join([
        template.render({'article': article})
        async for article in page_obj
    ])

    return HttpResponse(html)
<!-- articles.html -->
{% partialdef article_card %}
<article class="article">
  <h3>{{ article.title }}</h3>
  <p>{{ article.content }}</p>
</article>
{% endpartialdef %}

<div id="articles" hx-get="/articles/load-more/" hx-swap="beforeend">
  {% for article in articles %} {% partial article_card %} {% endfor %}
</div>

<button
  hx-get="/articles/load-more/?page={{ page.next_page_number }}"
  hx-target="#articles"
  hx-swap="beforeend">
  Load More
</button>

Pattern 4: Email con Modern API

# ✅ Rich email composition

from django.core.mail import EmailMultiAlternatives
from email.message import MIMEPart

def send_newsletter(users, content):
    for user in users:
        msg = EmailMultiAlternatives(
            subject=f'Newsletter {user.name}',
            body='Plain text version',
            from_email='news@example.com',
            to=[user.email],
        )

        # HTML alternative
        msg.attach_alternative(content.html, 'text/html')

        # Modern MIME attachments
        if content.has_pdf:
            part = MIMEPart()
            part.set_content(
                content.pdf_data,
                maintype='application',
                subtype='pdf'
            )
            part['Content-Disposition'] = 'attachment; filename="report.pdf"'
            msg.attach(part)

        msg.send()

📊 Compatibility & Migration

Python Support

# ✅ Django 6.0 requirements

# Supported: Python 3.12, 3.13
# Dropped: Python 3.10, 3.11

# Django 5.2.x: last to support 3.10/3.11

MariaDB Support

# ✅ MariaDB 10.6+

# Dropped: MariaDB 10.5 (EOL June 2025)
# Minimum: MariaDB 10.6

Breaking Changes

# ⚠️ Important changes

# 1. Email API
# EmailMessage.message() ora ritorna email.message.EmailMessage
# Non più SafeMIMEText/SafeMIMEMultipart

# 2. JSON serializer
# Aggiunge newline a fine output anche senza indent

# 3. Custom ORM expressions
# as_sql() deve ritornare params come tuple, non list

def as_sql(self, compiler, connection) -> tuple[str, tuple]:
    # ✅ Return tuple
    return sql, tuple(params)

🎓 Best Practices

1. Usa CSP Report-Only Prima

# ✅ Test before enforce

SECURE_CSP_REPORT_ONLY = {
    "default-src": [CSP.SELF],
    "report-uri": ["/csp-report/"],
}

# Monitora, fix violations, then enforce

2. Partials per Components

<!-- ✅ Reusable UI components -->

{% partialdef button %}...{% endpartialdef %} {% partialdef card %}...{%
endpartialdef %} {% partialdef modal %}...{% endpartialdef %}

3. Async Views per I/O

# ✅ Async for database/external APIs

async def my_view(request):
    data = await fetch_data()
    page = await paginator.aget_page(1)
    return render(request, 'template.html', {'data': data})

4. Modern Email API

# ✅ Use keyword arguments

msg = EmailMessage(
    subject='Subject',
    body='Body',
    from_email='from@example.com',
    to=['to@example.com'],
)

💡 Conclusioni

Django 6.0 migliora sicurezza e developer experience:

CSP integrato per protezione XSS ✅ Template Partials per componenti riusabili ✅ Modern Email API più pulita ✅ AsyncPaginator per performance ✅ GIS enhancements su più database ✅ StringAgg universaleCompositePrimaryKey support

Upgrade oggi:

# Installa Django 6.0
pip install Django==6.0

# Check compatibility
python -Wd manage.py check

# Run migrations
python manage.py migrate

Quando usare Django 6.0:

  • ✅ Nuovi progetti Python 3.12+
  • ✅ App che richiedono CSP
  • ✅ Component-based UI
  • ✅ Async I/O intensive
  • ✅ GIS applications