From fee5801db61fe50b64755fb0e100f10ab72bb66c Mon Sep 17 00:00:00 2001 From: Scott Wallace Date: Tue, 28 Dec 2021 13:51:57 +0000 Subject: [PATCH] Add listing --- slinky/__init__.py | 16 ++++++++++++++-- slinky/templates/add.html | 6 +++--- slinky/templates/list.html | 35 +++++++++++++++++++++++++++++++++++ slinky/web.py | 26 +++++++++++++++++++++----- templates/_head.html | 3 +++ tests/test_slinky.py | 12 ++++++++++++ tests/test_web.py | 18 ++++++++++++++++++ 7 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 slinky/templates/list.html diff --git a/slinky/__init__.py b/slinky/__init__.py index cd019c8..f524612 100644 --- a/slinky/__init__.py +++ b/slinky/__init__.py @@ -46,8 +46,8 @@ class Slinky: Class for Slinky """ - def __init__(self, url: str) -> None: - self.db = db.ShortcodeDB(url) # pylint: disable=invalid-name + def __init__(self, db_url: str) -> None: + self.db = db.ShortcodeDB(db_url) # pylint: disable=invalid-name self.session = self.db.session() def add( @@ -118,3 +118,15 @@ class Slinky: {db.ShortURL.fixed_views: db.ShortURL.fixed_views - 1} ) self.session.commit() + + def get_all(self) -> list[Shortcode]: + """ + Return a Shortcode object for a given shortcode + + Args: + shortcode (str): the shortcode to look up + + Returns: + Shortcode: full Shortcode object for the given shortcode + """ + return list(self.session.query(db.ShortURL).all()) diff --git a/slinky/templates/add.html b/slinky/templates/add.html index 9a943da..61d40c8 100644 --- a/slinky/templates/add.html +++ b/slinky/templates/add.html @@ -1,4 +1,4 @@ -{% include '_head.html' %} +{% include '_head.html' -%}
@@ -9,7 +9,7 @@
{{ form.url.label }} {{ form.url }}
{{ form.length.label }} {{ form.length }}
- {{ form.fixed_views.label }} {{ form.fixed_views }} (0 = unlimited)
+ {{ form.fixed_views.label }} {{ form.fixed_views }} (-1 for unlimited)
{{ form.expiry.label}} {{ form.expiry(class='datepicker') }} (leave as default for unlimited)
-{% include '_tail.html' %} +{% include '_tail.html' -%} diff --git a/slinky/templates/list.html b/slinky/templates/list.html new file mode 100644 index 0000000..8c5cd90 --- /dev/null +++ b/slinky/templates/list.html @@ -0,0 +1,35 @@ +{% include '_head.html' -%} + + +
+
+

Add a shortcode

+
+
+
+ {% if shortcodes -%} + + + + + + + + + + + {% for shortcode in shortcodes -%} + + + + + + + {% endfor -%} + +
ShortcodeURLRemaining viewsExpiry date
{{ shortcode.shortcode }}{{ shortcode.url }}{{ shortcode.fixed_views if shortcode.fixed_views >= 0 else 'Unlimited' }}{{ shortcode.expiry if shortcode.expiry != '9999-12-31 23:59:59.999999' else 'None' }}
+ {% endif -%} +
+
+ +{% include '_tail.html' -%} diff --git a/slinky/web.py b/slinky/web.py index b87d61b..66503af 100644 --- a/slinky/web.py +++ b/slinky/web.py @@ -65,7 +65,7 @@ def try_path_as_shortcode(path: str) -> Response: Try the initial path as a shortcode, redirect if found Returns: - Optional[Response]: redirect if found, otherwise continue on + Response: redirect if found, otherwise 404 """ should_redirect = True slinky = Slinky(cfg['db']) @@ -87,12 +87,12 @@ def try_path_as_shortcode(path: str) -> Response: @slinky_webapp.route('/_/add', methods=['GET', 'POST']) -def add() -> str: +def add() -> Response: """ Create and add a new shorturl Returns: - str: shortcode for the URL + str: HTTP response """ shortcode = '' url = '' @@ -107,11 +107,27 @@ def add() -> str: if url: slinky = Slinky(cfg['db']) - while True: + for attempts in range(50): try: shortcode = slinky.add(url, length, fixed_views, expiry) break except ValueError: - logging.warning('Shortcode already exists. Retrying.') + logging.warning( + 'Shortcode already exists. Retrying (%d/50).', attempts + ) + else: + return Response('Could not create a unique shortcode', 500) return render_template('add.html', form=form, shortcode=shortcode) + + +@slinky_webapp.route('/_/list', methods=['GET', 'POST']) +def lister() -> str: + """ + Create and add a new shorturl + + Returns: + str: shortcode for the URL + """ + slinky = Slinky(cfg['db']) + return render_template('list.html', shortcodes=slinky.get_all()) diff --git a/templates/_head.html b/templates/_head.html index e15f33f..23ce692 100644 --- a/templates/_head.html +++ b/templates/_head.html @@ -89,6 +89,9 @@ + diff --git a/tests/test_slinky.py b/tests/test_slinky.py index 1e40b24..033571e 100644 --- a/tests/test_slinky.py +++ b/tests/test_slinky.py @@ -56,3 +56,15 @@ class TestSlinky(TestCase): Slinky(self.test_db).add, 'https://www.example.com', ) + + def test_get_all(self) -> None: + """ + Ensure multiple results are returned and that they return all fields + """ + shortcodes = Slinky(self.test_db).get_all() + + self.assertGreater(len(shortcodes), 1) + assert hasattr(shortcodes[0], 'shortcode') + assert hasattr(shortcodes[0], 'url') + assert hasattr(shortcodes[0], 'fixed_views') + assert hasattr(shortcodes[0], 'expiry') diff --git a/tests/test_web.py b/tests/test_web.py index 4484d82..3f47f19 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -6,6 +6,7 @@ from unittest import TestCase, mock from slinky import web + @mock.patch.dict('slinky.web.cfg', {'db': 'sqlite:///tests/test.db'}) class TestWeb(TestCase): """ @@ -33,3 +34,20 @@ class TestWeb(TestCase): """ response = web.try_path_as_shortcode('egif') self.assertEqual(response.status_code, 404) + + @mock.patch( + 'slinky.web.ShortURLForm', + return_value=mock.Mock( + shortcode=mock.Mock(data=''), + url=mock.Mock(data='https://example.com'), + fixed_views=mock.Mock(data=0), + expiry=mock.Mock(data='1970-01-01 00:00:00.000000'), + ), + ) + @mock.patch('slinky.random_string', return_value='egie') + def test_no_unique_shortcode(self, *_: Any) -> None: + """ + Ensure non-unique shortcodes return a 500 error + """ + response = web.add() + self.assertEqual(response.status_code, 500)