Navigation

Related Articles

Back to Latest Articles
¿Cómo usar Codenerix GenList?

¿Cómo usar Codenerix GenList?


Juanmi Taboada
Juanmi Taboada
¿Cómo usar Codenerix GenList?

🇬🇧 Read it in English, “GenList

Anteriormente hablábamos sobre CodenerixModel para entender como se construye un modelo funcional con CODENERIX. Sin embargo, para que todo funciocune en Django, además de los modelos también hay que definir las vistas. Para permitir visualizar los contenidos webs existen a disposición del programador un conjunto de vistas heredables, todas ellas basadas en Vistas Genéricas de Django, y que siguiendo la política de CODENERIX son tan simples de usar como heredar de ellas.

En este artículo os hablaré sobre GenList, que es la vista genérica de CODENERIX que permite visualizar el listado de un modelo. GenList hereda directamente de ViewList de Django, por lo que todas las propiedades de un ViewList están disponibles en GenList. Veamos como sería el listado de un modelo “Contact”:

from codenerix.views import GenList
from app.models import Contact

class ContactList(GenList):
    model = Contact

Tan simple como eso, y el resultado visual equivaldría a algo parecido a esto (si usas los Ejemplos de CODENERIX):

Listado de contactos

De este modo hemos conseguido visualizar el modelo Contact del ejemplo “agenda” que tenéis aquí:

class Contact(CodenerixModel):
    first_name = models.CharField(verbose_name=_(u'Nombre(s)'), max_length=128)
    last_name = models.CharField(verbose_name=_(u'Apellidos'), max_length=128, blank=True, null=True)
    alias = models.CharField(verbose_name=_(u'Alias'), max_length=32, blank=True, null=True)
    organization = models.CharField(verbose_name=_(u'Organización'), max_length=64, blank=True, null=True)
    borndate = models.DateField(verbose_name=_(u'Cumpleaños'), blank=True, null=True)
    address = models.TextField(verbose_name=_(u'Dirección'), blank=True, null=True)
    created_by = models.ForeignKey(User, verbose_name=_(u'Creado por'), on_delete=models.CASCADE, related_name='contacts')

    def __fields__(self, info):
        return (
            ('first_name', _(u'Nombre(s)')),
            ('last_name', _(u'Apellidos')),
            ('alias', _(u'Alias')),
            ('organization', _(u'Organization')),
        )

Ya te habrás percatado que CODENERIX ha usado el método __fields__ del modelo para descubrir qué campos debe mostrar en el listado. Además se ha tomado la libertad de ofrecernos automáticamente un listado que permite ordenación ascendente/descendente acumulativa en cada uno de sus campos (ver columna “Last name” en la siguiente captura) y si pulsamos el icono del filtro (color celeste en la siguiente captura) incluso podremos ver que también se ha ocupado de permitirnos buscar en los campos uno por uno (ver columna “Organization” donde buscamos “Inc” limitando el listado a 2 resultados):

Listado de contactos filtrado con ordenación

En fin, que heredando de GenList hemos conseguido un gran trabajo con un resultado visual excelente para nuestros usuarios.

Pero GenList al igual que CodenerixModel permite usar los método: __fields____limitQ__, __searchF__ y __searchQ__. De este modo cuando se crea alguno de estos métodos en un GenList se usará el método de CodenerixModel o GenList según existan. De hecho su funcionamiento es exactamente igual que en CodenerixModel.

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

Por lo tanto ahora podemos decidir que nuestra vista se comporte de forma diferente a nuestro modelo. Esto se hace porque algunas veces queremos crear más de un listado del mismo modelo donde cada listado tiene propiedades diferentes, muestra información diferente dependiendo del usuario o bien usa un template distinto.

GenList además puede contener diferentes atributos que permiten configurar el funcionamiento de este de muchas maneras diferentes. Aquí dispones de una tabla de lo que existe a día de hoy:

Tabla de atributos permitidos por GenList
Atributo Descripción
annotations Es posible introducir agregadores dentro de los Queryset que usa CODENERIX (aprende más sobre agregadores en Django v2.0). Es muy útil cuando deseamos que calcule máximos, mínimos, medias o simplemente agrupe resultados por fecha, tipos u otros campos. Es posible que annotations también funcione como un método de la clase (ver ejemplo).
annotations = {
    'min_price': Min('books__price'),
    'max_price': Max('books__price')
}


def annotations(self, info):
    anot = []
    if info.user.is_superuser:
        anot['min_price'] = Min('books__price')
        anot['max_price'] = Max('books_price')
    return anot
appname Ignora el sistema automático de generación de URLs para que puedas usar en las urls una componente de aplicación diferente al de la aplicación de la vista. Esto es comúnmente usado para manejar el mismo modelo en diferentes aplicaciones (por ejemplo varios GenList). Sé cuidadoso, este campo altera el funcionamiento normal de CODENERIX y puede confundirte si lo usas incorrectamente. Puedes encontrar un ejemplo completo en funcionamiento en Github.
autofiltering En caso de estar a False desactiva el sistema de generación de filtros automáticos. Por defecto está a True.

autofiltering = False

client_context Es un diccionario y su contenido será enviado directamente en la respuesta JSON dentro de la estructura “meta” como “context“. Es muy útil para enviar datos de forma directa desde la vista al controlador o template de AngularJS.

client_context = {"var1": "value1"}

{
    "filter": ...,
    "meta": {
            ...
            "context": {
                "var1": "value1",
            },
            ...
        },
    "table": ...,
}
default_ordering El atributo puede ser una cadena o una lista de cadenas. La cadena puede comenzar con “-” o no. En este campo se indica el campo o los campos que van a usarse en la ordenación y el símbolo “-” indica que la ordenación se hará de forma descendente (sentido inverso), si no se indica nada se hará de forma ascendente. Si se da una lista, la ordenación se aplicará siguiendo escrupulosamente el orden en que aparecen los campos en esta lista.

default_ordering = '-name'

default_ordering = ['-name', 'date', '-xz']

default_rows_per_page Usado para indicar el número de filas por página que deseamos visualizar por defecto en este listado. El valor estándar usado en GenList es de 50.

default_rows_per_page = 150

export Fuerza la descarga de una lista como un fichero. Aquí se indica el formato. Por defecto es None.

export = "xlsx"

export_excel Muestra el botón de exportar a Excel. Por defecto está a True.

export_excel = False

export_name Nombre del fichero resultante de la exportación. Por defecto es “list“.

export_name = "listado"

extends_base Contiene la ruta del template base del que heredará el template de List. Generalmente acude a un “base/base.html“, pero cambiando esta variable se puede indicar que cargue otro fichero diferente para ofrecer al usuario una experiencia de entorno diferente.

 

extends_base = "base/base.html"

extra_context Contiene un diccionario que será mezclado con el contexto final que se entregará al render. Durante el proceso GenList puede sobreescribir las variables usadas aquí.
extra_context = {
    "variable1": "This is a CODENERIX variable that will be ready to use in the Template's Context",
    "variable2": { "a": 1, "b": 2},
    "variable3": 3.4,
}
field_check Activa la visualización del checkbox en los listados, es un checkbox que al ser marcado almacena el pk de la fila y puede ser usado en el $scope de AngularJS para hacer operaciones personalizadas. Tiene 3 estados posible este atributo:
  • None: no se muestra el checkbox.
  • False: se muestra el checkbox sin marcar.
  • True: se muestra el checkbox con todo marcado.

field_check = True

field_delete Activa la visualización del campo delete en los listados, es el icono de una papelera y cuando se hace click en él se envía al usuario a un borrado de dicho registro. Por defecto está a False.

field_delete = True

haystack Activa el soporte para Haystack en este listado. Por defecto está a False.

haystack = True

json Cuando este atributo está a True, la vista responderá siempre en formato JSON. Si la vista responde en formato JSON y se desea modificar la respuesta se puede trabajar sobre el método json_builder() de la vista, reescribiéndolo o simplemente reprocesando su resultado. Debemos saber que cuando las vistas reciben un parámetro “json” en GET/POST se activará automáticamente la respuesta JSON. También quedará activada este atributo cuando se reciba una cabecera “HTTP_X_REST” y su valor se pueda evaluar como un BooleanoTrue“. Es importante saber que por defecto este atributo se establece a True.

json = True

linkadd Muestra el botón de “Añadir” en los listados. Por defecto está a True.linkadd = False
linkedit Al hacer click en una fila de un listado pasa al modo edición de ese registro concreto. Por defecto está a True.

linkedit = False

model Modelo a usar.

model = Contact

modelname Ignora el sistema automático de generación de URLs para que puedas usar en las urls una componente de modelo diferente al del modelo de la vista. Esto es comúnmente usado para manejar el mismo modelo en diferentes aplicaciones (por ejemplo varios GenList). Sé cuidadoso, este campo altera el funcionamiento normal de CODENERIX y puede confundirte si lo usas incorrectamente. Puedes encontrar un ejemplo completo en funcionamiento en Github.
must_be_staff Si el usuario debe ser o no staff (capacidad para ver /admin/ de Django según el model User) para poder visualizar el contenido de este listado. Por defecto su valor es False.

must_be_staff = True

must_be_superuser Si el usuario debe ser o no superuser (según el model User de Django) para poder visualizar el contenido de este listado. Por defecto su valor es False.

must_be_superuser = True

ngincludes Mantiene el control sobre los posibles ng-includes que puedan aparecer en los parciales.

ngincludes = {'name':'path_to_partial'}

onlybase Provocará que GenListe actue sólo como un fichero base para otra vista vista. Por defecto está a False.

onlybase = True

permission Puede ser una cadena o una lista de cadenas. Cada cadena de texto representa un permiso. El usuario podrá ver el listado cuando tenga de forma directa al menos uno de los permisos que aparecen en este atributo.

permission = 'permission1'

permission = ['perm1', 'perm2', ...]

permission_group Puede ser una cadena o una lista de cadenas. Cada cadena de texto representa un permiso de grupo. El usuario podrá ver el listado cuando alguno de los grupos del usuario tenga alguno de los permisos que aparecen en este atributo.

permission_group = 'permission1'

permission_group = ['perm1', 'perm2', ...]

search_filter_button En caso de estar a False desactiva el botón de filtros por campos. Por defecto está a True.

search_filter_button = False

show_details Al hacer click en una fila de un listado pasa al modo detalle de ese registro concreto. Por defecto está a False.

show_details = True

show_modal Cuando está activado, empuja al sistema a renderizar en una ventana modal.

show_modal = True

static_app_row Carga el fichero de aplicación de AngularJS que aquí se indique, en caso contrario usa el por defecto: codenerix/js/apps.js.

static_app_row = "app/models_apps.js"

static_controllers_row Carga el fichero de controladores de AngularJS que aquí se indique, en caso contrario no carga ninguno.

static_controllers_row = "app/models_controllers.js"

static_filters_row Carga el fichero de filtros de AngularJS que aquí se indique, en caso contrario usa el por defecto: codenerix/js/rows.js.

static_filters_row = "app/models_rows.js"

static_partial_header Provoca que se cargue el parcial de cabecera de tabla indicado, si no se especifica no habrá parcial de cabecera.

static_partial_header = "app/models_header.html"

static_partial_row Provoca que se cargue el parcial de fila indicado en este atributo en vez del parcial por defecto: codenerix/partials/rows.html.

static_partial_row = "app/models_rows.html"

static_partial_summary Provoca que se cargue el parcial de cola de la tabla indicado en este atributo en vez del parcial por defecto: codenerix/partials/summary.html.

static_partial_summary = "app/models_summary.html"

template_base Es el template que actúa de base para el resto. Generalmente GenList renderiza mediante una serie de templates que van saltando de unos a otros por herencia. En el último paso los templates de CODENERIX llaman a un template base que por lo general suele ser “base/base” sin embargo esto se puede cambiar con este atributo para que use otro template de base. Recuerda que CODENERIX intentará localizar el resto del nombre del fichero “web” mediante los algoritmos de detección de templates.

template_base = 'frontend/web'

template_base_ext Extensión usada para template_base.

template_base_ext = 'html'

template_model Este es el template usado como punto de entrada para el renderizador de CODENERIX. Generalmente si no existe la ruta terminará usando “codenerix/list” (que terminará siendo “codenerix/list.html”) sin embargo es posible definir nuestro propio punto de entrada mediante este atributo. Esto es muy útil cuando queremos añadir “extra_css” o “extra_js” en el renderizado y nuestro template base soporta estos bloques.

template_model = 'app/contact_list'

template_model_ext Extensión usada para template_model.

template_model_ext = 'html'

 user Permite hacer que una vista se comporte de forma específica como un usuario concreto. Es muy útil cuando se desea visualizar el listado como si fuésemos otro usuario del sistema. Es un objeto User de Django que será usado en todos los procesos de la vista y entregado a todos los métodos que lo soliciten.

IMPORTANTE: el sistema de gestión de permisos no se ve afectado por esta variable, es decir, seguirá recibiendo el usuario que ha solicitado visualizar el listado y se le aplicarán a este las políticas de restricción de acceso, no al que aparece en este atributo.

user = User.objects.filter(is_superadmin=False).first()

vtable VTable es una tecnología mediante la cual el listado pasa a no estar paginado y CODENERIX hace una labor proactiva para estimar el ancho real del listado en pantalla y virtualiza el entorno de tal modo que precarga unas cuantas páginas en el navegador pero no todas y ajusta el scroll de la misma como debería ser si todos los registros estuviesen cargados de este modo cuando nos movemos en el listado CODENERIX carga páginas nuevas y descarga las antiguas para permitir al navegador funcionar a una velocidad adecuada de renderizado, pero el usuario percibe la sensación de estar trabajando en un listado inmenso y que todos los registros están ahí. Esta funcionalidad es experimental debido a que no ha sido testeada lo suficiente. Por defecto este atributo está a False.

vtable = True

ws_entry_point Este atributo define la ruta hacia el punto de entrada para las operaciones que desee realizar el listado, por ejemplo, al cargar el listado se acude a la ruta indicada por el “ws_entry_point”, el editar se repite su uso. Generalmente se usa en vistas extras que se añaden sobre una ya existente sobre el modelo y cuando se desea introducir un nuevo controlador de Angular o alguna otra cosa que modifique el comportamiento estándar de CODENERIX.

ws_entry_point = "planner/plane"

Además de estos atributos debemos conocer cómo GenList intenta localizar los templates para el renderizado de la página. GenList va a intentar construir una serie de URLs basándose en el perfil del usuario y su idioma, de tal modo que si el usuario es superadmin este tendrá un perfil “admin” y en caso contrario no tendrá perfil alguno. El idioma del usuario es detectado por CODENERIX y será entregado igualmente a la función de detección de templates.

Las posibles rutas para los templates serán (siguiendo este orden de prioridad):

  1. <ruta>/<fichero>.admin.<idioma>.html
  2. <ruta>/<fichero>.admin.html
  3. <ruta>/<fichero>.<usuario>.<idioma>.html
  4. <ruta>/<fichero>.<usuario>.html
  5. <ruta>/<fichero>.<idioma>.html
  6. <ruta>/<fichero>.html

De este modo si el usuario es “luis“, sí es superuser y su idioma en el momento de la consulta es el Español con código “es“, y la ruta al fichero es “base/home“, el sistema probará la existencia de los siguientes ficheros:

  1. base/home.admin.es.html
  2. base/home.admin.html
  3. base/home.luis.es.html
  4. base/home.luis.html
  5. base/home.es.html
  6. base/home.html

GenList también soporte la detección automática de ficheros estáticos para la carga de parciales y ficheros de AngularJS como lo son el fichero de aplicación, los controladores o los filtros. Para ello GenList intentará localizar estos ficheros en la carpeta de la aplicación dentro de “/static” (o cual sea la ruta de ficheros estáticos en disco según la constante STATIC_ROOT de settings de Django). Para esta detección usará el mismo algoritmo que usa la detección de templates. De este modo probará para cada fichero estático la posible existencia de este en disco y cargará estos en vez de los que existen por defecto en CODENERIX. Para la generar las rutas el sistema usará:

  1. La ruta: será la ruta de los ficheros estáticos, más el nombre de la APP donde se encuentre el modelo, más el nombre del modelo terminado en “s”.
  2. Tipo de fichero: podrá ser:
    1. <ninguno>: para el parcial que declara los filtros que se mostrarán por encima de la tabla del listado y generalmente para incluir la tabla con nginclude.
    2. rows: para el parcial de las filas
    3. header: para el parcial de la cabecera de la tabla (por debajo de la declaración de las columnas y por encima de las filas).
    4. summary: para el parcial del pie de la tabla (por debajo de las filas y por encima de las etiquetas de cierre de la tabla).
    5. app: para los ficheros de la aplicación.
    6. controllers: para los controladores.
    7. filters: para los ficheros de filtros de AngularJS.
  3. La extensión: será la de cada tipo de fichero:
    1. html: para los parciales
    2. js: para el resto
  4. El resultado final será el de concatenar:
    1. La ruta
    2. “_” más tipo de fichero
    3. La extensión.
  5. Para una app llamada “base“, con un modelo llamado “Contact“, para obtener el parcial de filas, de un usuario “luis“, que sí es superuser y visita el sitio en español, el sistema intentaría las siguientes rutas:
    1. static/base/contacts_rows.admin.es.html
    2. static/base/contacts_rows.admin.html
    3. static/base/contacts_rows.luis.es.html
    4. static/base/contacts_rows.luis.html
    5. static/base/contacts_rows.es.html
    6. static/base/contacts_rows.html
    7. static/codenerix/partials/rows.html (si las anteriores fallan)

Por último debemos hablar largo y tendido sobre el método json_builder() dado que es un método de GenList que permite controlar el resultado que se va a ofrecer al usuario. El método se define con 2 parámetros:

  • answer: contiene la respuesta pre-calculada por GenList a la que solo le falta el “body” que deberá situarse en answer[“table”][“body”].
  • context: contiene el contexto que estaría disponible para el render el generador del JSON en este caso. Dentro del contexto está el “object_list” que contiene el QuerySet resultado de la aplicación de los filtros y de lo que teóricamente GenList debería responder.

Existen varias técnicas para trabajar con json_builder():

Técnica 1:Body building on your own” en la que tú mismo construyes el cuerpo completo de la respuesta:

def json_builder(self,answer,context):

    # El body es un listado de registros (body o cuerpo del listado)
    body=[]

    # Procesamos cada elemento del object_list del contexto a fin de procesar todas las filas
    for o in context['object_list']:
        t={}
        t['name']=o.name
        t['surname']=o.surname
        phones=[]
        for o2 in o.phone.all():
            phone={}
            phone['country']=o2.country
            phone['prefix']=o2.prefix
            phone['number']=o2.number
            phones.append(t2)
        t['phone']=phones
        t['address']=o.address
        body.append(t)

    # Introduce el body en el cuerpo de la respuesta
    answer['table']['body']=body

    # Devuelve la respuesta ya lista
    return answer

En este ejemplo construimos una lista de forma manual donde nosotros somos responsables de rellenar cada fila o registro del body. Al terminar de procesar las filas que queremos enviar al navegador del usuario, cargamos estas filas (listado) en la respuesta y devolvemos esta.

Técnica 2:Body building with bodybuilder()” en la que usas el método bodybuilder() para ayudarte a construir el cuerpo de la respuesta:

def json_builder(self,answer,context):

    # Rellenamos directamente usando bodybuilder
    answer['table']['body']=self.bodybuilder(context['object_list'],{
        'id:user__register__id':None,
        'name':None,
        'surname':None,
        'phone': {
            'country':None,
            'prefix':None,
            'number':None,
            },
        'address':None,
        })

    # Answer the new context
    return answer

Cuando se usa bodybuilder() este espera recibir el listado de objetos a procesar y la forma de los registros que debe construir este. Lo que hace bodybuilder() es que por cada registro del object_list genera un entrada en el body, donde cada una de esas entradas son diccionarios que tienen la forma del segundo parámetro de la llamada, de este modo cada registro de este ejemplo tendría 5 claves en el diccionario:

  • id: sería el resultado de extraer del objeto el campo “user” y de este el campo “register” y de este el campo “id“. Si no se especificara como un aliasid:…”, la key contendría la ruta completa que sería “user__register__id“.
  • name: resultado de extraer del objeto el campo name o bien el resultado de llamar al método name().
  • surname: resultado de extraer del objeto el campo surname o bien el resultado de llamar al método surname().
  • phone: será un diccionario con 3 claves:
    • contry: resultado de extraer del objeto el campo phone, y de este el campo country o bien el resultado de llamar al método country().
    • prefix: resultado de extraer del objeto el campo phone, y de este el campo prefix o bien el resultado de llamar al método prefix().
    • number: resultado de extraer del objeto el campo phone, y de este el campo number o bien el resultado de llamar al método number().
  • address: resultado de extraer del objeto el campo address o bien el resultado de llamar al método address().

Técnica 3:Body building with autorules() and bodybuilder()” en la que usas autorules() y bodybuilder() para construir la respuesta:

def json_builder(self,answer,context):

    # Solicita las reglas a autorules
    rules=self.autorules()
    # Saca una de las claves del autorules (campo que no queremos en la respuesta)
    rules.pop('id')
    # Añade otra clave (campo que deseamos en la respuesta, en este caso un alias de user->id)
    rules['id:user__id']=None
    # Llama a bodybuider el nuevo conjunto de reglas
    answer['table']['body']=self.bodybuilder(context['object_list'],rules)

    # Devuelve el resultado
    return answer

En este caso lo que hacemos es pedir a GenList el conjunto de reglas que deberíamos usar, adaptamos ese conjunto de reglas a nuestro antojo y lo entregamos a bodybuilder() para que termine el trabajo de generar el cuerpo de la respuesta.

Notas sobre el optimizador de consultas

Debemos tener en cuenta que GenList incluye un optimizador de consultas para reducir del trabajo que se hace en Python y pasar parte de este al gestor de base de datos. Este optimizador no funciona cuando intervienen en el resultado datos que la base de datos no puede computar por si sola, esto es generalmente, cuando se incluyen métodos en los resultados y no son datos puramente almacenados en los objetos.

Otro detalle importante es que el optimizador convierte las filas del resultado en objetos de un diccionario, de tal modo que solo contiene los datos tal cual y no instancia los objetos del modelo como sería normal en Django.

Custom QuerySet o QuerySet personalizado

Finalmente cuando no podemos trabajar con los filtros o al introducir ciertas limitaciones (annotates por ejemplo) el resultado ya no funciona como deseamos….siempre nos quedará un “AS en la manga“, el método “custom_queryset(queryset, info)“. Este método debes reescribirlo en tu vista a fin de que CODENERIX lo use, dado que de otro modo operará normalmente. Este método recibe dos parámetros: el primero es el QuerySet calculado por GenList, el segundo es el objeto MODELINF que contiene información interna de la consulta, del modelo, argumentos, etc… y espera que devuelvas un QuerySet válido. Es por ello que puedes devolver el QuerySet que has recibido con algunos parámetros más aprovechando la potencia del ORM de Django o incluso podría ser un QuerySet de un modelo diferente.

Puedes leer más sobre las vistas genéricas de CODENERIX en “Como usar Codenerix GenCreate, GenUpdate, GenDetail y GenDelete“.

Show Comments (0)

Comments

Related Articles

¿Qué es CodenerixModel?
Codenerix

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

Posted on by Juanmi Taboada
Get your hardware for Arduino Underwater ROV
Underwater ROV

Get your hardware for Arduino Underwater ROV

In my last post, “Finishing the frame for an Underwater ROV”, I gave all details about the design I used to build the frame for Alioli Underwater ROV. In this post, I...

Posted on by Juanmi Taboada