Saltar a contenido

Python/Django

Enlaces

Cheatsheet

  • Classy Class-Based Views: Detailed descriptions, with full methods and attributes, for each of Django's class-based generic views.

Aprendizaje

Módulos interesantes

Entorno Django

Para tener un entorno aislado (sin depender con los paquetes y versiones del sistema), instalar primero pip de Python3. Se puede hacer instalando el paquete python3-pip de Ubuntu, pero dado que interesa actualizar a la última versión, es mejor instalarlo de forma independiente bajando el script get-pip.py de aquí ejecutándolo así:

1
$ sudo python3 get-pip.py

Después ejecutar en terminal:

1
2
3
4
$ sudo pip3 install virtualenv
$ virtualenv --python=`which python3` djangodev
$ source djangodev/bin/activate
(djangodev) $ pip install Django

Cada vez que se quiera adaptar el entorno de la sesión del terminal a este entorno aislado, hay que ejecutar el penúltimo comando anterior (source).

Entorno Django en Synology NAS

  1. Instalo módulo Python3 desde el Centro de Paquetes.
  2. Instalo pip en Python3:

    1
    2
    3
    4
    $ wget -nd https://bootstrap.pypa.io/get-pip.py
    $ python get-pip.py
    $ cd /usr/local/bin
    $ sudo ln -s /volume1/@appstore/py3k/usr/local/bin/pip3
    
  3. Instalo virtualenv:

    1
    2
    $ sudo pip3 install virtualenv
    $ python3 /volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages/virtualenv.py --python=`which python3` djangodev
    
  4. Arranco el entorno virtual e instalo Django:

    1
    2
    $ source djangodev/bin/activate
    (djangodev) $ pip install Django
    

Creación de proyecto Django

Desde el directorio donde queremos que se cree ejecutamos:

1
(djangodev) $ django-admin startproject project01

Creación de aplicación Django

Desde el directorio del proyecto (donde se encuentre el fichero manage.py) ejecutamos:

1
(djangodev) $ python manage.py startapp app01

Para incorporar los modelos de la nueva aplicación al mantenimiento automático que proporciona el módulo admin de Django, hay que incorporar al fichero project01/settings.py lo siguiente en la sección INSTALLED_APPS:

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
    'app01.apps.App01Config',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Luego ejecutamos el siguiente comando para generar las migraciones a partir de los modelos definidos en la app:

1
(djangodev) $ python manage.py makemigrations app01

Finalmente ejecutamos las migraciones propiamente dichas:

1
(djangodev) $ python manage.py migrate

Para poder utilizar el módulo admin de Django, hay que crear al menos un usuario:

1
(djangodev) $ python manage.py createsuperuser

Pienv

Enlaces

Instalación

1
$ sudo -H pip install pipenv

Creación de entorno para Python3

1
2
$ pipenv install --three
$ pipenv shell

Creación de entorno con versión específica de Python y de paquetes

  1. Instalar algunos paquetes necesarios:

    1
    2
    $ sudo apt-get install build-essential checkinstall
    $ sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
    
  2. Bajamos la versión source de aquí.

  3. Descomprimimos:

    1
    $ tar -xzvf Python-3.6.6.tgz
    
  4. Renombramos el directorio de las fuentes para no confundirlo con el de los binarios:

    1
    $ mv Python-3.6.5 Python-3.6.5_src
    
  5. Creamos el directorio para los binarios:

    1
    $ mkdir Python-3.6.5
    
  6. Entramos en el directorio de las fuentes y compilamos:

    1
    2
    3
    4
    5
    $ cd /home/usuario/Python-3.6.5_src
    $ ./configure --disable-ipv6 --prefix=/home/usuario/Python-3.6.5
    $ make
    $ make test
    $ make install
    
  7. Creamos el entorno:

    1
    2
    3
    4
    5
    $ cd directorio_proyecto
    $ pipenv install --python /home/usuario/Python-3.6.5/bin/python3
    $ pipenv install Django==1.11.12
    $ pipenv install _resto_de_paquetes_
    $ pipenv shell
    

Ejemplo de creación de entorno Django desde cero

Creamos repositorio git (por ejemplo remote_james) y lo sincronizamos (por ejemplo con /home/edumoreno/git/remote_james).

Ejecutamos (en el ejemplo se usa una versión de Python específica compilada anteriormente en la máquina):

1
2
3
4
5
6
$ cd ~/git/remote_james
$ pipenv install --python /home/edumoreno/Python-3.6.5/bin/python3
$ pipenv shell
$ pipenv install django
$ django-admin startproject remote_james .
$ python manage.py startapp james

