miércoles, 5 de diciembre de 2012

Django y celery

Me he decidido a utilizar un sistema de cola de eventos en una aplicación de django. Lo que me ha llevado a tomar esta decisión es fundamentalmente evitar tener que esperar a que un proceso termine para poder seguir interactuando con la aplicación web. Por tanto celery me ofrece justo esto.

Cuando hablamos de celery, siempre viene acompañado del backend RabbitMQ (cola de eventos de alto rendimiento). Pero hay otros sistemas menos complejos, como kombu, el cual utiliza la propia bbdd de django para el almacenamiento de mensajes.

Tenemos la siguiente situación:

- Django 1.3 con una aplicación que lanza procesos sobre diversos servidores
- mod_wsgi para comunicar apache con django

Estos son los dos sistemas que tendremos que tocar para añadir celery a nuestra aplicación.

Tendremos que instalar lo siguiente:

- pip install celery django-kombu django-celery
- apt-get install supervisor ó yum install supervisor (depende del s.o.)

Vamos a decirle a django que vamos a utilizar celery, para ello en nuestro settings.py vamos a añadir lo siguiente:

#Importamos django-celery
import djcelery
djcelery.setup_loader()

#Para evitar problemas a la hora de llamar a las aplicaciones dentro
#INSTALLED_APPS, haremos que tome en el sys.path la ruta actual.

#Por tanto, si la llamada de nuestra aplicación dentro de INSTALLED_APPS la
#hacemos como: misitio.aplicacion, ahora sólo la tendremos que llamar: aplicacion
sys.path.append(os.getcwd())


INSTALLED_APPS = (
    ....
    'djkombu',
    'djcelery',
)

#Configuración propia de celery
#Backend a utilizar
BROKER_BACKEND = "djkombu.transport.DatabaseTransport"
#Url de la cola de mensajería, kombu utiliza la bbdd de django
BROKER_URL = "django://"
#Para las tareas programadas, indicamos que utilice la bbdd para su control
CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler"


Ahora para wsgi, hay que indicarle que vamos a usar celery, por tanto añadimos en nuestro django.wsgi:

import djcelery
djcelery.setup_loader()


Con esto ya hemos acabado de configurar django, ahora actualizaremos nuestar bbdd para que incorpore las tablas necesarias:

python manage.py syncdb


La definición de tareas la haremos dentro de un fichero llamado tasks.py que crearemos dentro de la carpeta de nuestra aplicación. Tenemos la siguiente estructura:

misitio
|-- __init__.py
|-- aplicacion
|   |-- __init__.py
|   |-- admin.py
|   |-- models.py
|   |-- tasks.py
|   |-- tests.py
|   |-- views.py
|-- django.wsgi
|-- manage.py
|-- settings.py
|-- templates
|   |-- admin
|   |   |-- base.html
|   |   |-- base_site.html
|   |   `-- index.html
|   |
|    `-- aplicacion
|      |
|      `-- index.html
|
`-- urls.py


Vamos a definir una tarea muy simple, y que aparece en todos los ejemplos de celery, editamos el fichero tasks.py y añadimos esto:

from celery import task

@task()
def add(x, y):
    return x + y


El decorador @task() será el que indique que la función add será tratada como una tarea asíncrona dentro de celery y que podremos invocar.

Vamos a arrancar celery y a realizar algunas pruebas para comprobar que funciona. Para arrancarlo, de momento vamos a hacerlo del siguiente modo:

python manage.py celeryd -l info --settings=settings

 -------------- celery@rafabono v3.0.12 (Chiastic Slide)
---- **** -----
--- * ***  * -- [Configuration]
-- * - **** --- . broker:      django://localhost//
- ** ---------- . app:         default:0xb721c58c (djcelery.loaders.DjangoLoader)
- ** ---------- . concurrency: 4 (processes)
- ** ---------- . events:      OFF (enable -E to monitor this worker)
- ** ----------
- *** --- * --- [Queues]
-- ******* ---- . celery:      exchange:celery(direct) binding:celery
--- ***** -----

[Tasks]
  . aplicacion.tasks.add


Este es el mensaje que nos mostrará al arrancar. Como vemos nos da información del broker (el BROKER_URL que indicamos anteriormente), la concurrencia que ha tomado por defecto (4 procesos), la activación de eventos (que necesitaremos para la tareas programadas) y las colas. Más abajo nos da un listado de las tareas que tenemos en nuestro fichero tasks.py.


Ya que tenemos andando nuestros sistema de cola de eventos, veamos si realmente funciona. En otro teminal abrimos una shell de django (para evitar tener que importar el settings):

python manage.py shell

Y comenzaremos importanto la tarea y haciendo una llamada:

>>> from aplicacion.tasks import add
>>> add.delay(2, 2)


el método delay es el que utilizaremos para llamar a la tarea add con celery. Esto simplemente sumará 2 + 2 pero de forma asíncrona. Tras hacer la llamada veremos lo siguiente:

<AsyncResult: ab56fd91-5ac3-44a8-abee-98d35c6ac537>

Nos devuelve un objeto AsyncResult cuyo identificador es un uuid. La tarea ha sido enviada. Pero, ¿qué ha ocurrido con su ejecución?. Vamos al terminal desde donde lanzamos celery (python manage.py celeryd -l info --settings=settings) y veremos algo así:

[2012-12-05 16:59:18,503: INFO/MainProcess] Got task from broker: aplicacion.tasks.add[ab56fd91-5ac3-44a8-abee-98d35c6ac537]
[2012-12-05 16:59:18,599: INFO/MainProcess] Task aplicacion.tasks.add[ab56fd91-5ac3-44a8-abee-98d35c6ac537] succeeded in 0.0683989524841s: 4

Vemos como la tarea ha llegado a la cola y ha sido ejecutada, dando el resultado esperado: 4


¿Qué más cosas nos puede decir la tarea? Vamos a volver a hacer una llamada, pero esta vez pasándola a una variable:

>>> resultado = add.delay(4, 4)
>>> resultado.ready()


ready() nos dirá si la tarea ha finalizado. La mejor prueba que podemos hacer es para celery (un crtl + c desde el terminal donde lo lanzamos) y volver a hacer una llamada:

>>> resultado = add.delay(4, 4)
>>> resultado.ready()
False


