martes, 9 de octubre de 2018

Modo "inserción" en Vi(m)

¿A que más de una vez nos hemos encontrado con algo así en el fuente de una web?


pt type="text/javascript">
  function blabla() {
    //
  }
</script>



Curioso... ¿puede que tenga que ver con que el comando para empezar a escribir texto en vi(m) sea "i"?

sábado, 15 de septiembre de 2018

Refrescando Django (1)

A lo tonto, Django ya tiene unos cuantos añitos. Van por la versión 2.1 en el momento de escribir estas líneas (septiembre de 2018). Refresquemos un poco Django.

Vamos a hacer una web que sirva para guardar marcadores, parecido a lo que hacía Del.icio.us (qué tiempos...)

Requisitos


- La app debe presentar una pantalla de login y ofrecer al usuario la posibilidad de registrarse. El email será la clave para identificar al usuario.

- Una vez autentificado, la aplicación listará los marcadores que el usuario haya ido guardando, así como las etiquetas que el usuario haya asignado al marcador.

- En la pantalla de listado, se puede filtrar por etiquetas.

- Para añadir un marcador, se presenta un formulario al usuario. El sistema debe tratar de hacer una vista previa del marcador (extraer el título, algún resumen, foto representativa...)
El usuario puede asignar etiquetas a cada marcador. Las etiquetas se pueden sugerir autocompletando a partir de las que ya hay.

Preparación del proyecto


Empecemos instalando Django según las instrucciones de la documentación. Una vez instalado, inicializamos el proyecto.


django-admin startproject bookmarks

Lo primero que vamos a hacer es definir los diferentes entidades y sus correspondientes modelos.

Tag: representa una etiqueta para uno o varios marcadores. Sus campos serían, aparte del id, que ya lo autogenera Django, name y description. Sólo es obligatorio name.

Bookmark: representa un marcador. Sus campos serían title, description, url e image. Son obligatorios title y url.


Un marcador puede tener muchas etiquetas, a su vez, una etiqueta puede estar presente en muchos marcadores. Esto nos sugiere una relación ManyToMany. Solo es necesario hacer la asociación entre marcadores y etiquetas en un punto, según nos indican en la documentación. Lo más razonable parece hacer la asociación en la entidad Bookmark, puesto que es la entidad sobre la que todo gira.

User: representa un usuario. Vamos a reutilizar todo el sistema de gestión de usuarios de Django.

Una vez definidos las vistas que queremos y los modelos que vamos a necesitar, empezamos el desarrollo.

Dentro de un proyecto Django podemos tener varias apps, en este caso, para un proyecto tan pequeño, probablemente sólo necesitaremos una app. El proyecto es un contenedor, lo que "hace algo" son las diferentes apps que hay en un proyecto. Llamemos a la app bookmarks.

python3 manage.py startapp bookmarks 

CommandError: 'bookmarks' conflicts with the name of an existing Python module and cannot be used as an app name. Please try another name

No podemos, lógicamente. Ya hay un módulo llamado bookmarks (el correspondiente al proyecto). Tenemos que pensar un nombre para la aplicación que no entre en conflicto y que describa un poco la naturaleza de la función de esta aplicación. Llamémosla manager, ya que esta app será el gestor de marcadores.

En este punto tenemos en el proyecto dos módulos, uno llamado "bookmarks", que corresponde al proyecto en su globalidad, con sus ajustes comunes, rutado... y otro llamado "manager", que corresponde a la aplicación (para este proyecto probablemente sea la única app que lleguemos a hacer).



La carpeta de "fuera", la que contiene el proyecto, nos da igual como se llame, de hecho la vamos a renombrar.



Mejor así. Comprobemos que funciona:

BookmarksManager david$ python3 manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).

You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

