Sari la conținut
Atestia
Înapoi la proiecte

Proiectul de atestat

Proiect de atestat în Python: aplicație cu interfață și bază de date, fără nimic de instalat

Cum faci un proiect de atestat la informatică în Python: interfață cu Tkinter, bază de date SQLite cu chei străine reale, interogări parametrizate, executabil pentru calculatorul comisiei, planul pe luni și întrebările de la susținere.

12

minute de citit

14

secțiuni

8

întrebări frecvente

Actualizat pe , pe baza metodologiei oficiale.

Pe această pagină
  1. Pentru cine e potrivit
  2. Capcana numărul unu: un script în consolă nu e o aplicație
  3. Schema: trei tabele, scrise în cod
  4. Capcana pe care nu o vezi: cheile străine sunt oprite implicit
  5. Interfața: o fereastră care citește și scrie în bază
  6. Interogarea care se notează
  7. Datele utilizatorului nu intră niciodată direct în SQL
  8. Cum ajunge aplicația pe calculatorul comisiei
  9. Planul pe luni
  10. Ce te întreabă comisia
  11. Greșeli frecvente
  12. Ce livrezi
  13. Întrebări frecvente
  14. De unde vin informațiile de aici

Python e o alegere bună pentru proiect dintr-un motiv practic: tot ce îți trebuie pentru o aplicație completă — fereastră, butoane, tabel, bază de date — vine în biblioteca standard. Tkinter pentru interfață, sqlite3 pentru date. Nu instalezi niciun pachet și nu pornești niciun server.

Pagina asta e despre o aplicație desktop scrisă în cod: o fereastră Tkinter peste o bază SQLite cu cel puțin trei tabele legate. Dacă vrei o bază de date construită vizual, din ferestre, e proiectul în Access. Dacă vrei interfața desenată cu mouse-ul, într-un designer, e proiectul în C# cu Windows Forms. Dacă vrei o aplicație care rulează în browser, e proiectul în PHP și MySQL.

Dacă încă alegi între tehnologii, pornește de la cum îți alegi tema de proiect. Cadrul general al examenului e în ghidul atestatului.

Pentru cine e potrivit

Un proiect în Python merge dacă:

  • ai scris deja câteva programe în Python și sintaxa nu te mai încurcă — anul de atestat nu e momentul în care înveți un limbaj de la zero;
  • tema are date care se leagă — clienți și mașini, elevi și note, produse și comenzi;
  • vrei să scrii tot codul tu, inclusiv interfața, și să-l poți explica rând cu rând.

Nu merge dacă profesorul îndrumător cere explicit altă tehnologie sau dacă tema e în esență un site de prezentare. Pentru a doua variantă e mai potrivit un proiect în HTML, CSS și JavaScript.

Un lucru de știut dinainte: subiectele de programare de la proba practică se rezolvă în C/C++ sau Pascal, deci comisia e obișnuită cu C++. Întrebarea „de ce Python?” e aproape sigură. Un răspuns bun e concret: biblioteca standard îți dă interfață și bază de date fără instalări, iar timpul câștigat a mers în modelul de date. „E mai ușor” nu e un răspuns.

Capcana numărul unu: un script în consolă nu e o aplicație

Cel mai frecvent proiect slab în Python e un program cu input() și print() într-o buclă cu meniu. Funcționează, dar e o problemă de la proba practică, mai lungă — nu un produs software.

Nivel Ce conține Cum e primit
Meniu în consolă, date în liste Se pierde tot la închidere Insuficient
Fereastră Tkinter, date într-un fișier text Interfață, dar fără model de date Minim acceptabil
Fereastră + SQLite cu 3–4 tabele legate, interogări cu JOIN și agregare Aplicație reală Ținta
Plus căutare, validări, editare, export Aplicație închegată Peste așteptări

Ținta e rândul al treilea. Peste el treci doar dacă îți rămâne timp după documentație.

Schema: trei tabele, scrise în cod

Exemplul de pe pagina asta e un service auto: clienții au mașini, mașinile au reparații. Două relații unu la mai mulți, deci două chei străine de justificat.

# baza.py
import sqlite3

SCHEMA = """
CREATE TABLE IF NOT EXISTS clienti(
    id_client INTEGER PRIMARY KEY,
    nume      TEXT NOT NULL,
    telefon   TEXT
);
CREATE TABLE IF NOT EXISTS masini(
    id_masina        INTEGER PRIMARY KEY,
    nr_inmatriculare TEXT NOT NULL UNIQUE,
    marca            TEXT,
    id_client        INTEGER NOT NULL REFERENCES clienti(id_client)
);
CREATE TABLE IF NOT EXISTS reparatii(
    id_reparatie INTEGER PRIMARY KEY,
    id_masina    INTEGER NOT NULL REFERENCES masini(id_masina),
    data         TEXT NOT NULL,
    descriere    TEXT,
    cost         REAL CHECK (cost >= 0)
);
"""

