loko/streetup/api/admin.py
2026-07-22 14:48:40 +02:00

92 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# admin.py
from django import forms
from django.utils import timezone
from django.utils.html import escape, mark_safe
from django.contrib import admin
from django.contrib.admin.widgets import AdminSplitDateTime
from .models import PersonalAccessToken, _sha256 # _sha256 dispo dans models.py
import secrets
_HTML5_FMT = "%Y-%m-%dT%H:%M" # sans secondes ni timezone (exigence de datetime-local)
class PersonalAccessTokenForm(forms.ModelForm):
# Non obligatoire: on le remplit nous-mêmes si vide
token_hash = forms.CharField(required=False, widget=forms.TextInput(attrs={"readonly": "readonly"}))
# Aide à la saisie: durée si expires_at non remplit
expires_at = forms.DateTimeField(
required=False,
input_formats=[_HTML5_FMT], # pour le POST
widget=forms.DateTimeInput(
attrs={"type": "datetime-local"},
format=_HTML5_FMT, # pour l’affichage initial
),
)
ttl_days = forms.IntegerField(required=False, min_value=1, initial=30, help_text="Utilisé si 'expires_at' est vide.")
class Meta:
model = PersonalAccessToken
fields = ("user", "name", "collections", "expires_at", "revoked", "token_hash")
widgets = {
# JSONField par défaut convient; sinon remplacer par un Textarea
# "collections": forms.Textarea(attrs={"rows": 2}),
}
def clean(self):
cleaned = super().clean()
expires_at = cleaned.get("expires_at")
ttl_days = cleaned.get("ttl_days")
if not expires_at:
# Si pas de date fournie, calcule via ttl_days (ou 30 par défaut)
days = ttl_days or 30
cleaned["expires_at"] = timezone.now() + timezone.timedelta(days=days)
return cleaned
@admin.register(PersonalAccessToken)
class PersonalAccessTokenAdmin(admin.ModelAdmin):
form = PersonalAccessTokenForm
list_display = ("user", "name", "expires_at", "revoked", "created_at")
list_filter = ("revoked",)
search_fields = ("user__username", "user__email", "name")
readonly_fields = ("token_hash",) # visible mais non éditable
actions = ["revoke_tokens"]
@admin.action(description="Révoquer les tokens sélectionnés")
def revoke_tokens(self, request, queryset):
updated = queryset.update(revoked=True)
self.message_user(request, f"{updated} token(s) révoqué(s).")
def save_model(self, request, obj, form, change):
"""
À la création, si token_hash est vide:
- Génère un raw token 'pat_...'
- Hash en SHA-256 (même logique que le modèle)
- Sauve l'objet
- Affiche le raw token une seule fois dans un message admin
"""
raw_token_to_display = None
if not change and not obj.token_hash:
# Génère un raw token (même préfixe que la méthode issue)
raw = "pat_" + secrets.token_urlsafe(32)
obj.token_hash = _sha256(raw) # même algo que le modèle
raw_token_to_display = raw
# Remise en forme des collections: set trié, comme dans issue()
if obj.collections:
obj.collections = sorted(set(obj.collections))
# Si l'admin n'a pas rempli expires_at, le form l'a calculé via ttl_days
super().save_model(request, obj, form, change)
if raw_token_to_display:
# Affiche le raw token (copiable) + l'expiration
message = f"Token généré pour {obj.user}: {raw_token_to_display}\nExpire le {obj.expires_at:%Y-%m-%d %H:%M}"
self.message_user(
request,
mark_safe("<pre style='white-space:pre-wrap'>"+escape(message)+"</pre>")
)