29
loading...
This website collects cookies to deliver better user experience
pip install pipenv
pipenv shell
exit
Flask is a simple, easy-to-use microframework for Python that can help build scalable and secure web applications. The module doesn't come pre-installed with Python, so we need to install it using the command:
Flask-Migrate is an extension that handles SQLAlchemy database migrations for Flask applications using Alembic. The database operations are made available through the Flask command-line interface. To install the module, use the command:
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It helps you simplify things using SQLAlchemy with Flask by giving you useful defaults and extra helpers that make it easier to perform common tasks. To install the module, use the command:
Psycopg2 is the most popular PostgreSQL database adapter for the Python programming language. To install the module, use the command:
Gunicorn is a Python WSGI HTTP Server for UNIX. To install the module, use the command:
Python Decouple: We'll also use environment variables in this project. So, we are going to install another module called python-decouple to handle this:
__init__.py
file. So, let's create our core package first.$ mkdir core
__init__.py
file inside the core directory:$ cd core
$ touch __init__.py
$ cd ..
config.py
. We'll store the configurations for the project in this file. Within the file, add the following content:from decouple import config
DATABASE_URI = config("DATABASE_URL")
if DATABASE_URI.startswith("postgres://"):
DATABASE_URI = DATABASE_URI.replace("postgres://", "postgresql://", 1)
class Config(object):
DEBUG = False
TESTING = False
CSRF_ENABLED = True
SECRET_KEY = config('SECRET_KEY', default='guess-me')
SQLALCHEMY_DATABASE_URI = DATABASE_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False
class ProductionConfig(Config):
DEBUG = False
class StagingConfig(Config):
DEVELOPMENT = True
DEBUG = True
class DevelopmentConfig(Config):
DEVELOPMENT = True
DEBUG = True
class TestingConfig(Config):
TESTING = True
Config
class and defined various attributes inside that. Also, we have created different child classes (as per different stages of development) that inherit the Config
class..env
in the root directory and add the following content there:SECRET_KEY=verysecretkey
DATABASE_URL=sqlite:///shorty.db
APP_SETTINGS=config.DevelopmentConfig
FLASK_APP=core
config.py
file. We set it to the current stage of the project. The value of FLASK_APP is the name of the package we have created.core/ __init__.py
file:from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from decouple import config
app = Flask( __name__ )
app.config.from_object(config("APP_SETTINGS"))
db = SQLAlchemy(app)
migrate = Migrate(app, db)
from core import routes
app
of class Flask. We use the __name__
argument to indicate the app's module or package, so that Flask knows where to find other files such as templates..env
file. To use Flask-SQLAlchemy and Flask-Migrate in our application, we just need to create objects of the SQLAlchemy
class and Migrate
class from the flask_sqlalchemy
and flask_migrate
libraries respectively.routes
module, which doesn't exist yet.main.py
file with the following content:from core import app
if __name__ == ' __main__':
app.run()
models.py
file within the core package. Inside that, we can write the following code:from core import db
from datetime import datetime
class ShortUrls(db.Model):
id = db.Column(db.Integer, primary_key=True)
original_url = db.Column(db.String(500), nullable=False)
short_id = db.Column(db.String(20), nullable=False, unique=True)
created_at = db.Column(db.DateTime(), default=datetime.now(), nullable=False)
db
object that we had initialized in the __init__.py
file. Then we created a ShortUrls
class with a few fields such as id (primary key), original_url (provided by the user), short_id (generated by us or provided by the user), and created_at (timestamp).flask db init
— to initialize the database at the beginning (to be used only once)flask db migrate
— to migrate the new changes to the database (to be used every time we make changes in the database tables)flask db upgrade
— to upgrade our database with the new changes (to be used with the migrate command)routes.py
file in the core package and create a Python function to generate a short id.from random import choice
import string
def generate_short_id(num_of_chars: int):
"""Function to generate short_id of specified number of characters"""
return ''.join(choice(string.ascii_letters+string.digits) for _ in range(num_of_chars))
index.html
directly. We can use the Template Inheritance concept in Jinja2. So, let's create a templates directory within the core
package and create a base.html
file inside that. You can paste the HTML code into that file.<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
<title>{% block title %} {% endblock %}</title>
</head>
<body>
<div class="container mt-3">
{% for message in get_flashed_messages() %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{% block content %} {% endblock %}
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-eMNCOe7tC1doHpGoWe/6oMVemdAVTMs2xqW4mwXrXsW0L84Iytr2wi5v2QjrP/xp" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-cn7l7gDp0eyniUwwAZgrzD06kc/tftFf19TOAs2zVinnD/C7E91j9yyk5//jjpt/" crossorigin="anonymous"></script>
</body>
</html>
<meta>
tags provide information for the web browser, the <link>
tag links the Bootstrap CSS files, and the <script>
tags are links to JavaScript code that allows some additional Bootstrap features.<title>{% block title %} {% endblock %}</title>
tag allows the inheriting templates to define a custom title.for message in get_flashed_messages()
loop to display the flashed messages (warnings, alerts, and so on).{% block content %} {% endblock %}
placeholder is where inheriting templates place the content so that all templates have access to this base template, which avoids repetition.index.html
file that will extend this base.html
file:{% extends 'base.html' %}
{% block content %}
<h1 class="text-center mb-3">{% block title %} Welcome to Shorty {% endblock %}</h1>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8">
<form method="post" action="{{url_for('index')}}">
<div class="form-floating mb-3">
<input type="text" name="url" id="url"
placeholder="Enter looooooooooooong URL" class="form-control"
value="{{ request.form['url'] }}" autofocus></input>
<label for="url">URL</label>
</div>
<div class="form-floating mb-3">
<input type="text" name="custom_id" id="custom_id"
placeholder="Want to customise? (optional)" class="form-control"
value="{{ request.form['custom_id'] }}"></input>
<label for="custom_id">Custom Short ID</label>
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-lg btn-primary">Shorten</button>
</div>
</form>
{% if short_url %}
<hr>
<span><a href="{{ short_url }}" target="_blank">{{ short_url }}</a></span>
{% endif %}
</div>
<div class="col-md-2"></div>
</div>
{% endblock %}
base.html
, define a title, and create a form with two inputs named url
and custom_id
.url
input will allow users to enter URLs to shorten. It has a value of request.form['url']
, which stores data in cases of submission failure (that is if the user provides no URL). Similarly, custom_id
input will allow users to enter a custom short id. We then have a submit button.short_url
variable has any value—this is true if the form submits and the short URL generates successfully. If the condition is true, we display the short URL under the form.routes.py
as:from datetime import datetime
from core.models import ShortUrls
from core import app, db
from random import choice
import string
from flask import render_template, request, flash, redirect, url_for
def generate_short_id(num_of_chars: int):
"""Function to generate short_id of specified number of characters"""
return ''.join(choice(string.ascii_letters+string.digits) for _ in range(num_of_chars))
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
url = request.form['url']
short_id = request.form['custom_id']
if short_id and ShortUrls.query.filter_by(short_id=short_id).first() is not None:
flash('Please enter different custom id!')
return redirect(url_for('index'))
if not url:
flash('The URL is required!')
return redirect(url_for('index'))
if not short_id:
short_id = generate_short_id(8)
new_link = ShortUrls(
original_url=url, short_id=short_id, created_at=datetime.now())
db.session.add(new_link)
db.session.commit()
short_url = request.host_url + short_id
return render_template('index.html', short_url=short_url)
return render_template('index.html')
index()
function is a Flask view function , which is a function decorated using the special @app.route
decorator. Its return value gets converted into an HTTP response that an HTTP client, such as a web browser, displays.index()
view function, we accept both GET and POST requests by passing methods=['GET', 'POST']
to the app.route()
decorator.if request.method == 'POST'
condition until the last line. This is where we render a template called index.html
, which will contain a form for users to enter a URL to shorten.if request.method == 'POST'
condition is true, which means a user has submitted a URL. We store the URL in the url
variable. If the user has submitted an empty form, you flash the message The URL is required!
and redirect to the index page.custom_id
, we store it in short_id , else we generate random short id using the function that we had created before.new_link
with all the data such as original_url
short_id
and created_at
. Then we commit the transaction.request.host_url
, which is an attribute that Flask’s request
object provides to access the URL of the application’s host. This will be http://127.0.0.1:5000/
in a development environment and our_domain
if we deploy our application.short_url
variable will have a value like http://127.0.0.1:5000/asdf1gHJ
, which is the short URL that will redirect users to the original URL stored in the database with the ID that matches the asdf1gHJ
.index.html
template passing the short_url
variable to it.py
@app.route('/<short_id>')
def redirect_url(short_id):
link = ShortUrls.query.filter_by(short_id=short_id).first()
if link:
return redirect(link.original_url)
else:
flash('Invalid URL')
return redirect(url_for('index'))
short_id
through the URL and passes it to the url_redirect()
view function. For example, visiting http://127.0.0.1:5000/asdf1gHJ
would pass the string 'asdf1gHJ'
to the short_id
parameter.short_id
. If it is not None, the view function will redirect the user to the original_url
associated with this short_id
using the redirect()
Flask helper function. Otherwise, it will flash an error message to inform the user that the URL is invalid and redirect them to the index page..env
file in the project and copy and paste it into your Config Vars as below:APP_SETTINGS
to config.ProductionConfig
because we're deploying the application publicly.$ git init
$ git remote add origin <your-repository-url-here>
$ git add .
$ git commit -m "Initial commit"
$ git push origin main
<your-repository-url-here>
with the URL provided by GitHub.Procfile
and runtime.txt
:web: gunicorn main:app
web
, and the command needed to run it.web
is important here. It declares that this process type will be attached to the HTTP routing stack of Heroku, and will receive web traffic when deployed. Notice that the Procfile file doesn't have any extension.runtime.txt
file and add your Python version there as:python-3.9.7
.gitignore
file and add the following content:# Django #
*.log
*.pot
*.pyc
__pycache__
media
db.sqlite3
# Backup files #
*.bak
# If you are using PyCharm #
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/gradle.xml
.idea/**/libraries
*.iws /out/
# Python #
*.py[cod]
*$py.class
# Distribution / packaging
.Python build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery
celerybeat-schedule.*
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# mkdocs documentation
/site
# mypy
.mypy_cache/
# Sublime Text #
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
*.sublime-workspace
*.sublime-project
# sftp configuration file
sftp-config.json
# Package control specific files Package
Control.last-run
Control.ca-list
Control.ca-bundle
Control.system-ca-bundle
GitHub.sublime-settings
# Visual Studio Code #
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history
$ git add .
$ git commit -m "Ready for deployment"
$ git push origin main