La tarea no ha finalizado, pues la cola está parada. Por tanto esta tarea quedará encolada hasta que la cola vuelva a activarse. La volvemos a activar:

python manage.py celeryd -l info --settings=settings

y veremos como inmediatamente nos aparece el siguiente mensaje:

[2012-12-05 17:12:20,603: INFO/MainProcess] Task aplicacion.tasks.add[ee03a68a-0eb4-43c4-a98a-b3ba72fb5c8c] succeeded in 0.024453163147s: 8

si volvemos a preguntar por su estado:

>>> resultado.ready()
True


También podemos ver el resultado de la tarea:

>>> resultado.result
8


Y ver si ha finalizado correctamente:

>>> resultado.successful()
True


Bien, esto sólo una pequeñísima parte de celery, para más información: http://docs.celeryproject.org/en/latest/

La aplicación de todo esto: infinitas posibilidades. Cada cual que investigue y pruebe para adaptarlo a sus necesidades.


Si en vez de una tarea asíncrona queremos una tarea periódica, la forma de definirla será (por ejemplo):

@periodic_task(run_every=crontab(hour="*", minute="*/5", day_of_week="*"))
def prueba():
    print "Hola"


Esta tarea se ejecutará cada 5 minutos, imprimiendo un "Hola".


En cuanto a la forma de lanzar celery, para hacer pruebas siempre podemos hacerlo como hasta ahora, pero en un sistema productivo, habría que hacerlo de forma más segura. Una forma de hacerlo es con el servicio supervisor. Al comienzo de esta entra ya lo instalamos, por lo que tan sólo nos queda configurarlo.

Dependiendo de la version de supervisor, la configuración podrá varia más o menos. A continuación expongo un pequeño ejemplo del fichero supervisord.conf (suele estar en /etc/supervisor o directamente en /etc), sólo de la zona donde definimos celery:

[program:celery]
command=/path/python/bin/python /path/django/misitio/manage.py celeryd -v 2 -B -E -l INFO --settings=settings
process_name = celery
directory=/path/python/bin
user=celery
autostart=true
autorestart=true
priority = 0
stdout_logfile=/var/log/celeryd.log
redirect_stderr=true


Simplemente agregamos una sección para celery, donde indicaremos el comando a lanzar. Tendremos que añadir la opción -B (modo celerybeat para que escuche las peticiones de las tareas programadas) y -E (para los eventos). En el comando habrá que especificar las rutas de python y de nuestro proyecto django.
También le indicamos el usuario con el que se va a lanzar (no es conveniente hacerlo con root).

Con esto será suficiente, sólo nos queda iniciar supervisor (/etc/init.d/supervisor start). Si tenemos habilitado el acceso web podremos ver el estado de nuestro proceso celery.

lunes, 3 de diciembre de 2012

Cambiar versión de python para django

A continuación explico como cambiar la versión de python que utiliza django, para disponer de una versión más nueva y con más posibilidades.

El escenario sería el siguiente:

- Red Hat Enterprise Linux Server release 5.1
- Apache/2.2.3
- Django 1.3
- mod_wsgi (para conexión entre apache y python)
- Python 2.4.3


Queremos cambiar la versión de python para tener la 2.7.3.

Lo primero que haremos será instalar la nueva versión de python:

- Nos descargamos la versión 2.7.3: http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz
- La ruta de instalación será, por ejemplo, /opt/python273, luego pasamos a compilar:

tar xvfz Python-2.7.3.tgz
cd /Python-2.7.3
./configure --prefix=/opt/python273
make
make altinstall

Es importante hacer este make altinstall, pues vamos a instalar una versión alternativa de python, no queremos pisar la ya existente.

Ya tenemos instalado python en la versión 2.7.3. Si en nuestro python/django tenemos instalado diversos paquetes, sería interesante sacar un listado de estos para poder instalarlos en la nueva versión de python. Para ello:

- Sacar listado de paquetes de la vesión actual de python:

pip freeze > /tmp/paquetes.txt

- Instalar los paquetes en la nueva versión de python:

/opt/python273/bin/pip install -r /tmp/paquetes.txt

(Si no trae pip la nueva versión de python, pues nos lo bajamos y lo instalamos para esta versión).

Ahora debemos compilar nuestro módulo mod_wsgi con la nueva versión de python, así que nos bajamos mod_wsgi: http://modwsgi.googlecode.com/files/mod_wsgi-3.4.tar.gz

y para compilarlo:

./configure --with-python=/opt/python273/bin/python2.7
make
make install

Con esto ya tenemos nuestro mod_wsgi compilado para la nuestra nueva versión de python, ya sólo nos queda indicar en apache la ruta de nuestro python, con lo que en nuestro fichero de configuración añadiremos la siguiente directiva:

WSGIPythonHome /opt/python273


Si tienes un conf con un virtual configurado, esta directiva debe estar fuera del virtual (al final de </VirtualHost>).

Reiniciamos nuestro apache y listo, ya tenemos nuestro django funcionando con la nueva versión de python.

martes, 27 de noviembre de 2012

Establecer tiempo de sesión en django

Para controlar el tiempo de sesión en django, podemos hacer uso de las siguentes directivas dentro de nuestro settings.py:

#Tiempo de vida de la sesión en segundos
SESSION_COOKIE_AGE = 600


#Para que expire la sesión al cerrar el navegador. Por defecto está a False

SESSION_EXPIRE_AT_BROWSER_CLOSE = True

También es importante si tenemos varios proyectos publicados en el mismo apache, añadir la siguiente directiva:

SESSION_COOKIE_NAME = 'cadena_distintiva'

donde 'cadena_distintiva' deberá ser diferente, para que cada proyecto tenga una cookie diferente y no entre en conflicto con las de otros.

Limpiar django_sessions

Ocurre que la tabla de django "django_session" suele crecer de forma desmesurada, debido a que django por sí sólo no limpia las sesiones expiradas (en gran parte porque al salir no lo hacemos con logout).

Para evitar que crezca esta tabla, django nos ofrece el siguiente comando:

cleanup


el cual limpia de la bbdd aquellas entradas de sesiones ya expiradas.


Lo ideal por tanto, es añadir en el cron una entrada como esta:

0 8 * * * /usr/bin/python /ruta/proyecto/manage.py cleanup

