Comunidad de diseño web y desarrollo en internet online

Python en la web con Django (V): modelo de datos

Un modelo de datos es una descripción de los datos de una base de datos. Es una representación de la estructura de tu base de datos (tablas, campos, relaciones) en clases, en este caso escritas en Python.

¿Cómo no debemos conectarnos a nuestra base de datos?


Un ejemplo de cómo nos conectaríamos a base de datos sin modelo de datos y mostrar el resultado en una vista podría ser:

Código :

from django.shortcuts import render_to_response
import MySQLdb

def book_list(request):
   db = MySQLdb.connect(user='myuser',db='mydb',passwd='mypassword',host='localhost')
   cursor = db.cursor()
   cursor.execute('SELECT NAME FROM BOOKS ORDER BY NAME')
   names = [row[0] for row in cursor.fetchall()]
   db.close()
   return render_to_response('booklist.html',{'names':names})


¿Qué os parece? A mí me parece poco segur. Demasiados pasos para un simple select, demasiadas variables con lo que eso conlleva y 100 cosas más que podría decir.

Ahora veamos el mismo ejemplo usando modelo de datos:

Código :

from django.shortcuts import render_ro_response
from mysite.books.models import Book

def book_list(request):
   books = Book.objetcts.order_by('name')
   return render_to_response('booklist.html',{'books':books})


Mucho mejor así ¿verdad? Empecemos.

Configurando la base de datos


Tendremos que tener ya el servidor de base de datos activo y una base de datos creada (la mía se llama foroTutorial).
¿Dónde pensáis que debemos configurar la base de datos para Django? Como no, en el fichero settings.py. Lo abrimos y buscamos la variable DATABASES:

Código :

alberto@a-AMILO-Si-3655:~/django/tutorial$ view settings.py
[...]
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': '',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}


Esta variable es un diccionario que incluye la configuración de nuestras bases de datos. Por defecto se usará la que configuremos con el nombre default (que ya viene indicada). Vamos a explicar cada par de clave-valor que tenemos que configurar:

ENGINE:

indica a django que servidor de base de datos usamos. El valor de esta clave tendra que ser "django.db.backends." y uno de los siguientes valores:

  • MySQL -> "django.db.backends.mysql" y el adaptador MySQLdb
  • Oracle -> "django.db.backends.oracle" y el adaptador cx_Oracle
  • SQLite -> "django.db.backends.sqlite3" y el adaptador pysqlite
  • PostgreSQL -> "django.db.backends.postgresql" y el adaptador psycopg version 1.x
  • PostgreSQL -> "django.db.backends.postgresql_psycopg2" y el adaptador psycopg version 2.x


NAME:

nombre de la base de datos. Si usas SQLite tendremos que especificar la ruta al archivo de base de datos ('/home/alberto/database.db').

USER:

usuario de acceso. Si usas SQLite déjala en blanco.

PASSWORD:

password del usuario de acceso. Si usas SQLite o no tiene password déjala en blanco.

HOST:

como su nombre indica, es el host donde se encuentra el servidor de base de datos. Si está en el mismo que Django o usas SQLite déjala en blanco.

PORT:

puerto para conectarse a la base de datos. Si lo dejamos en blanco usará el puerto por defecto dependiendo del ENGINE. No hay que indicarlo para SQLite.

Bueno pues el nuestro quedara más o menos así (yo uso el usuario root, cosa para nada recomendable y menos con el password que tiene...):

Código :

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'foroTutorial',                      # Or path to database file if using sqlite3.
        'USER': 'root',                      # Not used with sqlite3.
        'PASSWORD': 'alberto',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}


Probando la conexión


Vamos a la terminal al directorio de nuestro proyecto y ejecutamos "python manage.py shell". Este comando abrirá una shell de python pero con todo nuestro proyecto configurado. Es muy útil para pruebas, cambios de configuraciones y otras tareas que veremos más adelante.

Código :

alberto@a-AMILO-Si-3655:~/django/tutorial$ python manage.py shell
Python 2.7.2+ (default, Oct  4 2011, 20:03:08) 
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.db import connection
>>> cursor = connection.cursor()


Si no aparece ningún error es que tenemos todo perfecto. Si aparece algún error revisadlo porque las excepciones son muy claras y te indican donde está el error.

Creando nuestra primera aplicación


Una aplicación de Django es un conjunto portable de una funcionalidad, en este caso de nuestro proyecto web. Normalmente incluyen modelos de datos y vistas que conviven en el mismo paquete. Por ejemplo, Cristalab sería el proyecto y las aplicaciones serian blog, foro, tutoriales, etc.
En tutoriales anteriores hemos trabajado sin aplicación, pero ya es hora de coger un buen hábito que es utilizar siempre al menos una aplicación. Si quieres utilizar modelos de datos es obligatorio así que ¿por qué no vamos a trabajar como es debido?

