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

View File

@@ -0,0 +1,41 @@
from django.contrib import admin
from .models import Kupac
@admin.register(Kupac)
class KupacAdmin(admin.ModelAdmin):
# Polja koja se vide u glavnoj tablici
list_display = ('naziv', 'oib', 'grad', 'telefon', 'prikaz_broja_strojeva', 'aktivno')
# Brzo uređivanje "aktivno" statusa direktno iz tablice
list_editable = ('aktivno',)
# Filtriranje s desne strane
list_filter = ('tip', 'aktivno', 'grad')
# Pretraga po ključnim poljima
# OBAVEZNO dodaj search_fields kako bi autocomplete radio u fleet i nalozi
search_fields = ('naziv', 'oib', 'email', 'grad')
# Organizacija forme za unos
fieldsets = (
('Osnovni podaci', {
'fields': (('naziv', 'tip'), 'oib')
}),
('Kontakt i lokacija', {
'fields': ('email', 'telefon', 'adresa', ('grad', 'postanski_broj'))
}),
('Dodatno', {
'fields': ('napomena', 'aktivno'),
'classes': ('collapse',)
}),
)
# Čitanje sistemskih podataka bez mogućnosti izmjene
readonly_fields = ('datum_kreiranja',)
def prikaz_broja_strojeva(self, obj):
"""Prikazuje broj strojeva direktno u listi kupaca"""
# Koristimo property koji si već definirao u modelu
return obj.broj_strojeva
prikaz_broja_strojeva.short_description = 'Strojevi'

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class KupciConfig(AppConfig):
name = 'kupci'

View File

View File

@@ -0,0 +1,8 @@
from typing import Any
from django.core.management.base import BaseCommand
class Command(BaseCommand):
def handle(self, *args: Any, **options: Any):
print("hello world")

View File

@@ -0,0 +1,75 @@
import random
from django.core.management.base import BaseCommand
from faker import Faker
from kupci.models import Kupac
class Command(BaseCommand):
help = 'Popunjava bazu podataka s testnim kupcima spremnim za logistički modul'
def add_arguments(self, parser):
parser.add_argument(
'total',
type=int,
help='Broj kupaca koje želiš kreirati',
default=10
)
def handle(self, *args, **kwargs):
total = kwargs['total']
fake = Faker(['hr_HR'])
self.stdout.write(self.style.MIGRATE_HEADING(f'🚀 Kreiram {total} testnih kupaca...'))
# Kontrolirani skup gradova u nominativu za savršen ispis relacija i odredišta u logistici
hr_gradovi = [
{'grad': 'Zagreb', 'pbr': '10000'},
{'grad': 'Karlovac', 'pbr': '47000'},
{'grad': 'Ozalj', 'pbr': '47280'},
{'grad': 'Split', 'pbr': '21000'},
{'grad': 'Rijeka', 'pbr': '51000'},
{'grad': 'Osijek', 'pbr': '31000'},
{'grad': 'Varaždin', 'pbr': '42000'},
{'grad': 'Zadar', 'pbr': '23000'},
{'grad': 'Velika Gorica', 'pbr': '10410'},
{'grad': 'Sisak', 'pbr': '44000'}
]
created_count = 0
for _ in range(total):
tip = random.choice(['pravno', 'fizicko'])
if tip == 'pravno':
naziv = fake.company()
else:
naziv = fake.name()
# Generiramo OIB (točno 11 znamenki u string formatu)
oib = "".join([str(random.randint(0, 9)) for _ in range(11)])
# Odabiremo grad i poštanski broj u paru kako bi podaci imali smisla
lokacija = random.choice(hr_gradovi)
# Generiramo telefonski broj koji je čist i siguran za tel: linkove u Astru
pozivni = random.randint(1, 9)
telefon = f"+385 {pozivni} {random.randint(100, 999)}-{random.randint(1000, 9999)}"
try:
# Upotrebljavamo create, a jedinstvenost OIB-a hvatamo u iznimci
Kupac.objects.create(
naziv=naziv,
oib=oib,
email=fake.email(),
telefon=telefon,
adresa=fake.street_address(),
grad=lokacija['grad'], # Usklađeno: čisti nominativ (npr. 'Karlovac')
postanski_broj=lokacija['pbr'], # Usklađeno: odgovarajući poštanski broj
tip=tip,
napomena="Automatski generiran testni kupac usklađen s modulom operativne logistike.",
aktivno=True
)
created_count += 1
except Exception as e:
# Ako OIB slučajno kolidira (unutar unique=True), preskoči i nastavi generirati sljedećeg
continue
self.stdout.write(self.style.SUCCESS(f'✅ Uspješno dodano {created_count} novih kupaca u bazu!'))

View File

@@ -0,0 +1,36 @@
# Generated by Django 6.0.5 on 2026-05-22 03:52
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Kupac',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('naziv', models.CharField(help_text='Ime i prezime ili puni naziv tvrtke', max_length=255)),
('oib', models.CharField(blank=True, max_length=11, null=True, unique=True, verbose_name='OIB')),
('email', models.EmailField(blank=True, max_length=254)),
('telefon', models.CharField(blank=True, max_length=50)),
('adresa', models.CharField(blank=True, max_length=255)),
('grad', models.CharField(blank=True, max_length=100)),
('postanski_broj', models.CharField(blank=True, max_length=10)),
('tip', models.CharField(choices=[('pravno', 'Pravna osoba (Tvrtka)'), ('fizicko', 'Fizička osoba')], default='pravno', max_length=10)),
('napomena', models.TextField(blank=True, help_text='Interni podaci o kupcu')),
('datum_kreiranja', models.DateTimeField(auto_now_add=True)),
('aktivno', models.BooleanField(default=True)),
],
options={
'verbose_name': 'Kupac',
'verbose_name_plural': 'Kupci',
'ordering': ['naziv'],
},
),
]

