plakateapp

Check-in [f0f3188ff4]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Rewrite in Go
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256: f0f3188ff4120bede33d615a6a607babf4e01948d3abe72cffaed80a8a3ce8e7
User & Date: git@tuxproject.de 2019-04-04 15:55:47
Context
2019-04-04
16:04
README passt jetzt check-in: ede61b8575 user: git@tuxproject.de tags: master, trunk
15:55
Rewrite in Go check-in: f0f3188ff4 user: git@tuxproject.de tags: master, trunk
2019-04-03
09:13
Updates (leaflet 1.4, jquery 3.3, gevent 1.4) check-in: 52ad3de2e9 user: git@tuxproject.de tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to README.md.

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

## Hä? Haste mal ein Beispiel?

Klar. Nehmen wir an, ihr wollt für irgendeinen Verein eure Stadt vollplakatieren, wollt aber den Überblick behalten, wo überall Plakate hängen, um sie später wieder entfernen zu können. Genau diesem Zweck dient diese Anwendung.

## Technik

Ihr braucht auf eurem Server nur Python 3 und die Module `Flask` und `gevent`, alles Weitere lädt die Website automatisch:

    pip install flask gevent
    python ./server.py

Die Karte ist anschließend über den Port 6090 (einstellbar direkt in der Datei `server.py`) erreichbar. Unter `/manageplakate` gibt es auch eine einfache Liste aller eingetragenen Plakate zum schnellen Löschen. Das Großteil des UIs wurde mit [Leafjet.js](http://leafletjs.com/) programmiert.

## Urheberrecht? Quatsch.

Die Plakateapp wurde ursprünglich für den Kommunalwahlkampf in Niedersachsen 2016 von [@tux0r](https://twitter.com/tux0r) hektisch (also eher zweckmäßig als gut) für die Piratenpartei Braunschweig programmiert (weshalb der Standard für die Position mitten in Braunschweig liegt, aber das könnt ihr im Javascript-Code ändern). All dies hier steht unter der [WTFPL v2](http://www.wtfpl.net/txt/copying/), ihr dürft also gern damit wegrennen und es teuer verscherbeln. Viel Spaß!







|

|
|

|



|
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

## Hä? Haste mal ein Beispiel?

Klar. Nehmen wir an, ihr wollt für irgendeinen Verein eure Stadt vollplakatieren, wollt aber den Überblick behalten, wo überall Plakate hängen, um sie später wieder entfernen zu können. Genau diesem Zweck dient diese Anwendung.

## Technik

Ihr braucht auf eurem Server nur Go, alles Weitere passiert automatisch:

    go get github.com/dertuxmalwieder/plakateapp
    plakateapp

Die Karte ist anschließend über den Port 6090 (einstellbar direkt in der Datei `plakateapp.go`) erreichbar. Unter `euerserver:6090/manageplakate` gibt es auch eine einfache Liste aller eingetragenen Plakate zum schnellen Löschen. Das Großteil des UIs wurde mit [Leafjet.js](http://leafletjs.com/) programmiert.

## Urheberrecht? Quatsch.

Die Plakateapp wurde ursprünglich für den Kommunalwahlkampf in Niedersachsen 2016 von [@tux0r](https://twitter.com/tux0r) hektisch (also eher zweckmäßig als gut) für die Piratenpartei Braunschweig programmiert (weshalb der Standard für die Position mitten in Braunschweig liegt, aber das könnt ihr im Javascript-Code ändern). 2019 wurde sie in Go neu implementiert. All dies hier steht unter der [WTFPL v2](http://www.wtfpl.net/txt/copying/), ihr dürft also gern damit wegrennen und es teuer verscherbeln. Viel Spaß!

Added src/plakateapp.go.























































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main

import (
    "encoding/json"
    "fmt"
    "html/template"
    "log"
    "net/http"

    // Bibliotheken aus GitHub:
    _ "github.com/mattn/go-sqlite3"
    "github.com/jmoiron/sqlx"
    "github.com/gorilla/mux"
)

type Plakat struct {
    // Mapping DB <-> Go-Datentypen:
    ID          int     `db:"id"`
    Latitude    float32 `db:"lat"`
    Longitude   float32 `db:"lon"`
}

func CheckError(err error) {
    if err != nil {
        panic(err)
    }
}

func FetchPlakate() []Plakat {
    db, err := sqlx.Open("sqlite3", "./plakate.db")
    CheckError(err)
    defer db.Close()
    
    // Liste erzeugen:
    plakate := []Plakat{}
    db.Select(&plakate, "SELECT * FROM plakate")
    
    return plakate
}
    
// ----------------------------------------
   
func HomeHandler(w http.ResponseWriter, r *http.Request) {
    // Startseite aufrufen:
    tmpl := template.Must(template.ParseFiles("templates/index.htm"))
    tmpl.Execute(w, "")
}

func ManagePlakateHandler(w http.ResponseWriter, r *http.Request) {
    // Datenbank aufrufen:
    plakate := FetchPlakate()
    
    // delete.htm mit Plakate-struct aufrufen:
    tmpl := template.Must(template.ParseFiles("templates/delete.htm"))
    tmpl.Execute(w, plakate)
}

func ListPlakateHandler(w http.ResponseWriter, r *http.Request) {
    // Datenbank aufrufen:
    plakate := FetchPlakate()
    
    // JSON-Objekt ausgeben:
    jsonobj, err := json.Marshal(plakate)
    CheckError(err)
    fmt.Fprintf(w, "%s", string(jsonobj))
}

func NeuesPlakatHandler(w http.ResponseWriter, r *http.Request) {
    // Plakat mit "lat" und "lon" erzeugen:
    lat := r.FormValue("lat")
    lon := r.FormValue("lon")
    
    // Datenbank aufrufen:
    db, err := sqlx.Open("sqlite3", "./plakate.db")
    CheckError(err)
    defer db.Close()
    
    stmt, err := db.Prepare("insert into plakate (lat, lon) values (?, ?)")
    CheckError(err)
    _, err = stmt.Exec(lat, lon)
    CheckError(err)
    
    fmt.Fprintf(w, "Plakat erfolgreich eingetragen!")
}

func DelHandler(w http.ResponseWriter, r *http.Request) {
    // Plakat mit vars["id"] löschen:
    vars := mux.Vars(r)

    // Datenbank aufrufen:
    db, err := sqlx.Open("sqlite3", "./plakate.db")
    CheckError(err)
    defer db.Close()
    
    // Löschen, falls möglich:
    stmt, err := db.Prepare("delete from plakate where id = ?")
    CheckError(err)
    _, err = stmt.Exec(vars["id"])
    CheckError(err)
    
    // Falls kein Fehler aufgetreten ist, umleiten auf /manageplakate:
    http.Redirect(w, r, "/manageplakate", http.StatusMovedPermanently)
}

func DelPostHandler(w http.ResponseWriter, r *http.Request) {
    data := r.FormValue("id")

    // Datenbank aufrufen:
    db, err := sqlx.Open("sqlite3", "./plakate.db")
    CheckError(err)
    defer db.Close()
    
    // Löschen, falls möglich:
    stmt, err := db.Prepare("delete from plakate where id = ?")
    CheckError(err)
    _, err = stmt.Exec(data)
    CheckError(err)
    
    // Falls kein Fehler aufgetreten ist, umleiten auf /manageplakate:
    http.Redirect(w, r, "/manageplakate", http.StatusMovedPermanently)
}

func main() {
    // Routing:
    r := mux.NewRouter()
    r.HandleFunc("/", HomeHandler)
    r.HandleFunc("/manageplakate", ManagePlakateHandler)
    r.HandleFunc("/listplakate", ListPlakateHandler).Methods("POST")
    r.HandleFunc("/neuesplakat", NeuesPlakatHandler).Methods("POST")
    r.HandleFunc("/del/{id:[0-9]+}", DelHandler)
    r.HandleFunc("/delpost", DelPostHandler).Methods("POST")
    
    // script.js auch aus /templates ausliefern:
    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./templates"))))
    http.Handle("/", r)

    // Server starten:
    log.Fatal(http.ListenAndServe(":6090", nil))
}

Deleted src/server.py.

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import sqlite3
import json
from flask import Flask, render_template, request, send_from_directory, redirect
from gevent.pywsgi import WSGIServer
from gevent import monkey

monkey.patch_all()

app = Flask(__name__)

PORT = 6090

@app.route('/')
def hauptseite():
    return render_template("index.htm")


@app.route("/manageplakate")
def manage_plakate():
    try:
        conn = sqlite3.connect("plakate.db")
        with conn:
            c = conn.cursor()
            c.execute("SELECT * FROM plakate")
            plakate = [dict(id=row[0], lat=row[1], lon=row[2]) for row in c.fetchall()]
            return render_template("delete.htm",plakate=plakate)
    except:
        return "err"


@app.route("/listplakate", methods=['POST'])
def list_plakate():
    try:
        conn = sqlite3.connect("plakate.db")
        with conn:
            c = conn.cursor()
            c.execute("SELECT * FROM plakate")
            plakate = [dict(id=row[0], lat=row[1], lon=row[2]) for row in c.fetchall()]
            return json.dumps(plakate)
    except:
        return "err"


@app.route("/neuesplakat", methods=['POST'])
def neues_plakat():
    try:
        latitude = request.form["lat"]
        longitude = request.form["lon"]

        conn = sqlite3.connect("plakate.db", detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
        with conn:
            c = conn.cursor()
            c.execute("INSERT INTO plakate (lat, lon) VALUES ({}, {});".format(float(latitude), float(longitude)))
            conn.commit()

        return "Plakat erfolgreich eingetragen!"
    except sqlite3.Error as e:
        return "Fehler! :-( {}".format(e.message)


@app.route("/del/<int:plakatid>", strict_slashes = False)
def del_plakat(plakatid):
    try:
        conn = sqlite3.connect("plakate.db", detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
        with conn:
            c = conn.cursor()
            c.execute("DELETE FROM plakate WHERE id={}".format(plakatid))
            conn.commit()
        return redirect("/manageplakate")
    except sqlite3.Error as e:
        return "Fehler! :-( {}".format(e.message)


@app.route("/delpost", methods=['POST'])
def delpost():
    try:
        plakatid = request.form["id"]
        conn = sqlite3.connect("plakate.db", detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
        with conn:
            c = conn.cursor()
            c.execute("DELETE FROM plakate WHERE id={}".format(plakatid))
            conn.commit()
        return "Geklappt."
    except sqlite3.Error as e:
        return "Fehler! :-( {}".format(e.message)


@app.route('/templates/<path:path>', strict_slashes = False)
def web_static(path):
    # Statische Templatedateien ausliefern
    return send_from_directory('templates', path)

http_server = WSGIServer(('0.0.0.0', PORT), app)
try:
    http_server.serve_forever()
except KeyboardInterrupt:
    print("Beende die Plakate-App...")
    if http_server.started:
        http_server.stop()
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






































































































































































































Changes to src/templates/delete.htm.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table>
<tr>
<th><b>Latitude</b></th>
<th><b>Longitude</b></th>
<th><b>Löschen</b></th>
</tr>
{% for plakat in plakate %}
<tr>
<td>{{ plakat.lat }}</td>
<td>{{ plakat.lon }}</td>
<td><a href="/del/{{ plakat.id }}">X</a></td>
</tr>
{% endfor %}
</table>






|

|
|
|

|

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table>
<tr>
<th><b>Latitude</b></th>
<th><b>Longitude</b></th>
<th><b>Löschen</b></th>
</tr>
{{range $quux, $Plakat := .}}
<tr>
<td>{{ $Plakat.Latitude }}</td>
<td>{{ $Plakat.Longitude }}</td>
<td><a href="/del/{{ $Plakat.ID }}">X</a></td>
</tr>
{{end}}
</table>

Changes to src/templates/index.htm.

5
6
7
8
9
10
11
12
13
14
15
16
17
18

        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css" />
        <script src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"></script>
        <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
        <script src="/templates/script.js"></script>
    </head>
    <body style="overflow:none">
        <div id="map" style="height:100%;width:100%;position:absolute;top:0px;left:0px;"></div>
    </body>
</html>








|






5
6
7
8
9
10
11
12
13
14
15
16
17
18

        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css" />
        <script src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"></script>
        <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
        <script src="/static/script.js"></script>
    </head>
    <body style="overflow:none">
        <div id="map" style="height:100%;width:100%;position:absolute;top:0px;left:0px;"></div>
    </body>
</html>

Changes to src/templates/script.js.

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
..
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
..
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
var plotlist;
var plotlayers=[];

function initmap() {
    // Karte initialisieren
    map = new L.Map('map');

    var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
    var osmAttrib='Karte von <a href="http://openstreetmap.org">OpenStreetMap</a>';
    var osm = new L.TileLayer(osmUrl, {minZoom: 12, maxZoom: 28, attribution: osmAttrib});

    // OSM anzeigen
    map.addLayer(osm);

    // bei Ortungserfolg Standort anzeigen:
    map.on('locationfound', function() {
................................................................................
    $.post(
      "/listplakate",
      {},
      function(data) {
          var json = JSON.parse(data);
          for (var i = 0; i < json.length; i++) {
              var plakat = json[i];
              var plakatlatlng = new L.LatLng(plakat.lat,plakat.lon)
              var marker = new L.Marker(plakatlatlng, {draggable:false})
.bindPopup("<input type='button' value='Plakat löschen' data-id='"+plakat.id+"' class='marker-delete-button'/>");

              marker.on("popupopen", onPopupOpen);
              map.addLayer(marker);
          }
      }
    );

................................................................................

    $(".marker-delete-button:visible").click(function () {
        $.post(
          "/delpost",
          { id: $(this).attr("data-id") },
          function(data) {
              // noop
	   }
        );
        map.removeLayer(tempMarker);
    });
}

$(document).ready(function() {
    initmap();
});







|
|







 







|

|







 







|








3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
..
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
..
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
var plotlist;
var plotlayers=[];

function initmap() {
    // Karte initialisieren
    map = new L.Map('map');

    var osmUrl='//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
    var osmAttrib='Karte von <a href="https://openstreetmap.org">OpenStreetMap</a>';
    var osm = new L.TileLayer(osmUrl, {minZoom: 12, maxZoom: 28, attribution: osmAttrib});

    // OSM anzeigen
    map.addLayer(osm);

    // bei Ortungserfolg Standort anzeigen:
    map.on('locationfound', function() {
................................................................................
    $.post(
      "/listplakate",
      {},
      function(data) {
          var json = JSON.parse(data);
          for (var i = 0; i < json.length; i++) {
              var plakat = json[i];
              var plakatlatlng = new L.LatLng(plakat.Latitude,plakat.Longitude)
              var marker = new L.Marker(plakatlatlng, {draggable:false})
.bindPopup("<input type='button' value='Plakat löschen' data-id='"+plakat.ID+"' class='marker-delete-button'/>");

              marker.on("popupopen", onPopupOpen);
              map.addLayer(marker);
          }
      }
    );

................................................................................

    $(".marker-delete-button:visible").click(function () {
        $.post(
          "/delpost",
          { id: $(this).attr("data-id") },
          function(data) {
              // noop
          }
        );
        map.removeLayer(tempMarker);
    });
}

$(document).ready(function() {
    initmap();
});