SecNot

jul 14, 2015

Two Scoops of Django 1.8

Portada del libro Two Scoops of Django 1.8

Se acaba de publicar la nueva entrega de Two Scoops of Django la guía definitiva de buenas practicas para Django 1.8, tengo la versión anterior y he comprado esta. No es un libro para principiantes, pero se lo recomiendo a todos los desarrolladores experimentados que seguro que encuentran algún truco o consejo útil. Además la versión de Django 1.8 tiene la particularidad de ser “Long-Term Support” (LTS) por lo que tiene soporte de 3 años, así que el libro va a estar actualizado durante más tiempo que versiones anteriores.

Por ahora sólo está disponible en inglés y lo puedes comprar en Amazon

Click to read and post comments

jul 12, 2015

Vista para la descarga de archivos generados dinámicamente

Hoy he estado programando en Django una vista para la descarga de informes, en la que los usuarios registrados de una página pueden pedir un informe de la actividad para un determinado año. La particularidad es que esos informes se generan de forma dinámica en el momento de la petición, nada complicado pero antes de empezar a programar he buscado que paquetes había disponibles, y me he encontrado django-downloadview. Con eso y un poco de código extra para comprobar que el usuario está autenticado he solucionado el problema. Algunas veces CBV parecen magia.

#views.py
from django.shortcuts import render
from django.core.files.base import ContentFile
from django_downloadview import VirtualDownloadView
from django.http import HttpResponseForbidden, HttpResponseServerError


class LoginRequiredMixin(object):

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated():
            return HttpResponseForbidden()
        else:
            return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)


class DownloadReportView(LoginRequiredMixin, VirtualDownloadView):

    def generar_report(self, user, year):
        # Generar contenido del informe para el usuario y fecha
        return "Report content for {} {}".format(user.username, year)

    def get_file(self):
        # Metodo de VirtualDownloadView que devuelve el archivo virtual
        report_name = 'report.txt'
        return ContentFile(self.report_content, name=report_name)

    def get(self, request, *args, **kwargs):

        report_year = kwargs.get('year', None)

        # El contenido del informe se genera aquí en lugar de en get_file, para
        # simplificar la
        try:
            self.report_content=self.generate_report(request.user, report_year)
        except Exception:
            return HttpResponseServerError("There was an error while generating the report")

        return super(DownloadReportView, self).get(request, *args, **kwargs)

Y el archivo urls.py para quien le pueda interesar:

#urls.py
from django.conf.urls import url
from .views import DownloadReportView

urlpatterns = [
        url( # Handle report downloads
                regex = r'^download_report/(?P<year>[0-9]{4})$',
                view  = DownloadReportView.as_view(),
                name  = 'download_user_report'),
]
Click to read and post comments

jul 08, 2015

Crea un servicio de Hosting VPS con Django I

Este es el primer artículo de una series en la que describo como usar Django en conjunción con DigitalOcean para crear tu propio proveedor de de servidores VPS. Algo que hace años hubiera sido impensable sin un equipo de administradores y programadores, y que hoy puede implementarse con unos pocos miles de lineas de código python.

Este artículo es una introducción al API de DigitalOcean y a su libreria de python, la cual usaré en futuros artículos para implementar el servicio.

API de DigitalOcean

El API de Digitalocean actualmente se encuentre en su segunda versión y es muy extenso, no solo permite crear, destruir, y manejar Droplets (nombre que usa para sus VPS), sino que permite controlar todos los aspectos de sus servicios, desde la gestión de DNS, hasta la creación backups, o gestión imágenes. Puede acceder a la documentación del API aquí pero por suerte ya existe una libreria para python llamada python-digitalocean que simplifica enormemente el proceso, crear un Droplet puede ser tan sencillo como:

import digitalocean
droplet = digitalocean.Droplet(token="digitalocean-personal-access-token",
                           name='servidor.midominio.com',
                           region='ams2', # Amsterdam
                           image='ubuntu-14-04-x64', # Ubuntu 14.04 x64
                           size_slug='512mb')
droplet.create()
droplet_id = droplet.id

El identificador devuelto durante la creación del Droplet luego puede ser usado para manejarlo, en el siguiente ejemplo se reinicia, apaga y después elimina:

import digitalocean

manager = digitalocean.Manager(token="digitalocean-personal-access-token")
droplet = manager.get_droplet(droplet_id)

droplet.reboot()
droplet.power_off()
droplet.destroy()

La librería también permite redirigir dominios gestionados desde la DNS de DigitalOcean, por ejemplo si tienes la dirección IP y el hostname de un Droplet puedes redirigir un subdominio:

