diff --git a/config/settings.py b/config/settings.py index 8880298..2f6b6e0 100644 --- a/config/settings.py +++ b/config/settings.py @@ -45,6 +45,7 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", "core.apps.CoreConfig", "dashboard.apps.DashboardConfig", + "shorturls.apps.ShorturlsConfig", ] MIDDLEWARE = [ diff --git a/config/urls.py b/config/urls.py index 222ea61..93b5eab 100644 --- a/config/urls.py +++ b/config/urls.py @@ -23,4 +23,5 @@ urlpatterns = [ path("", include("core.urls", namespace="core")), path("accounts/", include("accounts.urls")), path("dashboard/", include("dashboard.urls", namespace="dashboard")), + path("", include("shorturls.urls", namespace="shorturls")), ] diff --git a/pyproject.toml b/pyproject.toml index 4eaf014..9bb9c46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tdg-rocks" -version = "0.1.0" +version = "0.2.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.13" diff --git a/shorturls/__init__.py b/shorturls/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shorturls/admin.py b/shorturls/admin.py new file mode 100644 index 0000000..094ea45 --- /dev/null +++ b/shorturls/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from .models import ShortURL + + +@admin.register(ShortURL) +class ShortURLAdmin(admin.ModelAdmin): + list_display = ["long_url", "short_url", "followed"] diff --git a/shorturls/apps.py b/shorturls/apps.py new file mode 100644 index 0000000..b15790d --- /dev/null +++ b/shorturls/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ShorturlsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "shorturls" diff --git a/shorturls/migrations/0001_initial.py b/shorturls/migrations/0001_initial.py new file mode 100644 index 0000000..b794ddd --- /dev/null +++ b/shorturls/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.7 on 2025-11-06 12:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="ShortURL", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("followed", models.PositiveIntegerField(default=0)), + ("long_url", models.TextField()), + ("short_url", models.CharField(blank=True, max_length=20, unique=True)), + ], + options={ + "ordering": ["created"], + }, + ), + ] diff --git a/shorturls/migrations/__init__.py b/shorturls/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shorturls/models.py b/shorturls/models.py new file mode 100644 index 0000000..e9cdc4a --- /dev/null +++ b/shorturls/models.py @@ -0,0 +1,18 @@ +from django.db import models + +from .utils import create_short_url + + +class ShortURL(models.Model): + created = models.DateTimeField(auto_now_add=True) + followed = models.PositiveIntegerField(default=0) + long_url = models.TextField() + short_url = models.CharField(max_length=20, unique=True, blank=True) + + def save(self, *args, **kwargs): + if not self.short_url: + self.short_url = create_short_url(self) + super().save() + + class Meta: + ordering = ["created"] diff --git a/shorturls/tests.py b/shorturls/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/shorturls/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/shorturls/urls.py b/shorturls/urls.py new file mode 100644 index 0000000..c60903e --- /dev/null +++ b/shorturls/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +app_name = "shorturls" + +urlpatterns = [ + path("/", views.ShorturlsRedirectView.as_view(), name="redirect"), +] diff --git a/shorturls/utils.py b/shorturls/utils.py new file mode 100644 index 0000000..6e6fe21 --- /dev/null +++ b/shorturls/utils.py @@ -0,0 +1,19 @@ +from random import choice +from string import ascii_letters, digits + +from django.conf import settings + +SIZE = getattr(settings, "MAXIMUM_URL_CHARS", 10) +AVAILABLE_CHARS = ascii_letters + digits + + +def create_random_code(chars=AVAILABLE_CHARS): + return "".join([choice(chars) for _ in range(SIZE)]) + + +def create_short_url(model_instance): + random_code = create_random_code() + model_class = model_instance.__class__ + if model_class.objects.filter(short_url=random_code).exists(): + return create_short_url(model_instance) + return random_code diff --git a/shorturls/views.py b/shorturls/views.py new file mode 100644 index 0000000..8341564 --- /dev/null +++ b/shorturls/views.py @@ -0,0 +1,12 @@ +from django.shortcuts import get_object_or_404 +from django.views.generic.base import RedirectView + +from .models import ShortURL + + +class ShorturlsRedirectView(RedirectView): + def get_redirect_url(self, *args, **kwargs): + url = get_object_or_404(ShortURL, short_url=kwargs["shorturl"]) + url.followed += 1 + url.save() + return url.long_url diff --git a/uv.lock b/uv.lock index 77ed457..c9d2032 100644 --- a/uv.lock +++ b/uv.lock @@ -57,7 +57,7 @@ wheels = [ [[package]] name = "tdg-rocks" -version = "0.1.0" +version = "0.2.0" source = { virtual = "." } dependencies = [ { name = "django" },