From 6a8f1e1032ea9fdb1220a45a517bf5b1394d1751 Mon Sep 17 00:00:00 2001 From: grisel-davy Date: Sat, 2 Jan 2021 03:40:58 +0100 Subject: [PATCH] Prepare for bulk destinations redirect --- README.md | 7 ++- litl/forms.py | 17 +++--- litl/migrations/0004_auto_20210101_2227.py | 31 +++++++++++ litl/models.py | 29 +++++++--- litl/templates/add_slug.html | 14 +++++ litl/templates/base.html | 4 +- litl/templates/display.html | 2 +- litl/views.py | 62 +++++++++++++--------- 8 files changed, 119 insertions(+), 47 deletions(-) create mode 100644 litl/migrations/0004_auto_20210101_2227.py diff --git a/README.md b/README.md index f9ebdec..1504a63 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # litl -The world: Here is a free open source full-featured self-hosted URL shortener. - -Grizzly: *choke on PHP* I can do better! +The World: Here is a free open source full-featured self-hosted URL shortener. +Grizzly: I can do better! ## Description @@ -18,4 +17,4 @@ Litl est un racourcisseur d'URL qui vise à être simple d'utilisation, simple Idée de fonctionalité non essentielles: - * Permetre la création de *bulk URL*. On enregistre plusieurs url dans une seule url racourcie. Utiliser cette url racourcie réouvre toutes les urls dans différents onglets. \ No newline at end of file + * Permetre la création de *bulk URL*. On enregistre plusieurs url dans une seule url racourcie. Utiliser cette url racourcie réouvre toutes les urls dans différents onglets. diff --git a/litl/forms.py b/litl/forms.py index d4a9749..14a310a 100644 --- a/litl/forms.py +++ b/litl/forms.py @@ -9,13 +9,14 @@ from crispy_forms.helper import FormHelper from crispy_forms.layout import Submit from .models import Slug -class SlugAddForm(forms.ModelForm): - class Meta: - model = Slug - fields = ('destination','slug') +class SlugAddForm(forms.Form): + + destination = forms.CharField(label="Destination", max_length=300,required=True) + slug = forms.CharField(label="Slug", max_length=20,required=False) + bulk = forms.BooleanField(label="Bulk", required=False) - def __init__(self,*args,**kwargs): - super().__init__(*args,**kwargs) + def __init__(self, *args, **kwargs): + super(SlugAddForm, self).__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.add_input(Submit('submit','Shorten')) + self.helper.add_input(Submit('submit', 'Shorten', css_class='btn-primary')) + self.helper.form_method = 'POST' diff --git a/litl/migrations/0004_auto_20210101_2227.py b/litl/migrations/0004_auto_20210101_2227.py new file mode 100644 index 0000000..fdc8665 --- /dev/null +++ b/litl/migrations/0004_auto_20210101_2227.py @@ -0,0 +1,31 @@ +# Generated by Django 3.1.4 on 2021-01-01 22:27 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('litl', '0003_auto_20210101_1203'), + ] + + operations = [ + migrations.RemoveField( + model_name='slug', + name='destination', + ), + migrations.AddField( + model_name='slug', + name='bulk', + field=models.BooleanField(default=False, help_text='Generate a short URL leading to multiple destinations'), + ), + migrations.CreateModel( + name='Destination', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('destination', models.CharField(help_text='The destination URL, limited to 300 characters.', max_length=300)), + ('slug', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='litl.slug')), + ], + ), + ] diff --git a/litl/models.py b/litl/models.py index 83f1bea..d5773e8 100644 --- a/litl/models.py +++ b/litl/models.py @@ -7,17 +7,21 @@ from django.db import models class Slug(models.Model): + """Model of a slug. The slug represent the URL that leads to litl.*.*/slug + A slug has one user and can have from one to many destination. + """ slug = models.SlugField( max_length=20, null=False, blank=True, - help_text="Slug of the new URL. Up to 20 caracters including letters, number, underscores and hyphens.",) - destination = models.CharField( - max_length=300, - null=False, - blank=False, - help_text="The destination URL, limited to 300 characters.",) + help_text="Slug of the new URL. Up to 20 caracters including letters, number, underscores and hyphens.", ) + + bulk = models.BooleanField( + default=False, + help_text="Generate a short URL leading to multiple destinations", + ) + uid = models.CharField( max_length=100, null=True,) @@ -25,6 +29,17 @@ class Slug(models.Model): auto_now=True,) def __str__(self): - return '{} -> {:.15}...'.format(self.slug,self.destination) + return '{} -> ?'.format(self.slug) +class Destination(models.Model): + """Model of a destination. A destination is the end URL wanted by the user. + One Slug can have multiple destinations. + """ + slug = models.ForeignKey(Slug, on_delete=models.CASCADE) + + destination = models.CharField( + max_length=300, + null=False, + blank=False, + help_text="The destination URL, limited to 300 characters.",) diff --git a/litl/templates/add_slug.html b/litl/templates/add_slug.html index 830674b..7b25b72 100644 --- a/litl/templates/add_slug.html +++ b/litl/templates/add_slug.html @@ -3,5 +3,19 @@ {% load crispy_forms_tags %} {% block content %} + {% if form.errors %} + {% for field in form %} + {% for error in field.errors %} +
+ {{ error|escape }} +
+ {% endfor %} + {% endfor %} + {% for error in form.non_field_errors %} +
+ {{ error|escape }} +
+ {% endfor %} + {% endif %} {% crispy form %} {% endblock %} diff --git a/litl/templates/base.html b/litl/templates/base.html index 97f4626..60aff06 100644 --- a/litl/templates/base.html +++ b/litl/templates/base.html @@ -93,7 +93,7 @@ Code - - + + diff --git a/litl/templates/display.html b/litl/templates/display.html index fbc1b93..2b625f6 100644 --- a/litl/templates/display.html +++ b/litl/templates/display.html @@ -3,7 +3,7 @@ {% block content %}
-

