prije gemini provjere
This commit is contained in:
0
001.BACKEND/fleet/__init__.py
Normal file
0
001.BACKEND/fleet/__init__.py
Normal file
90
001.BACKEND/fleet/admin.py
Normal file
90
001.BACKEND/fleet/admin.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from .models import Stroj, Vozilo
|
||||
from operations.models import RadniNalog, PutniNalog # Importamo modele iz druge aplikacije
|
||||
|
||||
# --- INLINE KLASE ---
|
||||
|
||||
class PutniNalogInline(admin.TabularInline):
|
||||
"""Prikazuje putne naloge unutar pregleda vozila."""
|
||||
model = PutniNalog
|
||||
extra = 0
|
||||
fields = ('broj_naloga', 'datum_izdavanja', 'korisnik', 'pocetna_km', 'zavrsna_km', 'status')
|
||||
readonly_fields = ('broj_naloga', 'datum_izdavanja', 'korisnik', 'pocetna_km', 'zavrsna_km', 'status')
|
||||
can_delete = False
|
||||
verbose_name = "Povijest putovanja"
|
||||
verbose_name_plural = "Povijest putovanja za ovo vozilo"
|
||||
|
||||
class RadniNalogInline(admin.TabularInline):
|
||||
model = RadniNalog
|
||||
extra = 0
|
||||
# Umjesto običnog polja 'broj_naloga', koristimo metodu 'link_na_nalog'
|
||||
fields = ('link_na_nalog', 'datum_kreiranja', 'klijent', 'izvrsitelj', 'status')
|
||||
readonly_fields = ('link_na_nalog', 'datum_kreiranja', 'klijent', 'izvrsitelj', 'status')
|
||||
can_delete = False
|
||||
|
||||
def link_na_nalog(self, obj):
|
||||
if obj.id:
|
||||
# Generiramo URL za admin promjenu Radnog Naloga
|
||||
url = reverse('admin:operations_radninalog_change', args=[obj.id])
|
||||
return mark_safe(f'<a href="{url}" style="font-weight:bold;">RN-{obj.broj_naloga}</a>')
|
||||
return "-"
|
||||
|
||||
link_na_nalog.short_description = 'Broj naloga'
|
||||
|
||||
# --- GLAVNE ADMIN KLASE ---
|
||||
|
||||
@admin.register(Vozilo)
|
||||
class VoziloAdmin(admin.ModelAdmin):
|
||||
list_display = ('naziv', 'registracija', 'status', 'pocetni_kilometri', 'trenutni_kilometri')
|
||||
search_fields = ('naziv', 'registracija')
|
||||
list_filter = ('status',)
|
||||
ordering = ('naziv',)
|
||||
|
||||
# Dodajemo inline za putne naloge
|
||||
inlines = [PutniNalogInline]
|
||||
|
||||
@admin.register(Stroj)
|
||||
class StrojAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'serijski_broj',
|
||||
'prikaz_naziva',
|
||||
'vlasnik',
|
||||
'tip',
|
||||
'radni_sati',
|
||||
'registracija',
|
||||
'datum_zadnjeg_atesta'
|
||||
)
|
||||
|
||||
search_fields = (
|
||||
'serijski_broj',
|
||||
'naziv',
|
||||
'marka',
|
||||
'model_stroja',
|
||||
'vlasnik__naziv',
|
||||
'vlasnik__oib'
|
||||
)
|
||||
|
||||
list_filter = ('tip', 'marka', 'vlasnik')
|
||||
autocomplete_fields = ['vlasnik']
|
||||
|
||||
# Dodajemo inline za radne naloge
|
||||
inlines = [RadniNalogInline]
|
||||
|
||||
fieldsets = (
|
||||
('Osnovne informacije', {
|
||||
'fields': ('vlasnik', 'naziv', 'tip', 'serijski_broj')
|
||||
}),
|
||||
('Detalji o modelu', {
|
||||
'fields': (('marka', 'model_stroja'), 'godina_proizvodnje')
|
||||
}),
|
||||
('Tehnički podaci i održavanje', {
|
||||
'fields': ('radni_sati', 'registracija', 'datum_zadnjeg_atesta'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def prikaz_naziva(self, obj):
|
||||
return f"{obj.marka} {obj.model_stroja}".strip() or obj.naziv
|
||||
prikaz_naziva.short_description = 'Marka i Model'
|
||||
5
001.BACKEND/fleet/apps.py
Normal file
5
001.BACKEND/fleet/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FleetConfig(AppConfig):
|
||||
name = 'fleet'
|
||||
103
001.BACKEND/fleet/management/commands/populate_stroj.py
Normal file
103
001.BACKEND/fleet/management/commands/populate_stroj.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import random
|
||||
from datetime import date, timedelta
|
||||
from django.core.management.base import BaseCommand
|
||||
from faker import Faker
|
||||
from fleet.models import Stroj
|
||||
from kupci.models import Kupac
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Popunjava bazu podataka isključivo Liebherr strojevima sinkronizirano s logistikom flote'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'total',
|
||||
type=int,
|
||||
help='Broj Liebherr strojeva koje želiš kreirati',
|
||||
default=10
|
||||
)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
total = kwargs['total']
|
||||
fake = Faker(['hr_HR'])
|
||||
kupci = list(Kupac.objects.all())
|
||||
|
||||
if not kupci:
|
||||
self.stdout.write(self.style.ERROR("Greška: Nema kupaca u bazi. Prvo pokreni populate_kupci!"))
|
||||
return
|
||||
|
||||
# Bogatiji katalog usklađen s tvorničkim Liebherr oznakama i tvojim TIP_STROJA choices
|
||||
liebherr_katalog = {
|
||||
'dizalica_toranj': [
|
||||
'132 EC-H 8 Litronic',
|
||||
'280 EC-H 12',
|
||||
'81 K.1',
|
||||
'172 EC-B 8',
|
||||
'125 K'
|
||||
],
|
||||
'dizalica_auto': [
|
||||
'LTM 1030-2.1',
|
||||
'LTM 1050-3.1',
|
||||
'LTM 1060-3.1',
|
||||
'LTM 1120-4.1',
|
||||
'LTM 1350-6.1', # Dodan LTM 1350 (sukladno planiranim modifikacijama ventila)
|
||||
'LTC 1050-3.1'
|
||||
]
|
||||
}
|
||||
|
||||
self.stdout.write(self.style.MIGRATE_HEADING(f'🚀 Generiram {total} Liebherr strojeva u bazu...'))
|
||||
|
||||
created_count = 0
|
||||
for _ in range(total):
|
||||
tip = random.choice(['dizalica_toranj', 'dizalica_auto'])
|
||||
model_naziv = random.choice(liebherr_katalog[tip])
|
||||
|
||||
# Odabiremo nasumičnog vlasnika za ovaj stroj
|
||||
vlasnik_stroja = random.choice(kupci)
|
||||
|
||||
# Realistični Liebherr tvornički serijski brojevi (npr. LE-054321)
|
||||
serijski = f"LE-{fake.numerify(text='0######')}"
|
||||
|
||||
# USKLAĐENO: Registracija za autodizalice sada ima ispravne razmake i crticu
|
||||
# Koristimo reg. oznaku koja odgovara sjedištu vlasnika ili nasumični HR grad
|
||||
reg = None
|
||||
if tip == 'dizalica_auto':
|
||||
gradovi = ['ZG', 'KA', 'RI', 'ST', 'OS', 'KR']
|
||||
grad = random.choice(gradovi)
|
||||
reg_broj = fake.numerify(text='####')
|
||||
reg_slova = fake.bothify(text='??', letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||
reg = f"{grad} {reg_broj}-{reg_slova}"
|
||||
|
||||
# Radni sati dizalice
|
||||
sati = random.uniform(800.0, 15000.0) if tip == 'dizalica_toranj' else random.uniform(200.0, 6000.0)
|
||||
|
||||
# Atesti unutar zadnjih godinu dana
|
||||
datum_atesta = fake.date_between(start_date='-1y', end_date='today')
|
||||
|
||||
# USKLAĐENO: Generiramo lokaciju. Ako klijent/vlasnik ima definiran grad,
|
||||
# stroj se primarno nalazi na gradilištu u tom gradu (ili okolici), inače koristimo fake.city()
|
||||
lokacija_stroja = vlasnik_stroja.grad if (vlasnik_stroja and hasattr(vlasnik_stroja, 'grad')) else fake.city()
|
||||
|
||||
# Nasumična simulacija aktivnih gradilišta u regiji
|
||||
if random.random() > 0.6:
|
||||
lokacija_stroja = f"Gradilište {lokacija_stroja}"
|
||||
|
||||
try:
|
||||
Stroj.objects.create(
|
||||
vlasnik=vlasnik_stroja,
|
||||
naziv=f"Liebherr {model_naziv}",
|
||||
serijski_broj=serijski,
|
||||
marka='Liebherr',
|
||||
model_stroja=model_naziv,
|
||||
godina_proizvodnje=random.randint(2012, 2025),
|
||||
tip=tip,
|
||||
radni_sati=round(sati, 1), # Zaokružujemo na 1 decimalu sukladno formama
|
||||
registracija=reg,
|
||||
datum_zadnjeg_atesta=datum_atesta
|
||||
)
|
||||
created_count += 1
|
||||
self.stdout.write(f" ✅ Kreiran: Liebherr {model_naziv} (Vlasnik: {vlasnik_stroja.naziv})")
|
||||
except Exception as e:
|
||||
self.stderr.write(f" ❌ Greška kod S/N {serijski}: {e}")
|
||||
continue
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'\nGotovo! Uspješno dodano {created_count} Liebherr strojeva u bazu.'))
|
||||
86
001.BACKEND/fleet/management/commands/populate_vozila.py
Normal file
86
001.BACKEND/fleet/management/commands/populate_vozila.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import random
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
from faker import Faker
|
||||
from fleet.models import Vozilo
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Popunjava bazu podataka s gospodarskim vozilima sinkronizirano s Astro logistikom'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'total',
|
||||
type=int,
|
||||
help='Broj vozila koje želiš kreirati',
|
||||
default=5
|
||||
)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
total = kwargs['total']
|
||||
fake = Faker(['hr_HR'])
|
||||
|
||||
# Modeli koji odgovaraju tvom voznom parku za mobilne servisne timove
|
||||
modeli_podaci = [
|
||||
'VW Transporter T6',
|
||||
'Mercedes-Benz Vito',
|
||||
'Citroen Jumper',
|
||||
'Ford Transit Custom',
|
||||
'Renault Kangoo Maxi',
|
||||
'Peugeot Boxer',
|
||||
'Opel Vivaro',
|
||||
]
|
||||
|
||||
self.stdout.write(self.style.MIGRATE_HEADING(f'🚀 Kreiram {total} servisnih vozila u flotu...'))
|
||||
|
||||
brojac = 0
|
||||
maksimalni_pokusaji = total * 5
|
||||
pokusaji = 0
|
||||
|
||||
while brojac < total and pokusaji < maksimalni_pokusaji:
|
||||
pokusaji += 1
|
||||
model_naziv = random.choice(modeli_podaci)
|
||||
|
||||
# Generiranje realistične registracije u formatu: ZG 1234-AB
|
||||
gradovi = ['ZG', 'KR', 'KA', 'ST', 'RI', 'OS', 'VZ', 'PU']
|
||||
grad = random.choice(gradovi)
|
||||
|
||||
reg_broj = fake.numerify(text=random.choice(['###', '####']))
|
||||
reg_slova = fake.bothify(text=random.choice(['?', '??']), letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||
registracija = f"{grad} {reg_broj}-{reg_slova}"
|
||||
|
||||
# Kilometraža: Trenutni kilometri moraju pratiti povijest korištenja vozila
|
||||
pocetni = random.randint(10000, 220000)
|
||||
dodatni = random.randint(1500, 45000)
|
||||
trenutni = pocetni + dodatni
|
||||
|
||||
# POPRAVLJENO: Koristimo mala slova sukladno tvojim Choice ograničenjima u modelu
|
||||
status_izbor = random.choice(['aktivan', 'aktivan', 'servis', 'neaktivan'])
|
||||
|
||||
try:
|
||||
# Provjeravamo postoji li već ova registracija u bazi unaprijed
|
||||
if Vozilo.objects.filter(registracija=registracija).exists():
|
||||
continue
|
||||
|
||||
vozilo = Vozilo.objects.create(
|
||||
naziv=model_naziv,
|
||||
registracija=registracija,
|
||||
pocetni_kilometri=pocetni,
|
||||
trenutni_kilometri=trenutni,
|
||||
status=status_izbor
|
||||
)
|
||||
brojac += 1
|
||||
self.stdout.write(f" ✅ Kreirano: {vozilo.naziv} [{vozilo.registracija}] | Status: {status_izbor} ({brojac}/{total})")
|
||||
|
||||
except IntegrityError:
|
||||
# Ako baza ipak prijavi duplikat registracije unutar transakcije, samo idemo dalje
|
||||
continue
|
||||
except ValidationError as ve:
|
||||
# Ako se pojavi neka druga validacijska greška, ispiši je da znamo o čemu se radi
|
||||
self.stderr.write(f" ⚠️ Validacijska greška za {model_naziv}: {ve}")
|
||||
continue
|
||||
except Exception as e:
|
||||
self.stderr.write(f" ❌ Kritična greška pri upisu vozila: {e}")
|
||||
break
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'\nGotovo! Uspješno dodano {brojac} servisnih vozila u bazu flote.'))
|
||||
53
001.BACKEND/fleet/migrations/0001_initial.py
Normal file
53
001.BACKEND/fleet/migrations/0001_initial.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# Generated by Django 6.0.5 on 2026-05-22 03:53
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('kupci', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Vozilo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('naziv', models.CharField(max_length=50, verbose_name='Interni naziv')),
|
||||
('registracija', models.CharField(db_index=True, max_length=15, unique=True)),
|
||||
('pocetni_kilometri', models.PositiveIntegerField(default=0)),
|
||||
('trenutni_kilometri', models.PositiveIntegerField(default=0)),
|
||||
('status', models.CharField(choices=[('aktivan', 'Aktivan'), ('servis', 'Na Servisu'), ('neaktivan', 'Izvan Pogona')], default='aktivan', max_length=20)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Vozilo',
|
||||
'verbose_name_plural': 'Vozila',
|
||||
'ordering': ['naziv'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Stroj',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('naziv', models.CharField(help_text='Npr. Liebherr LTM 1030', max_length=100)),
|
||||
('serijski_broj', models.CharField(max_length=50, unique=True)),
|
||||
('marka', models.CharField(blank=True, max_length=50)),
|
||||
('model_stroja', models.CharField(blank=True, max_length=50)),
|
||||
('godina_proizvodnje', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('tip', models.CharField(choices=[('dizalica_toranj', 'Toranjska dizalica'), ('dizalica_auto', 'Autodizalica'), ('vilicar', 'Viličar'), ('platforma', 'Radna platforma')], max_length=30)),
|
||||
('radni_sati', models.DecimalField(decimal_places=2, default=0, max_digits=12)),
|
||||
('registracija', models.CharField(blank=True, help_text='Za autodizalice i vozila', max_length=20, null=True)),
|
||||
('datum_zadnjeg_atesta', models.DateField(blank=True, null=True)),
|
||||
('vlasnik', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='strojevi', to='kupci.kupac', verbose_name='Vlasnik/Kupac')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Stroj',
|
||||
'verbose_name_plural': 'Strojevi',
|
||||
'ordering': ['marka', 'model_stroja'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
001.BACKEND/fleet/migrations/__init__.py
Normal file
0
001.BACKEND/fleet/migrations/__init__.py
Normal file
88
001.BACKEND/fleet/models.py
Normal file
88
001.BACKEND/fleet/models.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
class Vozilo(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('aktivan', 'Aktivan'),
|
||||
('servis', 'Na Servisu'),
|
||||
('neaktivan', 'Izvan Pogona'),
|
||||
]
|
||||
|
||||
naziv = models.CharField(max_length=50, verbose_name="Interni naziv")
|
||||
registracija = models.CharField(max_length=15, unique=True, db_index=True)
|
||||
|
||||
# Kilometri
|
||||
pocetni_kilometri = models.PositiveIntegerField(default=0)
|
||||
trenutni_kilometri = models.PositiveIntegerField(default=0)
|
||||
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='aktivan'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Vozilo"
|
||||
verbose_name_plural = "Vozila"
|
||||
ordering = ['naziv']
|
||||
|
||||
def clean(self):
|
||||
# Osnovna logika: trenutni kilometri ne smiju biti manji od početnih
|
||||
if self.trenutni_kilometri < self.pocetni_kilometri:
|
||||
raise ValidationError({
|
||||
'trenutni_kilometri': "Trenutni kilometri ne mogu biti manji od početnih."
|
||||
})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Logika za nove zapise
|
||||
if not self.pk:
|
||||
self.trenutni_kilometri = self.pocetni_kilometri
|
||||
|
||||
self.registracija = self.registracija.upper().replace(" ", "")
|
||||
|
||||
# Pozivamo clean() prije spremanja (Django to ne radi automatski u save())
|
||||
self.full_clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.naziv} ({self.registracija})"
|
||||
|
||||
class Stroj(models.Model):
|
||||
TIP_STROJA = [
|
||||
('dizalica_toranj', 'Toranjska dizalica'),
|
||||
('dizalica_auto', 'Autodizalica'),
|
||||
('vilicar', 'Viličar'),
|
||||
('platforma', 'Radna platforma'),
|
||||
]
|
||||
|
||||
# Poveznica s kupcem: Jedan kupac može imati mnogo strojeva
|
||||
# --- RJEŠENJE ZA LOOP: Koristimo string 'kupci.Kupac' ---
|
||||
vlasnik = models.ForeignKey(
|
||||
'kupci.Kupac',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='strojevi',
|
||||
verbose_name="Vlasnik/Kupac"
|
||||
)
|
||||
|
||||
naziv = models.CharField(max_length=100, help_text="Npr. Liebherr LTM 1030")
|
||||
serijski_broj = models.CharField(max_length=50, unique=True)
|
||||
marka = models.CharField(max_length=50, blank=True)
|
||||
model_stroja = models.CharField(max_length=50, blank=True)
|
||||
godina_proizvodnje = models.PositiveIntegerField(null=True, blank=True)
|
||||
tip = models.CharField(max_length=30, choices=TIP_STROJA)
|
||||
|
||||
# Podaci za održavanje
|
||||
radni_sati = models.DecimalField(max_digits=12, decimal_places=2, default=0)
|
||||
registracija = models.CharField(max_length=20, blank=True, null=True, help_text="Za autodizalice i vozila")
|
||||
datum_zadnjeg_atesta = models.DateField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Stroj"
|
||||
verbose_name_plural = "Strojevi"
|
||||
ordering = ['marka', 'model_stroja']
|
||||
|
||||
def __str__(self):
|
||||
# Mala nadogradnja da ne vraća prazan string ako marka/model nedostaju
|
||||
oznaka = f"{self.marka} {self.model_stroja}".strip() or self.naziv
|
||||
return f"{oznaka} (S/N: {self.serijski_broj})"
|
||||
115
001.BACKEND/fleet/serializers.py
Normal file
115
001.BACKEND/fleet/serializers.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Stroj, Vozilo
|
||||
|
||||
|
||||
class StrojShortSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Kraća verzija serijalizatora za brze liste ili dropdown izbornike.
|
||||
"""
|
||||
class Meta:
|
||||
model = Stroj
|
||||
fields = ['id', 'naziv', 'serijski_broj', 'registracija']
|
||||
|
||||
class StrojSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Glavni serializer za strojeve/dizalice.
|
||||
"""
|
||||
# Prikazuje naziv vlasnika (kupca) umjesto samo ID-a
|
||||
# (source='vlasnik.naziv' jer je u modelu ForeignKey nazvan 'vlasnik')
|
||||
vlasnik_naziv = serializers.ReadOnlyField(source='vlasnik.naziv')
|
||||
|
||||
# get_tip_display() pretvara 'dizalica_auto' u 'Autodizalica' (čitljivije za UI)
|
||||
tip_human_readable = serializers.CharField(source='get_tip_display', read_only=True)
|
||||
|
||||
radni_nalozi = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Stroj
|
||||
fields = [
|
||||
'id',
|
||||
'vlasnik',
|
||||
'vlasnik_naziv',
|
||||
'naziv',
|
||||
'marka',
|
||||
'model_stroja',
|
||||
'serijski_broj',
|
||||
'tip',
|
||||
'tip_human_readable',
|
||||
'godina_proizvodnje',
|
||||
'radni_sati',
|
||||
'registracija',
|
||||
'datum_zadnjeg_atesta',
|
||||
'radni_nalozi', # Novo polje
|
||||
]
|
||||
|
||||
def get_radni_nalozi(self, obj):
|
||||
# Lokalni import rješava kružni import error s operations aplikacijom
|
||||
from operations.serializers import RadniNalogSerializer
|
||||
|
||||
# Dohvaćamo radne naloge i sortiramo ih tako da najnoviji budu prvi
|
||||
queryset = obj.radni_nalozi.all().order_by('-datum_kreiranja')
|
||||
|
||||
# Vraćamo serijalizirane podatke
|
||||
return RadniNalogSerializer(queryset, many=True, context=self.context).data
|
||||
|
||||
class VoziloSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Osnovni serializer koji se koristi za Create/Update operacije.
|
||||
"""
|
||||
class Meta:
|
||||
model = Vozilo
|
||||
fields = '__all__'
|
||||
|
||||
class VoziloListaSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer za brzi prikaz u tablicama.
|
||||
"""
|
||||
# Ljudski čitljiv status (npr. 'Na Servisu' umjesto 'servis')
|
||||
status_prikaz = serializers.CharField(source='get_status_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Vozilo
|
||||
fields = ['id', 'naziv', 'registracija', 'trenutni_kilometri', 'status', 'status_prikaz']
|
||||
|
||||
class VoziloListaSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer za brzi prikaz u tablicama.
|
||||
"""
|
||||
# Ljudski čitljiv status (npr. 'Na Servisu' umjesto 'servis')
|
||||
status_prikaz = serializers.CharField(source='get_status_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Vozilo
|
||||
fields = ['id', 'naziv', 'registracija', 'trenutni_kilometri', 'status', 'status_prikaz']
|
||||
|
||||
class VoziloDetaljiSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer s dodatnom statistikom za detaljni prikaz vozila.
|
||||
"""
|
||||
ukupno_predeno = serializers.SerializerMethodField()
|
||||
status_prikaz = serializers.CharField(source='get_status_display', read_only=True)
|
||||
|
||||
putni_nalozi = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Vozilo
|
||||
fields = [
|
||||
'id',
|
||||
'naziv',
|
||||
'registracija',
|
||||
'pocetni_kilometri',
|
||||
'trenutni_kilometri',
|
||||
'ukupno_predeno',
|
||||
'status',
|
||||
'status_prikaz',
|
||||
'putni_nalozi',
|
||||
]
|
||||
|
||||
def get_ukupno_predeno(self, obj):
|
||||
"""Izračunava razliku između trenutnih i početnih kilometara"""
|
||||
return obj.trenutni_kilometri - obj.pocetni_kilometri
|
||||
|
||||
def get_putni_nalozi(self, obj):
|
||||
from operations.serializers import PutniNalogSerializer
|
||||
queryset = obj.putni_nalozi.all()
|
||||
return PutniNalogSerializer(queryset, many=True, context=self.context).data
|
||||
3
001.BACKEND/fleet/tests.py
Normal file
3
001.BACKEND/fleet/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
11
001.BACKEND/fleet/urls.py
Normal file
11
001.BACKEND/fleet/urls.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import StrojViewSet, VoziloViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'strojevi', StrojViewSet, basename='stroj')
|
||||
router.register(r'vozila', VoziloViewSet, basename='vozilo')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
72
001.BACKEND/fleet/views.py
Normal file
72
001.BACKEND/fleet/views.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from rest_framework import viewsets, filters
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .models import Stroj, Vozilo
|
||||
from .serializers import (
|
||||
VoziloSerializer,
|
||||
VoziloListaSerializer,
|
||||
VoziloDetaljiSerializer,
|
||||
StrojSerializer
|
||||
)
|
||||
|
||||
class StrojViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
ViewSet za pregled, kreiranje i uređivanje strojeva i dizalica.
|
||||
"""
|
||||
# select_related radi SQL JOIN i povlači podatke o kupcu odmah
|
||||
queryset = Stroj.objects.all().select_related('vlasnik').prefetch_related('radni_nalozi')
|
||||
serializer_class = StrojSerializer
|
||||
|
||||
# Dodajemo mogućnosti filtriranja, pretrage i sortiranja
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter
|
||||
]
|
||||
|
||||
# Omogućuje filtriranje strojeva po vlasniku ili tipu
|
||||
# npr. /api/strojevi/?vlasnik=1 ili /api/strojevi/?tip=dizalica_auto
|
||||
filterset_fields = ['vlasnik', 'tip']
|
||||
|
||||
# Omogućuje pretragu po nazivu, marki ili serijskom broju
|
||||
search_fields = ['naziv', 'marka', 'serijski_broj', 'registracija']
|
||||
|
||||
# Omogućuje sortiranje po radnim satima ili godini proizvodnje
|
||||
ordering_fields = ['radni_sati', 'godina_proizvodnje', 'datum_zadnjeg_atesta']
|
||||
ordering = ['-datum_zadnjeg_atesta'] # Defaultno prikazuje one koji su zadnji atestirani
|
||||
|
||||
class VoziloViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
ViewSet za pregled, kreiranje i uređivanje voznog parka.
|
||||
Usklađen sa StrojViewSet arhitekturom.
|
||||
"""
|
||||
queryset = Vozilo.objects.all()
|
||||
|
||||
# Postavljanje filter backenda (isto kao u StrojViewSet)
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter
|
||||
]
|
||||
|
||||
# Omogućuje filtriranje vozila po statusu
|
||||
# npr. /api/fleet/vozila/?status=u_radu
|
||||
filterset_fields = ['status']
|
||||
|
||||
# Omogućuje pretragu po nazivu i registraciji
|
||||
search_fields = ['naziv', 'registracija']
|
||||
|
||||
# Omogućuje sortiranje po kilometrima ili statusu
|
||||
ordering_fields = ['trenutni_kilometri', 'status']
|
||||
ordering = ['naziv'] # Defaultno sortiranje po abecedi naziva
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""
|
||||
Dinamički odabir serializera za optimalne performanse.
|
||||
"""
|
||||
if self.action == 'list':
|
||||
return VoziloListaSerializer
|
||||
if self.action == 'retrieve':
|
||||
return VoziloDetaljiSerializer
|
||||
|
||||
# Default za POST/PUT/PATCH
|
||||
return VoziloSerializer
|
||||
Reference in New Issue
Block a user