September 15, 2018 - 11:10:40
Django version 2.1.1, using settings 'bookmarks.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[15/Sep/2018 11:10:49] "GET / HTTP/1.1" 200 16348
[15/Sep/2018 11:10:49] "GET /static/admin/css/fonts.css HTTP/1.1" 200 423
[15/Sep/2018 11:10:49] "GET /static/admin/fonts/Roboto-Regular-webfont.woff HTTP/1.1" 200 80304
[15/Sep/2018 11:10:49] "GET /static/admin/fonts/Roboto-Bold-webfont.woff HTTP/1.1" 200 82564

[15/Sep/2018 11:10:49] "GET /static/admin/fonts/Roboto-Light-webfont.woff HTTP/1.1" 200 81348

Ya hemos terminado de montar la estructura del proyecto.

Definición de modelos

Vamos con los modelos del ámbito de la aplicación: Bookmark y Tag




Empezamos desde el principio utilizando la riqueza de tipos que Django nos aporta. Por ejemplo, un URLField no es más que un campo de texto con un validador adicional que nos comprueba si la URL que pretendemos guardar está bien formada.

Al utilizar un campo ImageField nos salta un error:

ERRORS:
manager.Bookmark.image: (fields.E210) Cannot use ImageField because Pillow is not installed.

HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "pip install Pillow".


Nos recuerda que para tratar imágenes, debemos instalar Pillow.

Instalamos la aplicación editando el settings.py del proyecto:



Nos quedan definir las relaciones entre Bookmark y Tag y los usuarios del sistema. Para ello, vamos a heredar nuestra entidad BookmarkUser de la clase AbstractUser.



Ejecutamos los comandos makemigrations y migrate y ya tenemos la base de datos preparada.



Preparando el admin

Para que en la aplicación admin de Django aparezcan estas entidades debemos registrarlas en admin.py de la aplicación.



También vamos a crear un usuario administrativo para la interfaz administrativa. Este usuario no es un usuario de la aplicación de marcadores, es un usuario interno de Django. Lo llamaremos "root", en analogía a UNIX.



Si accedemos a la interfaz admin con este usuario, ya podemos empezar a cacharrear con las entidades.




Hasta ahora no hemos hecho nada diferente de lo que el tutorial de Django nos enseña. Vamos a empezar a desarrollar en este punto nuestras funcionalidades.







martes, 4 de septiembre de 2018

Editando fecha EXIF de muchas fotos en bloque

No sé si todo el mundo es tan manazas con la fotografía como yo, pero me suele pasar a menudo que tengo fotos sin metadatos de fecha (las típicas que te pasan por Whatsapp, o capturas de pantalla, escaneados,...)

El siguiente script (en Python) recorre recursivamente un directorio y para cada foto que se encuentra, busca si tiene un dato de fecha de creación en los metadatos EXIF.

Si no lo tiene, y el formato del nombre del fichero es del tipo *yyyymmdd*, añade una fecha en los datos EXIF de la foto basada en el nombre del fichero.

#!/usr/bin/env python3

import sys, os, re, piexif


if len(sys.argv) < 2:
    print ("Usage:", __file__ , "DIRECTORY")
    sys.exit(1)


date_tag = piexif.ExifIFD.DateTimeOriginal
exif_key = 'Exif'
regexp = re.compile(r'\b(20(\d{6}))\b') 

if os.path.isdir(sys.argv[1]):
    base_dir = os.path.abspath(sys.argv[1])
    #print ("Searching photos in ", base_dir)
    for root, dirs, files in os.walk(base_dir):
        for f in files:
            full_path = os.path.join(root, f)
            filename, ext = os.path.splitext(full_path)
            if ext.lower() in ('.jpg', '.jpeg'):
                try:
                    exif_data = piexif.load(full_path) 
                    if not date_tag in exif_data[exif_key]:
                        # print ("No date-time data for", full_path)
                        match = regexp.search(filename)
                        if match:
                            new_date = "%s:%s:%s 00:00:00" % (match[0][:4], match[0][4:6], match[0][6:])
                            #print ("The file", full_path, "seems to have a date-time", match[0], "=>", new_date)
                            exif_data[exif_key][date_tag] = new_date
                            exif_bytes = piexif.dump(exif_data)
                            try:
                                piexif.insert(exif_bytes, full_path)
                                print ('"'+full_path+'" modified with date-time', new_date)
                            except Exception as e2:
                                print (e2)                      

                except Exception as e1:
                    pass
                    #print ("Error reading", full_path, "Corrupted file?")


