Tutorial: Traducciones rápidas usando CSS para el plugin Opal Estate Pro

Continuando con mi aprendizaje de CSS, esta vez he encontrado que necesito cambiar el idioma de los controles generados por el plugin Opal Estate Pro, un plugin para integrar un sistema inmobiliario completo en WordPress, que puedes encontrar en este enlace.

La verdad, este plugin es muy interesante y ofrece muchas funciones, pero su documentación solo se limita a su pagina web y su soporte es solo en inglés. Por esta razón, mientras trabajaba en un proyecto, encontré la necesidad de hacer una traducción rapida hasta que encontraba como traducir los controles o encontrar un plugin en español.

El problema

Opal Estate Pro solo tiene soporte en inglés y el cliente quería los textos de los controles en español. En un principio pensé en usar el plugin Polylang, pero no funcionó. Como el ´presupuesto no alcanzaba para buscar una mejor alternativa, opté por usar CSS para cambiar el contenido de las etiquetas «label» mediante el siguiente código:

#a{
   content:"nuevocontenido";
}

Esta código permite agregar contenido a la etiqueta, por lo que nuestro texto se agregará al que ya estaba presente. para corregir esto, se aplica el siguiente estilo.

#a{
   visibility:hidden;
}
#a::before{
   content:"nuevocontenido";
   visibility:visible;
}

Al aplicar este estilo, se oculta el contenido anterior de la etiqueta y se agrega el nuevo contenido, dejando solo visible este ultimo.

Aprovechando que Opal Estate tiene clases especificas para cada uno de sus controles, podemos aprovechar esto para crear nuevas clases que aplicaremos en el CSS adicional del tema que tengamos activo.

/*Cambiar el idioma del label "Bedrooms" a español*/
.opalestate-label--Bedrooms {
    visibility: hidden;
}
.opalestate-label--Bedrooms::before {
    content: "Habitaciones";
    visibility: visible;
}
/*Cambiar el idioma del label "Parking" a español*/
.opalestate-label--Parking {
    visibility: hidden;
}
.opalestate-label--Parking::before {
    content: "Parqueo";
    visibility: visible;
}
/*Cambiar el idioma del label "Country" a español*/
.opalestate-label--country {
    visibility: hidden;
}
.opalestate-label--country::before {
    content: "País";
    visibility: visible;
}
/*Cambiar el idioma del label "Bathroom" a español*/
.opalestate-label--Bathrooms {
    visibility: hidden;
}
.opalestate-label--Bathrooms::before {
    content: "Baños";
    visibility: visible;
}
/*Cambiar el idioma del label "Type" a español*/
.opalestate-label--type {
    visibility: hidden;
}
.opalestate-label--type::before {
    content: "Tipo";
    visibility: visible;
}
/*Cambiar el idioma del label "Price" a español*/
.opalestate-label--price {
    visibility: hidden;
}
.opalestate-label--price::before {
    content: "Precio";
    visibility: visible;
}

De esta manera, conseguiremos nuestra traducción rápida para casos de emergencia. Por el momento, solo tengo estos controles traducidos y el código no funciona con el botón de búsqueda, así que toca investigar mas para llegar a un resultado completo. Por el momento, esto ha funcionado lo suficientemente bien como para dejarlo aplicado en producción.

Tutorial: Resaltar las respuestas correctas en Moodle

A medida que se ha ido modernizando los métodos de enseñanzas, he encontrado diferentes LMS (Learning Management System) como Chamilio, Lifter LMS y Moodle, donde este último es el más frecuente de todos. Cada uno tiene sus fortalezas y debilidades, pero he elegido Moodle para mis actividades de enseñanza debido a su modularidad y escalabilidad. Esto significa también que he encontrado problemas de diferentes magnitudes donde es necesario intervenir un poco en el código del sistema.

El problema que resolveremos hoy, probablemente pase desapercibido por muchos, pero es uno relacionado a la accesibilidad. Algunos temas lo resuelve, pero el predeterminado y algunos otros carecen de esta opcion.

Al hacer un cuestionario, moodle da la opción de marcar las respuestas correctas e incorrectas, destacando con tan solo un check los resultados. Una mejora de accesibilidad sería el resaltar toda la fila, puesto que esto hace más fácil la lectura. para esto, tenemos que hacer lo siguiente:

Buscamos en la administración del sitio, en la subsección de apariencia, el tema que tengamos activo. En este caso, mi tema activo es el predeterminado para mi version, llamado Boost.

Dentro del tema, vamos a los ajustes avanzados y agregamos en «SCSS sin modificar» agregamos el siguiente selector de cascada:

.answer .correct{
    background-color: mediumspringgreen;
    border-radius: 35px;
}

Y listo, tenemos resaltado la respuesta correcta con un bonito color verde claro. Eso sí, resaltar la respuesta incorrecta requiere algo más elaborado, pero lo dejaré para la próxima ocasión.

Resaltado de respuesta correcta en Moodle
Resaltado de respuesta correcta en Moodle

Tutorial: Solución para problemas de actualización en NextCloud

NextCloud es una plataforma de almacenamiento en la nube basada en PHP que por muchas situaciones, incluido el ser un fork de OwnCloud, han causado que tenga una comunidad mas pequeña de lo que merece, por lo que es posible encontrarse problemas básicos, que requieren mayor investigación y conocimiento técnico para resolverlos.

Al ser una solución SelfHosted, es normal encontrarse instancias de NextCloud en Raspberry Pi o computadoras de bajos recursos. Esto incide directamente en situaciones que no se darían al ser administrados de forma profesional, como es el caso del error que vamos a revisar hoy.

Según la Wikipedia:

Nextcloud es una serie de programas cliente-servidor que permiten la creación de servicios de alojamiento de archivos. Su funcionalidad es similar al software Dropbox, aunque Nextcloud es en su totalidad software de código abierto. Nextcloud permite a los usuarios crear servidores privados. Su modelo de desarrollo abierto permite añadir y/o modificar la funcionalidad del software del servidor en forma de aplicaciones. Nextcloud es una bifurcación de ownCloud, que también es un software de servicio de alojamiento en la nube

