Related Articles

Back to Latest Articles
¿Qué es CodenerixModel?

¿Qué es CodenerixModel?


Juanmi Taboada
Juanmi Taboada
¿Qué es CodenerixModel?

🇬🇧 Read it in English, “CodenerixModel

En el artículo anterior hablábamos sobre CODENERIX y sus bondades. Ahora vamos a comenzar una serie de artículos que permitan profundizar en esta tecnología.

Para que todo proyecto Django pueda soportar CODENERIX adecuadamente es necesario dar algunos pasos importantes que aseguren el buen comienzo del proyecto. No voy a repetir que la forma más sencilla de comenzar es usando los Ejemplos de CODENERIX disponibles en GITHUB.

Es por ello que en esta ocasión me voy a centrar en el CORE / KERNEL de CODENERIX y que conoceremos de ahora en adelante como CodenerixModel.

CodenerixModel no es más que la extensión del sistemas models.Model de Django donde se contemplan una serie nueva funcionalidades que explicaré a continuación. En primer lugar debemos tener en cuenta que la mayoría de funcionalidades que veremos de CODENERIX se consiguen mediante el uso de herencia directa de clases de este framework. Para ello vamos a ver un sencillo de ejemplo de una clase desarrollada con CODENERIX:

from codenerix.models import CodenerixModel
from django.db import models

class Author(CodenerixModel):
    name = models.CharField(_(u'Name'), max_length=128, blank=False, null=False)
    birth_date = models.CharField(_(u'Fecha de nacimiento'), max_length=128, blank=False, null=False)

    def __fields__(self, info):
        fields=[]
        fields.append(('name', _('Name'), 100, 'left'))
        fields.append(('birth_date', _('Birth Date')))
        return fields

En este ejemplo vemos varias cosas que nos llaman la atención frente a una declaración normal de un modelo en Django:

  1. Importamos CodenerixModel con:
    from codenerix.models import CodenerixModel
  2. El modelo hereda CodenerixModel (no de models.Model):
    class Author(CodenerixModel):
  3. Hay un método especial que es obligatorio y que se llama __fields__ y que recibe un parámetro “info“:
    def __fields__(self, info):

    debemos saber que este método debe estar definido en todas las clases que hereden de CodenerixModel dado que es usada por CODENERIX para conocer como debe mostrar la información al usuario final de la aplicación de tal modo que esta sea clara, sencilla y usable.

  4. El resto de información en la clase es simple de entender y responde al paradigma general que usa Django, de hecho CODENERIX hereda siempre de Django y pasa su gestión a este asegurando siempre de este modo que cualquier mejora de Django está disponible también en CODENERIX.

¿Qué es lo que contiene el método __fields__?

Recoge qué información del modelo es útil ser mostrada cuando el usuario desee trabajar con dicho modelo. Este método es llamado normalmente por los listados, dado que los campos que aparecen aquí serán usados como columnas en el listado.

def __fields__(self, info):
        fields=[]
        fields.append(('name', _('Name'), 100, 'left'))
        fields.append(('birth_date', _('Birth Date')))
        return fields

IMPORTANTE: Este método es obligatorio en todos los modelos que hereden de CodenerixModel.

El método recibe un argumento “info” que contiene información sobre la consulta recibida (request) incluyendo entre otros datos el usuario que la hace y otra meta información importante para la toma de decisiones que se hará dentro de __fields__. Este ejemplo es lineal, pero podemos aprovechar esta información para detectar el nivel de acceso de un usuario y mostrar unos campos u otros en el listado, adaptando el contenido del listado en función del usuario o las condiciones del sistema.

De este método se espera que responda con una lista de tuplas que debe contener al menos 2 elementos, el primero será el nombre del campo según el modelo de Django y el segundo el nombre traducible de dicho campo para ser mostrado al usuario en el título de la columna a la que hace referencia ese campo.

Cada tupla por lo tanto se compone de 2 elementos obligatorios y varios opcionales que paso a enumerar:

  • Obligatorio: Nombre del campo del modelo
  • Obligatorio: Nombre traducible del campo para ser mostrado al usuario (puede ser None para evitar que Django renderice esa columna, de este modo podemos recibir este campo en la respuesta JSON del listado pero sin ser mostrado visualmente. Es muy útil cuando se desean componer varios campos en uno sólo)
  • Opcional: Longitud del ancho del campo en píxeles (deprecated, está en desuso)
  • Opcional: Alineación del texto dentro de dicho campo (Ej: ‘center‘)
  • Opcional: Nombre del filtro a aplicar a dicho campo (Ej: ‘skype‘ que añadirá la información necesaria para que la salida del campo sea un número de teléfono clickable)

