aboutsummaryrefslogtreecommitdiffstats
path: root/brevisurl/backends/local.py
blob: 34d2d83ee497581bb46277b9193304a229429753 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import math
import random
import logging

from django.contrib.sites.models import Site
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import IntegrityError, transaction

import brevisurl.settings
from brevisurl import Error
from brevisurl.backends.base import BaseBrevisUrlBackend
from brevisurl.models import ShortUrl
from brevisurl.utils import absurl


log = logging.getLogger(__name__)


class TokensExhaustedError(Error):
    """Exception is raised when tokens are exhausted."""


class BrevisUrlBackend(BaseBrevisUrlBackend):

    def shorten_url(self, original_url):
        """
        :raises: ImproperlyConfigured, django.core.exceptions.ValidationError
        :raises: brevisurl.backends.local.TokensExhaustedError
        """

        if self.kwargs.get('domain') is not None:
            # Domain is present in keyword arguments supplied by constructor.
            domain = self.kwargs.get('domain')
        elif brevisurl.settings.LOCAL_BACKEND_DOMAIN is not None:
            # Domain is defined in settings.
            domain = brevisurl.settings.LOCAL_BACKEND_DOMAIN
        else:
            # Domain is taken from django site framework.
            domain = Site.objects.get_current().domain

        try:
            shortened_url = self.__generate_shortened_url(domain)
            try:
                short_url, created = ShortUrl.objects.get_or_create(backend=self.class_path,
                                                                    original_url=original_url,
                                                                    defaults={'shortened_url': shortened_url})
                if created:
                     log.info('Url "%s" shortened to "%s"', original_url, shortened_url)
                else:
                     log.info('Url "%s" already shortened to "%s"', original_url, short_url.shortened_url)
                return short_url
            except (IntegrityError, ValidationError) as e:
                # Check if the error is an URL validation error.
                if e.message_dict.has_key('original_url'):
                    raise

                # Generate another token.
                self.__check_tokens_exhausted()
                while True:
                    shortened_url = self.__generate_shortened_url(domain)
                    sid = transaction.savepoint()
                    try:
                        short_url = ShortUrl.objects.create(backend=self.class_path,
                                                            original_url=original_url,
                                                            shortened_url=shortened_url)
                        log.info('Url "%s" shortened to "%s"', original_url, shortened_url)
                        transaction.savepoint_commit()
                        return short_url
                    except (IntegrityError, ValidationError) as e:
                        transaction.savepoint_rollback(sid)
                        self.__check_tokens_exhausted()
        except Exception:
            if self.fail_silently:
                return None
            else:
                log.exception('Unknown exception raised while shortening url "%s"', original_url)
                raise

    def __check_tokens_exhausted(self):
        chars = brevisurl.settings.LOCAL_BACKEND_TOKEN_CHARS
        size = brevisurl.settings.LOCAL_BACKEND_TOKEN_LENGTH
        if ShortUrl.objects.count() >= math.pow(len(chars), size):
            raise TokensExhaustedError('Consider incrementing the token length or change the char list')

    def __generate_shortened_url(self, domain):
        chars = brevisurl.settings.LOCAL_BACKEND_TOKEN_CHARS
        size = brevisurl.settings.LOCAL_BACKEND_TOKEN_LENGTH
        random.shuffle(chars)
        token = ''.join([random.choice(chars) for x in range(size)])
        shortened_url = absurl(domain=domain,
                               path=reverse('brevisurl_redirect',
                                            kwargs={'token': token}))
        return shortened_url