View File

View File

@@ -0,0 +1,49 @@
from django.db import models
class Kupac(models.Model):
TIP_KUPCA = [
('pravno', 'Pravna osoba (Tvrtka)'),
('fizicko', 'Fizička osoba'),
]
naziv = models.CharField(
max_length=255,
help_text="Ime i prezime ili puni naziv tvrtke"
)
oib = models.CharField(
max_length=11,
unique=True,
null=True,
blank=True,
verbose_name="OIB"
)
email = models.EmailField(blank=True)
telefon = models.CharField(max_length=50, blank=True)
adresa = models.CharField(max_length=255, blank=True)
grad = models.CharField(max_length=100, blank=True)
postanski_broj = models.CharField(max_length=10, blank=True)
tip = models.CharField(
max_length=10,
choices=TIP_KUPCA,
default='pravno'
)
napomena = models.TextField(blank=True, help_text="Interni podaci o kupcu")
# Sistemski podaci
datum_kreiranja = models.DateTimeField(auto_now_add=True)
aktivno = models.BooleanField(default=True)
class Meta:
verbose_name = "Kupac"
verbose_name_plural = "Kupci"
ordering = ['naziv']
def __str__(self):
return self.naziv
@property
def broj_strojeva(self):
"""Vraća broj strojeva povezanih s ovim kupcem"""
return self.strojevi.count()

View File

@@ -0,0 +1,73 @@
from rest_framework import serializers
from .models import Kupac
from fleet.models import Stroj
from fleet.serializers import StrojSerializer
class KupacSerializer(serializers.ModelSerializer):
"""
Serializer za listu kupaca.
Fokus je na brzini i osnovnim kontaktnim podacima.
"""
# Izračunato polje iz modela (@property broj_strojeva)
broj_strojeva = serializers.IntegerField(read_only=True)
class Meta:
model = Kupac
fields = [
'id',
'naziv',
'grad',
'telefon',
'email',
'tip',
'aktivno',
'broj_strojeva' # Korisno za prikaz u tablici (npr. "5 strojeva")
]
class KupacDetaljiSerializer(serializers.ModelSerializer):
# Ugniježđeni strojevi - prikazujemo puni objekt, ne samo ID
strojevi = StrojSerializer(many=True, read_only=True)
# Dodatna polja koja nisu u bazi, ali su korisna za Astro frontend
ukupan_broj_strojeva = serializers.IntegerField(source='broj_strojeva', read_only=True)
class Meta:
model = Kupac
fields = [
'id',
'naziv',
'oib',
'email',
'telefon',
'adresa',
'grad',
'postanski_broj',
'tip',
'napomena',
'aktivno',
'datum_kreiranja',
'ukupan_broj_strojeva',
'strojevi' # Lista svih strojeva s punim detaljima
]
class KupacListaSerializer(serializers.ModelSerializer):
"""
Optimizirani serializer za prikaz u tablici (listi).
Vraća samo osnovne podatke potrebne za pregled svih klijenata.
"""
# Dohvaćamo property iz modela koji broji strojeve
broj_strojeva = serializers.IntegerField(read_only=True)
class Meta:
model = Kupac
fields = [
'id',
'naziv',
'grad',
'oib',
'telefon',
'tip',
'aktivno',
'broj_strojeva'
]

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

14
001.BACKEND/kupci/urls.py Normal file
View File

@@ -0,0 +1,14 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import KupacViewSet
# Kreiramo router i registriramo naš ViewSet
# 'r' ispred stringa označava raw string (dobra praksa za regex/putanje)
router = DefaultRouter()
router.register(r'svi', KupacViewSet, basename='kupac')
# Definiramo URL obrasce
urlpatterns = [
# Sve rute koje router generira (npr. /api/kupci/ i /api/kupci/{id}/)
path('', include(router.urls)),
]

View File

@@ -0,0 +1,35 @@
from rest_framework import viewsets, filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Kupac
from .serializers import KupacSerializer, KupacDetaljiSerializer, KupacListaSerializer
class KupacViewSet(viewsets.ModelViewSet):
"""
API endpoint koji omogućuje pregled, pretragu i uređivanje kupaca.
"""
# Optimizacija: prefetch_related smanjuje broj upita jer odmah vuče strojeve
queryset = Kupac.objects.all().prefetch_related('strojevi')
serializer_class = KupacSerializer
# Dodajemo filter backende
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter
]
# 1. Točno filtriranje (npr. ?tip=pravno)
filterset_fields = ['tip', 'aktivno', 'grad']
# 2. Pretraga (npr. ?search=gradnja)
# Pretražuje naziv ili OIB
search_fields = ['naziv', 'oib']
# 3. Sortiranje (npr. ?ordering=-datum_kreiranja)
ordering_fields = ['naziv', 'datum_kreiranja']
ordering = ['naziv'] # Defaultno sortiranje
def get_serializer_class(self):
if self.action == 'retrieve': # Ako se gleda pojedinačni kupac
return KupacDetaljiSerializer # Onaj s ugniježđenim strojevima
return KupacListaSerializer # Obični bez strojeva