Comunidad de diseño web y desarrollo en internet online

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

¿Qué vamos a ver aquí? Diferentes tipos de campos para nuestros modelos (que hay muchos) y opciones universales que aceptan, relaciones muchos-a-uno y muchos-a-muchos y cómo cruzarlas para extraer datos y alguna que otra cosa más.

Este tutorial va a ser más teórico que práctico porque si no se haría realmente extenso. También "oiréis" hablar de formularios de Django y de la web de administración. En otros tutos hablaremos de ellos.

Campos


  • AutoField : entero autoincremental
  • BooleanField : true/false
  • CharField : para cadenas cortas de texto. Requiere el argumento max_length.
  • CommaSeparatedIntegerField : enteros separados por comas.
  • DateField : campo fecha. Argumentos opcionales:
    auto_now -> asigna automáticamente la fecha actual cuando se actualiza el objeto del modelo. No se puede sobrescribir.
    auto_now_add -> asigna automáticamente la fecha actual cuando se crea el objeto del modelo. No se puede sobrescribir.
  • DateTimeField : campo fecha y hora. Tiene las mismas opciones que DateField.
  • EmailField : es un CharField que chequea que el valor sea una dirección de mail válida. No acepta max_length y su máximo es de 75 caracteres.
  • FileField : campo de carga de ficheros. Tiene un argumento requerido:
    upload_to -> ruta del sistema de archivos que se complementará con la configuración de la variable MEDIA_ROOT de settings.py.

    Para FileField y para ImageField tenemos que tener en cuenta:

    1. En la configuración (settings.py) es necesario definir la variable MEDIA_ROOT con la ruta completa donde quieres que se comiencen a subir los ficheros y MEDIA_URL con la URL pública base de ese directorio.

    2. Agregar el campo al modelo definiendo la opción upload_to para decirle a django a cual subdirectorio de MEDIA_ROOT subir el archivo.

    3. En BBDD se almacena la ruta relativa a partir de MEDIA_ROOT del archivo.

    4. La opción upload_to acepta formato strftime. Por ejemplo si tu MEDIA_ROOT es '/home/media' y upload_to es '%Y/%m/%d', si subes un archivo el día 20/01/2011 se mandara a '/home/media/2011/01/20'. ¡Cuidado con las fechas! Revisad la variable TIME_ZONE del fichero settings.py para evitar sustos.


Tenemos 3 funciones extras para este campo del modelo:

    get_<nombre_campo>_filename()
    get_<nombre_campo>_url()
    get_<nombre_campo>_size()


  • FilePathField : campo cuyas opciones se limitan a nombres de archivos en un cierto directorio del sistema de archivos. Tres argumentos:

      path -> Es obligatorio. Contiene la ruta absoluta del directorio del cual debe tomar sus valores.
      match -> Opcional. Expresión regular como string que usará para filtrar los nombres de los archivos.
      recursive -> Opcional. Es un booleano y por defecto es False.


    Ejemplo:

    Código :

    FilePathField(path="/home/images",match="avatar.*",recursive=True)


  • FloatField : número de punto flotante. Dos argumentos requeridos:

      max_digits -> cantidad máxima de dígitos permitidos incluyendo los decimales.
      decimal_places -> cantidad de posiciones decimales.


    Ejemplo si queremos almacenar números hasta 999 sería:

    Código :

     FloatField(max_digits=5,decimal_places=2)


  • ImageField : idéntico a FileField pero valida que sea una imagen. Tiene dos argumentos opcionales que son rellenados cuando se crea el modelo:

      height_field
      width_field


    Tenemos 2 funciones más que FileField:

      get_<nombre_campo>_height()
      get_<nombre_campo>_width()


    Este campo requiere la biblioteca Python Imaging Library.

  • IntegerField : simplemente enteros.
  • IPAddressField : una IP como string.
  • NullBooleanField : iguale que BooleanField pero acepta nulos.
  • PhoneNumberField : es un CharField que valida que sea un teléfono estilo US(XXX-XXX-XXXX) Para números de otros países se debe revisar el paquete django.contrib.localflavor.
  • PositiveIntegerField : entero positivo.
  • PositiveSmallIntegerField : valor máximo 65535
  • SlugField : slug es una etiqueta que contiene letras, números y guiones. Generalmente se usan para definir urls. Si no se especifica max_length django asume como máximo 50 caracteres.
  • SmallIntegerField : entero que normalmente acepta valores entre -32768 y 32768.
  • TextField : campo de texto de longitud ilimitada.
  • TimeField : campo de hora. Acepta las opciones de DateField y DateTimeField.
  • URLField : dos opciones extras:

      max_length -> por defecto son 200 caracteres.
      verify_exists -> booleano que indica si se debe chequear la existencia de la url.

  • USStateField : abreviatura de dos letras de un estado de US. Para otros paises/estados consultar django.contrib.localflavor.
  • XMLField : es un TextField que valida que sea un XML a partir de un esquema dado. Argumento obligatorio:

      schema_path -> ruta a un esquema del tipo RELAX NG.

    Se requiere jing para validar el XML.


    Argumentos universales



    • null : booleano con valor por defecto False. Si lo indicamos a True django almacenará valores NULL en BBDD. Únicamente sirve para campos que no sean string.
    • blank : booleano con valor por defecto False(fuerza que el campo sea obligatorio). Si lo indicamos a True django permitirá que el campo esté vacío.
      La mayor parte de las veces vienen juntos de la mano. Diferencias:

        null -> es para base de datos.
        blank -> es para validación de datos.

    • choices : iterable de dos tuplas para usar como opciones. Se usa para que los formularios de django lo indique como select y no como texto. El primer valor es lo que se almacenará en BBDD y el segundo la descripción asociada.

        Ejemplo:
        LANGUAGES_CHOICES=(
        ('SP','Spanish'),
        ('FR','French'),
        ('EN','English'),
        )

      class Language(models.Model):
      lang = models.CharField(max_length=2,choices=LANGUAGES_CHOICES)

    • db_column : para especificar el nombre de la columna. Por defecto se usa el nombre del atributo que define el modelo.
    • db_index : crea índice para el atributo si esta opción es True.
    • default : valor por defecto del atributo.
    • editable : si es False el valor no podrá editarse.
    • help_text : texto de ayuda extra que será mostrado bajo el campo en el formulario de administración de django. Es útil como documentación.
    • primary_key : True para que el campo sea la clave primaria del modelo. Este atributo implica: blank=False,null=False,unique=True.
    • radio_admin : por omisión, django utiliza una lista seleccionable para campos ForeignKey o con la opción choices en los formularios de django. Con este atributo a True conseguimos que sean radio.
    • unique : si es True el valor para este campo debe ser único en la tabla.
    • unique_for_date : a este atributo se le asigna el nombre de un campo DateField o DateTimeField. Se restringe a que el valor del campo sea único en la fecha especificada.
    • unique_for_month
    • unique_for_year
    • verbose_name : todos los campos salvo ForeignKey, ManyToManyField y OneToOneField admiten como primer argumento de posición un texto con la descripción del campo. Como los campos de relaciones tienen como primer argumento de posición la clase modelo a la que está relacionada, podemos conseguir darle la descripción con este argumento.


    Relaciones


    Dejo link a la documentación oficial ya que todo no se puede contar en esta vida.

    Uno-a-uno


    Para esta relación se utiliza el campo OneToOneField. También podríamos usar el campo ForeignField y el argumento unique=True. Para más información a la documentación oficial.

    Muchos-a-uno


    Para esta se utiliza el campo ForeignKey. Requiere como primer argumento el modelo con el que está relacionado.

    Código :

    class Comentarios(models.Model):
       tema = models.ForeignKey(Temas)

    Django creará un campo en la tabla del modelo con el patrón <atributo>_id.

    Si lo que necesitamos es hacer una relación de este tipo al mismo modelo lo indicaremos con la palabra reservada self como string:

    Código :

    class Comentarios(models.Model):
       quoted = models.ForeignKey('self')


    También podemos crear una relación a un modelo aun no definido. Para esto deberemos de indicar el modelo como string y no como referencia del modelo.

    Esta última posibilidad solo puede usarse para relacionar modelos de la misma aplicación. Si queremos relacionarlo con un modelo de otra aplicación tendremos que usar el primer método.
    Tiene argumentos extras los cuales la mayoría se utilizan básicamente para la web de administración de django. Más información a la documentación oficial.

    Muchos-a-muchos


    Para esta se utiliza el campo ManyToManyField. Requiere como primer argumento el modelo con el que está relacionado. También podemos crear la relación con el mismo modelo ('self') o con un modelo aun no definido('modelo').

    Django creará una tabla intermedia entre los modelos relacionados con las claves primarias (se puede especificar el nombre de esta tabla con el argumento db_table).

    Tiene argumentos extras los cuales la mayoría se utilizan básicamente para la web de administración de django. Más información en la documentación oficial.

    Búsquedas avanzadas con objetos Q


    Un objeto Q (del paquete django.db.models) se utiliza para encapsular una colección de argumentos de palabra clave. Estas palabras clave son ni más ni menos que patrones de búsqueda.

    Los objetos Q pueden combinarse con & (AND) y | (OR).

    Código :

    Temas.objetcts.filter(Q(titulo__startswith="Primer") | Q(titulo__startswith="Segundo"))


    Se pueden utilizar paréntesis para agrupar. Estos objetos deben preceder a los argumentos normales para los filtros.

    Objetos relacionados


    Cuando se define una relación en un modelo, tenemos una API de acceso al objeto del modelo relacionado. Por ejemplo un objeto c de nuestro modelo Comentarios puede acceder a su tema relacionado mediante el atributo tema-> c.tema

    También tenemos una API para el modelo inverso, es decir, si queremos que el modelo que no tiene definida la relación acceda al objeto del modelo con el que está relacionado. Para esto usamos el manejador <nombre_modelo>_set -> t.comentarios_set.all()

    Consultas que cruzan relaciones


    Para cruzar una relación en un filtro únicamente hace falta utilizar el nombre del atributo relación y los campos por los que queramos filtrar separados por guiones bajos.
    Por ejemplo, quiero todos los Comentarios que estén relacionados con temas que contengan la palabra "primer" en el titulo. Para esto haríamos:

    Código :

    c_list = Comentarios.objects.filter(tema__titulo__icontains="primer")


    Como podéis ver uso tema (nombre del atributo del modelo Comentarios) y no temas (nombre del modelo).

    También podemos hacer el filtro al contrario, desde el modelo que no define la relación al modelo que sí. Para ello tenemos que indicar el nombre del modelo en minúsculas seguido de los campos para el filtrado. Por ejemplo:

    Código :

    t_list = Temas.objects.filter(comentarios__texto__icontains="cristalab")


    Relaciones de clave foránea


    Si un modelo contiene un ForeignKey, las instancias tendrán acceso al objeto relacionado como un simple atributo:

    Código :

    c = Comentarios.objects.get(id=1)
    c.tema


    De esta manera podemos modificar la relación creada así:

    Código :

    c = Comentarios.objects.get(id=1)
    t = Temas.objects.get(id=1)
    c.tema = t
    c.save()


    Relaciones de clave foránea "inversas"


    Si antes hablábamos de relacionar el modelo con el campo ForeignField al modelo relacionado, ahora hablamos de la relación inversa. Podemos tener acceso al manager del modelo con el que tiene relación por lo que podríamos filtrar la consulta sin problemas.
    Para acceder al manager basta con escribir <nombre_modelo_minusculas>_set. Por ejemplo:

    Código :

    t = Temas.objects.get(id=1)
    t.comentarios_set.all()


    ¿Por qué aquí ponemos el nombre del modelo y no el atributo?
    Porque no tenemos ningún atributo en el modelo Temas que lo relacione con Comentarios ;-)

    Este manager, a parte de las funciones ya vistas, tiene funciones adicionales:

    • add(obj1, obj2, ...) -> agrega los objetos del modelo pasado por argumento al conjunto de objetos relacionados.

      Código :

      t = Temas.objects.get(id=1)
      c = Comentarios.objects.get(id=1)
      t.comentarios_set.add(c) #Creamos la relación entre el tema con id=1 y el comentario con id=1


    • create(<argumentos>) -> crea el objeto, lo guarda y lo relaciona. En argumentos debemos indicar todos los atributos con sus valores correspondientes para crear el modelo sin incluir el de la relación (en el ejemplo solo indicamos el atributo texto porque es el único atributo del modelo Comentarios). Esta función devuelve el objeto Comentarios creado.

      Código :

      t = Temas.objects.get(id=1)
      t.comentarios_set.create(texto="Texto de 1 comentario nuevo")


    • remove(obj1,obj2,...) -> si add agregaba los modelos a la relación, remove los quita de la relación.
    • clear() -> quita todos los objetos relacionados.


    Las funciones remove y clear sólo se pueden usar para atributos ForeignField que acepten nulos (null=True). Estas funciones no eliminan datos, sólo pasan la relación a null, de ahí que sea obligatorio que acepten nulos.

    Relaciones muchos a muchos


    También tenemos una API de acceso a ambos extremos de la relación.
    Para el modelo que define el campo ManyToManyField tenemos que usar el nombre del atributo.
    Para el modelo que no define el campo ManyToManyField usaremos <nombre_modelo_minusculas>_set.

    Nota sobre consultas de relaciones


    Cuando hacemos una consulta en las que hay relaciones de por medio podemos realizar esa consulta pasando como argumento una instancia del modelo. Con un ejemplo se ve perfecto:

    Código :

    t = Temas.objects.get(id=1)
    c = Comentarios.objects.filter(tema=t)
    c = Comentarios.objects.filter(tema=t.id)
    c = Comentarios.objects.filter(tema=1)


    Los tres filtrados son distintas maneras de conseguir el mismo resultado. Úsalas según te convenga.

    ATAJOS


    Los atajos más usados para modelos de datos son los siguientes (en el paquete django.shortcuts como no):

    get_object_or_404()


    Equivale a llamar a get y en caso de no existir el objeto lanza Http404(por defecto se lanzaría la excepción DoesNotExist).
    Toma como argumentos el modelo o manager del modelo y los filtros que quieras realizar.

    Código :

    c = get_object_or_404(Comentarios,id=20)
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "/usr/local/lib/python2.7/dist-packages/django/shortcuts/__init__.py", line 115, in get_object_or_404
        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
    Http404: No Comentarios matches the given query.
    
    c = Comentarios.objects.get(id=20)
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "/usr/local/lib/python2.7/dist-packages/django/db/models/manager.py", line 132, in get
        return self.get_query_set().get(*args, **kwargs)
      File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 349, in get
        % self.model._meta.object_name)
    DoesNotExist: Comentarios matching query does not exist.


    get_list_or_404()


    Se comporta como el anterior pero llama a filter en vez de a get.

    Código :

    t = get_list_or_404(Temas,titulo__contains="tema")
    t
    [<Temas: Temas object>, <Temas: Temas object>]
    t = get_list_or_404(Temas,titulo__contains="CRISTALAB")
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "/usr/local/lib/python2.7/dist-packages/django/shortcuts/__init__.py", line 128, in get_list_or_404
        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
    Http404: No Temas matches the given query.



    Bueno por fin lo terminé. Espero que no me haya comido nada importante y si es así y alguien sabio lee el tutorial que lo diga.

    En el siguiente tutorial vamos a dar una pincelada a la web de administración de django y en el siguiente a ese hablaremos de cookies, sesiones y como django nos ayuda con la creación y validación de usuarios.

    ¿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