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.

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 universale ✅ CompositePrimaryKey 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