"""Logo-beheer met kwaliteitscheck. GET /api/huisstijl/ -> huidig logo (alle ingelogde gebruikers). PUT /api/huisstijl/ -> upload logo (alleen beheerders); voert een kwaliteitscheck uit en geeft waarschuwingen terug. DELETE /api/huisstijl/ -> logo verwijderen (alleen beheerders). """ from rest_framework.decorators import api_view, parser_classes from rest_framework.parsers import FormParser, JSONParser, MultiPartParser from rest_framework.response import Response from .models import Huisstijl from .serializers import HuisstijlSerializer TOEGESTAAN = {"png", "jpg", "jpeg", "gif", "webp", "svg"} MAX_BYTES = 3 * 1024 * 1024 # 3 MB def _hex(rgb): return "#%02x%02x%02x" % (rgb[0], rgb[1], rgb[2]) def analyseer_logo(f): """Beoordeel een geüpload logo. Geeft {ok, fouten, waarschuwingen, info}. 'ok' is False bij harde problemen (verkeerd bestandstype, onleesbaar). Anders wordt het bestand opgeslagen, maar mogelijk met waarschuwingen (bv. een niet-transparante / gevulde achtergrond, of lage resolutie). """ naam = (f.name or "").lower() ext = naam.rsplit(".", 1)[-1] if "." in naam else "" fouten, waarsch = [], [] if ext not in TOEGESTAAN: fouten.append(f"Bestandstype '.{ext}' wordt niet ondersteund. Gebruik PNG, SVG, JPG, GIF of WebP.") return {"ok": False, "fouten": fouten, "waarschuwingen": waarsch, "info": {}} if getattr(f, "size", 0) and f.size > MAX_BYTES: waarsch.append(f"Het bestand is groot ({f.size // 1024} KB); overweeg een kleiner logo.") if ext == "svg": return {"ok": True, "fouten": [], "waarschuwingen": waarsch, "info": {"formaat": "SVG", "transparant": True, "breedte": None, "hoogte": None, "achtergrond": None}} try: from PIL import Image except Exception: # Zonder Pillow slaan we toch op, maar zonder beeldcontrole. return {"ok": True, "fouten": [], "waarschuwingen": waarsch + ["Beeldcontrole niet beschikbaar."], "info": {"formaat": ext.upper()}} try: f.seek(0) img = Image.open(f) img.load() except Exception: return {"ok": False, "fouten": ["Kan de afbeelding niet lezen of het bestand is beschadigd."], "waarschuwingen": [], "info": {}} breedte, hoogte = img.size transparant = False achtergrond = None heeft_alpha = img.mode in ("RGBA", "LA") or (img.mode == "P" and "transparency" in img.info) if heeft_alpha: rgba = img.convert("RGBA") hoeken = [rgba.getpixel((0, 0)), rgba.getpixel((breedte - 1, 0)), rgba.getpixel((0, hoogte - 1)), rgba.getpixel((breedte - 1, hoogte - 1))] if all(p[3] < 16 for p in hoeken): transparant = True else: achtergrond = hoeken[0][:3] waarsch.append("De hoeken zijn niet doorzichtig; het logo lijkt een gevulde achtergrond te hebben. " "Op een gekleurde balk valt dat lelijk op — gebruik bij voorkeur een transparante PNG of SVG.") else: rgb = img.convert("RGB") hoeken = [rgb.getpixel((0, 0)), rgb.getpixel((breedte - 1, 0)), rgb.getpixel((0, hoogte - 1)), rgb.getpixel((breedte - 1, hoogte - 1))] achtergrond = hoeken[0] eensgezind = len(set(hoeken)) == 1 if eensgezind and min(achtergrond) > 245: waarsch.append("Effen witte achtergrond zonder transparantie. Op een gekleurd vlak valt het witte blok op; " "een transparante PNG of SVG is netter.") elif eensgezind: waarsch.append(f"Effen achtergrondkleur gedetecteerd ({_hex(achtergrond)}). Gebruik bij voorkeur een transparante PNG of SVG.") else: waarsch.append("Geen transparantie (bv. JPG). Voor een nette weergave op gekleurde vlakken is een transparante PNG of SVG beter.") if breedte < 80 or hoogte < 80: waarsch.append(f"Lage resolutie ({breedte}×{hoogte}px); dit kan korrelig printen.") info = { "formaat": img.format or ext.upper(), "breedte": breedte, "hoogte": hoogte, "transparant": transparant, "achtergrond": _hex(achtergrond) if achtergrond else None, } return {"ok": True, "fouten": [], "waarschuwingen": waarsch, "info": info} @api_view(["GET", "PUT", "POST", "DELETE"]) @parser_classes([MultiPartParser, FormParser, JSONParser]) def huisstijl(request): obj = Huisstijl.actueel() schrijven = request.method in ("PUT", "POST", "DELETE") if schrijven and not (request.user and request.user.is_authenticated and request.user.is_staff): return Response({"detail": "Alleen beheerders (is_staff) kunnen het logo wijzigen. Vraag een beheerder om het logo in te stellen."}, status=403) if request.method == "DELETE": if obj.logo: obj.logo.delete(save=True) return Response(HuisstijlSerializer(obj, context={"request": request}).data) if request.method in ("PUT", "POST"): f = request.FILES.get("logo") if not f: return Response({"detail": "Geen bestand ontvangen (veld 'logo')."}, status=400) try: analyse = analyseer_logo(f) except Exception as e: # noqa: BLE001 — geef een leesbare reden i.p.v. 500. return Response({"detail": f"Kon de afbeelding niet verwerken: {e}"}, status=400) if not analyse["ok"]: return Response({"detail": " ".join(analyse["fouten"]), "kwaliteit": analyse}, status=400) try: f.seek(0) if obj.logo: obj.logo.delete(save=False) obj.logo = f obj.save() except Exception as e: # noqa: BLE001 return Response({"detail": f"Opslaan van het logo mislukte: {e}"}, status=400) data = HuisstijlSerializer(obj, context={"request": request}).data data["kwaliteit"] = analyse return Response(data) return Response(HuisstijlSerializer(obj, context={"request": request}).data)