def conecteaza(cale="service.db"):
    con = sqlite3.connect(cale)
    con.execute("PRAGMA foreign_keys = ON")   # implicit e OFF, la fiecare conexiune
    con.executescript(SCHEMA)
    return con

CREATE TABLE IF NOT EXISTS face ca aplicația să-și creeze singură baza la prima pornire. Nu livrezi un fișier gol separat și nu există pasul „rulează întâi scriptul de creare”.

telefon e TEXT, nu INTEGER. Într-o coloană INTEGER, SQLite transformă "0722000111" în numărul 722000111 — zeroul de la început dispare fără nicio eroare. Aceeași capcană ca la subiectul de baze de date de la proba practică.

Capcana pe care nu o vezi: cheile străine sunt oprite implicit

În modulul sqlite3 din Python, cheile străine nu se verifică decât dacă le activezi, la fiecare conexiune, cu PRAGMA foreign_keys = ON. Fără linia aia, REFERENCES din schemă e doar decor:

Ce încerci Fără PRAGMA Cu PRAGMA foreign_keys = ON
O mașină pentru clientul cu id 99, care nu există se salvează FOREIGN KEY constraint failed
Ștergi un client care are mașini se șterge, mașinile rămân orfane FOREIGN KEY constraint failed

Asta e exact întrebarea pe care o pune un evaluator care știe ce caută: „ce se întâmplă dacă șterg un client care are mașini?” Cu linia de mai sus ai un răspuns pe care îl poți demonstra pe loc.

CHECK (cost >= 0) funcționează și fără PRAGMA: o reparație cu cost negativ e refuzată cu CHECK constraint failed. E o validare la nivelul bazei — se apără și dacă interfața greșește.

Interfața: o fereastră care citește și scrie în bază

# aplicatie.py
import tkinter as tk
from tkinter import ttk, messagebox
from baza import conecteaza


class Aplicatie(tk.Tk):
    def __init__(self, con):
        super().__init__()
        self.con = con
        self.title("Service auto — clienți")

        ttk.Label(self, text="Nume").grid(row=0, column=0, padx=6, pady=4, sticky="w")
        self.nume = ttk.Entry(self, width=30)
        self.nume.grid(row=0, column=1, padx=6, pady=4)

        ttk.Label(self, text="Telefon").grid(row=1, column=0, padx=6, pady=4, sticky="w")
        self.telefon = ttk.Entry(self, width=30)
        self.telefon.grid(row=1, column=1, padx=6, pady=4)

        ttk.Button(self, text="Adaugă client", command=self.adauga).grid(
            row=2, column=1, padx=6, pady=4, sticky="e")

        self.tabel = ttk.Treeview(self, columns=("nume", "telefon"), show="headings")
        self.tabel.heading("nume", text="Nume")
        self.tabel.heading("telefon", text="Telefon")
        self.tabel.grid(row=3, column=0, columnspan=2, padx=6, pady=6)

        self.reincarca()

    def adauga(self):
        nume = self.nume.get().strip()
        if not nume:
            messagebox.showwarning("Date lipsă", "Numele clientului e obligatoriu.")
            return
        with self.con:  # commit la final, rollback dacă apare o eroare
            self.con.execute(
                "INSERT INTO clienti(nume, telefon) VALUES (?, ?)",
                (nume, self.telefon.get().strip()),
            )
        self.nume.delete(0, tk.END)
        self.telefon.delete(0, tk.END)
        self.reincarca()

    def reincarca(self):
        self.tabel.delete(*self.tabel.get_children())
        for id_client, nume, telefon in self.con.execute(
            "SELECT id_client, nume, telefon FROM clienti ORDER BY nume"
        ):
            self.tabel.insert("", tk.END, iid=id_client, values=(nume, telefon))


if __name__ == "__main__":
    Aplicatie(conecteaza()).mainloop()

Trei detalii din codul ăsta pe care trebuie să le poți explica:

  • command=self.adauga, fără paranteze. Îi dai butonului funcția, ca s-o apeleze el la click. Cu command=self.adauga(), funcția rulează o singură dată, la construirea ferestrei, iar butonul primește rezultatul ei — adică nimic. Butonul apare, dar nu face nimic la click.
  • with self.con: face commit la ieșirea din bloc. Fără el (sau fără con.commit()), datele apar în tabel cât timp aplicația e deschisă și dispar la închidere. E cel mai neplăcut tip de bug: în timpul lucrului totul pare corect.
  • iid=id_client leagă rândul din tabelul vizual de cheia primară. Când adaugi Șterge sau Editează, self.tabel.selection() îți dă direct id-ul din bază, fără să cauți după nume.

