Crivens!

holga.d

Dienstag, 27. Juli 2010, Themen:

Ein interessantes Konzept für eines vielleicht würdigen Lomo-Nachfolgers. Würde ich mir auch ein paar Euro kosten lassen…

der fall perlentaucher

Donnerstag, 15. Juli 2010, Themen:

Der Fall Perlentaucher gegen FAZ und SZ beschäftigt schon seit 2005 die deutschen Gerichte. Die Verhandlung vor dem Bundesgerichtshof findet heute ab 12 Uhr statt. CARTA hat eine sehr gute Zusammenfassung zu diesem Verfahren veröffentlicht. Jeder, der sich auch nur ansatzweise für die aktuellen Diskussionen zum Urheberrecht interresiert, wird den Ausgang des Verfahrens mit Spannung verfolgen. Ich bin vor allem auf die Urteilsbegründung gespannt und hoffe dass auch weiterhin nach Perlen getaucht werden darf.

wenn der schuss mal nicht nach hinten losgeht

Dienstag, 15. Juni 2010, Themen:

Unsere geehrte Kanzlerin hat sich gestern mit Herrn Sarkozy getroffen um eine bessere Zusammenarbeit in Wirtschaftspolitik innerhalb der Europäischen Union voran zu bringen. Unter anderem gab es dabei diesen Vorschlag:

Einig zeigten sich beide Seiten darin, dass Euro-Staaten, die häufiger gegen das Drei-Prozent-Defizitkriterium des Maastricht-Vertrags verstoßen, künftig vorübergehend das Stimmrecht entzogen werden soll. Quelle

Finde ich sehr viel sinnvoller, als die bisherige Regelung, dass einem überschuldeten Staat noch mehr Geld in Form von Strafzahlungen abgezweigt wird. Ob "Mutti" allerdings diese Grafik aus dem eigenen Hause kennt mag man fast in Zweifel ziehen.

Statistik der Verschuldung Deutschlands in % des Bruttoinlandsprodukts

Nur zu Erinnerung: Die Maastricht-Kriterien legen für den Schuldenstand ein Maximum von 60% des BIP fest.

toscawidget eigenheiten

Dienstag, 01. Juni 2010, Themen:

Bei meinem Turbogears-Projekt verwende ich zur Formularanzeige und -validierung die empfohlenen ToscaWidgets. Die Dokumentation dazu ist spärlich, aber wenn man verstanden hat, dass es sehr einfach ist eigene Templates zu verwenden, kommt man sehr gut mit ihnen zurecht.

Nur eines hat mir gestern und heute Kopfzerbrechen bereitet: die Validierung bei der Verwendung des FormFieldRepeater-Widgets. Dieses wird benötigt, wenn man wiederholende Formularbereiche – wie z.B. mehrere Email-Adressen pro Kontakt – verwenden möchte. Hier ein kleines Code-Beispiel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class EmailSubForm(twf.TableFieldSet):

    template = "…"
    hover_help = True

    class fields(WidgetsList):
        id       = twf.HiddenField(validator=twv.Int(not_empty=True))
        email    = twf.TextField(
                            label_text='Email-Address', 
                            validator=twv.Email(not_empty=True))
        remarks  = twf.TextField(
                            label_text='Remarks',
                            validator=twv.UnicodeString(not_empty=True))

class ContactForm(twf.TableForm):

    hover_help = True
    template = "…"
    submit_text = "Update Contact"

    class fields(WidgetsList):
        name   = twf.TextField(
                        label_text='Name',
                        validator=twv.UnicodeString(not_empty=True))
        []
        emails = twf.FormFieldRepeater(
                        widget=EmailSubForm(),
                        repetitions=0)

So weit, so gut. Das Formular ´ContactForm´ wird auf der Website angezeigt inklusive der vorhandenen Email-Adressen im "Unterformular" ´EmailSubForm´. Fehler bei der Eingabe im Hauptformular werden auch angezeigt – z.B. keine Eingabe im Feld ´name´. Jedoch wird auf Eingabefehler im Unterformular nicht geprüft!

Ich habe Herrn Google gefragt, viel gelesen, nichts gefunden was mir weitergeholfen hätte. Die Lösung kam durch ausprobieren. Setzt man den Wert für repetitions (letzte Zeile) auf 1 funktioniert alles. Warum? keine Ahnung.

Die korrigierte Fassung des ContactForms sieht so aus:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class ContactForm(twf.TableForm):

    hover_help = True
    template = "…"
    submit_text = "Update Contact"

    class fields(WidgetsList):
        name   = twf.TextField(
                        label_text='Name',
                        validator=twv.UnicodeString(not_empty=True))
        []
        emails = twf.FormFieldRepeater(
                        widget=EmailSubForm(),
                        repetitions=0)

Ich hoffe ich vergesse es nicht und es hilft jemandem weiter :-)

zum rücktritt von horst koehler

Dienstag, 01. Juni 2010, Themen:

Der gestrige Rücktritt unseres Bundespräsidenten hat auch mich sehr überrascht – vor allem jedoch die Begründung, die er dafür anführt. Natürlich wird man nicht geliebt wenn man unpopuläre Warheiten zum Besten gibt, aber gerade als Amtsinhaber muss man meiner Meinung nach Kritik aushalten können.

Die erste Frage, die ich mir jedoch stelle ist, ob dies der einzige Grund für diese Entscheidung ist. Eine Antwort darauf ist in naher Zukunft wohl nicht zu erwarten.

Wahrscheinlich spinne ich mir etwas zusammen, aber nach dem überraschenden Rücktritt von Roland Koch vor rund einer Woche bleibt ein mulmiges Gefühl. Bin gespannt, wer als Nächster an der Reihe ist…

trauriges aus der anstalt

Dienstag, 01. Juni 2010, Themen:

Schockschwerenot! Georg Schramm verlässt die Anstalt. Finde ich sehr schade. Ich hoffe sehr, dass die Anstalt nach der Sommerpause wieder öffnen wird. Hier noch das Inteview der Süddeutschen mit Urban Priol.

Zudem möchte ich den Aufruf des Schockwellenreiters unterstützen.

repoze.what.predicates in einem template überprüfen

Montag, 17. Mai 2010, Themen:

Ich stand vor dem Problem, wie ich bei Turbogears einen Test auf ein repoze.what.predicate in einem Template durchführen kann. Ok, klingt ein bisschen blöde :-). Eigentlich geht es nur darum, dass ich z.B. für die Admininstratoren einen Link anzeigen möchte, für andere Benutzer nicht.

Für die Überprüfung innerhalb eines Controllers bietet Turbogears eine solche Beschränkung durch den @require-Decorator an. Aber wie funktioniert es in einem Template? Google sei Dank bin ich auf einige Diskussionen rund um das Thema gestoßen und schließlich bei diesem Eintrag gelandet, welcher mich auf das Changeset 6347 verwiesen hat.

Turbogears stellt über tg.predicates die entsprechenden tests zur Verfügung. Und hier ein kleines Beispiel dazu:

1
<p>${"OK" if tg.predicates.has_permission("ein_recht") else "BLOCKED"}</p>

i shot the serif

Freitag, 14. Mai 2010, Themen:

I Shot the Serif

Gefunden bei So much Pun via FAIL Blog.

turbogears, nosetests und sqlalchemy.migrate

Mittwoch, 05. Mai 2010, Themen:

Ich habe heute mir halb den Kopf zerbrochen, wie ich bei TurboGears Tests anhand von Daten in einer Datenbank durchführen kann.

Das erste Problem ist eigentlich keines, aber es hat doch lange gedauert, bis ich darauf kam: Wie lade ich Test-Daten in eine separate Test-Datenbank?

Überall stand einfach nur, dass man zum Start der Anwendung benötigte Daten in websetup.py ablegen sollte. Mein Denkfehler war einfach nur, dass nosetests die SQL-Einstellungen von test.ini verwendet und nicht von developement.ini. Logisch! Aber hat bei mir etwas gedauert…

So, musste also einfach in der test.ini den Wert für sqlalchemy.url anpassen. Voreingestellt ist eine Memory-Datenbank über SQLite. Da ich jedoch Gebrauch von (MySQL)-spezifischen Enum-Feldern mache, musste ich diese ändern.

Das zweite Problem war die Integration von Sqlalchemy.migrate, welches ich zur Entwicklung der Datenbank verwende. Zuersteinmal habe ich die Basisklasse für den Test von Modellen rausgeworfen. Bei jedem Test hätte sich sonst das Datenbankschema komplett neu aufgebaut, Daten importiert und nach dem Test wieder die Datenbank zerstört. Und nein, einfach nur alle Modelle zu löschen geht bei mir nicht so einfach, da es einige zirkuläre Fremdschlüsselbeziehungen gibt.

Da nosetest verwendet wird, gibt es die Möglichkeit, in einem test-package für alle Tests verwendete setup und teardown Funktionen bereitzustellen.

Die Setup-Funktion bringt zuerst die Datenbank auf die aktuelle Version und lädt dann über websetup.py die Testdaten. Die Teardown-Funktion löscht wieder alle Tabellen und somit alle Daten. Das schöne daran ist, dass dies nur einmal pro Testreihe erfolgt.

Und falls ich doch zwischen den Tests mal wieder alles neu brauche, reicht ein einfacher Aufruf der restart() Funktion.

Als drittes sollte nun noch in TestController der Aufruf von websetup.py unterbunden werden. Haben wir ja schon :-)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
"""custom unit and functional test suite """

import os
from tg import config
from paste.deploy import loadapp
from paste.script.appinstall import SetupCommand
from routes import url_for
import migrate.versioning.api

from your_tg_app.model import DBSession

conf_dir = config.here
sqlalchemy_url = config['app_conf']['sqlalchemy.url']
repository = os.path.join(conf_dir, "migration")

def setup_package():
    """ loads the database fixture in websetup.py """
    # get the directory of the config file
    # setup the database
    migrate.versioning.api.upgrade(sqlalchemy_url, repository)
    # setup the data
    test_file = os.path.join(conf_dir, 'test.ini')
    cmd = SetupCommand('setup-app')
    cmd.run([test_file])

def teardown_package():
    """ removes all data in the database by downgrading via migrate """
    migrate.versioning.api.downgrade(sqlalchemy_url, repository, 0)

def restart():
    teardown_package()
    setup_package()

class TestController(object):
    """
    Base functional test case for the controllers.

    The delphi2 application instance (``self.app``) set up in this test 
    case (and descendants) has authentication disabled, so that developers can
    test the protected areas independently of the :mod:`repoze.who` plugins
    used initially. This way, authentication can be tested once and separately.

    Check delphi2.tests.functional.test_authentication for the repoze.who
    integration tests.

    This is the officially supported way to test protected areas with
    repoze.who-testutil (http://code.gustavonarea.net/repoze.who-testutil/).

    """

    application_under_test = 'main_without_authn'

    def setUp(self):
        """Method called by nose before running each test"""
        # Loading the application:
        conf_dir = config.here
        wsgiapp = loadapp('config:test.ini#%s' % self.application_under_test,
                          relative_to=conf_dir)
        self.app = TestApp(wsgiapp)
        # database setup done with setup_package()

    def tearDown(self):
        """Method called by nose after running each test"""
        # Cleaning up the database:
        DBSession.rollback()

eigene repoze.who-plugins in turbogears verwenden

Montag, 03. Mai 2010, Themen:

Momentan bin ich dabe eine Datenbank für ein Orchester zu programmieren. Ok, genauer gesagt eine schon in die Jahre gekommene Datenbank neu umzusetzen; und hierzu verwende ich Turbogears

