# operations/services.py import os from PIL import Image from io import BytesIO import requests from django.conf import settings from django.db import transaction from django.utils import timezone from rest_framework import serializers from .models import RadniNalog, PutniNalog from kalendar.services import kreiraj_kalendarski_unos def izvrsi_perform_create_logiku(serializer, user): """ Konzistentna poslovna logika za kreiranje radnog naloga. Dodjeljuje izvršitelja i automatski okida kalendarski servis. """ if not serializer.validated_data.get('izvrsitelj'): radni_nalog = serializer.save(izvrsitelj=user) else: radni_nalog = serializer.save() try: kreiraj_kalendarski_unos(radni_nalog) except Exception as e: # Možeš uvesti standardni logging sustav umjesto printa print(f"Greška pri kreiranju kalendarskog zapisa: {e}") return radni_nalog def generiraj_i_kreiraj_putni_nalog(radni_nalog_id, vozilo): """ Enkapsulirana atomska logika za generiranje jedinstvenog broja sekvence i kreiranje putnog naloga, sprječavajući dupliciranje brojeva pod opterećenjem. """ radni_nalog = RadniNalog.objects.get(id=radni_nalog_id) godina = timezone.now().year prefix = f"PN-{godina}-" with transaction.atomic(): # Zaključavamo zapise za tekuću godinu (Pessimistic Locking) zadnji_pn = PutniNalog.objects.select_for_update().filter( broj_naloga__startswith=prefix ).order_by('-broj_naloga').first() if zadnji_pn: try: zadnji_broj_str = zadnji_pn.broj_naloga.split('-')[-1] novi_broj = int(zadnji_broj_str) + 1 except (ValueError, IndexError): novi_broj = 1 else: novi_broj = 1 # Formatiranje broja: PN-2026-0001 broj_pn = f"{prefix}{novi_broj:04d}" # Siguran upis u bazu putni_nalog = PutniNalog.objects.create( broj_naloga=broj_pn, vozilo=vozilo, korisnik=radni_nalog.izvrsitelj, relacija=f"Zagreb - {radni_nalog.klijent.grad} - Zagreb", mjesto_odredista=radni_nalog.klijent.grad, vrijeme_polaska=timezone.now() ) # Povezivanje s radnim nalogom radni_nalog.putni_nalog = putni_nalog radni_nalog.save() return putni_nalog def get_optimized_image(image_url, width=600): """ On-the-fly optimizacija slike u memoriji. Prima URL ili lokalnu stazu, vraća sirove bajtove WebP slike. """ # 1. Dohvaćanje slike (podržava lokalne relativne staze i apsolutne URL-ove) if image_url.startswith('http://') or image_url.startswith('https://'): odgovor = requests.get(image_url, timeout=5) odgovor.raise_for_status() img_data = BytesIO(odgovor.content) else: # Pretvaramo relativnu stazu (npr. /media/slika.jpg) u apsolutnu stazu na disku # Mičemo početni slash ako postoji da osiguramo ispravno spajanje staza cista_staza = image_url.lstrip('/') if cista_staza.startswith('media/'): cista_staza = cista_staza.replace('media/', '', 1) staza_na_disku = os.path.join(settings.MEDIA_ROOT, cista_staza) if not os.path.exists(staza_na_disku): raise FileNotFoundError(f"Slika nije pronađena na lokaciji: {staza_na_disku}") with open(staza_na_disku, 'rb') as f: img_data = BytesIO(f.read()) # 2. Obrada slike kroz PIL (Pillow) paket with Image.open(img_data) as img: # Osiguranje kompatibilnosti profila boja (RGBA -> RGB za WebP) if img.mode in ('RGBA', 'LA'): pozadina = Image.new('RGB', img.size, (255, 255, 255)) pozadina.paste(img, mask=img.split()[3]) img = pozadina elif img.mode == 'P': img = img.convert('RGB') # Proporcionalni resize ako slika prelazi željenu širinu if img.width > width: faktor_skaliranja = width / float(img.width) nova_visina = int(float(img.height) * float(faktor_skaliranja)) img = img.resize((width, nova_visina), Image.Resampling.LANCZOS) # 3. Spremanje u memorijski buffer umjesto na disk memorijski_buffer = BytesIO() # Smanjujemo kvalitetu na 80% (idealni omjer oštrine i težine u bajtovima) img.save(memorijski_buffer, format='WEBP', quality=80) memorijski_buffer.seek(0) return memorijski_buffer.getvalue()