Django: model Logo, baza podataka i admin panel

Lekcija 5 od 9 · Python web: Django projekat

Da bi se logoi čuvali, Django mora da zna kako izgleda jedan logo: naziv, slogan, boja, oblik, font i ko je vlasnik. To opisujemo modelom – Python klasom iz koje Django sam pravi tabelu u bazi. Zatim ga uključujemo u admin panel i isprobavamo upite iz Python-a.

Pre nego što počneš: potrebno predznanje – URL-ovi i šabloni, funkcije u Python-u.
Kako da učiš: radi u svom projektu korak po korak i posle svake lekcije proveri da li server radi; zadatak na kraju prvo uradi sam/a. Kod je za Django 5.2 (LTS) i Python 3.12+; svaki deo upiši u fajl naveden u komentaru na vrhu primera.

Model Logo

# logoi/models.py
from django.conf import settings
from django.core.validators import RegexValidator
from django.db import models


class Logo(models.Model):
    OBLICI = [("krug", "krug"), ("kvadrat", "kvadrat"), ("romb", "romb")]
    FONTOVI = [
        ("Arial, sans-serif", "Arial"),
        ("Georgia, serif", "Georgia"),
        ("'Courier New', monospace", "Courier New"),
    ]

    vlasnik = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="logoi")
    naziv = models.CharField(max_length=20)
    slogan = models.CharField(max_length=30, blank=True)
    boja = models.CharField(max_length=7, default="#dd0022",
                            validators=[RegexValidator(r"^#[0-9a-fA-F]{6}$", "Boja mora biti oblika #RRGGBB.")])
    oblik = models.CharField(max_length=10, choices=OBLICI, default="krug")
    font = models.CharField(max_length=40, choices=FONTOVI, default="Arial, sans-serif")
    napravljen = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-napravljen"]          # najnoviji prvi

    def __str__(self):
        return f"{self.naziv} ({self.vlasnik})"

    def inicijali(self):
        """'Sekcija Pupin' -> 'SP', 'Logo' -> 'LO' (isto kao u generator.js)."""
        reci = self.naziv.split()
        if not reci:
            return "?"
        if len(reci) == 1:
            return reci[0][:2].upper()
        return (reci[0][0] + reci[1][0]).upper()
PoljeTipZnačenje
vlasnikForeignKeyveza ka korisniku; on_delete=CASCADE – brisanjem korisnika brišu se i njegovi logoi; related_name="logoi" omogućava korisnik.logoi.all()
naziv, sloganCharFieldtekst ograničene dužine; blank=True – može biti prazno
bojaCharField + validatordozvoljen samo zapis #RRGGBB
oblik, fontCharField + choicessamo vrednosti sa spiska – u formi postaje padajuća lista
napravljenDateTimeField(auto_now_add=True)vreme se upiše samo pri pravljenju

Svaki model dobija i polje id (redni broj, primarni ključ) automatski. Vrednosti u choices i polja su ista kao u generatoru iz frontend projekta.

Migracije: od klase do tabele

python manage.py makemigrations
python manage.py migrate

makemigrations uporedi modele sa stanjem baze i zapiše izmenu u logoi/migrations/0001_initial.py; migrate je primeni na bazu. Kada kasnije promeniš model (npr. dodaš polje), ponoviš obe komande. SQL ne pišeš ručno – ali ga možeš videti: python manage.py sqlmigrate logoi 0001.

Model u admin panelu

# logoi/admin.py
from django.contrib import admin
from .models import Logo


@admin.register(Logo)
class LogoAdmin(admin.ModelAdmin):
    list_display = ("naziv", "vlasnik", "oblik", "boja", "napravljen")
    list_filter = ("oblik", "napravljen")
    search_fields = ("naziv", "slogan", "vlasnik__username")

Posle ovoga u /admin/ postoji tabela Logos sa kolonama, filterom po obliku i datumu i pretragom. Dodaj nekoliko logoa ručno – trebaće za sledeće korake.

Ime u adminu se lepše prikazuje ako u class Meta dodaš verbose_name = "logo" i verbose_name_plural = "logoi".

Upiti iz Python-a (ORM)

python manage.py shell
>>> from django.contrib.auth.models import User
>>> from logoi.models import Logo
>>> ana = User.objects.get(username="ana")
>>> Logo.objects.create(vlasnik=ana, naziv="Sekcija Pupin", boja="#1f5f91", oblik="romb")
<Logo: Sekcija Pupin (ana)>
>>> Logo.objects.count()
1
>>> ana.logoi.all()                                  # svi logoi korisnice ana
<QuerySet [<Logo: Sekcija Pupin (ana)>]>
>>> Logo.objects.filter(oblik="krug").count()       # WHERE oblik = 'krug'
0
>>> Logo.objects.filter(naziv__icontains="pupin")  # sadrži "pupin", bez obzira na velika slova
<QuerySet [<Logo: Sekcija Pupin (ana)>]>
>>> l = Logo.objects.first(); l.inicijali()
'SP'
>>> l.slogan = "Programiramo"; l.save()             # izmena
>>> l.delete()                                      # brisanje
(1, {'logoi.Logo': 1})
ORMSQL (otprilike)
Logo.objects.all()SELECT * FROM logoi_logo
.filter(oblik="krug")WHERE oblik = 'krug'
.order_by("-napravljen")ORDER BY napravljen DESC
.create(...) / .save()INSERT / UPDATE
.delete()DELETE

ORM (Object-Relational Mapping) pretvara Python izraze u SQL i uvek koristi parametre – zato je otporan na SQL injekciju iz lekcije Bezbednost.

Video: baza u Django aplikaciji

Napomena uz video: video je snimljen za prvu verziju projekta (starija verzija Django-a, Heroku). Ideje su iste, ali se kod iz ove lekcije razlikuje u detaljima – kada se razlikuju, važi kod sa ove strane.

U videu se baza odmah prebacuje na PostgreSQL instaliran na računaru. U ovoj verziji projekta za razvoj ostajemo na SQLite-u, a PostgreSQL koristimo tek na serveru.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje.

ZADATAK 01Težina: srednje

Polje „omiljeni“

Dodaj modelu polje omiljeni (da/ne, podrazumevano ne), napravi i primeni migraciju i prikaži polje u admin listi, sa mogućnošću menjanja direktno u listi.

Česte greške i saveti
  • Posle svake izmene modela: makemigrations pa migrate. Greška no such column znači da je migracija zaboravljena.
  • Ne menjaj tabele ručno u bazi – sve izmene idu kroz modele i migracije.
  • Logo.objects.get(...) baca grešku ako nema rezultata ili ih ima više; za „možda nema“ koristi filter(...).first().

Proveri znanje

Otvori kviz

Modeli i baza

1. Šta radi python manage.py makemigrations?




2. Šta znači related_name="logoi" u ForeignKey?



3. Zašto je ORM otporan na SQL injekciju?