Para que periódicamente (a las 8 de la mañana por ejemplo) limpie las sesiones caducadas de nuestro proyecto django.

miércoles, 21 de noviembre de 2012

Montar un repositorio con Git

Ayer me entró la curiosidad sobre cómo utilizar el gestor de control de versiones git. Ya tengo montado mi repositorio en una máquina remota y configurado para realizar los cambios desde mi equipo para luego subirlos a producción. Os comento cómo hacer esto.

Escenario:

- Servidor de producción [SERVIDOR]. En este servidor tenemos corriendo una aplicación en django (en la ruta /opt/proyecto/aplicacionA). Aquí también motaremos nuestro repositorio remoto. Tendremos que tener git instalado (desde paquetería o compilando).

- Equipo local [LOCAL]. Aquí es desde donde haremos los desarrollos, para posteriormente, subirlos a producción. Tendremos que tener git instalado (desde paquetería o compilando).


Lo que sigue a continuación serán los pasos al estilo receta, para más info de git, acudir al sitio http://git-scm.com/book/es/

1) Crear repositorio remoto

- Accedemos por ssh a SERVIDOR
- Creamos la ubicación del repositorio y lo iniciamos:

    [SERVIDOR] mkdir /opt/repo/aplicacionA.git
    [SERVIDOR] cd /opt/repo/aplicacionA.git
    [SERVIDOR] git init --bare


¡IMPORTANTE! El usuario que utilicemos para conectarnos por ssh al SERVIDOR debe tener permisos sobre /opt/repo/aplicacionA.git

- Configuramos nuestro proyecto de producción para indicar la ubicación del repositorio y hacer la carga de código:

    [SERVIDOR] cd /opt/proyecto/aplcacionA
    [SERIVDOR] git init
    [SERVIDOR] git remote add origin /opt/repo/aplicacionA.git
    [SERVIDOR] git add .
    [SERVIDOR] git commit -m 'Primera entrada de commit'
    [SERVIDOR] git push origin master


Hasta aquí ya tenemos en nuestro repositorio remoto el código del proyecto de producción.

2) Descargar el repositorio
 
- Ahora nos descargaremos el proyecto en LOCAL para poder trabajar con él:

    [LOCAL] mkdir /opt/desarrollo
    [LOCAL] cd /opt/desarrollo
    [LOCAL] git clone ssh://usuario@SERVIDOR//opt/repo/aplicacionA.git


Ya tenemos en local una copia del código de producción.

3) Realizar cambios

- Si queremos realizar un cambio en local y luego aplicarlo a producción (ejemplo, añadir un nuevo fichero):

    [LOCAL] cd /opt/desarrollo/aplicacionA   
    [LOCAL] touch README
    [LOCAL] git add README    #Indicamos que tenga en cuenta este fichero
    [LOCAL] git commit -m "Añadimos fichero README"    #Hacemos commit con un comentario
    [LOCAL] git push origin master    #Subimos el fichero al repositorio


- Ahora aplicaremos este cambio en producción:

    [SERVIDOR] cd /opt/proyecto/aplcacionA
    [SERVIDOR] git pull origin master    #Obtenemos la última versión del respositorio


- Si nos equivocamos mientras hacemos un cambio:

    [LOCAL] cd /opt/desarrollo/aplicacionA
    [LOCAL] git add README
    [LOCAL] echo "Prueba" > README    #Hacemos un cambio en README, pero nos arrepentimos y queremos volver al estado anterior
    [LOCAL] git reset README    #Le decimos que no tenga en cuenta este fichero para los cambios en el respositorio
    [LOCAL] git checkout -- README    #Volvemos a la versión del repositorio del último commit.


- Para recuperar archivos antiguos, remito a esta entrada:

http://www.alvaroremesal.net/blog-alvaroremesal/recuperar-archivos-antigos-con-git

4) Crear ramas

    git branch nueva_rama
    git branch -> nos muestra las distintas ramas existentes
    git checkout nueva_rama -> nos cambiamos a esta nueva rama



Espero que sea útil.

Sacar configuración importante de settings.py en Django

Es posible que necesitemos sacar cierta información de carácter sensible del fichero settings.py de un proyecto, bien por facilitar el trabajo con control de versiones, o por querer tener diferentes configuraciones entre entornos.

Una manera sencilla de hacer esto es como se explica en https://code.djangoproject.com/wiki/SplitSettings.


Por ejemplo, imaginemos que queremos sacar la información de bbdd. Para ello nos vamos a crear un fichero settings.ini en /etc, cuyo contenido será este:

[database]
DATABASE_NAME: nombre_bbdd
DATABASE_USER: usuario
DATABASE_PASSWORD: pass
DATABASE_HOST: localhost
DATABASE_PORT: 3306


Luego tan sólo tendremos que modificar nuestro settings.py añadiendo esto:

from ConfigParser import RawConfigParser

config = RawConfigParser()
config.read('/etc/settings.ini')




Y en la sección DATABASES configuraremos lo siguiente:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'config.get('database', 'DATABASE_NAME')',                     
        'USER': config.get('database', 'DATABASE_USER'),                     
        'PASSWORD': config.get('database', 'DATABASE_PASSWORD'),                 
        'HOST': config.get('database', 'DATABASE_HOST'),                     
        'PORT': config.get('database', 'DATABASE_PORT'),                    
    }
}


Y listo, ya tendremos nuestro settings.py configurado para poder ser distribuido sin tener que estar pendiente de modificar ningún dato sensible.

jueves, 18 de octubre de 2012

Corregir problemas de codificación en Django

Para evitar errores de codificación en Django del tipo: 

django 'ascii' codec can't encode character u'\xf3'

debido a la codificación que tenga por defecto python (que suele ser ascii), simplemente tenemos que añadir a nuestro settings.py las siguientes líneas:


import sys
reload(sys)
sys.setdefaultencoding( "utf-8" )

Y conseguiremos establecer la codificación adecuada.

miércoles, 17 de octubre de 2012

Ejecutar comandos personalizados de Django desde el cron

Para poder ejecutar comandos personalizados de django desde el cron, necesitaremos aplicar el siguiente parche (si es que aún no lo trae):




Una vez aplicado el parche, ya podremos invocar nuestro comando de la siguiente manera:

minutos hora * * * /usr/bin/python /sitio/proyecto/django/manage.py comando



miércoles, 10 de octubre de 2012

Definir rango de puertos del ftp en alfresco 3.2

Debido a que el servicio de ftp de alfresco funciona en modo pasivo, este abrirá puertos por encima del 1024 para llevar a cabo la comunicación de datos.

Si tenemos un firewall muy restrictivo, nos interesará definir un rango determinado de puertos y abrir sólo ese rango.

Para definir el rango de puertos debemos añadir a /tomcat/shared/classes/alfresco-global.properties los siguientes parámetros:

ftp.dataPortFrom=50000
ftp.dataPortTo=60000

En este caso estamos definiendo el rango del 50000 al 60000.


Veamos si esto funciona de verdad. Hacemos un ftp en modo debug a nuestro servidor:

ftp -d 192.168.60.13 2122

Connected to 192.168.60.13.
220 FTP server ready
ftp: setsockopt: Bad file descriptor
Name (servidor:rafa): usuario
---> USER usuario
331 User name okay, need password for usuario
Password:
---> PASS XXXX
230 User logged in, proceed
---> SYST
215 UNIX Type: Java FTP Server
Remote system type is UNIX.

Indicamos el modo pasivo:

ftp> passive
Passive mode on.

Y lanzamos un comando:

ftp> ls
ftp: setsockopt (ignored): Permission denied
---> PASV
227 Entering Passive Mode (192,168,60,13,211,39)
---> LIST
150 File status okay, about to open data connection
drw-rw-rw-   1 user group 0 Jan  1  1970 Alfresco
drw-rw-rw-   1 user group 0 Jan  1  1970 AVM
226 Closing data connection
ftp>




Vemos como efectivamente el puerto que ha utilizado está por encima del 50000 y por debajo del 60000 (el puerto se obtiene de los valores que van después de la ip, 211*256+39=54055).

Si hacemos un tcpdump en el servidor, filtrando por ip origen, veremos que efectivamente ese es el puerto que ha utilizado:


Para puerto de datos:

10:40:20.810783 Out MAC ethertype IPv4 (0x0800), length 68: 192.168.60.13.54055 > ip_local.50958: . ack 2 win 46 <nop,nop,timestamp 552972262 76243556>

Para puerto de conexión:

10:40:20.810795  In MAC ethertype IPv4 (0x0800), length 68: ip_local.50583 > 192.168.60.13.2122: . ack 271 win 115 <nop,nop,timestamp 76243556 552972260>

lunes, 27 de agosto de 2012

Error al configurar Oracle Enterprise Manager Database Control

Al instalar Oracle 11gR2 en Ubuntu 11.04, me saltó un error en la parte de configuración del Database Control. No le presté mucha atención y posteriormente la intenté configurar lanzando:

emca -config dbcontrol db

pero tras introducir los datos, me volvió a escupir el mensaje de error:

27-ago-2012 12:25:57 oracle.sysman.emcp.EMConfig perform
INFO: Esta operación se está registrando en /u01/app/oracle/cfgtoollogs/emca/orcl/emca_2012_08_27_12_25_44.log.
27-ago-2012 12:25:58 oracle.sysman.emcp.EMConfig perform
GRAVE: Fallo al asignar puertos en los rangos especificados para los siguientes procesos: JMS [5540-5559],RMI [5520-5539],Database Control [5500-5519],EM Agent [3938] | [1830-1849]
Consulte el archivo log en /u01/app/oracle/cfgtoollogs/emca/orcl/emca_2012_08_27_12_25_44.log para obtener más información.
No se ha podido terminar la configuración. Consulte el archivo log en /u01/app/oracle/cfgtoollogs/emca/orcl/emca_2012_08_27_12_25_44.log para obtener más información.


El problema no estaba en los puertos, pues todos los rangos estaban libres. Googleando un poco conseguí encontrar la solución, que estaba en la resolción de nombres de la máquina. Es decir, para la configuración del Database Control, nuestro equipo debe ser capaz de resolver tanto localhost como nuestro hostname a la ip 127.0.0.1, por tanto, hay que tener algo así en nuestro /etc/hosts:


127.0.0.1 localhost.localdomain localhost
127.0.0.1 hostname.domain.com hostname

Una vez finalizada la instalación, puedes volver a configurar para que resuelva el hostname a tu propia ip.

viernes, 20 de julio de 2012

subprocess en python

Vamos a ver cómo hacer un script de parada/arranque de un servicio utilizando subprocess.

subprocess nos va a permitir lanzar un comando y esperar a que este termine, y una vez terminado, ver en qué estado ha finalizado.

El siguiente ejemplo se puede utilizar para llevar a cabo un reinicio automático de un servicios (jboss por ejemplo) y estar seguros de que el servicio es detenido y arrancado correctamente:

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import subprocess
script_arranque='/etc/init.d/jboss start'
script_parada='/etc/init.d/jboss stop'
#paramos jboss
pipe=subprocess.Popen(script_arranque, stdout=subprocess.PIPE, shell=True)
estado=pipe.wait()
out, err = pipe.communicate()
if estado == 0:
        print "Estado salida correcto."
else:
        print "Salida de error: " + str(err) 
'''en este caso, el único proceso java que corre es jboss, si hubiese más de uno, la siguiente opción no sería válida ' ' '
pipe=subprocess.Popen('pidof java', stdout=subprocess.PIPE, shell=True)
out, err = pipe.communicate()
pid = out.strip()
if pid != '':
        print "El proceso no se ha detenido, matamos el pid: " + pid + "."
        pipe=subprocess.Popen('kill -9 ' + pid, stdout=subprocess.PIPE, shell=True)
#Ya está parado jboss, ahora lo arrancamos
pipe=subprocess.Popen(script_parada, stdout=subprocess.PIPE, shell=True)
estado=pipe.wait()
out, err = pipe.communicate()
if estado == 0:
        print "Estado salida correcto."
else:
        print "Salida de error: " + str(err)


Un detalle a tener en cuenta cuando utilicemos subprocess es que si utilizamos la shell (shell=True) el comando que pasemos será una cadena, mientras que si no utilizamos la shell (shell=False) el comando se ha de pasar como una lista. Por ejemplo, para ejecutar un ls -la:

Con shell:
    subprocess.Popen('ls -la', shell=True)
Sin shell:
    subprocess.Popen(['ls','-la'], shell=False)


Para ver el estado en el que finaliza, podemos hacerlo mediante:

estado=pipe.wait()

ó

estado=pipe.returncode


Y para ver la salida estándar y la salida de error, haremos uso de communicate:

out, err = pipe.communicate()

Envío de correo en python bajo SSL

Si necesitáis enviar correos con python por ssl de una forma sencilla, recomiendo el uso de pyzmail (http://www.magiksys.net/pyzmail/). Para instalar pyzmail, lo podéis hacer directamente con pip:

pip install pyzmail

Si no tenemos instalado pip, para instalarlo:

wget http://python-distribute.org/distribute_setup.py 
python distribute_setup.py 
easy_install pip

Os dejo un sencillo código de ejemplo:

# -*- coding: iso-8859-15 -*- import pyzmail #Componemos el correo sender=('Yo','cuenta@remitente.es') recipients=['cuenta@destinatario.es',] subject='Prueba envio SSL' text_content='Este correo ha sido enviado bajo SSL' prefered_encoding='iso-8859-1' text_encoding='iso-8859-1' #Vamos a adjuntar dos ficheros fichero=open('fichero.txt.tar.gz', 'rb').read() fichero2=open('fichero.txt','rb').read() payload, mail_from, rcpt_to, msg_id=pyzmail.compose_mail( sender, recipients, subject, prefered_encoding, (text_content, text_encoding), html=None, attachments=[(fichero, 'application', 'octet-stream', 'fichero.tar.gz', 'us-ascii'),(fichero2, 'application', 'octet-stream', 'fichero.txt', 'us-ascii')]) #Datos de configuracion para el envio smtp_host='host' smtp_port=puerto para comunicacion ssl smtp_mode='ssl' smtp_login='cuenta@remitente.es' smtp_passwd='password' #Enviamos el correo ret=pyzmail.send_mail(payload, mail_from, rcpt_to, smtp_host, smtp_port=smtp_port, smtp_mode=smtp_mode, smtp_login=smtp_login, smtp_password=smtp_passwd) if isinstance(ret, dict): if ret: print 'fallo en los destinatario:', ', '.join(ret.keys()) else: print 'correcto' else: print 'error:', ret

viernes, 29 de junio de 2012

Conexión a bbdd oracle desde python

Para poder acceder a una bbdd oracle desde python tan sólo necesitaremos tener instalado:

- cliente oracle (lo puedes obtener de la página de oracle y registrándote en la misma)
- extensión cx_Oracle (lo puedes descargar desde la página http://cx-oracle.sourceforge.net/)

La forma de utilizarlo lo podemos ver en el siguiente ejemplo:

Con este script se pretende actualizar el campo de una tabla pasándole tres argumentos, dos para filtrar el dato y uno que será el nuevo valor.
También hacemos uso de optparse para pasear los argumentos.


#!/usr/bin/python # -*- coding: iso-8859-15 -*- import cx_Oracle, sys, os, datetime from optparse import OptionParser conn_str='usuario/pass@host:port/bbdd' log = '/ruta/para/log/script.log' #Fucion para escribir log def log (texto):         now = datetime.datetime.now()         f = open(log_propio, 'a')         f.write(str (now.ctime()) + ' -> ' + texto + '\n')         f.close() #Se parsea la entrada de datos parser = OptionParser(usage="script.py [-a argumento1] [-b argumento2] [-c valor]", version="script 1.0") parser.add_option("-a", help='Argumento 1 para filtrar el dato. Ej: -a 10. Opción oblicatoria.', type="string", dest="argumento1") parser.add_option("-b", help='Argumento 2.para filtrar el dato. Ej: -b 2012. Opción obligatoria.', type="string", dest="argumento2") parser.add_option("-c", help='Valor al que se quiere cambiar. Ej: -c 6. Opción obligatoria', type="string", dest="valor") (options, args) = parser.parse_args() if options.argumento1 is None:         print 'La opcion argumento1 es necesaria'         parser.print_help()         sys.exit(1) else:         argumento1=options.argumento1 if options.argumento2 is None:         print 'La opcion argumento2 es necesaria'         parser.print_help()         sys.exit(1) else:         arguemento2=options.argumento2 if options.valor is None:         print 'La opcion valor es necesaria'         parser.print_help()         sys.exit(1) else:         valor=options.valor db_conn = cx_Oracle.connect(conn_str) cursor = db_conn.cursor() cursor.execute('select valor from tabla where argumento1 =' + str(argumento1) + ' and argumento2=' + str(argumento2)) registro = cursor.fetchall() log ("Valor actual: " + str(registro)) print "Valor actual: " + str(registro) log ("Se cambia al valor " + valor) print "Se cambia al valor " + valor cursor.execute('update tabla set valor =' + valor + ' where argumento1=' + argumento1 + ' and argumento2=' + str(argumento2)) cursor.close() db_conn.commit() db_conn.close() log ("Valor modificado.")  

martes, 26 de junio de 2012

Curso Django Segunda Parte

Continuamos con la segunda parte del mini curso de django. Respecto a la primera parte, he añadido una par de cosas:

- La instalación de un paquete más: python-pygraphviz



Pues bien, ya tenemos creado un proyecto llamado misitio. Ahora es el momento de crear nuestra aplicación, la cual llamaremos inventario. Para crear un aplicación, simplemente hacemos:

cd /opt/djcode/misitio
python manage.py startapp inventario

Tras la ejecución de este comando (que no devuelve nada por pantalla), tendremos un nuevo directorio bajo el proyecto misitio:

ls -l inventario/

-rw-r--r-- 1 root root   0 mar 11 12:27 __init__.py
-rw-r--r-- 1 root root  57 mar 11 12:27 models.py
-rw-r--r-- 1 root root 383 mar 11 12:27 tests.py
-rw-r--r-- 1 root root  26 mar 11 12:27 views.py

De los ficheros que nos podemos encontrar, tenemos:

- models.py: es donde podremos construir nuestro modelo de datos para la aplicación. La aplicación, como ya hemos dicho, se trata de un inventario de software (en principio muy básico).

- views.py: es donde configuraremos las vistas. Las vistas no son más que funciones python que recoge los argumentos de una petición http y devuelve una respuesta. La forma de relacionar la petición http con la función de la vista es tarea del fichero urls.py, que veremos más adelante (está bajo la carpeta misitio).


Resumiendo un poco, ya hemos instalado el software base, creado un proyecto, hemos creado también el esqueleto de nuestra aplicación, ahora nos falta darle un modelo de datos, una vistas y relacionarlas con unos patrones de urls (peticiones) y más adelante darle a todo un formato más o menos presentable con las plantillas.

Teniendo esta idea de funcionamiento clara, la creación de aplicaciones en django se convertirá en una tarea muy rápida y sencilla (o no tan sencilla, todo dependerá de la complejidad de la aplicación).

Sin más dilatación, pasemos al modelo de datos. Imaginemos que en nuestra empresa ficticia BONOSA (el nombre se lo debo a un compañero de trabajo, las cosas de JuanMa ;) ) tenemos muchos servicios que ofrecemos a diversos clientes, y todos estos servicios se componen (de una forma muy básica) de máquinas, aplicaciones y clientes. Las máquinas a su vez tienen un nombre, una dirección ip y un sistema operativo. Las aplicaciones se componen de un software con una versión y una ruta de instalación. Luego estos servicios pertenecerán a un cliente (en nuestro ejemplo supondremos que un servicio sólo puede pertenecer a un cliente).

Con estos datos (vuelvo a repetir que muy básico, esto podría ampliarse muchísimo, pero para nuestro ejemplo es suficiente) ya tenemos controlado qué servicios ofrece nuestra empresa y a qué clientes.


Para definir este modelo de datos, debemos recurrir al fichero models.py que indicamos antes. Aquí será donde escribiremos las clases en python que definirán el modelo de datos, y posteriormente se plasmará en tablas perfectamente relacionadas (esto lo hace django él solito).


Si editamos el fichero models.py veremos lo siguiente:


from django.db import models

# Create your models here.


y ahí es donde comenzaremos nosotros a construir nuestro modelo de datos.

Antes de empezar a deminir nuestras clases, vamos a necesitar importar algo más, de modo que añadiremos despues de from django.db import models:

from smart_selects.db_fields import ChainedForeignKey

Este import es para poder utilizar los combos anidados que ofrece smart-selects para la zona de administración de django, que nos será de gran utilidad.


Tras el comentario # Create your models here.

comenzaremos a definir las clases (es importante el orden en el que aparecen, pues no se puede utilizar algo si aún no se ha definido):


Definición de la clase cliente. Tiene dos atributos: nombre (del tipo CharField) y descripción (del tipo TextField) donde podemos añadir un texto amplio, por ejemplo si paga o no paga (muy común últimamente).
Algunos detalles: 
Con unique=True estamos indicando que será un valor único (como clave primaria) y no podrá haber dos clientes con el mismo nombre. 
Con verbose_name le decimos el nombre con el que queremos que aparezca en la zona de administración. 
La función __str__(self) nos sirve para indicar qué valor debe devolver por defecto cuando pidamos un objeto de tipo Cliente. 
Con class Meta definimos algunos metadatos, como por ejemplo la ordenación que queremos y el nombre plural de la clase para la zona de administración.

class Cliente(models.Model):
    nombre = models.CharField(max_length=200, unique=True, verbose_name='Cliente')
    descripcion = models.TextField()

    def __str__(self):
                return self.nombre
    class Meta:
                ordering = ['nombre']
                verbose_name_plural = "Clientes"


Definición de la clase Servicio. Tiene tres atributos: nombre, descripción y cliente. Cliente es del tipo ForeignKey, pues queremos poder relacionar nuestro servicio con un cliente. Indicamos null=True si queremos dejar el campo en blanco, de este modo no nos obliga a asignar un cliente a un servicio.

class Servicio(models.Model):
        nombre = models.CharField(max_length=200, unique=True, verbose_name='Servicio')
        descripcion = models.TextField()
        cliente = models.ForeignKey(Cliente, null=True)
      
        def __str__(self):
                return self.nombre
        class Meta:
                ordering = ['nombre']


Definición de la clase TAplicacion. Esta clase nos servirá junto con VAplicacion para definir tipos de aplicaciones y versiones de aplicaciones. De este modo tenemos mucho más cerrado la elección de una aplicación y su versión, y así no introducir en campos de texto los nombres (que podrían dar a confusión).

class TAplicacion(models.Model):
    nombre = models.CharField(max_length=200, unique=True, verbose_name='Tipo Aplicacion')

        def __str__(self):
                return self.nombre

        class Meta:
                ordering = ['nombre']
        verbose_name_plural = "Tipos de Aplicaciones"


Definición de la clase VAplicacion. Esta clase tiene un atributo taplicacion que utilizamos para relacionar la versión con la aplicación.

class VAplicacion(models.Model):
    version = models.CharField(max_length=200, unique=False, verbose_name='Version de aplicacion')
    taplicacion = models.ForeignKey(TAplicacion)
        def __str__(self):
                return self.version

        class Meta:
                ordering = ['version']
        verbose_name_plural = "Versiones de Aplicaciones"

Definición de la clase SistemaOperativo. Definimos los diferentes S.O. que tengamos.

class SistemaOperativo(models.Model):
        nombre = models.CharField(max_length=200, unique=True, verbose_name='Sistema Operativo')

        def __str__(self):
                return self.nombre
        class Meta:
                ordering = ['nombre']
        verbose_name_plural = "Sistemas Operativos"

Definición de la clase Maquina. Tiene tres atributos: nombre, ip (del tipo IPAddressField, que nos valida si es una ip) y sistema (que nos permiterelacionarlo con un sistema operativo.

class Maquina(models.Model):
        nombre = models.CharField(max_length=200, unique=True, verbose_name='Maquina')
        ip = models.IPAddressField()
    sistema = models.ForeignKey(SistemaOperativo)

        def __str__(self):
                return self.nombre
    class Meta:
        ordering = ['nombre']


Y por fin la definición de la clase Aplicacion. Esta es la más completas. Tienes seis atributos: taplicacion (la aplicación será de un tipo de aplicación: apache, tomcat, jboss...), vaplicacion (la aplicación debe tener una versión, y debe estar relacionada con el tipo de aplicación), descripcion, ruta, maquina y el servicio al que pertenece dicha aplicación. 
De esta manera es como relacionamos la aplicación con el servicio. Aquí es donde hacemos uso de smart-selects, pues queremos que la versión de la aplicación dependa de la aplicación. Es decir, si la aplicación es apache, pues las con las que se debe relacionar sean la 2.0, 2.1 y 2.2 (por ejemplo, si  hemos metido dichas versiones) y si la aplicación es tomcat, que las versiones relacionadas sean la 5.5, 6.2 y 7.0. Esto se verá mejor cuando 
accedamos a nuestra zona de administración.
Hemos añadido también una fución que nos devuelve el tipo de aplicacion y su versión. Nos puede resultar útil más adelante y así vemos como django nos da
la posibilidad de difinir nuestras propias funciones dentro del modelo de datos. A partir de esta función sacamos un nuevo atributo: tvaplicacion, que no es más que el tipo y versión de la aplicación.

Puede parecer un poco enredado pero es un buen ejemplo para ver las  posibilidades que nos brinda django.

class Aplicacion(models.Model):
    taplicacion = models.ForeignKey(TAplicacion, verbose_name="Tipo Aplic", help_text="Selecciona el tipo de aplicacion")
        vaplicacion= ChainedForeignKey(VAplicacion, chained_field="taplicacion", chained_model_field="taplicacion", verbose_name="Version de Aplic", help_text="Selecciona la version de la aplicacion")
    descripcion = models.TextField(help_text="Descripcion de la aplicacion")
        ruta = models.CharField(max_length=200, unique=False, verbose_name="Ruta instalacion", help_text="Donde esta instalado")
    maquina = models.ManyToManyField(Maquina)
    servicio = models.ForeignKey(Servicio, verbose_name='Servicios')
   
    def _get_tvaplicacion(self):
        return '%s %s' % (self.taplicacion,self.vaplicacion)
    class Meta:
                ordering = ['taplicacion']
                verbose_name_plural = "Aplicaciones"
    tvaplicacion = property(_get_tvaplicacion)


En este enlace podemos ver más detalladamente la definición del modelo de datos: https://docs.djangoproject.com/en/dev/ref/models/fields/

Y aquí sobre smart-selects: https://github.com/digi604/django-smart-selects


Bien, ya tenemos configurado el modelo de datos, ahora hay que decirle a django con qué bbdd se debe comunicar. Para ellos necesitaremos configurar el fichero settings.py para decirle a django dónde estará la bbdd, instalar nuestra aplicación, el componente smart-selects y algunos parámetros regionales más:


Editamos el fichero settings.py y buscamos el campo DATABASES, donde añadiremos los siguientes valores:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'programas',                      # Or path to database file if using sqlite3.
        'USER': 'programas',                      # Not used with sqlite3.
        'PASSWORD': 'programas',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

Para la configuración regional, modificaremos:

TIME_ZONE = 'Europe/Madrid'

LANGUAGE_CODE = 'es-ES'

Y para instalar nuestra aplicación (inventario) y smart-selects:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'misitio.inventario',
    'django_extensions',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'smart_selects',

También hemos añadido django_extensions, que nos servirá para tener algunas herramientas extras y django.contrib.admin para la zona de administración.


Una vez configurado esto, pasaremos a validar tanto la conexión con la bbdd como la correcta sintáxis de models.py:


root@debian:/opt/djcode/misitio# python manage.py validate
0 errors found


Todo ha sido correcto. Si apareciese algún error, suelen ser muy descriptivos, por lo que no debería ser difícil solventarlo.

Para ver como quedaría el sql que genera django para construir la bbdd:

root@debian:/opt/djcode/misitio# python manage.py sqlall inventario

Y aparecerá el sql con la definción de las tablas, relaciones e índices.

Este sql lo podríamos ejecutar directamente en la bbdd y ya tendríamos todas nuestras tablas, pero para qué hacerlo si django lo hace por nosotros:


root@debian:/opt/djcode/misitio# python manage.py syncdb
Creating tables ...
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_user_permissions
Creating table auth_user_groups
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table inventario_cliente
Creating table inventario_servicio
Creating table inventario_taplicacion
Creating table inventario_vaplicacion
Creating table inventario_sistemaoperativo
Creating table inventario_maquina
Creating table inventario_aplicacion_maquina
Creating table inventario_aplicacion
Creating table django_admin_log

Y en este punto comenzará a pedirnos datos para la zona de administración de django que hemos activado:

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use 'root'):
E-mail address: correodeprueba@correo.com
Password:
Password (again):
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
No fixtures found.


Ya está construída la bbdd de nuestra aplicación y de la zona de administración.


¿Os gustaría ver un diagrama de clases del modelo de datos que acabamos de generar? Pues vamos a hacer uso de django_extension:


root@debian:/opt/djcode/misitio# python manage.py graph_models inventario -g -o /home/usuario/mi_proyecto.png

Y podréis ver un bonito diagrama generado de marena automáticas a partir del modelo de datos, ¿qué os parece?

Ahora vamos a modificar el fichero urls.py para poder acceder a la zona de administración y que smart-selects funcione correctamente. Añadimos los siguientes import y el autodiscover():

from django.contrib import admin
admin.autodiscover()
from django.conf import settings


y añadimos las siguientes url's:

    (r'^inventario/chaining/', include('smart_selects.urls')),
    (r'^inventario/', include(admin.site.urls)),

aquí estamos diciendo qué queremos que ocurra cuando pidamos http://ip:puerto/inventario/chaining/ (necesario para smart-selects) y http://ip:puerto/inventario (nos llevará a la zona de administración de django).

Pueden parecer muchos pasos y muchos cambios a la vez, pero de esta manera conseguiremos ver un resultado muy útil y prático de django. Cuando cojáis la dinámica de trabajo, veréis que siempre suele ser lo mismo.

Ya sólo nos faltaría darle forma a la zona de administración para adaptarla a como queramos trabajar. Para nuestro ejemplo, los datos que debemos rellenar son servicios con su cliente y sus aplicaciones, para las aplicaciones, los tipos de aplicaciones que hay y sus versiones, y poco más.

La zona de administración te permitirá enriquecer con datos tu aplicación, pero por defecto lo hará accediendo por clases según hemos configurado nuestro modelo. Es decir, nos dejará rellenar Servicios por un lado (con su nombre, descripción y cliente), Aplicaciones por otro lado (con su tipo de aplicación, versión, ...). Pero lo que nos gustaría es acceder directamente a Servicios y desde ahí añadir todo lo que atañe al servicio.

Sin haber visto antes la zona de administración de django puede parecer algo abstracto, de modo que probemos a acceder a la zona de administración. Para ello arrancamos nuestra aplicación con el servidor de pruebas de django:


python manage.py runserver 192.168.48.129:8000

Y ahora accedamos a la url: http://192.168.48.129:8000/inventario

Ahí está la interfaz de administración, en la que podremos ver si nos logamos la zona de autenticación y la de creación de sitios. Pero aún no vemos nuestras clases. Así que vamos a añadir en la carpeta inventario un nuevo archivo, llamado admin.py, cuyo contenido será el siguiente:


from misitio.inventario.models import Servicio, Maquina, SistemaOperativo, Aplicacion, TAplicacion, VAplicacion, Cliente
from django.contrib import admin


class TAplicacionAdmin(admin.ModelAdmin):
        pass

class VAplicacionAdmin(admin.ModelAdmin):
        list_display = ('version', 'taplicacion')

class AplicacionInline(admin.StackedInline):
        model = Aplicacion
        extra = 0
        filter_horizontal = ('maquina',)


class ClienteAdmin(admin.ModelAdmin):
        pass

class ServicioAdmin(admin.ModelAdmin):
        inlines = [ AplicacionInline ]
        list_display = ('nombre', 'descripcion', 'cliente')
        ordering = ('nombre',)
        search_fields = ('nombre', 'descripcion')
        save_on_top = True
        list_per_page = 20

class AplicacionAdmin(admin.ModelAdmin):
        list_display = ('taplicacion', 'descripcion', 'vaplicacion')
        ordering = ('taplicacion',)
        filter_horizontal = ('maquina',)
        save_on_top = True
        list_per_page = 20

class MaquinaAdmin(admin.ModelAdmin):
        list_display = ('nombre', 'ip', 'sistema')
        ordering = ('nombre',)
        save_on_top = True
        list_per_page = 20


class SistemaOperativoAdmin(admin.ModelAdmin):
        list_display = ('nombre',)
        ordering = ('nombre',)
        save_on_top = True
        list_per_page = 20


admin.site.register(Servicio,ServicioAdmin)
admin.site.register(Maquina,MaquinaAdmin)
admin.site.register(Aplicacion,AplicacionAdmin)
admin.site.register(SistemaOperativo,SistemaOperativoAdmin)
admin.site.register(TAplicacion,TAplicacionAdmin)
admin.site.register(VAplicacion,VAplicacionAdmin)
admin.site.register(Cliente,ClienteAdmin)


Paso a explicar por encima esto. Lo primero que debemos hacer es importar nuestras clases. Luego pasamos a definir cómo queremos que la zona de administración nos la muestre.

Si sólo queremos que la añada sin más, pues la declaramos así:


class TAplicacionAdmin(admin.ModelAdmin):
        pass

Fijaos que la nombreclatura que sigue es el nombre de la clase seguido de Admin.

Y aparecerá en la panatalla de administración lista para poder añadirle datos.

La zona de administración nos permite ver los datos, añadir o modificar. Por tanto si lo que queremos es ver ciertos campos en concreto, podemos definirlo del siguiente modo:

class VAplicacionAdmin(admin.ModelAdmin):
        list_display = ('version', 'taplicacion')


Y cuando visualicemos los datos de VAplicacion veremos sólo la columna version y taplicacion.

Una aplicación puedes estar configurada en multitud de máquinas. Django nos permite una forma de añadir máquinas muy sencilla con filter_horizontal. Nos aparecerá dos cajas, una con las máquinas disponibles y otra con las seleccionadas. Lo definimos así:

class AplicacionInline(admin.StackedInline):
        model = Aplicacion
        extra = 0
        filter_horizontal = ('maquina',)

Aquí además vemos que estamos utilizando admin.StackedInline en vez de admin.ModelAdmin. Esto es porque en el formulario vamos a utilizar la propiedad Inline, que lo que hace es permitir dentro de una clase, editar el contenido de otra clase. Esto lo hacemos así porque queremos editar un Servicio, y dentro de este, poder añadir y editar aplicaciones. Por tanto Aplicaciones la tenemos que definir como AplicacionInline(admin.StackedInline). Para entender mejor el tema de InLine, os recomiendo una ojeada a https://docs.djangoproject.com/en/dev/ref/contrib/admin/#inlinemodeladmin-objects.

Con extra = 0 estamos indicando que por defecto queremos 0 formularios creados. Es decir, cuando editemos un servicio, no aparecerá ningún formulario para Aplicaciones, y deberemos crearlo según demanda.

Para Servicio tenemos lo siguiente:

class ServicioAdmin(admin.ModelAdmin):
        inlines = [ AplicacionInline ]
        list_display = ('nombre', 'descripcion', 'cliente')
        ordering = ('nombre',)
        search_fields = ('nombre', 'descripcion')
        save_on_top = True
        list_per_page = 20

Será la entrada principal de datos, por lo que deberá albergar formularios inline (que ha hemos definido antes). Luego le indicamos los formularios inline con:

inlines = [ AplicacionInline ]

Además le indicamos que queremos ordenar los servicios por nombre, que nos permita realizar búsquedas por nombre y descripción (search_fields = ('nombre', 'descripcion')), que aparezca el botón de guardar en la zona superior del formulario además de en la zona inferior (save_on_top = True) y que muestre paginado los resultados cada 20 elementos (list_per_page = 20).


Bien, con esto ya hemos visto bastantes posibilidades de la zona de administración django. Os invito a que le echéis un vistazo a la documentación oficial donde viene explicado con detalle todas las funciones existentes.


Sólo nos queda arrancar nuevamente nuestra aplicación:

python manage.py runserver 192.168.48.129:8000

acceder a la url: http://192.168.48.129:8000/inventario

y comenzar a jugar con django añadiendo datos y toqueteando todo (la interfaz es bastante intuitiva...).

Os dejo un enlace para descargar los ficheros que hemos visto:



Espero que sirva de utilidad.



Obtener informacion de repositorios a través de los metadatos .git publicados por error

 A raiz de CTF realizado recientemente, me ha parecido interesante publicar este post sobre los errores de seguridad que se encuentran en mu...