reincarca() recitește tabelul din bază după fiecare modificare. E mai simplu și mai sigur decât să actualizezi manual rândurile din fereastră: ce vezi e întotdeauna ce e în bază.

Interogarea care se notează

Comisia nu se uită la butoane, ci la ce știe aplicația să calculeze. Un raport cu JOIN și GROUP BY e partea tehnică pe care o poți arăta în treizeci de secunde:

SELECT c.nume, COUNT(r.id_reparatie) AS reparatii, SUM(r.cost) AS total
FROM clienti AS c
JOIN masini    AS m ON m.id_client = c.id_client
JOIN reparatii AS r ON r.id_masina = m.id_masina
GROUP BY c.id_client
ORDER BY total DESC;

Pe un client cu două mașini și trei reparații, rezultatul e un singur rând: numele, 3, suma costurilor. Legătura client → reparație trece prin tabelul masini — exact ce justifici când explici schema. Tehnicile sunt aceleași ca la interogările SQL de la proba practică.

Datele utilizatorului nu intră niciodată direct în SQL

Semnul de întrebare din VALUES (?, ?) nu e un detaliu de stil. Comparația, pe aceeași bază cu doi clienți:

nume = "' OR '1'='1"   # ce a scris cineva în câmpul de căutare

con.execute(f"SELECT nume FROM clienti WHERE nume = '{nume}'").fetchall()
# -> ambii clienți: condiția a devenit adevărată pentru toate rândurile

con.execute("SELECT nume FROM clienti WHERE nume = ?", (nume,)).fetchall()
# -> []  textul e căutat ca text, nu executat

Cu f-string, textul introdus devine cod SQL. Cu ?, rămâne o valoare. Se numește SQL injection și e o întrebare bună de pus în documentație înainte s-o pună comisia. Aceeași regulă, pentru aplicații web, e tratată pe larg la PHP și MySQL.

Cum ajunge aplicația pe calculatorul comisiei

Pe calculatorul din sala de examen s-ar putea să nu existe Python. Soluția e un executabil:

pip install pyinstaller
pyinstaller --onefile --windowed --name ServiceAuto aplicatie.py

Rezultatul e dist/ServiceAuto.exe — pentru aplicația de pe pagina asta, un singur fișier de aproximativ 13 MB, care pornește fără Python instalat. --windowed ascunde fereastra neagră de consolă din spatele aplicației.

Baza service.db se creează în folderul din care pornești executabilul, la prima rulare. Pune exe-ul într-un folder al lui, cu baza cu date de test lângă el, și copiază folderul întreg.

Testează pe alt calculator cu cel puțin o săptămână înainte. Pe al tău merge întotdeauna. Adu o copie pe stick, una în cloud, și sursele .py separat — dacă executabilul refuză să pornească, sursele rulează pe orice calculator cu Python.

Planul pe luni

Termenul de depunere e 1 mai, cu referatul profesorului îndrumător. Înapoi de la data aia:

Când Ce faci
Decembrie Alegi tema, o discuți cu profesorul. Entitățile și relațiile pe hârtie
Ianuarie baza.py: schema, cheile străine, 20–30 de înregistrări de test pe tabel
Februarie Fereastra principală: adăugare, listare, ștergere pe primul tabel
Martie Restul tabelelor, căutarea, rapoartele cu JOIN și GROUP BY
Aprilie Executabilul testat pe alt PC, documentația, repetiția prezentării
1 mai Depunere

Separă baza de interfață din prima zi — două fișiere, ca mai sus. Când comisia te întreabă unde e logica de date, arăți un fișier, nu cauți prin 400 de rânduri amestecate.

Ce te întreabă comisia

„De ce Python și nu C++, cum ați lucrat la clasă?” Răspunsul concret: interfața și baza de date sunt în biblioteca standard, deci nu depinzi de nimic instalat în plus. Spune și ce ai făcut cu timpul ăsta.

„Ce face mainloop()?” Pornește bucla de evenimente: fereastra așteaptă click-uri și taste și apelează funcțiile legate de ele. Codul de după mainloop() rulează abia după ce închizi fereastra.

„Ce se întâmplă dacă șterg un client care are mașini?” Cu PRAGMA foreign_keys = ON, baza refuză ștergerea. Arată-le pe loc. Dacă ai ales altceva — de exemplu ON DELETE CASCADE — explică de ce.

„Unde se salvează datele?” În fișierul service.db, prin commit. Arată-le că datele rămân după ce închizi și redeschizi aplicația.

„Arată-mi o interogare mai complicată și explică-mi-o.” Alege dinainte una cu JOIN și GROUP BY, ca cea de mai sus, și repet-o cu voce tare.

