Amb Flask vas muntar cada peça de la web de Papyrus a mà: vas triar on desar el catàleg (el JSON de M6), vas escriure la validació del POST camp a camp, i si demà volguessis un panell perquè la Marta editi llibres sense tocar codi, l'hauries de construir. Django és la resposta de l'altra escola: un framework amb bateries incloses que porta de sèrie persistència, panell d'administració, formularis validats, gestió d'usuaris i més. En aquesta lliçó arrencaràs un projecte Django des de zero, entendràs la seva arquitectura MTV i la seva estructura de fitxers (que imposa, no suggereix), redefiniràs el Llibre de M5 com un model amb persistència regalada i acabaràs editant el catàleg des de l'admin — sense haver escrit ni una línia d'HTML. El mateix Papyrus, l'altre camí.
Contingut
- Filosofia: bateries incloses (quan Django, quan Flask)
- Instal·lació i
startproject: neixpapyrus_web - Projecte vs app:
startapp cataleg - L'estructura de fitxers, fitxer a fitxer
- El patró MTV (i el seu cosí MVC)
- Primera vista:
HttpResponseiurls.py - El model
Llibre: de dataclass amodels.Model - Migracions:
makemigrationsimigrate - L'admin de Django: el regal estrella
runserveri el mapa complet
Filosofia: bateries incloses (quan Django, quan Flask)
Flask et dona un nucli mínim i llibertat total; Django et dona un electrodomèstic complet i les seves instruccions. Cap dels dos no és "millor": resolen contextos diferents.
| Criteri | Flask | Django |
|---|---|---|
| Persistència | La tries tu (el nostre JSON de M6) | ORM inclòs: models que es desen sols |
| Panell d'administració | El construeixes tu | Inclòs i automàtic |
| Formularis validats | A mà (el POST de 10-03) | ModelForm inclòs (10-05) |
| Usuaris i sessions | Extensions de tercers | Inclosos |
| Estructura de projecte | La decideixes tu | La imposa Django (i això és bo en equip) |
| Ideal per a | APIs, projectes petits, aprendre peça a peça | Aplicacions completes amb parts estàndard |
| Risc típic | Reinventar rodes | Carregar amb peces que no fas servir |
Regla pràctica: si el teu projecte és sobretot una API o una cosa petita i peculiar, Flask; si és "una aplicació web clàssica" — catàleg, altes, usuaris, administració —, Django t'estalvia setmanes. Papyrus, que és exactament això, és un cas de llibre (mai tan ben dit).
Instal·lació i startproject: neix papyrus_web
En un entorn virtual nou (M1: un venv per projecte):
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install django
django-admin startproject papyrus_web
cd papyrus_webpip install django ha instal·lat també l'ordre django-admin, el generador de projectes. startproject papyrus_web crea una carpeta amb l'esquelet complet — primera diferència cultural amb Flask, on vas començar amb un app.py buit: Django et dona l'estructura feta perquè la seva filosofia és que tots els projectes Django s'assemblin.
Projecte vs app: startapp cataleg
Django distingeix dos conceptes que convé fixar aviat:
- El projecte (
papyrus_web) és el conjunt: configuració global, URLs arrel, el lloc sencer. - Una app és un component funcional amb els seus models, vistes i plantilles: el catàleg, algun dia les reserves, potser els socis. Un projecte conté diverses apps; una app ben feta es podria reutilitzar en un altre projecte.
Creem la primera:
I la registrem al projecte, a papyrus_web/settings.py, perquè Django la tingui en compte:
INSTALLED_APPS = [
"django.contrib.admin", # el panell d'administracio
"django.contrib.auth", # usuaris i permisos
# ... altres bateries incloses ...
"cataleg", # ← la nostra app
]Aquest INSTALLED_APPS és literalment la llista de bateries connectades: fixa't en quantes coses vénen enceses de fàbrica.
L'estructura de fitxers, fitxer a fitxer
Després de les dues ordres, l'arbre és aquest (els fitxers importants, comentats):
papyrus_web/ ← carpeta arrel del projecte
├── manage.py ← la teva navalla suissa: runserver, migrate, startapp...
├── papyrus_web/ ← el "cor" del projecte (configuracio)
│ ├── settings.py ← configuracio global
│ ├── urls.py ← taula d'URLs arrel
│ └── wsgi.py / asgi.py ← punts d'entrada per a servidors (produccio)
└── cataleg/ ← l'app
├── models.py ← les dades: aqui renaixera Llibre
├── views.py ← les vistes (com les de Flask)
├── admin.py ← que s'administra des del panell
├── migrations/ ← historial de canvis dels models
└── tests.py ← Django tambe creu en els tests (M9 somriu)| Fitxer | Responsabilitat | Equivalent Flask |
|---|---|---|
manage.py |
Executar ordres del projecte | el CLI flask |
settings.py |
Configuració: apps, base de dades, idioma, zona horària | el poc que configuraves a app.py |
urls.py |
Mapejar URLs → vistes | els @app.route (aquí sense decorador!) |
views.py |
Rebre petició, retornar resposta | les teves funcions vista |
models.py |
Definir les dades i la seva persistència | el teu models.py + magatzem.py + JSON |
admin.py |
Configurar el panell d'administració | no existeix: l'hauries de fer tu |
Dos ajustos de cortesia a settings.py abans de continuar: LANGUAGE_CODE = "ca" i TIME_ZONE = "Europe/Madrid" — l'admin et parlarà en català.
El patró MTV (i el seu cosí MVC)
Django organitza el codi segons el patró MTV: Model–Template–View. És la separació de responsabilitats que venim practicant des de M3, amb noms propis:
flowchart LR
P["Petició<br/>GET /llibres/"] --> U["urls.py<br/>quina vista atén això?"]
U --> V["View (views.py)<br/>lògica: quines dades calen"]
V --> M["Model (models.py)<br/>les dades: Llibre i la seva persistència"]
M --> V
V --> T["Template<br/>HTML amb forats"]
T --> R["Resposta HTML"]
- Model: les dades i les regles per accedir-hi. A Papyrus-Flask eren la dataclass més
magatzem.pymés el JSON; a Django serà una sola classe. - Template: la presentació — com les plantilles Jinja2 de 10-02, amb una sintaxi gairebé idèntica.
- View: la lògica que connecta totes dues — rep la petició, consulta models, tria plantilla.
Si has sentit parlar d'MVC (Model-View-Controller), el patró clàssic: és el mateix amb les etiquetes mogudes. La "View" de Django fa de Controller de l'MVC, i el Template fa de "View" de l'MVC. Confús d'anomenar, idèntic d'esperit: dades, lògica i presentació, cadascú al seu lloc.
Primera vista: HttpResponse i urls.py
El "hola món" Django, per veure el mecanisme despullat. A cataleg/views.py:
from django.http import HttpResponse
def inici(request):
return HttpResponse("<h1>Papyrus, llibreria de barri</h1>")Dues diferències amb Flask salten a la vista: la funció rep sempre request com a primer paràmetre (a Flask era un objecte global que importaves), i no hi ha decorador de ruta. A Django, les URLs viuen separades de les vistes, a papyrus_web/urls.py:
from django.contrib import admin
from django.urls import path
from cataleg import views
urlpatterns = [
path("admin/", admin.site.urls), # el panell, ja endollat de serie
path("", views.inici), # "" = l'arrel del lloc
]path(ruta, vista) fa el que feia @app.route: apuntar a la taula. La diferència és filosòfica: Django prefereix totes les URLs juntes en un índex en lloc de repartides per decoradors — en projectes grans aquest índex únic s'agraeix. Arrenca i comprova-ho:
http://127.0.0.1:8000 (Django fa servir el port 8000; Flask, el 5000) mostra la teva salutació. runserver inclou recàrrega automàtica, com el mode debug de Flask — i amb la mateixa advertència: és un servidor de desenvolupament, no de producció.
El model Llibre: de dataclass a models.Model
Aquí arriba el moment estrella de la lliçó. Recorda la dataclass de M5:
La seva reencarnació Django, a cataleg/models.py:
from django.db import models
class Llibre(models.Model):
titol = models.CharField(max_length=200)
preu = models.DecimalField(max_digits=6, decimal_places=2)
estoc = models.IntegerField(default=0)
IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05
def preu_final(self, soci=False):
preu = float(self.preu) * (1 + self.IVA_LLIBRES)
if soci:
preu *= 1 - self.DESCOMPTE_SOCI
return round(preu, 2)
def hi_ha_estoc(self):
return self.estoc > 0
def __str__(self):
return self.titolCompara membre a membre:
| Dataclass (M5) | Model Django | Què hi afegeix Django |
|---|---|---|
titol: str |
CharField(max_length=200) |
Longitud màxima, validació |
preu: float |
DecimalField(max_digits=6, decimal_places=2) |
Decimals exactes per a diners (millora!) |
estoc: int = 0 |
IntegerField(default=0) |
Tipus i valor per defecte persistits |
| — | hereta de models.Model |
Desar-se, carregar-se, cercar-se: gratis |
Les mateixes tres dades de sempre, els mateixos mètodes preu_final() i hi_ha_estoc() (Hamlet continuarà costant 9.83 per a socis — els invariants de M9 viatgen amb nosaltres). La novetat és el que no veus: per heretar de models.Model (herència de M5 en acció), cada Llibre sap desar-se amb llibre.save() i consultar-se amb Llibre.objects... (l'espremerem a 10-05). A Flask vas escriure carregar_cataleg i desar_cataleg sobre JSON; això és la persistència que Django et regala. Per sota fa servir una base de dades SQLite — un fitxer db.sqlite3 al teu projecte, sense instal·lar ni configurar res. La teoria de bases de dades SQL queda fora d'aquest curs: avui en tenim prou que el regal funcioni. Un detall professional de propina: DecimalField evita els arrodoniments estranys del float amb diners — Django fins i tot millora la nostra dataclass.
Migracions: makemigrations i migrate
Definir el model encara no crea res: cal dir a la base de dades quina forma tenen les dades. Aquest diàleg són les migracions, en dos passos:
python manage.py makemigrations cataleg
# Migrations for 'cataleg':
# cataleg/migrations/0001_initial.py
# + Create model Llibre
python manage.py migrate
# Applying cataleg.0001_initial... OK (i les d'admin, auth...)Conceptualment:
makemigrationscompara els teusmodels.pyamb l'últim estat conegut i escriu la recepta del canvi (un fitxer Python amigrations/): "crear la taula de Llibre amb aquestes columnes".migrateaplica les receptes pendents a la base de dades real.
És control de versions per a la forma de les teves dades: cada canvi futur del model (afegir un camp autor, per exemple) generarà la seva migració 0002_..., i qualsevol company reprodueix la teva base de dades executant migrate. Veuràs que el primer cop s'apliquen també migracions d'admin i auth: són les taules de les bateries incloses preparant-se.
L'admin de Django: el regal estrella
I ara, la demostració de força. Tres línies a cataleg/admin.py:
Un superusuari per entrar (et demanarà usuari i contrasenya per terminal):
Arrenca runserver, entra a http://127.0.0.1:8000/admin/, identifica't i contempla: un panell d'administració complet — llistat de llibres, formulari d'alta amb validació per tipus, edició, esborrament amb confirmació, cercador — sense haver escrit ni una sola plantilla ni una sola vista. Dona d'alta el catàleg canònic: L'Odissea (12.50, 4), Hamlet (9.95, 6), El Quixot (15.90, 8) i Faust (21.00, 10). La Marta ja pot gestionar l'inventari des del navegador; a Flask, aquest panell t'hauria costat unes quantes tardes. El __str__ que hem definit és el que l'admin mostra als llistats — sense ell veuries «Llibre object (1)».
runserver i el mapa complet
El cicle de treball Django que repetiràs mil vegades, resumit:
python manage.py runserver # desenvolupar i provar
# ... edites models.py ...
python manage.py makemigrations # anotar el canvi de dades
python manage.py migrate # aplicar-loErrors Comuns i Consells
- Oblidar registrar l'app a
INSTALLED_APPS: el símptoma clàssic ésmakemigrationsresponent "No changes detected". Django només mira les apps registrades. - Editar
models.pyi no migrar: en accedir-hi veuràsOperationalError: no such table. Model canviat =makemigrations+migrate, sempre, en aquest ordre. - Confondre
django-adminambmanage.py:django-adminnomés per astartproject; després, tot ambpython manage.py ..., que carrega la configuració del teu projecte. - Fer servir
FloatFieldper a diners: la temptació ve de la dataclass.DecimalFieldexisteix precisament per a imports; acostuma-t'hi des del primer model. - Esborrar
db.sqlite3omigrations/"per arreglar" un error: en aprenentatge t'ho pots permetre, però és dinamita: perds dades i historial. Millor entendre l'error — gairebé sempre és una migració sense aplicar. - Buscar l'
if __name__ == "__main__": a Django no arrenques tu l'aplicació; l'arrencamanage.py. Deixa anar el control: el framework crida el teu codi, no a l'inrevés (això és, literalment, el que defineix un framework).
Exercicis
Exercici 1: el camp autor
El catàleg canònic sempre ha tingut autors (Homer, Shakespeare, Cervantes, Goethe) i el model no els desa. Afegeix autor = models.CharField(max_length=100, default="") al model, genera i aplica la migració, i completa els autors des de l'admin. Ajusta __str__ perquè mostri "Hamlet — Shakespeare".
Exercici 2: una pàgina de socis
Afegeix la vista socis (amb HttpResponse i HTML mínim llistant en Lluís, la Marta i en Pau amb els seus carnets LLUIS-001, MARTA-002 i PAU-003) i connecta-la a la URL /socis/. Comprova-la al navegador.
Exercici 3: preus de soci a la consola de Django
python manage.py shell obre un intèrpret amb el teu projecte carregat. Fes-lo servir per recórrer tots els llibres i imprimir titol: preu_final(soci=True). Verifica contra els valors canònics: 12.35, 9.83, 15.71 i 20.75. (Avançament inevitable: per a "tots els llibres" necessitaràs Llibre.objects.all() — la porta de 10-05.)
Solucions
Exercici 1:
class Llibre(models.Model):
titol = models.CharField(max_length=200)
autor = models.CharField(max_length=100, default="")
# ... la resta igual ...
def __str__(self):
return f"{self.titol} — {self.autor}"El default="" és necessari: la base de dades ja conté llibres i Django necessita saber què posar a la columna nova de les files existents (si no l'hi dones, makemigrations t'ho preguntarà interactivament).
Exercici 2:
# cataleg/views.py
def socis(request):
return HttpResponse(
"<h1>Socis de Papyrus</h1>"
"<ul><li>Lluis — LLUIS-001</li>"
"<li>Marta — MARTA-002</li>"
"<li>Pau — PAU-003</li></ul>"
)# papyrus_web/urls.py
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.inici),
path("socis/", views.socis),
]HTML en cadenes una altra vegada — igual d'incòmode que a Flask. Les plantilles Django que ho arreglen arriben a 10-05.
Exercici 3:
>>> from cataleg.models import Llibre
>>> for llibre in Llibre.objects.all():
... print(f"{llibre.titol}: {llibre.preu_final(soci=True)}")
L'Odissea: 12.35
Hamlet: 9.83
El Quixot: 15.71
Faust: 20.75Els quatre números canònics, ara sortint d'una base de dades en lloc d'un JSON. Llibre.objects.all() és el teu primer QuerySet: la sintaxi completa de consultes t'espera a la lliçó següent.
Conclusió
Django ha ensenyat les seves cartes: una estructura imposada però clara (settings.py configura, urls.py encamina, views.py respon, models.py defineix les dades), el patró MTV separant dades, lògica i presentació, i sobretot les bateries — el model Llibre que heretant de models.Model guanya la persistència que a Flask vas programar a mà sobre JSON, les migracions que versionen la forma de les teves dades, i aquell admin que va convertir la Marta en gestora de l'inventari amb tres línies de codi. La dataclass de M5 i el model Django són el mateix llibre amb coberta diferent: mateixes dades, mateixos preu_final() i hi_ha_estoc(), mateixos 12.35, 9.83, 15.71 i 20.75. Però de moment els visitants només veuen un HttpResponse amb HTML incrustat: res de catàleg navegable, ni cerques, ni altes públiques. A la propera lliçó completarem l'app: consultes amb QuerySets, plantilles amb herència (et sonaran moltíssim), formularis amb ModelForm que validen sols, i la web de Papyrus acabada per segona vegada — mateixa funcionalitat, dos camins, i tu sabent recórrer tots dos.
Curs de Programació en Python
Mòdul 1: Introducció a Python
- Introducció a Python
- Configuració de l'Entorn de Desenvolupament
- Sintaxi de Python i Tipus de Dades Bàsics
- Variables i Constants
- Entrada i Sortida Bàsica
- Entorns Virtuals i Gestió de Paquets
Mòdul 2: Estructures de Control
Mòdul 3: Funcions i Mòduls
- Definició de Funcions
- Arguments de Funció
- Funcions Lambda
- Mòduls i Paquets
- Visió General de la Biblioteca Estàndard
Mòdul 4: Estructures de Dades
Mòdul 5: Programació Orientada a Objectes
Mòdul 6: Gestió de Fitxers
- Lectura i Escriptura de Fitxers
- Treball amb Fitxers CSV
- Gestió de Dades JSON
- Operacions amb Fitxers i Directoris
Mòdul 7: Gestió d'Errors i Excepcions
- Introducció a les Excepcions
- Gestió d'Excepcions
- Llançament d'Excepcions
- Excepcions Personalitzades
- Bones Pràctiques i Registre d'Errors amb logging
Mòdul 8: Temes Avançats
- Anotacions de Tipus (type hints)
- Decoradors
- Generadors
- Gestors de Context
- Concurrència: Fils i Processos
- Asyncio per a la Programació Asíncrona
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb unittest
- Proves amb pytest
- Desenvolupament Guiat per Proves
- Tècniques de Depuració
- Ús de pdb per a la Depuració
Mòdul 10: Desenvolupament Web amb Python
- Introducció al Desenvolupament Web
- Fonaments del Framework Flask
- Construcció d'APIs REST amb Flask
- Introducció a Django
- Construcció d'Aplicacions Web amb Django
Mòdul 11: Ciència de Dades amb Python
- Introducció a la Ciència de Dades
- NumPy per a la Computació Numèrica
- Pandas per a la Manipulació de Dades
- Matplotlib per a la Visualització de Dades
- Introducció a l'Aprenentatge Automàtic amb scikit-learn
