Sonntag, 14. August 2016

Motorola Moto G 3. Gen - Bilder werden im Dateimanager nicht angezeigt

Ein Verwandter hatte sich kürzlich ein Motorola Moto G 3. Generation als Smartphone gekauft. Da dieses Moto G einen Slot für Micro-SD Karten hat, wurde direkt eine 32 GB Karte eingelegt.

Das Smartphone hat dann nach dem ersten Start noch ein Update auf Android 6.0 gemacht und läuft völlig problemlos. Geschossene Fotos werden dabei auf die externe Speicherkarten gespeichert.

Jetzt sollten die Fotos auf den Computer übertragen werden. Also wurde das Handy per USB-Kabel an den Computer angeschlossen. Dieser erkannte das Smartphone und die SD-Karte auch direkt. Aber auf der SD-Karte wurden keine Fotos gefunden bzw. der Ordner "DCIM/Camera" wurde erst gar nicht angezeigt. Das Problem trat gleichermaßen unter Windows 10 als auch unter Ubuntu Linux 14.04 auf. Es macht auch keinen Unterschied, ob man am Smartphone als Modus für den Datentransfer "PTP" oder "MTP" auswählt.

Eine kurze Recherche im Internet zeigt, dass dieses Probleme wohl viele Nutzer haben. In einem englischsprachigen Forum wurde gesagt, dass dies ein Bug sei, der im Zusammenhang mit dem Upgrade auf Android 6.0 steht. Aber, und das ist das gut, es wurde direkt auch eine Lösung gezeigt, welche auch funktioniert hat. Diese ist im folgenden aufgeführt:

Hinweis: Auch wenn es beim Beschreiten des Lösungswegs normalerweise nicht zu Datenverlust kommen sollte, ist es dringend anzuraten, die auf der SD-Karte befindlichen Fotos vorher zu sichern (z.B. via Google Drive, Dropbox etc.).

Die Lösung des Problems besteht darin, dass man von Android zwischengespeicherte Daten löscht und somit das Betriebssystem dazu "bewegt" die Daten neu zu lesen und anzulegen. Anschließend werden die Verzeichnisses im Dateimanager auch wieder wie gewohnt angezeigt. Das ganze geht so:

Man geht in die "Einstellungen" und wählt dort den Punkt "Apps". Hier klickt man rechts oben die drei senkrechten Punkte und wählt den Menüpunkt "Systemprozesse anzeigen" aus.
Nun sucht man in der langen Liste der Prozesse die Punkte "Externer Speicher" und "Medienspeicher".
Beide Einträge öffnet man, klickt auf das Feld "Speicher" und klickt dann auf "Daten löschen" und "Cache leeren" .

Jetzt muss man das Smartphone noch ausschalten und dann wieder einschalten. Bevor man es an den Computer anschließt sollte ein paar Minuten (ca. 8-10) gewartet werden, damit Android die Daten neu anlegen kann (wie lange dies wirklich dauert weiß ich nicht, aber das englischsprachig Forum empfiehlt, so lange zu warten).

Schließt man das Moto G 3. Gen jetzt an den Computer an und wählt unter "USB zum Aufladen"  die Option "Dateien übertragen (MTP)", dann sollte man wieder alle Ordner auf der SD-Karte sehen. Inklusive des Ordners, in dem die Fotos liegen. Jedenfalls hat es bei meinem Verwandten so einwandfrei funktioniert.

Samstag, 30. Januar 2016

Python, WTForms, JavaScript: Formulare dynamisch erweitern

Folgender Fall: in einer Webanwendung sollen Daten über ein Formular eingegeben werden. Die Felder des Formulars sind klar definiert, aber wie viele Daten eingegeben werden sollen, ist offen.

Ein Beispiel hierfür wäre z.B. eine Webanwendung für Rezepte. Es gibt Rezepte, die haben eine handvoll Zutaten. Aber es gibt auch Rezepte, die haben mehrere Dutzend Zutaten. Hier macht es also wenig Sinn, die Anzahl der Eingabefelder für das Formular fix festzulegen. Vielmehr gilt es, die Möglichkeit für den Nutzer zu schaffen, das Formular dynamisch zu erweitern.

Die Zutaten hier dafür: Python 3.4, Bottle 0.12, WTForms 2.1 , JavaScript und jQuery 2.2. Bottle als Webframework ist hier übrigens nur Mittel zum Zweck, das Beispiel lässt sich genau so mit anderen Python Webframeworks umsetzen.

Das Beispiel besteht aus vier Codeteilen:
  • der Bottle Hauptanwendung, die das Routing etc. festlegt
  • den Formularklassen, erstellt mit WTForms
  • den beiden Templates zur Dateneingabe und zur Datenausgabe
Wer das Beispiel nachstellen möchte: wie bei Bottle üblich wird erwartet, dass die Templates im Verzeichnis views liegen. Das benötigte jQuery wird im Verzeichnis static erwartet. Alternativ könnte man natürlich auch jQuery von einem der vielen CDN-Servern online laden.

Die Hauptdatei sieht so aus:

import os
from bottle import route, run, template, debug, static_file, request
from my_forms import InputForm

BASE_DIR = os.path.dirname(os.path.dirname(__file__))

@route('/ingredients')
def index():
    form = InputForm()
    return template('dyn_form_input_wt.html', form=form)

@route('/ingredients', method='POST')
def post_data():
    form_data =  request.forms
    form = InputForm(form_data)
    if not form.validate():
        errors = form.errors['ingredients']
    else:
        errors = None
    ingredients = []
    for i in range(0, int(len(form_data)/3)):
        ingredients.append((form_data['ingredients-'+str(i)+'-description'],
                            form_data['ingredients-'+str(i)+'-unit'],
                            form_data['ingredients-'+str(i)+'-quantity']))
    return template('output.html',
                    ingredients=ingredients,
                    errors=errors)

@route('/static/<filename>')
def server_static(filename):
    return static_file(filename, root=os.path.join(BASE_DIR, 'static'))

debug=True
run(host='localhost', port=8080, reloader=True)

Hier gibt es eigentlich nicht viel zu sagen. Eine Route liefert das Formular aus, die Route mit method='POST' nimmt die Formulardaten auf und sortiert diese zurück in eine Liste. In einer realen Anwendung würde man an dieser Stelle die Daten z.B. in eine Datenbank schreiben.
Wichtig ist hierbei aber, dass davon ausgegangen wird, dass die name-Attribute der Formularfelder a) der Namensgebung von WTForms FieldList entsprechen - also Name des Formulars - Zähler - Name des Felds - , b) der Zähler bei Null beginnt und c) die Zählung lückenlos ist (also 0, 1, 2, 3 usw.). Dass das tatsächlich auch so ist, dafür sorgt der JavaScript in der weiter unten gezeigten Template-Datei dyn_form_input_wt.html.

Die Datei my_forms.py, welche die Formularklassen enthält, sieht so aus:

from wtforms.form import Form
from wtforms.fields import StringField, SelectField, DecimalField, FormField, \
    FieldList
from wtforms.validators import NumberRange, InputRequired

UNITS = [('kg', 'kg'),
         ('g', 'g'),
         ('TL', 'TL'),
         ('EL', 'EL'),
         ('St', 'St')] 

class IngredientForm(Form):
    description = StringField('description',
                              [InputRequired()])                                  
    unit = SelectField('unit', choices=UNITS)
    quantity = DecimalField('quantity',
                            [InputRequired(), NumberRange(min=0)],
                             places=3)
                            
class InputForm(Form):
    ingredients = FieldList(FormField(IngredientForm), min_entries=1)

Das ist soweit alles WTForms Standard ohne Tricks und Kniffe. In der Klasse InputForm wird die IngredientForm per FormField zu einem Formular-Feld zusammengefasst, FieldList wiederum fasst mehrere Formular-Felder zusammen. Mehr Infos dazu sind in der Dokumentation von WTForms zu finden.

Das Template dyn_form_input_wt.html ist für die Eingabe der Zutaten sowie das dynamische Hinzufügen und Entfernen von Eingabefeldern zuständig. Das Template sieht so aus:

<!DOCTYPE html>
<html>
<head>
    <title>Dynamic Forms</title>
    <script src="static/jquery-2.2.0.min.js"></script>
</head>
<body>
<form action="/ingredients" method="POST">
    <div class="input_fields_wrap">
    <!-- here goes the form fields -->
    </div>
    <button class="add_field_button">Add field</button>
    <button class="remove_field_button">Remove field</button>
    <input type="submit">
</form>
<script>
$(document).ready(function() {
    var max_fields = 20; //maximum input boxes allowed
    var wrapper = $(".input_fields_wrap"); //Fields wrapper
    var addButton = $(".add_field_button"); //Add button ID
    var removeButton = $(".remove_field_button"); //Add button ID
    var htmlString = '<div id="input_fields_0">{{ !form.ingredients }}</div>';
    var field_counter = 0;
    $(wrapper).append(makeString()); //add the first form
    $(addButton).click(function(e){ //on add input button click
        e.preventDefault();
        if(field_counter < max_fields){ //max input box allowed
            field_counter++;
            $(wrapper).append(makeString());}
        else { window.alert('max number of ingredients reached!')}
    });
    $(removeButton).click(function(e){ //on remove input button click
        e.preventDefault();
        if(field_counter > 0){ //make sure at least one field is there
            $('#input_fields_'+field_counter).remove();
            field_counter--;}
        else { window.alert('Cannot delete, one input has to remain.')}
    });
    function makeString() {
        var myString = htmlString;
        return myString.replace(/0/g, field_counter);
    };
});
</script>
</body>
</html>

Wie zu sehen ist, besteht das Template aus zwei Sektionen: dem HTML-Teil und dem JavaScript Teil. Erster ist ziemlich "straight forward" und bedarf wohl keiner weiterer Erklärung, letzter macht die Hauptarbeit und liefert die Dynamik.

Im HTML-Teil wird nur der "Rahmen" für das Formular angelegt, es werden aber keine Formularfelder erzeugt. Dies geschieht ausschließlich per JavaScript / jQuery.

Den Ausgangspunkt bildet die Definition der Variablen htmlString, in der auch das WTForms Formular gerendert wird, über {{ !form.ingredients }}

Die Zeile $(wrapper).append(makeString()); erzeugt den ersten Satz Formularfelder, der Zähler field_counter steht hier noch auf Null.

Zum Hinzufügen weitere Felder ist eine JavaScript-Funktion an den Button "Add field" gebunden, welche a) checkt, ob nicht die per max_fields festgelegte Anzahl an Feldern schon erreicht ist, b) den Feldzähler field_counter um eins erhöht und dann c) einen Satz Formularfelder hinzufügt.

Zum Löschen, welches über eine an den Button "Remove Fields" gebundene Funktion erfolgt, ist der Ablauf ähnlich. Hier wird zuerst geprüft, ob mehr als ein Satz Formularfelder vorhanden ist. Wenn ja wird das letzte entfernt und der field_counter Zähler um eins dekrementiert.

Die Funktion makeString ist dafür verantwortlich, dass die Nummerierung der Formularfelder (bzw. genau genommen deren Attribute wie id, name etc. gem. dem aktuellen Stand von field_counter angepasst werden. Dazu wird einfach die String-Methode replace() mit den entsprechenden Werten auf den Ausgangsstring angewendet und als neuer String zurück geliefert.

Wenn das Formular abgesendet wurde, wird in diesem Beispiel hier das Template output.html aufgerufen, welches die eine Liste der Zutaten mit Menge und Einheit sowie mögliche Fehler ausgibt. Das Template sieht so aus:

<!DOCTYPE html>
<html>
<head>
<title>Dynamic Forms Output</title>
</head>
<body>
<p>Received ingredients from form data:</p>
<ul>
% for ingredient in ingredients:
<li>{{ ingredient[0] }}: {{ ingredient[2] }} {{ ingredient[1] }}
% end
</ul>
% if errors:
<p>The form has the following errors:
<ul>
% for error in errors:
<li>{{ error }}
% end
</ul>
% end
</body>
</html>

Das Template ist hier auch nur Mittel zum Zweck, bei Produktiv-Code würde man hier sicherlich anders vorgehen.

Wie hier gezeigt wurde, ist es nicht so schwierig und aufwendig, dynamisch erweiterbare Formular zu erzeugen, auch wenn diese in den ansonsten "starren" Klassen eines Python Formularframeworks definiert sind.
Dazu benötigt werden aus WTForms insbesonders die Klassen FormField und FieldList sowie auf der Client-Seite ein bisschen JavaScript mit jQuery.


Samstag, 3. Oktober 2015

systemd timer unter Raspbian

Ende September ist eine neue Raspian Version erschienen, welche auf Debian Jessie basiert. Und damit ist auch systemd als Init-Dienst standardmäßig an Bord.

Davon bekommt man als "normaler" Nutzer erst mal wenig mit, weil man in der Regel wenig "Berührung" mit dem Init-System hat.

Durch den Einsatz von systemd bietet sich aber die Möglichkeit, dessen Timer zu verwenden. systemd Timer sind ein Dienst, welcher periodisch bestimmte, frei festzulegende Aktion ausführen kann - und sind damit eine Alternative zu cron.

Das wird im folgenden anhand eines Beispiels gezeig.

Unter Verwendung eines systemd Timer soll das folgende Python Skript alle 5 Minuten ausgeführt werden:

#!/usr/bin/env python3

import sqlite3
import datetime
from sense_hat import SenseHat

s = SenseHat()
conn = sqlite3.connect('/home/pi/code/ambient_data.db')
sql = 'INSERT INTO data VALUES (?, ?, ?, ?)'
with conn:
    conn.execute(sql, (s.get_temperature(),
                       s.get_pressure(),
                       s.get_humidity(),
                       datetime.datetime.now()))
conn.close()

Das Skript liest den Temperatur-, Luftdruck- und Luftfeuchtigkeitssensor eines SenseHAT aus und schreibt die Daten in eine SQLite-Datenbank.
Das Skript heißt ambient_temperature_logger.py und liegt unter /home/pi/code.
Die zugehörige Datenbank ambient_data.db liegt im gleichen Verzeichnis.

Damit das Skript automatisch ausgeführt wird, muss man noch zwei Dateien für systemd erstellen:
  • eine .service Datei, welche das Skript aufruft
  • eine .timer Datei, welche die Daten für das automatische Ausführen enthält.
Beide Dateien legt man mit Root-Rechten im Verzeichnis /etc/systemd/system an.

Die Datei ambient_data_logger.service bekommt folgenden Inhalt:

[Unit]
Description=SenseHat Ambient Data Logger

[Service]
Type=simple
ExecStart=/home/pi/code/ambient_data_logger.py

Der Inhalt ist wohl weitestgehend selbsterklärend:
Im Abschnitt [Unit] steht eine Beschreibung, was passiert. Im Abschnitt [Service] wird bei Type der Typ festgelegt, was bei Skripten wie in diesem Beispiel normalerweise simple ist. Unter ExecStart wird der Befehl zum Aufruf des Skripts angegeben.

Die Datei ambient_data_logger.timer bekommt den folgenden Inhalt:

[Unit]
Description=Runs the ambient_data_logger Python script every 5 minutes

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Unit=ambient_data_logger.service

[Install]
WantedBy=multi-user.target

Unter [Unit] wird wieder ein Beschreibung hinterlegt, unter [Install] WantedBy, welchem target der Timer zugeordnet wird. Für Skripte wie diese ist multi-user.target eine gute Wahl, weil der (aktiverte) Timer dann beim Systemstart mit gestartet wird.

Im Abschnitt [Timer] wird festgelegt, wann was passieren soll:
  • OnBootSec legt fest, wann das Skript (bzw. im Jargon von systemd die "Unit") ausgeführt werden soll - hier also nach 2 Minuten.
  • OnUnitActiveSec legt fest, nach wie viel Minuten die Unit jedes weitere Mal ausgeführt werden soll - hier also alle 5 Minuten
  • Unit legt fest, welche .service-Datei durch den Timer aufgerufen wird.
Jetzt muss man die .timer und die .service-Dateien noch ausführbar machen:

$ sudo chmod +x ambient_data_logger.*

und den Timer mit den folgenden beiden Befehlen aktivieren:

$ sudo systemctl start ambient_data_logger.timer
$ sudo systemctl enable ambient_data_logger.timer

Der erste Befehl startet den Timer, der zweite stellt sicher, dass der Timer auch nach einem Reboot aktiviert wird.
Zum Deaktivieren des Timers ersetzt man im ersten Befehl start durch stop und im zweiten Befehl enable durch disable.

Um zu prüfen, ob der Timer auch aktiv ist und läuft, kann man sich alle aktiven Timer anzeigen lassen:

$ systemctl list-timers

Das Anlegen von Timer von systemd ist also ziemlich gradlinig und nicht weiter schwierig.

Da systemd bei anderen Distribution (z.B. Fedora, Arch) schon länger im Einsatz ist, findet man im Internet reichlich Beispiele zu systemd und Timern. Da systemd weitestgehend unabhängig von den jeweiligen Eigenheiten der darüber liegenden Distribution ist, sind die Beispiele auch auf Raspbian übertragbar.
Außerdem sind die Man-Pages zu systemd und systemd.timer recht ausführlich.

Samstag, 15. August 2015

Python: PIL/Pillow - Bild anzeigen mit Image.show() ( xv vs. xli vs. command vs. imagemagick)

Möchte man sich mit Hilfe von Python ein Bild anzeigen lassen, dann geht das mit drei Zeilen Code und dem Pillow-Modul. Pillow ist ein aktiv entwickelter "friendly fork" von PIL (=Python Image Library). PIL bzw. Pillow sind der quasi-Standard unter Python, wenn es das Handling und Manipulieren von Grafik-Dateien geht.

Die folgenden drei Codezeilen zeigen das Bild "foo.png" an:

>>> from PIL import Image
>>> im = Image.open('foo.png')
>>> im.show()

So zumindest die Theorie. In der Praxis passiert aber zumindest unter Ubuntu 14.04 und auch unter Raspbian nichts. Nach dem Aufruf von im.show() wird sofort wieder der Prompt des Python-Interpreters angezeigt. Keine Fehlermeldung, aber auch kein Bild.

Ein Blick in die Doku von Pillow brachte dann ans Tageslicht, dass die show-Funktion den Bildbetrachter xv aufruft. Dieser war in den 90er Jahren des vorherigen Jahrhunderts wohl mal der de-facto Bildbetrachter auf Unix-System, ist heute jedoch (ziemlich) veraltet. Und damit auch in den meisten Distributionen wie Debian, Ubuntu etc. nicht mehr enthalten.

Um trotzdem Bilder mittels show() anzeigen zu können, gibt es zwei alternative Wege:

Einer ist, das Paket imagemagick zu installieren. Dieses ist für quasi alle Linux-Distributionen in den Paketquellen enthalten.
Danach funktioniert der Aufruf von show(), es wird display aus dem ImageMagick-Paket zur Anzeige des Bilds verwendet. Leider ist dieser Weg in der Doku von Pillow nicht dokumentiert. Ein Blick in den Quellcode von von Image zeigt aber, dass Pillow auf Linux-Systemen explizit nach display sucht und diese - sofern vorhanden - verwendet.

Eine andere Alternative ist im Raspberry Pi Forum zu finden. Hier wird also das Paket xli (xli="command line tool for viewing images in X11") installiert und ein Softlink von xv auf xli angelegt:

$ sudo apt-get install xli
$ cd /usr/local/bin
$ sudo ln -s /usr/bin/xli xv


Dies funktioniert sowohl unter Raspbian als auch unter Ubuntu (und vermutlich auch bei den meisten anderen Linux-Distros, die xli in den Paketquellen haben.

Die show-Funktion kennt das optionale Argument command=, mit dem der Bildbetrachter explizit vorgegeben werden. So steht es jedenfalls in der Dokumentation. Allerdings hat das auf keinem meiner Systeme funktioniert.

Montag, 3. August 2015

Python: Ausrichten von Paragraphen in Platypus / ReportLab

Paragraphen von Reportlabs Playtypus sind dazu da, "normalen" Text aufzunehmen und dann im PDF-Dokument darzustellen. Und wie in jeder anderen Textdarstellung auch hat der Text in diesen Paragraphen natürlich auch einen Ausrichtung. Diese ist per Voreinstellung linksbündig.

Nun gibt es aber auch genug Anwendungsfälle, wo man den Text nicht linksbündig ausrichten möchte. So bietet es sich bei längern Textpassagen an, den Text als Blocksatz zu formatieren, damit das rechte Textende nicht "flattert".

Das ist natürlich auch mit ReportLab / Platypus auch ohne Probleme möglich. Es werden alle vier gängigen Textformatierungen unterstützt: linksbündig, rechtsbündig, zentriert und Blocksatz.

Wie das geht, ist im folgenden Beispiel gezeigt. Für rechtsbündig, zentriert und Blocksatz wird einfach das bestehenden Stylesheet um neue Styles erweitert, die die entsprechenden Ausrichtung haben:

from reportlab.platypus import Paragraph, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
from reportlab.lib.pagesizes import A4

text = '''Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam 
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita 
kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'''

text_short = 'Lorem ipsum dolor sit amet'

styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='RightAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_RIGHT))
styles.add(ParagraphStyle(name='CenterAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_CENTER))
styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_JUSTIFY))
doc = SimpleDocTemplate('align_test.pdf', pagesize=A4)
story = []
story.append(Paragraph(text_short, styles['Normal']))
story.append(Paragraph(text_short, styles['RightAlign']))
story.append(Paragraph(text_short, styles['CenterAlign']))
story.append(Paragraph(text, styles['JustifyAlign']))
doc.build(story)

Führt man den obigen Code aus, dann enthält dieser vier Paragraphen: der erste ist linksbündig ausgerichtet, der zweite rechtsbündig, der dritte zentriert und der vierte als Blocksatz.

Intern verwendet ReportLab für die Ausrichtung Integer-Werte, die importierten Konstanten TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY stellen diese einfach nur dar, wie man leicht im interaktiven Python-Interpreter sehen kann:

>>> from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
>>> TA_CENTER
1
>>> TA_LEFT
0
>>> TA_RIGHT
2
>>> TA_JUSTIFY
4

>>>

Im obigen Codebeispiel könnte man also statt

styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_JUSTIFY))
auch schreiben:

styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=4))

Das Ergebnis ist das gleich und man würde den Import der TA_* Konstanten "sparen".

Samstag, 1. August 2015

WTForms: Daten für SelectField nachträglich einfügen

WTForms gehört in der Python-Welt zu den populäreren Modulen zum Generieren und Validieren von HTML-Formularen für Webanwendungen.

Hier im Blogeintrag geht es aber nicht um WTForms im gesamten - die offizielle Doku ist ja so wie so recht ausführlich - sondern "nur" um das nachträgliche Hinzufügen von Auswahlmöglichkeiten für ein SelectField (=Auswahlfeld). Wobei das im folgenden gezeigt auch für das RadioField gilt.

Vorab noch: Alle folgenden Beispiele sind mit WTForms 2.0.2 unter Python 3.4 getestet. Der Einfachheit wird alles im interaktiven Python-Interpreter auf der Kommandozeile durchgespielt. Das Vorgehen in realen Webanwendungen ist aber identisch.

Eine einfaches Formular mit einem SelectField sieht z.B. so aus:

>>> from wtforms import Form, StringField, SelectField
>>> class TestForm(Form):
...     name = StringField('name')
...     gender = SelectField('gender',

            choices=[('m', 'male'),
                     ('f', 'female')])
>>>

Zuerst werden die benötigten Klassen importiert, dann wird eine eigene Formularklasse Namens TestForm definiert, welche die zwei Felder name und gender hat. gender ist dabei ein  Auswahlfeld.

