Taula de continguts

Django Web Framework

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:

, , , , , , , , , , , , , ,


Articles sobre Django en aquesta wiki

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


Introducció per a DevOps

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:

  1. Configurar un virtualenv amb les llibreries adequades (requirements.txt).
  2. Crear el projecte amb les versions adequades i actualitzades.
  3. Tenir el projecte sota un VCS (sistema de versionat) com Git.
  4. Configurar les dades sensibles del projecte (contrasenyes, tokens, etc.) perquè no es publiquin al repositori de codi.
  5. Habilitar configuració de l'app mitjançant variables d'entorn, enlloc de fitxers.
  6. Fer un README per facilitar la clonació i posta en marxa del projecte.
  7. Configurar la BD adequadament.
  8. Configurar el servidor d'arxius estàtics.
  9. Posar el projecte en producció amb un servidor d'aplicacions (uWSGI) + un servidor web (Nginx) per als arxius estàtics.


Entorn Python Venv

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:

$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install django


Quickstart: crear projecte mínim

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/


ORM, models i migracions

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:

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.


Crear repositori Git

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.

Configurar .gitignore

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:

Una versió mínima dels arxius i carpetes que NO volem que es pugin al repo seria aquest:

.gitignore
*.pyc
/env/
/venv/
/static/*
/media/*
.env
db.sqlite3
.coverage
__pycache__

Pujar els arxius

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


requirements.txt

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.


Versions

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==3.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:

Ara 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


Variables d'entorn amb django-environ

É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:

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:

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:

settings.py
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
.env
ALLOWED_HOSTS=localhost,127.0.0.1,mysite.com


Utilitzant MySQL

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


Django en producció

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ó.

Servidor d'aplicacions uWSGI

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.


Arxius estàtics

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.

Configurar Nginx per a servir arxius estàtics

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):

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.

Engegant Django app amb Supervisor

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.

/etc/supervisord/conf.d/polls.conf
[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


Més Django

Pots continuar a l'article Django Docker per dockeritzar la nostra webapp.

Altres parts de Django interessants: