In our previous redis blog we gave a brief introduction on how to interface between python and redis. In this post, we will use Redis as a cache, to build the backend of our basic twitter app.
We first start the server, if it’s in a stopped state.
sudo service redis_6379 start sudo service redis_6379 stop
In case you have not installed the redis server, you can install the server and configure it with python using the previous tutorial.
We will work on creating our own custom Twitter and post tweets to this. Users should be able to post tweets, and there should be a timeline forthe posts. The screenshot of the final product is shown below.
Let us start building the module. There are some build dependencies; therefore ensure the following dependencies are installed.
sudo apt-get install build-essential sudo apt-get install python3-dev sudo apt-get install libncurses5-dev
Once done, fire-up a virtualenv and install the requirements.
virtualenv venv -p python3.5 source venv/bin/activate wget https://raw.githubusercontent.com/infinite-Joy/retwis-py/master/requirements.txt pip install -r requirements.txt
Create a folder structure of the following format.
mkdir retwis cd retwis
Flask lets us create the template files – layout.html, login.html and signup.html. These templates are designed using the Jinja2 templates which Flask uses. We can use template inheritance and login and signup pages will inherit from layout.html.
Check out the three template files shown below.
cat templates/layout.html <!doctype html> <title>Retwis</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous"> <link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}"> <nav class="navbar navbar-default navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <h1>Retwis</h1> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li> {% if not session.username %} <a href="{{ url_for('login') }}">log in</a> {% else %} <a href="{{ url_for('logout') }}">log out</a> {% endif %} </li> </ul> </div> </div> </nav> <div class="main-body"> <div class="container"> {% block body %}{% endblock %} </div> </div>
Note that we have abstracted out the common elements of all the pages. We have defined the header with the title and then in the body; if a session is present, there will be the login link, else there will be the logout link.
Check out the login and the signup html which are almost similar.
cat templates/login.html {% extends "layout.html" %} {% block body %} <h2>Login</h2> {% if error %}<p class="error"><strong>Error:</strong> {{ error }}{% endif %} <form action="{{ url_for('login') }}" method="post"> <div class="form-group"> <label for="username">Username</label> <input class="form-control" type="text" name="username"> </div> <div class="form-group"> <label for="password">Password</label> <input class="form-control" type="password" name="password"> </div> <button class="btn btn-default" type="submit">Login</button> </form> <a class="btn btn-default" href="{{ url_for('signup') }}">Sign up</a> {% endblock %}
cat templates/signup.html {% extends "layout.html" %} {% block body %} <h2>Signup</h2> {% if error %}<p class="error"><strong>Error:</strong> {{ error }}{% endif %} <form action="{{ url_for('signup') }}" method="post"> <div class="form-group"> <label for="username">Username</label> <input class="form-control" type="text" name="username"> </div> <div class="form-group"> <label for="password">Password</label> <input class="form-control" type="password" name="password"> </div> <button class="btn btn-default" type="submit">Sign up</button> </form> {% endblock %}
As you can see, if there is no error, then we define the username and the password fields that are bound with the “post” method.
We can now create the basic flask app and see if the two templates get rendered correctly. We create two endpoints for the templates and then render them. Check out the code below.
cat views.py from flask import Flask from flask import render_template app = Flask(__name__) DEBUG=True @app.route('/signup') def signup(): error = None return render_template('signup.html', error=error) @app.route('/') def login(): error = None return render_template('login.html', error=error) if __name__ == "__main__": app.run()
To run the server use the following command.
python views.py
On your browser, open http:127.0.0.1:5000/signup
You should be able to see the two pages above.
We will also need to create the home page which the user will fall back to once he is logged in. Create a home.html in the templates folder and then write the tweets block.
cd templates cat home.html {% extends "layout.html" %} {% block body %} <form action="{{ url_for('home') }}" method="post"> <div class="form-group"> <input class="form-control" type="text" name="tweet" placeholder="What are you thinking?"> </div> <button class="btn btn-default" type="submit">Post</button> </form> {% for post in timeline %} <li class="tweet"> {{ post.username }} at {{ post.ts }} {{ post.text }} </li> {% else %} <h2>No posts!</h2> {% endfor %} {% endblock %}
As you see, if there are posts on the timeline, then list the username, time, and the text, else put “No posts” in header format. Let’s build the code for that in view.py and see how it looks.
@app.route('/home') def home(): return render_template('home.html', timeline=[{"username": "dummy_username", "ts": "today", "text": "dummy text"}])
If you check out the url http://localhost:5000/home, you should get the page below.
We will be using redis to get user information. If you don’t have redis-py already installed in your virtual environment, install it using pip.
pip install redis
Next, we need to plugin redis to our flask app and see that it gets instantiated before each request.
cat views.py import redis from flask import Flask from flask import render_template app = Flask(__name__) DEBUG=True def init_db(): db = redis.StrictRedis( host=DB_HOST, port=DB_PORT, db=DB_NO) return db @app.before_request def before_request(): g.db = init_db() # remaining code here.
We will interface the signup page with redis and on signing up, the user information should get populated in the redis datastore.
We change the “signup” function to the code below.
cat views.py import redis from flask import Flask from flask import render_template from flask import request from flask import url_for from flask import session from flask import g app = Flask(__name__) # other code … @app.route('/signup', methods=['GET', 'POST']) def signup(): error = None if request.method == 'GET': return render_template('signup.html', error=error) username = request.form['username'] password = request.form['password'] user_id = str(g.db.incrby('next_user_id', 1000)) g.db.hmset('user:' + user_id, dict(username=username, password=password)) g.db.hset('users', username, user_id) session['username'] = username return redirect(url_for('home')) # other code ...
Here, we take the username and the password from the form and push them to the redis database. Note that we increment the keys by 1000. This is a standard for redis keys. For more information, consult the official docs.
We will also need to set a secret key to use session information which is used in the code above. You can read about sessions and how to set session keys from the official docs. We will also do a little bit of refactoring and keep the settings information together.
cat views.py # import statements app = Flask(__name__) # settings DEBUG=True # I am using a SHA1 hash. Use a more secure algo in your PROD work SECRET_KEY = '8cb049a2b6160e1838df7cfe896e3ec32da888d7' app.secret_key = SECRET_KEY # Redis setup DB_HOST = 'localhost' DB_PORT = 6379 DB_NO = 0 def init_db(): ------------------------------------------------------------------ def before_request(): ----------------------------------------------------------- def signup(): ------------------------------------------------------------------- def login(): -------------------------------------------------------------------- def home(): --------------------------------------------------------------------- if __name__ == "__main__": app.run()
Check out the form now and try to submit some user information.
? redis-cli 127.0.0.1:6379> HGETALL * (empty list or set) 127.0.0.1:6379> KEYS * 1) "users" 2) "user:1000" 3) "next_user_id" 127.0.0.1:6379> HGETALL "users" 1) "hackerearth" 2) "1000" 127.0.0.1:6379> HGETALL "user:1000" 1) "username" 2) "hackerearth" 3) "password" 4) "hackerearth"
Once the session and signup functions work fine, we can then focus on the home page where people can login once they have signed up. These two pages should fall back safely to the home page.
@app.route('/', methods=['GET', 'POST']) def login(): error = None if request.method == 'GET': return render_template('login.html', error=error) username = request.form['username'] password = request.form['password'] user_id = str(g.db.hget('users', username), 'utf-8') if not user_id: error = 'No such user' return render_template('login.html', error=error) saved_password = str(g.db.hget('user:' + str(user_id), 'password'), 'utf-8') if password != saved_password: error = 'Incorrect password' return render_template('login.html', error=error) session['username'] = username return redirect(url_for('home'))
The code tells us if the request method is “GET”, then we render the login page. This is the first page that comes up when we go to the page http:localhost:5000/.
After that, we will fill up the fields with the previous values. The entered username and password is pulled from the form. Using this username, we get the user ID from the redis database and this user ID is used to retrieve the password. This password is then matched with the entered password. If there is a match, then we will be redirected to the “home page.”
We now need to work on the home page. The home page is the biggest of the three modules as these do several things simultaneously. It should handle the session information. If the session information is not there, it should transfer to the login page. It should retrieve the posts of the user and push them to the redis database and get the data in turn. So we will replace the home function in views.py to the code below.
Cat views.py @app.route('/home', methods=['GET', 'POST']) def home(): if not session: return redirect(url_for('login')) user_id = g.db.hget('users', session['username']) if request.method == 'GET': return render_template('home.html', timeline=_get_timeline(user_id)) text = request.form['tweet'] post_id = str(g.db.incr('next_post_id')) g.db.hmset('post:' + post_id, dict(user_id=user_id, ts=datetime.utcnow(), text=text)) g.db.lpush('posts:' + str(user_id), str(post_id)) g.db.lpush('timeline:' + str(user_id), str(post_id)) g.db.ltrim('timeline:' + str(user_id), 0, 100) return render_template('home.html', timeline=_get_timeline(user_id)) def _get_timeline(user_id): posts = g.db.lrange('timeline:' + str(user_id), 0, -1) timeline = [] for post_id in posts: post = g.db.hgetall('post:' + str(post_id, 'utf-8')) timeline.append(dict( username=g.db.hget('user:' + str(post[b'user_id'], 'utf-8'), 'username'), ts=post[b'ts'], text=post[b'text'])) return timeline
Note, the timeline part is handled in the _get_timeline function. We get the timeline from the redis database and then for all the posts we put the username, time and the post text to a timeline list. This list is returned to the home function, which takes the user tweet post and pushes it to redis, after which it renders the current posts in the timeline. We will also need to “import datetime.”
import redis import datetime from flask import Flask from flask import render_template from flask import request from flask import url_for from flask import session from flask import g from flask import redirect # rest of the code
We need to build the url for logout for the template to work correctly.
@app.route('/logout') def logout(): session.pop('username', None) return redirect(url_for('login'))
Now, check it in the browser. Hit http://locahost:5000; login with your credentials. You should be able to post tweets now to the post.
A big shoutout to kushmansingh/retwis-py who inspired me to write the blog.
References
Defining a Recruitment Management System In today's competitive talent landscape, attracting and retaining top performers…
Understanding the Recruitment Funnel Imagine a broad opening at the top, gradually narrowing down to…
With the growing demand for highly skilled professionals, traditional hiring methods such as reviewing resumes…
Finding the perfect fit for your team can feel like searching for a unicorn. But…
In today's competitive job market, attracting and keeping top talent is crucial for small businesses…
The tech industry thrives on innovation, and at the heart of that innovation lies a…