122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
# 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() |