{{slug.destination}}

+

{{destinations.first.destination}}

diff --git a/litl/views.py b/litl/views.py index 15df9eb..0c94b42 100644 --- a/litl/views.py +++ b/litl/views.py @@ -9,7 +9,7 @@ from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect from django.contrib import messages from .settings import TEMPLATES, RANDOM_SLUG_LEN, UID_LEN -from .models import Slug +from .models import Slug, Destination from .forms import SlugAddForm import random, string @@ -25,39 +25,50 @@ def index(request): def AddSlug(request): """Display the form for creating a new slug. """ - form = SlugAddForm(request.POST or None) - if request.method == 'POST' and form.is_valid(): - # Add slug if needed - if form.cleaned_data['slug'] == '': - rand_slug = ''.join(random.choice(ensemble) for _ in range(RANDOM_SLUG_LEN)) - while Slug.object.filter(slug = rand_slug).exists(): - rand_string = ''.join(random.choice(ensemble) for _ in range(RANDOM_SLUG_LEN)) - form.cleaned_data['slug'] = rand_string - else: - # If slug added by the user, check uniqueness - if Slug.objects.filter(slug=form.cleaned_data['slug']).exists(): - messages.error(request,"This slug is already in use.") - return render(request,'add_slug.html',{'form':form}) + if request.method == 'POST': + form = SlugAddForm(request.POST) + if form.is_valid(): + # Add slug if needed + if form.cleaned_data['slug'] == '': + rand_slug = ''.join(random.choice(ensemble) for _ in range(RANDOM_SLUG_LEN)) + while Slug.object.filter(slug = rand_slug).exists(): + rand_string = ''.join(random.choice(ensemble) for _ in range(RANDOM_SLUG_LEN)) + form.cleaned_data['slug'] = rand_string + else: + # If slug added by the user, check uniqueness + if Slug.objects.filter(slug=form.cleaned_data['slug']).exists(): + messages.error(request,"This slug is already in use.") + return render(request,'add_slug.html',{'form':form}) - # Generate uid - rand_uid = ''.join(random.choice(ensemble) for _ in range(UID_LEN)) - while Slug.objects.filter(uid = rand_uid).exists(): + # Generate uid rand_uid = ''.join(random.choice(ensemble) for _ in range(UID_LEN)) - - slug = form.save(commit=False) - slug.uid = rand_uid - slug.save() - return HttpResponseRedirect('display/{}'.format(slug.slug)) - + while Slug.objects.filter(uid = rand_uid).exists(): + rand_uid = ''.join(random.choice(ensemble) for _ in range(UID_LEN)) + + slug = Slug(slug = form.cleaned_data['slug'], + uid = rand_uid, + bulk = form.cleaned_data['bulk'],) + slug.save() + + destination = Destination(slug = slug, + destination = form.cleaned_data['destination'],) + destination.save() + + return HttpResponseRedirect('display/{}'.format(slug.slug)) + else: + form = SlugAddForm() + # First call, return empty form return render(request,'add_slug.html',{'form':form}) def display(request, slug_got): slug = Slug.objects.filter(slug = slug_got).all() + if slug.count()!=1: return render(request,'unknown.html',{'slug':slug_got}) else: - context = {'slug':slug.get(), 'request': request} + destinations = Destination.objects.filter(slug=slug.get()).all() + context = {'slug':slug.get(), 'destinations':destinations, 'request': request} return render(request,'display.html',context) def redirect(request,slug_got): @@ -66,5 +77,6 @@ def redirect(request,slug_got): context = {'slug':slug_got,'request': request} return render(request,'unknown.html',context) else: - return HttpResponsePermanentRedirect(slug.get().destination) + destinations = Destination.objects.filter(slug=slug.get()).all() + return HttpResponsePermanentRedirect(destinations.first().destination)