import digitalocean
import tldextract

droplet_ip = "83.54.134.34"
droplet_hostname = "servidor.midominio.com"

# Crear subdominio usando el hostname del servidor
d_subdomain, d_domain, d_suffix = tldextract.extract(droplet_hostname)

domain = digitalocean.Domain(token="digitalocean-personal-access-token",
                            name=d_domain+"."+d_suffix)

result = domain.create_new_domain_record(type="A", name=d_subdomain, data=droplet_ip)

domain_record_id = result['domain_record']['id']

# Eliminar registro creado
record = digitalocean.Record(
            id=domain_record_id,
            domain_name=d_domain+"."+d_suffix,
            token="digitalocean-personal-access-token")

record.destroy()

Y con esto tenemos los fundamentos para manejar Droplets, pero antes de poder entregarlo al usuario es necesario configurarlo/customizarlo al gusto del usuario.

Metadata y Cloudinit

DigitalOcean incluye un API de Metadatos, que permite a los Droplets acceder a sus propios datos, suministrar datos de usuario durante su creación, y procesar esos datos usando CloudInit si es que contienen alguno de los formatos soportados. En el siguiente ejemplo se envía una cadena que contiene dos variables:

import digitalocean

data = 'USUARIO="admin"\nCLAVE="secreto"'

droplet = digitalocean.Droplet(token=settings.DIGITALOCEAN_SECRET_TOKEN,
                           name='servidor.midominio.com',
                           region='ams2', # Amsterdam
                           image='ubuntu-14-04-x64',
                           size_slug='512mb',
                           user_data=data)
droplet.create()
droplet_id = droplet.id

Desde el droplet se puede acceder a los datos suministrados:

$ curl -sS http://169.254.169.254/metadata/v1/user-data
USUARIO="admin"
CLAVE="secreto"

CloudInit como su nombre indica es una herramienta para inicializar servidores en la nube, la primera vez que arranca el servidor extrae los datos de configuración, y si contienen alguno de los formatos soportados los procesa. Uno de esos formatos es shell script, por ejemplo podemos enviar el siguiente script para crear un archivo de swap de 2GB:

#!/bin/bash
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
echo '/swapfile none swap sw 0 0' >>/etc/fstab

Cloud Config es otro formato muy versátil, que permite realizar la mayoría de las tareas más comunes de configuración de forma muy sencilla, y ademas proporciona funcionalidad extra con su sistema de módulos, en el siguiente ejemplo se añade los mismos 2GB de swap:

#cloud-config
swap:
    filename: /swapfile
    size: 2000000000

Por último el formato Include File consiste en una lista de URLS cuyo contenido es descargado y procesado usando las mismas reglas que si fueran los datos proporcionados con user-data, por ejemplo:

#include
https://myvpsservice.com/scripts/swap-2GB
https://myvpsservice.com/scripts/install-apache
https://myvpsservice.com/scripts/add-keys

Seguro que puedes ver el potencial de éste formato para esta aplicación. Si te interesa conocer el resto de los formatos y módulos disponibles te recomiendo que heches un vistazo a la documentación de cloud-init.

En la segunda parte de esta serie profundizare más en como implementar un sistema de configuración de VPS con Django.

Click to read and post comments

jun 24, 2015

DNIField para Django

Si vas a crear un formulario donde se pueda introducir un DNI/NIF no necesitas crear tu propio DNIField o programar un validador, ya existe en un paquete llamado django-localflavor que agrupa ese tipo de campos para los distintos países.

Por ejemplo para España incluye:

  • ESPhoneNumberField - Números de teléfonos fijos y móviles.
  • ESIdentityCardNumberField - Numero de identificación NIF/CIF/NIE.
  • ESCCCField - Código de cuenta de cliente en formato EEEE-OOOO-CC-AAAAAAAAAA
  • ESProvinceSelect - Selección de provincia.

Instalación

Como siempre pip es la mejor opción para instalar el paquete:

$ pip install django-localflavor

Luego añade 'localflavor' a INSTALLED_APPS en settings.conf:

INSTALLED_APPS = (
    # ...
    'localflavor',
)

Uso

Un ejemplo de como crear un formulario sencillo:

#forms.py
from django import forms
from localflavor.es.forms import ESIdentityCardNumberField, ESPhoneNumberField, ESProvinceField

class ClienteForm(forms.Form):
    nombre = forms.CharField(max_length=200)
    dni = ESIdentityCardNumberField(only_nif=True)
    telefono = ESPhoneNumberField()
    ...