Nextcloud en la Wikipedia. Consultado al 2/12/2022

El problema

Hasta el momento no he encontrado una explicación razonable para este problema, pero ha aparecido en todas mis instalaciones hasta el momento según las siguientes configuraciones:

  1. VPS 1GB de RAM
    • PHP + Apache + NGINX + Subdominio
    • PHP + Apache + Subdominio
    • PHP + NGINX + Subdominio
  2. Raspberry Pi Modelo B 512 MB de RAM
    • PHP + Apache en Localhost
  3. Pine A64 1GB de RAM
    • PHP + Apache en Localhost
  4. Laptop Core i7 séptima generación 12GB de RAM
    • PHP + Apache en Localhost + túnel SSH inverso + NGINX (proxy inverso) + Subdominio (si, ha sido la configuración mas loca que he hecho)

Claro, cada instalación ha tenido sus diferentes problemas, pero en general, el actualizador es el problema principal. Actualmente la configuración mas efectiva que tengo funcionando es la de VPS 1 GB de RAM con PHP-FPM con NGINX.

Aunque en un principio pensé que podría ser cosa de la RAM, el haber tenido el mismo problema con una computadora de 12GB me hizo entender que tal vez haya algo mas de fondo. Aun así, lo mejor es resolver y seguir y ya encontraré la razón después.

Error: Step 5 is currently in process. Please reload this page later.

Este error aparece al intentar actualizar NextCloud con la interfaz web por primera vez al atascarse en el paso 5 del proceso de actualización.

Error Step 5 is currently in process. Please reload this page later

Luego de fallar en la actualización, el actualizador web deja de funcionar.

Step 5 is currently in process. Please reload this page later

Este error es bastante curioso pues no afecta al funcionamiento de NextCloud. Aun así, lo recomendable es mantenerse al día con las actualizaciones correspondientes

Si buscas en la carpeta de datos de tu instancia de NextCloud, puedes encontrar este registro, indicando que se ha atascado en el paso 5. Puedes borrarlo y el actualizador volverá a comenzar, aunque se atascará en el mismo paso.

Step 5 is currently in process. Please reload this page later

Para corregir el problema, debes hacer lo siguiente:

Elimina el archivo updater.log en el directorio de datos.

En el mismo directorio, elimina la carpeta updater_ocheg, este proceso puede tomar algún tiempo.

A partir de aqui, puedes seguir los pasos de la documentacion oficial para la actualizacion por consola, como esta descrito en este enlace:

https://docs.nextcloud.com/server/latest/admin_manual/maintenance/update.html

sudo -u www-data php --define apc.enable_cli=1 /var/www/nextcloud/updater/updater.phar

Este comando permite ejecutar el actualizador mediante la lineal de comandos y se mostrará el asistente de actualización por línea de comandos. Sigue los pasos indicados.

Asistente de actualizacion de NextCloud por Interfaz de linea de Comandos (CLI)
Asistente de actualizacion de NextCloud por Interfaz de linea de Comandos (CLI)
Actualizacion NextCloud completa

Una vez terminada la actualización, se mostrará un mensaje de éxito y la actualización estará completa.

Conclusiones

Es probable que para cada necesidad haya software que la resuelva, y para cada software pagado haya una version libre. Y aunque libre suela estar asociado a gratis, aunque no haya un costo real, fisico o tangible, es cierto que terminamos pagando de otras formas, por ejemplo, estudiando muchas cosas tecnicas, pagando por VPS, invirtiendo tiempo que podriamos estar gastando en algo mas productivo y cosas asi.

El selfhosting es una iniciativa que me ha encantado. Es divertido experimentar y descubrir soluciones a problemas extraños y, aunque haya mucha, muchisima frustracion, es algo que siempre termino repitiendo. Probablemente haga un post hablando sobre el selfhosting mas adelante, pero por ahora, aqui queda esta entrada.

Tutorial: Introducción a la POO mediante ejemplo practico 

Python Screensaver. Debido a que el proceso de actualizar pantalla depende de la velocidad del procesador, en equipos modernos se ve feo.

Escritura de un programa simple de salvapantallas con Python, utilizando programación orientada a objetos.

Tabla de Contenidos:

  • Introducción
  • Sobre el aprendizaje desde los fundamentos
  • Estructura del Tutorial
  • Requerimientos previos del tutorial
  • Desarrollo
    • Objeto «universo.cielo.componentes»
    • Objeto «universo.estrellas.componentes»
    • Raíz del programa: «main.py»
  • Flujo del programa
    • Inicialización de clases
  • Conclusiones
  • Webgrafía

Introducción

Como buen principiante que soy, he tenido que deambular buscando tutoriales que me expliquen como hacer ciertas cosas y que resuelven con relativo éxito mis necesidades. Además, mis comienzos en la programación con la universidad fueron muy anticuados, siendo que estudie programación estructurada en lugar de programación orientada a objetos, lo que me dificulta mucho aprender lo ultimo.

Como seguro se habrán dado cuenta, la mayoría de los tutoriales buscan llegar de un punto «A» a un punto «B» sin complicarse mas, para lo que recurren a una forma simple de programación que puede derivar en lo que se conoce como «código spaguetti», mientras que para aprender programación orientada a objetos, hay que recurrir a textos mecánicos que explican superficialmente y usan ejemplos muy cortos que no aportan demasiado para entender los conceptos.

Este tutorial probablemente no sea el mejor de su tipo, pero mi objetivo es escribir algo que siento que me hubiese servido a entender mejor algunos conceptos, para lo cual he recurrido a un programa escrito en Python por Suraj Singh quien ha escrito un tutorial breve, pero ha aportado un código super comentado que he traducido, comentado mas y reestructurado para poder explicar algunos conceptos que considero son necesarios para entender mejor sobre Python y programación orientada a objetos.