Incorporamos nueva app en fichero settings.py:

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
    'james.apps.JamesConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Ejecutamos:

1
2
3
4
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver 0.0.0.0:8000

Personalización del modelo User

Es recomendable cuando se empieza un proyecto, sustituir la gestión del modelo User por uno propio, ya que si se quiere hacer más adelante con la aplicación ya en marcha, es muy complicada la migración entre tablas. Un par de buenos artículos sobre el tema son:

Creamos el modelo así:

1
2
3
4
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

Y lo activamos definiendo la siguiente propiedad en el fichero settings.py:

1
AUTH_USER_MODEL = 'app01.User'

Si no queremos perder los formularios especiales de la aplicación admin para el modelo original, además hay que personalizar la clase UserAdmin de la siguiente forma:

1
2
3
4
5
6
from django.contrib.auth.admin import UserAdmin

class MyUserAdmin(UserAdmin):
    model = User

admin.site.register(User, MyUserAdmin)

Gestión de migraciones

Una vez generadas las migraciones, si queremos obtener el código SQL a que equivalen hay que ejecutar el comando (en el ejemploo solicitamos el código correspondiente a la migración 0008):

1
(djangodev) $ python manage.py sqlmigrate app01 0008

Para situarnos en un punto concreto de la serie de migraciones (deshaciendo por tanto las posteriores cuyos ficheros se podrán borrar):

1
(djangodev) $ python manage.py migrate app01 0010

Para mostrar los nombres de todas las migraciones (si no ponemos el nombre al final se muestran las de todo el sitio):

1
(djangodev) $ python manage.py showmigrations app01

Para deshacer todas las migraciones:

1
(djangodev) $ python manage.py migrate app01 zero

Para crear una migración vacía que podremos utilizar para hacer transferencias de datos (ver ejemplo aquí):

1
(djangodev) $ python manage.py makemigrations app01 --empty

Para saber cómo gestionar las migraciones en diferentes ramas del desarrollo y evitar/solucionar conflictos, leer este artículo.

Formateo de cadenas

  • Con tuplas (no se puede cambiar el orden de los parámetros):

    1
    "%s parte de %s la cadena" % (var1, var2)
    
  • Con diccionario (se puede cambiar el orden de los parámetros):

    1
    "%(par1)s parte de %(par2)s la cadena" % {'par1': var1, 'par2': var2}
    

Directorio instalación Django

Para averiguar dónde están los ficheros de Django, ejecutar el siguiente comando:

1
(djangodev) $ python -c "import django; print(django.__path__)"

Tutorial Django

Tutorial Django de Mozilla Developer Network

Referencia Django

Temas interesantes

Uso de Class Views

Básicas

Documentadas aquí.

TemplateView

La más sencilla. Sólo necesita definir la propiedad template_name apuntando a la plantilla. Para añadir datos al contexto (esto funciona en todas las view classes) se puede definir la función get_context_data (ver ejemplo e apartado Passing variables to the template aquí)

RedirectView

Se puede usar tal cual para definir redirecciones en los ficheros url.py. Por ejemplo:

1
2
3
4
urlpatterns = [
    url(r'^$', RedirectView.as_view(url=reverse_lazy('admin:index'))),
    ...
]

Pero también se puede heredar de ella para por ejemplo hacer una vista "proxy" para actualizar un contador de visitas a otra vista, como el ejemplo que hay aquí.

Genéricas para visualización de modelos

Son las diseñadas para generar listados y vistas de detalle de un modelo. Son más adecuadas para visualización, como la que se haría por ejemplo en un blog. Documentadas aquí.

En las dos clases de este tipo, sólo se necesita definir la propiedad model.

DetailView

La plantilla se define por convención añadiendo _detail.html al nombre del modelo. Dentro de la plantilla, el objeto que se quiere representar aparece en la variable de contexto object.

ListView

La plantilla se define por convención añadiendo _list.html al nombre del modelo. Dentro de la plantilla, la colección de objetos que se quiere representar aparece en la variable de contexto object_list.

Genéricas para edición de modelos

Permiten personalizar las vistas típicas del mantenimiento de un modelo (creación, actualización y borrado). Documentadas aquí.

En cualquiera de las cuatro siguientes clases, en la plantilla incluiremos los tags HTML para el formulario (no se generan automáticamente porque la página podría contener varios formularios o por si no estamos generando HTML). Se suele hacer algo así:

1
2
3
4
<form action="" method="post">{{ "{% csrf_token " }}%}
    {{ "{{ form " }}}}
    <input type="submit" value="Save" />
