Jack the Bodiless (Galactic Milieu Trilogy #1)
![]() |
Review:The pliocene exile saga was one of my favourite scifi bok series when I was a teenager. I had read it all in the wrong order because finding the books in Argentina was not easy. |
![]() |
Review:The pliocene exile saga was one of my favourite scifi bok series when I was a teenager. I had read it all in the wrong order because finding the books in Argentina was not easy. |
Aquí están algunos de los features que quiero:
Login via twitter / Facebook / Google / OpenID
Número ilimitado de threads
Soporte de like / dislike en threads y en posts
Avatares
HTML en los posts
Que mande mail al usuario si le responden
Feeds RSS para los threads
Se lo puede ver en acción en http://foro.netmanagers.com.ar (por un tiempo limitado ;-)
Y aquí está el código:
import bottle import disqusapi as disqus import json shortname = 'magicmisteryforum' api = disqus.DisqusAPI(open("key").read().strip()) @bottle.route('/', method='GET') def index(): msg = bottle.request.GET.get('msg', '') threads = api.forums.listThreads(forum=shortname, limit=100) print threads[0] return bottle.template('main.tpl', threads=threads, shortname=shortname, msg=msg) @bottle.route('/new', method='POST') def new(): title = bottle.request.forms.get('title', None) if not title: bottle.redirect('/?msg=Missing%20Thread%20Name') return thread = api.threads.create(forum=shortname, title = title) thread_id = thread.__dict__['response']['id'] # Redirecting to /thread/thread_id doesn't work # because threads take a few seconds to appear on the listing bottle.redirect('/') @bottle.route('/thread/:id') def thread(id): t = api.threads.details(thread=id) return bottle.template('thread.tpl', shortname=shortname, id=id, thread=t.__dict__['response']) @bottle.route('/static/:path#.+#') def server_static(path): return bottle.static_file(path, root='./static') app = bottle.app() app.catchall = False #Now most exceptions are re-raised within bottle. bottle.run(host='184.82.108.14', port=80, app=app)
Requiere Bottle y la Disqus python API
Por supuesto que hay un poquito de templates, acá está main.tpl y thread.tpl. Como apesto para el HTML, usa Bluetrip CSS y es sencillo de customizar.
POR SUPUESTO QUE HAGO TRAMPA!
Esta cosa es apenas una capa de pintura encima de Disqus! Más un blog sin posts pero con comentarios que un foro! Pero... qué le falta para ser un foro de verdad? Funciona, no? Hasta se podrían usar categorías de Disqus para crear subforos...
Teniendo todo en cuenta, creo que es un hack bonito.
Y si esperás unos días, esto lleva a otra cosa que es mucho más mágica...
Código fuente completo en http://magicforum.googlecode.com
Claro que si yo fuera japonés y encima de todo eso tuviera que ver como algunos pelotudos dicen que dios me está castigando porque (lo que sea que al pelotudo no le gusta de Japón), estaría tentado de buscar al susodicho ganso y ... bueno, considerando que los japoneses están demostrando ser una gente muy razonable y civilizada, digamos que decirle algo, muy educadamente.
Por otro lado, no soy japonés, por lo que me siento libre de explicar en detalle porqué los que dicen "en una de esas es <algo> castigando a | diciendole a Japón <alguna cosa>" son un desperdicio de oxígeno.
Me voy a enfocar en un ejemplo, porque es una pelotuda religiosa en especial: la candidata a presidente Lilita Carrió.
Acá están las declaraciones::
"Dios nos está diciendo que debemos cuidar el planeta, que no sigamos destruyendo la tierra, que vivamos en la verdad, en la decencia, en la justicia, que no usemos la tecnología, aunque sea de manera pacífica. Hay que leer los signos de los tiempos"
Veamos de a pedacitos, como hubiera dicho Jack el destripador si en realidad hubiera sido Jack el descuartizador:
"Dios nos está diciendo que debemos cuidar el planeta"
Debo confesar mi confusión al ver que un ser todopoderoso se hace entender menos que mi nene de casi 4. Cuando quiere jugar a la pelota viene con la pelota y me dice "pa, quiero jugar a la pelota".
Por otro lado, al parecer dios, para decirnos que cuidemos el planeta y dejemos de usar tecnología prefiere provocar una serie de catástrofes en las antípodas, traer las noticias por internet (un milagro tecnológico) para que las vea Lilita, interprete la verdadera intención de dios, y me las cuente por radio (otro milagro tecnológico más viejo).
Eso tiene sentido para alguien? O sea, por qué dios no dice las cosas derecho viejo, clarito? Porque para los religiosos, lo divertido es advinar. Son como sacerdotes romanos tratando de ver la voluntad de Júpiter en las entrañas de un animal, pero usan la vida y el sufrimiento de la gente.
Uy, mirá, gente sufriendo en Japón, dios quiere que no usemos la Wii!
No solo va eso en contra de toda la doctrina cristiana, desde la virtud de la caridad (si dios lo hizo para decirnos algo, por definición se lo merecían) a la prohibición de adivinar la intención de dios observando portentos (sí, está prohibido para vos, monaguillo, hablá con un cura).
"[Dios nos está diciendo] que no sigamos destruyendo la tierra"
Ah, bueno! Ok! La próxima, ojalá encuentre una manera más sutil que romper medio país y (ojalá que no, no creo que pase) contaminar radiactivamente una zona del mismo.
"[Dios nos está diciendo] que vivamos en la verdad"
Dale. Yo empiezo por no creer en un dios que (de verdad) no existe. Cuando me alcances seguimos hablando.
"[Dios nos está diciendo] que no usemos la tecnología, aunque sea de manera pacífica."
Me encantaría que Lilita dejara de usar tecnología porque significaría que no tendría que ver sus estupideces nunca más. Por otro lado, sino tuviéramos tecnología ni siquiera nos hubiéramos enterado de lo de Japón todavía. Supongo que tal vez quiso decir, o dijo "tecnología nuclear" y está sacado de contexto.
Por otro lado, número de muertos por tecnología nuclear desde 1950: 1000? 10000?
Número de muertos por terremotos y tsunami en los últimos 5 años: 100000? 200000?
Sí acabo de inventar esos números, pero creo que están mas bien que mal. Entonces básicamente, dios mató más gente esta semana para que no usemos energía nuclear de losque la energía nuclear mató en 50 años. No es exactamente una estrategia comunicacional efectiva.
"Hay que leer los signos de los tiempos"
Ok, leé el "sign of The Times":
No voten a esta pelotuda religiosa. Es peligrosa y probablemente tenga algún problema mental.
Cliente Twitter client (no soporta identi.ca en la primera versión)
Con estos features: http://pastebin.lugmen.org.ar/6464
Implementado antes del 4/4
Menos de 16384 bytes (de python). Puede ser mas grande por iconos y cosas así.
Veremos qué sale :-)
Por supuesto, podría hacer más, pero hasta yo tengo mis standards!
No usar ;
No usar if whatever: f()
Salvo eso, hice algunos trucos sucios, pero, en este momento, es un browser bastante completo en 127 líneas de código según sloccount, así que ya jugué suficiente y mañana tengo trabajo que hacer.
Pero antes, consideremos como se implementaron algunos features (voy a cortar las líneas para que la página quede razonablemente angosta), y veamos también las versiones "normales" de lo mismo. La versión "normal" no está probada, avisen si está rota ;-)
Esto noes algo que deba aprenderse. De hecho es casi un tratado en como no hacer las cosas. Es el código menos pitónico y menos claro que vas a ver esta semana.
Es corto, es expresivo, pero es feo feo.
Voy a comentar sobre esta versión.
Un browser no es gran cosa si no se puede usar con proxy. Por suerte el stack de red de Qt tiene buen soporte de proxy. El chiste es configurarlo.
De Vicenzo soporta proxies HTTP y SOCKS parseando la variable de entorno http_proxy
y seteando el proxy a nivel aplicación en Qt:
proxy_url = QtCore.QUrl(os.environ.get('http_proxy', '')) QtNetwork.QNetworkProxy.setApplicationProxy(QtNetwork.QNetworkProxy(\ QtNetwork.QNetworkProxy.HttpProxy if unicode(proxy_url.scheme()).startswith('http')\ else QtNetwork.QNetworkProxy.Socks5Proxy, proxy_url.host(),\ proxy_url.port(), proxy_url.userName(), proxy_url.password())) if\ 'http_proxy' in os.environ else None
Como es la versión normal de esa cosa?
if 'http_proxy' in os.environ: proxy_url = QtCore.QUrl(os.environ['http_proxy']) if unicode(proxy_url.scheme()).starstswith('http'): protocol = QtNetwork.QNetworkProxy.HttpProxy else: protocol = QtNetwork.QNetworkProxy.Socks5Proxy QtNetwork.QNetworkProxy.setApplicationProxy( QtNetwork.QNetworkProxy( protocol, proxy_url.host(), proxy_url.port(), proxy_url.userName(), proxy_url.password()))
Los abusos principales contra python son el uso del operador ternario para hacer un if de una línea (y anidarlo) y el largo de línea.
Este feature está en versiones recientes de PyQt: si pasás nombres de propiedades como argumentos con nombre, se les asigna el valor. Si pasás una señal como argumento con nombre, se conectan al valor.
Es un feature excelente, que te ayuda a crear código claro, local y conciso, y me encanta tenerlo. Pero si te querés ir a la banquina, es mandada a hacer.
Esto está por todos lados en De Vicenzo, éste es sólo un ejemplo (sí, es una sola línea):
QtWebKit.QWebView.__init__(self, loadProgress=lambda v:\ (self.pbar.show(), self.pbar.setValue(v)) if self.amCurrent() else\ None, loadFinished=self.pbar.hide, loadStarted=lambda:\ self.pbar.show() if self.amCurrent() else None, titleChanged=lambda\ t: container.tabs.setTabText(container.tabs.indexOf(self), t) or\ (container.setWindowTitle(t) if self.amCurrent() else None))
Por adonde empiezo...
Hay expresiones lambda
usadas para definir los callbacks en el lugar en vez de conectarse con una función o método "de verdad".
Hya lambdas con el operador ternario:
Hay lambdas que usan or
o una tupla para engañar al intérprete y que haga más de una cosa en un solo lambda!
No voy ni a intentar desenredar esto con fines educativos, pero digamos que esa línea contiene cosas que deberían ser 3 métodos separados, y debería estar repartida en 6 líneas o mas.
Llamarlo un manager es exagerar porque no se puede parar una descarga después que empieza, pero bueno, te deja bajar cosas y seguir browseando, y te da un reporte de progreso!
Primero, en la línea 16
creé un diccionario bars
para llevar registro de los downloads.
Después, tenía que delegar el contenido no soportado al método indicado, y eso se hace en las líneas 108 and 109
Básicamente, con eso cada vez que hacés click en algo que WebKit no puede manejar, se llama al método fetch
con el pedido de red como argumento.
def fetch(self, reply): destination = QtGui.QFileDialog.getSaveFileName(self, \ "Save File", os.path.expanduser(os.path.join('~',\ unicode(reply.url().path()).split('/')[-1]))) if destination: bar = QtGui.QProgressBar(format='%p% - ' + os.path.basename(unicode(destination))) self.statusBar().addPermanentWidget(bar) reply.downloadProgress.connect(self.progress) reply.finished.connect(self.finished) self.bars[unicode(reply.url().toString())] = [bar, reply,\ unicode(destination)]
No hay mucho golf acá salvo las líneas largas, pero una vez que metés enters es la manera obvia de hacerlo:
Pedí un nombre de archivo
Creás un progressbar, lo ponés en el statusbar, y lo conectas a las señales de progreso de la descarga.
Entonces, por supuesto, está el slot progress
que actualiza la barra:
progress = lambda self, received, total:\ self.bars[unicode(self.sender().url().toString())][0]\ .setValue(100. * received / total)
Sí, definí un método como lambda para ahorrar una línea. [facepalm]
Y elslot finished
para cuando termina el download:
def finished(self): reply = self.sender() url = unicode(reply.url().toString()) bar, _, fname = self.bars[url] redirURL = unicode(reply.attribute(QtNetwork.QNetworkRequest.\ RedirectionTargetAttribute).toString()) del self.bars[url] bar.deleteLater() if redirURL and redirURL != url: return self.fetch(redirURL, fname) with open(fname, 'wb') as f: f.write(str(reply.readAll()))
hasta soporta redirecciones correctamente! Más allá d eso, nada más esconde la barra, guarda los datos, fin del cuentito. La línea larga ni siquiera es mi culpa!
Hay un problema en que el archivo entero se mantiene en memoria hasta el fin de la descarga. Si te bajás un DVD, te va a doler.
Usar el with
ahorra una línea y no pierde un file handle, comparado con las alternativas.
De nuevo Qt me salva las papas, porque hacer esto a mano debe ser difícil. Sin embargo, resulta que el soporte de impresión... está hecho. Qt, especialmente usado vía PyQt es tan completo!
self.previewer = QtGui.QPrintPreviewDialog(\ paintRequested=self.print_) self.do_print = QtGui.QShortcut("Ctrl+p",\ self, activated=self.previewer.exec_)
No necesité nada de golf. Eso es exactamente el código que se necesita, y es la manera recomendada de enganchar "Ctrl+p" con la impresión de la página.
No hay otros trucos. Todo lo que queda es crear widgets, conectar unas cosas con otras, y disfrutar la increíble experience de programar PyQt, donde podés escribir un web browser entero (salvo el motor) en 127 líneas de código.