Quiero aclarar que el tutorial de Suraj Singh no es malo, de hecho, lo considero genial y es la razón por la que lo estoy usando de ejemplo, sino que quiero expandir lo que enseña para cumplir con mis expectativas sobre el aprendizaje desde los fundamentos.

Sobre el aprendizaje desde los fundamentos

Cuando comenzamos a programar, es muy probable que hayamos empezado por necesidad, por lo cual seguramente habremos buscado con desesperación códigos y los hemos apilado hasta que funcionaran. Esto nos permite aprender muchas cosas y muy rápido, con lo que podemos concluir con éxito lo que nos movió a buscar aprender a programar en primer lugar, pero nos hace confiarnos y descuidar los fundamentos que mas adelante nos permitirían resolver tareas con mas eficiencia y eficacia.

Estructura del Tutorial

Este tutorial tratará de explicar de forma detallada todos los conceptos requeridos para lograr la creación de un salvapantalla simple escrito en Python. Hay que recordar que no es un salvapantalla real, en el sentido de que no puede ser instalado en las plataformas nativas de salvapantallas de Windows o Linux y solo es un modelo básico para explicar los conceptos.

Estará agrupado en dos partes:

  • Tutorial documentado con código y gráficos.
  • Código comentado, listo para ejecutar.

Requerimientos previos del tutorial

  • Saber escribir un hola mundo en Python

Si, solo eso. Hay muchas cosas implicadas en escribir hola mundo en Python, por ejemplo, tener instalado el compilador, saber usar la consola y muchas cosas mas.

Al tener instalado el compilador también se incluyen muchas herramientas y en la mayoría de instalaciones al menos, ya vienen incorporadas las herramientas de Tkinter, para la creación de ventanas en modo gráfico.

Aunque el tutorial puede ser ejecutado en el «Notepad» si les parece bien, yo recomiendo el uso de «Visual Studio Code», junto a los complementos de Python, porque tiene funciones de asistencia que facilita depurar e incluso aprender como funcionan las cosas. Si quieres, puedes presionar la tecla Control + Click izquierdo para poder ver la función, clase o variable donde fueron definidas.

Desarrollo

La programación orientada a objetos, busca la optimización del código mediante el agrupamiento de funciones y atributos conocidos como «objetos». Para entenderlo provisionalmente, he decidido tratar de visualizar, tanto la estructura de carpetas como la organización de funciones, clases y atributos, como objetos del mundo real, por lo que la estructura del directorio representa una caja y la he etiquetado como «python screensaver»

Dentro de esta caja he creado el primer archivo llamado «main.py» que contendrá la lógica del programa y dentro de la misma, he agregado otra caja que he llamado «universo».

Python permite trabajar con módulos, que no son mas que archivos que contienen código adicional que mantenemos separado del archivo principal para que sea mas fácil de editar y depurar. Durante la ejecución, el interprete unirá todos los archivos en un solo script y los leerá secuencialmente para poder ejecutar el programa en un proceso detallado en el modelo de ejecución, en la documentación oficial.

Para crear un módulo, debemos crear una carpeta que contenga un archivo vacío con nombre «__init__.py». una vez creado, no le volveremos a tocar.

Dado que el programa que estamos escribiendo es un salvapantalla de un cielo estrellado, los nombres de los módulos deberían reflejar esto, por lo que la carpeta universo contendrá al cielo, con sus funciones(o sea, el código que define su comportamiento) y atributos y a las estrellas, también con sus funciones y atributos. (Podríamos ignorar un poco el debate de si el cielo contiene a las estrellas o el universo contiene al cielo). Esto deja nuestro directorio de la siguiente manera:

Jerarquía archivos Python screensaver

Es recomendable que cada parte del código sea fácil de entender, que cada nombre y variable sea fácil de deducir y todo este muy bien ordenado porque, aunque al inicio funcione, si hay que modificar o corregir mas adelante, puede llegar a ser muy complicado y enigmático. Es por eso que en esta ocasión he elegido este esquema para los nombres de carpetas y módulos. Por supuesto, si se desea se puede usar cualquier esquema, no hay reglas estrictas en este sentido.

Nota: Usualmente el primer archivo de tu código debería llamarse «main.py», puesto que este es el nombre mas frecuentemente usado para indicar que todo el programa comienza por allí, pero en otros lenguajes de programación, como JavaScript(con NodeJS), se llaman «index.js». Esto no es estrictamente necesario puesto que el primer archivo puede tomar cualquier nombre. Solo es un acuerdo tácito entre programadores para facilitar la lectura del código.

En el archivo «main.py» vamos a comenzar haciendo nuestras primeras importaciones de módulos del sistema:

import tkinter as tk
import time
import random

Los módulos se pueden importar por su nombre, pero durante la escritura del código, tal vez prefieras utilizar un «alias» o apodo, para reducir la cantidad de texto al escribir. En mi caso he importado el módulo tkinter como tk, pero en otros tutoriales pueden optar por otros nombres. Por supuesto, también puedes importarlo sin ningún alias, no hay problema con eso y como ves, la mayor parte de los módulos aquí usados aparte de tkinter, se usan de esa manera.

Nuestro salvapantalla tiene que preparar algunas cosas antes de visualizarse, por lo que nuestro siguiente componente en ser cargado es la clase «cielo» dentro del módulo «universo».

Cuando creamos nuestra estructura de archivos, consideramos la carpeta «Python screensaver» como nuestro contenedor y dentro del mismo, creamos la carpeta «universo».

Al definir el archivo «__init__.py», la carpeta pasa a ser parte del módulo, por lo que al importar el objeto «cielo», la buscará en el módulo universo, según este esquema:

import nombre_módulo.nombre_objeto

Así que importaríamos nuestro módulo de la siguiente manera:

import universo.cielo

Nuestro trabajo ahora consiste en desarrollar el objeto «cielo.componentes».

