Lesetagebuch 5

Die neue Lesetagebuch-Version wurde letzte Nacht auf den Server geschubst.

Weise Programmierer sagen immer »Was auch immer du machst, mach auf keinen Fall einen kompletten Rewrite«, und darum bin ich Anfang des Jahres losgezogen um einen kompletten Rewrite zu machen. Wie ein echter Profi.

In den bisherigen Versionen hatte ich meine Datenbankanbindung selbst geschrieben. Das bewegte sich zwischen »Alle Objekte sind Dictionaries und enthalten nur, was die Datenbank enthält« und »Alle Objekte haben eigene Klassen, die auf verschiedene Weisen mit der Datenbank sprechen, bis auf die ›Später Lesen‹-Einträge, weil ich dafür zu faul bin«.

Natürlich war das kein dauerhaft haltbarer Zustand. Darum fing ich an, die Seite komplett neu zu schreiben, diesmal nicht nur auf Basis von Flask und Jinja2, sondern mit Hilfe von Peewee und WTForms. Peewee ist ein wunderbarer, leichter ORM, der subclassing sehr einfach macht, und WTForms geht mir manchmal auf die Nerven, erspart es mir aber, Formularvalidierung selber schreiben zu müssen.

»Und warum hat das dann so lange gedauert?«, fragt ihr euch jetzt vielleicht, »Du bekamst doch quasi alles geschenkt?«. Tja. John Lennon hat es ja schon gesagt: Das Leben ist, was passiert, während man versucht, einen Rewrite einer Bücherleseplattform zu schreiben.

Fix your life (and your URLs to static files in your Jinja templates)

Whenever you want to link to a static file in Jinja templates, you probably use url_for('static', filename=your_file). That’s great, but it may cause problems with your users, who may keep using a cached version of your kickass CSS file. But it doesn’t have to be that way. Cachebust your URLs for maximum convenience!

import app

app.create_jinja_environment()

def url_for_buster(endpoint, **values):
    if endpoint == 'static':
        filename = values.get('filename', None)
        if filename:
            file_path = os.path.join(app.root_path,
                                     endpoint, filename)
            values['q'] = int(os.stat(file_path).st_mtime)
    return url_for(endpoint, **values)

app.jinja_env.globals.update(url_for=url_for_buster)

Daniel, what are you doing?

Great question! I’m overwriting Jinja’s regular url_for function with a custom function that adds a parameter q to all URLs that link to the static endpoint, but that doesn’t change other URLs. The value of q is the timestamp of the linked file. That way, whenever you update the file, the parameter is changed and the browser thinks it’s a different file.

So, instead of linking /static/style.css, you’ll link to /static/style.css?q=1407762775. Nice.

R: [Irgendwas über Mozart und Wien]. Mozart wohnte doch mal in Wien?

D: Zumindest behauptet Falco das.

R: Wer ist Falco?

D: Das ist dieser Sänger? Der »Rock Me Amadeus« gesungen hat? Falco? Der Österreicher? Falco? Der King of Pop? Falco?

R: Der mit der Sonnenbrille und der roten Jacke?

D: DAS IST HEINO. WAS IST FALSCH MIT DIR.

Widowfix Filter for Jinja2

So, if you are anything like me (or Max), you hate it when the last word of a paragraph is on its own. Mostly, you don’t really have a choice. But with the following Flask snippet, you do!

It doesn’t really matter where you put it. In most cases, I’ll have a utils.py file which contains these kind of things, but because you directly register the filter with the app object, the location is not important.

from yourapp.app import app
from jinja2 import Markup

def widowfix(value):
    value = escape(value)
    splitted = value.rsplit(' ', 1)
    return Markup(' '.join(splitted))

app.jinja_env.filters['widowfix'] = widowfix

What exactly happens here?

In the first line, we import our app. This is simple.

Then, we import Markup. It takes a string as a parameter and returns a Markup object that will be treated as “safe” by Jinja. This is important, because we don’t want the   to be escaped.

We create the widowfix function. It takes a value, escapes it, splits it at the last space and puts it back together, with a   as the glue.

Finally, we register that function as a new Jinja filter with the name widowfix.

To use it in your templates, simply write

{{ my_text|widowfix }}

The last space in the string will be replaced by a non-breaking space, so the last two words will always be right next to each other. No more widows!

Update

I got a tumblr message reminding me that it may be unsafe to just not escape the whole variable. (S/he actually called it “a security nightmare”. I don’t know about that, but at least it’s dramatic.)

Yes, you probably want to escape the variable before adding the &nbsp, so I added value = escape(value) to the function. Thanks, anon!