Para crearla basta con lanzar el comando en el directorio de nuestro proyecto: django-admin startapp <nombre_aplicacion>. Vamos a crear un mini-foro por lo que llamamos a nuestra aplicación foro. Vamos a ver qué pasa, porque creo que tenemos un nuevo directorio:

Código :

tutorial/foro:
total 12
-rw-r--r-- 1 alberto alberto  57 2012-02-19 12:45 models.py
-rw-r--r-- 1 alberto alberto   0 2012-02-19 12:45 __init__.py
-rw-r--r-- 1 alberto alberto 383 2012-02-19 12:45 tests.py
-rw-r--r-- 1 alberto alberto  26 2012-02-19 12:45 views.py


  • __init__.py : indica a python que aqui hay código suyo.
  • views.py : este os sonará ya
  • models.py : es el fichero donde definiremos nuestro modelo de datos
  • tests.py : se utiliza para escribir y lanzar pruebas sobre nuestra aplicación.


Definir modelos


Como hemos dicho en la descripción, el modelo de datos son clases Python que representan la estructura de base de datos.
Vamos a editar el fichero models.py y dejarlo de la siguiente manera:

Código :

alberto@a-AMILO-Si-3655:~/django/tutorial/foro$ view models.py
from django.db import models

# Create your models here.
class Temas(models.Model):
        titulo = models.CharField(max_length=40)
        cuerpo = models.CharField(max_length=300)

class Comentarios(models.Model):
        texto = models.CharField(max_length=150)
        tema = models.ForeignKey(Temas)



  • Cada modelo es una subclase de Model que se encuentra en el paquete django.db.models.
  • Cada modelo generalmente corresponde a una tabla y cada atributo del modelo se corresponde a un campo de la tabla.
  • Para establecer atributos (o campos) escribiremos: <nombre_atributo> = <tipo_de_campo>
  • El tipo de campo se corresponderá con la instancia de una clase del paquete models dependiendo de cual queramos o necesitemos.
  • Por defecto django pone un campo id como clave primaria a todos los modelos y será un dígito autoincremental.


En el próximo tutorial hablaremos de los diferentes tipos de campo y de relaciones entre tablas.
Ahora hemos creado un atributo tema en el modelo Comentarios que apunta al modelo Temas. Esto lo he hecho para ir preparándome el terreno para el segundo tutorial y básicamente es una relación 1-1 de las tablas Comentarios-Temas, porque un comentario sólo pertenece a un tema.

Instalando nuestros modelos


Una vez tenemos definidos los modelos es hora de que la base de datos se entere.
Primero tendremos que modificar nuestro fichero settings.py una vez más para indicarle que tenemos una aplicación nueva:

Código :

alberto@a-AMILO-Si-3655:~/django/tutorial$ view settings.py
[...]
INSTALLED_APPS = (
    #'django.contrib.auth',
    #'django.contrib.contenttypes',
    #'django.contrib.sessions',
    #'django.contrib.sites',
    #'django.contrib.messages',
    #'django.contrib.staticfiles',
    'tutorial.foro',
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)


Como veis hay muchas aplicaciones "instaladas" por defecto. Todas las que veis ya insertadas se utilizan para gestión de usuarios y sesiones web entre otras funciones. Yo las tengo comentadas porque de momento no las necesitamos. Cuando toque el tutorial de sesiones en python volveremos a añadirlas.
Añadimos nuestra aplicación indicando tutorial.foro (proyecto.aplicacion) y OJO con la coma al final, no me canso de decirlo. En las tuplas es buena práctica dejar siempre una coma al final.

Django ya sabe que tenemos una aplicación nueva. Ahora vamos a usarla para facilitarnos la vida.
Podemos obtener toda la sentencia sql para crear la base de datos y no tener que escribirla a mano. Para eso lanzamos el comando "python manage.py sqlall <nombre_aplicacion>" en nuestro caso foro. Pero antes de crear nada lee el siguiente punto.

Syncdb


Podemos pedirle a django que cree toda la estructura sin tener que conectarnos nosotros y copiar y pegar el sql. Para eso lanzamos el comando "python manage.py syncdb" y veremos algo así:

Código :

alberto@a-AMILO-Si-3655:~/django/tutorial$ python manage.py syncdb
Creating tables ...
Creating table foro_temas
Creating table foro_comentarios
Installing custom SQL ...
Installing indexes ...
No fixtures found.