En el módulo cielo debemos repetir la importación de los módulos, pero en este caso solo hace falta uno:

import tkinter as tk

Además, importaremos también el otro objeto creado en el módulo universo llamado estrellas.

import universo.estrellas

Objeto «universo.cielo.componentes»

Según la Wikipedia:

Un objeto es un ente orientado a objetos (programa de computadoras) que consta de un estado y de un comportamiento, que a su vez constan respectivamente de datos almacenados y de tareas realizables durante el tiempo de ejecución. Un objeto puede ser creado instanciando una clase, como ocurre en la programación orientada a objetos, o mediante escritura directa de código y la replicación de otros objetos, como ocurre en la programación basada en prototipos.

En nuestro caso, los atributos de nuestro módulo «cielo» se encuentran en nuestra clase «componentes» donde está definida una función que establece los parámetros iniciales de funcionamiento y variables que contienen sus atributos (def __init__(self, *args, **kwargs)) y dos funciones que definen el comportamiento del cielo (def crear_estrellas(self) y def actualizar_pantalla(self))

El código de la clase «universo.cielo.componentes» queda entonces de la siguiente manera:

import tkinter as tk
import universo.estrellas

CANTIDAD_ESTRELLAS = 2

class componentes(tk.Canvas):
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs) 
        self.estrellas = []
        self.crear_estrellas()

def crear_estrellas(self):
    for i in range(CANTIDAD_ESTRELLAS):
        self.estrellas.append(universo.estrellas.componentes(self))
    return

def actualizar_pantalla(self):
    for i in self.estrellas:
        i.actualizar()
    return

Objeto «universo.estrellas.componentes»

Nota: No hay una forma lineal de escribir código en cuanto a programación orientada a objetos se refiere. Como te has dado cuenta, hasta el momento hemos creado una clase y ahora crearemos otra clase, ninguna de las cuales puede ser ejecutadas individualmente. Nuestro archivo «main.py» continua vacío excepto algunas importaciones que agregamos al principio. Esto es debido a que el código ya ha pasado por algunas pruebas y esquematizaciones, lo que permite crear este código ya organizado.

Es cuestión del programador el determinar la mejor forma de escribir su código, pero para practicar te recomiendo escribir programas sencillos en un solo archivo y practicar luego modularizándolos y convirtiendo en objetos algunas funciones y variables. Lo importante es entender los conceptos antes de tirarse a un proyecto mas grande, aunque si han llegado a este tutorial, es probable que ya hayan puesto en marcha algo de código antes de entender como funciona.

Al igual que en el módulo anterior, hacemos las respectivas importaciones, creamos las variables globales y creamos la clase:

import random

VELOCIDAD_RADIO=[i/10.0 for i in range(-10,-2)]+[i/10.0 for i in range(2,10)]
RADIO=25
COLOR_ESTRELLA = "yellow"

class componentes:
    def __init__(self, padre):
        self.padre = padre
        self.comenzar_movimiento()
        self.crear_circulo_pequeño()
    
    def comenzar_movimiento(self):
        self.x1 = self.padre.winfo_width()/2
        self.y1 = self.padre.winfo_height()/2
        self.velocity_x = random.choice(VELOCIDAD_RADIO)
        self.velocity_y = random.choice(VELOCIDAD_RADIO)
        return

    def crear_circulo_pequeño(self):
        x1=self.x1
        y1=self.y1
        x2,y2=x1+RADIO, y1+RADIO
        self.estrella = self.padre.create_oval(x1,y1,x2,y2, fill=COLOR_ESTRELLA)
        return

    def parar_movimiento(self):
        self.padre.coords(self.estrella, self.x1,self.y1,self.x1+RADIO,self.y1+RADIO)
        self.comenzar_movimiento()
        return

    def actualizar(self):
        self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
        x,y = self.padre.coords(self.estrella)[:2]
        if x<0 or x>1500:
            self.parar_movimiento()
        elif y<0 or y>1000:
            self.parar_movimiento()
        return

Raíz del programa: «main.py»

Podría parecer un poco extraño no haber comenzado desde el inicio, pero el objetivo de este tutorial no es explicar el diseño de un código modular, sino explicar los conceptos implicados en el desarrollo del código mediante programación orientada a objetos. Debido a esto, recién llegamos a la parte que unificará todo el código escrito hasta el momento.

El código entonces, quedaría así:

import tkinter as tk
import time
import random
import universo.cielo

COLOR_FONDO="black"
NIVEL_TRANSPARENCIA=1
EVENTOS_DE_CIERRE = ['<Any-KeyPress>', '<Any-Button>']

def main():
    raiz=tk.Tk()#establecer la ventana raiz o inicial

    pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO)
    pantalla.pack(expand="yes",fill="both") 
    
    raiz.wait_visibility(pantalla)
    raiz.wm_attributes('-alpha',NIVEL_TRANSPARENCIA)
    raiz.wm_attributes("-topmost", True)
    raiz.overrideredirect(1)
    raiz.state('zoomed')

    def salir(event):
        raiz.destroy()
        return
    
    for seq in EVENTOS_DE_CIERRE:
        raiz.bind_all(seq, salir)

    while True:
        raiz.update()
        raiz.update_idletasks()
        pantalla.actualizar_pantalla()

if __name__ == '__main__':
main()

Flujo del programa

El código ya se encuentra completamente comentado y explicando que hace cada función, variable, clase y modulo, pero hare unas cuantas aclaraciones aquí.

Inicialización de clases

Importar los módulos no es suficiente para empezar a usarlos. Es necesario inicializarlos, para lo cual, se los llama solos o dentro de una variable, pasándoles los parámetros necesarios para funcionar.

El modulo «universo.cielo.componentes» es inicializada en la función «main()» del archivo main.py de la siguiente manera:

pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO)