Die Auswahlmöglichkeiten werden über das choices-Attribute festgelegt. Diesem wird eine Liste von Tupeln zuwiesen, wobei das erste Element der value der Option ist und das zweite Element der angezeigte Text.

Erzeugt man nun eine neue Instanz der Klasse

>>> my_form = TestForm()

kann man sich das HTML-Markup, welche das Formular erzeugt, auch direkt auf der Kommandozeile anzeigen lassen, z.B. für gender:

>>> str(my_form.gender)
'<select id="gender" name="gender"><option value="m">male</option><option value="f">female</option></select>'

>>>

Jetzt gibt es aber natürlich auch genug Anwendungsfälle, wo man in einer Webanwendung ein Auswahlfeld dynamisch befüllen will und somit die Werte bei der Definition der Klasse noch nicht kennt.

Auch das ist natürlich mit WTForms problemlos möglich und zwar indem man das choices-Attribut erst später mit Daten füttert. Dies wird im folgenden Beispiel gezeigt:

>>> class TestForm2(Form):
...     name = StringField('name')
...     fav_color = SelectField('favourite color')
...
>>> form2 = TestForm2()


Wie zu sehen ist, wird für das SelectField kein Wert für choices übergeben. Folglich kann auch kein HTML generiert werden, stattdessen wird ein Fehler geworfen:

>>> str(form2.fav_color)
Traceback (most recent call last):
...
TypeError: 'NoneType' object is not iterable
>>>


Versorgt man das choices-Attribute mit Daten, dann funktioniert alles:

>>> my_choices = [('w', 'white'),
                  ('r', 'red'),
                  ('b', 'blue')]
>>> form2.fav_color.choices = my_choices
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'

>>>

In einer realen Anwendung würde die Liste natürlich nicht hard-coded im Quelltext stehen, sondern z.B. aus einer Datenbankabfrage kommen.

Wie andere Field-Klassen auch kennt SelectField ebenfalls einen Default-Wert, was sich im HTML-Formular dann so darstellt, dass diese Option vorausgewählt ist.

Wollte man z.B. im ersten Beispiel female als Default-Wert haben, dann müsste die Zeile im Code so geändert werden:

gender = SelectField('gender',
            choices=[('m', 'male'),
                     ('f', 'female')],
            default='f')

Will man beim 2. Beispiel  die Farbe weiß als Default-Wert setzen, dann muss man das default-Attribute ebenfalls mit einem Wert versorgen:

>>> form2.fav_color.default='w'
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'
>>> form2.fav_color.default
'w'
>>>


Das alleine reicht aber nicht, wie im obigen Code zu sehen ist, hat sich am generierten HTML aber nichts geändert, obwohl der Default-Wert korrekt gesetzt ist.

Damit das HTML wie gewünscht ist, muss ein weiterer Schritt durchgeführt werden, und zwar der Aufruf der process-Methode der Klasse: :

>>> form2.process()
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option selected value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'
>>>


So ist auch der Default-Wert korrekt gesetzt.

Sonntag, 26. Juli 2015

Python: Tabellen in ReportLab

ReportLab ist die wohl leistungsfähigste Bibliothek für Python, wenn es um das Erzeugen von PDF-Dateien geht. Von High-Level Zugriffen via Platypus (=Page Layout and Typography Using Scripts) bis zu Low-Level mit direktem Zugriff auf den Canvas bietet ReportLab alles.

Tabellen unterstützt Platypus mit Hilfe der Table-Klasse (welche ein Flowable darstellt) direkt und die Verwendung ist auch erst Mal nicht weiter schwierig.

Es soll ein einfaches PDF-Dokument mit Hilfe von ReportLab gebaut werden. Das Dokument besteht aus einem Absatz Text, einer Tabelle und einem Absatz Text nach der Tabelle.

Der Code dafür sieht im einfachsten Fall so aus:

from reportlab.platypus import Paragraph, Table, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4


my_data = [['foo', 'bar', 'spam'],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]
          
styles = getSampleStyleSheet()
doc = SimpleDocTemplate('table_test.pdf', pagesize=A4)
story = []
story.append(Paragraph('Text vor Tabelle', styles['Normal']))
story.append(Table(my_data))
story.append(Paragraph('Text nach Tabelle', styles['Normal']))
doc.build(story)


my_data enthält die Daten für die Tabelle, der Rest ist weitestgehend selbsterklärend.

Nur das das Dokument nicht gerade "hübsch". Das Ergebnis ist im folgenden Screenshot zu sehen:

Standardmäßig werden für die Tabelle keine Rahmen gezeichnet und die Tabelle ist horizontal auf der Seite zentriert. Außerdem ist der Text sehr nah an der Tabelle.

Das ist im folgenden Code korrigiert:

from reportlab.platypus import Paragraph, Table, TableStyle, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors

my_data = [['foo', 'bar', 'spam'],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]
          
styles = getSampleStyleSheet()
doc = SimpleDocTemplate('table_test.pdf', pagesize=A4)
story = []
story.append(Paragraph('Text vor Tabelle', styles['Normal']))
t = Table(my_data)
t.hAlign = 'LEFT'
t.spaceBefore =  10
t.spaceAfter = 10
t.setStyle(TableStyle(

    [('BOX', (0,0), (-1,-1), 0.5, colors.black),
     ('INNERGRID', (0,0), (-1,-1), 0.5, colors.black)]))
story.append(t)
story.append(Paragraph('Text nach Tabelle', styles['Normal']))
doc.build(story)


Das ganze liefert als Ergebnis folgendes:
Über das Attribute hAlign wird die Tabelle die Ausrichtung der Tabelle bestimmt, der Wert LEFT richtet sie linksbündig aus. Mit den Attributen spaceBefore und SpaceAfter wird zusätzlicher Leerraum (hier: 10 px) eingefügt.

Die Tabelle an sich wird über die setStyle-Methode formatiert, welche als Wert die TableStyle-Klasse übergeben bekommt. Diese erlaubt einen detaillierten Eingriff in die Formatierung der Tabelle.

Die Werte für TableStyle werden als Liste von Tupeln übergeben. Im obigen Beispiel wird mit BOX ein Rahmen um die Tabelle gezeichnet und INNERGRID zeichnet den inneren Rahmen, beide in schwarz.
Die Tupel (0, 0), (-1, -1) definieren den Geltungsbereich der Formatierung. (0,0) ist dabei die Zelle links oben, (-1, -1) die Zelle rechts unten - somit ist also die ganze Tabelle abgedeckt.

Möchte man z.B. das innere Gitter mur für einen Teil der Zellen zeichnen und in der mittleren Zelle soll die Schrift rot sein, so ist die Codezeile wie folgt anzupassen:

t.setStyle(TableStyle(
    [('BOX', (0,0), (-1,-1), 0.5, colors.black
     ('INNERGRID', (0,0), (1,1), 0.5, colors.black),
     ('TEXTCOLOR', (1,1), (1,1), colors.red)]))

Und das Ergebnis sieht dann so aus:


TableStyle bietet eine Vielzahl weiterer Möglichkeiten, dass Aussehen der Tabelle zu beeinflussen. Eine komplette Übersicht ist im Kapitel 7 der ReportLab Dokumentation zu finden.

So ist es z.B. auch möglich, Zellen innerhalb einer Spalte oder Zeile zusammenzufassen. Möchte man z.B. die drei Zellen der ersten Zeile zusammenfassen, so ist der obiger Code wie folgt zu ändern:

...
my_data = [['Überschrift', '', ''],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]

....
t.setStyle(TableStyle(
    [('BOX', (0,0), (-1,-1), 0.5, colors.black),
     ('INNERGRID', (0,0), (-1,-1), 0.5, colors.black),
     ('SPAN', (0, 0), (-1, 0))]))


Das sieht im Ergebnis so aus:

Die bisher generierten Tabellen ermitteln die Breite der Spalten und Höhe der Zeilen automatisch, aber auch das kann natürlich beeinflusst werden. Möchte man z.B. eine fixe Spaltenbreite  von 100 px haben, so ist im Code die Zeile wie folgt zu ändern:

t = Table(my_data, colWidths=100)

Weitere Möglichkeit sind ebenfalls im Kapitel 7 der Dokumentation zu finden.

In den obigen Beispielen wird der Table-Klasse einfach eine Liste von Strings bzw. Integern übergeben. Man kann aber auch eine Liste von Flowables übergeben, was sich anbieten, wenn man z.B. längeren Text oder Grafiken in der Tabelle unterbringen möchte.

Des Weiteren gibt es noch die Klasse LongTable, welche anstellen von Table verwendet werden kann. Das Ergebnis ist laut Dokumentation das gleiche, allerdings soll LongTable bei sehr langen Tabelle etwas performanter sein.

Wie hier gezeigt wird, ist das Generieren einer Tabelle in einem mit Platypus gebauten PDF-Dokuments in ReportLab nicht weiter schwierig. Wie bei ReportLab üblich kann man auf Wunsch aber auch sehr detailliert in die Gestaltung und das Layout der Tabelle eingreifen.

Alle Beispiele aus diesem Blogbeitrag sind unter Python 3.4 und ReportLab 3.0 erstellt.

Sonntag, 19. Juli 2015

Grafikfehler auf Lenovo T450 und die passende Abhilfe

Seit ein paar Monaten nutze ich beruflich ein Lenovo T450 Thinkpad in der Varianten mit Intel Grafik (und ohne zusätzlich Nvidia Grafik).

Ubuntu 14.04 läuft darunter auch ohne Probleme, nur einen kleinen "Schönheitsfehler" gab es: nach dem Login war manchmal die Beschriftung der Symbole / Dateien auf dem Desktop unvollständig. Unvollständig heißt, dass einzelne oder ganz selten auch alle Buchstaben fehlten. Das kam bei ca. 50% der Logins vor. "Meine" Abhilfe war: abmelden, neu anmelden und alles war ok - zumindest in 95% der Fälle. Selten musst ich mich auch nochmal abmelden und einloggen, damit alles war, wie es sein sollte.

Auch mit dem oben beschriebenen Grafikfehler war das System uneingeschränkt benutzbar. Das Fehlen der Buchstaben trat z.B. auch im Menü der Systemeinstellung und in der Tab-Beschriftung des Firefox auf. Der Inhalt der Programmfenster an sich (z.B. die vom Firefox dargestellte Seite, die Liste der Lieder in Quod Libet etc.) war aber immer korrekt und fehlerlos.

Der Grafikfehler war zwar etwas nervig, aber die "Lösung" "Ausloggen, Einloggen" war so schnell und funktionierend, dass ich nie weiter für den Grund des Problems gesucht habe.

Anfang Juli hatte ich dann im Planet von ubuntuusers.de einen Beitrag des Intux-Blogs gelesen, in dem genau das gleiche Problem und eine passende Lösung für ein Thinkpad E550 beschrieben wurde.

Und das ist auch genau die Lösung, die beim Thinkpad T450 mit Intel GraKa funktioniert. Das ganze geht unter Ubuntu 14.04 in vier Schritten:
  • die Verzeichnis /etc/X11/xorg.conf.d/ anlegen
  • darin die Datei 20-intel.conf anlegen
  • die Datei mit folgendem Inhalt befüllen
    Section "Device"
       Identifier  "Intel Graphics"
       Driver      "intel"
       Option      "AccelMethod"  "uxa"
    EndSection
  • abmelden und wieder anmelden
Zumindest bei mir sind seitdem die oben beschrieben Grafikfehler nicht mehr aufgetreten.

An diese Stelle nochmal Danke an den Intux-Blog und zum Abschluss nochmal der volle Link auf den Blogartikel: http://www.intux.de/2015/07/das-thinkpad-e550-und-die-grafik/

Sonntag, 28. Juni 2015

Mair 1 Festival 2015, 2. Tag - Expire, Nasty, Suicidal Tendencies, Terror

Am, 27.6.2015, dem 2. Tag des Mair 1 Festivals 2015, ging es für mich so früh wie noch nie zum Mair 1, nämlich bereits um 14:30 Uhr. Der Grund: um 14.50 Uhr war der Auftritt von Expire - und den wollte ich nicht verpassen.

Als ich ankam, waren auf der großen Bühne gerade (die mir bis dato völlig unbekannten) Bury My Regrets zugange. Das ist eine deutsche Hardcore Band, welche ein solides, gut anzusehende und -hörendes Konzert ablieferten.

Jetzt kam Expire auf die kleinere Bühne. Die CDs von denen gefallen mir sehr gut, also war da schon eine gewisse Erwartungshaltung für das Konzert vorhanden.
Und die wurde nicht enttäuscht. Live hatte die Band genau so viel Power wie auf CD und mehr Energie als ein Dampfhammer mit Überdruck. Aufgrund der (für ein Festival) relativ frühen Stunde waren zwar noch nicht so viel Leute vor Ort, aber das vorhandene Publikum ging trotzdem voll bei dem sehr guter Auftritt von Expire mit.
Expire live auf der Bühne
Danach ging es dann erst Mal wieder nach Hause, weil mich dann erst so richtig wieder die Bands interessierten, die am Abend spielten.

Gegen 19.50 Uhr war ich dann auch wieder auf dem Festivalgelände. Zum dem Zeitpunkt waren noch Caliban auf der großen Bühne. Davon habe ich allerdings nichts gesehen, weil gerade am Eingang zum Festivalgelände eine Motocross Freestyle Show begann. Und so fliegende Motorräder aus der Nähe zu betrachten ist schon recht beeindruckend.

Um 20 Uhr starteten dann Nasty ihre Konzert. Die kannte ich bisher nur vom Namen. Die Band aus dem deutschsprachigen Teil von Belgien lieferten eine gute Show mit viel Interaktion mit dem Publikum ab. Das ging auch ziemlich mit, auch wenn ich so das Gefühl hatte, dass der entscheidende Funk nicht überspringen wollte.

Um 20.50 Uhr war es dann so weit - die Suicidal Tendencies betraten die große Bühne. Lebende Legenden, Veteranen des Hardcore, Inkarnation des Skatepunk und ich behaupte mal so was wie die heimlichen Headliner des Mair 1 2015.
Und: die Show war super gut, ohne wenn und aber. Mike Muir flitze eine Stunde lang wie ein angestochenes Schwein über die Bühne und auch der Rest der Band war voll dabei. Suicidal Style halt.
Zum Song "Possessed to Skate" holte man sich dann noch mal schnell ca. 50 Leute aus dem Publikum auf die Bühne, um dort abzufeiern.
Insgesamt lieferten die Suicidal Tendencies einen tollen, kurzweiligen Auftritt.
ST auf der Bühne des Mair 1
Auf der kleineren Bühne folgten dann Terror. Die Band kannte ich bis dahin auch nur vom Namen und hatte aufgrund dessen eher Metalcore vermutet. Ist es aber nicht - Terror spielt Hardcore-Punk mit leicht metall-lastigem Sound.
Aufgrund einer Rückverletzung des Sängers spielte Terror wohl in leicht veränderter Besetzung, was der Sache aber keine Abbruch tat. Es war eine energie-geladene Hardcore Show, hat mir gut gefallen. Vielleicht sollte ich mir doch mal eine CD von denen kaufen.
Terror live auf dem Mair 1
Den Abschluss des Mair 1 bildeten Millencolin. Und obwohl ich ja gerne Punkrock - auch von schwedischen Bands - höre, konnte ich mit Millencolin noch nie was anfangen. Und, was soll ich sagen: das war auch hier live auf dem Mair 1 nicht anders. Drei Songs angeschaut und nach Hause gegangen. Das ist nichts für mich.

Das Mair 1 war auf jeden Fall auch 2015 ein gutes Festival, was einen Besuch wert war. Ich freue mich schon auf das Mair 1 2016!

Samstag, 27. Juni 2015

Mair 1 Festival 2015, 1. Tag - Strung Out, Agnostic Front, Breakdown of Sanity

Am 26. und 27.6.2015 war es wieder so weit: das Mair 1 Festival 2015 findet statt, an der bekannten Location in der Nähe von Montabaur.

Das Lineup ist wie in den Jahren zuvor schon ziemlich gut, was die Musikrichtungen Hardcore, Metalcore und Punk betrifft, auch wenn mich dieses Jahr die Bands nicht sooo ansprechen (im Vergleich zu einigen anderen Lineups aus den Vorjahren).

Nichts desto trotz ging es auch dieses Jahr wieder zum Mair 1 - ist ja in der Nähe, die Atmosphäre ist immer entspannt und die Organisation sehr gut.

Als ich am 1. Festivaltag ankam, spielten gerade die Emil Bulls auf der großen Bühne ihre letzten beiden Stücke. Klang ganz ok, mehr kann ich dazu nicht sagen.

Danach kamen Breakdown of Sanity auf die kleinere Bühne. Diese Band war mit bis dato völlig unbekannt, aber nach den ersten paar Takten war klar: das ist wohl Metalcore mit Hang zum düsteren - also musikalisch nicht ganz so mein Fall.
Die Show war gut und kam auch beim Publikum sehr gut an, aber leider war der Sound bei der ersten paar Liedern schlecht - außer der wummernden Double-Bass (der in allen Liedern ausgiebig zum Einsatz kommt) und dem Gesang war nicht viel zu hören. Zum Glück wurde das nach drei oder vier Liedern besser und der Sound war so, wie er sein sollte.

Danach waren dann die Bands an der Reihe, weswegen ich heute eigentlich zum Mair 1 gefahren war: Agnostic Front und Strung Out.

Pünktlich betraten Agnostic Front um 20.50 Uhr die große Bühne. Das Urgestein der New York Hardcore fackelte nicht lange und gab ab dem ersten Song Vollgas, so dass der Funke auch direkt auf's Publikum übersprang.
Und wer schon, wie Agnostic Front, seit übr 30 Jahren im Geschäft ist, der weiß natürlich auch, wie man eine gute Hardcore Show aufzieht und spielt. Und so war dieses Konzert eine kurzweilig, unterhaltsame Stunde. Der letzte Song war nochmal ein Highlight, da Agnostic Front den Ramons Song "Blitzkrieg Bop" brachten. Sehr schön.
Agonstic Front auf der Bühne
Dann waren Strung Out auf der kleineren Bühne an der Reihe. Von denen habe ich nur eine CD, das bereits 1992 erschienene Album "Suburban Teenage Wasteland Blues". Und da Strung Out in deren Songs doch trotz kalifornischem Punk Rock einen Hang zum dunklen und melancholischen hat war ich zugegebener Maßen etwas skeptisch, wie die Show werden würde.

Um es direkt zu sagen: die Skepsis war völlig unbegründet. Strung Out spielten ein mitreisendes, Enerigie-gelandenes Konzert vom ersten bis zum letzten Takt. Sehr guter Sound und auch der von der Band oft eingesetzte mehrstimmige Gesang kam live klar und druckvoll rüber.
Unter Strich muss ich dann auch sagen: Strung Out war mit Sicherheit einer der besten Auftritte, die ich bisher auf dem Mair 1 gesehen habe. Super gut.

Strung Out auf der Bühne
Den Abschluss machten dann die Guana Apes. Musikalisch so wie so nicht mein Fall und ehrlich gesagt fand ich die Band aus der Sparte "Rock mit Hang zum Mainstream" auf dem Mair 1 auch ein wenig deplatziert. Zwei Songs habe ich mir angeschaut, bevor ich gefahren bin. Publikum war aber definitiv noch genug da, also an Popularität mangelt es den Guana Apes scheinbar nicht.

Auch wenn ich ja "nur" drei Bands ganz gesehen hatte war's ein guter 1. Tag auf dem Mair 1 2015. Schon alleine wegen des tollen Konzerts von Strung Out.