Cosas a tener en cuenta sobre syncdb:

  1. Este comando recorre todas las aplicaciones de la variable INSTALLED_APPS y crea la estructura en nuestra base de datos si no está creada ya, por lo que si no hubiéramos descomentado las aplicaciones de django, hubiéramos visto una salida diferente a la nuestra y tendríamos muchas más tablas creadas.
  2. Syncdb sólo crea, no modifica ni elimina. ¿Qué quiere decir esto? Si ahora agregáramos un atributo al modelo Temas y ejecutáramos syncdb no pasaría nada. Si eliminásemos el modelo Comentarios y ejecutáramos syncdb no haría nada. Esto es por seguridad, nos tocan esas tareas a nosotros. En cambio si añadiéramos un modelo más, este cambio sí lo realizaría en base de datos.
  3. Las tablas las creará en minúsculas y con el patrón <aplicacion>_<nombre_modelo>.
  4. Creará los campos id como claves primarias autoincrementales.
  5. El campo clave foránea seguirá el patrón <tabla_a_la_que_apunta>_id.


Si revisamos nuestra base de datos esto es lo que veremos:

Código :

mysql> show tables;
+------------------------+
| Tables_in_foroTutorial |
+------------------------+
| foro_comentarios       |
| foro_temas             |
+------------------------+
2 rows in set (0.00 sec)

mysql> desc foro_temas;
+--------+--------------+------+-----+---------+----------------+
| Field  | Type         | Null | Key | Default | Extra          |
+--------+--------------+------+-----+---------+----------------+
| id     | int(11)      | NO   | PRI | NULL    | auto_increment |
| titulo | varchar(40)  | NO   |     | NULL    |                |
| cuerpo | varchar(300) | NO   |     | NULL    |                |
+--------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

mysql> desc foro_comentarios;
+---------+--------------+------+-----+---------+----------------+
| Field   | Type         | Null | Key | Default | Extra          |
+---------+--------------+------+-----+---------+----------------+
| id      | int(11)      | NO   | PRI | NULL    | auto_increment |
| texto   | varchar(150) | NO   |     | NULL    |                |
| tema_id | int(11)      | NO   | MUL | NULL    |                |
+---------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)


Insertando datos


A partir de ahora voy a utilizar la shell de nuestro proyecto por lo que no hace falta que salgáis.
Para insertar datos tenemos dos maneras de hacerlo:

Código :

>>> from tutorial.foro.models import Temas
>>> prueba = Temas(titulo='Titulo primer tema',cuerpo='Este es el cuerpo de nuestro primer tema del foro')
>>> prueba.save()


O esta:

Código :

>>> from tutorial.foro.models import Temas
>>> prueba = Temas.objects.create(titulo='Titulo segundo tema',cuerpo='Cuerpazo de nuestro segundo tema del foro')
>>> 


Atentos al import: <proyecto>.<aplicacion>.models import <modelos>
La diferencia es que en el primero puedes modificarlo antes de guardarlo en base de datos con .save() y, en el segundo, se inserta directamente.

Seleccionando datos


Nos acordamos de importar los modelos que vamos a usar, en este caso Temas: from tutorial.foro.models import Temas
Para seleccionar todos los objetos del modelo Temas haremos:

Código :

>>> from tutorial.foro.models import Temas
>>> lista = Temas.objects.all()
>>> lista
[<Temas: Temas object>, <Temas: Temas object>]
>>> type(lista)
<class 'django.db.models.query.QuerySet'>


Objects es un administrador que se encarga de todas las operaciones a nivel de tablas. Todos los modelos llevan asociado este administrador y lo usaremos para trabajar con los datos.
all() nos extraerá todos los datos de la tabla foro_temas. Todas estas funciones devuelven un QuerySet que contiene el listado de objetos de nuestro modelo. Funcionan igual que las listas por lo que si quisiéramos acceder al primero haríamos:

Código :

>>> lista[0].id,lista[0].cuerpo
(1L, u'Este es el cuerpo de nuestro primer tema del foro')
>>> type(lista[0].id)
<type 'long'>
>>> type(lista[0].cuerpo)
<type 'unicode'>
>>> str(lista[0].cuerpo)
'Este es el cuerpo de nuestro primer tema del foro'


Os parecerá raro que muestre 1L o u'TEXTO'. Si os fijáis en los tipos vemos que el número es del tipo long y el texto es unicode. Podemos modificarlos utilizando las funciones int() y str().