La clase «universo.cielo.componentes» recibe como parámetro, la inicialización que hemos realizado del componente «tkinter». Esto nos evita tener que inicializar las clases y redefinir nuestros parámetros cada vez que lo necesitemos. Reutilizar código es una de las piezas clave de la Programación Orientada a Objetos y en este caso, lo que haremos se conoce como «Herencia»

La clase «universo.cielo.componentes» recibe los parámetros e inicializa las variables y funciones necesarias. Es decir, crea una instancia de la ventana que hemos creado, crea un arreglo que contendrá las estrellas e inicializara la función «crear_estrellas()» para definir el comportamiento inicial de la clase.

def __init__(self, *args, **kwargs):
    tk.Canvas.__init__(self, *args, **kwargs) 
    self.estrellas = []
    self.crear_estrellas()

En Python, se utiliza la palabra «self» para representar una instancia de la clase. Esto le permite acceder a los atributos y funciones que esta posee. Todas las funciones definidas en la clase, deberán utilizar esta palabra para poder acceder a sus variables inicializadas en «__init__»

El arreglo «self.estrellas» no contendrá datos, sino instancias de la siguiente clase que vamos a inicializar: «universo.estrellas.componentes»

La función «crear_estrellas()» crea un ciclo donde se agregan diferentes instancias de la clase «universo.estrellas.componentes» de acuerdo a la cantidad que hayamos definido en la variable «CANTIDAD_ESTRELLAS»

def crear_estrellas(self):
    for i in range(CANTIDAD_ESTRELLAS):
        self.estrellas.append(universo.estrellas.componentes(self))
    return

Las clases son instanciadas cada vez que son invocadas. En el caso de «universo.estrellas.componentes», sera instanciada 2 veces según lo definido en «CANTIDAD_ESTRELLAS», lo cual significa que el objeto estrella llega a existir dos veces, lo cual es equivalente a tener físicamente dos estrellas, con sus respectivos atributos y comportamiento.

La clase «universo.estrellas.componentes», al momento de ser inicializada, heredará todos los atributos de la clase que la llama, estableciéndose una relación «Padre-Hija» Esto se logra enviando como parámetro la palabra «self» y recibiéndola en una variable que en este caso llamaremos «self.padre»

def __init__(self, padre):
   self.padre = padre
   self.comenzar_movimiento()
   self.crear_circulo_pequeño()

La clase «universo.estrellas.componentes» al ser inicializada, guarda la información de la clase padre en la variable «self.padre» y llama dos funciones: «self.comenzar_movimiento()» y «self.crear_circulo_pequeño()». Estas funciones establecen los parámetros iniciales del objeto en la pantalla y crean el objeto en la pantalla.

Una vez inicializadas las clases, la función «main()» tiene acceso completo a sus atributos y funciones, por lo cual, se procede a actualizarlas desde el loop«While True»

while True:
    raiz.update() 
    raiz.update_idletasks()
    pantalla.actualizar_pantalla()

La variable «pantalla» ahora tiene acceso a la función «actualizar_pantalla()» dentro de la clase «universo.cielo.componentes» la cual hará un barrido de los objetos creados en «self.estrellas» para que actualicen sus propiedades.

«universo.cielo.componentes.actualizar_pantalla()»

def actualizar_pantalla(self):
    for i in self.estrellas:
        i.actualizar()
    return

«universo.estrellas.componentes.actualizar()»

def actualizar(self):
    self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
    x,y = self.padre.coords(self.estrella)[:2]
    if x<0 or x>1500:
        self.parar_movimiento()
    elif y<0 or y>1000:
        self.parar_movimiento()
    return

Conclusiones

La programación orientada a objetos puede ser muy confusa a veces. Como hemos visto, un programa relativamente sencillo puede ser un laberinto difícil de rastrear, sin embargo, nos permite realizar cosas complejas de forma mas eficiente.

Entender la programación orientada a objetos desde su enfoque inicial, como una representación de los objetos de la vida real, puede facilitar mucho las cosas. Es de sentido común que todo objeto que nos rodea, tiene atributos y comportamientos definidos, pero es un poco mas abstracto los conceptos de herencia, abstracción, polimorfismo, acoplamiento y encapsulamiento, pero es un buen punto de partida.

Conforme se siga practicando, es posible entender cosas mas complejas y también es posible llegar a entender, por que se hizo tan popular en la actualidad.

Webgrafía

Módulos en Python. Por Learn Python. https://www.learnpython.org/es/Modules%20and%20Packages

how to create beautiful moving stars/snow like screen saver using python and tkinter – tutorial – two | python gui example. Por Suraj Singh. https://www.bitforestinfo.com/blog/04/11/how-to-create-beautiful-movingstars-r-snow-like-screensaver-using-python-and-tkinter.html

Objeto(programación) https://es.wikipedia.org/wiki/Objeto_(programación)

Código Espagueti https://es.wikipedia.org/wiki/Código_espagueti

Modelo de Ejecución https://docs.python.org/es/3/reference/executionmodel.html

Herencia (informática)https://es.wikipedia.org/wiki/Herencia_(inform%C3%A1tica)

Código y esquema de directorio:

Jerarquía archivos python screensaver
Jerarquía archivos python screensaver
# Importar los componentes necesarios
import tkinter as tk
import time
import random

#importar los componentes que contienen las clases que hemos creado
import universo.cielo

#Definir variables globales
COLOR_FONDO="black" #Establecer la variable que define el atributo del color del cielo
NIVEL_TRANSPARENCIA=1
EVENTOS_DE_CIERRE = ['<Any-KeyPress>', '<Any-Button>'] #Se puede crear un arreglo de eventos para inicializarlos todos de una vez


