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. |
This is only the first part of a project to create the simplest (for me) software forum possible.
Here are the features I want:
Login using twitter / Facebook / Google / OpenID
Unlimited number of threads
Support for like / dislike both on threads and on posts
Avatars
HTML in comments
Mail the user on replies
RSS feeds for threads
You can see it in action at http://foro.netmanagers.com.ar (for a limited time only ;-)
And here is the code:
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)
It requires Bottle and the Disqus python API
Of course, there is also a bit of templating involved, here is main.tpl and the thread.tpl. Since I suck at HTML, it uses Bluetrip CSS and it's more than simple enough to customize.
OF COURSE I AM CHEATING!
This thing is just a simple veneer around Disqus! More like a blog with comments and without posts than a forum! But ... what's missing to make this a real forum? It works, doesn't it? You could even use Disqus categories to create subforums...
All things considered, I think it's a cute hack.
And if you wait a few days, this will lead to something much more magical!
Full source code at http://magicforum.googlecode.com
As everyone knows, there was a big quake in Japan, then a Tsunami, then a volcano erupted, then a nuclear plant caught fire. All things considered, a really crappy week.
Then again, if I were japanese and I had to read idiots telling me this was because god is pubishing me because of (whatever the idiot doesn't like about Japan), I would be sorely tempted to find the morons and ... ok, considering the japanese are showing they are very reasonable people, probably just tell him something politely.
OTOH, I am not japanese. Which means I can explain in great detail why those who say "maybe it's <whatever> punishing | telling japan <something>" are a complete waste of oxygen.
I will focus on one example, because it's a very special religious moron: a presidential candidate in Argentina, called Lilita Carrió.
Here's what she said (spanish is the original, of course):
"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"
"God is telling us that we should take care of the planet, that we should stop destroying the earth, that we should live in truth, in decency, in justice, and stop using technology, even if it's peacefully. We should read the sign of the times".
Let's consider that little by little.
"God is telling us that we should take care of the planet"
I must confess I am amazed that an almightly being is less capable of communicating ideas than my 3.9 year old kid. When he wants me to play ball, he brings the ball and tells me "Dad, let's play ball".
On the other hand, god apparently, to tell us to stop using technology, causes a series of catastrophic events in the other end of the world, then brings us the news over the Internet (a technological miracle), so that Lilita can divine god's intentions and then re-broadcast them to us over the radio (of course, an erarlier technological miracle).
Now, does that make sense to anyone? I mean, why doesn't god just, you know, say what he means in a reasonable manner? Because for religious people, the fun is in the divination. They are acting like roman priests divining the future in the entrails of an animal, except they are using the life and suffering of people.
Oh, look, suffering in Japan, that means we should stop using the Wii!
Not only is that approach completely against everything christian doctrine teaches, from with the virtue of charity (if god did it to tell us something, by definition they deserved it!) to the injunction against divining god's messages in portents (yes, it is forbidden, go ask a priest).
"[God is telling us] that we should stop destroying the earth"
Oh, gee, ok then! OTOH, maybe a more subtle way than half-breaking everything in a whole country to let us know next time? Please?
"[God is telling us] that we should live in truth"
Ok, yes, let's do that. I will start by not believing in god, who truly does not exist. When you catch up to that we'll argue some more, ok?
"[God is telling us to] stop using technology, even if it's peacefully."
I would love if this presidential candidate didn't use technology because it would mean I would not have to see her sanctimonious stupidity ever again. OTOH, if we wouldn't have technology, we would probably not know about the earthquake yet. I suppose she may have been saying "nuclear technology" and this is out of context.
OTOH, number of people killed by peaceful nuclear technology since 1950: 1000? 10000?
number of people killed by earthquakes and tsunami in the last 5 years: 100000? 200000?
Yes, those are numbers I just made up, but I am betting they are more right than wrong, so, basically, god has killed more people this week telling us not to use nuclear power, than nuclear power has killed in the last 50 years. Not exactly good communication skills.
"We should read the sign of the times"
Ok, here it is:
Don't vote for this blithering idiot. She's dangerous, and probably mentally ill.
In the spirit of the De Vicenzo web browser, I am starting a new program, called Pato Cabrera. Here are the rules:
Twitter client (no identi.ca in the first version, but to be added later)
Has these features: http://pastebin.lugmen.org.ar/6464
Has to be implemented before April 4th
Smaller than 16384 bytes (of python code) but may be larger because of artwork.
Let's see how it works :-)
I have already posted a couple of times (1, 2) about De Vicenzo , an attempt to implement the rest of the browser, starting with PyQt's WebKit... limiting myself to 128 lines of code.
Of course I could do more, but I have my standards!
No using ;
No if whatever: f()
Other than that, I did a lot of dirty tricks, but right now, it's a fairly complete browser, and it has 127 lines of code (according to sloccount) so that's enough playing and it's time to go back to real work.
But first, let's consider how some features were implemented (I'll wrap the lines so they page stays reasonably narrow), and also look at the "normal" versions of the same (the "normal" code is not tested, please tell me if it's broken ;-).
This is not something you should learn how to do. In fact, this is almost a treatise on how not to do things. This is some of the least pythonic, less clear code you will see this week.
It is short, and it is expressive. But it is ugly.
I'll discuss this version.
A browser is not much of a browser if you can't use it without a proxy, but luckily Qt's network stack has good proxy support. The trick was configuring it.
De Vicenzo supports HTTP and SOCKS proxies by parsing a http_proxy
environment variable and setting Qt's application-wide proxy:
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
How would that look in normal code?
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()))
As you can see, the main abuses against python here are the use of the ternary operator as a one-line if (and nesting it), and line length.
This is a feature of recent PyQt versions: if you pass property names as keyword arguments when you create an object, they are assigned the value. If you pass a signal as a keyword argument, they are connected to the given value.
This is a really great feature that helps you create clear, local code, and it's a great thing to have. But if you are writing evil code... well, you can go to hell on a handbasket using it.
This is all over the place in De Vicenzo, and here's one example (yes, this is one line):
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))
Oh, boy, where do I start with this one.
There are lambda
expressions used to define the callbacks in-place instead of just connecting to a real function or method.
There are lambdas that contain the ternary operator:
There are lambdas that use or
or a tuple to trick python into doing two things in a single lambda!
I won't even try to untangle this for educational purposes, but let's just say that line contains what should be replaced by 3 methods, and should be spread over 6 lines or more.
Ok, calling it a manager is overreaching, since you can't stop them once they start, but hey, it lets you download things and keep on browsing, and reports the progress!
First, on line 16 I created a bars
dictionary for general bookkeeping of the downloads.
Then, I needed to delegate the unsupported content to the right method, and that's done in lines 108 and 109
What that does is basically that whenever you click on something WebKit can't handle, the method fetch
will be called and passed the network request.
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 real code golfing here, except for long lines, but once you break them reasonably, this is pretty much the obvious way to do it:
Ask for a filename
Create a progressbar, put it in the statusbar, and connect it to the download's progress signals.
Then, of course, we need ths progress
slot, that updates the progressbar:
progress = lambda self, received, total:\ self.bars[unicode(self.sender().url().toString())][0]\ .setValue(100. * received / total)
Yes, I defined a method as a lambda to save 1 line. [facepalm]
And the finished
slot for when the download is done:
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()))
Notice that it even handles redirections sanely! Beyond that, it just hides the progress bar, saves the data, end of story. The longest line is not even my fault!
There is a big inefficiency in that the whole file is kept in memory until the end. If you download a DVD image, that's gonna sting.
Also, using with
saves a line and doesn't leak a file handle, compared to the alternatives.
Again Qt saved me, because doing this manually would have been a pain. However, it turns out that printing is just ... there? Qt, specially when used via PyQt is such an awesomely rich environment.
self.previewer = QtGui.QPrintPreviewDialog(\ paintRequested=self.print_) self.do_print = QtGui.QShortcut("Ctrl+p",\ self, activated=self.previewer.exec_)
There's not even any need to golf here, that's exactly as much code as you need to hook Ctrl+p to make a QWebView
print.
There are no other tricks. All that's left is creating widgets, connecting things to one another, and enjoying the awesome experience of programming PyQt, where you can write a whole web browser (except the engine) in 127 lines of code.