66 lines
1.9 KiB
Python
66 lines
1.9 KiB
Python
# users/signals.py
|
|
import io
|
|
import random
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
from django.conf import settings
|
|
from django.core.files.base import ContentFile
|
|
from django.db.models.signals import post_save
|
|
from django.dispatch import receiver
|
|
|
|
from .models import User
|
|
|
|
PALETTE = ["#34344A", "#845A6D", "#D47386", "#C89B7B", "#32746D"]
|
|
|
|
def initials_for(user: User) -> str:
|
|
if getattr(user, "initials", None):
|
|
return user.initials[:2].upper()
|
|
|
|
first = (user.first_name or "").strip()
|
|
last = (user.last_name or "").strip()
|
|
|
|
if first and last:
|
|
return (first[0] + last[0]).upper()
|
|
if first:
|
|
return first[:2].upper()
|
|
# fallback: email
|
|
return (user.email or "U")[:2].upper()
|
|
|
|
def generate_avatar_png(text: str, bg_hex: str, size: int = 256) -> bytes:
|
|
img = Image.new("RGBA", (size, size), bg_hex)
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
font_path = getattr(settings, "AVATAR_FONT_PATH", None)
|
|
if font_path:
|
|
font = ImageFont.truetype(font_path, int(size * 0.42))
|
|
else:
|
|
font = ImageFont.load_default()
|
|
|
|
bbox = draw.textbbox((0, 0), text, font=font)
|
|
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
|
x = (size - tw) / 2
|
|
y = (size - th) / 2 - (size * 0.05)
|
|
|
|
draw.text((x, y), text, font=font, fill="white")
|
|
|
|
out = io.BytesIO()
|
|
img.save(out, format="PNG", optimize=True)
|
|
return out.getvalue()
|
|
|
|
@receiver(post_save, sender=User)
|
|
def set_default_avatar(sender, instance: User, created: bool, **kwargs):
|
|
if not created:
|
|
return
|
|
if instance.image:
|
|
return
|
|
|
|
text = initials_for(instance)
|
|
bg = random.choice(PALETTE)
|
|
|
|
png_bytes = generate_avatar_png(text=text, bg_hex=bg, size=256)
|
|
filename = f"user_{instance.pk}_avatar.png"
|
|
instance.image.save(filename, ContentFile(png_bytes), save=False)
|
|
User.objects.filter(pk=instance.pk).update(
|
|
image=instance.image.name,
|
|
)
|