</form>

El tag para el formulario tiene tres variantes:

  • {{ "{{ form.as_p " }}}}: Cada campo del formulario se genera como un <p>.
  • {{ "{{ form.as_table " }}}}: Cada campo del formulario se genera como una fila de una tabla.
  • {{ "{{ form.as_ul " }}}}: Cada campo del formulario se genera como una lista sin numerar.

Para máximo control siempre podemos generar cada campo del formulario por separado o incluso escribir el formulario completo a mano (ver documentación Working with forms).

FormView

Gestiona la vista con un formulario genérico. Si se produce error en la validación, vuelve a cargar la misma URL con los campos rellenos e información sobre los errores; Si se supera la validación se redirije a otra URL. En el contexto de la plantilla tendremos el formulario bajo la variable form.

Necesita definir las propiedades siguientes:

  • form_class: Clase formulario.
  • success_url: URL a la que se redirije en caso de superar la validación.
  • template_name: Plantilla.

También es interesante definir el método siguiente:

  • form_valid: Se ejecutará cuando el formulario se valide correctamente. Obtendremos los datos del formulario del diccionario self.form.cleaned_data.

Ver ejemplos aquí y en apartado Now for a FormView de aquí.

CreateView

Para la creación de un modelo.

UpdateView

Para la actualización de un modelo.

DeleteView

Para el borrado de un modelo.

Empaquetadas

FilterView

Es una especie de ListView con filtros. Ver documentación aquí.

Idioms

  • Retorno seguro del primer elemento de una lista: return (get_list()[:1] or [None])[0]
  • Valor predeterminado en caso de que variable sea None: return variable or 0
  • Acceso seguro al atributo de un objeto: getattr(object, name[, default]) La función getattr nos permite indicar un valor predeterminado si un objeto no tiene el atributo que buscamos. Funciona también si el objeto es None, lo que nos permite acceder de forma segura a un objeto cuando no sabemos si existe.
  • Suma de una propiedad de una lista de objetos: suma = sum(logro.puntos for logro in logros_totales)
  • Suma de una propiedad de una lista de objetos: suma = sum(list(map(lambda x: x.puntos, logros_totales)))
  • Operador ternario: valor_cuando_true if condición else valor_cuando_false
  • List comprehensions: new_list = [expression(i) for i in old_list if filter(i)]
  • Filtro de los elementos de una lista que contienen un fragmento de cadena, por ejemplo las constantes de pygame que empiezan por K_: filter(lambda x:'K_' in x, dir(pygame))
  • Switch/case: Se suele utilizar un diccionario como se explica aquí.

Template filters

  • {{ variable|default_if_none:"" }}: Muestra el texto pasado como argumento si variable vale None.
  • {{ variable|default:"" }}: Muestra el texto pasado como argumento si variable es evaluada como False (cadena vacía, lista vacía, etc.).
  • {{ variable_date|date:"d-M" }}: Formatea una variable de tipo date.
  • {{ variable_list_string|join:", " }}: Concatena una lista de strings.
  • {{ variable_string|urlencode }}: Codifica una string para que sea segura en una URL.
  • {{ variable_string|unicode_decode }}: Decodifica un string en unicode.
  • {{ variable_string|safe }}: Genera los caracteres para poder interpretar el string como HTML en lugar de escaparlos como se haría normalmente.
  • {{ variable_list|length }}: Número de elementos de la lista.
  • {{ variable_int|add:1 }}: Añade 1 a la variable entera.

Snippets

  • Colección completa de objetos de un modelo: Unidad.objects.all()
  • Colección completa de objetos de un modelo ordenada: Unidad.objects.all().order_by('nombre')
  • Colección filtrada de objetos de un modelo: Actividad.objects.filter(fecha__year=2017)
  • Filtro OR (es necesario hacer from django.db.models import Q): GrupoUnidades.objects.filter(Q(pausada=True) | Q(fecha_fin__isnull=False))
  • Instancia concreta de un objeto: Unidad.objects.get(pk=3)
  • Servidor HTTP en el directorio actual: python -m SimpleHTTPServer 8080
  • Convertir un set en un list ordenado: una_lista = sorted(un_set, key=lambda x: x.position)
  • Template filters:
    • Valor predeterminado: {{ "{{ elemento_de_context|default_if_none:'Valor predeterminado' " }}}}
  • Agregar elementos a un diccionario de listas creando las claves si no existen (fuente):dic.setdefault(key,[]).append(value)

Filtro en ListView

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# urls.py
from django.conf.urls import url
from . import views