def main():
    raiz=tk.Tk()#Establecer la ventana raiz o inicial

    pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO) #crear la ventana, utilizando la clase «cielo» como lienzo donde se dibujaran los componentes
    pantalla.pack(expand="yes",fill="both") #agrega los componentes en el lienzo
    
    raiz.wait_visibility(pantalla) #Se espera a que la ventana sea construida para cargar los componentes
    raiz.wm_attributes('-alpha',NIVEL_TRANSPARENCIA) #Se agrega el atributo de transparencia a la ventana
    raiz.wm_attributes("-topmost", True) #Se agrega el atributo «por encima de todo» a la ventana
    raiz.overrideredirect(1) #Evita redimensionar la ventana
    raiz.state('zoomed') #Inicia la ventana en estado "maximizado"

    #Esta funcion permite terminar el programa al ser disparada por los eventos definidos en la variable EVENTOS_DE_CIERRE
    def salir(event):
        raiz.destroy()
        return
    
    #Permite vincular la ventana a la lista de eventos definidos en la variable «EVENTOS_DE_CIERRE»
    for seq in EVENTOS_DE_CIERRE:
        raiz.bind_all(seq, salir)

    while True:
        raiz.update() #Actualiza la ventana
        raiz.update_idletasks() #Actualiza la ventana si no hay actividad
        pantalla.actualizar_pantalla() #Recarga la ventana mediante la clase cielo

#establecemos la funcion main como arranque al iniciar el script
if __name__ == '__main__':
 main()
#Importar los componentes necesarios
import tkinter as tk

#Importar los componentes que contienen las clases que hemos creado
import universo.estrellas

#Crear una variable globlal para la cantidad de estrellas en pantalla.
CANTIDAD_ESTRELLAS = 2

# Definir la clase componentes
class componentes(tk.Canvas):
    # Definir la clase inicial, que recibira los parametros desde donde sea inicializada.
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs) # Crear un objeto tkinter con los parametros iniciales
        self.estrellas = [] # Establecer un arreglo de «estrellas» obtenidas de la clase «universo.estrellas.componentes»
        self.crear_estrellas() # Llamar a funcion crear_estrellas() al inicializar la clase

    # Funcion para crear estrellas
    def crear_estrellas(self):
        for i in range(CANTIDAD_ESTRELLAS): # Crea un arreglo de instancias de universo.estrellas.componentes de acuerdo a la cantidad definida en la variable CANTIDAD_ESTRELLAS
            self.estrellas.append(universo.estrellas.componentes(self))
        return

    #Funcion para actualizar la posicion de las estrellas en el cielo
    def actualizar_pantalla(self):
        for i in self.estrellas:
            i.actualizar()
        return

import random

VELOCIDAD_RADIO=[i/10.0 for i in range(-10,-2)]+[i/10.0 for i in range(2,10)]
RADIO=25
COLOR_ESTRELLA = "yellow"



class componentes:
    # Definir la clase inicial, que recibira los parametros desde donde sea inicializada.
    def __init__(self, padre):
       self.padre = padre
       self.comenzar_movimiento()
       self.crear_circulo_pequeño()
    
    # Establece los parametros iniciales para realizar los movimientos del objeto en la pantalla
    def comenzar_movimiento(self):
        self.x1 = self.padre.winfo_width()/2
        self.y1 = self.padre.winfo_height()/2
        self.velocity_x = random.choice(VELOCIDAD_RADIO)
        self.velocity_y = random.choice(VELOCIDAD_RADIO)
        return

    # Crea un circulo en la pantalla
    def crear_circulo_pequeño(self):
        x1=self.x1
        y1=self.y1
        x2,y2=x1+RADIO, y1+RADIO
        self.estrella = self.padre.create_oval(x1,y1,x2,y2, fill=COLOR_ESTRELLA)
        return

    # Detiene el movimiento del objeto en pantalla
    def parar_movimiento(self):
        self.padre.coords(self.estrella, self.x1,self.y1,self.x1+RADIO,self.y1+RADIO)
        self.comenzar_movimiento()
        return

    # Actualiza la posicion del objeto en pantalla
    def actualizar(self):
        self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
        x,y = self.padre.coords(self.estrella)[:2]
        if x<0 or x>1500:
            self.parar_movimiento()
        elif y<0 or y>1000:
            self.parar_movimiento()
        return

Actualización:

¡Codigo en GitHub!

Acceder a un terminal mediante SSH inverso

Una de las cosas mas útiles que he deseado tener disponible, es el acceso SSH a mi computadora persona, desde cualquier parte del mundo. He conocido herramientas como ngrok y localtunnel que sirven, mas o menos, para lo que necesito, pero quería intentar con una herramienta que no dependa de servidores externos y ajenos a mi poder, sea porque no quiero compartir mis datos con terceros, o porque simplemente quería aprender a hacerlo por mi mismo.

Luego de mucho investigar, he encontrado algo que existe desde hace mucho tiempo y que probablemente es conocido por todo el mundo, pero recién aprendí a hacerlo por mi cuenta; SSH permite hacer túneles inversos.

Esquemas de control remoto

Desde el periodo de confinación por el COVID-19, todo tipo de servicios se hicieron sumamente populares, como zoom para las conferencias, y AnyDesk para trabajo remoto.

Aunque es de conocimiento común, se puede controlar un equipo utilizando el modelo Cliente-Servidor, donde el cliente se conecta al servidor y el servidor responde a sus peticiones.

Modelo Cliente-Servidor
Modelo Cliente-Servidor

En este modelo, el equipo a controlar debe ser el servidor, es decir, debe mantenerse escuchando para poder responder las peticiones del cliente, es decir, el controlador.

Este modelo es sencillo si hay conexión directa entre el cliente y el servidor, por lo que se puede utilizar la siguiente combinación de programas para el acceso remoto:

  • SSH – SSHd Acceso mediante consola (y montones de trucos mas).
  • RDP/XRDP – Escritorio remoto de Windows/Remina(Linux) Acceso mediante escritorio remoto usando el protocolo RDP de Windows.
  • Vino-Vinagre Acceso mediante VNC que admite múltiples sistemas operativos.

El problema entonces es que todo esto funciona en una red local, pero no a través de internet.

La gran nube

La red de redes
La red de redes

Podríamos definir a internet como la red que une a muchas redes pequeñas mediante un mapa de rutas complejas trazado por algoritmos aun mas complejos de entender.