Al campo dni se le ha añadido la opción only_nif=True para que sólo acepte NIF y NIE (Numero identificación extranjeros), sin ella también aceptaría códigos CIF.

Click to read and post comments

jun 22, 2015

Inicializar la base de datos en Django

Algunas veces es necesario inicializar la base de datos antes de poder usar una aplicación, si esta aplicación no va a reutilizarse no es ningún problema, en cambio si es algo que planeas usar en varios proyectos, es mucho mas práctico añadir los datos de inicialización en la misma aplicación y cargarlos usando loaddata. Imaginemos una aplicación para la gestión de los productos de una tienda, en la que tenemos los siguientes modelos:

#tienda/models.py
from django.db import models

class Categoria(models.Model):

    nombre = models.CharField(max_length=80)
    slug = models.SlugField(unique=True, db_index=True)
    descripcion = models.TextField(max_length=2000)


class Producto(models.Model):

    nombre = models.Charfield(max_length=200)
    descripcion models.TextField(max_length=2000)
    precio = models.DecimalField()
    categoria = models.ForeignKey(Categoria)

Django permite crear un directorio llamado fixtures dentro de la aplicación donde almacenar archivos de datos que luego pueden ser volcados a la base de datos para inicializarla.

Estos archivos pueden crearse usado tres formatos JSON, YAML, XML, yo creo que los dos primeros son la mejor opción, más fáciles de leer y modificar manualmente. Veamos un ejemplo para JSON:

[
{
    "model": "tienda.categoria",
    "fields": {
        "nombre": "Ordenadores portatiles",
        "slug": "portatiles",
        "descripcion": "Ordenadores portatiles de 13 a 17 pulgadas",
    },
    "pk": 100000
},
{
    "model": "tienda.categoria",
    "fields": {
        "nombre": "Tablets",
        "slug": "tablets",
        "descripcion": "Tablets android",
    },
    "pk": 100001
},
{
    "model": "tienda.producto",
    "fields": {
        "nombre": "ASUS MeMO Pad 10 ME103K 16GB",
        "precio": "120.50",
        "descripcion": "La ASUS MeMO Pad 10 se creó pensando en...",
        "categoria": 100001,
    }
}
]

y los mismos datos en YAML:

- model: tienda.categoria
pk: 100000
fields:
    nombre: "Ordenadores portatiles"
    slug: "portatiles"
    descripcion: "Ordenadores portatiles de 13 a 17 pulgadas"

-model: tienda.categoria
pk: 100001
fields:
    nombre: "Tablets"
    slug: "tablets"
    descripcion: "Tablets Android"

- model: tienda.producto
pk: 100000
fields:
    nombre: "ASUS MeMO Pad 10 ME103K 16GB"
    precio: "120.50"
    descripcion: "La ASUS MeMO Pad 10 se creó pensando en..."
    categoria: 100001

Si tienes el archivo de inicialización en la ruta tienda/fixtures/categorias.json, se puede cargar los datos con:

$ python manage.py loaddata categorias

Por último aunque es posible crear y editar manualmente los archivos, Django proporciona una herramienta para volcar la base de datos a un archivo usando cualquiera de los formatos soportado, con lo que se puede usar el interfaz Admin para crear los datos y luego volcarlos con:

$ python manage.py dumpdata tienda --indent 4 --format json --output tienda/fixtures/categorias.json

Y eso es todo por hoy.

Click to read and post comments

may 24, 2015

Crear servidores KVM en Ubuntu 14.04

Algunas veces necesito una maquina virtual para probar aplicaciones antes de desplegarlas, o para trastear con alguna configuración, este tutorial describe el método para crearlas más rápido y sencillo que he encontrado.

Instalación

En el huésped en el que se van a alojar las maquinas, son necesarios los siguientes paquetes:

$ sudo apt-get install ubuntu-virt-server qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils

desde donde vayas gestionar las maquinas, es recomendable instalar:

$ sudo apt-get install virt-manager

Configuracion de red

El siguiente paso es modificar la configuración de red del anfitrión, yo prefiero usar bridging de manera que las maquinas virtuales tengan asignadas direcciones de red validas. El funcionamiento es ingenioso, se crea un switch virtual usando la tarjeta de red física, al que los huéspedes se conectan como si se tratara de una conexión real.

Solo es necesario modificar en archivo /etc/network/interfaces, por ejemplo si este es el archivo original:

#/etc/network/interfaces

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 192.168.0.42
    network 192.168.0.0
    netmask 255.255.255.0
    broadcast 192.168.0.255
    gateway 192.168.0.1
    dns-nameserver 80.53.60.25

Debemos convertir el interfaz eth0 en br0 y añadir las opciones:

#/etc/network/interfaces

auto lo
iface lo inet loopback

auto br0
iface br0 inet static
    address 192.168.0.42
    network 192.168.0.0
    netmask 255.255.255.0
    broadcast 192.168.0.255
    gateway 192.168.0.1
    dns-nameserver 80.53.60.25
    bridge_ports eth0
    bridge_stp off
    bridge_fd 0
    bridge_maxwait 4

Tras esto reinicia el sistema o el servicio de red.

$ /etc/init.d/networking restart
$ reboot

Configuración del sistema

Añade un usuario al grupo de libvirtd y kvm para que pueda manipulas las maquinas, sin necesidad de ser root:

$ sudo adduser `id -un` libvirtd
$ sudo adduser `id -un` kvm

Crea un directorio donde almacenar las imágenes KVM, por ejemplo:

$ mkdir /home/kvm-images
$ chown libvirt-qemu:libvirtd /home/kvn-images

Adicionalmente puedes crea un par de claves ssh para el usuario, de forma que no tengas que introducir la clave cada vez que administras remotamente la máquina. En este post explico como se hace.

Crear la máquina virtual

Con todo configurado ya estamos listos para crear la máquina con el comando ubuntu-vm-builder:

$ sudo ubuntu-vm-builder kvm trusty \
    --domain servidor \
    --hostname kvm1 \
    --arch amd64 \
    --mem 2048 \
    --cpus 1 \
    --rootsize 20000 \
    --swapsize 2048 \
    --destdir /home/kvm-images/kvm2 \
    --user secnot \
    --pass secreto \
    --bridge br0 \
    --ip 192.168.0.201 \
    --mask 255.255.255.0 \
    --net 192.168.0.0 \
    --bcast 192.168.0.255 \
    --gw 192.168.0.1 \
    --dns 80.53.60.25 \
    --components main,universe \
    --addpkg acpid \
    --addpkg openssh-server \
    --addpkg linux-image-generic \
    --addpkg unattended-upgrades \
    --libvirt qemu:///system ;

Aunque todos esos parámetros parecen muy complicados, sólo la primera linea es necesaria, las demás permiten especificar la configuración para el huésped:

  • destdir - Directorio en el que quieres que se cree la imagen.
  • domain, hostname - Dominio y nombre de host del huésped.
  • arch - Arquitectura de la maquina a instalar.
  • mem, cpus - Cuanta memoria y CPUs son asignadas al huésped.
  • rootsize, swapsize - Tamaño del disco raíz en MB (por defecto 4000MB), y de la partición de intercambio.
  • user, pass - Nombre de usuario y clave para la primera cuenta de usuario en el huésped.
  • bridge - Indica a que bridge conectar el interfaz de red del huésped.
  • ip, mask, bcast, gw, dns - Configuración de red para el huésped.
  • mirror, components - Indica al huésped de que repositorio descargar los paquetes, útil para acelerar la instalacion si tienes copias locales.
  • addpkg - Paquetes a instalar en el huesped durante su creación. Algunos paquetes son imprescindibles, acpid para poder apagar la maquina, openssh-server para poder conectarse remotamente, y linux-image-generic para evitar el error "This kernel does not support a non-PAE CPU"
  • libvirt - Indica al instalador en que host debe instalar la maquina.
  • ssh-key - Añade la clave pública suministrada (ruta absoluta) a la lista de claves autorizadas del root.

Podemos comprobar que está funcionando correctamente con virsh:

$ virsh list --all
Id    Nombre                         Estado
----------------------------------------------------
1     kvm1                           ejecutando

Si no fue arrancado automáticamente, puedes hacerlo a mano con:

$ virsh start kvm1
Se ha iniciado el dominio kvm1

y detenerlo con:

$ virsh shutdown kvm1
El dominio kvm1 está siendo apagado

Gestión remota

Si además quieres gestionar de forma remota todos los huéspedes KVM, instala virt-manager, y añade la dirección ip del anfitrión con Archivo>Añadir conexión... usando como nombre de usuario la cuenta que añadiste a los grupos de kvm. El resultado debería ser similar a esto:

Ventana principal de virt-manager

Si haces doble click en cualquiera de las máquinas, puedes abrir una consola, o la página de detalles de configuración seleccionando vista en la barra de tareas:

Ventana detalles virt-manager
Click to read and post comments