aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVladimir Gorej2012-05-27 20:14:17 +0200
committerVladimir Gorej2012-05-27 20:14:17 +0200
commit4bd7b72aa0ca3a09ab99e1a3b41753df81de6af9 (patch)
tree6328ed9894666eefc1f0d6b6c7b28a87b1fb4d31
downloaddjango-brevisurl-4bd7b72aa0ca3a09ab99e1a3b41753df81de6af9.tar.bz2
Initial commit
-rw-r--r--.gitignore8
-rw-r--r--LICENSE26
-rw-r--r--README.rst0
-rw-r--r--brevisurl/__init__.py37
-rw-r--r--brevisurl/backends/__init__.py0
-rw-r--r--brevisurl/backends/base.py41
-rw-r--r--brevisurl/backends/local.py53
-rw-r--r--brevisurl/models.py58
-rw-r--r--brevisurl/settings.py3
-rw-r--r--brevisurl/templatetags/__init__.py0
-rw-r--r--brevisurl/templatetags/brevisurltags.py11
-rw-r--r--brevisurl/tests/__init__.py6
-rw-r--r--brevisurl/tests/backends/__init__.py1
-rw-r--r--brevisurl/tests/backends/test_local.py55
-rw-r--r--brevisurl/tests/templatetags/__init__.py0
-rw-r--r--brevisurl/tests/templatetags/test_brevisurltags.py27
-rw-r--r--brevisurl/tests/test_brevisurl.py22
-rw-r--r--brevisurl/tests/test_models.py38
-rw-r--r--brevisurl/tests/test_utils.py22
-rw-r--r--brevisurl/tests/test_views.py28
-rw-r--r--brevisurl/urls.py8
-rw-r--r--brevisurl/utils.py18
-rw-r--r--brevisurl/views.py36
23 files changed, 498 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bab1113
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+*.pyc
+*.pyo
+*.swp
+*.swo
+.idea
+pip-log.txt*
+*.swn
+/dist/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..813aeb1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+The New BSD License
+
+Copyright (c) 2012, CodeScale s.r.o.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of CodeScale s.r.o. nor the names of its contributors
+ may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/README.rst
diff --git a/brevisurl/__init__.py b/brevisurl/__init__.py
new file mode 100644
index 0000000..66f2efa
--- /dev/null
+++ b/brevisurl/__init__.py
@@ -0,0 +1,37 @@
+import brevisurl.settings
+from brevisurl.utils import load_object
+
+
+def get_connection(backend=None, fail_silently=False, **kwargs):
+ """Load a brevisurl backend and return an instance of it.
+
+ If backend is None (default) settings.BREVISURL_BACKEND is used.
+ Both fail_silently and other keyword arguments are used in the
+ constructor of the backend.
+
+ :param backend: path to brevisurl backend
+ :type backend: string
+ :param fail_silently: whether to fail silently when error intercepted or not
+ :type fail_silently: bool
+ :returns: instance os BaseBrevisUrlBackend
+ :rtype: brevisurl.backends.base.BaseBrevisUrlBackend
+
+ """
+ path = backend or brevisurl.settings.DEFAULT_BACKEND
+ klass = load_object(path)
+ return klass(fail_silently=fail_silently, **kwargs)
+
+
+def shorten_url(original_url, fail_silently=False, connection=None):
+ """Shortcut util for shortening urls using default brevisurl backend if none supplied.
+
+ :param original_url: url that will be shortened
+ :type original_url: string
+ :param backend: one of brevisurl backends
+ :type backend: brevisurl.backends.BaseBrevisUrlBackend
+ :returns: shortened url from original url
+ :rtype: brevisurl.models.ShortUrl
+
+ """
+ connection = connection or get_connection(fail_silently=fail_silently)
+ return connection.shorten_url(original_url) \ No newline at end of file
diff --git a/brevisurl/backends/__init__.py b/brevisurl/backends/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/brevisurl/backends/__init__.py
diff --git a/brevisurl/backends/base.py b/brevisurl/backends/base.py
new file mode 100644
index 0000000..3fc053a
--- /dev/null
+++ b/brevisurl/backends/base.py
@@ -0,0 +1,41 @@
+from abc import ABCMeta
+
+
+class BaseBrevisUrlBackend(object):
+ """Base class for brevisurl backend implementations. Subclasses must at least overwrite shorten_url()."""
+
+ __metaclass__ = ABCMeta
+
+
+ def __init__(self, fail_silently=False, **kwargs):
+ self.fail_silently = fail_silently
+ self.class_path = '{0}.{1}'.format(self.__module__, self.__class__.__name__)
+
+ def open(self):
+ """Open a network connection.
+
+ This method can be overwritten by backend implementations to
+ open a network connection.
+
+ It's up to the backend implementation to track the status of
+ a network connection if it's needed by the backend.
+
+ The default implementation does nothing.
+
+ """
+ pass
+
+ def close(self):
+ """Close a network connection."""
+ pass
+
+ def shorten_url(self, original_url):
+ """Shortens url into more compact form.
+
+ :param original_url: url that will be shortened
+ :type original_url: string
+ :returns: shortened url from original url
+ :rtype: brevisurl.models.ShortUrl
+
+ """
+ raise NotImplementedError \ No newline at end of file
diff --git a/brevisurl/backends/local.py b/brevisurl/backends/local.py
new file mode 100644
index 0000000..42d0263
--- /dev/null
+++ b/brevisurl/backends/local.py
@@ -0,0 +1,53 @@
+import string
+import random
+import logging
+
+from django.contrib.sites.models import Site
+
+from brevisurl.backends.base import BaseBrevisUrlBackend
+from brevisurl.models import ShortUrl
+
+
+log = logging.getLogger(__name__)
+
+
+class BrevisUrlBackend(BaseBrevisUrlBackend):
+
+ PROTOCOL = 'http'
+
+ def shorten_url(self, original_url):
+ """
+ :raises: ImproperlyConfigured, django.core.exceptions.ValidationError
+ """
+ try:
+ short_url = ShortUrl.objects.get(backend=self.class_path, original_url=original_url)
+ log.info('Url "%s" already shortened to "%s"', original_url, short_url.shortened_url)
+ return short_url
+ except ShortUrl.DoesNotExist:
+ pass
+
+ try:
+ current_site = Site.objects.get_current()
+ short_url = ShortUrl()
+ short_url.original_url = original_url
+ short_url.shortened_url = '{0}://{1}/{2}'.format(self.PROTOCOL, current_site.domain,
+ self.__generate_token())
+ short_url.backend = self.class_path
+ short_url.save()
+ log.info('Url "%s" shortened to "%s"', original_url, short_url.shortened_url)
+ return short_url
+ except Exception:
+ if self.fail_silently:
+ return None
+ else:
+ log.exception('Unknown exception raised while shortening url "%s"', original_url)
+ raise
+
+ def __generate_token(self, size=5):
+ chars = list(string.ascii_letters + string.digits)
+ random.shuffle(chars)
+ while True:
+ token = ''.join([random.choice(chars) for x in range(size)])
+ if not ShortUrl.objects.filter(backend=self.class_path, shortened_url__endswith=token).count():
+ break
+ return token \ No newline at end of file
diff --git a/brevisurl/models.py b/brevisurl/models.py
new file mode 100644
index 0000000..a9264ab
--- /dev/null
+++ b/brevisurl/models.py
@@ -0,0 +1,58 @@
+import hashlib
+import logging
+from django.core.exceptions import ValidationError
+
+from django.db import models
+from django.core.validators import URLValidator
+
+from brevisurl import get_connection
+
+
+log = logging.getLogger(__name__)
+
+
+class ShortUrl(models.Model):
+ """Model that represents shortened url."""
+ original_url = models.URLField(null=False, blank=False)
+ original_url_hash = models.CharField(max_length=64, null=False, blank=False)
+ shortened_url = models.URLField(max_length=200, null=False, blank=False, unique=True)
+ backend = models.CharField(max_length=200, null=False, blank=False)
+ created = models.DateTimeField(auto_now_add=True, db_index=True, null=False, blank=False)
+
+ def __unicode__(self):
+ return self.shortened_url
+
+ def get_connection(self, fail_silently=False):
+ if not hasattr(self, 'brevis_connection'):
+ if self.pk is not None:
+ self.brevis_connection = get_connection(backend=self.backend, fail_silently=fail_silently)
+ else:
+ self.brevis_connection = get_connection(fail_silently=fail_silently)
+ return self.brevis_connection
+
+ def clean(self):
+ url_validator = URLValidator()
+ try:
+ url_validator(self.original_url)
+ except ValidationError:
+ log.exception('ShortUrl.original_url is not valid URL')
+ raise
+ try:
+ url_validator(self.shortened_url)
+ except ValidationError:
+ log.exception('ShortUrl.shortened_url is not valid URL')
+ raise
+ return super(ShortUrl, self).clean()
+
+ def save(self, force_insert=False, force_update=False, using=None):
+ if self.pk is None:
+ self.original_url_hash = hashlib.sha256(self.original_url).hexdigest()
+ self.full_clean()
+ return super(ShortUrl, self).save(force_insert, force_update, using)
+
+ class Meta:
+ unique_together = (('original_url_hash', 'backend'),)
+ verbose_name = 'Short url'
+ verbose_name_plural = 'Short urls'
+ ordering = ['-created']
+ get_latest_by = 'created' \ No newline at end of file
diff --git a/brevisurl/settings.py b/brevisurl/settings.py
new file mode 100644
index 0000000..590f8b1
--- /dev/null
+++ b/brevisurl/settings.py
@@ -0,0 +1,3 @@
+from django.conf import settings
+
+DEFAULT_BACKEND = getattr(settings, 'BREVISURL_BACKEND', 'brevisurl.backends.local.BrevisUrlBackend') \ No newline at end of file
diff --git a/brevisurl/templatetags/__init__.py b/brevisurl/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/brevisurl/templatetags/__init__.py
diff --git a/brevisurl/templatetags/brevisurltags.py b/brevisurl/templatetags/brevisurltags.py
new file mode 100644
index 0000000..2d883f0
--- /dev/null
+++ b/brevisurl/templatetags/brevisurltags.py
@@ -0,0 +1,11 @@
+from django import template
+
+from brevisurl import shorten_url as shorten_url_util, get_connection
+
+register = template.Library()
+
+
+@register.filter
+def shorten_url(original_url):
+ short_url = shorten_url_util(original_url, connection=get_connection(fail_silently=True))
+ return short_url.shortened_url if short_url is not None else original_url \ No newline at end of file
diff --git a/brevisurl/tests/__init__.py b/brevisurl/tests/__init__.py
new file mode 100644
index 0000000..729e40a
--- /dev/null
+++ b/brevisurl/tests/__init__.py
@@ -0,0 +1,6 @@
+from brevisurl.tests.test_brevisurl import *
+from brevisurl.tests.test_utils import *
+from brevisurl.tests.test_views import *
+from brevisurl.tests.test_models import *
+from brevisurl.tests.backends.test_local import *
+from brevisurl.tests.templatetags.test_brevisurltags import * \ No newline at end of file
diff --git a/brevisurl/tests/backends/__init__.py b/brevisurl/tests/backends/__init__.py
new file mode 100644
index 0000000..7a8337f
--- /dev/null
+++ b/brevisurl/tests/backends/__init__.py
@@ -0,0 +1 @@
+__author__ = 'char0n'
diff --git a/brevisurl/tests/backends/test_local.py b/brevisurl/tests/backends/test_local.py
new file mode 100644
index 0000000..8939314
--- /dev/null
+++ b/brevisurl/tests/backends/test_local.py
@@ -0,0 +1,55 @@
+from django.core.exceptions import ValidationError
+from django.test import TestCase
+from django.core.validators import URLValidator
+
+from brevisurl import get_connection
+from brevisurl.models import ShortUrl
+
+
+class TestLocalBrevisUrlBackend(TestCase):
+
+ def setUp(self):
+ self.connection = get_connection('brevisurl.backends.local.BrevisUrlBackend')
+
+ def test_shorten_url(self):
+ self.assertEqual(ShortUrl.objects.all().count(), 0)
+ original_url = 'http://www.codescale.net/'
+ short_url = self.connection.shorten_url(original_url)
+ self.assertEqual(ShortUrl.objects.all().count(), 1)
+ self.assertEqual(short_url.original_url, original_url)
+ self.assertRegexpMatches(short_url.shortened_url, URLValidator.regex)
+
+ def test_shorten_url_reuse_old(self):
+ original_url = 'http://www.codescale.net/'
+ self.assertEqual(ShortUrl.objects.all().count(), 0)
+ short_url = self.connection.shorten_url(original_url)
+ self.assertEqual(ShortUrl.objects.all().count(), 1)
+ self.assertEqual(short_url.original_url, original_url)
+ self.assertRegexpMatches(short_url.shortened_url, URLValidator.regex)
+ short_url = self.connection.shorten_url(original_url)
+ self.assertEqual(ShortUrl.objects.all().count(), 1)
+ self.assertEqual(short_url.original_url, original_url)
+ self.assertRegexpMatches(short_url.shortened_url, URLValidator.regex)
+
+ def test_shorten_url_create_new(self):
+ original_url = 'http://www.codescale.net/'
+ self.assertEqual(ShortUrl.objects.all().count(), 0)
+ short_url = self.connection.shorten_url(original_url)
+ self.assertEqual(ShortUrl.objects.all().count(), 1)
+ self.assertEqual(short_url.original_url, original_url)
+ self.assertRegexpMatches(short_url.shortened_url, URLValidator.regex)
+ original_url = 'http://www.codescale.net/en/company/'
+ short_url = self.connection.shorten_url(original_url)
+ self.assertEqual(ShortUrl.objects.all().count(), 2)
+ self.assertEqual(short_url.original_url, original_url)
+ self.assertRegexpMatches(short_url.shortened_url, URLValidator.regex)
+
+ def test_shorten_url_invalid_original_url(self):
+ with self.assertRaises(ValidationError):
+ self.connection.shorten_url('www.codescale.')
+ self.assertEqual(ShortUrl.objects.all().count(), 0)
+
+ def test_shorten_url_invalid_original_url_fail_silently(self):
+ self.connection.fail_silently = True
+ shorl_url = self.connection.shorten_url('www.codescale.')
+ self.assertIsNone(shorl_url) \ No newline at end of file
diff --git a/brevisurl/tests/templatetags/__init__.py b/brevisurl/tests/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/brevisurl/tests/templatetags/__init__.py
diff --git a/brevisurl/tests/templatetags/test_brevisurltags.py b/brevisurl/tests/templatetags/test_brevisurltags.py
new file mode 100644
index 0000000..de1ed00
--- /dev/null
+++ b/brevisurl/tests/templatetags/test_brevisurltags.py
@@ -0,0 +1,27 @@
+from django.core.validators import URLValidator
+from django.test import TestCase
+from django.template import Template, Context
+
+from brevisurl.models import ShortUrl
+
+
+class TestShortenUrlTag(TestCase):
+
+ def test_shorten_url_tag(self):
+ original_url = 'http://www.codescale.net/'
+ url = Template("""
+ {% load brevisurltags %}
+ {{ url|shorten_url }}
+ """).render(Context({'url': original_url})).strip()
+ self.assertEqual(ShortUrl.objects.all().count(), 1)
+ self.assertEqual(ShortUrl.objects.all()[0].original_url, original_url)
+ self.assertRegexpMatches(url, URLValidator.regex)
+
+ def test_shorten_url_tag_invalid_url(self):
+ original_url = 'www.codescale.'
+ url = Template("""
+ {% load brevisurltags %}
+ {{ url|shorten_url }}
+ """).render(Context({'url': original_url})).strip()
+ self.assertEqual(ShortUrl.objects.all().count(), 0)
+ self.assertEqual(url, original_url) \ No newline at end of file
diff --git a/brevisurl/tests/test_brevisurl.py b/brevisurl/tests/test_brevisurl.py
new file mode 100644
index 0000000..8780b05
--- /dev/null
+++ b/brevisurl/tests/test_brevisurl.py
@@ -0,0 +1,22 @@
+from django.utils import unittest
+
+from brevisurl import get_connection, shorten_url
+from brevisurl.backends.local import BrevisUrlBackend
+from brevisurl.backends.base import BaseBrevisUrlBackend
+
+
+class TestGetConnection(unittest.TestCase):
+
+ def test_get_default_connection(self):
+ connection = get_connection()
+ self.assertIsInstance(connection, BrevisUrlBackend)
+
+ def test_get_custom_connection(self):
+ base_connection = get_connection(backend='brevisurl.backends.base.BaseBrevisUrlBackend')
+ local_connection = get_connection(backend='brevisurl.backends.local.BrevisUrlBackend')
+ self.assertIsInstance(base_connection, BaseBrevisUrlBackend)
+ self.assertIsInstance(local_connection, BrevisUrlBackend)
+
+ def test_get_connection_non_existing_backend(self):
+ with self.assertRaises(AttributeError):
+ get_connection(backend='brevisurl.backends.local.NonExistingBackend') \ No newline at end of file
diff --git a/brevisurl/tests/test_models.py b/brevisurl/tests/test_models.py
new file mode 100644
index 0000000..6942124
--- /dev/null
+++ b/brevisurl/tests/test_models.py
@@ -0,0 +1,38 @@
+from django.core.exceptions import ValidationError
+from django.test import TestCase
+from django.contrib.sites.models import Site
+
+from brevisurl.models import ShortUrl
+from brevisurl import get_connection
+
+
+class TestModels(TestCase):
+
+ def test_model_save(self):
+ site = Site.objects.get_current()
+ connection = get_connection('brevisurl.backends.local.BrevisUrlBackend')
+ short_url = ShortUrl()
+ short_url.original_url = 'http://www.codescale.net/'
+ short_url.shortened_url = '{0}://{1}/12345'.format(connection.PROTOCOL, site.domain)
+ short_url.backend = connection.class_path
+ short_url.save()
+ self.assertIsNotNone(short_url.pk)
+
+ def test_model_save_invalid_original_url(self):
+ with self.assertRaises(ValidationError):
+ self.site = Site.objects.get_current()
+ self.connection = get_connection('brevisurl.backends.local.BrevisUrlBackend')
+ self.short_url = ShortUrl()
+ self.short_url.original_url = 'www.codescale.'
+ self.short_url.shortened_url = '{0}://{1}/12345'.format(self.connection.PROTOCOL, self.site.domain)
+ self.short_url.backend = self.connection.class_path
+ self.short_url.save()
+
+ def test_model_save_invalid_shortened_url(self):
+ with self.assertRaises(ValidationError):
+ connection = get_connection('brevisurl.backends.local.BrevisUrlBackend')
+ short_url = ShortUrl()
+ short_url.original_url = 'http://www.codescale.net/'
+ short_url.shortened_url = 'www.codescale.'
+ short_url.backend = connection.class_path
+ short_url.save() \ No newline at end of file
diff --git a/brevisurl/tests/test_utils.py b/brevisurl/tests/test_utils.py
new file mode 100644
index 0000000..6d31e77
--- /dev/null
+++ b/brevisurl/tests/test_utils.py
@@ -0,0 +1,22 @@
+from django.utils import unittest
+
+from brevisurl.utils import load_object
+from brevisurl.backends.local import BrevisUrlBackend
+
+class TestLoadObject(unittest.TestCase):
+
+ def test_load_valid_object(self):
+ klass = load_object('brevisurl.backends.local.BrevisUrlBackend')
+ self.assertEqual(klass, BrevisUrlBackend)
+
+ def test_load_import_error(self):
+ with self.assertRaises(ImportError):
+ load_object('brevisurl.local.BrevisUrlBackend')
+
+ def test_load_attribute_error(self):
+ with self.assertRaises(AttributeError):
+ load_object('brevisurl.backends.local.NonExistingBackend')
+
+ def test_load_value_error(self):
+ with self.assertRaises(ValueError):
+ load_object('brevisurl') \ No newline at end of file
diff --git a/brevisurl/tests/test_views.py b/brevisurl/tests/test_views.py
new file mode 100644
index 0000000..ca20c43
--- /dev/null
+++ b/brevisurl/tests/test_views.py
@@ -0,0 +1,28 @@
+from django.test import TestCase
+from django.test.client import Client
+from django.core.urlresolvers import reverse
+from django.contrib.sites.models import Site
+
+from brevisurl import get_connection
+from brevisurl.models import ShortUrl
+
+
+class TestBrevisUrlRedirectView(TestCase):
+
+ def setUp(self):
+ self.site = Site.objects.get_current()
+ self.connection = get_connection('brevisurl.backends.local.BrevisUrlBackend')
+ self.short_url = ShortUrl()
+ self.short_url.original_url = 'http://www.codescale.net/'
+ self.short_url.shortened_url = '{0}://{1}/12345'.format(self.connection.PROTOCOL, self.site.domain)
+ self.short_url.backend = self.connection.class_path
+ self.short_url.save()
+ self.client = Client()
+
+ def test_redirect(self):
+ response = self.client.get(reverse('brevisurl_redirect', kwargs={'token': 12345}))
+ self.assertEqual(response.status_code, 301)
+
+ def test_redirect_non_existing_token(self):
+ response = self.client.get(reverse('brevisurl_redirect', kwargs={'token': 54321}))
+ self.assertEqual(response.status_code, 404) \ No newline at end of file
diff --git a/brevisurl/urls.py b/brevisurl/urls.py
new file mode 100644
index 0000000..e2ca7e9
--- /dev/null
+++ b/brevisurl/urls.py
@@ -0,0 +1,8 @@
+from django.conf.urls import patterns, url
+
+from brevisurl import views
+
+
+urlpatterns = patterns('brevisurl.views',
+ url(r'^(?P<token>[a-zA-Z0-9]{5})$', views.BrevisUrlRedirectView.as_view(), name='brevisurl_redirect'),
+) \ No newline at end of file
diff --git a/brevisurl/utils.py b/brevisurl/utils.py
new file mode 100644
index 0000000..e0c3d52
--- /dev/null
+++ b/brevisurl/utils.py
@@ -0,0 +1,18 @@
+from django.utils import importlib
+
+
+def load_object(import_path):
+ """Util for importing objects from import path.
+
+ :param import_path: import path of object to be imported e.g. module.submodule.Class
+ :type import_path: string
+ :returns: imported object
+ :rtype: object
+ :raises: ValueError, ImportError, AttributeError
+
+ """
+ if not (isinstance(import_path, basestring) and '.' in import_path):
+ raise ValueError('There must be at least one dot in import path: "%s"', import_path)
+ module_name, object_name = import_path.rsplit('.', 1)
+ module = importlib.import_module(module_name)
+ return getattr(module, object_name) \ No newline at end of file
diff --git a/brevisurl/views.py b/brevisurl/views.py
new file mode 100644
index 0000000..21cd1a5
--- /dev/null
+++ b/brevisurl/views.py
@@ -0,0 +1,36 @@
+import logging
+
+from django.http import Http404
+from django.contrib.sites.models import Site
+from django.views.generic.base import RedirectView
+from django.core.exceptions import ImproperlyConfigured
+
+from brevisurl import get_connection
+from brevisurl.models import ShortUrl
+
+
+log = logging.getLogger(__name__)
+
+
+class BrevisUrlRedirectView(RedirectView):
+
+ def get_redirect_url(self, **kwargs):
+ try:
+ token = kwargs.pop('token')
+ except KeyError:
+ log.exception('Token not found in keyword arguments')
+ raise Http404
+ connection = get_connection('brevisurl.backends.local.BrevisUrlBackend')
+ protocol = connection.PROTOCOL
+ try:
+ site = Site.objects.get_current()
+ except ImproperlyConfigured:
+ log.exception('No site object configured for this django project')
+ raise Http404
+ try:
+ short_url = '{0}://{1}/{2}'.format(protocol, site.domain, token)
+ short_url_obj = ShortUrl.objects.get(backend=connection.class_path, shortened_url=short_url)
+ except ShortUrl.DoesNotExist:
+ log.exception('No shortened url found for backend: "%s" and token: "%s"', connection.class_path, token)
+ raise Http404
+ return short_url_obj.original_url \ No newline at end of file