Inicialmente, los proveedores de internet buscan conectar un solo dispositivo a internet, pero en una casa, esto no es lo ideal. Hay muchos dispositivos y cada día parecen haber mas, por lo que una sola conexión no abastecería.

Para solucionar esto, se crea una pequeña red local mediante routers que guiaran todas las conexiones hacia el exterior.

Es probable que por seguridad el proveedor de internet haya limitado las conexiones del router a solo salientes, por lo que no hay forma de conectarse desde afuera.

También es probable que el mismo proveedor haya creado una infraestructura compleja de subredes anidadas que dificultan aun mas encontrar el camino hacia un equipo en especifico desde el exterior. Casi cualquier dispositivo que no esté en la red local, estará en la misma situación, por lo que la comunicación entre ambos es virtualmente imposible.

La Solución

Internet es una maraña de dispositivos conectados, pero si alejamos lo suficiente nuestra perspectiva, nos daremos cuenta de que en realidad, parece una gigantesca red local.

Esto significa que es posible conectarse a algún nodo que tenga conexión directa y publica a internet, utilizando lo que es llamado IP Pública

Algunos proveedores de internet ofrecen el servicio de IP publica a sus clientes, por lo que ajustando algunas configuraciones en el router, ya se puede acceder desde cualquier parte del mundo a nuestro equipo, pero esto es innecesariamente peligroso, por cuanto internet es un lugar plagado de atacantes buscando vulnerabilidades en cada IP que puedan encontrar.

Para resolver esto y evitarle a los clientes todo tipo de pesadillas, las empresas proveedoras de servicios de control remoto, crearon servidores de conexión inversa, que sigue un modelo de Cliente – Servidor – Cliente.

Modelo Cliente Servidor Cliente
Modelo Cliente Servidor Cliente

Para este ejercicio, vamos a imitar el modelo que utilizan las grandes empresas y crearemos un puente para alcanzar algún equipo de nuestra red local, desde cualquier parte de internet.

Desarrollo de la Solución

Aunque técnicamente se puede realizar con solo dos dispositivos, lo mas común es que se necesiten tres, así que enumeraremos los requisitos a continuación:

  • Dispositivo de destino: Se encuentra aislado en una red local. y es incapaz de recibir conexiones entrantes debido a la configuración de su red y su proveedor de internet, pero puede ejecutar conexiones salientes a cualquier parte de internet.
    • Servidor SSH Puede ser cualquiera, como OpenSSH o Dropbear. Nota importante: el dispositivo debe tener instalado algún servidor SSHd. He hecho pruebas con termux en Android y, aunque tiene uno, su problema esta relacionado con que no permite el login de usuario y contraseña, para lo cual se deben usar otros métodos de autenticación.
    • Cliente SSH Puede ser cualquiera.
  • Servidor: Se encuentra en un lugar accesible de internet y permite conexiones entrantes o salientes libremente. Generalmente es un servidor, pero puede ser cualquier dispositivo mientras tenga una IP pública.
  • Dispositivo de salida: Generalmente está en la misma situación que el dispositivo de destino, pero en otra red. lo importante es que tenga conexión saliente hacia el servidor.

Seguramente ya dispones de los dispositivos de destino y de salida, pero el servidor es algo menos probable, así que, por que no arriesgarse con un droplet de Digital Ocean?

DigitalOcean Referral Badge

Utilizando este enlace puedes crear una cuenta con un crédito inicial de 100 dólares, totalmente sin costo durante 60 días. Úsalo para aprender y practicar con servidores Linux y cuando se acabe el periodo de prueba, puedes comprar Droplets desde 5 dólares mensuales. Recuerda administrar responsablemente tus VPS para evitar sorpresas al final del mes.

Ahora vamos a empezar a aplicar la configuración de los distintos dispositivos implicados:

Dispositivo de Destino

Este dispositivo, que se encuentra atrapado en una red que no permite conexiones entrantes, va a permanecer conectado todo el tiempo al servidor para que el túnel se mantenga vivo. Eso significa que la ventana del terminal tendrá que mantenerse abierta en todo momento o de lo contrario el túnel morirá.

En las aplicaciones de control remoto esto también sucede, pero es mas transparente al usuario debido a que utilizan servicios para mantener vivo el cliente y revivir el túnel cada que sea necesario. En nuestro caso se puede realizar algo similar, enviando la conexión al fondo mediante screen u otras herramientas.

En el dispositivo de destino, hay que escribir lo siguiente

ssh -R 16789:localhost:22 usuario@servidor

donde:

  • ssh Inicia la aplicación de shell segura.
  • -R 16789:localhost:22 vincula un puerto arbitrario(de nuestra elección) en el dispositivo de destino, al puerto 22 del servidor SSHd.
    • 16789 es un puerto arbitrario, es decir, podemos poner cualquiera, siempre que sea un puerto libre en nuestro equipo.
    • localhost o 127.0.0.1 es la dirección local de nuestro dispositivo.
    • 22 es el numero del puerto al que vamos a redirigir la conexión entrante. En este caso, es el puerto de SSHd.
  • usuario@servidor
    • usuario Es el nombre del usuario existente en el servidor al que nos vamos a conectar y utilizar de puente.
    • servidor Es el nombre de dominio o dirección IP del servidor al que nos estamos conectando.

Al ejecutar este comando, se abrirá una conexión con el servidor, que deberás mantener viva tanto como quieras acceder por el túnel inverso. Por supuesto, mientras estas en ese túnel, puedes usar libremente al servidor como si fuera una conexión normal.

Dispositivo de salida

Puede parecer raro comenzar por el dispositivo de salida, pero asumiremos que en el servidor ya está SSHd bien configurado.

En el dispositivo de salida deberás acceder al servidor con una conexión normal de SSH

ssh usuario@servidor

Donde:

  • ssh Es el comando de la consola segura
  • usuario@servidor
    • usuario Es el usuario del servidor
    • servidor Es el nombre de dominio o la dirección IP del servidor