Der Turbogears-Quickstart enthält schon eine vorgefertigte Benutzer- und Rechteverwaltung. Leider stimmt diese nicht mit der Struktur der Datenbank überein, wie ich sie gerne hätte. Da mit vielen Freiwilligen gearbeitet wird, die sich dazu noch von Projekt zu Projekt unterscheiden können, war die Idee die bereits in der Datenbank registrieten Personen einfach zu Benutzern machen zu können. Da sich später vielleicht auch Mitspieler anmelden sollen um z.B. die eigenen Telefonnummern zu ändern oder eine Zusage für ein Projekt zu geben wollte ich die vorhanden Email-Adressen als Benutzernamen verwenden.

Um einen Benutzer anhand seiner Email-Adresse anmelden zu können – ohne dass diese Adresse doppelt in der Datenbank abgelegt wird – muss ich eine Abfrage über mehrere Tabellen machen: users->persons->emails (in Wirklichkeit noch ein wenig komplizierter, aber lassen wir es dabei :-)

Der erste Versuch war die repoze.who und repoze.what Konfiguration unangetastet zu lassen und mit SQLAlchemy-Comparators zu arbeiten – ich bin dabei kläglich gescheitert. Und spätestens wenn die oben genannte Funktion für die Musiker hinzukommen sollte, werden eigene Plugins für repoze.who und / oder repoze.what benötigt – also fange ich doch gleich damit an…

Leider ist die Dokumentation dazu auf der Turbogears-Seite etwas spärlich und verstreut. Teilweise mit Links auf das repoze-Projekt, das wiederum auf Turbogears verweist. Auch die such im allwissenden Netz hat keine einfaches Tutorial hervorgebracht. Daher will ich hier beschreiben werden welche Schritte nötig sind, um die Integration eigener Plugins in Turbogears 2 zu ermöglichen.

Die Suche

Nach dem ich mich durch das Setup von Turbogears gewühlt habe und ich endlich mit den richtigen Begriffen die Google-Treffer so weit einschränken konnte, dass sie mir etwas sinnvolles liefern bin ich über diese Seite gestolpert: http://blog.axant.it/archives/tag/turbogears. Darin wird zwar beschrieben, wie man eine Authentifizierung über mongodb hinbekommt, war aber trotzdem sehr hilfreich.

Die wichtigsten Erkenntnisse kurz zusammengefasst:

Daher muss man "einfach" nur die add_auth_middleware-Methode überschreiben, um eigene Plugins zu verwenden.

tg.configuration.AppConf ableiten

Zuerst muss eine eigene Klasse für die AppConf geschaffen werden, die sich aus dieser ableitet. Die einzige Methode die überschrieben werden muss ist add_auth_middleware(). Hierbei wurde nur der Aufruf app = setup_derived_auth(app, skip_authentication=skip_authentication, […] geändert um eine eigen Funktion aufzurufen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import logging
from copy import copy
from tg.configuration import AppConfig

class DerivedAppConfigAuth(AppConfig):
    """
    Own App-Config class to implement custom repoze.(who|what) plugins

    The class tg.configuration.AppConfig is derived to implement custom plugins
    for authentification and authorization.
    """

    def add_auth_middleware(self, app, skip_authentication):
        """
        Configure authentication and authorization.

        :param app: The TG2 application.
        :param skip_authentication: Should authentication be skipped if
            explicitly requested? (used by repoze.who-testutil)
        :type skip_authentication: bool

        """
        from repoze.what.plugins.quickstart import setup_sql_auth
        from repoze.what.plugins.pylonshq import booleanize_predicates

        # Predicates booleanized:
        booleanize_predicates()

        # Configuring auth logging:
        if 'log_stream' not in self.sa_auth:
            self.sa_auth['log_stream'] = logging.getLogger('auth')

        # Removing keywords not used by repoze.who:
        auth_args = copy(self.sa_auth)
        pprint(auth_args)
        if 'password_encryption_method' in auth_args:
            del auth_args['password_encryption_method']
        if not skip_authentication:
            if not 'cookie_secret' in auth_args.keys():
                msg = "base_config.sa_auth.cookie_secret is required " \
                "you must define it in app_cfg.py or set " \
                "sa_auth.cookie_secret in development.ini"
                print msg
                raise ConfigurationError(message=msg)
        app = setup_derived_auth(app, skip_authentication=skip_authentication,
                             **auth_args)
        return app

repoze.what.quickstart.setup_sql_auth()

Als nächstes muss man eine Funktion erstellen, welche die gleichen Aufgaben übernimmt wie repoze.what.quickstart.setup_sql_auth(). Hierzu habe ich einfach die Funktion kopiert, umbenannt und in die gleiche Datei eingefügt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from repoze.what.plugins.quickstart import find_plugin_translations
from repoze.what.plugins.sql import configure_sql_adapters
from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin
from repoze.who.plugins.friendlyform import FriendlyFormPlugin
from repoze.what.middleware import setup_auth

def setup_derived_auth(app, user_class, group_class, permission_class,
                   dbsession, form_plugin=None, form_identifies=True,
                   cookie_secret='secret', cookie_name='authtkt',
                   login_url='/login', login_handler='/login_handler',
                   post_login_url=None, logout_handler='/logout_handler',
                   post_logout_url=None, login_counter_name=None,
                   translations={}, **who_args):
    """
    Configure :mod:`repoze.who` and :mod:`repoze.what` with SQL-only 
    authentication and authorization, respectively.
    […]
    """
    plugin_translations = find_plugin_translations(translations)

    if group_class is None or permission_class is None:
        group_adapters = permission_adapters = None
    else:
        source_adapters = configure_sql_adapters(
            user_class,
            group_class,
            permission_class,
            dbsession,
            plugin_translations['group_adapter'],
            plugin_translations['permission_adapter'])
        group_adapters = {'sql_auth': source_adapters['group']}
        permission_adapters = {'sql_auth': source_adapters['permission']}

    # Setting the repoze.who authenticators:
    # using custom class here, removed translations for this class
    if 'authenticators' not in who_args:
        who_args['authenticators'] = []
    orchestra_auth = OrchestraDbTeamUserAuthenticatorPlugin(user_class, dbsession)
    who_args['authenticators'].append(('orchestra_auth', Orchestraauth))

    cookie = AuthTktCookiePlugin(cookie_secret, cookie_name)

    # Setting the repoze.who identifiers
    if 'identifiers' not in who_args:
        who_args['identifiers'] = []
    who_args['identifiers'].append(('cookie', cookie))

    if form_plugin is None:
        form = FriendlyFormPlugin(
            login_url,
            login_handler,
            post_login_url,
            logout_handler,
            post_logout_url,
            login_counter_name=login_counter_name,
            rememberer_name='cookie')
    else:
        form = form_plugin

    if form_identifies:
        who_args['identifiers'].insert(0, ('main_identifier', form))

    # Setting the repoze.who challengers:
    if 'challengers' not in who_args:
        who_args['challengers'] = []
    who_args['challengers'].append(('form', form))

    # Setting up the repoze.who mdproviders:
    # using custom class here, removed translations for this class
    orchestra_user_md = OrchestraDbTeamUserMDPlugin(user_class, dbsession)
    if 'mdproviders' not in who_args:
        who_args['mdproviders'] = []
    who_args['mdproviders'].append(('orchestra_user_md', orchestra_user_md))

    middleware = setup_auth(app, group_adapters, permission_adapters, 
                            **who_args)
    return middleware

Die wichtigsten Änderungen betreffen die Bereiche für den repoze.who.Authenticator und repoze.who.metadataprovider. Die neuen repoze.who-Plugins OrchestraDbTeamUserAuthenticatorPlugin und OrchestraDbTeamUserMDPlugin müssen natürlich noch erstellt werden.

eigene repoze.who-Plugins

Bei den eigenen Plugins folge ich ganz der Linie von repoze.who.plugins.sa

Basisklasse

Analog zu repoze.who.plugins.sa wird zuerst eine Basisklasse definiert, die vor allem eine Methode bietet den Benutzer anhand der Email-Adresse abzufragen. Hierzu verwende ich eine Klassenmethode in meinem Model, da ich diese öfters brauche, z.B. für eine "Passwort vergessen" Funktion. Ich habe dabei darauf geachtet, dass die Abfrage im User-Model die SQLAlchemy-Funktion .one() benutzt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound

class _BaseRepozeWhoOrchestraDdPlugin(object):
    """ Baseclass for the custom authenticator and metadata provider """

    def __init__(self, user_class, dbsession):
        """
        Setup the plugin.

        :param user_class: The SQLAlchemy/Elixir class for the users.
        :param session: The SQLAlchemy/Elixir session.

        """
        self.user_class = user_class
        self.dbsession = dbsession

    def get_user(self, email):
        try:
            return self.user_class.by_email_address(email)
        except (NoResultFound, MultipleResultsFound):
            # As recommended in the docs for repoze.who, it's important to
            # verify that there's only _one_ matching userid.
            return None

Authenticator Plugin

Als nächstes kommt das Plugin zur Authentifizierung. Die einzig benötigte Methode hierbei ist authenticate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from zope.interface import implements
from repoze.who.interfaces import IAuthenticator

class OrchestraDbTeamUserAuthenticatorPlugin(_BaseRepozeWhoOrchestraDdPlugin):
    """
    :mod:`repoze.who` custom authenticator for OrchestraDb
    """

    implements(IAuthenticator)

    # IAuthenticator
    def authenticate(self, environ, identity):
        if not ('login' in identity and 'password' in identity):
            return None

        user = self.get_user(identity['login'])

        if user:
            if user.validate_password(identity['password']):
                return identity['login']

Metadata-Provider-Plugin

Nun noch das Plugin für den Metadata-Provider:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from repoze.who.interfaces import IMetadataProvider
class OrchestraDbTeamUserMDPlugin(_BaseRepozeWhoOrchestraDdPlugin):
    """
    :mod:`repoze.who` custom metadata provider that loads the SQLAlchemy-powered
    object for the current user in the OrchestraDb.

    It loads the object into ``identity['user']``.

    """

    implements(IMetadataProvider)

    def add_metadata(self, environ, identity):
        identity['user'] = self.get_user(identity['repoze.who.userid'])

app_cfg.py anpassen

Im letzten Schritt muss man natürlich noch dafür sorgen, dass die abgeleitete AppConfig auch verwendet wird:

1
2
3
# from tg.configuration import AppConfig
from orchestra.config.derived_app_config_auth import DerivedAppConfigAuth
base_config = DerivedAppConfigAuth()

So, ich hoffe das hilft jemandem weiter, der vor dem gleichen Problem stand. Anbei noch ein Zip der finalen Datei.

surfstick image modus aktivieren und deaktivieren

Freitag, 30. April 2010, Themen:

Da ich es bestimmt früher oder später wieder einmal brauche: Um bei meinem Surfstick das darauf befindliche Image zu aktivieren oder deaktivieren muss mann wiefolgt vorgehen:

Systemsteuerung -> Netzwerk -> Modem -> Nullmodem einstellen

Bei den PPP-Optionen muss man "PPP-Echopakete versenden" und "Über eine Terminalfentser verbinden" aktivieren. Nun nur noch auf verbinden klicken.

Im Terminalfenster zuerst echo eingeben, damit eine Ausgabe erfolgen kann. Nicht erschrecken, man sieht noch nichts. Über den Befehl ATI erhält man Firmewareversion und andere Angaben

CD Funktion deaktivieren      at^u2diag=0
CD Funktion aktivieren        at^u2diag=1

Quelle: http://mobile-surfstick.de/

sqlalchemy-migrate tutorial

Dienstag, 27. April 2010, Themen:

Ich liebe SQLAlchemy um über Python auf Datenbanken zuzugreifen. Für ein neues Projekt wollte ich so weit wie möglich auch das Datenbank-Schema mit etwas mehr Disziplin angehen. Hierfür bietet sich natürlich sqlalchemy-migrate an. Leider fand ich die Dokumentation an einigen Stellen etwas schwach. Es wird gezeigt, wie man Scripte zur Migration erstellt und es werden auch Fehlerquellen beleuchtet, aber es fehlt eine einfache Anleitung in der z.B. aufgezeigt wird wie man am Besten Spalten ändert, hinzufügt oder löscht. Google sei Dank bin ich dann auf dieses Tutorial gestoßen. Ist schon etwas älter, es war für mich das fehlende Steinchen.

turbogears 2 und snow leopard

Mittwoch, 21. April 2010, Themen:

Folgt man beim Installieren von Turbogears 2 der Anleitung erhält man unter Mac OS X 10.6 (Snow Leopard) eine Fehlermeldung bei dem Modul Extremes. Der Workaround ist eigentlich sehr einfach: man installiert zuerst die neueste Version von Extremes bevor man Turbogears installiert.

1
2
3
4
easy_install Extremes
[]
easy_install -i http://www.turbogears.org/2.0/downloads/current/index tg.devtools
[]

static blog engine - vii- workbench.py

Dienstag, 13. April 2010, Themen:

Das letzte "große" Modul der static blog engine ist workbench.py. Die darin enthaltene Klasse Workbench könnte man als die Ablaufsteuerung der sbe bezeichen. Sie ruft nach bedarf die einzelnen vorgestellten Module bzw. Klassen auf und ist für die Ausgabe der statischen HTML-Seiten sowie der Atom-XML-Datei zuständig.

Die wichtigsten Methoden sind dabei:

In jeder dieser Methoden wird zuerst ein Inventar erstellt, die betroffenen Artikel gesucht und zwischengespeichert. Durch den Aufruf von deploy() wird zunächst ein RenderingSet erstellt und alle darin enthaltenden Elemente als HTML ausgegeben. Zur Ausgabe als HTML zählt natürlich auch das Kopieren verknüpfter Dateien.

Bei Aufruf der Methode update wird im zusätzlich noch die Index-Datei ausgegeben, der Atom-Feed neu aufgebaut und die Tag-Seiten aktualisiert. Daher sind die Methoden rebuild_lastest_year und rebuild_year dazu gedacht Änderungen an einem Template anzuwenden.

Für die Tag-Seiten gibt es noch besondere Methoden, um diese bei Fehlern oder ähnlichem zu aktualisieren: rebuild_tags_of_latest_year(), rebuild_tags_of_year(year_as_string) und rebuild_all_tags() die eigentlich selbsterklärend sein sollten.

Als Todo steht eigentlich noch an, dass ich die Tag-Verwaltung in ein eigenes Modul auslagern wollte, ich mich aber noch nicht dazu aufraffen konnte. Mahl sehen.

gamers hell

Mittwoch, 07. April 2010, Themen:

Bei xkdc gibt es den Albtraum eines jeden Gamers zu sehen.

Gamers Hell

Machte mir große Freude.

kommentare!

Montag, 29. März 2010, Themen:

So, habe es heute endlich geschafft die Kommentarfunktion einzubauen. War gar nicht so schwer - hatte ich auch nicht erwartet ;-). Danke geht natürlich an disqus.

static blog engine – vi – inbox.py

Montag, 29. März 2010, Themen:

Wie letzte Woche erwähnt, werde ich jetzt kurz etwas zu inbox.py schreiben. Wie schon am Namen zu vermuten ist, handelt es sich hierbei um ein Modul, dass neue Artikel bearbeitet. Hierzu stehen folgende Methoden der Inbox-Klasse bereit:

inbox.py und inventory.py spielen gut zusammen, sofern man die Reihenfolge beachtet. Zuerst sollten die Einträge im Eingangskorb bearbeitet werden und erst danach die Bestandsaufnahme erfolgen. Da eine Suche nach geänderten Dateien durch inventory.py das Änderungs- und nicht das Erstellungsdatum beachtet werden dadurch neue Artikel automatisch gefunden, ohne das eine Datenübergabe zwischen den beiden Modulen erfolgen muss. Des weiteren entfällt durch diese Reihenfolge das Einsortieren neuer Artikel (bzw. deren Elternknoten) in die gefundenen Knoten.

mercurial tutorial

Samstag, 27. März 2010, Themen:

Joel Spolsky hat eine nette Einführung zu Mercurial – einem "distributed source control management" geschrieben. Eine wirklich gute praktischeEinführung in das Thema. Ich habe mich vor einiger Zeit für die Konkurenz Bazaar entschieden – einfach weil es damals das beste Tutorial hatte :-). Als drittes System im Bunde sollte natürlich auch git erwähnt werden. Ihr sollt schließlich die Wahl haben.

herr rösler, medikamente und die marktwirtschaft

Freitag, 26. März 2010, Themen:

Ich wundere mich etwas über den von unserem Gesundheitsminister Rösler (FDP) Vorschlag, dass die Medikamentenpreise nicht mehr alleine von der Pharma-Unternehmen festgelegt werden sollen. Nicht, dass ich eine Beschränkung der Medikamentenkosten zu Lasten der Industrie ablehnen würde, im Gegenteil. Gerade im internationalen Vergleich haben wir recht hohe Preise für Medikamente, nicht nur für neue sondern auch für "alteingesessene".

Wenn mich mein Gedächtnis nicht täuscht – kann ja passieren, werde auch immer älter – war doch gerade die FDP die Partei, die sich für eine freie, uneingeschränkte Marktwirtschaft eingesetzt hat. Jahrelang galt für sie die staatliche Einflussnahme auf die Märkte als unerwünscht – sofern damit keine "systemrelevanten" Betriebe gerettet wurden. Alles frei nach dem Motto "Der Markt wirds schon richten".

