diff --git a/qlinks/fields.py b/qlinks/fields.py new file mode 100644 index 0000000..efb0a86 --- /dev/null +++ b/qlinks/fields.py @@ -0,0 +1,19 @@ +from django.core.validators import RegexValidator +from django.db import models +from django.forms import SlugField + +URL_SLUG_VALIDATOR = RegexValidator(r'^[-a-zA-Z0-9_/]+\Z') + + +class ShortURLFormField(SlugField): + default_validators = [URL_SLUG_VALIDATOR] + + +class ShortURLField(models.SlugField): + default_validators = [URL_SLUG_VALIDATOR] + + def formfield(self, **kwargs): + return super().formfield(**{ + 'form_class': ShortURLFormField, + **kwargs, + }) diff --git a/qlinks/migrations/0003_short_allow_slash.py b/qlinks/migrations/0003_short_allow_slash.py new file mode 100644 index 0000000..657480c --- /dev/null +++ b/qlinks/migrations/0003_short_allow_slash.py @@ -0,0 +1,19 @@ +from django.db import migrations + +import qlinks.fields + + +class Migration(migrations.Migration): + dependencies = [ + ('qlinks', '0002_link_next_check'), + ] + + operations = [ + migrations.AlterField( + model_name='link', + name='short', + field=qlinks.fields.ShortURLField(blank=True, + help_text='the part of URL after / that will redirect to the long URL, e.g. https://my.short.link/[slug]', + max_length=64, unique=True, verbose_name='short link slug'), + ), + ] diff --git a/qlinks/models.py b/qlinks/models.py index 7f3c8f5..f85ab18 100644 --- a/qlinks/models.py +++ b/qlinks/models.py @@ -8,6 +8,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from qlinks.cdn_cache import cdn_cache +from qlinks.fields import ShortURLField from qlinks.health import check_url @@ -18,9 +19,9 @@ def compute_next_check(): class Link(models.Model): - short = models.SlugField(max_length=64, verbose_name=_('short link slug'), unique=True, blank=True, - help_text=_('the part of URL after / that will redirect to the long URL, ' - 'e.g. https://my.short.link/[slug]')) + short = ShortURLField(max_length=64, verbose_name=_('short link slug'), unique=True, blank=True, + help_text=_('the part of URL after / that will redirect to the long URL, ' + 'e.g. https://my.short.link/[slug]')) long = models.URLField(max_length=512, verbose_name=_('long URL')) created_on = models.DateTimeField(verbose_name=_('creation time'), auto_now_add=True) updated_on = models.DateTimeField(verbose_name=_('last update'), default=timezone.now) diff --git a/qlinks/urls/short.py b/qlinks/urls/short.py index 00b628d..952c7e2 100644 --- a/qlinks/urls/short.py +++ b/qlinks/urls/short.py @@ -5,6 +5,6 @@ from django.urls import path from qlinks.views import short_link urlpatterns = [ - path('', partial(short_link, slug=''), name='short_link'), - path('<slug>', short_link, name='short_link'), + path('', partial(short_link, path=''), name='short_link'), + path('<path:path>', short_link, name='short_link'), ] diff --git a/qlinks/views.py b/qlinks/views.py index b3255dc..061477c 100644 --- a/qlinks/views.py +++ b/qlinks/views.py @@ -5,8 +5,8 @@ from django.shortcuts import get_object_or_404 from qlinks.models import Link -def short_link(request, slug): - link = get_object_or_404(Link.objects.values_list('long', flat=True), short=slug) +def short_link(request, path): + link = get_object_or_404(Link.objects.values_list('long', flat=True), short=path) return HttpResponseRedirect(link, headers={ 'X-Powered-By': settings.QLINKS_POWERED_BY })