Skip to content

Commit caaef35

Browse files
author
Matthew Shirtliffe
committed
url shortener wip with some tests
1 parent d679ab4 commit caaef35

File tree

8 files changed

+138
-0
lines changed

8 files changed

+138
-0
lines changed

url_shortener/app.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from flask import Flask, request, render_template, redirect, session, url_for, g
2+
from flask_wtf import FlaskForm
3+
from wtforms import BooleanField, StringField, validators, SubmitField
4+
from datetime import datetime
5+
6+
import random
7+
import string
8+
import pickledb
9+
10+
# export FLASK_APP=app.py
11+
# flask run
12+
13+
def is_url(form, field):
14+
if not field.data.startswith('https://'):
15+
raise validators.ValidationError('A valid url is required')
16+
17+
class URLForm(FlaskForm):
18+
url = StringField('url', [is_url])
19+
submit = SubmitField('Submit')
20+
21+
app = Flask(__name__)
22+
app.config['SECRET_KEY'] = 'SECRET_KEY'
23+
app.config['PICKLE_DB'] = 'urls.db'
24+
25+
def get_db():
26+
db = getattr(g, 'db', None)
27+
if db is None:
28+
db = pickledb.load(app.config['PICKLE_DB'], False, sig=False)
29+
g.db = db
30+
return db
31+
32+
@app.teardown_appcontext
33+
def close_connection(exception):
34+
db = getattr(g, 'db', None)
35+
if db is not None:
36+
db.dump()
37+
# db.close()
38+
39+
40+
@app.route('/', methods=['GET', 'POST'], defaults={'url_id': None})
41+
@app.route('/<url_id>', methods=['GET', 'POST'])
42+
def create_url(url_id):
43+
44+
db = get_db()
45+
46+
if url_id:
47+
return redirect(db.get(url_id))
48+
49+
form = URLForm(request.form)
50+
if request.method == 'POST' and form.validate_on_submit():
51+
url = form.url.data
52+
key = ''.join([random.choice(string.ascii_lowercase) for _ in range(3)] + [random.choice(string.digits) for _ in range(3)])
53+
db.set(key, url)
54+
db.dump()
55+
session['url'] = url
56+
session['short_url'] = f'{request.base_url}{key}'
57+
form.url.data = None
58+
return redirect(url_for('create_url'))
59+
60+
return render_template('index.html', form=form, url=session.get('url', None))
61+

url_shortener/templates/index.html

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!DOCTYPE html>
2+
<html lang='en'>
3+
4+
<head>
5+
<meta charset='UTF-8'>
6+
<meta http-equiv='X-UA-Compatible' content='ie=edge'>
7+
<meta name="author" content="$author">
8+
<title>URL Shortener</title>
9+
</head>
10+
11+
<body>
12+
<main>
13+
<section id='form'>
14+
<h1>Enter URL</h1>
15+
<form method="POST" action="/">
16+
{{ form.hidden_tag() }}
17+
<div>{{ form.url.label }}: {{ form.url(value=url) }}</div>
18+
{{ form.submit() }}
19+
</form>
20+
{% if form.errors %}
21+
<ul class="errors">
22+
{% for field_name, field_errors in form.errors|dictsort if field_errors %}
23+
{% for error in field_errors %}
24+
<li>{{ form[field_name].label }}: {{ error }}</li>
25+
{% endfor %}
26+
{% endfor %}
27+
</ul>
28+
{% endif %}
29+
{% if session['short_url'] %}
30+
short url: <a href="{{ session['short_url'] }}">{{ session['short_url'] }}</a>
31+
{% endif %}
32+
33+
</section>
34+
</main>
35+
</body>
36+
37+
</html>

url_shortener/tests/__init__.py

Whitespace-only changes.

url_shortener/tests/system/__init__.py

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import os
2+
from unittest import TestCase
3+
from unittest.mock import patch, call
4+
from app import app
5+
6+
import json
7+
from datetime import datetime
8+
9+
10+
class AppTest(TestCase):
11+
12+
13+
def setUp(self):
14+
15+
app.config['WTF_CSRF_ENABLED'] = False
16+
app.config['PICKLE_DB'] = 'test.db'
17+
app.testing = True
18+
app.debug = False
19+
self.app = app.test_client()
20+
21+
@classmethod
22+
def tearDownClass(cls):
23+
os.remove('test.db')
24+
25+
def test_home(self):
26+
with self.app as client:
27+
response = client.get('/')
28+
self.assertEqual(response.status_code, 200)
29+
self.assertIn('<h1>Enter URL</h1>', response.get_data().decode("utf-8") )
30+
31+
def test_create_url(self):
32+
with self.app as client:
33+
response = client.post('/', data={'url': 'https://matthewshirtliffe.co.uk/'}, follow_redirects=False)
34+
self.assertEqual(response.status_code, 302)

url_shortener/tests/unit/__init__.py

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from unittest import TestCase
2+
import app
3+
4+
class FilteringRecordsTest(TestCase):
5+
pass

url_shortener/urls.db

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"eub856": "https://matthewshirtliffe.co.uk/", "lwo400": "https://matthewshirtliffe.co.uk/", "krc534": "https://matthewshirtliffe.co.uk/", "kki760": "https://stackoverflow.com/questions/35107885/how-to-generate-dynamic-urls-in-flask", "wfh963": "https://stackoverflow.com/questions/35107885/how-to-generate-dynamic-urls-in-flask", "xyc675": "https://matthewshirtliffe.co.uk/", "kgq193": "https://matthewshirtliffe.co.uk/", "bmd844": "https://matthewshirtliffe.co.uk/", "psq841": "https://stackoverflow.com/questions/35107885/how-to-generate-dynamic-urls-in-flask", "gzi932": "https://stackoverflow.com/questions/35107885/how-to-generate-dynamic-urls-in-flask", "nqu677": "https://stackoverflow.com/35107885/how-to-generate-dynamic-urls-in-flask", "elo901": "https://stackoverflow.com", "jeq805": "https://matthewshirtliffe.co.uk/", "uah996": "https://matthewshirtliffe.co.uk/", "lmk766": "https://matthewshirtliffe.co.uk/", "hej309": "https://stackoverflow.com", "iah536": "https://stackoverflow.com", "gqv243": "https://stackoverflow.com", "hev402": "https://stackoverflow.com", "rnh112": "https://stackoverflow.comsdfdsfds", "hey227": "https://www.dummies.com/programming/python/how-to-delete-a-file-in-python/"}

0 commit comments

Comments
 (0)