Una vez que haya una sesión iniciada en el servidor, debemos ejecutar este comando y tendremos acceso a la consola del dispositivo de destino.

ssh usuario@localhost -p 16789

Donde:

  • ssh Es el comando de la consola segura.
  • usuario@localhost Es el usuario del dispositivo de destino.
    • usuario es el usuario del dispositivo de destino.
  • -p 16789 Es el puerto que abrimos en el dispositivo de destino.

Si queremos acceder a nuestro dispositivo de destino, hemos de dejar encendido el túnel, pero una falla de la red podría causar que la conexión se cierre. Al no estar cerca del equipo para restaurar la conexión, no podremos revivir el túnel hasta que volvamos presencialmente a restaurarla, así que lo recomendable es utilizar una herramienta que reviva el túnel cada que sea necesario o utilizar un cliente que pueda mantener conexiones persistentes a pesar de los quiebres de red.

Mientras tanto, se pueden utilizar sin problema los demás protocolos que funcionan sobre SSH, como SFTP o SCP por ejemplo.

Eso si, hay que recordar que para conectarse, hay que usar el puerto arbitrario que hemos definido anteriormente.

SFTP:

 sftp -oPort=16789 usuario@localhost

SCP

scp -p -P 16789 /tmp/ssh.txt usuario@localhost:

Hay muchas dudas que me quedan pendientes pero las iré estudiando conforme vaya necesitando mas cosas, pero tener este túnel es algo interesante para practicar.

Bibliografía/Webgrafía

Conexión a servidor mediante túnel inverso ssh Obtenido de https://openwebinars.net/blog/conexion-servidor-mediante-tunel-inverso-ssh/

Túnel inverso. Obtenido de :https://mundo-hackers.weebly.com/tuacutenel-inverso-ssh.html

Creación de túnel inverso. Obtenido de https://campusvirtual.ull.es/ocw/pluginfile.php/2172/mod_resource/content/0/perlexamples/node48.html

Bitácora de desarrollo: Bot Descargar Musica 31/5/22

El articulo a continuación es una bitácora del desarrollo de un bot o plataforma de bots que actualmente tengo funcionando. Se puede acceder usando el siguiente URL: Descargar_Musica

He comenzado esta especie de portafolio con un pequeño bot de Telegram para descargar música desde YouTube. Para esto, he recurrido a las siguientes tecnologías:

Los cambios que he realizado recientemente son muy escasos. Mi conocimiento sobre javascript es relativamente superficial, por lo que he decidido aprender clases, módulos y librerías en javascript. Debido a esto, ahora se han realizado los siguientes cambios:

Creación del directorio data/descargar_musica/descargas

Con el fin de crear un sistema de control para bots unificado, he creado este directorio para que en el futuro, pueda agregar mas directorios de datos para cada bot. En esta situación en especifico, este cambio se hace por la necesidad de asegurar que si hay datos corruptos o incompletos, o un sistema de borrado deficiente, al modificar o borrar los archivos, esto no afecte a otros bots o al propio código fuente.

Creación de las librerias db_logging_system y telegram_logging_system

Con el fin de crear un código mas simple y fácil de mantener, he creado estas librerías universales, que permiten registrar a los nuevos usuarios que utilicen el bot y otra que registre los eventos en un canal distinto.

PD: Sigo sin entender mucho esto, pero ahi logre hacerlo funcionar.

Creación de un vídeo tutorial

Este bot no tiene mayor complicación. Lo único que requiere es que envíen un enlace y comienza a trabajar, sin embargo, siguen mandandole enlaces de spotify e incluso itunes. Si bajar música de estas plataformas fuera legal (y hubiese alguna ganancia aparte de practicar programación) seguro que implementaría una función adecuada, pero por el momento, esto no es asi.

Telegram tiene muchas funciones interesantes y una de mis preferidas son los bots inline. Es posible conseguir la url de un vídeo de youtube, usando el bot @vid y una vez seleccionado el vídeo, mi bot comenzará a trabajar.

Uso de los directorios data/descargar_musica/descargas y data/descargar_musica/info

En la revisión anterior, los vídeos se descargaban en el mismo directorio de ejecución. Sean estos archivos temporales o archivos completos, todos se guardaban sin reparo allí, por lo que si ocurría un error o el sistema caía abruptamente, este directorio que contiene los ejecutables, se llenaba de basura que era difícil de limpiar. Para esto, se ha creado el directorio descargas, dentro de uno asignado al bot en cuestión, donde irán todas las descargas realizadas y las que hayan fallado también.

el directorio info, existe en cambio para guardar datos permanentes que no son parte del código, como el vídeo instructivo mencionado previamente

Otros cambios

  • Se agregaron mas registros para mas eventos (errores, eventos, arranque y comandos)
  • Se limpió el código aplicando las nuevas librerías creadas
  • Se optimizo el tiempo de respuesta del bot
  • Se agregaron nuevos mensajes de respuesta
  • Se agregaron nuevas instrucciones para situaciones no controladas
    • En caso de introducir un enlace no reconocido, se agregó la misma instrucción del comando sobre_mi.
    • En caso de haber un error al descargar, se agregó un mensaje de notificación.
    • En caso de haber un error al convertir el archivo.
    • En caso de haber un error al eliminar los archivos temporales.

Proximos cambios

El bot ya se encuentra en un estado bastante aceptable para lo que queria aprender, pero deseo aprender mas cosas, por lo que espero poder agregar lo siguiente:

  • Registro particular para cada bot (sistema “Panoptic”)
  • Control de depuración por comandos para el bot
  • Control web para el bot
  • Control general para todos los bots en el sistema
  • Mejor registro de información común
  • Mejor registro de información especifica
  • Control personalizado de acuerdo al usuario
  • Integración con otras plataformas de bot (discord, twitch, etc)
Interlan
A %d blogueros les gusta esto: