Skip to content

euribates/olakase

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ola K Ase - Gestor de tareas

Logo Ola K Ase

Ola K Ase es un gestor de tareas, pensado como proyecto didáctico para la enseñanza de Django.

Documentación

Guión

  • Crear una migración automática: Añadir un campo booleano, urgent a la clase Task

    • Crear el campo, sin valor por defecto

    • Crear la migración

    • La migración solo es un fichero generado automáticamente. Abrir la migración con el editor.

    • Intentar aplicarla

    • Borrarla. No está aplicada, así que por ahora solo es código generado automáticamente, se puede borrar sin consecuencias. Si estuviera aplicada, ahí se nos complica un poco la cosa.

    • Modificar el campo, añadir el valor por defecto False para que todas las tareas se marquen como no urgentes. Este seguramente es lo que nuestros usuarios quieren.

    • Marcar algunas de las tareas como urgentes. Las usaremos en los siguientes pasos

    • Cosas que hemos aprendido:

      • Podemos hacer migraciones de forma automática
      • Pero, puede que no se puedan aplicar, dependiendo del estado de la base de datos
      • No hay (mucha) magia, solo son ficheros creados automáticamente
      • Las migraciones forman una jerarquía. Normalmente, una lista encadenada.
      • Lo mejor es que las migraciones sean lo más sencillas posibles. Es mejor tener 10 migraciones sencillas, cada una de ellas que realiza un único cambio en la base de datos, que tener una única migracion que realize 10 cambios.
  • Sustituir el campo booleano urgent por un campo priority, con una serie de niveles, descritos en la siguiente tabla:

    Código Descripción
    LOW Prioridad baja
    NOR Prioridad normal
    URG Prioridad urgente
    CRI Prioridad crítica
    • Mejor varias migraciones sencillas que una compleja:

      • Añadir el nuevo campo, o bien con la posibilidad de que valga nulo, o con un valor por defecto, por ejemplo, 'NOR'.

      • Hacer una migración propia, en la que recorremos todas las tareas marcadas como urgent y las codificamos com prioridad URG.

      • Borrar el campo urgent

      Hay que garantizar que se ejecuten en el orden adecuado, para eso tenemos el campo dependencies de las migraciones. Modificar la ultima migración para indicar que solo se debe aplicar despues de aplicadas las ados anteriores.

Notas

Como hacer migraciones propias

Podemos crear nuestras propias migraciones. Este es realmente útil para cambios en las bases de datos que ya estén en producción.

Primero creamos una migración vacía (empty), con el comando makemigrations, pero tenemos que incluir el flag --empty y, además, como Django no sabe que es lo que vamos a hacer, tenemos que darle un nombre con --name. El nombre sera parte del nombre de un fichero, así que no debemos usar espacios, acentos, caracteres extraños, etc.):

./manage.py makemigrations --empty --name nombre_que_quieras_para_la_migracion <app>

Esto creará un fichero de migración, que no hace nada, con un contenido similar a este:

# Generated by Django 3.2.12 on 2025-11-22 09:04 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('agora', '0003_alter_table_add_field_flag'), ] operations = [ ]

Como vemos, solo se define el campo de dependencias, las operaciones a realizar en esta migración están vacías. Vamos a incluir código SQL para crear la secuencia:

CREATE SEQUENCE Agora.seq_foto_diputado START WITH 1 INCREMENT BY 1;

Para ello haremos uso de la clase migration.RunSQL, que nos permite definir la migración tanto en una dirección como en otra, es decir, crearemos este objeto con dos sentencias SQL, una para definir como aplicar la migración, y otra para deshacerla. En nuestro caso, quedaría así:

# Generated by Django 3.2.12 on 2022-03-22 09:04 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('agora', '0003_asunto_bop_cargo_claseiniciativa_composicion_diputado_diputadogrupo_ds_dsc_dsdp_fotodiputado_grupopa'), ] operations = [ migrations.RunSQL( 'CREATE SEQUENCE Agora.seq_foto_diputado START WITH 1 INCREMENT BY 1', 'DROP SEQUENCE Agora.seq_foto_diputado', ) ]

Fuentes:

Cómo crear migraciones propias usando código Python en vez de SQL

Si las migraciones usando solo SQL se quedan cortas, también podemos hacer migraciones personalizadas que usen código Python e incluso, con ciertas limitaciones, nuestro código ya existente.

Para ello, en vez de usar la clase RunSQL usaremos la clase RunPython. Esta clase espera uno o dos parámetros, igual que RunSQL, pero estos parámetros deben ser callables, normalmente funciones.

Estas funciones debe aceptar dos parámetros: el primero es un registro que mantiene los versiones a lo largo de la historia de todos los modelos, de forma que podamos acceder al modelo tal y como era en la evolución del proyecto. El segundo parámetro es una instancia de a clase SchemaEditor, que se puede usar para realizar cambios manuales en el esquema de la base de datos (Pero que no es recomendable usar, ya que puede confundir, y mucho, al sistema de migraciones).

Veamos un ejemplo, en el que calculamos la letra inicial, normalizada, de un texto y lo almacenamos en otro campo. Esto puede ser útil a efectos de filtrar y clasificar las entradas:

from django.db import migrations def make_inicial(text: str) -> str: if text: normaliza_table = str.maketrans("ÁÉÍÓÚ", "AEIOU") char = text[0].upper() return char.translate(_normaliza_table) return '' def set_inicial(apps, schema_editor): # No podemos usar el modelo Task directamente, porque puede # que a estas alturas exista una version posterior al modelo # que espera la migración. Por eso tenemos que _viajar en el tiempo_ # y cargar el modelo que se corresponda con el momento histórico # de esta migración. Task = apps.get_model("tasks", "Task") for task in Task.objects.filter(inicial=None): task.inicial = make_inicial(task.name) task.save() class Migration(migrations.Migration): dependencies = [ ("dc2", "0001_initial"), ] operations = [ migrations.RunPython(set_inicial), ]

Al igual que con RunSQL, podemos implementar la operación que deshaga este cambio, y pasarla como segundo parámetro. Si hacemos esto con todas nuestras migraciones personales (En las automáticas se realiza siempre), podemos viajar atrás y adelante en la historia del esquema de la base de datos, que puede ser una capacidad interesante. Para el ejemplo anterior, quedaría así:

from django.db import migrations ... def unset_inicial(apps, schema_editor): Task = apps.get_model("tasks", "Task") Task.objects.update(inicial=None) class Migration(migrations.Migration): dependencies = [ ("dc2", "0001_initial"), ] operations = [ migrations.RunPython(set_inicial, unset_inicial), ]

En este caso, añadir la opción de deshacer solo nos ha llevado dos líneas de código, y nos permite seguir navegando por el historial de migraciones.

Como condensar/simplificar (squash) las migraciones en Django

Existe una opción en el manage.py llamada squashmigrations que nos permite condensar todas las migraciones aplicadas (o un subconjunto de ellas) de forma que se sustituyan por una única migración. Además, intenta optimizar las migraciones al mezclarlas, de forma que se eliminan los cambios que son sobrescritos por migraciones posteriores.

Por ejemplo, si tenemos una acción de tipo CreateModel() y más tarde aparece otra de tipo DeleteModel() para el mismo modelo, se pueden eliminar no solo las dos acciones indicadas, sino también cualquier acción intermedia que modifique al modelo.

Igualmente, acciones como AlterField() o AddField() son trasladadas a la versión final de la acción CreateModel.

La versión final condensada también mantiene referencias al conjunto de migraciones que reemplaza. De esa forma Django puede entender cosas como el histórico de grabaciones o las dependencias entre migraciones.

Django enumera de forma automática los ficheros de migraciones, partiendo de 0001_initial.py. De esa forma puede determinar el orden de aplicación de las migraciones, y nosotros podemos usarlo para indicar el conjunto de las migraciones que queremos condensar, en forma de rango.

Por ejemplo, supongamos que tenemos la siguiente lista de migraciones:

./foo ./migrations 0001_initial.py 0002_userprofile.py 0003_article_user.py 0004_auto_20190101_0123.py 

En la mayoría de los casos, querríamos condensarlas todas en un único fichero. Para ello, ejecutamos la siguiente orden:

python manage.py squashmigrations foo 0004

El resultado será condensar todas las migraciones, desde la 1 hasta la 4, generando una nueva migración con el nombre: 0001_squashed_0004_auto_<timestamp>.py

Si examinamos este fichero, descubriremos dos cosas interesantes:

  • La nueva migración está marcada como initial=True, lo que significa que sera la nueva migración inicial de esta aplicación. Si se aplicara en una nueva base de datos, las migraciones anteriores se ignorarían.

  • Se ha añadido un nuevo atributo, replaces, que es una lista de las migraciones que son reemplazadas por esta.

Fuentes:

About

Ola K Ase - Gestor de tareas

Resources

License

Stars

Watchers

Forks

Packages

No packages published