Hasta aquí todo bien pero no siempre queremos extraer todos los datos. Queremos filtrar, excluir, ordenarlos,... pues para todo esto tenemos las siguientes funciones del administrador objects:


  1. filter : es el más usado. Equivale a WHERE y podemos filtrar por los atributos del modelo, por ejemplo: Temas.objects.filter(id=2)
    Se puede filtrar por varios campos como si de un AND se tratase: Temas.objects.filter(id=1,titulo='Titulo primer tema').
    Ahora extendemos un poco más los usos de filter.
  2. exclude : acción inversa a filter. También equivale a WHERE y excluye de los resultados los que cumplan las condiciones que le pasemos por parámetro. Por ejemplo podríamos hacer: Temas.objects.all().exclude(id=2)
  3. order_by : para ordenar el resultado. Equivale a ORDER BY y tendremos que pasar por parámetro un string con el título del atributo por el que queramos ordenar. Podemos ordenar por varios y hacerlo tanto ascendente como descendentemente.
    *ascendente: Temas.objects.order_by('titulo','id')
    *descendente: Temas.objects.order_by('-titulo')
  4. values : devuelve el resultado como diccionario de los valores y no como un QuerySet:

    Código :

    >>> lista = Temas.objects.filter(id=1).values()
    >>> lista
    [{'cuerpo': u'Este es el cuerpo de nuestro primer tema del foro', 'titulo': u'Titulo primer tema', 'id': 1L}]


  5. get : idéntico a filter pero solo sirve para "consultas" que devuelvan un único resultado. Esta función devolverá un objeto de la clase del modelo y no un QuerySet. Si la consulta devuelve más de un resultado se devolverá una excepción:

    Código :

    >>> lista = Temas.objects.all().get()
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 351, in get
        % (self.model._meta.object_name, num, kwargs))
    MultipleObjectsReturned: get() returned more than one Temas -- it returned 2! Lookup parameters were {}
    >>> lista = Temas.objects.filter(id=1).get()
    >>> lista
    <Temas: Temas object>
    >>> type(lista)
    <class 'tutorial.foro.models.Temas'>


  6. count : cuenta los registros devueltos.


Patrones de búsqueda


Estos patrones son diferentes argumentos que podemos pasar a filter(), exclude() y get() para obtener el resultado esperado.
Se utilizan de la siguiente manera: funcion(<campo>__<patron>=valor), por ejemplo, Temas.objects.filter(titulo__icontains='primer')
Si dentro del texto a buscar introducimos los caracteres ', %, _, ; y alguno más, estos serán escapados automáticamente. Si por ejemplo escribiéramos filter(titulo="Primer%''''") esto se corresponde con titulo='Primer\%\'\'\'\''. Esto evita problemas de seguridad como inyecciones de SQL.


  1. exact : es el que usa por defecto si no lo indicamos. [...].filter(id=1) == [...].filter(id__exact=1)
  2. iexact : busca el exacto pero sin distinguir mayúsculas de minúsculas
  3. contains : busca que el campo contenga el texto. Equivale a LIKE '%<texto>%'.
  4. icontains : igual que el anterior pero sin distinguir mayúsculas de minúsculas
  5. gt, gte, lt, lte : sirve para números y se corresponden con >, >=, < y <=. Cuidado al usarlo con texto: 10 es menor que 4
  6. in : para buscar los valores que coincidan con los de la lista. La forma de usarlo es pasando una lista o tupla después del igual: id__in=[1,5,7]
  7. startswith, istartswith, endswith, iendswith: no hace falta decir nada...
  8. range : para búsquedas dentro de un rango. Equivale a BETWEEN X AND Y. Ejemplo: id__range=(1,5)
  9. year, month, day : para búsquedas en campos date o datetime. Podemos filtrar por año, mes o dia. Ejemplo: date__year=2012,date__month=2
  10. isnull : debemos pasarle True si queremos que extraiga los nulos o None si queremos que extraiga los no nulos.
  11. search : igual que contains pero más rápido. Requiere MySQL y que el campo sea índice del tipo full_text.
  12. pk : sirve para buscar por la clave primaria sin tener que indicar el campo. pk=5 sería igual que id=5


Eliminando datos


Para eliminar únicamente basta con filtrar por lo que queramos borrar y llamar a la función delete():

Código :

prueba = Temas.objects.filter(id=1)
prueba.delete()


Y pedir disculpas por el retraso de este tutorial pero el tiempo libre escasea en mi vida :-). A cambio prometo hacer dos tutoriales de modelos de datos muy completos.


Uff!!! cuantas cosas sobre los modelos de datos y todavía nos queda más. En el próximo capítulo aprenderemos los tipos de datos que podemos utilizar y un poquito más sobre cómo crear relaciones entre tablas, seleccionar, etc.

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro

Entra al foro y participa en la discusión

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate