How to Build a Python Web Application Using Flask and Neo4j
@_nicolemargaret
About Me

Context
Flaskr

SQLite

Flaskr
SQL to Neo4j
SQL
SELECT u.username, p.text, GROUP_CONCAT(t.name)
FROM Users u
JOIN Posts p
ON u.id = p.user_id
JOIN PostsTags pt
ON p.id = pt.post_id
JOIN Tags t
ON pt.tag_id = t.id
GROUP BY u.username, p.text

Neo4j

Neo4j

MATCH (u:User)-[:PUBLISHED]->(p:Post)<-[:TAGGED]-(t:Tag)
RETURN u.username, p.text, COLLECT(t.name)
pip install py2neo
from py2neo import Graph, Node, Relationship
graph = Graph()
user = Node("User", username="nicole")
post = Node("Post", text="hello pycon")
graph.create(Relationship(user, "PUBLISHED", post))
Application Features
neo4j-flask-blog.herokuapp.com
Register A User
views.py
@app.route('/register', methods=['GET','POST'])
def register():
error = None
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if len(username) < 1:
error = 'Your username must be at least one character.'
elif len(password) < 5:
error = 'Your password must be at least 5 characters.'
elif not User(username).register(password):
error = 'A user with that username already exists.'
else:
session['username'] = username
flash('Logged in.')
return redirect(url_for('index'))
return render_template('register.html', error=error)
models.py
class User:
def __init__(self, username):
self.username = username
def find(self):
user = graph.find_one("User", "username", self.username)
return user
def register(self, password):
if not self.find():
user = Node("User", username=self.username, password=bcrypt.encrypt(password))
graph.create(user)
return True
else:
return False
Add A Post
@app.route('/add_post', methods=['POST'])
def add_post():
title = request.form['title']
tags = request.form['tags']
text = request.form['text']
if not title:
abort(400, 'You must give your post a title.')
if not tags:
abort(400, 'You must give your post at least one tag.')
if not text:
abort(400, 'You must give your post a text body.')
User(session['username']).add_post(title, tags, text)
return redirect(url_for('index'))
views.py
class User:
...
def add_post(self, title, tags, text):
user = self.find()
post = Node(
"Post",
id=str(uuid.uuid4()),
title=title,
text=text,
timestamp=timestamp(),
date=date()
)
rel = Relationship(user, "PUBLISHED", post)
graph.create(rel)
tags = [x.strip() for x in tags.lower().split(',')]
for t in set(tags):
tag = graph.merge_one("Tag", "name", t)
rel = Relationship(tag, "TAGGED", post)
graph.create(rel)
models.py
Like A Post
@app.route('/like_post/<post_id>')
def like_post(post_id):
username = session.get('username')
if not username:
abort(400, 'You must be logged in to like a post.')
User(username).like_post(post_id)
flash('Liked post.')
return redirect(request.referrer)
views.py
class User:
...
def like_post(self, post_id):
user = self.find()
post = graph.find_one("Post", "id", post_id)
graph.create_unique(Relationship(user, "LIKED", post))
models.py
Profile Pages
@app.route('/profile/<username>')
def profile(username):
posts = get_users_recent_posts(username)
similar = []
common = []
viewer_username = session.get('username')
if viewer_username:
viewer = User(viewer_username)
if viewer.username == username:
similar = viewer.get_similar_users()
else:
common = viewer.get_commonality_of_user(username)
return render_template(
'profile.html',
username=username,
posts=posts,
similar=similar,
common=common
)
views.py
A User's Recent Posts
def get_users_recent_posts(username):
query = """
MATCH (user:User)-[:PUBLISHED]->(post:Post),
(tag:Tag)-[:TAGGED]->(post)
WHERE user.username = {username}
RETURN post.id AS id,
post.date AS date,
post.timestamp AS timestamp,
post.title AS title,
post.text AS text,
COLLECT(tag.name) AS tags
ORDER BY timestamp DESC
LIMIT 5
"""
return graph.cypher.execute(query, username=username)
models.py
Visiting Your Own Profile
class User:
...
def get_similar_users(self):
# Find three users who are most similar to the logged-in user
# based on tags they've both blogged about.
query = """
MATCH (you:User)-[:PUBLISHED]->(:Post)<-[:TAGGED]-(tag:Tag),
(they:User)-[:PUBLISHED]->(:Post)<-[:TAGGED]-(tag)
WHERE you.username = {username} AND you <> they
WITH they, COLLECT(DISTINCT tag.name) AS tags, COUNT(DISTINCT tag) AS len
ORDER BY len DESC LIMIT 3
RETURN they.username AS similar_user, tags
"""
return graph.cypher.execute(query, username=self.username)
models.py
Visiting Another User's Profile
class User:
...
def get_commonality_of_user(self, username):
# Find how many of the logged-in user's posts the other user
# has liked and which tags they've both blogged about.
query = """
MATCH (they:User {username:{they}}),
(you:User {username:{you}})
OPTIONAL MATCH (they)-[:LIKED]->(post:Post)<-[:PUBLISHED]-(you)
OPTIONAL MATCH (they)-[:PUBLISHED]->(:Post)<-[:TAGGED]-(tag:Tag),
(you)-[:PUBLISHED]->(:Post)<-[:TAGGED]-(tag)
RETURN COUNT(DISTINCT post) AS likes, COLLECT(DISTINCT tag.name) AS tags
"""
return graph.cypher.execute(query, they=username, you=self.username)[0]
models.py
Questions?
github.com/nicolewhite/neo4j-flask
@_nicolemargaret
nicole@neotechnology.com
How to Build a Python Web Application Using Flask and Neo4j
By Nicole White
How to Build a Python Web Application Using Flask and Neo4j
- 2,358