else:
    print (sys.argv[1], "is not a valid directory.")
    sys.exit(1)

Es especialmente interesante el módulo piexif, no lo conocía. Funciona un poco a bajo nivel, pero para hacer estas manipulaciones rápidas va muy bien.

miércoles, 8 de agosto de 2018

Clean Code

Lectura recomendada:



Hay veces que repasando código ajeno uno se puede encontras cosas muy farragosas. Este método simplemente pretende buscar entre una lista de rangos cuál corresponde a un valor en concreto.
Por ejemplo: "A", para valores entre 100 y 51; "B" para valores entre 50 y 31; "C" para valores por debajo de 30.

Partíamos de este código, que prácticamente da miedo verlo:

  • El nombre del método es excesivamente largo y para un lector angloparlante, está mal escrito.
  • Los nombres de las variables también son muy largos y con elementos redundantes: "nameNosequé" es innecesario: toda variable se usa precisamente para nombrar algo.
  • La iteración es poco natural (con una condición negada como primer control del bucle) y usa construcciones del lenguaje (en este caso, la función "each" de PHP) que están "deprecated".
    Con nombres tan largos y esta asignación metida en el control del bucle, queda realmente ilegible:

    while (!isset($nameOfMatchedTransparencyRanking) && (list($transparencyRankingName, $arrayTransparencyRankingInterval) = each($arrayTransparencyRankings))) {
    // Cuerpo del bucle
    }
Para recorrer una estructura de datos de este tipo (array asociativo) es más natural y legible -y posiblemente, más eficiente- usar un bucle foreach.


No es la mejor implementación del mundo, pero al menos ya es más legible y se entiende un poco mejor qué hace este método. Si quisiéramos que fuese más genérico, y no estuviese atado a una aplicación en concreto, los nombres de variables podrían ser aún más genéricos (p.ej, $value en vez de $transparency).

Hacer un return entre medias del bucle no es muy académico ni formal, pero permite un código compacto y sencillo. Esta forma de escribir la utilizaban los mismísimos creadores de C, y en funciones cortas como esta, es perfectamente aceptable. En un método más largo, más allá de 10 líneas, personalmente, ya no lo utilizaría porque tendríamos un punto de salida descontrolado que no se ve claramente.



(Capturas del libro "The C Programming Language" de D. Ritchie y B. Kernighan. Si no sabes quiénes son estos señores, no sé que haces leyendo este blog ;-)

miércoles, 25 de julio de 2018

Sobre la "obligatoriedad" de usar HTTPS

Ayer (24/07/2018) Google publicó la versión 68 de su navegador, Chrome.

Una de las novedades más comentada es que empieza a marcar de forma explícita en la barra de direcciones cuando una web no se sirve bajo https:



Hasta ahora sólo se mostraba una advertencia, pero no de forma explícita. Si la página carga bajo https pero intenta referenciar recursos que no están bajo https, muestra un icono en la barra de estado que permite cargarlos.



Si optamos por cargar los "scripts no seguros", nos lo advierte claramente:


Algunas reflexiones tras muchas jornadas inventariando plantillas, ficheros de configuración, código...

  • Es una buena idea que las páginas se sirvan bajo https, así como los recursos que éstas emplean (css, js, imágenes,...)
    No vamos a repetir aquí todo el argumentario de seguridad, está claro que el hecho de que la conexión esté cifrada la hace más segura.
  • Menos mal que tenemos Let's Encrypt, si no, no hubiese sido posible para muchos sitios pequeños o medianos poder servir su web bajo https.
  • Hay un aspecto muy preocupante: la falsa sensación de seguridad que podemos crear, y que nos podemos llegar a creer.
    Servir nuestra web bajo https no la hace segura, esto es una obviedad. Si nuestras web son vulnerables (SQLi, XSS,...), lo seguirán siendo las sirvamos bajo http o https.
    Es nuestra obligación como técnicos recordárselo a las gerencias: tener el candadito verde no garantiza nada, es una protección más, simplemente.
  • También es algo preocupante el hecho de que Google y su calendario de productos marque el calendario de los proyectos IT, cada vez más.
    Debería hacernos reflexionar el hecho de que no se acometen las mejoras en general hasta que no viene uno de los grandes a apretarnos las clavijas. Pasó con la eliminación de Flash (lo que Apple no consiguió con su esnobismo lo consiguió Google recurriendo a motivos más terrenales: "anunciantes, ya no admitimos anuncios en flash"), ahora ha pasado con la implantación de https por defecto en las webs.
    Hay cosas que desde IT sabemos que deben hacerse y por calendarios de lanzamiento de producto, prioridades de la compañía, etc, se van posponiendo hasta que nos tiene que venir el hermano mayor (Google, Facebook, Microsoft, Apple,...) a decirnos "o implementáis esto o ...".
  • Cabrea un poco el encontrarse con tanto gurú y listillo que se creen que por saber migrar un blog en Wordpress o una web en Drupal tienen la suficiente autoridad como para pontificar y criticar a los que les está costando hacer esta migración.
    Si se tiene un site que tenga una infraestructura que implique varias zonas (una red interna, una red externa,...), servicios y servidores que interactúan entre sí, capas de caché, tareas programadas, varios CMS diferentes, componentes "legacy",.. el trabajo es bastante arduo, nada trivial.




martes, 6 de marzo de 2018

Euler #35

https://projecteuler.net/problem=35

La (pequeña) dificultad de este problema consiste en calcular todas las posibles rotaciones de un número. La sintaxis de Python hace muy cómoda esta tarea.

import sympy

def check_rotations(number):
 all_primes = True
 number_str = str(number)
 number_str = number_str[1:] + number_str[:1]
 while int(number_str) <> number:
  if not sympy.isprime(int(number_str)):
   all_primes = False
   break
  number_str = number_str[1:] + number_str[:1]
 return all_primes

circular_primes = []
for n in range(1, 1000000):
 if sympy.isprime(n) and check_rotations(n):
  circular_primes.append(n)

print circular_primes
print len(circular_primes)

Puntos a destacar:
  • sympy es una librería de Python especializada en matemáticas con símbolos (polinomios, resolución de ecuaciones, matrices,...) En nuestro script simplemente utilizamos la función isprime() para comprobar si el número es primo. Podríamos haber reutilizado alguna de las funciones que ya escribimos en problemas anteriores.
  • La expresión "cadena"[1:] devuelve todos los caracteres exceptuando el primero: "adena"
  • La expresión "cadena"[:1] devuelve el primer caracter, podríamos haber escrito también "cadena"[0]
  • La sintaxis de subíndices de Python garantiza siempre que s == s[:i] + s[i:] 
Una posible optimización es recorrer solo los números impares, fijando 2 como único par a incluir:

circular_primes = [2]
for n in range(3, 1000000, 2): # El tercer parámetro de range es "step"

martes, 20 de febrero de 2018

Euler #34

Tras un largo parón, otro problema del proyecto Euler, de nuevo lo solucionamos con Python. Tratando los números como cadenas de texto y convirtiendo los dígitos de nuevo a enteros el problema es muy sencillo de resolver.

https://projecteuler.net/problem=34


def fact(n):
 if n <= 1:
  return 1
 else:
  return n * fact(n - 1)

candidates = []
n = 3
while True:
# print "Analizyng", n
 n_str = str(n)
 partial_sum = 0
 for digit in n_str:
  partial_sum += fact(int(digit))
 if partial_sum == n:
  candidates.append(n)
 if n > 1000000:
  break
 n += 1

#print candidates
print sum(candidates)