first draft of the poll website
This commit is contained in:
parent
87f7977806
commit
f2ef35b429
8 changed files with 259 additions and 0 deletions
5
wortschlucker/README
Normal file
5
wortschlucker/README
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Requirements:
|
||||||
|
apt-get install python-bobo python-genshi python-sqlobject
|
||||||
|
|
||||||
|
Start the server:
|
||||||
|
bobo2.6 wortschlucker.py
|
BIN
wortschlucker/database.sqlite
Normal file
BIN
wortschlucker/database.sqlite
Normal file
Binary file not shown.
7
wortschlucker/forms.py
Normal file
7
wortschlucker/forms.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import formencode
|
||||||
|
|
||||||
|
class PollForm(formencode.Schema):
|
||||||
|
author = formencode.validators.UnicodeString(strip=True, not_empty=True)
|
||||||
|
title = formencode.validators.UnicodeString(strip=True, not_empty=True)
|
||||||
|
description = formencode.validators.UnicodeString(strip=True, not_empty=True)
|
||||||
|
|
31
wortschlucker/templates/layout.html
Normal file
31
wortschlucker/templates/layout.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!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('@*')">
|
||||||
|
<title py:with="title = list(select('title/text()'))">
|
||||||
|
WortSchlucker<py:if test="title">: ${title}</py:if>
|
||||||
|
</title>
|
||||||
|
<link rel="stylesheet" href="${base_url}media/layout.css" type="text/css" />
|
||||||
|
<script type="text/javascript" src="${base_url}media/jquery.js"></script>
|
||||||
|
${select('*[local-name()!="title"]')}
|
||||||
|
</head>
|
||||||
|
</py:match>
|
||||||
|
|
||||||
|
<py:match path="body" once="true">
|
||||||
|
<body py:attrs="select('@*')">
|
||||||
|
<div id="header">
|
||||||
|
<a href="${base_url}"><img src="${base_url}media/logo.gif" width="201" height="79" alt="WortSchlucker logo" /></a>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
${select('*|text()')}
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
<p class="copyright">provided by <a href="http://senselab.org" title="Website of sense.lab e.V.">sense.lab e.V.</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</py:match>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
21
wortschlucker/templates/poll_details.html
Normal file
21
wortschlucker/templates/poll_details.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="layout.html" />
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Poll details</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Poll: ${poll.title}</h1>
|
||||||
|
<ul>
|
||||||
|
<li>Author: ${poll.author}</li>
|
||||||
|
<li>Created: ${poll.get_creation_time_string()}</li>
|
||||||
|
<li>Description: ${poll.description}</li>
|
||||||
|
<li>URL: <a href="${poll.get_url()}" title="link of this poll">${poll.get_url()}</a></li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
36
wortschlucker/templates/poll_new.html
Normal file
36
wortschlucker/templates/poll_new.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="layout.html" />
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Create a poll</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>New poll</h1>
|
||||||
|
<form action="" method="post">
|
||||||
|
<p>
|
||||||
|
<label for="author">Your name:</label>
|
||||||
|
<input type="text" id="author" name="author" />
|
||||||
|
<span py:if="'author' in errors" class="error">${errors.author}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="title">Poll name:</label>
|
||||||
|
<input type="text" id="title" name="title" />
|
||||||
|
<span py:if="'title' in errors" class="error">${errors.title}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="description">Description:</label>
|
||||||
|
<input type="text" id="description" name="description" />
|
||||||
|
<span py:if="'description' in errors" class="error">${errors.description}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<input type="submit" value="Create poll" />
|
||||||
|
<input type="submit" name="cancel" value="Cancel" />
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
27
wortschlucker/templates/polls.html
Normal file
27
wortschlucker/templates/polls.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
|
xmlns:py="http://genshi.edgewall.org/">
|
||||||
|
<xi:include href="layout.html" />
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Poll list</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>All polls</h1>
|
||||||
|
<p py:if="errors" class="error">Error:
|
||||||
|
<ul>
|
||||||
|
<li py:for="message in errors.values()">${message}</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<p><a href="${base_url}polls/new" title="Create a new poll">Create a new poll</a></p>
|
||||||
|
<ul py:if="polls">
|
||||||
|
<li py:for="poll in polls">
|
||||||
|
<a href="${poll.get_url()}" title="Details of poll ${poll.title}">
|
||||||
|
Poll: ${poll.title} (${poll.get_num_of_submissions()}/${poll.get_num_of_submitters()})
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
132
wortschlucker/wortschlucker.py
Executable file
132
wortschlucker/wortschlucker.py
Executable file
|
@ -0,0 +1,132 @@
|
||||||
|
#!/usr/bin/env python2.6
|
||||||
|
|
||||||
|
# add the current directory to the python search path
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, "./")
|
||||||
|
|
||||||
|
import forms
|
||||||
|
import sqlobject
|
||||||
|
import bobo
|
||||||
|
from genshi.template import TemplateLoader
|
||||||
|
import genshi
|
||||||
|
import formencode
|
||||||
|
import uuid
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
db_filename = "database.sqlite"
|
||||||
|
db_filename = os.path.abspath(db_filename)
|
||||||
|
database = sqlobject.connectionForURI("sqlite://" + db_filename)
|
||||||
|
sqlobject.sqlhub.processConnection = database
|
||||||
|
loader = TemplateLoader(os.path.join(os.path.dirname(__file__), 'templates'), auto_reload=True)
|
||||||
|
|
||||||
|
BASE_DICT = {
|
||||||
|
"base_url": "/", # the trailing slash is necessary
|
||||||
|
"errors": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
class WordSubmission(sqlobject.SQLObject):
|
||||||
|
submitter = sqlobject.StringCol()
|
||||||
|
content = sqlobject.StringCol()
|
||||||
|
poll_id = sqlobject.ForeignKey("Poll")
|
||||||
|
|
||||||
|
|
||||||
|
class Poll(sqlobject.SQLObject):
|
||||||
|
author = sqlobject.StringCol()
|
||||||
|
hash_key = sqlobject.StringCol()
|
||||||
|
title = sqlobject.StringCol()
|
||||||
|
description = sqlobject.StringCol()
|
||||||
|
timestamp_creation = sqlobject.DateTimeCol()
|
||||||
|
|
||||||
|
def get_num_of_submitters(self):
|
||||||
|
all_submitters = [submission.submitter for submission in WordSubmission.selectBy(poll_id=self.id)]
|
||||||
|
unique_submitters = []
|
||||||
|
for submitter in all_submitters:
|
||||||
|
if not submitter in unique_submitters:
|
||||||
|
unique_submitters.append(submitter)
|
||||||
|
return len(unique_submitters)
|
||||||
|
|
||||||
|
def get_num_of_submissions(self):
|
||||||
|
return WordSubmission.selectBy(poll_id=self.id).count()
|
||||||
|
|
||||||
|
def get_url(self):
|
||||||
|
return "%spolls/%s" % (BASE_DICT["base_url"], self.hash_key)
|
||||||
|
|
||||||
|
def get_creation_time_string(self):
|
||||||
|
return str(self.timestamp_creation)
|
||||||
|
|
||||||
|
def get_default_values(**kwargs):
|
||||||
|
value_dict = dict(BASE_DICT)
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
value_dict[key] = value
|
||||||
|
return value_dict
|
||||||
|
|
||||||
|
def render(filename, **values):
|
||||||
|
return loader.load(filename).generate(**values).render("html", doctype="html")
|
||||||
|
|
||||||
|
def get_poll_id(hash_key):
|
||||||
|
polls = Poll.selectBy(hash_key=hash_key)
|
||||||
|
if polls.count() == 1:
|
||||||
|
return polls[0].id
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_new_hash_key():
|
||||||
|
hash_key = uuid.uuid4().get_hex()
|
||||||
|
while not get_poll_id(hash_key) is None:
|
||||||
|
hash_key = uuid.uuid4().get_hex()
|
||||||
|
return hash_key
|
||||||
|
|
||||||
|
@bobo.query('/polls/new')
|
||||||
|
@bobo.query('/polls/new/:author/:title/:description')
|
||||||
|
def create_poll(cancel=False, author=None, title=None, description=None):
|
||||||
|
data = {"author": author, "title": title, "description": description}
|
||||||
|
value_dict = get_default_values()
|
||||||
|
if cancel:
|
||||||
|
return bobo.redirect(BASE_DICT["base_url"])
|
||||||
|
elif (author is None) and (title is None) and (description is None):
|
||||||
|
return render("poll_new.html", **value_dict)
|
||||||
|
else:
|
||||||
|
errors = {}
|
||||||
|
try:
|
||||||
|
data = forms.PollForm.to_python(data)
|
||||||
|
except formencode.Invalid, errors_packed:
|
||||||
|
errors = errors_packed.unpack_errors()
|
||||||
|
if errors:
|
||||||
|
value_dict["errors"] = errors
|
||||||
|
return render("poll_new.html", **value_dict)
|
||||||
|
else:
|
||||||
|
# create the new poll
|
||||||
|
hash_key = get_new_hash_key()
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
new_poll = Poll(hash_key=hash_key, timestamp_creation=now, **data)
|
||||||
|
return bobo.redirect(new_poll.get_url())
|
||||||
|
|
||||||
|
@bobo.query('')
|
||||||
|
@bobo.query('/')
|
||||||
|
@bobo.query('/polls')
|
||||||
|
@bobo.query('/polls/')
|
||||||
|
def show_polls():
|
||||||
|
value_dict = get_default_values()
|
||||||
|
value_dict["polls"] = Poll.select()
|
||||||
|
return render("polls.html", **value_dict)
|
||||||
|
|
||||||
|
@bobo.query('/polls/:poll_hash')
|
||||||
|
def show_one_poll(poll_hash=None):
|
||||||
|
poll_id = get_poll_id(poll_hash)
|
||||||
|
if not poll_id is None:
|
||||||
|
value_dict = get_default_values()
|
||||||
|
value_dict["poll"] = Poll.get(poll_id)
|
||||||
|
return render("poll_details.html", **value_dict)
|
||||||
|
else:
|
||||||
|
return bobo.redirect(BASE_DICT["base_url"])
|
||||||
|
|
||||||
|
|
||||||
|
if not Poll.tableExists():
|
||||||
|
#Poll.dropTable()
|
||||||
|
Poll.createTable()
|
||||||
|
if not WordSubmission.tableExists():
|
||||||
|
WordSubmission.createTable()
|
||||||
|
for poll in Poll.select():
|
||||||
|
print poll
|
||||||
|
|
Loading…
Reference in a new issue