erster Entwurf fuer simples Umfrage-Web-Interface

This commit is contained in:
lars 2012-08-10 23:57:38 +00:00
parent b4c3a84104
commit 59ccb5a87c
11 changed files with 465 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View file

@ -0,0 +1,85 @@
body {
font-size: 12px;
color: #464646;
}
h1, h2, h3 {
margin: 0;
font-weight: normal;
color: #549900;
}
h1 {
font-size: 250%;
margin-bottom: 10px;
}
h2 { font-size: 140%; }
p, ul, ol {
margin-top: 0;
line-height: 240%;
text-align: justify;
}
a { color: #D99821; }
a:hover { text-decoration: none; }
a img {
border: none;
}
table.options label {
font-style: italic;
}
table.options td {
padding: 5px;
vertical-align: top;
}
table.options td ul {
padding: 2px 20px 2px 20px;
}
table.options td ul li {
margin-left: 2px;
}
table.options td.answer {
text-align: center;
}
ul.radio li {
list-style-type: none;
}
table.options tr.option_title {
font-size: 150%;
font-weight:bold;
}
table.options tr.option_image td {
text-align: center;
}
table.options td.column0 {
background-color: #c5c5ff;
}
table.options td.column1 {
background-color: #c5ffc5;
}
input.submit {
font-size: 14px;
font-weight:bold;
display: inline-block;
padding:0px;
padding-bottom:5px;
padding-left:10px;
margin-left:10px;
width:30%;
line-height:100%;
}

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
DEFINITION_OPTIONS = (
(u"Grüner Platz", "http://stadtgestalten.org/umfrage/kirchenplatz2012/media/option1.png",
(u"verbreiterte Gehwege", u"Wochenmarkt auf Ostseite", u"Grüne Platzfläche im Westen (keine Nutzung der Freiflächen für Stände, Außengastronomie, etc. möglich)", u"Grünes Kirchenumfeld", u"51 PKW-Stellplätze auf dem Platz (Ostseite) und an der Südseite", u"Bushaltestelle im Bestand", u"Taxistand vor Kirchenplatz Nr. 13", u"Baumpflanzungen auf dem Platz und straßenbegleitend an den Gehwegen")),
(u"Karo", "http://stadtgestalten.org/umfrage/kirchenplatz2012/media/option2.png",
(u"verbreiterte Gehwege", u"Wochenmarkt auf Ostseite", u"gepflasterte Platzfläche im Westen (Nutzung der Freiflächen für Stände, Außengastronomie, etc. möglich)", u"Kirchenumfeld als befestigte Fläche und Grün", u"30 PKW-Stellplätze auf dem Platz (Ostseite)", u"Bushaltestelle auf der Westseite (gegenüber Mühlenstraße)", u"Taxistand vor Kirchenplatz Nr. 1-2", u"Baumpflanzungen auf dem Platz und straßenbegleitend an den Gehwegen")),
(u"Marktplatz mit Baumhain", "http://stadtgestalten.org/umfrage/kirchenplatz2012/media/option3.png",
(u"verbreiterte Gehwege", u"Wochenmarkt auf Ostseite (ingeschränkte Fläche wg. Baumpflanzungen)", u"teilweise gepflasterte Platzfläche im Westen (Nutzung der Freiflächen (nicht unter den Bäumen) für Stände, Außengastronomie, etc. möglich) sowie starke Baumpflanzungen", u"Kirchenumfeld als befestigte Fläche", u"44 PKW-Stellplätze straßenbegleitend auf der Nord- und Südseite", u"Bushaltestelle auf der Westseite (gegenüber Mühlenstraße)", u"Taxistand vor Kirchenplatz Nr. 13", u"Baumpflanzungen auf dem Platz und straßenbegleitend an den Gehwegen")),
(u"Befestigter Platz", "http://stadtgestalten.org/umfrage/kirchenplatz2012/media/option4.png",
(u"verbreiterte Gehwege", u"Wochenmarkt auf Ostseite (ingeschränkte Fläche wg. Baumpflanzungen)", u"teilweise gepflasterte Platzfläche im Westen (Nutzung der Freiflächen für Stände, Außengastronomie, etc. möglich) sowie starke Baumpflanzungen", u"Kirchenumfeld als befestigte Fläche", u"39 PKW-Stellplätze straßenbegleitend auf der Nord- und Südseite", u"Bushaltestelle im Bestand", u"Taxistand vor Kirchenplatz Nr. 13", u"Baumpflanzungen auf dem Platz und auf der West- und Ostseite straßenbegleitend an den Gehwegen")))
DEFINITION_QUALITY_RANGES = {
"mehr_weniger": (
(u"++", u"++ Ja, trifft voll zu"),
(u"+", u"+ Ja, mit Abstrichen"),
(u"-", u"- Kaum bis wenig"),
(u"--", u"-- Überhaupt nicht")),
"baeume": (
(u"++", u"++ Lineare Anordnung (Baumreihen)"),
(u"--", u"-- Beliebige Anordnung in Einzelbäume, Baumgruppen, Baumhainen")),
"gesamt": (
(u"++", u"++ Sehr gute Variante, berücksichtigt so gut wie alle Anforderungen"),
(u"+", u"+ In Teilen gut gelungene Variante"),
(u"-", u"- Wenig attraktiv, berücksichtigt zu wenige Anforderungen"),
(u"--", u"-- Unattraktiv, unpassende Variante")),
}
DEFINITION_QUESTIONS = (
(u"Frage 1: Lädt der Platz durch seine geplante Neugestaltung insgesamt zum Flanieren/Verweilen/Aufenthalt ein?", "mehr_weniger"),
(u"Frage 2: Wird die Funktion des Platzes als Orts- und Versorgungsmittelpunkt durch den Entwurf gestärkt?", "mehr_weniger"),
(u"Frage 3: Wird die Kirche als Bauwerk mit Portal und grünem Umfeld durch den Entwurf entsprechend betont?", "mehr_weniger"),
(u"Frage 4: Berücksichtigt die Variante ausreichend Fläche/ Teilbereiche für temporäre Nutzungen (Markt, Festivitäten)?", "mehr_weniger"),
(u"Frage 5: Sind die Hauptwege für Fußgänger über den Platz erkennbar und nutzbar (u.a. Verbindung Mühlenstraße-Kirchenstraße)?", "mehr_weniger"),
(u"Frage 6: Steht die Anordnung der Stellplätze im Einklang mit der Neugestaltung des Platzes und wird die Anzahl als ausreichend eingeschätzt?", "mehr_weniger"),
(u"Frage 7: Sind Bushaltestelle und Taxistand für die Benutzer gut und aus Sicht der Platzgestaltung attraktiv angeordnet?", "mehr_weniger"),
(u"Frage 8: Ist das zugrunde gelegte Begrünungskonzept gestalterisch attraktiv (Straßenraum, Kirchenumfeld)?", "mehr_weniger"),
(u"Frage 9: Sind ausreichend Baumanpflanzungen an der Straße sowie im Kirchenumfeld berücksichtigt worden?", "mehr_weniger"),
(u"Frage 10: Ist eine lineare Anordnung der Bäume (Baumreihen) im Kirchenumfeld oder eine beliebige Anordnung von Einzelbäumen neben Baumgruppen, Baumhainen gestalterisch und funktional besser?", "baeume"),
(u"Abschließende Gesamtbewertung der Planungsvarianten", "gesamt"),
)

View file

@ -0,0 +1,183 @@
#!/usr/bin/env python2.6
# -*- coding: utf-8 -*-
import os
# the basedir is the parent dir of the location of this script
BASE_DIR = os.path.dirname(os.path.abspath(os.path.join(__file__, os.path.pardir)))
# add the project directory to the python search path
import sys
sys.path.insert(0, os.path.join(BASE_DIR, "src"))
from dataset import DEFINITION_OPTIONS, DEFINITION_QUALITY_RANGES, \
DEFINITION_QUESTIONS
import ConfigParser
import datetime
import uuid
import sqlobject
import bobo
import genshi
import genshi.template
import genshi.filters
CONFIG_FILE = os.path.join(BASE_DIR, "umfrage.conf")
config = ConfigParser.SafeConfigParser()
config.read(CONFIG_FILE)
db_uri = config.get("database", "uri")
sqlobject.sqlhub.processConnection = sqlobject.connectionForURI(db_uri)
loader = genshi.template.TemplateLoader(os.path.join(BASE_DIR, 'templates'), auto_reload=False)
BASE_DICT = {
"base_url": "/umfrage/kirchenplatz2012/", # the trailing slash is necessary
"errors": {},
}
class Session(sqlobject.SQLObject):
created = sqlobject.DateTimeCol(default=sqlobject.DateTimeCol.now)
name = sqlobject.UnicodeCol(default=lambda: uuid.uuid4().hex)
class Question(sqlobject.SQLObject):
text = sqlobject.UnicodeCol()
weight = sqlobject.IntCol()
quality_levels = sqlobject.PickleCol()
class Option(sqlobject.SQLObject):
title = sqlobject.UnicodeCol()
text = sqlobject.UnicodeCol()
image = sqlobject.UnicodeCol()
weight = sqlobject.IntCol()
class Answer(sqlobject.SQLObject):
text = sqlobject.UnicodeCol(default="")
quality = sqlobject.UnicodeCol(default="")
question = sqlobject.ForeignKey("Question")
option = sqlobject.ForeignKey("Option")
session = sqlobject.ForeignKey("Session")
def get_default_values(**kwargs):
value_dict = dict(BASE_DICT)
# we always need "options" (e.g. on frontpage)
value_dict["options"] = list(Option.select())
value_dict["options"].sort(key=lambda item: item.weight)
for key, value in kwargs.items():
value_dict[key] = value
return value_dict
def render(filename, input_data=None, **values):
stream = loader.load(filename).generate(**values)
if not input_data is None:
stream |= genshi.filters.HTMLFormFiller(data=input_data)
return stream.render("html", doctype="html")
def get_previous_question(question):
questions = Question.select().orderBy("-weight")
for one_q in questions:
if one_q.weight < question.weight:
return one_q
return None
def get_next_question(question):
questions = Question.select().orderBy("weight")
for one_q in questions:
if one_q.weight > question.weight:
return one_q
return None
@bobo.query('/')
def show_question(session_id=None, question_id=None, go_backward=False,
**kwargs):
params = get_default_values()
session = None
if session_id:
session = list(Session.selectBy(name=session_id))
if session:
session = session[0]
if not session:
session = Session()
params["session"] = session
return render("start.html", **params)
question = None
if question_id:
try:
question = Question.get(int(question_id))
except ValueError:
pass
# any new input values? (update "Answer" objects)
target_question = None
if question:
for option in Option.select():
opt_params = {}
for key in ("text", "quality"):
dict_key = "option_%s_%s" % (option.id, key)
if dict_key in kwargs:
opt_params[key] = kwargs[dict_key]
# at least one item was found
if opt_params:
answer = Answer.selectBy(session=session, question=question,
option=option)
if not answer:
# create new one
answer = Answer(session=session, question=question,
option=option)
else:
answer = answer[0]
# update one value
for key in opt_params:
setattr(answer, key, opt_params[key])
if go_backward:
target_question = get_previous_question(question)
else:
target_question = get_next_question(question)
if not target_question:
# special case: we are finished
return render("summary.html", session=session)
if not target_question:
target_question = Question.select().orderBy("weight")[0]
# populate the next question
input_data = {}
for answer in Answer.selectBy(session=session, question=target_question):
for key in ("text", "quality"):
input_data["option_%s_%s" % (answer.option.id, key)] = \
getattr(answer, key)
params.update({"session": session,
"question": target_question,
"next_question": get_next_question(target_question),
"previous_question": get_previous_question(target_question),
})
return render("question.html", input_data=input_data, **params)
@bobo.query('')
def redirect_startpage():
return bobo.redirect(BASE_DICT["base_url"])
# initialize the tables (do it only once!)
if True:
tables = (Session, Question, Option, Answer)
# drop all
drop_tables = list(tables)
drop_tables.reverse()
for table in drop_tables:
if table.tableExists():
table.dropTable()
for table in tables:
table.createTable()
for index, (title, image_url, lines) in enumerate(DEFINITION_OPTIONS):
Option(title=title, image=image_url,
text=os.linesep.join(lines), weight=index)
for index, (text, quality_levels) in enumerate(DEFINITION_QUESTIONS):
Question(text=text, weight=index, quality_levels=DEFINITION_QUALITY_RANGES[quality_levels])
# this application should be usable with mod_wsgi
application = bobo.Application(bobo_resources=__name__)

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/" py:strip="">
<py:match path="head" once="true">
<head py:attrs="select('@*')">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title py:with="title = list(select('title/text()'))">
Umfrage<py:if test="title">: ${title}</py:if>
</title>
<link rel="stylesheet" href="${base_url}media/style.css" type="text/css" />
${select('*[local-name()!="title"]')}
</head>
</py:match>
<tr py:match="trRenderOptionTitle" class="option_title">
<th py:for="column, option in enumerate(options)" class="column${column % 2}">${option.title}</th>
</tr>
<tr py:match="trRenderOptionImage" class="option_image">
<td py:for="column, option in enumerate(options)" class="column${column % 2}"><img src="${option.image}" width="${1024/len(options)}" /></td>
</tr>
<tr py:match="trRenderOptionText" class="option_text">
<td py:for="column, option in enumerate(options)" class="column${column % 2}"><ul><li py:for="line in option.text.splitlines()">${line}</li></ul></td>
</tr>
<py:match path="body" once="true">
<body py:attrs="select('@*')">
<div id="content">
${select('*|text()')}
</div>
</body>
</py:match>
</html>

View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:py="http://genshi.edgewall.org/"
lang="de">
<xi:include href="layout.html" />
<head/>
<body>
<h1>${question.text}</h1>
<form action="${base_url}" method="POST">
<input type="hidden" name="session_id" value="${session.name}" />
<input type="hidden" name="question_id" value="${question.id}" />
<table class="options">
<colgroup><col py:for="option in options" width="${100/len(options)}%" /></colgroup>
<trRenderOptionTitle />
<tr>
<td py:for="column, option in enumerate(options)" class="answer column${column % 2}">
<label for="option_${option.id}_text">Bewertung:</label><br/>
<textarea rows="5" style="width:90%" id="option_${option.id}_text" name="option_${option.id}_text"></textarea>
<br/>
<ul class="radio">
<li py:for="(key, text) in question.quality_levels">
<input type="radio" name="option_${option.id}_quality" value="${key}">${unicode(text)}</input><br/>
</li>
</ul>
</td>
</tr>
<tr>
<td colspan="${len(options) / 2}" style="text-align:left">
<input py:if="previous_question" type="submit" class="submit" name="go_backward" value="zur&uuml;ck" /></td>
<td colspan="${len(options) - len(options) / 2}" style="text-align:right">
<input py:if="next_question" type="submit" class="submit" name="go_forward" value="weiter" /></td>
</tr>
<tr><td colspan="${len(options)}"><hr/></td></tr>
<trRenderOptionImage />
<trRenderOptionText />
</table>
</form>
</body>
</html>

View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:py="http://genshi.edgewall.org/"
lang="de">
<xi:include href="layout.html" />
<head/>
<body>
<h1>B&uuml;rgerbeteiligung: Neugestaltung des Kirchplatzes in Warnem&uuml;nde</h1>
<p style="border-width:1px; background-color:lightgreen; border-style:groove; padding:10px">Die Stadt Rostock m&ouml;chte die
<a href="http://rathaus.rostock.de/sixcms/detail.php?id=37572"
title="Offizieller Aufruf zur B&uuml;rgerbeteilung">Meinung Ihrer B&uuml;rgerinnen und B&uuml;rger erfragen</a>.
Dazu bietet sie leider nur ein etwas <a href="http://rathaus.rostock.de/sixcms/media.php/1068/onlinefragebogen-wkp.pdf"
title="Offizielles Umfrage-Formular als PDF">unhandliches PDF-Formular an</a>.<br/>
Das folgende web-basierte Formular soll die gew&uuml;nschte Meinungsbekundung dagegen
erleichtern und somit die Breite der Umfrage vergr&ouml;&szlig;ern.
<br/><br/>
<form method="POST">
<input type="hidden" name="session_id" value="${session.name}" />
<input type="submit" class="submit" value="Umfrage starten ..." />
</form>
</p>
<!--
<table class="options">
<colgroup><col py:for="option in options" width="${100/len(options)}%" /></colgroup>
<trRenderOptionTitle />
<trRenderOptionImage />
<trRenderOptionText />
</table>
-->
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:py="http://genshi.edgewall.org/"
lang="de">
<xi:include href="layout.html" />
<head/>
<body>
<h1>Zusammenfassung</h1>
Der folgende Text fasst Ihre Eingaben zusammen. Sie k&ouml;nnen ihn nun nach
Belieben anpassen. Anschlie&szlig;end haben Sie zwei M&ouml;glichkeiten:
<ul>
<li>&Uuml;bertragen Sie den Text in Ihr gewohntes Mailprogramm und senden Sie die Mail an stadtplanung@rostock.de.</li>
ODER
<li>Geben Sie unterhalb des Textes Ihre Mailadresse an und klicken Sie auf <i>Senden</i>. Dadurch wird die Mail sofort ihn Ihrem Namen an die obige Mailadresse der Stadtverwaltung verschickt.</li>
</ul>
Falls Sie Anregungen oder Kritik bez&uuml;glich des Planungsverfahrens
&auml;u&szlig;ern wollen, dann f&uuml;gen Sie diese bitte am Ende des Textes an.
(Im Formular der Stadt war daf&uuml;r kein Eingabefeld vorgesehen.)
Die Stadt wird f&uuml;r Ihre Eingabe sicherlich dankbar sein!
</body>
</html>