Existen otros 3 métodos especiales más que aportan diferentes funcionalidades a los modelos, estos son: __limitQ__, __searchF__ y __searchQ__.

¿Qué es el método __limitQ__?

Es un método limitante, su objetivo consiste en limitar los resultados a los que el usuario tiene acceso. De este modo limitQ permite controlar el resultado devuelto al usuario en función al entorno de consulta, de tal modo que si eres un usuario sin privilegios, no podrás ver ciertos registros porque se aplicará un filtro. Es muy útil cuando se trabaja con Roles dado que permite controlar los registros que ve el usuario en función al rol que tiene.

def __limitQ__(self, info):
    limits = {}
    if not info.user.is_admin:
        criteria = []
        criteria.append(Q(model__pk=pk_condition))
        criteria.append(Q(model__field1=condition))
        limits['profile_people_limit'] = reduce(operator.or_, criterials)
    return limits

El método espera devolver un diccionario donde la key indica simplemente la línea que opera (por si ocurre un error al procesar la búsqueda) y por otro lado el valor es un objeto Q de Django. El objeto Q será inyectado en el Queryset del listado cuando este sea solicitado aplicándose o no el filtro en función a la consulta.

¿Qué es el método __searchQ__?

Es el método utilizado para filtrar las consultas con el texto que se introduce en la caja superior de los listados. De este modo todo aquello que el usuario escriba en la caja de búsqueda nos llegará en la variable “text” y esto permitirá filtrar la búsqueda con este texto.

def __searchQ__(self, info, text):
    text_filters = {}
    text_filters['identifier1'] = Q(CharField1__icontains=text)
    text_filters['identifier2'] = Q(TextField1__icontains=text)
    text_filters['identifier3'] = Q(IntegerField=34)

    #If text have this a especific word can return another Q condition.
    if text.find(u'magic') != -1:
        text_filters['identifier4'] = Q(identifier=34)

    return text_filters

El método espera devolver un diccionario donde la key indica simplemente la línea que opera (por si ocurre un error al procesar la búsqueda) y por otro lado el valor es un objeto Q de Django. El objeto Q será inyectado en el Queryset del listado cuando este sea solicitado aplicándose o no el filtro en función a la consulta.

¿Qué es el método __searchF__?

Es el encargado de mostrar los filtros de búsqueda y es usado cuando deseamos que se muestren filtros que faciliten la búsqueda. En concreto su uso se refiere a la definición personalizada de estos filtros, por ejemplo un selector que muestre “A”, “M” y “S”, de tal modo que si pulsas “A” buscarás todo lo que empiece por “A”, idem para M y S, pero no podrás hacer búsqueda por otras letras. Es muy útil para construir búsqueda combinadas, por ejemplo: “dame los del tipo A” (que se refieren a los verdes y morados conjuntamente). También es usado para generar filtros complejos. CODENERIX incluye de forma automática filtros en todos los campos de un listado con los que sabe operar de forma nativa, así por ello saldrán filtros para los campos de texto, campos numéricos, BooleanFields, ChoiceFields y fechas.

def __searchF__(self, info):
    list1 = []
    for l1 in Model.objects.all():
        list1.append((l1.id, l1.field1 + ' ' + l1.field2))

    list2 = []
    for li in Model2.objects.all():
        list2.append((li.id, str(li.field)))

    text_filters = {}
    text_filters['field1'] = (_('Field1'), lambda x: Q(field1__startswith=x), [('h', _('Empieza con h')), ('S', _('Empieza con S'))])
    text_filters['field2'] = (_('Field2'), lambda x: Q(field2__pk=x), list1)
    text_filters['external'] = (_('Field3'), lambda x: Q(pk=x),list2)
    return text_filters

El método espera devolver un diccionario donde la clave del diccionario indica el nombre del filtro, si el nombre del filtro coincide con un campo del listado, entonces el filtro se mostrará en la fila oculta debajo del título del listado y que se despliega con el icono “≡”, en caso contrario se mostrará como un filtro externo. El valor de dicha clave deberá ser una tupla con 3 elementos:

(_('Field1'), lambda x: Q(field1__startswith=x), [('h', _('Empieza con h')), ('S', _('Empieza con S'))])

de tal modo que el primer elemento será el nombre del filtro en formato traducible, el segundo una función lambda que recibe el valor del filtro y devuelve un objeto Q que ayuda a que ese filtro funcione del modo en que se espera, el tercer elemento deberá ser una lista con las opciones que se van a mostrar en el filtro. El objeto Q devuelto por la función lambda será inyectado en el Queryset del listado cuando este sea solicitado aplicándose o no el filtro en función a la consulta.

¿Cómo interaccionan CodenerixModel y GenList?

Aunque más adelante hablaremos de GenList (vista usada para construir los listados) debo decir que __fields__, __limitQ__, __searchQ__ y __searchF__ son métodos que pueden aparecer indistintamente en el modelo y en la vista de un listado. A continuación se muestra cómo interaccionan los filtros cuando un modelo especifica un filtro y un listado no, o viceversa.

En las siguientes 3 tablas se muestran 3 columnas:

  • La primera indica si el método está definido en CodenerixModel.
  • La segunda indica si está definido en GenList.
  • La tercera columna se indica cual es el resultado de aplicar dichos filtros.
__fields__
CodenerixModel GenList Se aplica…
NO NO — ERROR —
NO SI — ERROR —
SI NO CodenerixModel
SI SI GenList
__limitQ__
CodenerixModel GenList Se aplica…
NO NO No se aplica ningún filtro
NO SI Se aplica filtro de GenList
SI NO Se aplica filtro de CodenerixModel
SI SI Se aplica filtro de GenList y de CodenerixModel uniendo a ambos mediante el operador lógicoAND“.
__searchQ__ & __searchF__
CodenerixModel GenList Se aplica…
NO NO No se aplica ningún filtro
NO SI Se aplica filtro de GenList
SI NO Se aplica filtro de CodenerixModel
SI SI Se aplica filtro de GenList

Estas tablas las recordaremos de nuevo más adelante cuando explique GenList al detalle.

¿Y qué más?

Además de los métodos anteriores debemos destacar que CodenerixModel añade una serie de funcionalidades extras a los modelos estándares de Django.

  1. De serie añade los campos created y updated que son gestionados automáticamente por CODENERIX para recordar cuando fue creado el registro y cuando fue editado por última vez.
  2. Permite el uso de una nueva clase “Meta” (equivalente a la de Django) que se llama “CodenerixMeta“.
  3. Los permiso estándares son sustituidos por: add, change, delete, view y list. Los nuevos tipos son “view” y “list” par definir si el registro puede ser visualizado o listado.
  4. Los modelos también disponen de la posibilidad de usar los métodos lock_update y lock_delete para detener una actualización del registro o un borrado. Ejemplo de esto es que exista un registro que dependa de una circunstancia concreta de los datos almacenados. Gracias a estos métodos se pueden bloquear registros de forma caprichosa en un listado e informar al usuario adecuadamente de la razón de dicho bloqueo.
    def lock_delete(self):
        if self.documents.exists():
            return _("No se puede borrar el apunte, existe un documento que lo bloquea")
        elif self.people.exists():
            return _("No se puede borrar el apunte, existen personas aun trabajando con él")
        else:
            return super(Apunte, self).lock_delete()

    En este ejemplo se impide el borrado de un apunte cuando tiene documentos asociados o cuando hay personas usándolo, en otro caso se delega la responsabilidad a la clase padre.

  5. Cuando se va a borrar un registro, CodenerixModel analiza las relaciones con otros modelos y las protecciones de borrado de estos para responder de forma “educada” en caso de intentar borrar un registro que por definición del modelo (models.PROTECT) no puede ser borrado.
  6. Todas las clases que heredan de CodenerixModel también pueden heredar a su vez de GenLog. Al hacerlo CODENERIX registrará en un log cualquier operación que ocurra en un registro de esa clase.

Ahora ya estamos preparados para aprender el uso de GenList.

Comments

Related Articles

Codenerix

Como usar Codenerix GenCreate, GenUpdate, GenDetail y GenDelete

🇬🇧 Read it in English, “GenCreate, GenUpdate, GenDetail and GenDelete“ En el último artículo describíamos Codenerix GenList para comenzar a usar los listados de CODENERIX. Ha...

Posted on by Juanmi Taboada
Codenerix

Python VS JSON, YAML, CSV & XML

Examples of how to work with JSON, YAML, CSV, and XML files in Python. Today I saw myself preparing some exercises for my student who is learning Python programming language, and...

Posted on by Juanmi Taboada