Was mich daher verwundert ist, dass sich gerade der "Shootingstar" der FDP für einen staatlichen Eingriff in einen bisher unregulierten Markt eintritt. Man könnte nun behaupten, dies sei eigentlich kein staatlicher Eingriff, da die Preise zwischen den Unternehmen und den Krankenkassen ausgehandelt werden sollen. Da dies jedoch nicht freiwillig geschieht – sonst hätten es die Pharmabranche längst selbst zur Rettung des Gesundheitssystem vorgeschlagen – sondern von der Politik vorgegeben wird lasse ich dieses Argument nicht gelten.

Auch der Hinweis die Pharmaunternehmen haben ein Monopol auf neue Medikamente und daher muss ein Eingriff erfolgen halte ich für etwas "anrüchig". Natürlich hat ein Unternehmen ein Monopol auf neu eingeführte Produkte – und dieses ist auch staatlich garantiert. Das ist für Medikamente ebenso vorhanden wie z.B. für die "Goldbären".

Was die Branche mit neuen Medikamenten jedoch (meistens) nicht hat ist ein Therapiemonopol. Daher finde ich vor allem ein Aspekt der Initiative von Herrn Rösler sehr gut: Die Pharmaunternehmen sollen während der einjährigen Verhandlungsphase einen Beweis für die (verbesserte) Wirksamkeit des Medikamentes vorlegen. Aber halt, hat sich nicht aufch die FDP gegen die Einführungeiner Positivliste ausgeprochen, die somit durch die Hintertür teilweise eingeführt wird?

Das größte Manko des Vorschlages ist jedoch, dass Verhandlungen nur über neue Medikamente geführt werden sollen. In wie weit sich die Krankenkassen gegenüber der Industrie durchsetzen kann und wie schlagkräftig das Schiedsgericht sein wird sich erst dann herausstellen, wenn die Initiative Wirklichkeit werden sollte.

static blog engine – v – article.py

Donnerstag, 25. März 2010, Themen:

Eigentlich wollte ich nach dem Modul inventory.py mit dem Modul inbox.py weitermachen, da diese beiden zusammen gehören. Allerdings werde ich einen Ausflug zur Artikelklasse einschieben, da einige Funktionen bei inbox.py eine Rolle spielen.

Wie der Name schon vermuten lässt, handelt es sich bei der Klasse Article um einen Eintrag im Blog. Anders jedoch als bei ArticleNodes handelt es sich nicht um die logische Beziehung zu Elternelementen oder nachvolgenden Artikel sondern um den Inhalt und entsprechende Meta-Angaben.

Ein Objekt der Klasse liest eine Datei (oder ein Datei-ähnliches Objekt) ein, stellt sicher dass alle Metaangaben stimmig sind, kann den eigentlichen Inhalt des Eintrages durch einen Parser schicken um eine HTML-Ausgabe zu erhalten und nach lokalen Verknüpfungen suchen. Aber der Reihe nach:

Aufbau der eines Artikels

Der Aufbau eines Eintrages sieht wie folgt aus: Zuerst kommen Zeilen bei denen die Metaangaben stehen. Jeweils ein Schlüsselwort gefolgt durch einen Doppelpunkt und danach der jeweilige Wert der dem Schlüssel zugeordnet ist. Danach erfolgt eine Leerzeile und darauf der eigentliche Blog-Eintrag.

1
2
3
4
Title:  static blog engine – v – article.py
Tags:   sbe, python

Eigentlich wollte ich nach dem Modul […]

Metaangaben

Die folgenden Metaangaben werden momentan verstanden. Groß-/Kleinschreibung ist nicht relevant und fehlende Angaben werden anhand von Einstellungen oder Dateieigenschaften gesetzt.

Schlüssel Beschreibung
Title der Titel des Eintrages
Author der Autor
Created Zeitpunkt, wann der Eintrag erstellt wurde, Format: 2010-03-25T11:49:46
Tags Tags, durch ein Komma getrennt
Comments On oder Off, schaltet Kommentare an oder aus, noch nicht verwendet
Trackback On oder Off, schaltet Trackbacks an oder aus, noch nicht verwendet
TagURI eindeutige Identifizierung des Eintrages, wird automatisch ermittelt

Ist kein Titel für den Eintrag vorhanden wird innerhalb des Textes nach einer Überschrift gesucht und die erste die gefunden wird verwendet. Wurde keine ermittelt wird ein Standardwert aus der Konfiguration benutzte. Für den Schlüssel Created wird das Erstellungsdatum abgefragt, sofern es sich um ein Dateiobjekt handelt.

Parser

Momentan werden nur zwei Parser unterstützt: Markdown und einen Pseudoparser für HTML – dabei wird einfach der "Text" der Datei unverändert zurückgegeben. Die Unterstützung für andere Parser ist einfach zu bewerkstelligen, sie müssen einfach nur (X)HTML zurückgeben können. Den erstellten HTML-Code wird zwischengespeichert, da es sich meistens um eine relativ aufwendige Prozedur handelt.

Lokale Links

Zuerst muss ich klären was ich als "Lokale Links" verstehe. Falls ein Bild mit einem Eintrag verknüpft wird sollte dies auch einfach in die Inbox gelegt werden und mit dem Artikel zusammen an den richtigen Ort verschoben werden. Allerdings gibt es auch Verknüpfungen – z.B. zu einem immer wiederkehrenden Element – die absolut sind (http://example.com/a_picture.jpg) oder sich auf ein anderes Verzeichnis verweisen (../previous_day/a_picture.jpg). Nach diesen sollte nicht gesucht werden. Ich habe für diese den Namen "Lokale Links" gewählt und sie haben ein recht einfaches Unterscheidundsmerkmal: sie enthalten keinen Slash /. Eine Art von Links – die in ein Unterverzeichnis (photos/a_picture.jpg) fällt dabei unter den Tisch obwohl diese durchaus kopierenswert wären. Da dies meiner Ansicht nach nicht allzuoft vorkommt, kann man ein Unterverzeichnis auch mal von Hand kopieren oder verschieben. Ein Artikelobjekt stellt eine Methode zur Verfügung, welche die lokalen Links extrahiert und zurückgibt.

static blog engine – episode iv – inventory.py

Donnerstag, 25. März 2010, Themen:

Die Scanner-Klasse im Modul inventory.py ist eine der wichtigsten Grundlagen für die "static blog engine". Zuerst sucht sie alle bereits vorhandenen Artikel und erstellt danach die Jahr/Monat/Tag-Struktur anhand der Artikel. Warum habe ich diese Weg gewählt und nicht gleich beim Scannen auch Jahr, Monat und Tag miterstellen lassen? Ich stand vor dem Problem, dass falls der letzte Artikel eines Tages (Monates / Jahres) gelöscht würde, ich die komplette Struktur nochmals durchsuchen müsste. Da übergeordnete Objekte nur von vorhandenen Artikeln abgeleitet werden entfällt dieses.

Des weiteren bietet die Scanner-Klasse die Möglichkeit alle Artikel eines Jahres oder alle geänderten Artikel seit einem Zeitpunk (Unixtimestamp) als ein Python set zurückzugeben.

Das Moul enthält noch eine weitere Klasse RenderingSet. Übergibt man dieser ein Python-set mit Artikeln ermittelt diese alle Knoten (siehe nodes.py) die ausgegeben werden müssen. Dies sind nicht nur die Artikel und übergeordnete Objekte selbst, sondern auch die vorherigen und nachfolgenden Artikel – es könnte sich um einen neuen Artikel handeln oder der Titel könnte geändert sein. Desweiteren müssen auch vorheriger und nachfolgender Tag / Monat / Jahr neu ausgegben werden, da es sich um einen neuen Tag / Monat / Jahr handeln könnte.

Das Ausgeben von vorherigen bzw. nächsten Objekten müsste nicht sein, wenn man überprüfen würde, ob der Tag / Monat / Jahr schon existiert. Allerdings wäre dann eine Art Index der bereits vorhandenen Objekten nötig, gegen die man einen Vergleich anstellt. So finde ich es viel einfacher.

static blog engine – teil 3 – nodes.py

Dienstag, 23. März 2010, Themen:

Mit die erste und schwierigste Frage war, wie ich einzelne Jahre, Monate, Tage und Artikel repräsentieren sollte. Auf der einen Seite wollte ich den jeweils vorhergehenden und nachfolgenden Artikel (oder Jahr oder Monat oder Tag) einfach abfragen können, auf der anderen Seite gibt es natürlich eine Art Baumstruktur. Jeder Artikel ist einem Tag zugeordnet, dieser wiederum einem Monat und dieser natürlich einem Jahr.

Die erste Anforderung würde für eine Linked List sprechen, die zweite für eine Baum-ähnliche Struktur. Da beide Lösungen nicht wirklich optimal für meine Zwecke sind, habe ich mich für eine einfache Zwitterlösung entschieden – zudem hatte ich Lust zum programmieren :-)

Noch eine kleine Randnotiz: Da Artikel in Ordnern mit der Struktur Jahr/Monat/Tag abgelegt werden, wird einfach der relative Pfad (z.B. 2010/03/23/02-sbe-zum-dritten.mkd zur Identifizierung benutzt. Dies vereinfacht auch die Ermittlung der Bestandteile des Pfades Dank des os.paht Moduls.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class ScannerNode(object):
    """ Basic Node object for hierarchical linked list """
    def __init__(self, id):
        self.id = id
        self.prev = None
        self.next = None
        self.parent = None
        self.children = []

    def append_child(self, child_node):
        """ appends a child node to the children list and 
            links the child to the parent """
        self.children.append(child_node)
        child_node.parent = self

    def get_deploy_path(self):
        """ returns the path to wich the node shall be deployed """
        if self.id.endswith(os.path.sep):
            return self.id
        return self.id + "/"

    def get_name_parts(self):
        """ returns the parts of the identifier """
        return self.id.split(os.path.sep)

    def get_base_name(self):
        """ returns the basename of the identifier """
        return os.path.basename(self.id)

Dies stellt die Basisklasse der einzelnen Einträge (Nodes) dar. Über die Attribute .prev und .next lässt sich einfach auf den vorherigen bzw. nächsten Eintrag zugreifen; .parent ermöglicht den Zugriff auf das Elternelement und in .children[] können die entsprechenden Kinder-Knoten abgelegt werden.

Es gibt für Artikel, Tage, Monate und Jahre einzelne Klassen die alle von ScannerNode abgeleitet sind und weiter Methoden enthalten die den Zugriff auf Kinderelemente erleichtern oder im Falle von ArticleNode den entsprechenden Blog-Artikel einlesen und zwischenspeichern können.

Bei dieser Struktur handelt es sich um keine klassische Linked List, da die einzelnen Knoten in einer Liste gesammelt werden und nur untereinander eine Verknüpfung erhalten. Eine klare Baumstruktur ist es auch nicht, da die Verknüpfung von Kinderknoten untereinander Elternknoten überspringen können. Ok, etwas verwirrend, vielleicht eine Grafik dazu?

Verdeutlichung der Beziehungen zwischen den Knoten

static blog engine – teil 2

Dienstag, 23. März 2010, Themen:

Nachdem ich im ersten Teil den Ablauf und die Funktionsweise der static blog enginge kurz erläutert habe, gehe will ich nun etwas mehr auf die Umsetztung eingehen. Wie bereits erwähnt, dient dies auch als Dokumentation für mich.

Fangen wir doch einfach mal mit einer Auflistung der vorhandenen module an:

Modul Kurzbeschreibung
article.py Modul für einzelne Blog-Artikel
cli.py Command Line Interface
commons.py in mehreren Modulen verwendete Funktionenen
inbox.py zur Bearbeitung des Eingangs
inventory.py Erfassung der vorhandenen Blog-Artikel
nodes.py Repräsentation von Jahren, Monaten, Tagen und Artikeln
progress.py Darstellung eines Fortschrittsbalken
settings.py Ermittlung der Einstellungen
workbench.py Bringt alles obrige zusammen

Zugegeben, nur mit der Kurzbeschreibung kann man nicht viel anfangen. Ganz grob lassen sich die Module in "Arbeitstiere" (article.py, inbox.py, inventory.py, nodes.py, workbench.py) und "Hilfsmodule" (cli.py, commons.py, progress.py, settings.py) einteilen.

Ich in den nächsten Artikeln die einzelnen "Arbeitstiere" näher beleuchten. Ob ich auch mit den Hilfsmodulen so verfahre oder diese irgendwo mit einflechte, wird sich noch zeigen.

orso in berlin

Dienstag, 23. März 2010, Themen:

Ich bin wieder zurück von einem sehr anstrengenden aber auch sehr schönem Wochenende in Berlin,an dem ich ORSO – The Rock Symphony Orchestra im Friedrichstadpalast tatkräftig zur Seite stand. Es gibt einen sehr netten, wenn auch kurzen (Vorab-)Bericht darüber beim RBB

stopschild für kirchen

Dienstag, 16. März 2010, Themen:

Das Stopschild für Kirchen wirft eine Frage auf: muss jeder Kirchenbesucher seine Adresse hinterlassen sobald die Vorratsdatenspeicherung wieder kommt? Oder stellt man jeden, der Kirchensteuer zahlt unter Pauschalverdacht?

static blog engine – teil 1

Dienstag, 16. März 2010, Themen:

Die Idee zu einer Möglichkeit Webseiten statisch zu erstellen ist nicht neu und hat bei mir auch lange "gegärt". Ich wusste von Anfang an, was ich erreichen wollte, habe mich aber beim Wie doch etwas schwer getan. Ich glaube es ist nun der fünfte oder sechste Versuch, bei dem ich soweit zufrieden bin, dass ich ihn nun auch wirklich einsetze.

Dies hier sind die Vorgaben, die ich mir selbst gestellt hatte: - keine Datenbank-Anbindung, nach Möglichkeit nur Text-Dateien und Ordner - einfache CLI-Anwendung, kein Web-Interface zum erstellen der Artikel - Artikel sollen in Markdown (ich liebe es) und / oder HTML geschrieben werden - neue Artikel sollen zusammen mit verknüpften Dateien wie Bilder oder PDFs in einem Ordner abgelegt werden und durch die Anwendung überprüft und an ihrem Zielort verschoben werden. - geänderte Artikel sollen automatisch erkannt werden - die Möglichkeit Tags zu setzten sollte von Anfang an vorhanden sein

Natürlich sollten auch Kommentare und Trackbacks bzw. Pingbacks eingebunden werden, aber das habe ist nicht die höchste Priorität. Da es sich bei Kommentaren und Pingbacks um dynamische Elemente handelt werde ich diese über einen externen Service einbinden, doch dazu später mehr.

Gut, nun aber zum praktischen Ablauf: Zuerst erstellt man einen neuen Artikel in einem Eingangs-Ordner. Der Artikel enthält eine Bild-Verknüpfung und ist mit dem Tag "Beispiel" versehen.

neuer Artikel im Eingangs-Ordner

Durch den Aufruf sbe update oder die Kurzform sbe (update ist das Standard-Kommando) werden zuerst die Artikel im Eingangs-Ordner bearbeitet. Es wird dabei überprüft ob alle Angaben wie Autor, Tags, Titel, etc. und Verknüpfungen vorhanden sind. Der Artikel und das verknüpfte Bild wird dann in den entsprechenden Ordner verschoben. Dabei bekommt der Artikel eine Nummer vorgesetzt um später die Reihenfolge innerhalb eines Tages einfacher ermitteln zu können.

Artikel in den entsprechenden Ordner verschoben

Nun werden alle Artikel gesucht, die sich seit der letzten Aktualisierung geändert haben. Zugegeben in diesem Beispiel tut sich da nicht viel. Alle geänderten und neue Artikel werden nun als HTML-Datei ausgegeben. Natürlich wird dabei auch das Bild mit kopiert.

Artikel in HTML gewandelt

Danach werden die Dateien für den Tag, den Monat und das Jahr erstellt bzw. geändert.

erstellen der Index-Dateien

Es fehlt noch die Startseite, sowie der Atom-Feed.

Startseite und Atom-Feed

Als letztes werden die Tag-Dateien ausgegeben. Dazu wird für jeden gefundenen Tag eine extra Liste gespeichert um nicht jedes mal alle Artikel einlesen zu müssen.

Tag Listen

Der deploy-Ordner kann man nun einfach veröffentlichen, in dem man ihn z.B. mit FTP auf einen Webspace lädt. Über folgende Pfade kann man auf die "Einzelteile" des Blogs zugreifen – vorausgesetzt der Server ist so konfiguriert, dass er beim Aufruf eines Ordners automatisch die "index.html"-Datei ausliefert:

Pfad Beschreibung
/ Startseite des Blogs
/2010/ Übersichtsseite für das Jahr 2010
/2010/03/ Übersichtsseite für den März 2010
/2010/03/13/ Übersichtsseite für den 13. März 2010
/2010/03/13/01-ein-artikel.html Unser erstellter Artikel
/tags/ Übersichtsseite der Tags
/tags/beispiel.html Übersichtsseite des Tags "beispiel"