├── .gitignore ├── README.md ├── requirements.txt ├── requirements_test.txt ├── retwis ├── __init__.py ├── settings.py ├── static │ └── style.css ├── templates │ ├── home.html │ ├── layout.html │ ├── login.html │ └── signup.html └── views.py ├── runserver.py └── test_retwis.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | staticfiles -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A sample twitter app to show interfacing between flask and redis 2 | 3 | This is totally python3 compatible 4 | 5 | # Usage 6 | 7 | virtualenv venv -p python3.5 8 | source venv/bin/activate 9 | 10 | pip install -r requirements.txt 11 | 12 | python runserver.py 13 | 14 | # Testing 15 | 16 | pip install pytest 17 | py.test 18 | 19 | 20 | ~ What is Retwis? 21 | 22 | A redis powered flask post tweeting application 23 | 24 | ~ How do I use it? 25 | 26 | 1. change the settings in the settings.py file to your own 27 | and start using it. 28 | 29 | 2. run retwis 30 | 31 | flask run 32 | 33 | the application will greet you on 34 | http://localhost:5000/ 35 | 36 | ~ Is it tested? 37 | 38 | Run `py.test` to see the results. 39 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.11.1 2 | Jinja2==2.8 3 | MarkupSafe==0.23 4 | Werkzeug==0.11.11 5 | click==6.6 6 | itsdangerous==0.24 7 | redis==2.10.5 8 | -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | py==1.4.31 2 | pytest==3.0.4 3 | -------------------------------------------------------------------------------- /retwis/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | app.config.from_object('retwis.settings') 5 | 6 | import retwis.views -------------------------------------------------------------------------------- /retwis/settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | SECRET_KEY = 'e7fc1b2cd7dfde6c6e00850a40d7249a2d10103e30baa332' 3 | # Redis setup 4 | DB_HOST = 'localhost' 5 | DB_PORT = 6379 6 | DB_NO = 0 -------------------------------------------------------------------------------- /retwis/static/style.css: -------------------------------------------------------------------------------- 1 | .main-body{ 2 | padding-top:70px; 3 | } -------------------------------------------------------------------------------- /retwis/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |
4 |
5 | 6 |
7 | 8 |
9 | {% for post in timeline %} 10 |
  • 11 | {{ post.username }} at {{ post.ts }} 12 | {{ post.text }} 13 |
  • 14 | {% else %} 15 |

    No posts!

    16 | {% endfor %} 17 | {% endblock %} -------------------------------------------------------------------------------- /retwis/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | Retwis 3 | 4 | 5 | 23 |
    24 |
    25 | {% block body %}{% endblock %} 26 |
    27 |
    -------------------------------------------------------------------------------- /retwis/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Login

    4 | {% if error %}

    Error: {{ error }}{% endif %} 5 |

    6 |
    7 | 8 | 9 |
    10 |
    11 | 12 | 13 |
    14 | 15 |
    16 | Sign up 17 | {% endblock %} -------------------------------------------------------------------------------- /retwis/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

    Signup

    4 | {% if error %}

    Error: {{ error }}{% endif %} 5 |

    6 |
    7 | 8 | 9 |
    10 |
    11 | 12 | 13 |
    14 | 15 |
    16 | {% endblock %} -------------------------------------------------------------------------------- /retwis/views.py: -------------------------------------------------------------------------------- 1 | # import from stdlib 2 | from datetime import datetime 3 | 4 | # import third party modules 5 | from flask import (g, request, render_template, 6 | session, redirect, url_for) 7 | import redis 8 | 9 | # import self modules 10 | from retwis import app 11 | 12 | 13 | def init_db(): 14 | db = redis.StrictRedis( 15 | host=app.config['DB_HOST'], 16 | port=app.config['DB_PORT'], 17 | db=app.config['DB_NO']) 18 | return db 19 | 20 | 21 | @app.before_request 22 | def before_request(): 23 | g.db = init_db() 24 | 25 | 26 | @app.route('/signup', methods=['GET', 'POST']) 27 | def signup(): 28 | error = None 29 | if request.method == 'GET': 30 | return render_template('signup.html', error=error) 31 | username = request.form['username'] 32 | password = request.form['password'] 33 | user_id = str(g.db.incrby('next_user_id', 1000)) 34 | g.db.hmset('user:' + user_id, dict(username=username, password=password)) 35 | g.db.hset('users', username, user_id) 36 | session['username'] = username 37 | return redirect(url_for('home')) 38 | 39 | 40 | @app.route('/', methods=['GET', 'POST']) 41 | def login(): 42 | error = None 43 | if request.method == 'GET': 44 | return render_template('login.html', error=error) 45 | username = request.form['username'] 46 | password = request.form['password'] 47 | user_id = str(g.db.hget('users', username), 'utf-8') 48 | if not user_id: 49 | error = 'No such user' 50 | return render_template('login.html', error=error) 51 | saved_password = str(g.db.hget('user:' + str(user_id), 'password'), 'utf-8') 52 | if password != saved_password: 53 | error = 'Incorrect password' 54 | return render_template('login.html', error=error) 55 | session['username'] = username 56 | return redirect(url_for('home')) 57 | 58 | 59 | @app.route('/logout') 60 | def logout(): 61 | session.pop('username', None) 62 | return redirect(url_for('login')) 63 | 64 | 65 | @app.route('/home', methods=['GET', 'POST']) 66 | def home(): 67 | if not session: 68 | return redirect(url_for('login')) 69 | user_id = g.db.hget('users', session['username']) 70 | if request.method == 'GET': 71 | return render_template('home.html', timeline=_get_timeline(user_id)) 72 | text = request.form['tweet'] 73 | post_id = str(g.db.incr('next_post_id')) 74 | g.db.hmset('post:' + post_id, dict(user_id=user_id, 75 | ts=datetime.utcnow(), text=text)) 76 | g.db.lpush('posts:' + str(user_id), str(post_id)) 77 | g.db.lpush('timeline:' + str(user_id), str(post_id)) 78 | g.db.ltrim('timeline:' + str(user_id), 0, 100) 79 | return render_template('home.html', timeline=_get_timeline(user_id)) 80 | 81 | 82 | def _get_timeline(user_id): 83 | posts = g.db.lrange('timeline:' + str(user_id), 0, -1) 84 | timeline = [] 85 | for post_id in posts: 86 | post = g.db.hgetall('post:' + str(post_id, 'utf-8')) 87 | timeline.append(dict( 88 | username=g.db.hget('user:' + str(post[b'user_id'], 'utf-8'), 'username'), 89 | ts=post[b'ts'], 90 | text=post[b'text'])) 91 | return timeline 92 | -------------------------------------------------------------------------------- /runserver.py: -------------------------------------------------------------------------------- 1 | from retwis import app 2 | 3 | app.run() 4 | -------------------------------------------------------------------------------- /test_retwis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Retwis Tests 4 | ~~~~~~~~~~~~ 5 | 6 | Tests the retwis application. 7 | 8 | :copyright: (c) 2016 by Joydeep Bhattacharjee. 9 | :license: MIT, see LICENSE for more details. 10 | """ 11 | 12 | import os 13 | import tempfile 14 | import pytest 15 | from retwis import app 16 | from retwis.views import init_db 17 | 18 | 19 | @pytest.fixture 20 | def client(request): 21 | db_fd, app.config['DATABASE'] = tempfile.mkstemp() 22 | app.config['TESTING'] = True 23 | client = app.test_client() 24 | with app.app_context(): 25 | init_db() 26 | 27 | def teardown(): 28 | os.close(db_fd) 29 | os.unlink(app.config['DATABASE']) 30 | request.addfinalizer(teardown) 31 | 32 | return client 33 | 34 | 35 | def login(client, username, password): 36 | return client.post('/login', data=dict( 37 | username=username, 38 | password=password 39 | ), follow_redirects=True) 40 | 41 | 42 | def logout(client): 43 | return client.get('/logout', follow_redirects=True) 44 | 45 | 46 | def test_empty_db(client): 47 | """Start with a blank database.""" 48 | rv = client.get('/') 49 | assert b'Login' in rv.data 50 | 51 | 52 | def test_login_logout(client): 53 | """Make sure login and logout works""" 54 | rv = login(client, app.config['username'], 55 | app.config['password']) 56 | assert b'You were logged in' in rv.data 57 | rv = logout(client) 58 | assert b'You were logged out' in rv.data 59 | rv = login(client, app.config['username'] + 'x', 60 | app.config['password']) 61 | assert b'Invalid username' in rv.data 62 | rv = login(client, app.config['username'], 63 | app.config['password'] + 'x') 64 | assert b'Invalid password' in rv.data 65 | 66 | 67 | def test_messages(client): 68 | """Test that messages work""" 69 | login(client, app.config['username'], 70 | app.config['password']) 71 | rv = client.post('/home', data=dict( 72 | title='', 73 | text='HTML allowed here' 74 | ), follow_redirects=True) 75 | assert b'No entries here so far' not in rv.data 76 | assert b'<Hello>' in rv.data 77 | assert b'HTML allowed here' in rv.data 78 | --------------------------------------------------------------------------------