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í:
Ya te habrás percatado que CODENERIXha 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ógico “AND“.
__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).
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.
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 Booleano “True“. 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.
Es el template que actúa de base para el resto. Generalmente GenListrenderiza mediante una serie de templates que van saltando de unos a otros por herencia. En el último paso los templates deCODENERIX 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 yCODENERIX 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):
<ruta>/<fichero>.admin.<idioma>.html
<ruta>/<fichero>.admin.html
<ruta>/<fichero>.<usuario>.<idioma>.html
<ruta>/<fichero>.<usuario>.html
<ruta>/<fichero>.<idioma>.html
<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:
base/home.admin.es.html
base/home.admin.html
base/home.luis.es.html
base/home.luis.html
base/home.es.html
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á:
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”.
Tipo de fichero: podrá ser:
<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.
rows: para el parcial de las filas
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).
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).
app: para los ficheros de la aplicación.
controllers: para los controladores.
filters: para los ficheros de filtros de AngularJS.
La extensión: será la de cada tipo de fichero:
html: para los parciales
js: para el resto
El resultado final será el de concatenar:
La ruta
“_” más tipo de fichero
La extensión.
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:
static/base/contacts_rows.admin.es.html
static/base/contacts_rows.admin.html
static/base/contacts_rows.luis.es.html
static/base/contacts_rows.luis.html
static/base/contacts_rows.es.html
static/base/contacts_rows.html
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:
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 alias “id:…”, 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 CODENERIXlo 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.
Open South Code – 5 y 6 Mayo (La Térmica, Málaga) EVENTO GRATUITO DE SOFWARE LIBRE CREATIVIDAD, TENDENCIAS EN PROGRAMACIÓN, FUTURO Tiene lugar la 2ª edición del Opensouthcode el 5 y 6 de Mayo en La Térmica de Málaga.Evento gratuito dedicado en exclusiva al software/hardware libre, consolidándose como el evento de referencia para todos los interesados en difundir, […]
Yes, I am a developer who writes its stuff with “vim”. What’s up? Several times I have discussed if Vim is or is not an IDE. Many people don’t understand why we love it so much vim, and they even think Vim is just a text editor. Despite what they think, I can prove Vim […]