Skip to content

Commit 9dd75f9

Browse files
committed
Flask-based forum!
1 parent ff99b16 commit 9dd75f9

File tree

8 files changed

+185
-94
lines changed

8 files changed

+185
-94
lines changed

vagrant/Vagrantfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ VAGRANTFILE_API_VERSION = "2"
55

66
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
77
config.vm.provision "shell", path: "pg_config.sh"
8-
# config.vm.box = "hashicorp/precise32"
98
config.vm.box = "ubuntu/trusty32"
109
config.vm.network "forwarded_port", guest: 8000, host: 8000
1110
config.vm.network "forwarded_port", guest: 8080, host: 8080

vagrant/forum/forum.py

Lines changed: 26 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
#
2-
# DB Forum - a buggy web forum server backed by a good database
3-
#
1+
#!/usr/bin/env python3
2+
#
3+
# A buggy web service in need of a database.
44

5-
# The forumdb module is where the database interface code goes.
6-
import forumdb
5+
import datetime
6+
from flask import Flask, request, redirect, url_for
77

8-
# Other modules used to run a web server.
9-
import cgi
10-
from wsgiref.simple_server import make_server
11-
from wsgiref import util
8+
from forumdb import get_posts, add_post
9+
10+
app = Flask(__name__)
1211

1312
# HTML template for the forum page
1413
HTML_WRAP = '''\
@@ -21,14 +20,14 @@
2120
textarea { width: 400px; height: 100px; }
2221
div.post { border: 1px solid #999;
2322
padding: 10px 10px;
24-
margin: 10px 20%%; }
23+
margin: 10px 20%%; }
2524
hr.postbound { width: 50%%; }
2625
em.date { color: #999 }
2726
</style>
2827
</head>
2928
<body>
3029
<h1>DB Forum</h1>
31-
<form method=post action="/post">
30+
<form method=post>
3231
<div><textarea id="content" name="content"></textarea></div>
3332
<div><button id="go" type="submit">Post message</button></div>
3433
</form>
@@ -40,68 +39,27 @@
4039

4140
# HTML template for an individual comment
4241
POST = '''\
43-
<div class=post><em class=date>%(time)s</em><br>%(content)s</div>
42+
<div class=post><em class=date>%s</em><br>%s</div>
4443
'''
4544

46-
## Request handler for main page
47-
def View(env, resp):
48-
'''View is the 'main page' of the forum.
49-
50-
It displays the submission form and the previously posted messages.
51-
'''
52-
# get posts from database
53-
posts = forumdb.GetAllPosts()
54-
# send results
55-
headers = [('Content-type', 'text/html')]
56-
resp('200 OK', headers)
57-
return [HTML_WRAP % ''.join(POST % p for p in posts)]
5845

59-
## Request handler for posting - inserts to database
60-
def Post(env, resp):
61-
'''Post handles a submission of the forum's form.
62-
63-
The message the user posted is saved in the database, then it sends a 302
64-
Redirect back to the main page so the user can see their new post.
65-
'''
66-
# Get post content
67-
input = env['wsgi.input']
68-
length = int(env.get('CONTENT_LENGTH', 0))
69-
# If length is zero, post is empty - don't save it.
70-
if length > 0:
71-
postdata = input.read(length)
72-
fields = cgi.parse_qs(postdata)
73-
content = fields['content'][0]
74-
# If the post is just whitespace, don't save it.
75-
content = content.strip()
76-
if content:
77-
# Save it in the database
78-
forumdb.AddPost(content)
79-
# 302 redirect back to the main page
80-
headers = [('Location', '/'),
81-
('Content-type', 'text/plain')]
82-
resp('302 REDIRECT', headers)
83-
return ['Redirecting']
46+
@app.route('/', methods=['GET'])
47+
def main():
48+
'''Main page of the forum.'''
49+
posts = "".join(POST % (date, text) for text, date in get_posts())
50+
html = HTML_WRAP % posts
51+
return html
8452

85-
## Dispatch table - maps URL prefixes to request handlers
86-
DISPATCH = {'': View,
87-
'post': Post,
88-
}
8953

90-
## Dispatcher forwards requests according to the DISPATCH table.
91-
def Dispatcher(env, resp):
92-
'''Send requests to handlers based on the first path component.'''
93-
page = util.shift_path_info(env)
94-
if page in DISPATCH:
95-
return DISPATCH[page](env, resp)
96-
else:
97-
status = '404 Not Found'
98-
headers = [('Content-type', 'text/plain')]
99-
resp(status, headers)
100-
return ['Not Found: ' + page]
54+
@app.route('/', methods=['POST'])
55+
def post():
56+
'''New post submission.'''
57+
message = request.form['content']
58+
add_post(message)
59+
return redirect(url_for('main'))
10160

10261

103-
# Run this bad server only on localhost!
104-
httpd = make_server('', 8000, Dispatcher)
105-
print "Serving HTTP on port 8000..."
106-
httpd.serve_forever()
62+
if __name__ == '__main__':
63+
app.run(host='0.0.0.0', port=8000)
64+
app.run(debug=True)
10765

vagrant/forum/forumdb.py

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,15 @@
1-
#
2-
# Database access functions for the web forum.
3-
#
1+
# "Database code" for the DB Forum.
42

5-
import time
3+
import datetime
64

7-
## Database connection
8-
DB = []
5+
POSTS = [("This is the first post.", datetime.datetime.now())]
96

10-
## Get posts from database.
11-
def GetAllPosts():
12-
'''Get all the posts from the database, sorted with the newest first.
7+
def get_posts():
8+
"""Return all posts from the 'database', most recent first."""
9+
return reversed(POSTS)
1310

14-
Returns:
15-
A list of dictionaries, where each dictionary has a 'content' key
16-
pointing to the post content, and 'time' key pointing to the time
17-
it was posted.
18-
'''
19-
posts = [{'content': str(row[1]), 'time': str(row[0])} for row in DB]
20-
posts.sort(key=lambda row: row['time'], reverse=True)
21-
return posts
11+
def add_post(content):
12+
"""Add a post to the 'database' with the current timestamp."""
13+
POSTS.append((content, datetime.datetime.now()))
2214

23-
## Add a post to the database.
24-
def AddPost(content):
25-
'''Add a new post to the database.
2615

27-
Args:
28-
content: The text content of the new post.
29-
'''
30-
t = time.strftime('%c', time.localtime())
31-
DB.append((t, content))

vagrant/forum/solution/forum.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
#
3+
# A buggy web service in need of a database.
4+
5+
import datetime
6+
from flask import Flask, request, redirect, url_for
7+
8+
from forumdb_initial import get_posts, add_post
9+
10+
app = Flask(__name__)
11+
12+
# HTML template for the forum page
13+
HTML_WRAP = '''\
14+
<!DOCTYPE html>
15+
<html>
16+
<head>
17+
<title>DB Forum</title>
18+
<style>
19+
h1, form { text-align: center; }
20+
textarea { width: 400px; height: 100px; }
21+
div.post { border: 1px solid #999;
22+
padding: 10px 10px;
23+
margin: 10px 20%%; }
24+
hr.postbound { width: 50%%; }
25+
em.date { color: #999 }
26+
</style>
27+
</head>
28+
<body>
29+
<h1>DB Forum</h1>
30+
<form method=post>
31+
<div><textarea id="content" name="content"></textarea></div>
32+
<div><button id="go" type="submit">Post message</button></div>
33+
</form>
34+
<!-- post content will go here -->
35+
%s
36+
</body>
37+
</html>
38+
'''
39+
40+
# HTML template for an individual comment
41+
POST = '''\
42+
<div class=post><em class=date>%s</em><br>%s</div>
43+
'''
44+
45+
46+
@app.route('/', methods=['GET'])
47+
def main():
48+
'''Main page of the forum.'''
49+
posts = "".join(POST % (date, text) for text, date in get_posts())
50+
html = HTML_WRAP % posts
51+
return html
52+
53+
54+
@app.route('/', methods=['POST'])
55+
def post():
56+
'''New post submission.'''
57+
message = request.form['content']
58+
add_post(message)
59+
return redirect(url_for('main'))
60+
61+
62+
if __name__ == '__main__':
63+
app.run(host='0.0.0.0', port=8000)
64+
app.run(debug=True)
65+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# "Database code" for the DB Forum.
2+
3+
import datetime
4+
5+
POSTS = [("This is the first post.", datetime.datetime.now())]
6+
7+
def get_posts():
8+
"""Return all posts from the 'database', most recent first."""
9+
return reversed(POSTS)
10+
11+
def add_post(content):
12+
"""Add a post to the 'database' with the current timestamp."""
13+
POSTS.append((content, datetime.datetime.now()))
14+
15+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Database code for the DB Forum, full solution!
2+
3+
import psycopg2, bleach
4+
5+
DBNAME = "forum"
6+
7+
def get_posts():
8+
"""Return all posts from the 'database', most recent first."""
9+
db = psycopg2.connect(database=DBNAME)
10+
c = db.cursor()
11+
c.execute("select content, time from posts order by time desc")
12+
return c.fetchall()
13+
db.close()
14+
15+
def add_post(content):
16+
"""Add a post to the 'database' with the current timestamp."""
17+
db = psycopg2.connect(database=DBNAME)
18+
c = db.cursor()
19+
c.execute("insert into posts values (%s)", (bleach.clean(content),)) # good
20+
db.commit()
21+
db.close()
22+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Database code for the DB Forum.
2+
#
3+
# This is NOT the full solution!
4+
5+
import psycopg2
6+
7+
DBNAME = "forum"
8+
9+
def get_posts():
10+
"""Return all posts from the 'database', most recent first."""
11+
db = psycopg2.connect(database=DBNAME)
12+
c = db.cursor()
13+
c.execute("select content, time from posts order by time desc")
14+
return c.fetchall()
15+
db.close()
16+
17+
def add_post(content):
18+
"""Add a post to the 'database' with the current timestamp."""
19+
db = psycopg2.connect(database=DBNAME)
20+
c = db.cursor()
21+
c.execute("insert into posts values (%s)" % content) # Almost but not quite.
22+
db.commit()
23+
db.close()
24+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Database code for the DB Forum.
2+
#
3+
# This is still NOT the full solution!
4+
5+
import psycopg2, bleach
6+
7+
DBNAME = "forum"
8+
9+
def get_posts():
10+
"""Return all posts from the 'database', most recent first."""
11+
db = psycopg2.connect(database=DBNAME)
12+
c = db.cursor()
13+
c.execute("select content, time from posts order by time desc")
14+
return c.fetchall()
15+
db.close()
16+
17+
def add_post(content):
18+
"""Add a post to the 'database' with the current timestamp."""
19+
db = psycopg2.connect(database=DBNAME)
20+
c = db.cursor()
21+
c.execute("insert into posts values (%s)", (content,)) # Better, but ...
22+
db.commit()
23+
db.close()
24+

0 commit comments

Comments
 (0)