django-link-shortener
Advanced tools
| from shortener.models import UrlMap, UrlProfile | ||
| from django.conf import settings | ||
| from django.db import IntegrityError | ||
| from datetime import timedelta | ||
| from django.utils import timezone | ||
| import random | ||
| def get_random(tries=0): | ||
| length = getattr(settings, 'SHORTENER_LENGTH', 5) | ||
| length += tries | ||
| # Removed l, I, 1 | ||
| dictionary = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz234567890" | ||
| return ''.join(random.choice(dictionary) for _ in range(length)) | ||
| def create(user, link): | ||
| # check if user allowed to save link | ||
| try: | ||
| # use user settings | ||
| p = UrlProfile.objects.get(user=user) | ||
| enabled = p.enabled | ||
| max_urls = p.max_urls | ||
| max_concurrent = p.max_concurrent_urls | ||
| lifespan = p.default_lifespan | ||
| max_uses = p.default_max_uses | ||
| except UrlProfile.DoesNotExist: | ||
| # Use defaults from settings | ||
| enabled = getattr(settings, 'SHORTENER_ENABLED', True) | ||
| max_urls = getattr(settings, 'SHORTENER_MAX_URLS', -1) | ||
| max_concurrent = getattr(settings, 'SHORTENER_MAX_CONCURRENT', -1) | ||
| lifespan = getattr(settings, 'SHORTENER_LIFESPAN', -1) | ||
| max_uses = getattr(settings, 'SHORTENER_MAX_USES', -1) | ||
| # Ensure User is allowed to create | ||
| if not enabled: | ||
| raise PermissionError("not authorized to create shortlinks") | ||
| # Expiry date, -1 to disable | ||
| if lifespan != -1: | ||
| expiry_date = timezone.now() + timedelta(seconds=lifespan) | ||
| else: | ||
| expiry_date = timezone.make_aware(timezone.datetime.max, timezone.get_default_timezone()) | ||
| # Ensure user has not met max_urls quota | ||
| if max_urls != -1: | ||
| if UrlMap.objects.filter(user=user).count() >= max_urls: | ||
| raise PermissionError("url quota exceeded") | ||
| # Ensure user has not met concurrent urls quota | ||
| if max_concurrent != -1: | ||
| if UrlMap.objects.filter(user=user, date_expired__gt=timezone.now()).count() >= max_concurrent: | ||
| raise PermissionError("concurrent quota exceeded") | ||
| # Try up to three times to generate a random number without duplicates. | ||
| # Each time increase the number of allowed characters | ||
| for tries in range(3): | ||
| try: | ||
| short = get_random(tries) | ||
| m = UrlMap(user=user, full_url=link, short_url=short, max_count=max_uses, date_expired=expiry_date) | ||
| m.save() | ||
| return m.short_url | ||
| except IntegrityError: | ||
| continue | ||
| raise KeyError("Could not generate unique shortlink") | ||
| def expand(link): | ||
| try: | ||
| url = UrlMap.objects.get(short_url__exact=link) | ||
| except UrlMap.DoesNotExist: | ||
| raise KeyError("invalid shortlink") | ||
| # ensure we are within usage counts | ||
| if url.max_count != -1: | ||
| if url.max_count <= url.usage_count: | ||
| raise PermissionError("max usages for link reached") | ||
| # ensure we are within allowed datetime | ||
| # print(timezone.now()) | ||
| # print(url.date_expired) | ||
| if timezone.now() > url.date_expired: | ||
| raise PermissionError("shortlink expired") | ||
| url.usage_count += 1 | ||
| url.save() | ||
| return url.full_url | ||
| Metadata-Version: 1.1 | ||
| Name: django-link-shortener | ||
| Version: 0.1 | ||
| Version: 0.3 | ||
| Summary: A simple Django Link Shortener. | ||
| Home-page: https://www.example.com/ | ||
| Home-page: https://github.com/ronaldgrn/django-link-shortener | ||
| Author: Petronald Green | ||
| Author-email: petronaldgreen@gmail.com | ||
| License: MIT License | ||
| Download-URL: https://github.com/ronaldgrn/django-link-shortener/archive/0.2.tar.gz | ||
| Description: ===================== | ||
@@ -13,10 +14,27 @@ django-link-shortener | ||
| django-link-shortener is a simple time and usage quantity sensitive | ||
| url shortening app | ||
| .. image:: https://travis-ci.org/ronaldgrn/django-link-shortener.svg?branch=master | ||
| :target: https://travis-ci.org/ronaldgrn/django-link-shortener | ||
| .. image:: https://img.shields.io/pypi/l/django-link-shortener.svg | ||
| :alt: PyPI - License | ||
| :target: https://pypi.org/project/django-link-shortener/ | ||
| Quick start | ||
| ----------- | ||
| .. image:: https://img.shields.io/pypi/v/django-link-shortener.svg | ||
| :alt: PyPI | ||
| :target: https://pypi.org/project/django-link-shortener/ | ||
| 1. Add "shortener" to your INSTALLED_APPS setting like this:: | ||
| django-link-shortener is a simple time and usage sensitive url shortening app. | ||
| Uses A-Za-z0-9 with the exception of I, i and 1. | ||
| Requires user to be logged in for link creation. | ||
| Usage | ||
| ----- | ||
| 1. pip install django-link-shortener | ||
| 2. Add "shortener" to your INSTALLED_APPS setting like this:: | ||
| INSTALLED_APPS = [ | ||
@@ -27,12 +45,101 @@ ... | ||
| 2. Include the polls URLconf in your project urls.py like this:: | ||
| 3. Include the polls URLconf in your project urls.py like this:: | ||
| path('s/', include('shortener.urls')), | ||
| 3. Run `python manage.py migrate` to create the shortener models. | ||
| 4. Run `python manage.py migrate` to create the shortener models. | ||
| 4. Start the development server and visit http://127.0.0.1:8000/s/ | ||
| to create a test link | ||
| 5. Visit http://127.0.0.1:8000/<insert_test_code_here>/ to be redirected | ||
| Testing | ||
| ------- | ||
| 1. Add the following to settings | ||
| ``` | ||
| SHORTENER_ENABLE_TEST_PATH = True | ||
| ``` | ||
| 1. Start the development server and visit http://127.0.0.1:8000/s/test/<My-URL-HERE> | ||
| to create a test shortcode. | ||
| or | ||
| Use shortener.create(user, link) to generate a link via code. Use shortener.expand(link) | ||
| to revert | ||
| 6. Visit http://127.0.0.1:8000/s/<shortcode>/ to be redirected | ||
| Configuration Options | ||
| --------------------- | ||
| Place in settings.py. Each setting be overridden on a per-user basis using the admin UrlProfile section | ||
| SHORTENER_ENABLED | ||
| Default: True | ||
| Controls whether users without a shortener profile can create shortlinks. | ||
| SHORTENER_MAX_URLS | ||
| Default: -1 | ||
| Controls the default maximum limit of generated urls per account. | ||
| -1 sets infinite. | ||
| SHORTENER_MAX_CONCURRENT | ||
| Default: -1 | ||
| Controls the default maximum limit of *concurrent* (active) generated urls per account. | ||
| -1 sets infinite | ||
| SHORTENER_LIFESPAN | ||
| Default: -1 | ||
| Sets the default lifespan of links in seconds | ||
| -1 sets infinite | ||
| SHORTENER_MAX_USES | ||
| Default: -1 | ||
| Sets the default amount of times a link can be followed | ||
| -1 sets infinite | ||
| SHORTENER_LENGTH | ||
| Default: 5 | ||
| Note: Omitted from UrlProfile | ||
| Sets how many digits should be used for links. | ||
| Tries up to three times to generate a unique shortcode where | ||
| Each failure will result in length temporaily being increased by 1. | ||
| SHORTENER_ENABLE_TEST_PATH | ||
| Default: False | ||
| If true, creates shortlinks for logged in users at s/test/<<url>>/ | ||
| The response is the shortcode to use used at s/<<shortcode>> | ||
| Common Use Cases | ||
| ---------------- | ||
| goo.gl type usage (default). Unlimited concurrent links for an unlimited length of time | ||
| :: | ||
| SHORTENER_ENABLED = True | ||
| SHORTENER_MAX_URLS = -1 | ||
| SHORTENER_MAX_CONCURRENT = -1 | ||
| SHORTENER_LIFESPAN = -1 | ||
| SHORTENER_MAX_USES = -1 | ||
| Internal temporary link usage (such as on nodeferret.com). 100 Temp links per minute. 1 usage per link. | ||
| :: | ||
| SHORTENER_ENABLED = True | ||
| SHORTENER_MAX_URLS = -1 | ||
| SHORTENER_MAX_CONCURRENT = 100 # To prevent spamming | ||
| SHORTENER_LIFESPAN = 600 | ||
| SHORTENER_MAX_USES = 1 | ||
| Keywords: url shortener,link shortener | ||
| Platform: UNKNOWN | ||
@@ -39,0 +146,0 @@ Classifier: Environment :: Web Environment |
@@ -13,3 +13,3 @@ LICENSE | ||
| shortener/models.py | ||
| shortener/shortern.py | ||
| shortener/shortener.py | ||
| shortener/tests.py | ||
@@ -16,0 +16,0 @@ shortener/urls.py |
+119
-12
| Metadata-Version: 1.1 | ||
| Name: django-link-shortener | ||
| Version: 0.1 | ||
| Version: 0.3 | ||
| Summary: A simple Django Link Shortener. | ||
| Home-page: https://www.example.com/ | ||
| Home-page: https://github.com/ronaldgrn/django-link-shortener | ||
| Author: Petronald Green | ||
| Author-email: petronaldgreen@gmail.com | ||
| License: MIT License | ||
| Download-URL: https://github.com/ronaldgrn/django-link-shortener/archive/0.2.tar.gz | ||
| Description: ===================== | ||
@@ -13,10 +14,27 @@ django-link-shortener | ||
| django-link-shortener is a simple time and usage quantity sensitive | ||
| url shortening app | ||
| .. image:: https://travis-ci.org/ronaldgrn/django-link-shortener.svg?branch=master | ||
| :target: https://travis-ci.org/ronaldgrn/django-link-shortener | ||
| .. image:: https://img.shields.io/pypi/l/django-link-shortener.svg | ||
| :alt: PyPI - License | ||
| :target: https://pypi.org/project/django-link-shortener/ | ||
| Quick start | ||
| ----------- | ||
| .. image:: https://img.shields.io/pypi/v/django-link-shortener.svg | ||
| :alt: PyPI | ||
| :target: https://pypi.org/project/django-link-shortener/ | ||
| 1. Add "shortener" to your INSTALLED_APPS setting like this:: | ||
| django-link-shortener is a simple time and usage sensitive url shortening app. | ||
| Uses A-Za-z0-9 with the exception of I, i and 1. | ||
| Requires user to be logged in for link creation. | ||
| Usage | ||
| ----- | ||
| 1. pip install django-link-shortener | ||
| 2. Add "shortener" to your INSTALLED_APPS setting like this:: | ||
| INSTALLED_APPS = [ | ||
@@ -27,12 +45,101 @@ ... | ||
| 2. Include the polls URLconf in your project urls.py like this:: | ||
| 3. Include the polls URLconf in your project urls.py like this:: | ||
| path('s/', include('shortener.urls')), | ||
| 3. Run `python manage.py migrate` to create the shortener models. | ||
| 4. Run `python manage.py migrate` to create the shortener models. | ||
| 4. Start the development server and visit http://127.0.0.1:8000/s/ | ||
| to create a test link | ||
| 5. Visit http://127.0.0.1:8000/<insert_test_code_here>/ to be redirected | ||
| Testing | ||
| ------- | ||
| 1. Add the following to settings | ||
| ``` | ||
| SHORTENER_ENABLE_TEST_PATH = True | ||
| ``` | ||
| 1. Start the development server and visit http://127.0.0.1:8000/s/test/<My-URL-HERE> | ||
| to create a test shortcode. | ||
| or | ||
| Use shortener.create(user, link) to generate a link via code. Use shortener.expand(link) | ||
| to revert | ||
| 6. Visit http://127.0.0.1:8000/s/<shortcode>/ to be redirected | ||
| Configuration Options | ||
| --------------------- | ||
| Place in settings.py. Each setting be overridden on a per-user basis using the admin UrlProfile section | ||
| SHORTENER_ENABLED | ||
| Default: True | ||
| Controls whether users without a shortener profile can create shortlinks. | ||
| SHORTENER_MAX_URLS | ||
| Default: -1 | ||
| Controls the default maximum limit of generated urls per account. | ||
| -1 sets infinite. | ||
| SHORTENER_MAX_CONCURRENT | ||
| Default: -1 | ||
| Controls the default maximum limit of *concurrent* (active) generated urls per account. | ||
| -1 sets infinite | ||
| SHORTENER_LIFESPAN | ||
| Default: -1 | ||
| Sets the default lifespan of links in seconds | ||
| -1 sets infinite | ||
| SHORTENER_MAX_USES | ||
| Default: -1 | ||
| Sets the default amount of times a link can be followed | ||
| -1 sets infinite | ||
| SHORTENER_LENGTH | ||
| Default: 5 | ||
| Note: Omitted from UrlProfile | ||
| Sets how many digits should be used for links. | ||
| Tries up to three times to generate a unique shortcode where | ||
| Each failure will result in length temporaily being increased by 1. | ||
| SHORTENER_ENABLE_TEST_PATH | ||
| Default: False | ||
| If true, creates shortlinks for logged in users at s/test/<<url>>/ | ||
| The response is the shortcode to use used at s/<<shortcode>> | ||
| Common Use Cases | ||
| ---------------- | ||
| goo.gl type usage (default). Unlimited concurrent links for an unlimited length of time | ||
| :: | ||
| SHORTENER_ENABLED = True | ||
| SHORTENER_MAX_URLS = -1 | ||
| SHORTENER_MAX_CONCURRENT = -1 | ||
| SHORTENER_LIFESPAN = -1 | ||
| SHORTENER_MAX_USES = -1 | ||
| Internal temporary link usage (such as on nodeferret.com). 100 Temp links per minute. 1 usage per link. | ||
| :: | ||
| SHORTENER_ENABLED = True | ||
| SHORTENER_MAX_URLS = -1 | ||
| SHORTENER_MAX_CONCURRENT = 100 # To prevent spamming | ||
| SHORTENER_LIFESPAN = 600 | ||
| SHORTENER_MAX_USES = 1 | ||
| Keywords: url shortener,link shortener | ||
| Platform: UNKNOWN | ||
@@ -39,0 +146,0 @@ Classifier: Environment :: Web Environment |
+114
-10
@@ -5,10 +5,27 @@ ===================== | ||
| django-link-shortener is a simple time and usage quantity sensitive | ||
| url shortening app | ||
| .. image:: https://travis-ci.org/ronaldgrn/django-link-shortener.svg?branch=master | ||
| :target: https://travis-ci.org/ronaldgrn/django-link-shortener | ||
| .. image:: https://img.shields.io/pypi/l/django-link-shortener.svg | ||
| :alt: PyPI - License | ||
| :target: https://pypi.org/project/django-link-shortener/ | ||
| Quick start | ||
| ----------- | ||
| .. image:: https://img.shields.io/pypi/v/django-link-shortener.svg | ||
| :alt: PyPI | ||
| :target: https://pypi.org/project/django-link-shortener/ | ||
| 1. Add "shortener" to your INSTALLED_APPS setting like this:: | ||
| django-link-shortener is a simple time and usage sensitive url shortening app. | ||
| Uses A-Za-z0-9 with the exception of I, i and 1. | ||
| Requires user to be logged in for link creation. | ||
| Usage | ||
| ----- | ||
| 1. pip install django-link-shortener | ||
| 2. Add "shortener" to your INSTALLED_APPS setting like this:: | ||
| INSTALLED_APPS = [ | ||
@@ -19,11 +36,98 @@ ... | ||
| 2. Include the polls URLconf in your project urls.py like this:: | ||
| 3. Include the polls URLconf in your project urls.py like this:: | ||
| path('s/', include('shortener.urls')), | ||
| 3. Run `python manage.py migrate` to create the shortener models. | ||
| 4. Run `python manage.py migrate` to create the shortener models. | ||
| 4. Start the development server and visit http://127.0.0.1:8000/s/ | ||
| to create a test link | ||
| 5. Visit http://127.0.0.1:8000/<insert_test_code_here>/ to be redirected | ||
| Testing | ||
| ------- | ||
| 1. Add the following to settings | ||
| ``` | ||
| SHORTENER_ENABLE_TEST_PATH = True | ||
| ``` | ||
| 1. Start the development server and visit http://127.0.0.1:8000/s/test/<My-URL-HERE> | ||
| to create a test shortcode. | ||
| or | ||
| Use shortener.create(user, link) to generate a link via code. Use shortener.expand(link) | ||
| to revert | ||
| 6. Visit http://127.0.0.1:8000/s/<shortcode>/ to be redirected | ||
| Configuration Options | ||
| --------------------- | ||
| Place in settings.py. Each setting be overridden on a per-user basis using the admin UrlProfile section | ||
| SHORTENER_ENABLED | ||
| Default: True | ||
| Controls whether users without a shortener profile can create shortlinks. | ||
| SHORTENER_MAX_URLS | ||
| Default: -1 | ||
| Controls the default maximum limit of generated urls per account. | ||
| -1 sets infinite. | ||
| SHORTENER_MAX_CONCURRENT | ||
| Default: -1 | ||
| Controls the default maximum limit of *concurrent* (active) generated urls per account. | ||
| -1 sets infinite | ||
| SHORTENER_LIFESPAN | ||
| Default: -1 | ||
| Sets the default lifespan of links in seconds | ||
| -1 sets infinite | ||
| SHORTENER_MAX_USES | ||
| Default: -1 | ||
| Sets the default amount of times a link can be followed | ||
| -1 sets infinite | ||
| SHORTENER_LENGTH | ||
| Default: 5 | ||
| Note: Omitted from UrlProfile | ||
| Sets how many digits should be used for links. | ||
| Tries up to three times to generate a unique shortcode where | ||
| Each failure will result in length temporaily being increased by 1. | ||
| SHORTENER_ENABLE_TEST_PATH | ||
| Default: False | ||
| If true, creates shortlinks for logged in users at s/test/<<url>>/ | ||
| The response is the shortcode to use used at s/<<shortcode>> | ||
| Common Use Cases | ||
| ---------------- | ||
| goo.gl type usage (default). Unlimited concurrent links for an unlimited length of time | ||
| :: | ||
| SHORTENER_ENABLED = True | ||
| SHORTENER_MAX_URLS = -1 | ||
| SHORTENER_MAX_CONCURRENT = -1 | ||
| SHORTENER_LIFESPAN = -1 | ||
| SHORTENER_MAX_USES = -1 | ||
| Internal temporary link usage (such as on nodeferret.com). 100 Temp links per minute. 1 usage per link. | ||
| :: | ||
| SHORTENER_ENABLED = True | ||
| SHORTENER_MAX_URLS = -1 | ||
| SHORTENER_MAX_CONCURRENT = 100 # To prevent spamming | ||
| SHORTENER_LIFESPAN = 600 | ||
| SHORTENER_MAX_USES = 1 |
+5
-3
@@ -12,4 +12,4 @@ import os | ||
| name='django-link-shortener', | ||
| version='0.1', | ||
| packages=find_packages(), | ||
| version='0.3', | ||
| packages=['shortener'], | ||
| include_package_data=True, | ||
@@ -19,5 +19,7 @@ license='MIT License', | ||
| long_description=README, | ||
| url='https://www.example.com/', | ||
| url='https://github.com/ronaldgrn/django-link-shortener', | ||
| download_url='https://github.com/ronaldgrn/django-link-shortener/archive/0.2.tar.gz', | ||
| author='Petronald Green', | ||
| author_email='petronaldgreen@gmail.com', | ||
| keywords=['url shortener', 'link shortener'], | ||
| classifiers=[ | ||
@@ -24,0 +26,0 @@ 'Environment :: Web Environment', |
| from django.contrib import admin | ||
| from .models import UrlMap, UrlProfile | ||
| # Register your models here. | ||
| admin.site.register(UrlMap) | ||
| admin.site.register(UrlProfile) |
@@ -1,2 +0,2 @@ | ||
| # Generated by Django 2.0.5 on 2018-05-31 23:29 | ||
| # Generated by Django 2.0.5 on 2018-06-01 03:53 | ||
@@ -6,3 +6,2 @@ from django.conf import settings | ||
| import django.db.models.deletion | ||
| import shortener.models | ||
@@ -29,3 +28,3 @@ | ||
| ('date_created', models.DateTimeField(auto_now_add=True)), | ||
| ('date_expired', models.DateTimeField(default=shortener.models.get_expiry)), | ||
| ('date_expired', models.DateTimeField()), | ||
| ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||
@@ -39,6 +38,6 @@ ], | ||
| ('enabled', models.BooleanField(default=True)), | ||
| ('url_count', models.IntegerField(default=0)), | ||
| ('max_urls', models.IntegerField(default=-1)), | ||
| ('max_concurrent_urls', models.IntegerField(default=100)), | ||
| ('default_lifespan', models.IntegerField(default=120)), | ||
| ('default_max_uses', models.IntegerField(default=-1)), | ||
| ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||
@@ -45,0 +44,0 @@ ], |
| from django.db import models | ||
| from django.contrib.auth.models import User | ||
| from django.conf import settings | ||
| from datetime import datetime, timedelta | ||
@@ -7,8 +7,4 @@ | ||
| # Create your models here. | ||
| def get_expiry(): | ||
| return datetime.now() + timedelta(seconds=120) | ||
| class UrlMap(models.Model): | ||
| user = models.ForeignKey(User, on_delete=models.CASCADE) | ||
| user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) | ||
| full_url = models.CharField(max_length=256) | ||
@@ -20,3 +16,3 @@ short_url = models.CharField(max_length=50, unique=True, db_index=True) | ||
| date_created = models.DateTimeField(auto_now_add=True) | ||
| date_expired = models.DateTimeField(default=get_expiry) | ||
| date_expired = models.DateTimeField() | ||
@@ -28,5 +24,4 @@ def __str__(self): | ||
| class UrlProfile(models.Model): | ||
| user = models.OneToOneField(User, on_delete=models.CASCADE) | ||
| user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) | ||
| enabled = models.BooleanField(default=True) | ||
| url_count = models.IntegerField(default=0) | ||
| max_urls = models.IntegerField(default=-1) | ||
@@ -37,1 +32,2 @@ max_concurrent_urls = models.IntegerField(default=100) | ||
| default_lifespan = models.IntegerField(default=120) | ||
| default_max_uses = models.IntegerField(default=-1) |
+55
-1
| from django.test import TestCase | ||
| from shortener import shortener | ||
| from tests.testapp.models import CustomUser | ||
| import time | ||
| # Create your tests here. | ||
| class UrlMapTestCase(TestCase): | ||
| def setUp(self): | ||
| self.bob = CustomUser.objects.create_user('bob', 'bob@bob.com', 'bobpassword') | ||
| self.alice = CustomUser.objects.create_user('alice', 'alice@alice.com', 'alicepassword') | ||
| def test_url_creation(self): | ||
| url = shortener.create(self.bob, "http://devget.net/") | ||
| self.assertEqual(shortener.expand(url), "http://devget.net/") | ||
| def test_invalid_link(self): | ||
| url = shortener.create(self.bob, "http://devget.net/") | ||
| self.assertEqual(shortener.expand(url), "http://devget.net/") # good shortlink | ||
| with self.assertRaisesMessage(KeyError, 'invalid shortlink'): | ||
| self.assertEqual(shortener.expand('photosynthesis'), "http://devget.net/") # bad shortlink | ||
| def test_shortener_enabled_setting(self): | ||
| with self.settings(SHORTENER_ENABLED=False): | ||
| with self.assertRaisesMessage(PermissionError, 'not authorized to create shortlinks'): | ||
| url = shortener.create(self.bob, "http://devget.net/") | ||
| def test_max_urls_setting(self): | ||
| with self.settings(SHORTENER_MAX_URLS=2): | ||
| shortener.create(self.bob, "http://devget.net/") | ||
| shortener.create(self.bob, "http://devget.net/") | ||
| with self.assertRaisesMessage(PermissionError, 'url quota exceeded'): | ||
| shortener.create(self.bob, "http://devget.net/") | ||
| def test_max_concurrent_setting(self): | ||
| with self.settings(SHORTENER_MAX_CONCURRENT=2): | ||
| shortener.create(self.bob, "http://devget.net/") | ||
| shortener.create(self.bob, "http://devget.net/") | ||
| with self.assertRaisesMessage(PermissionError, 'concurrent quota exceeded'): | ||
| shortener.create(self.bob, "http://devget.net/") | ||
| def test_lifespan_setting(self): | ||
| with self.settings(SHORTENER_LIFESPAN=4): # 4 seconds | ||
| url = shortener.create(self.bob, "http://blog.devget.net/") | ||
| self.assertEqual(shortener.expand(url), "http://blog.devget.net/") | ||
| time.sleep(5) | ||
| with self.assertRaisesMessage(PermissionError, 'shortlink expired'): | ||
| self.assertEqual(shortener.expand(url), "http://blog.devget.net/") | ||
| def test_max_uses_setting(self): | ||
| with self.settings(SHORTENER_MAX_USES=2): | ||
| url = shortener.create(self.bob, "http://blog.devget.net/") | ||
| self.assertEqual(shortener.expand(url), "http://blog.devget.net/") | ||
| self.assertEqual(shortener.expand(url), "http://blog.devget.net/") | ||
| with self.assertRaisesMessage(PermissionError, 'max usages for link reached'): | ||
| # Ensure error is raised when we hit limit | ||
| shortener.expand(url) | ||
| from django.urls import path | ||
| from shortener import views | ||
| from django.conf import settings | ||
| urlpatterns = [ | ||
| path('', views.index), | ||
| path('<link>/', views.expand), | ||
| ] | ||
| if getattr(settings, 'SHORTENER_ENABLE_TEST_PATH', False): | ||
| urlpatterns.append(path('test/<path:link>', views.test)) |
+12
-10
| from django.shortcuts import redirect | ||
| from django.http import HttpResponse | ||
| from shortener import shortern | ||
| from shortener import shortener | ||
| # Create your views here. | ||
| def index(request): | ||
| data = shortern.convert(request.user, "http://google.com/") | ||
| return HttpResponse(data) | ||
| def test(request, link): | ||
| if request.user.is_authenticated: | ||
| data = shortener.create(request.user, link) | ||
| return HttpResponse(data) | ||
| else: | ||
| return HttpResponse('unauthorized') | ||
| def expand(request, link): | ||
| link = shortern.revert(link) | ||
| if link: | ||
| # return HttpResponse(shortern.revert(link)) | ||
| return redirect(str(link.full_url)) # TODO: permanent=True | ||
| else: | ||
| HttpResponse("Something bad happened") | ||
| try: | ||
| link = shortener.expand(link) | ||
| return redirect(link) # TODO: permanent=True | ||
| except Exception as e: | ||
| return HttpResponse(e.args) |
| from shortener.models import UrlMap, UrlProfile | ||
| from django.conf import settings | ||
| from django.db import IntegrityError | ||
| import random | ||
| import string | ||
| def get_random(tries=0): | ||
| if hasattr(settings, 'SHORTENER_LENGTH'): | ||
| length = settings.SHORTENER_LENGTH | ||
| else: | ||
| length = 5 | ||
| length += tries | ||
| # Removed l, I, 1 | ||
| # return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length)) | ||
| return ''.join(random.choice("ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz234567890") for _ in range(length)) | ||
| def convert(user, link): | ||
| # Try up to three times to generate a random number without duplicates. | ||
| # Each time increase the number of allowed characters | ||
| for tries in range(3): | ||
| try: | ||
| short = get_random(tries) | ||
| m = UrlMap(user=user, full_url=link, short_url=short) | ||
| m.save() | ||
| return m.short_url | ||
| except IntegrityError: | ||
| continue | ||
| raise KeyError("Could not generate unique shortlink") | ||
| def revert(link): | ||
| try: | ||
| url = UrlMap.objects.get(short_url__exact=link) | ||
| return url | ||
| except UrlMap.DoesNotExist: | ||
| return -1 # TODO: improve this | ||
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
26290
124.57%244
60.53%