„Ce ai face altfel dacă ai lua-o de la capăt?” O limitare reală, spusă concret: lipsa editării, căutarea doar după nume, un singur utilizator.

Cum decurge proba și ce înseamnă motivarea teoretică: prezentarea proiectului în fața comisiei.

Greșeli frecvente

  • Meniu în consolă în loc de aplicație. Un program cu input() într-o buclă e un exercițiu, nu un produs software.
  • Date ținute în liste. Totul dispare la închidere, iar la prezentare pornești de la zero.
  • REFERENCES fără PRAGMA foreign_keys = ON. Schema arată corect, dar baza acceptă orice.
  • commit uitat. Datele există doar până închizi aplicația.
  • command=functie() cu paranteze. Butonul nu face nimic la click.
  • SQL construit cu f-string sau cu +. Prima întrebare despre securitate și n-ai răspuns.
  • Telefon sau CNP ca INTEGER. Zeroul din față dispare.
  • Tot codul într-un singur fișier. Greu de explicat și imposibil de arătat pe bucăți.
  • Executabilul netestat pe alt calculator. Descoperi problema în sala de examen.
  • Bază aproape goală. Trei înregistrări nu produc niciun raport convingător.

Ce livrezi

  1. Aplicația — executabilul, baza cu date de test și sursele .py;
  2. Documentația — schema cu relațiile, justificarea tabelelor, capturi ale ferestrelor și explicația interogărilor principale;
  3. Prezentarea — maximum 30 de minute, cu aplicația rulând.

Pentru partea de SQL, teoria e în baze de date și SQL și în interogări SQL, cu probleme rezolvate din examenele reale.

Întrebări frecvente

Se acceptă Python la proiectul de atestat?

Metodologia nu impune un limbaj pentru proiect — tehnologia o stabilește catedra liceului tău. Întreabă profesorul îndrumător înainte să începi și justifică alegerea în documentație.

Trebuie să instalez ceva în afară de Python?

Pentru aplicație, nu: Tkinter și sqlite3 fac parte din biblioteca standard. Ai nevoie de PyInstaller doar la final, ca să produci executabilul.

Tkinter sau PyQt?

Tkinter e suficient pentru atestat și vine cu Python. PyQt arată mai modern, dar e o instalare în plus și un strat de complexitate pe care trebuie să-l poți explica. Nota vine din modelul de date, nu din aspectul ferestrei.

De ce nu se verifică cheia străină, deși am scris REFERENCES?

În sqlite3 din Python, cheile străine sunt dezactivate implicit. Rulează PRAGMA foreign_keys = ON imediat după fiecare sqlite3.connect().

De ce dispar datele după ce închid aplicația?

Lipsește commit. Pune modificările într-un bloc with con: sau apelează con.commit() după ele.

Pot folosi MySQL în loc de SQLite?

Poți, dar MySQL e un server care trebuie instalat și pornit și pe calculatorul comisiei. SQLite e un singur fișier lângă aplicație. Pentru o aplicație desktop, SQLite e alegerea mai sigură.

Câte tabele trebuie să aibă baza de date?

Nu există un număr impus. Sub două tabele nu ai nicio relație de justificat; trei-patru tabele legate corect sunt suficiente pentru orice temă de liceu.

Se poate face proiectul în echipă?

Da, în echipă de 2–3 elevi, în funcție de complexitate. Împărțirea naturală la Python: unul pe baza.py și rapoarte, altul pe ferestre — fiecare trebuie să poată explica și partea celuilalt.

De unde vin informațiile de aici

Cerința ca proiectul să fie un produs software însoțit de prezentarea și motivarea teoretică, termenul de depunere de 1 mai, referatul profesorului îndrumător și posibilitatea lucrului în echipă de 2–3 elevi sunt din Metodologia de organizare și desfășurare a examenului de atestare a competențelor profesionale ale absolvenților claselor de matematică-informatică și matematică-informatică, intensiv informatică, aprobată prin OMECI nr. 4.843/2009 și modificată prin OM nr. 3.009/2021.

Limbajul, numărul de tabele și nivelul tehnic așteptat nu sunt prevăzute în metodologie — se stabilesc de catedra de specialitate a liceului tău. Codul de pe pagina asta a fost rulat pe Python 3.14 cu Tk 9.0, iar comportamentele descrise (cheile străine oprite implicit, datele pierdute fără commit, zeroul pierdut din telefon, injecția prin f-string, executabilul PyInstaller) au fost verificate pe același cod. Verifică întotdeauna cerința profesorului îndrumător.

Cealaltă jumătate

Despre examen

Proiectul e doar una dintre cele două probe. Cum se desfășoară examenul, ce termene are și ce se cere la documentație și la susținere: