Seven URL Shortener
Tags: sql, python, flask, sqlalchemy
Published: 2021-12-12 10:56:53

## Introduction

[Flask](https://flask.palletsprojects.com/en/1.1.x/) is a framework for building web applications utilizing Python and [SQLite](https://sqlite.org/index.html) [here with [sqlalchemy](https://www.sqlalchemy.org) ] is a database engine that can be used to store application data.

Here, We wil be buliding a URL shortener, a service that takes any URL and generates a short URL, similar to [tinyurl](http://tinyurl.com/) or [bit.ly](https://bitly.com).

We are going to generate random strings for the alias using python library [random](https://docs.python.org/3/library/random.html).  

We will be using Flask, SQLite to build your URL shortener. Our application will allow users to enter a URL and generate a shorter version, in addition to a statistics page where users can view the number of times a URL has been clicked. We will be using the [Bulma](https://bulma.io) toolkit to style our application.

## Setting Up Dependencies
 We need to activate environment and install Flask using the pip package installer. Then you’ll create the database you will use to store URLs.
 ```sh
$ source env/bin/activate
```
Once you have activated your programming environment, install Flask and the sqlalchemy library using the following command:
```sh
pip install Flask
```

```sh
pip install Flask-SQLAlchemy
```

```sh
pip install python-dotenv
```

## Environment variables

###  `.flaskenv`
We will be adding a file `.flaskenv`  to mention the directory of our app
```env
FLASK_APP=url_shortener
```

### `.env`
We will add another file `.env` to set admin username, password and database url as environment variables.  
```env
ADMIN_USERNAME=admin
ADMIN_PASSWORD=password
DATABASE_URL=sqlite:///db.sqlite3
```

## Creating APP files

Now, We will create a new directory to store all our flask files  
```sh
mkdir url_shortener
```

First we will be creating Dunder init file  to write `create_app()` function and have the settings file. 

### `url_shortener/__init__.py`
```py
from flask import Flask
from .extensions import db
from .routes import short

def create_app(config_file='settings.py'):
	app = Flask(__name__)
	app.config.from_pyfile(config_file)
	db.init_app(app)
	app.register_blueprint(short)
	return app
```

settings file will be holding the settings for the app and will be related to `.env` file.  
we will be adding SQLAlchemy URI, ADMIN_USERNAME and ADMIN_PASSWORD to the file in order to make it ease to edit in future. We will turn off track modification for SQLAlchemy.      

### `url_shortener/settings.py`
```py
import os

SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

SQLALCHEMY_TRACK_MODIFICATIONS = False

ADMIN_USERNAME = os.environ.get('ADMIN_USERNAME')

ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD')
```


To import db objects we will create `extensions.py` file. 

### `url_shortener/extensions.py`
```py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
```

We will init database in Dunder init file too.  

 
To shorten links we will be making a `model.py` file.  

### `url_shortener/extensions.py`
```py
import string
from datetime import datetime
from random import choices
from .extensions import db

class Link(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	original_url = db.Column(db.String(512))
	short_url = db.Column(db.String(3), unique=True)
	visits = db.Column(db.Integer, default=0)
	date_created = db.Column(db.DateTime, default=datetime.now)

	def __init__(self, **kwargs):
		super().__init__(**kwargs)
		self.short_url = self.generate_short_link()

	def generate_short_link(self):
		characters = string.digits + string.ascii_letters
		short_url = ''.join(choices(characters, k=7))
		link = self.query.filter_by(short_url=short_url).first()
		if link:
		return self.generate_short_link()
	return short_url
```

We have created a table `Link` in which we will have the following columns:  

- 	id - Integer, primary key
-	original_url - String
-	short_url - String, unique
-	visits - Integer, default=0
- 	date_created - DateTime, default=datetime.now

We have extended Dunder init to call the methods with keyword arguments. Then, we will call `generate_short_link` where we will get a random 7 letters string from Upper case, Lower case or  Numbers. For this we have used `choices()` from `random` library. We will check if the generated string exists in our database and if it exits we will run again to get unique string. 

Now we are done with our model. So, we need to redirect our generated string to the original url. We will create `routes.py` file.  Main motto of this app is for personal use. So, we will be using basic authentication. We will be having `check_auth()` function which will be comparing the username and password with the ones given as environment variables. `authenticate()` will be saying that authorization is required. In `requires_auth()` it will ask for authorization if its not authorized else it will check for authorisation with `check_auth()` then it will return the function keyword arguments. 

### [`url_shortener/auth.py`](https://flask-httpauth.readthedocs.io/en/latest/)
```py
from functools import wraps
from flask import request, Response, current_app

def check_auth(username, password):
	#This function is called to check if a username password combination is valid.
	return username == current_app.config['ADMIN_USERNAME'] \
		and password == current_app.config['ADMIN_PASSWORD']

def authenticate():
	#Sends a 401 response that enables basic auth
	return Response(
	'Could not verify your access level for that URL.\n'
	'You have to login with proper credentials', 401,
	{'WWW-Authenticate': 'Basic realm="Login Required"'})

def requires_auth(f):
	@wraps(f)
	def decorated(*args, **kwargs):
		auth = request.authorization
		if not auth or not check_auth(auth.username, auth.password):
			return authenticate()
		return f(*args, **kwargs)
	return decorated
```
We will be adding the authorisation as decorator in `routes.py`

we will create a `Blueprint` - `short` . Now we need to create some endpoints. 

-  `/short_url` will be used to redirect the url. 
- `/` will be used for index. 
- `/add_link` will be used to add the url and will be using `POST` method. 
- `/stats` will be used to display the statistics. 
- `errorhandler(404)` will be shown when the user goes out with a random link. 

In order to work with routes we need to create `html` files, which we will be trying to keep simple - have used [Bulma](http://bulma.io/) to do it so. 

We will create a directory for these files. 
```sh
mkdir templates
```

### `url_shortener/templates/index.html`
```html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>URL Shortener</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
    <link href='https://fonts.googleapis.com/css?family=Tangerine' rel='stylesheet'>
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <style>
        .center {
            padding: 30px;
            border: 3px blue;
            text-align: center;
        }

        h1 {
            font-family: 'Tangerine', serif;
            font-size: 50px;
            text-align: center;
        }

        h2 {

            font-size: 37px;
            text-align: left;
        }
    </style>
</head>

<body>

    <h1 style="color:black;" allign="center"><b>Seven URL Shortener</b></h1>
    <div class="tabs is-centered" >
        <ul>
            <li class="is-active">
                <a>
                    <span class="icon is-small"><i class="fas fa-home" aria-hidden="true"></i></span>
                    <span onclick="window.location='/'">Home</span>
                </a>
            </li>
            <li>
                <a>
                    <span class="icon is-small"><i class="far fa-file-alt" aria-hidden="true"></i></span>
                    <span onclick="window.location='/stats'">statistics</span>
                </a>
            </li>
        </ul>
    </div>
    <div class="center">
        <div>

            <img src="https://github.com/rith-vik-7/trash/blob/main/short.jpg?raw=true" alt="short" width="700" height="433">
        </div>
        <section class="section">
            <div class="container">
                <form method="POST" action="{{ url_for('short.add_link') }}">
                    <div class="field">

                        <div class="control">
                            <input class="input" type="text" placeholder="Shorten your link" name="original_url">

                        </div>

                    </div>
                    <div class="control">
                        <button type="submit" style="float: right;" class="button is-info">Shorten</button>
                    </div>

                </form>
            </div>
        </section>
    </div>
</body>
<script>
document.addEventListener('keydown', function() {
  if (event.keyCode == 123) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  } else if (event.ctrlKey && event.shiftKey && event.keyCode == 73) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  } else if (event.ctrlKey && event.keyCode == 85) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  }
}, false);

if (document.addEventListener) {
  document.addEventListener('contextmenu', function(e) {
    alert("This function has been disabled to prevent you from stealing my code!");
    e.preventDefault();
  }, false);
} else {
  document.attachEvent('oncontextmenu', function() {
    alert("This function has been disabled to prevent you from stealing my code!");
    window.event.returnValue = false;
  });

}
</script>
</html>
```

### `url_shortener/templates/link_added.html`
```html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>URL Shortener</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
  <link href='https://fonts.googleapis.com/css?family=Tangerine' rel='stylesheet'>
  <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
  <style>
    .center {
      padding: 30px;
      border: 3px blue;
      text-align: center;
    }

    h1 {
      font-family: 'Tangerine', serif;
      font-size: 50px;
      text-align: center;
    }

    h2 {

      font-size: 37px;
      text-align: left;
    }
  </style>
</head>

<body>

  <h1 style="color:black;" allign="center"><b>Seven URL Shortener</b></h1>
  <div class="tabs is-centered">
    <ul>
      <li class="is-active">
        <a>
          <span class="icon is-small"><i class="fas fa-home" aria-hidden="true"></i></span>
          <span onclick="window.location='/'">Home</span>
        </a>
      </li>
      <li>
        <a>
          <span class="icon is-small"><i class="far fa-file-alt" aria-hidden="true"></i></span>
          <span onclick="window.location='/stats'">statistics</span>
        </a>
      </li>
    </ul>
  </div>
  <div class="center">
    <div>

      <img src="https://github.com/rith-vik-7/trash/blob/main/short.jpg?raw=true" alt="short" width="700" height="433">
    </div>
    <div class="container">
      <div class="field">

        <div class="control">
          <input class="input" type="text" name="original_url" value="{{ original_url }}">
        </div>
      </div>
      <div class="field">
        <label class="label"><b>Seven</b> Shortened URL</label>
        <div class="control">
          <input class="input" type="text" name="original_url"
            value="{{ url_for('short.redirect_to_url', short_url=new_link, _external=True) }}">
        </div>
      </div>
    </div>
    </section>
  </div>
</body>
<script>
document.addEventListener('keydown', function() {
  if (event.keyCode == 123) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  } else if (event.ctrlKey && event.shiftKey && event.keyCode == 73) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  } else if (event.ctrlKey && event.keyCode == 85) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  }
}, false);

if (document.addEventListener) {
  document.addEventListener('contextmenu', function(e) {
    alert("This function has been disabled to prevent you from stealing my code!");
    e.preventDefault();
  }, false);
} else {
  document.attachEvent('oncontextmenu', function() {
    alert("This function has been disabled to prevent you from stealing my code!");
    window.event.returnValue = false;
  });

}
</script>
</html>
```

### `url_shortener/templates/stats.html`
```html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>URL Shortener</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
    <link href='https://fonts.googleapis.com/css?family=Tangerine' rel='stylesheet'>
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <style>
        .center {
            padding: 30px;
            border: 3px blue;
            text-align: center;
        }

        h1 {
            font-family: 'Tangerine', serif;
            font-size: 50px;
            text-align: center;
        }

        h2 {

            font-size: 37px;
            text-align: left;
        }
    </style>
</head>

<body>

    <h1 style="color:black;" allign="center"><b>Seven URL Shortener</b></h1>
    <div class="tabs is-centered">
        <ul>
            <li>
                <a>
                    <span class="icon is-small"><i class="fas fa-home" aria-hidden="true"></i></span>
                    <span onclick="window.location='/'">Home</span>
                </a>
            </li>
            <li>
                <a>
                    <span class="icon is-small"><i class="fas fa-link" aria-hidden="true"></i></span>
                    <span>Link</span>
                </a>
            </li>
            <li class="is-active">
                <a>
                    <span class="icon is-small"><i class="far fa-file-alt" aria-hidden="true"></i></span>
                    <span onclick="window.location='/stats'">statistics</span>
                </a>
            </li>
        </ul>
    </div>

    <section class="section">
        <div class="container">
            <table class="table is-bordered is-striped is-narrow is-hoverable">
                <thead>
                    <tr>
                        <th>Original URL</th>
                        <th>Short URL</th>
                        <th>Visits</th>
                    </tr>
                </thead>

                <tbody>
                    {% for link in links %}
                    <tr>
                        <td>{{ link.original_url }}</td>
                        <td>{{ url_for('short.redirect_to_url', short_url=link.short_url, _external=True) }}</td>
                        <td>{{ link.visits }}</td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </section>
</body>
<script>
document.addEventListener('keydown', function() {
  if (event.keyCode == 123) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  } else if (event.ctrlKey && event.shiftKey && event.keyCode == 73) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  } else if (event.ctrlKey && event.keyCode == 85) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  }
}, false);

if (document.addEventListener) {
  document.addEventListener('contextmenu', function(e) {
    alert("This function has been disabled to prevent you from stealing my code!");
    e.preventDefault();
  }, false);
} else {
  document.attachEvent('oncontextmenu', function() {
    alert("This function has been disabled to prevent you from stealing my code!");
    window.event.returnValue = false;
  });

}
</script>
</html>
```

### `url_shortener/templates/404.html`
```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>404 ERROR</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    
  <body  style="background-color:#192643;">
  <section class="section">
    <div class="container">
      
      <img src="https://github.com/rith-vik-7/trash/blob/main/404.gif?raw=true" alt="Trulli" width="1000" height="900">
    </div>
  </section>
  </body>
<script>
document.addEventListener('keydown', function() {
  if (event.keyCode == 123) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  } else if (event.ctrlKey && event.shiftKey && event.keyCode == 73) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  } else if (event.ctrlKey && event.keyCode == 85) {
    alert("This function has been disabled to prevent you from stealing my code!");
    return false;
  }
}, false);

if (document.addEventListener) {
  document.addEventListener('contextmenu', function(e) {
    alert("This function has been disabled to prevent you from stealing my code!");
    e.preventDefault();
  }, false);
} else {
  document.attachEvent('oncontextmenu', function() {
    alert("This function has been disabled to prevent you from stealing my code!");
    window.event.returnValue = false;
  });

}
</script>
</html>
```

We will be calling function in `add_link()` method to get the short url. Then, we will call render template that link is added and we will display the generated url by passing the generated short url and original url. 

In `short_url()` we will be using `redirect()` function from flask to redirect short url to original url and get the counts of visits by using a counter which increments whenever the short url is called to redirect the original url.  If the short url is not found in the database it will be redirected to 404 error.

Then from `stats` we will redirect the visit counts along with original and short url to the rendered template. 

### `url_shortener/routes.py`
```py
from flask import Blueprint, render_template, request, redirect
from .extensions import db
from .models import Link
from .auth import requires_auth

short = Blueprint('short', __name__)

@short.route('/<short_url>')
def redirect_to_url(short_url):
	link = Link.query.filter_by(short_url=short_url).first_or_404()
	link.visits = link.visits + 1
	db.session.commit()
	return redirect(link.original_url)

@short.route('/')
@requires_auth
def index():
	return render_template('index.html')

@short.route('/add_link', methods=['POST'])
@requires_auth
def add_link():
	original_url = request.form['original_url']
	link = Link(original_url=original_url)
	db.session.add(link)
	db.session.commit()

	return render_template('link_added.html',
		new_link=link.short_url, original_url=link.original_url)

@short.route('/stats')
@requires_auth
def stats():
	links = Link.query.all()
	return render_template('stats.html', links=links)

@short.errorhandler(404)
def page_not_found(e):
	return render_template('404.html'), 404
```

We will then add `register_blueprint()` to `__init__.py`

### Creating the database

We need to create the database now. 

For instance in order to setup, We need to make some changes to `url_shortener/settings.py`
```py
import os

SQLALCHEMY_DATABASE_URI = 'sqlite:///db.sqlite3' #os.environ.get('DATABASE_URL')
SQLALCHEMY_TRACK_MODIFICATIONS = False
ADMIN_USERNAME = os.environ.get('ADMIN_USERNAME')
ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD')
```


```sh
python3
```

```py
> from url shortener import create_ app
> from url shortener.extensions import db
> from url shortener.models import Link
> db.create_all (app=create_app())
```

After noticing `db.sqlite3` we can change the `SQLALCHEMY_DATABASE_URI` back as before. 

We can cross check the table if its created. 
```sh
sqlite3 db.sqlite3
```

```sql
sqlite> .tables                                                                                                                                             
link
sqlite> .exit
```

## [Source](https://github.com/rithvik78/URL-shortener)
Full Source Code: [Github](https://github.com/rithvik78/URL-shortener)  
[Deployment](http://rithvik7.pythonanywhere.com)  
[Blog](https://rithvik78.github.io/blog/urlshortener/) - Due to insufficient credits, currently down - will try to rehost some-other way.

## Deploy Locally

```sh
flask run
```

We can find the app locally deployed at [http://127.0.0.1:5000/](http://127.0.0.1:5000/).  

```
username: admin
password: password
```