app_name = 'lms'
urlpatterns = [
    url(r'^asistencia/(?P<pk>\d+)/grupo/(?P<admin>\w+)/$', views.AsistenciaGrupoView.as_view(), name='asistencia_grupo')
]


# views.py
class AsistenciaGrupoView(generic.ListView):
    template_name = 'lms/asistencia_grupo.html'

    def get_queryset(self):
        semanas = int(self.request.GET.get('semanas', '12'))
        return Jornada.objects.filter(grupo__id=self.kwargs['pk'], fecha__gte=datetime.today() - timedelta(weeks=semanas)).order_by('fecha')

    def get_context_data(self, **kwargs):
        context = super(AsistenciaGrupoView, self).get_context_data(**kwargs)
        # Lista de semanas que mostraremos en el combo para filtrar
        context['lista_semanas'] = [(4, 1),
                                    (8, 2)]
        # Parámetro de filtrado del querystring (12 semanas como valor predeterminado)
        context['semanas'] = int(self.request.GET.get('semanas', '12'))
        # Valor del PK del grupo recogido de la URL
        context['pk'] = self.kwargs['pk']

        return context


# template
    <form method="get" action="{{ "{% url 'lms:asistencia_grupo' pk " }}%}">
        <label for="semanas_id">Meses</label>
        <select class="form-control" name="semanas" id="semanas_id" onchange='if(this.value != 0) { this.form.submit(); }'>
            {{ "{% for v, d in lista_semanas " }}%}
            <option value="{{ "{{ v " }}}}"{{ "{% if semanas == v " }}%} selected='selected'{{ "{% endif " }}%}>{{ "{{ d " }}}}</option>
            {{ "{% endfor " }}%}
        </select>
    </form>

Instrospección

Lista de atributos

Podemos obtenerlos de dos maneras:

  • objeto.__dict__
  • vars(objeto)

Lista de atributos y métodos

  • dir(objeto)

Logging

Log a fichero

Configurar un logger en el fichero de settings correspondiente añadiendo esto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': 'debug.log',
        },
    },
    'loggers': {
        'log': {
            'handlers': ['file'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

En el módulo donde queramos obtener log, añadir al principio lo siguiente:

1
2
3
import logging

logger = logging.getLogger('log')

Y dentro de la función donde queramos emitir algo al log:

1
logger.debug('lo que sea')

Log a consola

Si sólo queremos imprimir en consola una traza rápida, es más fácil escribiendo simplemente:

1
print('lo que sea', file=sys.stderr)

Traza de queries a consola

Configurar el siguiente logger en el fichero de settings correspondiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
LOGGING = {
    'version': 1,
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        }
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
        }
    },
    'loggers': {
        'django.db.backends': {
            'level': 'DEBUG',
            'handlers': ['console'],
        }
    }
}

pip

Instalación versión específica de un paquete

1
$ pip install Django==1.11.6

Usando Webpack para construir el frontend

Enlaces:

Procedimiento

  1. Instalamos Vue-CLI en la máquina de desarrollo:
    1
    2
    sudo npm install -g @vue/cli
    sudo npm install -g @vue/cli-init
    
  2. Generamos el scaffolding para vue y webpack ejecutando lo siguiente desde el directorio que queremos que contenga el proyecto (en el ejemplo lo llamaremos vue_test):
    1
    vue init webpack vue_test
    
  3. Durante la generación del proyecto se nos hacen las siguientes preguntas:
    1. Vue build: Elegimos Runtime + Compiler
    2. Install vue-router?: Elegimos Y
    3. Use ESLint to lint your code?: n
    4. Set up unit tests: n
    5. Setup e2e tests with Nightwatch?: n
    6. Should we run npm install for you after the project has been created?: Yes, use NPM
  4. Generamos el scaffolding para Django:
    1
    2
    cd vue_test/
    django-admin startproject vue_test .
    
  5. Instalamos el plugin de webpack webpack-bundle-tracker y el módulo Django django-webpack-loader (con pipenv):
    1
    2
    npm install --save-dev webpack-bundle-tracker
    pipenv install django-webpack-loader
    
  6. Añadimos webpack_loader a las aplicaciones Django:
    1
    2
    3
    4
    5
    # ./vue_test/settings.py
    INSTALLED_APPS = [
    'webpack_loader',
    #...
    ]
    
  7. Configurar webpack para que use BundleTracker:
    1
    2
    3
    4
    5
    6
    7
    8
    // build/webpack.base.conf.js
    let BundleTracker = require('webpack-bundle-tracker')
    module.exports = {
      // ...
      plugins: [
        new BundleTracker({filename: './webpack-stats.json'}),
      ],
    }
    
  8. Definir las rutas estáticas:
    • Configuración webpack:
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      // ./config/index.js
      module.exports = {
        build: {
          assetsRoot: path.resolve(__dirname, '../dist/'),
          assetsSubDirectory: '',
          assetsPublicPath: '/static/',
          // ...
        },
        dev: {
          assetsPublicPath: 'http://localhost:8080/',
          // ...
        }
      }
      
    • Configuración Django:
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      # ./vue_test/settings.py
      STATICFILES_DIRS = (
          os.path.join(BASE_DIR, 'dist'),
          os.path.join(BASE_DIR, 'static'),
      )
      STATIC_ROOT = os.path.join(BASE_DIR, 'public')
      STATIC_URL = '/static/'
      
      WEBPACK_LOADER = {
          'DEFAULT': {
              'BUNDLE_DIR_NAME': '',
              'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
          }
      }
      
  9. Configuración webpack para que resuelva los ficheros estáticos desde /static:
    1
    2
    3
    4
    5
    6
    7
    8
    // build/webpack.base.conf.js
    module.exports = {
      resolve: {
        alias: {
          // ...
          '__STATIC__': resolve('static'),
        },
    }
    
  10. Configuración del Hot Reload:
    1
    2
    3
    4
    5
    6
    7
    // build/webpack.dev.conf.js
    devServer: {
      headers: {
        "Access-Control-Allow-Origin":"\*"
      },
      // ...
    }
    

Los contenidos estáticos se servirán desde uno y otro entorno de la siguiente forma:

1
2
3
4
5
<!-- in a Django template -->
<img src="{{ "{% static 'logo.png' " }}%}">

<!-- in a Vue component -->
<img src="~__STATIC__/logo.png">

Finalmente, para servir los contenidos cliente en desarrollo hay que ejecutar:

1
2
cd vue-test
npm run dev

Y para servir los contenidos del backend:

1
2
3
cd vue-test
pipenv shell
python manage.py runserver

Cuando hagamos npm run build todo lo gestionado por webpack se compilará dentro del directorio dist.

La documentación de webpack se encuentra aquí.

Instalación de JupyterHub

Enlaces

Niveles de instalación

Instalación The Littlest JupyterHub

  1. Instalación de dependencias y JupyterHub:

    1
    2
    $ sudo apt-get install python3 git curl
    $ curl https://raw.githubusercontent.com/jupyterhub/the-littlest-jupyterhub/master/bootstrap/bootstrap.py | sudo -E python3 - --admin niubit
    
  2. Configuración de puerto de Traefik en:

    1
    /opt/tljh/state/traefik.toml
    
  3. Reinicio de traefik:

    1
    $ sudo systemctl restart traefik.service
    
  4. Si queremos detener el servicio hay que desactivar los dos siguientes:

    1
    2
    $ sudo systemctl disable jupyterhub.service 
    $ sudo systemctl disable traefik.service 
    

Configuración de fichero de log

  1. Añadimos lo siguiente al final del fichero /opt/tljh/hub/lib/python3.6/site-packages/tljh/jupyterhub_config.py:

    1
    c.JupyterHub.extra_log_file = '/var/log/jupyterhub.log'
    
  2. Reiniciamos el servicio:

    1
    $ sudo systemctl restart jupyterhub.service
    

Añadiendo módulos Python al sistema

  1. Abrir una terminal en Jupyter con un usuario admin.
  2. Ejecutar:

    1
    $ sudo -E pip install <paquete>
    

Visualización de Jupyter Notebooks

En este artículo mencionan las siguientes opciones:

  • Share your notebook file with gists or on github, both of which render the notebooks. See this example.
  • If you upload your notebook to a github repository, you can use the handy mybinder service to allow someone half an hour of interactive Jupyter access to your repository.
  • Store your notebook e.g. in dropbox and put the link to nbviewer. nbviewer will render the notebook from whichever source you host it.

Impresión con formato

Si queremos que la salida por consola tenga formato, hay que utilizar la codificación de tipo Set Graphics Mode que se lista en esta página con el código \033 como secuencia de Esc.

Por ejemplo si queremos destacar en negrita la primera palabra de la siguiente frase lo haremos así:

1
print("\033[1m;ERROR\033[0m Algo ha salido mal.")

Queries complejas

Los parámetros que aplicamos al método filter se aplican con un and entre ellos a la query resultante. Para hacer queries más complejas (con and y or con prioridades establecidas con paréntesis) hay que utilizar los objetos Q.