Django és un framework per a aplicacions web en Python.
És conegut com The framework for perfectionists with deadlines per la seva rapidesa de desenvolupament.
En aquest article tractarem els temes de l'entorn d'execució d'un projecte Django, per tal que es pugui posar en producció de forma segura.
Django té una documentació excel·lent, en particular el Tutorial Django.
Referències:
2022/02/17 09:39 | ENRIC MIEZA SANCHEZ | |
2023/04/19 17:22 | ENRIC MIEZA SANCHEZ | |
2021/08/20 10:53 | ENRIC MIEZA SANCHEZ | |
2022/02/07 18:53 | ENRIC MIEZA SANCHEZ | |
2023/06/23 08:47 | David Lozano | |
2023/05/11 16:14 | ENRIC MIEZA SANCHEZ | |
2019/11/11 15:21 | ENRIC MIEZA SANCHEZ | |
2021/11/24 10:54 | ENRIC MIEZA SANCHEZ | |
2021/11/29 21:25 | ENRIC MIEZA SANCHEZ | |
2023/03/23 17:19 | ENRIC MIEZA SANCHEZ | |
2023/06/30 11:35 | Miquel Àngel Amorós | |
2023/06/27 11:11 | MIKEL LOPEZ VILLARROYA | |
2023/06/28 20:31 | EVA BARBEITO ANDRADE | |
2022/02/11 18:07 | ENRIC MIEZA SANCHEZ | |
2023/07/06 07:02 | RAQUEL ALAMAN NAVAS | |
2023/06/24 10:21 | JORDI GUAL PURTI | |
2021/12/28 12:04 | ENRIC MIEZA SANCHEZ |
Desenvolupar un projecte (en Django o en molts altres frameworks) és una tasca força diferent a la de construir la infraestructura correcta per a posar el projecte en producció, ja sigui directament en un servidor propi o de forma dockeritzada en contenidors. Això darrer és el què farem en aquest article, que és la feina més pròpia del DevOps.
Prepar un projecte Django per a què sigui segur implica diverses coses:
Per treballar en Python ens convé una eina d'entorn que ens aïlli les diferents versions de llibreries que farem servir, i no tenir incompatibilitats entre les diferents aplicacions que puc tenir (ni tampoc amb el sistema operatiu, que té les seves pròpies versions de les llibreries).
A l'article Python venv teniu detalls a fons de com utilitzar aquestes eines.
Resumidament, necessitem crear l'entorn virtual, entrar-hi i instal·lar Django en una versió LTS (x.2):
$ python3 -m venv env $ source env/bin/activate (env) $ pip install django==4.2
> python3 -m venv env > env\Scripts\Activate.bat (env) > pip install django==4.2
Si bé el més habitual és tenir el virtualenv
dins la carpeta del projecte, en aquest cas l'hem de tenir a fora forçosament, ja que encara no hem creat l'esquelet.
Crearem un projecte Django i provarem si funciona simplement amb:
(env) $ django-admin startproject myproject (env) $ cd myproject (env) $ ./manage.py runserver 0.0.0.0:8000
I per comprovar-ho accedeix amb el navegador a http://localhost:8000
.
Si volem endinsar-nos a la programació amb Django es recomana seguir l'excel·lent tutorial oficial Django.
Per entrar al admin panel de Django cal fer migració (crea BD) i crear un usuari:
(env) $ ./manage.py migrate (env) $ ./manage.py createsuperuser
Visita el admin panel a http://localhost:8000/admin/
Per seguir amb la creació de models, caldrà crear al menys una aplicació dins del projecte Django:
(env) $ ./manage.py startapp myapp
Donar d'alta la nova app afegint aquestes línies als arxius:
# ... INSTALLED_APPS = [ "myapp.apps.MyappConfig", # ...
I activar els models a l'admin panel a:
from django.contrib import admin from .models import * admin.site.register(MyModel1) admin.site.register(MyModel2) # ...
Cada cop que modifiquem el model de dades del projecte caldrà afegir:
(env) $ ./manage.py makemigrations (env) $ ./manage.py migrate
Un Object-Relational Mapper és una eina que facilita l'accés a dades mapejant les taules i relacions en objectes (classes) del llenguatge de programació. Això permet una abstracció de la base de dades concreta que s'està emprant i no haver d'utilitzar SQL, sinó crides a mètodes dels objectes instanciats.
Un model, en el context d'un ORM, és una class que està mapejada a una taula de la base de dades. Així, per exemple, es pot crear la següent class a l'arxiu models.py
de Django:
from django.db import models class Llibre (models.Model): titol = models.CharField(max_length=100) autor = models.CharField(max_length=200) resum = models.TextField(null=True,blank=True) data_edicio = models.DateField()
Per crear un llibre i guardar-lo a la taula, enlloc d'emprar SQL, amb el ORM faríem:
llibre = Llibre() llibre.titol = "El mag del Kremlin" llibre.autor = "Giuliano da Empoli" import datetime llibre.data_edicio = datetime.datetime(2023, 3, 29) # per persistir l'objecte a la BD cal cridar el mètode save() llibre.save()
En el context dels frameworks de desenvolupament, una eina de migració gestiona les versions i evolució dels esquemes de les bases de dades.
Durant el desenvolupament convé planificar amb anterioritat l'esquema de la base de dades per tal que pateixi el mínim de modificacions. Cada cop que es modifica la base de dades hi ha un gran risc de que parts del codi deixin de funcionar o de que perdem dades en el canvi (migració) d'una versió de l'esquema a la següent. Malgrat tot, preveure totes les situacions es fa impossible i aplicar canvis a l'esquema de la base de dades es fa imprescindible, per molt que es vulgui minimitzar. L'ús d'eines de migració milloren aquesta gestió i permeten una transició ordenada i, sobretot, automatitzada, d'un esquema al següent.
El mecanisme és simple: cal un arxiu de migració que realitzi els canvis a la BD a cada evolució. Cada migració pot implicar un canvi simple com afegir o esborrar una columna fins a grans canvis com afegir multitud de taules i relacions.
Tenim dos exemples complementaris i paradigmàtics en la gestió de les migracions: els dels frameworks Django i Laravel:
makemigrations
l'arxiu de migració es genera automàticament. El framework analitza el codi del model actual i l'anterior, i genera les ordres adequades dins un script de migració.En el projecte que estavem desenvolupant, podem crear models i després realitzar les migracions executant:
(env1) $ ./manage.py makemigrations (env1) $ ./manage.py migrate
Veurem que ara apareix un arxiu db.sqlite3
a l'arrel del projecte on s'hauran creat algunes taules, en particular users
i groups
, que son objectes builtin de Django (venen predefinits tot i que es poden modificar). SQLite és un SGBD senzill per BD locals en un sol arxiu amb propòsits de desenvolupament (no és apta per a desplegament).
Per visualitzar les taules podem crear un superusuari i entrar a l'admin panel:
(env1) $ ./manage.py createsuperuser (env1) $ ./manage.py runserver
Entrem a http://localhost:8000/admin/ amb l'usuari i contrasenya creats i podrem veure, crear i modificar usuaris i grups i les diferents característiques d'aquests.
Anem a Github (o a Gitlab, Bitbucket, etc.) i fem crear un nou repositori. Si ho feu a la web, ella mateix ens donarà la «xuleta» de com inicialitzar el nostre projecte i connectar-lo al repo remot.
No pugeu res al repositori remot fins que hagueu configurat correctament el projecte amb el .gitignore
com es descriu més avall.
En particular, prengueu cura de no fer cap git add .
(afegeix tots els arxius) fins no tenir el .gitignore
a l'arrel.
Teniu articles amb informació extensa sobre el sistema de control de versions i de Git en particular.
Abans d'entrar tots els arxius ens convé crear un arxiu .gitignore
a l'arrel del nostre repositori (si esteu seguint el tutorial Django oficial, seria la carpeta mysite
). Aquest li dirà a la instrucció git add
que ignori els arxius que compleixi amb determinats patrons.
En particular, NO volem que entrin al repo:
virtualenv
es crearà de nou cada cop que clonem un repositori.Una versió mínima dels arxius i carpetes que NO volem que es pugin al repo seria aquest:
*.pyc /env/ /venv/ /static/* /media/* .env db.sqlite3 .coverage __pycache__
Si tens correctament ajustat el .gitignore
, ja pots afegir tots els arxius al repositori local i pujar-los al repo remot:
(env) $ git init (env) $ git branch -m main (env) $ git add . (env) $ git commit -am "primer commit"
Aquí hauries de enllaçar el teu repositori local amb el remot (per ex. de Github):
(env) $ git remote add origin https://github.com/... (env) $ git push -u origin main
Podem afegir llibreries que necessitem al nostre entorn. Ens serà convenient, de cara a clonar i/o portar a producció el projecte, que sapiguem exactament les versions que necessitem. Podem saber les versions que tenim a l'actual venv amb:
(env) $ pip freeze
Hi ha un consens en utilitzar l'arxiu requirements.txt
per a aquest objectiu. Per tant, cada cop que afegim noves dependències, caldrà afegir-les a aquest arxiu, de forma manual, o bé amb l'anterior instrucció.
Afegim una llibreria (necessitarem django-environ
més endavant):
(env) $ pip install django-environ
Veiem que ara tenim la llibreria afegida amb:
(env) $ pip freeze
I ara volquem tota aquesta info a requirements.txt
:
(env) $ pip freeze > requirements.txt
Quan necessitis carregar les llibreries a l'instal·lar un nou repo, ho pots fer així:
(env) $ pip install -r requirements.txt
No oblidis afegir l'arxiu requirements.txt
a git amb git add
i pujar-ho al teu repo remot.
L'elecció de versions del projecte és molt important. Si examinem el contingut de requirements.txt
(generat amb pip freeze
) veurem que ens mostra les versions de tots els paquets que tenim instal·lats en el virtualenv. En el meu cas (Desembre 2021) em surten aquestes:
asgiref==3.4.1 Django==3.2.9 django-environ==0.8.1 mysqlclient==2.1.0 pytz==2021.3 sqlparse==0.4.2
En algun cas es poden trobar problemes per fer funcionar un projecte, si el sistema operatiu és diferent. En aquests casos potser és millor simplificar el requirements.txt
posant els packages imprescindibles i deixar que el propi gestor pip
trobi les dependències. En aquest cas no ho faríem amb el pip freeze
, sinó que crearíem el fitxe a mà:
django==4.2 django-environ mysqlclient
Hem de tenir en compte el cicle de vida de les versions de Django que fem servir. Les versions LTS són les Long Term Service que ens donaran cobertura durant més temps. Podem veure la política de versions a la web de descàrrega de Django:
A data de juny de 2024 utilitzarem la 4.2 (LTS) però caldrà tenir cura d'actualitzar a la següent LTS (5.2) abans de que el suport de l'actual finalitzi.
És important que les dades sensibles com passwords, keys, tokens, etc. no es pugin al VCS.
Si heu pujat el vostre repositori a un Github públic és fàcil que hagueu rebut un email de Gitguardian o del mateix Github (dependabot) dient-vos que la vostra clau de seguretat del projecte Django no és bo que estigui a l'arxiu settings.py
, que és on apareix inicialment.
Quan després configurem una base de dades MySQL haurem de configurar usuari i contrasenya al mateix arxiu settings.py
, amb el què encara rebrem més emails.
Instal·lem django-environ per facilitar que més tard puguem passar aquestes dades sensibles a l'aplicació mitjançant les variables d'entorn, i així no posar-les en arxius. En producció es passaran aquests valors com a variables d'entorn (per exemple, en utilitzar Docker).
En development, per facilitar el treball diari, posarem les dades sensibles a l'arxiu .env
. Aquest arxiu carregarà els continguts com a variables d'entorn just abans d'engegar l'aplicació (quan fem ./manage.py runserver
).
Si no ho heu fet abans:
(env) $ pip install django-environ
Retirarem les dades sensibles de settings.py
i les situarem a l'arxiu .env
(que ja em marcat com a exclòs al .gitignore
) i que simula la càrrega de variables d'entorn com quan estiguem en mode de producció.
Mireu la documentació "quickstart" de Django-environ per ajustar el settings.py
, traient la configuració de les variables:
SECRET_KEY
DEBUG
Més endavant ajustarem la info de la base de dades.
Crea l'arxiu .env
amb les variables d'entorn, en aquest cas SECRET_KEY
i també DEBUG
.
Per comprovar que funciona bé, mira de canviar el valor de DEBUG
de True
a False
. Si visitem una URL errònia el missatge que dona hauria de ser ben diferent (amb DEBUG=True
hauria de donar més detalls de l'error).
Un parell de bones pràctiques:
.env
al .gitignore
per tal de no pujar per error l'arxiu de variables d'entorn a producció..env
, que es diugui .env.example
Per fer una llista com ALLOWED_HOSTS
dins el .env
podem seguir aquest fil amb possibles solucions. Potser la més elegant és fer-ho com a list:
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
ALLOWED_HOSTS=localhost,127.0.0.1,mysite.com
Per defecte, Django utilitza la BD SQLite, però ens cal una BD apta per a producció com MySQL o PostgreSQL. A la doc oficial de Django tenim com utilitzar els diversos SGBD més habituals.
Per utilitzar MySQL caldrà instal·lar els binaris necessaris al sistema operatiu. Per a Ubuntu:
$ sudo apt install libmysqlclient-dev python3-dev python3-mysqldb gcc pkgconf
I també els connectors del virtualenv de Python:
(env) $ pip install mysqlclient
Com que estem utilitzant django-environ
(veure secció anterior), caldrà que ajustem settings.py
per a que la info de la BD la prenguem del .env
.
La pàgina quickstart de django-environ explica com configurar la BD amb una DATABASE_URL
, enlloc de configurar paràmetre per paràmetre.
Ajusta la variable de la BD d'aquesta manera:
DATABASE_URL='mysql://usuari:contrasenya@host:port/nom_db'
Per veure si la configuració de la BD funciona, pots fer (si la BD està buida crearà les taules):
(env) $ ./manage.py migrate
Si vols utilitzar SQLite enlloc de MySQL, la variable a utilitzar seria:
DATABASE_URL=sqlite:///db.sqlite3
Ja hem configurat el nostre projecte adientment, ara ens queda portar-lo a producció amb un servidor d'aplicacions. Molts tutorials posen en marxa Django amb el propi servidor de desenvolupament (el ./manage.py runserver) però això no és una bona pràctica. El servidor de desenvolupament no està pensat per gestionar adequadament el multithreading i altres implicacions en producció.
Necessitem un servidor d'aplicacions específic, com cal, per posar Django en producció de forma segura.
WSGI significa «Web Server Gateway Interface» i és una especificació standard per al desplegament d'aplicacions web sobre servidors web com Apache o Nginx. Veurem a continuació alguns servidors que segueixen aquest patró.
uWSGI (doc oficial) funciona en pur Python. Podem seguir el tutorial per Django amb uWSGI i Nginx de la documentació oficial.
Assegura't que tens instal·lades les dependències per compilar packages:
(env) $ sudo apt install gcc build-essential python3-dev
En realitat la seva instal·lació i posta en marxa és molt senzilla:
(env) $ pip install uwsgi (env) $ uwsgi --http :8000 --module mysite.wsgi
Substitueix mysite pel nom del teu projecte (potser és myproject?).
Veuràs que a l'executar Django amb el servidor d'aplicacions de producció uWSGI (o amb qualsevol altre) la ruta /admin
no es veurà correctament. Falten els arxius estàtics: CSS, JavaScript i imatges. Això és normal. Segueix llegint per saber com s'ha d'instal·lar correctament.
Si has posat en marxa el projecte amb un servidor de producció (no amb el servidor de development) veuràs que si intentes accedir al panell d'administració /admin no es veu correctament. Falten els arxius estàtics, imatges i CSS, que cal servir amb un servidor web com Apache o Nginx, que son molt més eficients que forçar a Django a fer-ho.
Per recopilar tots els arxius estàtics caldrà ajustar la variable STATIC_ROOT
i STATIC_URL
a settings.py
:
STATIC_ROOT = 'static/' STATIC_URL = '/static/'
I recopilar els arxius estàtics amb la instrucció:
(env) $ ./manage.py collectstatic
Veurem que a l'arrel del projecte s'haurà creat la carpeta static
amb els arxius estàtics com imatges i fulles d'estil CSS.
Afegeix static/
al .gitignore
.
Un cop recopilats els arxius estàtics a la carpeta static/
ens caldrà servir aquests arxius amb un Nginx, que és força més lleuger que l'Apache.
Django no ha de servir mai arxius estàtics, ja que és menys eficient que el Web Server, que està pensat per a això específicament. El següent diagrama mostra com es durà a terme la posada en producció final:
Segueix la secció de Nginx del tutorial oficial de Django amb uWSGI i Nginx per finalitzar la configuració del servidor web amb connexió a uWSGI.
Fixa't que en aquesta configuració final Nginx servirà el nostre projecte al port 8000 i que uWSGI posarà en marxa la site a través de socket i al port 8001:
(env) $ uwsgi --socket :8001 --module mysite.wsgi
Canvia la configuració per tal que escolti al port 80 i que respongui al domini mysite.polls. Recorda modificar l'arxiu /etc/polls
per tal que el navegador miri localhost
quan anem a mysite.polls.
Comprova que accedint al panell d'administració /admin
es visualitzen els CSS correctament.
Troubleshooting (possibles errades):
mysite.conf
de Nginx. Millor que deixis només activada el mysite i desactivis la default (links simbòlics).sudo journalctl -xeu nginx.service
STATIC_ROOT = BASE_DIR / 'static/
' al settings.css
./manage.py collectstatic
i que no dona errors.Internal Server Error
probablement hagis fet malament la comanda uwsgi
(hauràs posat mysite.wsgi quan en realitat la teva carpeta es diu diferent de mysite).
Si no veus els arxius estàtics perquè et dona un error FORBIDDEN, revisa que els permisos de tot el path fins a les carpetes static
i media
hi pot accedir l'usuari de NGINX (www-data). Ja saps que està prohibidíssim posar permisos 777, busca una solució raonable i segura.
Supervisord
Si has fet l'exercici anterior veuràs que uWSGI només està actiu si l'executes. El què voldrem ara és que la nostra webapp s'executi amb l'arrencada del computador. Una bona eina per aconseguir això és http://supervisord.org
Instal·la i configura http://supervisord.org per tal que a l'arrencar la màquina també funcioni la nostra webapp mysite
.
[program:polls] directory=/home/user/dev/mysite command=/home/user/dev/djenv/bin/uwsgi --socket :8001 --module mysite.wsgi
Cal recarregar supervisord:
$ sudo supervisorctl reload
Comprovem si està en marxa:
$ sudo supervisorctl status
Pots continuar a l'article Django Docker per dockeritzar la nostra webapp.
Altres parts de Django interessants: