prije gemini provjere

This commit is contained in:
mariomitte
2026-05-31 19:21:25 +02:00
commit 9aff9c5dc1
191 changed files with 17016 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
# 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()