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.